mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 22:14:03 +02:00
ALSA: seq: Use RCU for the port subscriber list
Each sequencer port keeps two subscriber groups (c_src and c_dest),
each protected by both an rwlock (list_lock) and a rw_semaphore
(list_mutex). The rwlock is taken read-side in the event delivery hot
path (__deliver_to_subscribers()) for delivering every event to
subscribers, while the mutex serializes subscribe/unsubscribe and
covers the sleepable delivery and query walks.
Subscriptions change rarely but delivery happens constantly, so this is
a textbook read-mostly case. Convert the subscriber list traversal to
RCU and drop the rwlock entirely while keeping the existing list_mutex
for serializing the writers. The atomic delivery path now runs
lock-free under rcu_read_lock() instead of contending on the shared
rwlock.
Along with the conversion to RCU, the subscriber lists are switched
from list_head to hlist so that removal can use hlist_del_init_rcu():
it keeps the ->next pointer intact for concurrent readers while
clearing ->pprev, which lets the double-deletion guard (added in
commit 13d5e5d472) keep detecting an already-removed entry via
hlist_unhashed().
Dropping write_lock_irq() from the writers is safe: no writer runs in
atomic/IRQ context, and the sole atomic reader now uses RCU, which is
IRQ-safe. Port lifetime handling (use_lock/closing drain in
port_delete()) is orthogonal and unchanged.
Note that the conversion to RCU has another merit: it automatically
"fixes" the (rather false) lockdep warnings for the doubly read-locks
of the same subscriber list, too.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20260810133711.42483-2-tiwai@suse.de
This commit is contained in:
parent
e7da28b820
commit
a9760db775
|
|
@ -717,10 +717,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client,
|
|||
|
||||
/* lock list */
|
||||
if (atomic)
|
||||
read_lock(&grp->list_lock);
|
||||
rcu_read_lock();
|
||||
else
|
||||
down_read_nested(&grp->list_mutex, hop);
|
||||
list_for_each_entry(subs, &grp->list_head, src_list) {
|
||||
hlist_for_each_entry_rcu(subs, &grp->list_head, src_list,
|
||||
lockdep_is_held(&grp->list_mutex)) {
|
||||
/* both ports ready? */
|
||||
if (atomic_read(&subs->ref_count) != 2)
|
||||
continue;
|
||||
|
|
@ -741,7 +742,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client,
|
|||
memcpy(event, &event_saved, saved_size);
|
||||
}
|
||||
if (atomic)
|
||||
read_unlock(&grp->list_lock);
|
||||
rcu_read_unlock();
|
||||
else
|
||||
up_read(&grp->list_mutex);
|
||||
memcpy(event, &event_saved, saved_size);
|
||||
|
|
@ -1938,7 +1939,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg)
|
|||
{
|
||||
struct snd_seq_query_subs *subs = arg;
|
||||
struct snd_seq_port_subs_info *group;
|
||||
struct list_head *p;
|
||||
struct hlist_node *p;
|
||||
int i;
|
||||
|
||||
struct snd_seq_client *cptr __free(snd_seq_client) =
|
||||
|
|
@ -1965,15 +1966,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg)
|
|||
/* search for the subscriber */
|
||||
subs->num_subs = group->count;
|
||||
i = 0;
|
||||
list_for_each(p, &group->list_head) {
|
||||
hlist_for_each(p, &group->list_head) {
|
||||
if (i++ == subs->index) {
|
||||
/* found! */
|
||||
struct snd_seq_subscribers *s;
|
||||
if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) {
|
||||
s = list_entry(p, struct snd_seq_subscribers, src_list);
|
||||
s = hlist_entry(p, struct snd_seq_subscribers, src_list);
|
||||
subs->addr = s->info.dest;
|
||||
} else {
|
||||
s = list_entry(p, struct snd_seq_subscribers, dest_list);
|
||||
s = hlist_entry(p, struct snd_seq_subscribers, dest_list);
|
||||
subs->addr = s->info.sender;
|
||||
}
|
||||
subs->flags = s->info.flags;
|
||||
|
|
@ -2529,19 +2530,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer,
|
|||
struct snd_seq_port_subs_info *group,
|
||||
int is_src, char *msg)
|
||||
{
|
||||
struct list_head *p;
|
||||
struct hlist_node *p;
|
||||
struct snd_seq_subscribers *s;
|
||||
int count = 0;
|
||||
|
||||
guard(rwsem_read)(&group->list_mutex);
|
||||
if (list_empty(&group->list_head))
|
||||
if (hlist_empty(&group->list_head))
|
||||
return;
|
||||
snd_iprintf(buffer, msg);
|
||||
list_for_each(p, &group->list_head) {
|
||||
hlist_for_each(p, &group->list_head) {
|
||||
if (is_src)
|
||||
s = list_entry(p, struct snd_seq_subscribers, src_list);
|
||||
s = hlist_entry(p, struct snd_seq_subscribers, src_list);
|
||||
else
|
||||
s = list_entry(p, struct snd_seq_subscribers, dest_list);
|
||||
s = hlist_entry(p, struct snd_seq_subscribers, dest_list);
|
||||
if (count++)
|
||||
snd_iprintf(buffer, ", ");
|
||||
snd_iprintf(buffer, "%d:%d",
|
||||
|
|
|
|||
|
|
@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl
|
|||
/* initialize snd_seq_port_subs_info */
|
||||
static void port_subs_info_init(struct snd_seq_port_subs_info *grp)
|
||||
{
|
||||
INIT_LIST_HEAD(&grp->list_head);
|
||||
INIT_HLIST_HEAD(&grp->list_head);
|
||||
grp->count = 0;
|
||||
grp->exclusive = 0;
|
||||
rwlock_init(&grp->list_lock);
|
||||
init_rwsem(&grp->list_mutex);
|
||||
grp->open = NULL;
|
||||
grp->close = NULL;
|
||||
|
|
@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client,
|
|||
bool is_src, bool ack);
|
||||
|
||||
static inline struct snd_seq_subscribers *
|
||||
get_subscriber(struct list_head *p, bool is_src)
|
||||
get_subscriber(struct hlist_node *p, bool is_src)
|
||||
{
|
||||
if (is_src)
|
||||
return list_entry(p, struct snd_seq_subscribers, src_list);
|
||||
return hlist_entry(p, struct snd_seq_subscribers, src_list);
|
||||
else
|
||||
return list_entry(p, struct snd_seq_subscribers, dest_list);
|
||||
return hlist_entry(p, struct snd_seq_subscribers, dest_list);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client,
|
|||
struct snd_seq_port_subs_info *grp,
|
||||
int is_src)
|
||||
{
|
||||
struct list_head *p, *n;
|
||||
struct hlist_node *p, *n;
|
||||
|
||||
list_for_each_safe(p, n, &grp->list_head) {
|
||||
hlist_for_each_safe(p, n, &grp->list_head) {
|
||||
struct snd_seq_subscribers *subs;
|
||||
|
||||
subs = get_subscriber(p, is_src);
|
||||
|
|
@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client,
|
|||
* remove the subscriber info
|
||||
*/
|
||||
if (atomic_dec_and_test(&subs->ref_count))
|
||||
kfree(subs);
|
||||
kfree_rcu(subs, rcu);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ok we got the connected port */
|
||||
delete_and_unsubscribe_port(c, aport, subs, !is_src, true);
|
||||
kfree(subs);
|
||||
kfree_rcu(subs, rcu);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -499,20 +498,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client,
|
|||
bool is_src, bool exclusive, bool ack)
|
||||
{
|
||||
struct snd_seq_port_subs_info *grp;
|
||||
struct list_head *p;
|
||||
struct hlist_node *p;
|
||||
struct snd_seq_subscribers *s;
|
||||
int err;
|
||||
|
||||
grp = is_src ? &port->c_src : &port->c_dest;
|
||||
guard(rwsem_write)(&grp->list_mutex);
|
||||
if (exclusive) {
|
||||
if (!list_empty(&grp->list_head))
|
||||
if (!hlist_empty(&grp->list_head))
|
||||
return -EBUSY;
|
||||
} else {
|
||||
if (grp->exclusive)
|
||||
return -EBUSY;
|
||||
/* check whether already exists */
|
||||
list_for_each(p, &grp->list_head) {
|
||||
hlist_for_each(p, &grp->list_head) {
|
||||
s = get_subscriber(p, is_src);
|
||||
if (match_subs_info(&subs->info, &s->info))
|
||||
return -EBUSY;
|
||||
|
|
@ -526,11 +525,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client,
|
|||
}
|
||||
|
||||
/* add to list */
|
||||
guard(write_lock_irq)(&grp->list_lock);
|
||||
if (is_src)
|
||||
list_add_tail(&subs->src_list, &grp->list_head);
|
||||
hlist_add_tail_rcu(&subs->src_list, &grp->list_head);
|
||||
else
|
||||
list_add_tail(&subs->dest_list, &grp->list_head);
|
||||
hlist_add_tail_rcu(&subs->dest_list, &grp->list_head);
|
||||
grp->exclusive = exclusive;
|
||||
atomic_inc(&subs->ref_count);
|
||||
|
||||
|
|
@ -544,17 +542,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client,
|
|||
bool is_src, bool ack)
|
||||
{
|
||||
struct snd_seq_port_subs_info *grp;
|
||||
struct list_head *list;
|
||||
struct hlist_node *list;
|
||||
bool empty;
|
||||
|
||||
grp = is_src ? &port->c_src : &port->c_dest;
|
||||
list = is_src ? &subs->src_list : &subs->dest_list;
|
||||
scoped_guard(write_lock_irq, &grp->list_lock) {
|
||||
empty = list_empty(list);
|
||||
if (!empty)
|
||||
list_del_init(list);
|
||||
grp->exclusive = 0;
|
||||
}
|
||||
empty = hlist_unhashed(list);
|
||||
if (!empty)
|
||||
hlist_del_init_rcu(list);
|
||||
grp->exclusive = 0;
|
||||
|
||||
if (!empty)
|
||||
unsubscribe_port(client, port, grp, &subs->info, ack);
|
||||
|
|
@ -590,8 +586,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector,
|
|||
|
||||
subs->info = *info;
|
||||
atomic_set(&subs->ref_count, 0);
|
||||
INIT_LIST_HEAD(&subs->src_list);
|
||||
INIT_LIST_HEAD(&subs->dest_list);
|
||||
INIT_HLIST_NODE(&subs->src_list);
|
||||
INIT_HLIST_NODE(&subs->dest_list);
|
||||
|
||||
exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE);
|
||||
|
||||
|
|
@ -612,7 +608,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector,
|
|||
delete_and_unsubscribe_port(src_client, src_port, subs, true,
|
||||
connector->number != src_client->number);
|
||||
error:
|
||||
kfree(subs);
|
||||
kfree_rcu(subs, rcu);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -633,7 +629,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector,
|
|||
*/
|
||||
scoped_guard(rwsem_write, &dest->list_mutex) {
|
||||
/* look for the connection */
|
||||
list_for_each_entry(subs, &dest->list_head, dest_list) {
|
||||
hlist_for_each_entry(subs, &dest->list_head, dest_list) {
|
||||
if (match_subs_info(info, &subs->info)) {
|
||||
__delete_and_unsubscribe_port(dest_client, dest_port,
|
||||
subs, false,
|
||||
|
|
@ -648,7 +644,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector,
|
|||
|
||||
delete_and_unsubscribe_port(src_client, src_port, subs, true,
|
||||
connector->number != src_client->number);
|
||||
kfree(subs);
|
||||
kfree_rcu(subs, rcu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -662,7 +658,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
|
|||
int err = -ENOENT;
|
||||
|
||||
guard(rwsem_read)(&src_grp->list_mutex);
|
||||
list_for_each_entry(s, &src_grp->list_head, src_list) {
|
||||
hlist_for_each_entry(s, &src_grp->list_head, src_list) {
|
||||
if (addr_match(dest_addr, &s->info.dest)) {
|
||||
*subs = s->info;
|
||||
err = 0;
|
||||
|
|
|
|||
|
|
@ -28,17 +28,17 @@
|
|||
|
||||
struct snd_seq_subscribers {
|
||||
struct snd_seq_port_subscribe info; /* additional info */
|
||||
struct list_head src_list; /* link of sources */
|
||||
struct list_head dest_list; /* link of destinations */
|
||||
struct hlist_node src_list; /* link of sources */
|
||||
struct hlist_node dest_list; /* link of destinations */
|
||||
atomic_t ref_count;
|
||||
struct rcu_head rcu; /* for deferred free */
|
||||
};
|
||||
|
||||
struct snd_seq_port_subs_info {
|
||||
struct list_head list_head; /* list of subscribed ports */
|
||||
struct hlist_head list_head; /* list of subscribed ports */
|
||||
unsigned int count; /* count of subscribers */
|
||||
unsigned int exclusive: 1; /* exclusive mode */
|
||||
struct rw_semaphore list_mutex;
|
||||
rwlock_t list_lock;
|
||||
int (*open)(void *private_data, struct snd_seq_port_subscribe *info);
|
||||
int (*close)(void *private_data, struct snd_seq_port_subscribe *info);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user