git: 74db53d5d765 - main - snd_hdsp: Avoid allocation in the interrupt handler

Kevin Bowling <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src,gmane.os.freebsd.current.scm
Message-ID <[email protected]>
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=74db53d5d7657d0508940d1193f05a39df85434a

commit 74db53d5d7657d0508940d1193f05a39df85434a
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-08-22 01:13:12 +0000
Commit:     Kevin Bowling <[email protected]>
CommitDate: 2026-08-22 01:13:26 +0000

    snd_hdsp: Avoid allocation in the interrupt handler
    
    Cache PCM children instead of calling device_get_children() from the
    interrupt handler.  Drain callbacks before child detach so cached
    pointers cannot outlive the PCM softc.  Allocate the parent softc by
    its actual size.
    
    This mirrors snd_hdspe's interrupt dispatch and detach lifecycle.
    
    Reported by:    christos
    MFC after:      1 week
---
 sys/dev/sound/pci/hdsp-pcm.c | 103 +++++++++++++++++++++++++++++++------------
 sys/dev/sound/pci/hdsp.c     |  26 +++++------
 sys/dev/sound/pci/hdsp.h     |   9 ++++
 3 files changed, 96 insertions(+), 42 deletions(-)

diff --git a/sys/dev/sound/pci/hdsp-pcm.c b/sys/dev/sound/pci/hdsp-pcm.c
index 93ee576a3a67..b4df8ca90555 100644
--- a/sys/dev/sound/pci/hdsp-pcm.c
+++ b/sys/dev/sound/pci/hdsp-pcm.c
@@ -402,38 +402,21 @@ hdsp_running(struct sc_info *sc)
 {
 	struct sc_pcminfo *scp;
 	struct sc_chinfo *ch;
-	device_t *devlist;
-	int devcount;
-	int i, j;
-	int running;
+	unsigned int i;
+	int j;
 
-	running = 0;
-
-	devlist = NULL;
-	devcount = 0;
-
-	if (device_get_children(sc->dev, &devlist, &devcount) != 0)
-		running = 1;	/* On error, avoid channel config changes. */
-
-	for (i = 0; running == 0 && i < devcount; i++) {
-		scp = device_get_ivars(devlist[i]);
+	for (i = 0; i < HDSP_MAX_PCMDEV; i++) {
+		scp = sc->pcms[i];
+		if (scp == NULL)
+			continue;
 		for (j = 0; j < scp->chnum; j++) {
 			ch = &scp->chan[j];
-			if (ch->run) {
-				running = 1;
-				break;
-			}
+			if (ch->run)
+				return (1);
 		}
 	}
 
-#if 0
-	if (running == 1)
-		device_printf(sc->dev, "hdsp is running\n");
-#endif
-
-	free(devlist, M_TEMP);
-
-	return (running);
+	return (0);
 }
 
 static void
@@ -1043,12 +1026,15 @@ hdsp_pcm_attach(device_t dev)
 {
 	char status[SND_STATUSLEN];
 	struct sc_pcminfo *scp;
+	struct sc_info *sc;
 	const char *buf;
 	uint32_t pcm_flags;
 	int err;
 	int play, rec;
+	int i;
 
 	scp = device_get_ivars(dev);
+	sc = scp->sc;
 	scp->ih = &hdsp_pcm_intr;
 
 	if (scp->hc->ports & HDSP_CHAN_9632_ALL)
@@ -1086,8 +1072,8 @@ hdsp_pcm_attach(device_t dev)
 	}
 
 	snprintf(status, SND_STATUSLEN, "port 0x%jx irq %jd on %s",
-	    rman_get_start(scp->sc->cs),
-	    rman_get_start(scp->sc->irq),
+	    rman_get_start(sc->cs),
+	    rman_get_start(sc->irq),
 	    device_get_nameunit(device_get_parent(dev)));
 	err = pcm_register(dev, status);
 	if (err) {
@@ -1097,19 +1083,78 @@ hdsp_pcm_attach(device_t dev)
 
 	mixer_init(dev, &hdspmixer_class, scp);
 
+	/* Register the PCM child for interrupt dispatch. */
+	mtx_lock(&sc->lock);
+	for (i = 0; i < HDSP_MAX_PCMDEV; i++) {
+		if (sc->pcms[i] == NULL) {
+			sc->pcms[i] = scp;
+			break;
+		}
+	}
+	mtx_unlock(&sc->lock);
+	if (i == HDSP_MAX_PCMDEV)
+		device_printf(dev, "Too many PCM children.\n");
+
 	return (0);
 }
 
+static int
+hdsp_pcm_quiesce(struct sc_pcminfo *scp)
+{
+	struct sc_info *sc;
+	unsigned int i;
+	int slot;
+
+	sc = scp->sc;
+	slot = -1;
+	mtx_lock(&sc->lock);
+	for (i = 0; i < HDSP_MAX_PCMDEV; i++) {
+		if (sc->pcms[i] == scp) {
+			sc->pcm_detaching[i] = true;
+			while (sc->pcm_refs[i] != 0)
+				cv_wait(&sc->pcm_cv, &sc->lock);
+			slot = (int)i;
+			break;
+		}
+	}
+	mtx_unlock(&sc->lock);
+	return (slot);
+}
+
+static void
+hdsp_pcm_unquiesce(struct sc_pcminfo *scp, int slot, bool detach)
+{
+	struct sc_info *sc;
+
+	sc = scp->sc;
+	mtx_lock(&sc->lock);
+	KASSERT(slot >= 0 && slot < HDSP_MAX_PCMDEV &&
+	    sc->pcms[slot] == scp && sc->pcm_detaching[slot],
+	    ("invalid PCM slot %d", slot));
+	if (detach)
+		sc->pcms[slot] = NULL;
+	sc->pcm_detaching[slot] = false;
+	mtx_unlock(&sc->lock);
+}
+
 static int
 hdsp_pcm_detach(device_t dev)
 {
-	int err;
+	struct sc_pcminfo *scp;
+	int err, slot;
+
+	scp = device_get_ivars(dev);
+	slot = hdsp_pcm_quiesce(scp);
 
 	err = pcm_unregister(dev);
 	if (err) {
 		device_printf(dev, "Can't unregister device.\n");
+		if (slot >= 0)
+			hdsp_pcm_unquiesce(scp, slot, false);
 		return (err);
 	}
+	if (slot >= 0)
+		hdsp_pcm_unquiesce(scp, slot, true);
 
 	return (0);
 }
diff --git a/sys/dev/sound/pci/hdsp.c b/sys/dev/sound/pci/hdsp.c
index 4fd3647edf51..b52b53231e50 100644
--- a/sys/dev/sound/pci/hdsp.c
+++ b/sys/dev/sound/pci/hdsp.c
@@ -101,11 +101,8 @@ hdsp_intr(void *p)
 {
 	struct sc_pcminfo *scp;
 	struct sc_info *sc;
-	device_t *devlist;
-	int devcount;
+	unsigned int i;
 	int status;
-	int err;
-	int i;
 
 	sc = (struct sc_info *)p;
 
@@ -113,17 +110,18 @@ hdsp_intr(void *p)
 
 	status = hdsp_read_1(sc, HDSP_STATUS_REG);
 	if (status & HDSP_AUDIO_IRQ_PENDING) {
-		if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
-			return;
-
-		for (i = 0; i < devcount; i++) {
-			scp = device_get_ivars(devlist[i]);
-			if (scp->ih != NULL)
-				scp->ih(scp);
+		for (i = 0; i < HDSP_MAX_PCMDEV; i++) {
+			scp = sc->pcms[i];
+			if (scp == NULL || sc->pcm_detaching[i] ||
+			    scp->ih == NULL)
+				continue;
+			sc->pcm_refs[i]++;
+			scp->ih(scp);
+			if (--sc->pcm_refs[i] == 0 && sc->pcm_detaching[i])
+				cv_broadcast(&sc->pcm_cv);
 		}
 
 		hdsp_write_1(sc, HDSP_INTERRUPT_ACK, 0);
-		free(devlist, M_TEMP);
 	}
 
 	mtx_unlock(&sc->lock);
@@ -952,6 +950,7 @@ hdsp_attach(device_t dev)
 		    "Analog input level ('LowGain', '+4dBU', '-10dBV')");
 	}
 
+	cv_init(&sc->pcm_cv, "snd_hdsp pcm");
 	bus_attach_children(dev);
 	return (0);
 }
