git: 46b338381400 - main - snd_hdspe: Use busdma for DMA buffers

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Mon, 02 Nov 2026 16:01:35 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=46b338381400cb8106d6d2af945e7950553c16f4

commit 46b338381400cb8106d6d2af945e7950553c16f4
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-07-21 03:36:02 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-11-02 16:01:30 +0000

    snd_hdspe: Use busdma for DMA buffers
    
    Program DMA pages from busdma segments and synchronize buffer ownership.
    Unwind partially allocated resources on failure.
    
    Reviewed by:    br
    MFC after:      3 days
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D58372
---
 sys/dev/sound/pci/hdspe-pcm.c |  24 ++++++--
 sys/dev/sound/pci/hdspe.c     | 132 ++++++++++++++++++++++++++++++++----------
 sys/dev/sound/pci/hdspe.h     |   5 ++
 3 files changed, 126 insertions(+), 35 deletions(-)

diff --git a/sys/dev/sound/pci/hdspe-pcm.c b/sys/dev/sound/pci/hdspe-pcm.c
index 017142949d62..0914d8b13b0f 100644
--- a/sys/dev/sound/pci/hdspe-pcm.c
+++ b/sys/dev/sound/pci/hdspe-pcm.c
@@ -562,14 +562,13 @@ buffer_copy(struct sc_chinfo *ch)
 			length -= offset;
 		}
 	} else {
-		/* Position per channel is n times smaller than PCM. */
+		/* Snapshot the write position before synchronizing the buffer. */
 		pos = sndbuf_getfreeptr(ch->buffer) / n;
-		/* Get DMA buffer write position. */
 		dma_pos = hdspe_read_2(sc, HDSPE_STATUS_REG);
 		dma_pos &= HDSPE_BUF_POSITION_MASK;
-		/* Copy what is newly available. */
 		length = (dma_pos + HDSPE_CHANBUF_SIZE) - pos;
 		length %= HDSPE_CHANBUF_SIZE;
+		bus_dmamap_sync(sc->dmat, sc->rmap, BUS_DMASYNC_POSTREAD);
 	}
 
 	/* Position and length in samples (4 bytes). */
@@ -598,6 +597,12 @@ buffer_copy(struct sc_chinfo *ch)
 			row = hdspe_port_first(ports);
 	}
 
+	if (ch->dir == PCMDIR_PLAY && length > 0) {
+		bus_dmamap_sync(sc->dmat, sc->pmap, BUS_DMASYNC_PREWRITE);
+	} else if (ch->dir == PCMDIR_REC) {
+		bus_dmamap_sync(sc->dmat, sc->rmap, BUS_DMASYNC_PREREAD);
+	}
+
 	ch->position = ((pos + length) * 4) % HDSPE_CHANBUF_SIZE;
 }
 
@@ -617,6 +622,11 @@ clean(struct sc_chinfo *ch)
 	if (ch->dir == PCMDIR_PLAY)
 		buf = sc->pbuf;
 
+	if (ch->dir == PCMDIR_PLAY)
+		bus_dmamap_sync(sc->dmat, sc->pmap, BUS_DMASYNC_POSTWRITE);
+	else
+		bus_dmamap_sync(sc->dmat, sc->rmap, BUS_DMASYNC_POSTREAD);
+
 	/* Iterate through rows of ports with contiguous slots. */
 	ports = ch->ports;
 	row = hdspe_port_first_row(ports);
@@ -632,6 +642,12 @@ clean(struct sc_chinfo *ch)
 		row = hdspe_port_first_row(ports);
 	}
 
+	if (ch->dir == PCMDIR_PLAY)
+		bus_dmamap_sync(sc->dmat, sc->pmap, BUS_DMASYNC_PREWRITE);
+	else
+		bus_dmamap_sync(sc->dmat, sc->rmap,
+		    BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
+
 	ch->position = 0;
 
 	return (0);
@@ -762,8 +778,8 @@ hdspechan_trigger(kobj_t obj, void *data, int go)
 #if 0
 		device_printf(scp->dev, "hdspechan_trigger(): stop or abort\n");
 #endif
-		clean(ch);
 		hdspechan_enable(ch, 0);
+		clean(ch);
 		hdspe_stop_audio(sc);
 		break;
 
diff --git a/sys/dev/sound/pci/hdspe.c b/sys/dev/sound/pci/hdspe.c
index d5a092a2e5f8..b129083f4b60 100644
--- a/sys/dev/sound/pci/hdspe.c
+++ b/sys/dev/sound/pci/hdspe.c
@@ -137,17 +137,57 @@ hdspe_intr(void *p)
 	mtx_unlock(&sc->lock);
 }
 
