git: 4c1004c00ff2 - main - snd_hdspe: Avoid allocation in the interrupt handler

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Mon, 17 Aug 2026 17:45:44 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=4c1004c00ff250f2a9e5bff5ae90c25d6b70feb3

commit 4c1004c00ff250f2a9e5bff5ae90c25d6b70feb3
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-07-21 03:31:57 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-17 17:44:41 +0000

    snd_hdspe: Avoid allocation in the interrupt handler
    
    Cache PCM children and drain interrupt callbacks before detach.
    Allocate the parent softc by its actual size.
    
    Reviewed by:    br
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D58370
---
 sys/dev/sound/pci/hdspe-pcm.c | 96 ++++++++++++++++++++++++++++++++-----------
 sys/dev/sound/pci/hdspe.c     | 26 ++++++------
 sys/dev/sound/pci/hdspe.h     |  9 ++++
 3 files changed, 94 insertions(+), 37 deletions(-)

diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c
index 0daa8fa8bb92..e0b6168efcba 100644
--- a/sys/dev/sound/pci/hdspe-pcm.c
+++ b/sys/dev/sound/pci/hdspe-pcm.c
@@ -391,35 +391,21 @@ hdspe_running(struct sc_info *sc)
 {
 	struct sc_pcminfo *scp;
 	struct sc_chinfo *ch;
-	device_t *devlist;
-	int devcount;
-	int i, j;
-	int err;
-
-	if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
-		goto bad;
+	unsigned int i;
+	int j;
 
-	for (i = 0; i < devcount; i++) {
-		scp = device_get_ivars(devlist[i]);
+	for (i = 0; i < HDSPE_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)
-				goto bad;
+				return (1);
 		}
 	}
 
-	free(devlist, M_TEMP);
-
 	return (0);
-bad:
-
-#if 0
-	device_printf(sc->dev, "hdspe is running\n");
-#endif
-
-	free(devlist, M_TEMP);
-
-	return (1);
 }
 
 static void
@@ -1034,12 +1020,15 @@ hdspe_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 = &hdspe_pcm_intr;
 
 	if (scp->hc->ports & HDSPE_CHAN_AIO_ALL)
@@ -1077,8 +1066,8 @@ hdspe_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) {
@@ -1088,19 +1077,78 @@ hdspe_pcm_attach(device_t dev)
 
 	mixer_init(dev, &hdspemixer_class, scp);
 
+	/* Register the PCM child for interrupt dispatch. */
+	mtx_lock(&sc->lock);
+	for (i = 0; i < HDSPE_MAX_PCMDEV; i++) {
+		if (sc->pcms[i] == NULL) {
+			sc->pcms[i] = scp;
+			break;
+		}
+	}
+	mtx_unlock(&sc->lock);
+	if (i == HDSPE_MAX_PCMDEV)
+		device_printf(dev, "Too many PCM children.\n");
+
 	return (0);
 }
 
+static int
+hdspe_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 < HDSPE_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
+hdspe_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 < HDSPE_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
 hdspe_pcm_detach(device_t dev)
 {
-	int err;
+	struct sc_pcminfo *scp;
+	int err, slot;
+
+	scp = device_get_ivars(dev);
+	slot = hdspe_pcm_quiesce(scp);
 
 	err = pcm_unregister(dev);
 	if (err) {
 		device_printf(dev, "Can't unregister device.\n");
+		if (slot >= 0)
+			hdspe_pcm_unquiesce(scp, slot, false);
 		return (err);
 	}
+	if (slot >= 0)
+		hdspe_pcm_unquiesce(scp, slot, true);
 
 	return (0);
 }
diff --git a/sys/dev/sound/pci/hdspe.c b/sys/dev/sound/pci/hdspe.c
index d08b42df0d82..d5a092a2e5f8 100644
--- a/sys/dev/sound/pci/hdspe.c
+++ b/sys/dev/sound/pci/hdspe.c
@@ -111,11 +111,8 @@ hdspe_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;
 
@@ -123,17 +120,18 @@ hdspe_intr(void *p)
 
 	status = hdspe_read_1(sc, HDSPE_STATUS_REG);
 	if (status & HDSPE_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 < HDSPE_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);
 		}
 
 		hdspe_write_1(sc, HDSPE_INTERRUPT_ACK, 0);
-		free(devlist, M_TEMP);
 	}
 
 	mtx_unlock(&sc->lock);
@@ -844,6 +842,7 @@ hdspe_attach(device_t dev)
 		    "Analog input level ('LowGain', '+4dBU', '-10dBV')");
 	}
 
+	cv_init(&sc->pcm_cv, "snd_hdspe pcm");
 	bus_attach_children(dev);
 	return (0);
 }
@@ -891,6 +890,7 @@ hdspe_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);
@@ -907,7 +907,7 @@ static device_method_t hdspe_methods[] = {
 static driver_t hdspe_driver = {
 	"hdspe",
 	hdspe_methods,
-	PCM_SOFTC_SIZE,
+	sizeof(struct sc_info),
 };
 
 DRIVER_MODULE(snd_hdspe, pci, hdspe_driver, 0, 0);
diff --git a/sys/dev/sound/pci/hdspe.h b/sys/dev/sound/pci/hdspe.h
index 85acbe9f7374..2ad955eb1784 100644
--- a/sys/dev/sound/pci/hdspe.h
+++ b/sys/dev/sound/pci/hdspe.h
@@ -211,6 +211,9 @@ struct sc_pcminfo {
 	struct hdspe_channel	*hc;
 };
 
+/* Maximum number of PCM children. */
+#define	HDSPE_MAX_PCMDEV		8
+
 /* HDSPe device private data */
 struct sc_info {
 	device_t		dev;
@@ -241,6 +244,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[HDSPE_MAX_PCMDEV];
+	unsigned int		pcm_refs[HDSPE_MAX_PCMDEV];
+	bool			pcm_detaching[HDSPE_MAX_PCMDEV];
+	struct cv		pcm_cv;
 };
 
 #define	hdspe_read_1(sc, regno)						\