mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 13:14:02 +02:00
ALSA: seq: Use RCU for the client port list
Each sequencer client keeps a list of its ports (ports_list_head) protected by both an rwlock (ports_lock) and a mutex (ports_mutex). The rwlock is taken read-side on the event delivery hot path: snd_seq_port_use_ptr() walks the list to resolve a port on every dispatched event, while the mutex serializes port creation/deletion. Ports change rarely but delivery happens constantly, so this is the another read-mostly case as the port subscriber list. Convert the port list traversal to RCU and drop the rwlock entirely; the existing ports_mutex keeps serializing the writers. The atomic delivery path (snd_seq_port_use_ptr(), snd_seq_port_query_nearest()) now runs lock-free under rcu_read_lock() instead of contending on the shared rwlock. The writers switch to list_add_tail_rcu()/list_del_rcu(). snd_seq_insert_port() now stores the port number and name before publishing the node so RCU readers only ever observe a fully initialized port. One drawback is that snd_seq_delete_all_ports() drops the O(1) splice trick and unlinks each port individually, though: the splice repointed the last port's ->next away from the list head, which would send a concurrent lockless reader off the end of the list. Unlike the subscriber objects, ports are not freed via kfree_rcu(): port_delete() must drain outstanding use_lock references (and run private_free()) synchronously. The rwlock previously guaranteed that no reader could take a new use_lock reference once the port was unlinked -- list_del under write_lock excluded snd_use_lock_use() under read_lock. list_del_rcu() offers no such exclusion, so a reader still traversing the list can grab a reference after the unlink. port_delete() therefore calls synchronize_rcu() after the port has been unlinked and before snd_use_lock_sync(): once the grace period elapses no new reference can appear, and the existing drain then frees the port safely. 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. Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://patch.msgid.link/20260810133711.42483-3-tiwai@suse.de
This commit is contained in:
parent
a9760db775
commit
4c252fbc06
|
|
@ -212,7 +212,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
|
|||
}
|
||||
client->type = NO_CLIENT;
|
||||
snd_use_lock_init(&client->use_lock);
|
||||
rwlock_init(&client->ports_lock);
|
||||
mutex_init(&client->ports_mutex);
|
||||
INIT_LIST_HEAD(&client->ports_list_head);
|
||||
mutex_init(&client->ioctl_mutex);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ struct snd_seq_client {
|
|||
/* ports */
|
||||
int num_ports; /* number of ports */
|
||||
struct list_head ports_list_head;
|
||||
rwlock_t ports_lock;
|
||||
struct mutex ports_mutex;
|
||||
struct mutex ioctl_mutex;
|
||||
int convert32; /* convert 32->64bit */
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client,
|
|||
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
guard(read_lock)(&client->ports_lock);
|
||||
list_for_each_entry(port, &client->ports_list_head, list) {
|
||||
guard(rcu)();
|
||||
list_for_each_entry_rcu(port, &client->ports_list_head, list) {
|
||||
if (port->addr.port == num) {
|
||||
if (port->closing)
|
||||
break; /* deleting now */
|
||||
|
|
@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl
|
|||
|
||||
num = pinfo->addr.port;
|
||||
found = NULL;
|
||||
guard(read_lock)(&client->ports_lock);
|
||||
list_for_each_entry(port, &client->ports_list_head, list) {
|
||||
guard(rcu)();
|
||||
list_for_each_entry_rcu(port, &client->ports_list_head, list) {
|
||||
if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) &&
|
||||
!check_inactive)
|
||||
continue; /* skip inactive ports */
|
||||
|
|
@ -153,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port,
|
|||
|
||||
num = max(port, 0);
|
||||
guard(mutex)(&client->ports_mutex);
|
||||
guard(write_lock_irq)(&client->ports_lock);
|
||||
struct list_head *insert_before = &client->ports_list_head;
|
||||
list_for_each_entry(p, &client->ports_list_head, list) {
|
||||
if (p->addr.port == port)
|
||||
|
|
@ -165,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port,
|
|||
if (port < 0) /* auto-probe mode */
|
||||
num = p->addr.port + 1;
|
||||
}
|
||||
/* insert the new port */
|
||||
list_add_tail(&new_port->list, insert_before);
|
||||
client->num_ports++;
|
||||
/* finish initializing the port before publishing it to RCU readers */
|
||||
new_port->addr.port = num; /* store the port number in the port */
|
||||
if (!new_port->name[0])
|
||||
sprintf(new_port->name, "port-%d", num);
|
||||
/* insert the new port */
|
||||
list_add_tail_rcu(&new_port->list, insert_before);
|
||||
client->num_ports++;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
|
@ -253,7 +253,13 @@ static int port_delete(struct snd_seq_client *client,
|
|||
{
|
||||
/* set closing flag and wait for all port access are gone */
|
||||
port->closing = 1;
|
||||
snd_use_lock_sync(&port->use_lock);
|
||||
/* the port has already been unlinked from the client's port list;
|
||||
* wait for a grace period so that RCU readers still traversing the
|
||||
* list can no longer take a new use_lock reference, then drain the
|
||||
* outstanding references before freeing
|
||||
*/
|
||||
synchronize_rcu();
|
||||
snd_use_lock_sync(&port->use_lock);
|
||||
|
||||
/* clear subscribers info */
|
||||
clear_subscriber_list(client, port, &port->c_src, true);
|
||||
|
|
@ -276,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port)
|
|||
struct snd_seq_client_port *found = NULL, *p;
|
||||
|
||||
scoped_guard(mutex, &client->ports_mutex) {
|
||||
guard(write_lock_irq)(&client->ports_lock);
|
||||
list_for_each_entry(p, &client->ports_list_head, list) {
|
||||
if (p->addr.port == port) {
|
||||
/* ok found. delete from the list at first */
|
||||
list_del(&p->list);
|
||||
list_del_rcu(&p->list);
|
||||
client->num_ports--;
|
||||
found = p;
|
||||
break;
|
||||
|
|
@ -296,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port)
|
|||
/* delete the all ports belonging to the given client */
|
||||
int snd_seq_delete_all_ports(struct snd_seq_client *client)
|
||||
{
|
||||
struct list_head deleted_list;
|
||||
struct snd_seq_client_port *port, *tmp;
|
||||
|
||||
/* move the port list to deleted_list, and
|
||||
* clear the port list in the client data.
|
||||
|
||||
/* unlink and delete each port; port_delete() waits for an RCU grace
|
||||
* period before draining the port, so concurrent lockless readers can
|
||||
* no longer take a new use_lock reference on it
|
||||
*/
|
||||
guard(mutex)(&client->ports_mutex);
|
||||
scoped_guard(write_lock_irq, &client->ports_lock) {
|
||||
if (!list_empty(&client->ports_list_head)) {
|
||||
list_add(&deleted_list, &client->ports_list_head);
|
||||
list_del_init(&client->ports_list_head);
|
||||
} else {
|
||||
INIT_LIST_HEAD(&deleted_list);
|
||||
}
|
||||
client->num_ports = 0;
|
||||
}
|
||||
|
||||
/* remove each port in deleted_list */
|
||||
list_for_each_entry_safe(port, tmp, &deleted_list, list) {
|
||||
list_del(&port->list);
|
||||
list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) {
|
||||
list_del_rcu(&port->list);
|
||||
client->num_ports--;
|
||||
snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
|
||||
port_delete(client, port);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user