Skip to content

Commit f57f3df

Browse files
committed
ALSA: pcm: More fine-grained PCM link locking
We have currently two global locks, a rwlock and a rwsem, that are used for managing linking the PCM streams. Due to these global locks, once when a linked stream is used, the lock granularity suffers a lot. This patch attempts to eliminate the former global lock for atomic ops. The latter rwsem needs remaining because of the loosy way of the loop calls in snd_pcm_action_nonatomic(), as well as for avoiding the deadlock at linking. However, these are used far rarely, actually only by two actions (prepare and reset), where both are no timing critical ones. So this can be still seen as a good improvement. The basic strategy to eliminate the rwlock is to assure group->lock at adding or removing a stream to / from the group. Since we already takes the group lock whenever taking the all substream locks under the group, this shouldn't be a big problem. The reference to group pointer in snd_pcm_substream object is protected by the stream lock itself. However, there are still pitfalls: a race window at re-locking and the lifecycle of group object. The former is a small race window for dereferencing the substream group object opened while snd_pcm_action() performs re-locking to avoid ABBA deadlocks. This includes the unlink of group during that window, too. And the latter is the kfree performed after all streams are removed from the group while it's still dereferenced. For addressing these corner cases, two new tricks are introduced: - After re-locking, the group assigned to the stream is checked again; if the group is changed, we retry the whole procedure. - Introduce a refcount to snd_pcm_group object, so that it's freed only when it's empty and really no one refers to it. (Some readers might wonder why not RCU for the latter. RCU in this case would cost more than refcounting, unfortunately. We take the group lock sooner or later, hence the performance improvement by RCU would be negligible. Meanwhile, because we need to deal with schedulable context depending on the pcm->nonatomic flag, it'll become dynamic RCU/SRCU switch, and the grace period may become too long.) Along with these changes, there are a significant amount of code refactoring. The complex group re-lock & ref code is factored out to snd_pcm_stream_group_ref() function, for example. Signed-off-by: Takashi Iwai <[email protected]>
1 parent 7df5a5f commit f57f3df

File tree

2 files changed

+124
-44
lines changed

2 files changed

+124
-44
lines changed

include/sound/pcm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <linux/mm.h>
3131
#include <linux/bitops.h>
3232
#include <linux/pm_qos.h>
33+
#include <linux/refcount.h>
3334

3435
#define snd_pcm_substream_chip(substream) ((substream)->private_data)
3536
#define snd_pcm_chip(pcm) ((pcm)->private_data)
@@ -439,6 +440,7 @@ struct snd_pcm_group { /* keep linked substreams */
439440
spinlock_t lock;
440441
struct mutex mutex;
441442
struct list_head substreams;
443+
refcount_t refs;
442444
};
443445

444446
struct pid;

sound/core/pcm_native.c

Lines changed: 122 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
8585
*
8686
*/
8787

88-
static DEFINE_RWLOCK(snd_pcm_link_rwlock);
8988
static DECLARE_RWSEM(snd_pcm_link_rwsem);
9089

9190
/* Writer in rwsem may block readers even during its waiting in queue,
@@ -105,8 +104,24 @@ void snd_pcm_group_init(struct snd_pcm_group *group)
105104
spin_lock_init(&group->lock);
106105
mutex_init(&group->mutex);
107106
INIT_LIST_HEAD(&group->substreams);
107+
refcount_set(&group->refs, 0);
108108
}
109109

110+
/* define group lock helpers */
111+
#define DEFINE_PCM_GROUP_LOCK(action, mutex_action) \
112+
static void snd_pcm_group_ ## action(struct snd_pcm_group *group, bool nonatomic) \
113+
{ \
114+
if (nonatomic) \
115+
mutex_ ## mutex_action(&group->mutex); \
116+
else \
117+
spin_ ## action(&group->lock); \
118+
}
119+
120+
DEFINE_PCM_GROUP_LOCK(lock, lock);
121+
DEFINE_PCM_GROUP_LOCK(unlock, unlock);
122+
DEFINE_PCM_GROUP_LOCK(lock_irq, lock);
123+
DEFINE_PCM_GROUP_LOCK(unlock_irq, unlock);
124+
110125
#define PCM_LOCK_DEFAULT 0
111126
#define PCM_LOCK_IRQ 1
112127
#define PCM_LOCK_IRQSAVE 2
@@ -116,21 +131,19 @@ static unsigned long __snd_pcm_stream_lock_mode(struct snd_pcm_substream *substr
116131
{
117132
unsigned long flags = 0;
118133
if (substream->pcm->nonatomic) {
119-
down_read_nested(&snd_pcm_link_rwsem, SINGLE_DEPTH_NESTING);
120134
mutex_lock(&substream->self_group.mutex);
121135
} else {
122136
switch (mode) {
123137
case PCM_LOCK_DEFAULT:
124-
read_lock(&snd_pcm_link_rwlock);
138+
spin_lock(&substream->self_group.lock);
125139
break;
126140
case PCM_LOCK_IRQ:
127-
read_lock_irq(&snd_pcm_link_rwlock);
141+
spin_lock_irq(&substream->self_group.lock);
128142
break;
129143
case PCM_LOCK_IRQSAVE:
130-
read_lock_irqsave(&snd_pcm_link_rwlock, flags);
144+
spin_lock_irqsave(&substream->self_group.lock, flags);
131145
break;
132146
}
133-
spin_lock(&substream->self_group.lock);
134147
}
135148
return flags;
136149
}
@@ -140,19 +153,16 @@ static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream,
140153
{
141154
if (substream->pcm->nonatomic) {
142155
mutex_unlock(&substream->self_group.mutex);
143-
up_read(&snd_pcm_link_rwsem);
144156
} else {
145-
spin_unlock(&substream->self_group.lock);
146-
147157
switch (mode) {
148158
case PCM_LOCK_DEFAULT:
149-
read_unlock(&snd_pcm_link_rwlock);
159+
spin_unlock(&substream->self_group.lock);
150160
break;
151161
case PCM_LOCK_IRQ:
152-
read_unlock_irq(&snd_pcm_link_rwlock);
162+
spin_unlock_irq(&substream->self_group.lock);
153163
break;
154164
case PCM_LOCK_IRQSAVE:
155-
read_unlock_irqrestore(&snd_pcm_link_rwlock, flags);
165+
spin_unlock_irqrestore(&substream->self_group.lock, flags);
156166
break;
157167
}
158168
}
@@ -1138,35 +1148,77 @@ static void snd_pcm_group_assign(struct snd_pcm_substream *substream,
11381148
list_move(&substream->link_list, &new_group->substreams);
11391149
}
11401150

1151+
/*
1152+
* Unref and unlock the group, but keep the stream lock;
1153+
* when the group becomes empty and no longer referred, destroy itself
1154+
*/
1155+
static void snd_pcm_group_unref(struct snd_pcm_group *group,
1156+
struct snd_pcm_substream *substream)
1157+
{
1158+
bool do_free;
1159+
1160+
if (!group)
1161+
return;
1162+
do_free = refcount_dec_and_test(&group->refs) &&
1163+
list_empty(&group->substreams);
1164+
snd_pcm_group_unlock(group, substream->pcm->nonatomic);
1165+
if (do_free)
1166+
kfree(group);
1167+
}
1168+
1169+
/*
1170+
* Lock the group inside a stream lock and reference it;
1171+
* return the locked group object, or NULL if not linked
1172+
*/
1173+
static struct snd_pcm_group *
1174+
snd_pcm_stream_group_ref(struct snd_pcm_substream *substream)
1175+
{
1176+
bool nonatomic = substream->pcm->nonatomic;
1177+
struct snd_pcm_group *group;
1178+
bool trylock;
1179+
1180+
for (;;) {
1181+
if (!snd_pcm_stream_linked(substream))
1182+
return NULL;
1183+
group = substream->group;
1184+
/* block freeing the group object */
1185+
refcount_inc(&group->refs);
1186+
1187+
trylock = nonatomic ? mutex_trylock(&group->mutex) :
1188+
spin_trylock(&group->lock);
1189+
if (trylock)
1190+
break; /* OK */
1191+
1192+
/* re-lock for avoiding ABBA deadlock */
1193+
snd_pcm_stream_unlock(substream);
1194+
snd_pcm_group_lock(group, nonatomic);
1195+
snd_pcm_stream_lock(substream);
1196+
1197+
/* check the group again; the above opens a small race window */
1198+
if (substream->group == group)
1199+
break; /* OK */
1200+
/* group changed, try again */
1201+
snd_pcm_group_unref(group, substream);
1202+
}
1203+
return group;
1204+
}
1205+
11411206
/*
11421207
* Note: call with stream lock
11431208
*/
11441209
static int snd_pcm_action(const struct action_ops *ops,
11451210
struct snd_pcm_substream *substream,
11461211
int state)
11471212
{
1213+
struct snd_pcm_group *group;
11481214
int res;
11491215

1150-
if (!snd_pcm_stream_linked(substream))
1151-
return snd_pcm_action_single(ops, substream, state);
1152-
1153-
if (substream->pcm->nonatomic) {
1154-
if (!mutex_trylock(&substream->group->mutex)) {
1155-
mutex_unlock(&substream->self_group.mutex);
1156-
mutex_lock(&substream->group->mutex);
1157-
mutex_lock(&substream->self_group.mutex);
1158-
}
1216+
group = snd_pcm_stream_group_ref(substream);
1217+
if (group)
11591218
res = snd_pcm_action_group(ops, substream, state, 1);
1160-
mutex_unlock(&substream->group->mutex);
1161-
} else {
1162-
if (!spin_trylock(&substream->group->lock)) {
1163-
spin_unlock(&substream->self_group.lock);
1164-
spin_lock(&substream->group->lock);
1165-
spin_lock(&substream->self_group.lock);
1166-
}
1167-
res = snd_pcm_action_group(ops, substream, state, 1);
1168-
spin_unlock(&substream->group->lock);
1169-
}
1219+
else
1220+
res = snd_pcm_action_single(ops, substream, state);
1221+
snd_pcm_group_unref(group, substream);
11701222
return res;
11711223
}
11721224

@@ -1193,6 +1245,7 @@ static int snd_pcm_action_nonatomic(const struct action_ops *ops,
11931245
{
11941246
int res;
11951247

1248+
/* Guarantee the group members won't change during non-atomic action */
11961249
down_read(&snd_pcm_link_rwsem);
11971250
if (snd_pcm_stream_linked(substream))
11981251
res = snd_pcm_action_group(ops, substream, state, 0);
@@ -1821,6 +1874,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
18211874
struct snd_card *card;
18221875
struct snd_pcm_runtime *runtime;
18231876
struct snd_pcm_substream *s;
1877+
struct snd_pcm_group *group;
18241878
wait_queue_entry_t wait;
18251879
int result = 0;
18261880
int nonblock = 0;
@@ -1837,7 +1891,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
18371891
} else if (substream->f_flags & O_NONBLOCK)
18381892
nonblock = 1;
18391893

1840-
down_read(&snd_pcm_link_rwsem);
18411894
snd_pcm_stream_lock_irq(substream);
18421895
/* resume pause */
18431896
if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
@@ -1862,6 +1915,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
18621915
}
18631916
/* find a substream to drain */
18641917
to_check = NULL;
1918+
group = snd_pcm_stream_group_ref(substream);
18651919
snd_pcm_group_for_each_entry(s, substream) {
18661920
if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
18671921
continue;
@@ -1871,12 +1925,12 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
18711925
break;
18721926
}
18731927
}
1928+
snd_pcm_group_unref(group, substream);
18741929
if (!to_check)
18751930
break; /* all drained */
18761931
init_waitqueue_entry(&wait, current);
18771932
add_wait_queue(&to_check->sleep, &wait);
18781933
snd_pcm_stream_unlock_irq(substream);
1879-
up_read(&snd_pcm_link_rwsem);
18801934
if (runtime->no_period_wakeup)
18811935
tout = MAX_SCHEDULE_TIMEOUT;
18821936
else {
@@ -1888,9 +1942,17 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
18881942
tout = msecs_to_jiffies(tout * 1000);
18891943
}
18901944
tout = schedule_timeout_interruptible(tout);
1891-
down_read(&snd_pcm_link_rwsem);
1945+
18921946
snd_pcm_stream_lock_irq(substream);
1893-
remove_wait_queue(&to_check->sleep, &wait);
1947+
group = snd_pcm_stream_group_ref(substream);
1948+
snd_pcm_group_for_each_entry(s, substream) {
1949+
if (s->runtime == to_check) {
1950+
remove_wait_queue(&to_check->sleep, &wait);
1951+
break;
1952+
}
1953+
}
1954+
snd_pcm_group_unref(group, substream);
1955+
18941956
if (card->shutdown) {
18951957
result = -ENODEV;
18961958
break;
@@ -1910,7 +1972,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
19101972

19111973
unlock:
19121974
snd_pcm_stream_unlock_irq(substream);
1913-
up_read(&snd_pcm_link_rwsem);
19141975

19151976
return result;
19161977
}
@@ -1972,7 +2033,8 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
19722033
int res = 0;
19732034
struct snd_pcm_file *pcm_file;
19742035
struct snd_pcm_substream *substream1;
1975-
struct snd_pcm_group *group;
2036+
struct snd_pcm_group *group, *target_group;
2037+
bool nonatomic = substream->pcm->nonatomic;
19762038
struct fd f = fdget(fd);
19772039

19782040
if (!f.file)
@@ -1989,8 +2051,8 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
19892051
goto _nolock;
19902052
}
19912053
snd_pcm_group_init(group);
2054+
19922055
down_write_nonfifo(&snd_pcm_link_rwsem);
1993-
write_lock_irq(&snd_pcm_link_rwlock);
19942056
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
19952057
substream->runtime->status->state != substream1->runtime->status->state ||
19962058
substream->pcm->nonatomic != substream1->pcm->nonatomic) {
@@ -2001,13 +2063,21 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
20012063
res = -EALREADY;
20022064
goto _end;
20032065
}
2066+
2067+
snd_pcm_stream_lock_irq(substream);
20042068
if (!snd_pcm_stream_linked(substream)) {
20052069
snd_pcm_group_assign(substream, group);
2006-
group = NULL;
2070+
group = NULL; /* assigned, don't free this one below */
20072071
}
2008-
snd_pcm_group_assign(substream1, substream->group);
2072+
target_group = substream->group;
2073+
snd_pcm_stream_unlock_irq(substream);
2074+
2075+
snd_pcm_group_lock_irq(target_group, nonatomic);
2076+
snd_pcm_stream_lock(substream1);
2077+
snd_pcm_group_assign(substream1, target_group);
2078+
snd_pcm_stream_unlock(substream1);
2079+
snd_pcm_group_unlock_irq(target_group, nonatomic);
20092080
_end:
2010-
write_unlock_irq(&snd_pcm_link_rwlock);
20112081
up_write(&snd_pcm_link_rwsem);
20122082
_nolock:
20132083
kfree(group);
@@ -2018,22 +2088,27 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
20182088

