git: 47ae0a869c7d - main - sound: Start each channel individually

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Wed, 17 Jun 2026 10:52:49 UTC
The branch main has been updated by christos:

URL: https://cgit.FreeBSD.org/src/commit/?id=47ae0a869c7db693ffb1ac058d63dcb79c4e68a8

commit 47ae0a869c7db693ffb1ac058d63dcb79c4e68a8
Author:     Goran Mekić <meka@tilda.center>
AuthorDate: 2026-06-17 10:34:05 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2026-06-17 10:34:45 +0000

    sound: Start each channel individually
    
    Unlock all members before starting any of them. Holding multiple channel
    locks while calling chn_start() on a virtual channel can trigger the
    parent, which acquires PCM_LOCK() while other virtual channels are still
    locked -- a lock order reversal.
    
    Reviewed by:    christos
    Differential Revision:  https://reviews.freebsd.org/D57399
---
 sys/dev/sound/pcm/dsp.c | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c
index 05fdc18e31f8..1fa665b6b6cf 100644
--- a/sys/dev/sound/pcm/dsp.c
+++ b/sys/dev/sound/pcm/dsp.c
@@ -2742,16 +2742,29 @@ dsp_oss_syncstart(int sg_id)
 
 	/* Proceed only if no errors encountered. */
 	if (ret == 0) {
-		/* Launch channels */
-		while ((sm = SLIST_FIRST(&sg->members)) != NULL) {
-			SLIST_REMOVE_HEAD(&sg->members, link);
+		/*
+		 * Unlock all members before starting any of them.
+		 * Holding multiple channel locks while calling chn_start()
+		 * on a virtual channel can trigger the parent, which
+		 * acquires PCM_LOCK() while other virtual channels are
+		 * still locked -- a lock order reversal.
+		 */
+		SLIST_FOREACH(sm, &sg->members, link) {
+			sm->ch->sm = NULL;
+			sm->ch->flags &= ~CHN_F_NOTRIGGER;
+			CHN_UNLOCK(sm->ch);
+		}
 
+		/*
+		 * Start each channel individually, then remove it from
+		 * the sync group and free its member structure.
+		 */
+		while ((sm = SLIST_FIRST(&sg->members)) != NULL) {
 			c = sm->ch;
-			c->sm = NULL;
+			CHN_LOCK(c);
 			chn_start(c, 1);
-			c->flags &= ~CHN_F_NOTRIGGER;
 			CHN_UNLOCK(c);
-
+			SLIST_REMOVE_HEAD(&sg->members, link);
 			free(sm, M_DEVBUF);
 		}