ALSA: usb-audio: Check available frames for the next packet size
authorTakashi Iwai <tiwai@suse.de>
Wed, 29 Sep 2021 08:08:41 +0000 (10:08 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Dec 2021 08:04:36 +0000 (09:04 +0100)
commit d215f63d49da9a8803af3e81acd6cad743686573 upstream.

This is yet more preparation for the upcoming changes.

Extend snd_usb_endpoint_next_packet_size() to check the available
frames and return -EAGAIN if the next packet size is equal or exceeds
the given size.  This will be needed for avoiding XRUN during the low
latency operation.

As of this patch, avail=0 is passed, i.e. the check is skipped and no
behavior change.

Link: https://lore.kernel.org/r/20210929080844.11583-7-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
sound/usb/endpoint.c
sound/usb/endpoint.h
sound/usb/pcm.c

index 8e164d7..1f757a7 100644 (file)
@@ -148,18 +148,23 @@ int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep)
  * This won't be used for implicit feedback which takes the packet size
  * returned from the sync source
  */
-static int slave_next_packet_size(struct snd_usb_endpoint *ep)
+static int slave_next_packet_size(struct snd_usb_endpoint *ep,
+                                 unsigned int avail)
 {
        unsigned long flags;
+       unsigned int phase;
        int ret;
 
        if (ep->fill_max)
                return ep->maxframesize;
 
        spin_lock_irqsave(&ep->lock, flags);
-       ep->phase = (ep->phase & 0xffff)
-               + (ep->freqm << ep->datainterval);
-       ret = min(ep->phase >> 16, ep->maxframesize);
+       phase = (ep->phase & 0xffff) + (ep->freqm << ep->datainterval);
+       ret = min(phase >> 16, ep->maxframesize);
+       if (avail && ret >= avail)
+               ret = -EAGAIN;
+       else
+               ep->phase = phase;
        spin_unlock_irqrestore(&ep->lock, flags);
 
        return ret;
@@ -169,20 +174,25 @@ static int slave_next_packet_size(struct snd_usb_endpoint *ep)
  * Return the number of samples to be sent in the next packet
  * for adaptive and synchronous endpoints
  */
-static int next_packet_size(struct snd_usb_endpoint *ep)
+static int next_packet_size(struct snd_usb_endpoint *ep, unsigned int avail)
 {
+       unsigned int sample_accum;
        int ret;
 
        if (ep->fill_max)
                return ep->maxframesize;
 
-       ep->sample_accum += ep->sample_rem;
-       if (ep->sample_accum >= ep->pps) {
-               ep->sample_accum -= ep->pps;
+       sample_accum += ep->sample_rem;
+       if (sample_accum >= ep->pps) {
+               sample_accum -= ep->pps;
                ret = ep->packsize[1];
        } else {
                ret = ep->packsize[0];
        }
+       if (avail && ret >= avail)
+               ret = -EAGAIN;
+       else
+               ep->sample_accum = sample_accum;
 
        return ret;
 }
@@ -190,16 +200,27 @@ static int next_packet_size(struct snd_usb_endpoint *ep)
 /*
  * snd_usb_endpoint_next_packet_size: Return the number of samples to be sent
  * in the next packet
+ *
+ * If the size is equal or exceeds @avail, don't proceed but return -EAGAIN
+ * Exception: @avail = 0 for skipping the check.
  */
 int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep,
-                                     struct snd_urb_ctx *ctx, int idx)
+                                     struct snd_urb_ctx *ctx, int idx,
+                                     unsigned int avail)
 {
-       if (ctx->packet_size[idx])
-               return ctx->packet_size[idx];
-       else if (ep->sync_source)
-               return slave_next_packet_size(ep);
+       unsigned int packet;
+
+       packet = ctx->packet_size[idx];
+       if (packet) {
+               if (avail && packet >= avail)
+                       return -EAGAIN;
+               return packet;
+       }
+
+       if (ep->sync_source)
+               return slave_next_packet_size(ep, avail);
        else
-               return next_packet_size(ep);
+               return next_packet_size(ep, avail);
 }
 
 static void call_retire_callback(struct snd_usb_endpoint *ep,
@@ -263,7 +284,7 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep,
                unsigned int length;
                int counts;
 
-               counts = snd_usb_endpoint_next_packet_size(ep, ctx, i);
+               counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0);
                length = counts * ep->stride; /* number of silent bytes */
                offset = offs * ep->stride + extra * i;
                urb->iso_frame_desc[i].offset = offset;
index a1099ec..1f1a725 100644 (file)
@@ -46,6 +46,7 @@ void snd_usb_endpoint_free_all(struct snd_usb_audio *chip);
 
 int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep);
 int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep,
-                                     struct snd_urb_ctx *ctx, int idx);
+                                     struct snd_urb_ctx *ctx, int idx,
+                                     unsigned int avail);
 
 #endif /* __USBAUDIO_ENDPOINT_H */
index ec7eeb1..8ad48c3 100644 (file)
@@ -1365,7 +1365,7 @@ static void prepare_playback_urb(struct snd_usb_substream *subs,
        spin_lock_irqsave(&subs->lock, flags);
        subs->frame_limit += ep->max_urb_frames;
        for (i = 0; i < ctx->packets; i++) {
-               counts = snd_usb_endpoint_next_packet_size(ep, ctx, i);
+               counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0);
                /* set up descriptor */
                urb->iso_frame_desc[i].offset = frames * stride;
                urb->iso_frame_desc[i].length = counts * stride;