+struct hdspe_dma_map {
+	uint32_t	*pages;
+	unsigned int	npages;
+	int		error;
+};
+
 static void
-hdspe_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+hdspe_dma_map_pages(void *arg, bus_dma_segment_t *segs, int nseg, int error)
 {
-#if 0
-	device_printf(sc->dev, "hdspe_dmapsetmap()\n");
-#endif
+	struct hdspe_dma_map *map;
+	bus_addr_t addr;
+	bus_size_t len;
+	int i;
+
+	map = arg;
+	map->npages = 0;
+	map->error = error;
+	if (error != 0)
+		return;
+
+	for (i = 0; i < nseg; i++) {
+		addr = segs[i].ds_addr;
+		len = segs[i].ds_len;
+		if (addr % HDSPE_DMA_PAGE_SIZE != 0 ||
+		    len % HDSPE_DMA_PAGE_SIZE != 0) {
+			map->error = EINVAL;
+			return;
+		}
+		while (len != 0) {
+			if (map->npages == HDSPE_DMA_PAGES) {
+				map->error = EFBIG;
+				return;
+			}
+			map->pages[map->npages++] = (uint32_t)addr;
+			addr += HDSPE_DMA_PAGE_SIZE;
+			len -= HDSPE_DMA_PAGE_SIZE;
+		}
+	}
+	if (map->npages != HDSPE_DMA_PAGES)
+		map->error = EFBIG;
 }
 
 static int
 hdspe_alloc_resources(struct sc_info *sc)
 {
+	struct hdspe_dma_map map;
+	bool pmap_loaded, rmap_loaded;
+	int error;
+
+	pmap_loaded = false;
+	rmap_loaded = false;
 
 	/* Allocate resource. */
 	sc->csid = PCIR_BAR(0);
@@ -171,76 +211,103 @@ hdspe_alloc_resources(struct sc_info *sc)
 	    bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV,
 		NULL, hdspe_intr, sc, &sc->ih)) {
 		device_printf(sc->dev, "Unable to alloc interrupt resource.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
-	/* Allocate DMA resources. */
+	/* Allocate page-aligned DMA segments for each direction. */
 	if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev),
-		/*alignment*/4,
+		/*alignment*/HDSPE_DMA_PAGE_SIZE,
 		/*boundary*/0,
 		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
 		/*highaddr*/BUS_SPACE_MAXADDR,
 		/*filter*/NULL,
 		/*filterarg*/NULL,
-		/*maxsize*/2 * HDSPE_DMASEGSIZE,
-		/*nsegments*/2,
+		/*maxsize*/HDSPE_DMASEGSIZE,
+		/*nsegments*/HDSPE_DMA_PAGES,
 		/*maxsegsz*/HDSPE_DMASEGSIZE,
 		/*flags*/0,
 		/*lockfunc*/NULL,
 		/*lockarg*/NULL,
 		/*dmatag*/&sc->dmat) != 0) {
 		device_printf(sc->dev, "Unable to create dma tag.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
 	sc->bufsize = HDSPE_DMASEGSIZE;
 
 	/* pbuf (play buffer). */
-	if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_WAITOK,
-	    &sc->pmap)) {
+	if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf,
+	    BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->pmap)) {
 		device_printf(sc->dev, "Can't alloc pbuf.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
-	if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize,
-	    hdspe_dmapsetmap, sc, BUS_DMA_NOWAIT)) {
+	map.pages = sc->paddr;
+	map.error = 0;
+	error = bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize,
+	    hdspe_dma_map_pages, &map, BUS_DMA_NOWAIT);
+	if (error == 0)
+		pmap_loaded = true;
+	if (error != 0 || map.error != 0) {
 		device_printf(sc->dev, "Can't load pbuf.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
 	/* rbuf (rec buffer). */
-	if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_WAITOK,
-	    &sc->rmap)) {
+	if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf,
+	    BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->rmap)) {
 		device_printf(sc->dev, "Can't alloc rbuf.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
-	if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize,
-	    hdspe_dmapsetmap, sc, BUS_DMA_NOWAIT)) {
+	map.pages = sc->raddr;
+	map.error = 0;
+	error = bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize,
+	    hdspe_dma_map_pages, &map, BUS_DMA_NOWAIT);
+	if (error == 0)
+		rmap_loaded = true;
+	if (error != 0 || map.error != 0) {
 		device_printf(sc->dev, "Can't load rbuf.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
-	bzero(sc->pbuf, sc->bufsize);
-	bzero(sc->rbuf, sc->bufsize);
+	/* Synchronize the buffers before the first DMA cycle. */
+	bus_dmamap_sync(sc->dmat, sc->pmap, BUS_DMASYNC_PREWRITE);
+	bus_dmamap_sync(sc->dmat, sc->rmap, BUS_DMASYNC_PREREAD);
 
 	return (0);
+
+fail:
+	if (sc->ih != NULL)
+		bus_teardown_intr(sc->dev, sc->irq, sc->ih);
+	if (rmap_loaded)
+		bus_dmamap_unload(sc->dmat, sc->rmap);
+	if (sc->rbuf != NULL)
+		bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap);
+	if (pmap_loaded)
+		bus_dmamap_unload(sc->dmat, sc->pmap);
+	if (sc->pbuf != NULL)
+		bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap);
+	if (sc->dmat != NULL)
+		bus_dma_tag_destroy(sc->dmat);
+	if (sc->irq != NULL)
+		bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irqid, sc->irq);
+	if (sc->cs != NULL)
+		bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->csid, sc->cs);
+	return (ENXIO);
 }
 
 static void
 hdspe_map_dmabuf(struct sc_info *sc)
 {
-	uint32_t paddr, raddr;
 	int i;
 
-	paddr = vtophys(sc->pbuf);
-	raddr = vtophys(sc->rbuf);
-
-	for (i = 0; i < HDSPE_MAX_SLOTS * 16; i++) {
+	/* Program the hardware page tables with DMA page addresses. */
+	for (i = 0; i < HDSPE_DMA_PAGES; i++) {
 		hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_OUT + 4 * i,
-                    paddr + i * 4096);
+		    sc->paddr[i]);
 		hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_IN + 4 * i,
-                    raddr + i * 4096);
+		    sc->raddr[i]);
 	}
 }
 
