aconnect occasionally causes kernel oopses

Takashi Iwai tiwai at suse.de
Mon Aug 2 19:12:56 CEST 2021


On Mon, 02 Aug 2021 17:21:17 +0200,
folkert wrote:
> 
> [ kernel bug if repeatingly aconnect'ing midi devices ]
> 
> > Does this happen if you do reconnect of kernel sequencer client?
> > You can use snd-virmidi as well as snd-dummy.
> > I'm asking it because it'll simplify the test a lot, which will be
> > almost self-contained.
> 
> Like this?
> 
> root at lappiemctopface:~# aplaymidi -l
>  Port    Client name                      Port name
>  14:0    Midi Through                     Midi Through Port-0
>  20:0    Virtual Raw MIDI 1-0             VirMIDI 1-0
>  21:0    Virtual Raw MIDI 1-1             VirMIDI 1-1
>  22:0    Virtual Raw MIDI 1-2             VirMIDI 1-2
>  23:0    Virtual Raw MIDI 1-3             VirMIDI 1-3
> 128:0    rtpmidi lappiemctopface          Network
> 128:1    rtpmidi lappiemctopface          metronoom
> 128:2    rtpmidi lappiemctopface          AppleMidi2IPMidiBridge
> 128:3    rtpmidi lappiemctopface          oensoens
> 130:0    FLUID Synth (11462)              Synth input port (11462:0)
> 
> and then:
> 
> root at lappiemctopface:~# cat test.sh 
> while true
> do
> 	aconnect 20:0 21:0
> 	aconnect -d 20:0 21:0
> done
> 
> root at lappiemctopface:~# for i in `seq 0 3` ; do (./test.sh &) ; done
> 
> This hard locks-up my laptop: it doesn't even respond to capslock (led
> on/off) anymore nor the ctrl+prtscr+alt+b combination.

Thanks, that's really helpful!
I see the possible race now.

Could you try the quick fix below?


Takashi

---
--- a/sound/core/seq/seq_ports.c
+++ b/sound/core/seq/seq_ports.c
@@ -200,6 +200,17 @@ get_subscriber(struct list_head *p, bool is_src)
 		return list_entry(p, struct snd_seq_subscribers, dest_list);
 }
 
+static void subscriber_get(struct snd_seq_subscribers *subs)
+{
+	atomic_inc(&subs->ref_count);
+}
+
+static void subscriber_put(struct snd_seq_subscribers *subs)
+{
+	if (atomic_dec_and_test(&subs->ref_count))
+		kfree(subs);
+}
+
 /*
  * remove all subscribers on the list
  * this is called from port_delete, for each src and dest list.
@@ -228,8 +239,7 @@ static void clear_subscriber_list(struct snd_seq_client *client,
 			 * we decrease the counter, and when both ports are deleted
 			 * remove the subscriber info
 			 */
-			if (atomic_dec_and_test(&subs->ref_count))
-				kfree(subs);
+			subscriber_put(subs);
 			continue;
 		}
 
@@ -489,6 +499,8 @@ static int check_and_subscribe_port(struct snd_seq_client *client,
 			s = get_subscriber(p, is_src);
 			if (match_subs_info(&subs->info, &s->info))
 				goto __error;
+			if (!subs->ready)
+				goto __error;
 		}
 	}
 
@@ -505,7 +517,7 @@ static int check_and_subscribe_port(struct snd_seq_client *client,
 	else
 		list_add_tail(&subs->dest_list, &grp->list_head);
 	grp->exclusive = exclusive;
-	atomic_inc(&subs->ref_count);
+	subscriber_get(subs);
 	write_unlock_irq(&grp->list_lock);
 	err = 0;
 
@@ -536,6 +548,7 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client,
 	if (!empty)
 		unsubscribe_port(client, port, grp, &subs->info, ack);
 	up_write(&grp->list_mutex);
+	subscriber_put(subs);
 }
 
 /* connect two ports */
@@ -555,7 +568,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector,
 		return -ENOMEM;
 
 	subs->info = *info;
-	atomic_set(&subs->ref_count, 0);
+	atomic_set(&subs->ref_count, 1);
 	INIT_LIST_HEAD(&subs->src_list);
 	INIT_LIST_HEAD(&subs->dest_list);
 
@@ -572,13 +585,14 @@ int snd_seq_port_connect(struct snd_seq_client *connector,
 	if (err < 0)
 		goto error_dest;
 
+	subs->ready = true;
 	return 0;
 
  error_dest:
 	delete_and_unsubscribe_port(src_client, src_port, subs, true,
 				    connector->number != src_client->number);
  error:
-	kfree(subs);
+	subscriber_put(subs);
 	return err;
 }
 
@@ -597,8 +611,8 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector,
 	down_write(&src->list_mutex);
 	/* look for the connection */
 	list_for_each_entry(subs, &src->list_head, src_list) {
-		if (match_subs_info(info, &subs->info)) {
-			atomic_dec(&subs->ref_count); /* mark as not ready */
+		if (match_subs_info(info, &subs->info) && subs->ready) {
+			subs->ready = false;
 			err = 0;
 			break;
 		}
@@ -611,7 +625,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector,
 				    connector->number != src_client->number);
 	delete_and_unsubscribe_port(dest_client, dest_port, subs, false,
 				    connector->number != dest_client->number);
-	kfree(subs);
+	subscriber_put(subs);
 	return 0;
 }
 
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
index b1f2c4943174..d9644d5f109e 100644
--- a/sound/core/seq/seq_ports.h
+++ b/sound/core/seq/seq_ports.h
@@ -30,6 +30,7 @@ struct snd_seq_subscribers {
 	struct list_head src_list;	/* link of sources */
 	struct list_head dest_list;	/* link of destinations */
 	atomic_t ref_count;
+	bool ready;
 };
 
 struct snd_seq_port_subs_info {


More information about the Alsa-devel mailing list