git: 4c1004c00ff2 - main - snd_hdspe: 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=4c1004c00ff250f2a9e5bff5ae90c25d6b70feb3

commit 4c1004c00ff250f2a9e5bff5ae90c25d6b70feb3
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-07-21 03:31:57 +0000
Commit:     Kevin Bowling <[email protected]>
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)						\
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.