aboutsummaryrefslogtreecommitdiffstats
path: root/sound/usb/endpoint.c
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2022-05-16 12:48:07 +0200
committerTakashi Iwai <tiwai@suse.de>2022-05-16 12:49:03 +0200
commitc11117b634f4f832c4420d3cf41c44227f140ce1 (patch)
treee5f1db89edb15c8c82bb7efad9fd8c4090ef3498 /sound/usb/endpoint.c
parentALSA: hda: cs35l41: Add Amp Name based on channel and index (diff)
downloadlinux-dev-c11117b634f4f832c4420d3cf41c44227f140ce1.tar.xz
linux-dev-c11117b634f4f832c4420d3cf41c44227f140ce1.zip
ALSA: usb-audio: Refcount multiple accesses on the single clock
When a clock source is connected to multiple nodes / endpoints, the current USB-audio driver tries to set up at each time one of them is configured. Although it reads the current rate and updates only if it differs, some devices seem unhappy with this behavior and spew the errors when reading/updating the rate unnecessarily. This patch tries to reduce the redundant clock setup by introducing a refcount for each clock source. When the stream is actually running, a clock rate is "locked", and it bypasses the clock and/or refuse to change any longer. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=215934 Link: https://lore.kernel.org/r/20220516104807.16482-1-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/usb/endpoint.c')
-rw-r--r--sound/usb/endpoint.c90
1 files changed, 81 insertions, 9 deletions
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 743b8287cfcd..df5a70013a85 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -35,6 +35,14 @@ struct snd_usb_iface_ref {
struct list_head list;
};
+/* clock refcounting */
+struct snd_usb_clock_ref {
+ unsigned char clock;
+ atomic_t locked;
+ int rate;
+ struct list_head list;
+};
+
/*
* snd_usb_endpoint is a model that abstracts everything related to an
* USB endpoint and its streaming.
@@ -591,6 +599,25 @@ iface_ref_find(struct snd_usb_audio *chip, int iface)
return ip;
}
+/* Similarly, a refcount object for clock */
+static struct snd_usb_clock_ref *
+clock_ref_find(struct snd_usb_audio *chip, int clock)
+{
+ struct snd_usb_clock_ref *ref;
+
+ list_for_each_entry(ref, &chip->clock_ref_list, list)
+ if (ref->clock == clock)
+ return ref;
+
+ ref = kzalloc(sizeof(*ref), GFP_KERNEL);
+ if (!ref)
+ return NULL;
+ ref->clock = clock;
+ atomic_set(&ref->locked, 0);
+ list_add_tail(&ref->list, &chip->clock_ref_list);
+ return ref;
+}
+
/*
* Get the existing endpoint object corresponding EP
* Returns NULL if not present.
@@ -768,6 +795,14 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
goto unlock;
}
+ if (fp->protocol != UAC_VERSION_1) {
+ ep->clock_ref = clock_ref_find(chip, fp->clock);
+ if (!ep->clock_ref) {
+ ep = NULL;
+ goto unlock;
+ }
+ }
+
ep->cur_audiofmt = fp;
ep->cur_channels = fp->channels;
ep->cur_rate = params_rate(params);
@@ -777,7 +812,6 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
ep->cur_period_frames = params_period_size(params);
ep->cur_period_bytes = ep->cur_period_frames * ep->cur_frame_bytes;
ep->cur_buffer_periods = params_periods(params);
- ep->cur_clock = fp->clock;
if (ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
endpoint_set_syncinterval(chip, ep);
@@ -894,8 +928,8 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip,
ep->altsetting = 0;
ep->cur_audiofmt = NULL;
ep->cur_rate = 0;
- ep->cur_clock = 0;
ep->iface_ref = NULL;
+ ep->clock_ref = NULL;
usb_audio_dbg(chip, "EP 0x%x closed\n", ep->ep_num);
}
mutex_unlock(&chip->mutex);
@@ -907,6 +941,8 @@ void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep)
ep->need_setup = true;
if (ep->iface_ref)
ep->iface_ref->need_setup = true;
+ if (ep->clock_ref)
+ ep->clock_ref->rate = 0;
}
/*
@@ -1314,6 +1350,33 @@ static int snd_usb_endpoint_set_params(struct snd_usb_audio *chip,
return 0;
}
+static int init_sample_rate(struct snd_usb_audio *chip,
+ struct snd_usb_endpoint *ep)
+{
+ struct snd_usb_clock_ref *clock = ep->clock_ref;
+ int err;
+
+ if (clock) {
+ if (atomic_read(&clock->locked))
+ return 0;
+ if (clock->rate == ep->cur_rate)
+ return 0;
+ if (clock->rate && clock->rate != ep->cur_rate) {
+ usb_audio_dbg(chip, "Mismatched sample rate %d vs %d for EP 0x%x\n",
+ clock->rate, ep->cur_rate, ep->ep_num);
+ return -EINVAL;
+ }
+ }
+
+ err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, ep->cur_rate);
+ if (err < 0)
+ return err;
+
+ if (clock)
+ clock->rate = ep->cur_rate;
+ return 0;
+}
+
/*
* snd_usb_endpoint_configure: Configure the endpoint
*
@@ -1343,8 +1406,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
* to update at each EP configuration
*/
if (ep->cur_audiofmt->protocol == UAC_VERSION_1) {
- err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt,
- ep->cur_rate);
+ err = init_sample_rate(chip, ep);
if (err < 0)
goto unlock;
}
@@ -1374,7 +1436,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
if (err < 0)
goto unlock;
- err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, ep->cur_rate);
+ err = init_sample_rate(chip, ep);
if (err < 0)
goto unlock;
@@ -1407,15 +1469,15 @@ unlock:
/* get the current rate set to the given clock by any endpoint */
int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock)
{
- struct snd_usb_endpoint *ep;
+ struct snd_usb_clock_ref *ref;
int rate = 0;
if (!clock)
return 0;
mutex_lock(&chip->mutex);
- list_for_each_entry(ep, &chip->ep_list, list) {
- if (ep->cur_clock == clock && ep->cur_rate) {
- rate = ep->cur_rate;
+ list_for_each_entry(ref, &chip->clock_ref_list, list) {
+ if (ref->clock == clock) {
+ rate = ref->rate;
break;
}
}
@@ -1456,6 +1518,9 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
if (atomic_inc_return(&ep->running) != 1)
return 0;
+ if (ep->clock_ref)
+ atomic_inc(&ep->clock_ref->locked);
+
ep->active_mask = 0;
ep->unlink_mask = 0;
ep->phase = 0;
@@ -1565,6 +1630,9 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending)
if (ep->sync_source)
WRITE_ONCE(ep->sync_source->sync_sink, NULL);
stop_urbs(ep, false, keep_pending);
+ if (ep->clock_ref)
+ if (!atomic_dec_return(&ep->clock_ref->locked))
+ ep->clock_ref->rate = 0;
}
}
@@ -1591,12 +1659,16 @@ void snd_usb_endpoint_free_all(struct snd_usb_audio *chip)
{
struct snd_usb_endpoint *ep, *en;
struct snd_usb_iface_ref *ip, *in;
+ struct snd_usb_clock_ref *cp, *cn;
list_for_each_entry_safe(ep, en, &chip->ep_list, list)
kfree(ep);
list_for_each_entry_safe(ip, in, &chip->iface_ref_list, list)
kfree(ip);
+
+ list_for_each_entry_safe(cp, cn, &chip->clock_ref_list, list)
+ kfree(ip);
}
/*