@@ -770,6 +837,7 @@ hdspe_attach(device_t dev)
 	err = hdspe_alloc_resources(sc);
 	if (err) {
 		device_printf(dev, "Unable to allocate system resources.\n");
+		mtx_destroy(&sc->lock);
 		return (ENXIO);
 	}
 
@@ -857,6 +925,8 @@ static void
 hdspe_dmafree(struct sc_info *sc)
 {
 
+	bus_dmamap_sync(sc->dmat, sc->rmap, BUS_DMASYNC_POSTREAD);
+	bus_dmamap_sync(sc->dmat, sc->pmap, BUS_DMASYNC_POSTWRITE);
 	bus_dmamap_unload(sc->dmat, sc->rmap);
 	bus_dmamap_unload(sc->dmat, sc->pmap);
 	bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap);
diff --git a/sys/dev/sound/pci/hdspe.h b/sys/dev/sound/pci/hdspe.h
index 2ad955eb1784..05c88594d585 100644
--- a/sys/dev/sound/pci/hdspe.h
+++ b/sys/dev/sound/pci/hdspe.h
@@ -125,6 +125,8 @@
 #define	HDSPE_CHANBUF_SAMPLES		(16 * 1024)
 #define	HDSPE_CHANBUF_SIZE		(4 * HDSPE_CHANBUF_SAMPLES)
 #define	HDSPE_DMASEGSIZE		(HDSPE_CHANBUF_SIZE * HDSPE_MAX_SLOTS)
+#define	HDSPE_DMA_PAGE_SIZE		4096
+#define	HDSPE_DMA_PAGES			(HDSPE_DMASEGSIZE / HDSPE_DMA_PAGE_SIZE)
 
 #define	HDSPE_CHAN_AIO_LINE		(1 << 0)
 #define	HDSPE_CHAN_AIO_EXT		(1 << 1)
@@ -240,6 +242,9 @@ struct sc_info {
 	uint32_t		bufsize;
 	bus_dmamap_t		pmap;
 	bus_dmamap_t		rmap;
+	/* DMA buffer page addresses. */
+	uint32_t		paddr[HDSPE_DMA_PAGES];
+	uint32_t		raddr[HDSPE_DMA_PAGES];
 	uint32_t		period;
 	uint32_t		speed;
 	uint32_t		force_period;