Skip to content

Commit c11117b

Browse files
committed
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>
1 parent 00f87ec commit c11117b

4 files changed

Lines changed: 85 additions & 10 deletions

File tree

sound/usb/card.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ static int snd_usb_audio_create(struct usb_interface *intf,
635635
INIT_LIST_HEAD(&chip->pcm_list);
636636
INIT_LIST_HEAD(&chip->ep_list);
637637
INIT_LIST_HEAD(&chip->iface_ref_list);
638+
INIT_LIST_HEAD(&chip->clock_ref_list);
638639
INIT_LIST_HEAD(&chip->midi_list);
639640
INIT_LIST_HEAD(&chip->mixer_list);
640641

sound/usb/card.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct audioformat {
4444

4545
struct snd_usb_substream;
4646
struct snd_usb_iface_ref;
47+
struct snd_usb_clock_ref;
4748
struct snd_usb_endpoint;
4849
struct snd_usb_power_domain;
4950

@@ -62,6 +63,7 @@ struct snd_urb_ctx {
6263
struct snd_usb_endpoint {
6364
struct snd_usb_audio *chip;
6465
struct snd_usb_iface_ref *iface_ref;
66+
struct snd_usb_clock_ref *clock_ref;
6567

6668
int opened; /* open refcount; protect with chip->mutex */
6769
atomic_t running; /* running status */
@@ -138,7 +140,6 @@ struct snd_usb_endpoint {
138140
unsigned int cur_period_frames;
139141
unsigned int cur_period_bytes;
140142
unsigned int cur_buffer_periods;
141-
unsigned char cur_clock;
142143

143144
spinlock_t lock;
144145
struct list_head list;

sound/usb/endpoint.c

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ struct snd_usb_iface_ref {
3535
struct list_head list;
3636
};
3737

38+
/* clock refcounting */
39+
struct snd_usb_clock_ref {
40+
unsigned char clock;
41+
atomic_t locked;
42+
int rate;
43+
struct list_head list;
44+
};
45+
3846
/*
3947
* snd_usb_endpoint is a model that abstracts everything related to an
4048
* USB endpoint and its streaming.
@@ -591,6 +599,25 @@ iface_ref_find(struct snd_usb_audio *chip, int iface)
591599
return ip;
592600
}
593601

602+
/* Similarly, a refcount object for clock */
603+
static struct snd_usb_clock_ref *
604+
clock_ref_find(struct snd_usb_audio *chip, int clock)
605+
{
606+
struct snd_usb_clock_ref *ref;
607+
608+
list_for_each_entry(ref, &chip->clock_ref_list, list)
609+
if (ref->clock == clock)
610+
return ref;
611+
612+
ref = kzalloc(sizeof(*ref), GFP_KERNEL);
613+
if (!ref)
614+
return NULL;
615+
ref->clock = clock;
616+
atomic_set(&ref->locked, 0);
617+
list_add_tail(&ref->list, &chip->clock_ref_list);
618+
return ref;
619+
}
620+
594621
/*
595622
* Get the existing endpoint object corresponding EP
596623
* Returns NULL if not present.
@@ -768,6 +795,14 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
768795
goto unlock;
769796
}
770797

798+
if (fp->protocol != UAC_VERSION_1) {
799+
ep->clock_ref = clock_ref_find(chip, fp->clock);
800+
if (!ep->clock_ref) {
801+
ep = NULL;
802+
goto unlock;
803+
}
804+
}
805+
771806
ep->cur_audiofmt = fp;
772807
ep->cur_channels = fp->channels;
773808
ep->cur_rate = params_rate(params);
@@ -777,7 +812,6 @@ snd_usb_endpoint_open(struct snd_usb_audio *chip,
777812
ep->cur_period_frames = params_period_size(params);
778813
ep->cur_period_bytes = ep->cur_period_frames * ep->cur_frame_bytes;
779814
ep->cur_buffer_periods = params_periods(params);
780-
ep->cur_clock = fp->clock;
781815

782816
if (ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
783817
endpoint_set_syncinterval(chip, ep);
@@ -894,8 +928,8 @@ void snd_usb_endpoint_close(struct snd_usb_audio *chip,
894928
ep->altsetting = 0;
895929
ep->cur_audiofmt = NULL;
896930
ep->cur_rate = 0;
897-
ep->cur_clock = 0;
898931
ep->iface_ref = NULL;
932+
ep->clock_ref = NULL;
899933
usb_audio_dbg(chip, "EP 0x%x closed\n", ep->ep_num);
900934
}
901935
mutex_unlock(&chip->mutex);
@@ -907,6 +941,8 @@ void snd_usb_endpoint_suspend(struct snd_usb_endpoint *ep)
907941
ep->need_setup = true;
908942
if (ep->iface_ref)
909943
ep->iface_ref->need_setup = true;
944+
if (ep->clock_ref)
945+
ep->clock_ref->rate = 0;
910946
}
911947

912948
/*
@@ -1314,6 +1350,33 @@ static int snd_usb_endpoint_set_params(struct snd_usb_audio *chip,
13141350
return 0;
13151351
}
13161352

1353+
static int init_sample_rate(struct snd_usb_audio *chip,
1354+
struct snd_usb_endpoint *ep)
1355+
{
1356+
struct snd_usb_clock_ref *clock = ep->clock_ref;
1357+
int err;
1358+
1359+
if (clock) {
1360+
if (atomic_read(&clock->locked))
1361+
return 0;
1362+
if (clock->rate == ep->cur_rate)
1363+
return 0;
1364+
if (clock->rate && clock->rate != ep->cur_rate) {
1365+
usb_audio_dbg(chip, "Mismatched sample rate %d vs %d for EP 0x%x\n",
1366+
clock->rate, ep->cur_rate, ep->ep_num);
1367+
return -EINVAL;
1368+
}
1369+
}
1370+
1371+
err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, ep->cur_rate);
1372+
if (err < 0)
1373+
return err;
1374+
1375+
if (clock)
1376+
clock->rate = ep->cur_rate;
1377+
return 0;
1378+
}
1379+
13171380
/*
13181381
* snd_usb_endpoint_configure: Configure the endpoint
13191382
*
@@ -1343,8 +1406,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
13431406
* to update at each EP configuration
13441407
*/
13451408
if (ep->cur_audiofmt->protocol == UAC_VERSION_1) {
1346-
err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt,
1347-
ep->cur_rate);
1409+
err = init_sample_rate(chip, ep);
13481410
if (err < 0)
13491411
goto unlock;
13501412
}
@@ -1374,7 +1436,7 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
13741436
if (err < 0)
13751437
goto unlock;
13761438

1377-
err = snd_usb_init_sample_rate(chip, ep->cur_audiofmt, ep->cur_rate);
1439+
err = init_sample_rate(chip, ep);
13781440
if (err < 0)
13791441
goto unlock;
13801442

@@ -1407,15 +1469,15 @@ int snd_usb_endpoint_configure(struct snd_usb_audio *chip,
14071469
/* get the current rate set to the given clock by any endpoint */
14081470
int snd_usb_endpoint_get_clock_rate(struct snd_usb_audio *chip, int clock)
14091471
{
1410-
struct snd_usb_endpoint *ep;
1472+
struct snd_usb_clock_ref *ref;
14111473
int rate = 0;
14121474

14131475
if (!clock)
14141476
return 0;
14151477
mutex_lock(&chip->mutex);
1416-
list_for_each_entry(ep, &chip->ep_list, list) {
1417-
if (ep->cur_clock == clock && ep->cur_rate) {
1418-
rate = ep->cur_rate;
1478+
list_for_each_entry(ref, &chip->clock_ref_list, list) {
1479+
if (ref->clock == clock) {
1480+
rate = ref->rate;
14191481
break;
14201482
}
14211483
}
@@ -1456,6 +1518,9 @@ int snd_usb_endpoint_start(struct snd_usb_endpoint *ep)
14561518
if (atomic_inc_return(&ep->running) != 1)
14571519
return 0;
14581520

1521+
if (ep->clock_ref)
1522+
atomic_inc(&ep->clock_ref->locked);
1523+
14591524
ep->active_mask = 0;
14601525
ep->unlink_mask = 0;
14611526
ep->phase = 0;
@@ -1565,6 +1630,9 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep, bool keep_pending)
15651630
if (ep->sync_source)
15661631
WRITE_ONCE(ep->sync_source->sync_sink, NULL);
15671632
stop_urbs(ep, false, keep_pending);
1633+
if (ep->clock_ref)
1634+
if (!atomic_dec_return(&ep->clock_ref->locked))
1635+
ep->clock_ref->rate = 0;
15681636
}
15691637
}
15701638

@@ -1591,12 +1659,16 @@ void snd_usb_endpoint_free_all(struct snd_usb_audio *chip)
15911659
{
15921660
struct snd_usb_endpoint *ep, *en;
15931661
struct snd_usb_iface_ref *ip, *in;
1662+
struct snd_usb_clock_ref *cp, *cn;
15941663

15951664
list_for_each_entry_safe(ep, en, &chip->ep_list, list)
15961665
kfree(ep);
15971666

15981667
list_for_each_entry_safe(ip, in, &chip->iface_ref_list, list)
15991668
kfree(ip);
1669+
1670+
list_for_each_entry_safe(cp, cn, &chip->clock_ref_list, list)
1671+
kfree(ip);
16001672
}
16011673

16021674
/*

sound/usb/usbaudio.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct snd_usb_audio {
4545
struct list_head pcm_list; /* list of pcm streams */
4646
struct list_head ep_list; /* list of audio-related endpoints */
4747
struct list_head iface_ref_list; /* list of interface refcounts */
48+
struct list_head clock_ref_list; /* list of clock refcounts */
4849
int pcm_devs;
4950

5051
struct list_head midi_list; /* list of midi interfaces */

0 commit comments

Comments
 (0)