mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Takashi Iwai <tiwai@suse.de>
To: linux-sound@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Subject: [PATCH 1/5] ALSA: seq: Use RCU for the port subscriber list
Date: Mon, 10 Aug 2026 15:37:02 +0200	[thread overview]
Message-ID: <20260810133711.42483-2-tiwai@suse.de> (raw)
In-Reply-To: <20260810133711.42483-1-tiwai@suse.de>

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 13d5e5d4725c) 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>
---
 sound/core/seq/seq_clientmgr.c | 25 ++++++++--------
 sound/core/seq/seq_ports.c     | 52 ++++++++++++++++------------------
 sound/core/seq/seq_ports.h     |  8 +++---
 3 files changed, 41 insertions(+), 44 deletions(-)

diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
index 23ec239640c3..77f5020f1873 100644
--- a/sound/core/seq/seq_clientmgr.c
+++ b/sound/core/seq/seq_clientmgr.c
@@ -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",
diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c
index 6612e92d801f..357c72ed0d3b 100644
--- a/sound/core/seq/seq_ports.c
+++ b/sound/core/seq/seq_ports.c
@@ -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;
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
index b689c0f4867c..12ad86bf1489 100644
--- a/sound/core/seq/seq_ports.h
+++ b/sound/core/seq/seq_ports.h
@@ -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);
 };
-- 
2.55.0


  reply	other threads:[~2026-08-10 13:37 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-10 13:37 [PATCH 0/5] ALSA: seq: Optimization with RCU Takashi Iwai
2026-08-10 13:37 ` Takashi Iwai [this message]
2026-08-10 13:37 ` [PATCH 2/5] ALSA: seq: Use RCU for the client port list Takashi Iwai
2026-08-10 13:37 ` [PATCH 3/5] ALSA: seq: Use RCU for the client table Takashi Iwai
2026-08-10 13:37 ` [PATCH 4/5] ALSA: seq: Use RCU for the virmidi file list Takashi Iwai
2026-08-10 13:37 ` [PATCH 5/5] ALSA: seq: Use RCU for the UMP client output substream Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260810133711.42483-2-tiwai@suse.de \
    --to=tiwai@suse.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®