20192089
static void relink_to_local(struct snd_pcm_substream *substream)
20202090
{
2091+
snd_pcm_stream_lock(substream);
20212092
snd_pcm_group_assign(substream, &substream->self_group);
2093+
snd_pcm_stream_unlock(substream);
20222094
}
20232095

20242096
static int snd_pcm_unlink(struct snd_pcm_substream *substream)
20252097
{
20262098
struct snd_pcm_group *group;
2099+
bool nonatomic = substream->pcm->nonatomic;
2100+
bool do_free = false;
20272101
int res = 0;
20282102

20292103
down_write_nonfifo(&snd_pcm_link_rwsem);
2030-
write_lock_irq(&snd_pcm_link_rwlock);
2104+
20312105
if (!snd_pcm_stream_linked(substream)) {
20322106
res = -EALREADY;
20332107
goto _end;
20342108
}
20352109

20362110
group = substream->group;
2111+
snd_pcm_group_lock_irq(group, nonatomic);
20372112

20382113
relink_to_local(substream);
20392114

@@ -2042,11 +2117,14 @@ static int snd_pcm_unlink(struct snd_pcm_substream *substream)
20422117
relink_to_local(list_first_entry(&group->substreams,
20432118
struct snd_pcm_substream,
20442119
link_list));
2045-
kfree(group);
2120+
do_free = !refcount_read(&group->refs);
20462121
}
20472122

2123+
snd_pcm_group_unlock_irq(group, nonatomic);
2124+
if (do_free)
2125+
kfree(group);
2126+
20482127
_end:
2049-
write_unlock_irq(&snd_pcm_link_rwlock);
20502128
up_write(&snd_pcm_link_rwsem);
20512129
return res;
20522130
}

0 commit comments

Comments
 (0)