summaryrefslogtreecommitdiffstats
path: root/sound/usb/pcm.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2021-09-29 10:08:43 +0200
committerTakashi Iwai <tiwai@suse.de>2021-09-30 13:55:24 +0200
commitd5f871f89e21bb71827ea57bd484eedea85839a0 (patch)
tree2b9790fde0ddfbf638ebb3ce25d883da4c39fd45 /sound/usb/pcm.c
parentALSA: usb-audio: Add spinlock to stop_urbs() (diff)
downloadlinux-d5f871f89e21bb71827ea57bd484eedea85839a0.tar.xz
linux-d5f871f89e21bb71827ea57bd484eedea85839a0.zip
ALSA: usb-audio: Improved lowlatency playback support
This is another attempt to improve further the handling of playback stream in the low latency mode. The latest workaround in commit 4267c5a8f313 ("ALSA: usb-audio: Work around for XRUN with low latency playback") revealed that submitting URBs forcibly in advance may trigger XRUN easily. In the classical mode, this problem was avoided by practically delaying the submission of the actual data with the pre-submissions of silent data before triggering the stream start. But that is exactly what we want to avoid. Now, in this patch, instead of the previous workaround, we take a similar approach as used in the implicit feedback mode. The URBs are queued at the PCM trigger start like before, but we check whether the buffer has been already filled enough before each submission, and stop queuing if the data overcomes the threshold. The remaining URBs are kept in the ready list, and they will be retrieved in the URB complete callback of other (already queued) URBs. In the complete callback, we try to fill the data and submit as much as possible again. When there is no more available in-flight URBs that may handle the pending data, we'll check in PCM ack callback and submit and process URBs there in addition. In this way, the amount of in-flight URBs may vary dynamically and flexibly depending on the available data without hitting XRUN. The following things are changed to achieve the behavior above: * The endpoint prepare callback is changed to return an error code; when there is no enough data available, it may return -EAGAIN. Currently only prepare_playback_urb() returns the error. The evaluation of the available data is a bit messy here; we can't check with snd_pcm_avail() at the point of prepare callback (as runtime->status->hwptr hasn't been updated yet), hence we manually estimate the appl_ptr and compare with the internal hwptr_done to calculate the available frames. * snd_usb_endpoint_start() doesn't submit full URBs if the prepare callback returns -EAGAIN, and puts the remaining URBs to the ready list for the later submission. * snd_complete_urb() treats the URBs in the low-latency mode similarly like the implicit feedback mode, and submissions are done in (now exported) snd_usb_queue_pending_output_urbs(). * snd_usb_queue_pending_output_urbs() again checks the error value from the prepare callback. If it's -EAGAIN for the normal stream (i.e. not implicit feedback mode), we push it back to the ready list again. * PCM ack callback is introduced for the playback stream, and it calls snd_usb_queue_pending_output_urbs() if there is no in-flight URB while the stream is running. This corresponds to the case where the system needs the appl_ptr update for re-submitting a new URB. * snd_usb_queue_pending_output_urbs() and the prepare EP callback receive in_stream_lock argument, which is a bool flag indicating the call path from PCM ack. It's needed for avoiding the deadlock of snd_pcm_period_elapsed() calls. * Set the new SNDRV_PCM_INFO_EXPLICIT_SYNC flag when the new low-latency mode is deployed. This assures catching each applptr update even in the mmap mode. Fixes: 4267c5a8f313 ("ALSA: usb-audio: Work around for XRUN with low latency playback") Link: https://lore.kernel.org/r/20210929080844.11583-9-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb/pcm.c')
-rw-r--r--sound/usb/pcm.c102
1 files changed, 81 insertions, 21 deletions
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index 8ad48c35c559..d5a14e5b9ad3 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -598,9 +598,6 @@ static int lowlatency_playback_available(struct snd_pcm_runtime *runtime,
/* implicit feedback mode has own operation mode */
if (snd_usb_endpoint_implicit_feedback_sink(subs->data_endpoint))
return false;
- /* too short periods? */
- if (subs->data_endpoint->nominal_queue_size >= subs->buffer_bytes)
- return false;
return true;
}
@@ -1095,6 +1092,10 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
int ret;
runtime->hw = snd_usb_hardware;
+ /* need an explicit sync to catch applptr update in low-latency mode */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK &&
+ as->chip->lowlatency)
+ runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC;
runtime->private_data = subs;
subs->pcm_substream = substream;
/* runtime PM is also done there */
@@ -1347,44 +1348,66 @@ static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs,
return bytes;
}
-static void prepare_playback_urb(struct snd_usb_substream *subs,
- struct urb *urb)
+static int prepare_playback_urb(struct snd_usb_substream *subs,
+ struct urb *urb,
+ bool in_stream_lock)
{
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
struct snd_usb_endpoint *ep = subs->data_endpoint;
struct snd_urb_ctx *ctx = urb->context;
- unsigned int counts, frames, bytes;
+ unsigned int frames, bytes;
+ int counts;
+ unsigned int transfer_done, frame_limit, avail = 0;
int i, stride, period_elapsed = 0;
unsigned long flags;
+ int err = 0;
stride = ep->stride;
frames = 0;
ctx->queued = 0;
urb->number_of_packets = 0;
+
spin_lock_irqsave(&subs->lock, flags);
- subs->frame_limit += ep->max_urb_frames;
+ frame_limit = subs->frame_limit + ep->max_urb_frames;
+ transfer_done = subs->transfer_done;
+
+ if (subs->lowlatency_playback &&
+ runtime->status->state != SNDRV_PCM_STATE_DRAINING) {
+ unsigned int hwptr = subs->hwptr_done / stride;
+
+ /* calculate the byte offset-in-buffer of the appl_ptr */
+ avail = (runtime->control->appl_ptr - runtime->hw_ptr_base)
+ % runtime->buffer_size;
+ if (avail <= hwptr)
+ avail += runtime->buffer_size;
+ avail -= hwptr;
+ }
+
for (i = 0; i < ctx->packets; i++) {
- counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0);
+ counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, avail);
+ if (counts < 0)
+ break;
/* set up descriptor */
urb->iso_frame_desc[i].offset = frames * stride;
urb->iso_frame_desc[i].length = counts * stride;
frames += counts;
+ avail -= counts;
urb->number_of_packets++;
- subs->transfer_done += counts;
- if (subs->transfer_done >= runtime->period_size) {
- subs->transfer_done -= runtime->period_size;
- subs->frame_limit = 0;
+ transfer_done += counts;
+ if (transfer_done >= runtime->period_size) {
+ transfer_done -= runtime->period_size;
+ frame_limit = 0;
period_elapsed = 1;
if (subs->fmt_type == UAC_FORMAT_TYPE_II) {
- if (subs->transfer_done > 0) {
+ if (transfer_done > 0) {
/* FIXME: fill-max mode is not
* supported yet */
- frames -= subs->transfer_done;
- counts -= subs->transfer_done;
+ frames -= transfer_done;
+ counts -= transfer_done;
urb->iso_frame_desc[i].length =
counts * stride;
- subs->transfer_done = 0;
+ transfer_done = 0;
}
i++;
if (i < ctx->packets) {
@@ -1398,13 +1421,19 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
}
}
/* finish at the period boundary or after enough frames */
- if ((period_elapsed ||
- subs->transfer_done >= subs->frame_limit) &&
+ if ((period_elapsed || transfer_done >= frame_limit) &&
!snd_usb_endpoint_implicit_feedback_sink(ep))
break;
}
- bytes = frames * stride;
+ if (!frames) {
+ err = -EAGAIN;
+ goto unlock;
+ }
+
+ bytes = frames * stride;
+ subs->transfer_done = transfer_done;
+ subs->frame_limit = frame_limit;
if (unlikely(ep->cur_format == SNDRV_PCM_FORMAT_DSD_U16_LE &&
subs->cur_audiofmt->dsd_dop)) {
fill_playback_urb_dsd_dop(subs, urb, bytes);
@@ -1434,10 +1463,19 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
subs->period_elapsed_pending = 1;
period_elapsed = 0;
}
+
+ unlock:
spin_unlock_irqrestore(&subs->lock, flags);
+ if (err < 0)
+ return err;
urb->transfer_buffer_length = bytes;
- if (period_elapsed)
- snd_pcm_period_elapsed(subs->pcm_substream);
+ if (period_elapsed) {
+ if (in_stream_lock)
+ snd_pcm_period_elapsed_under_stream_lock(subs->pcm_substream);
+ else
+ snd_pcm_period_elapsed(subs->pcm_substream);
+ }
+ return 0;
}
/*
@@ -1469,6 +1507,27 @@ static void retire_playback_urb(struct snd_usb_substream *subs,
snd_pcm_period_elapsed(subs->pcm_substream);
}
+/* PCM ack callback for the playback stream;
+ * this plays a role only when the stream is running in low-latency mode.
+ */
+static int snd_usb_pcm_playback_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+ struct snd_usb_endpoint *ep;
+
+ if (!subs->lowlatency_playback || !subs->running)
+ return 0;
+ ep = subs->data_endpoint;
+ if (!ep)
+ return 0;
+ /* When no more in-flight URBs available, try to process the pending
+ * outputs here
+ */
+ if (!ep->active_mask)
+ snd_usb_queue_pending_output_urbs(ep, true);
+ return 0;
+}
+
static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream,
int cmd)
{
@@ -1572,6 +1631,7 @@ static const struct snd_pcm_ops snd_usb_playback_ops = {
.trigger = snd_usb_substream_playback_trigger,
.sync_stop = snd_usb_pcm_sync_stop,
.pointer = snd_usb_pcm_pointer,
+ .ack = snd_usb_pcm_playback_ack,
};
static const struct snd_pcm_ops snd_usb_capture_ops = {