@@ -999,6 +998,7 @@ hdsp_detach(device_t dev)
 		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq);
 	if (sc->cs)
 		bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->cs);
+	cv_destroy(&sc->pcm_cv);
 	mtx_destroy(&sc->lock);
 
 	return (0);
@@ -1015,7 +1015,7 @@ static device_method_t hdsp_methods[] = {
 static driver_t hdsp_driver = {
 	"hdsp",
 	hdsp_methods,
-	PCM_SOFTC_SIZE,
+	sizeof(struct sc_info),
 };
 
 DRIVER_MODULE(snd_hdsp, pci, hdsp_driver, 0, 0);
diff --git a/sys/dev/sound/pci/hdsp.h b/sys/dev/sound/pci/hdsp.h
index 18737709ec3f..81a0be7dc1e6 100644
--- a/sys/dev/sound/pci/hdsp.h
+++ b/sys/dev/sound/pci/hdsp.h
@@ -220,6 +220,9 @@ struct sc_pcminfo {
 	struct hdsp_channel	*hc;
 };
 
+/* Maximum number of PCM children. */
+#define	HDSP_MAX_PCMDEV		4
+
 /* HDSP device private data */
 struct sc_info {
 	device_t		dev;
@@ -249,6 +252,12 @@ struct sc_info {
 	uint32_t		speed;
 	uint32_t		force_period;
 	uint32_t		force_speed;
+
+	/* PCM children used for interrupt dispatch. */
+	struct sc_pcminfo	*pcms[HDSP_MAX_PCMDEV];
+	unsigned int		pcm_refs[HDSP_MAX_PCMDEV];
+	bool			pcm_detaching[HDSP_MAX_PCMDEV];
+	struct cv		pcm_cv;
 };
 
 #define	hdsp_read_1(sc, regno)						\
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.