git: 5355c97e129a - main - snd_hdsp: Use busdma for DMA buffers

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

URL: https://cgit.FreeBSD.org/src/commit/?id=5355c97e129a004f013570be0b7a99c8610e1757

commit 5355c97e129a004f013570be0b7a99c8610e1757
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-11-02 16:09:33 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-11-02 16:09:55 +0000

    snd_hdsp: Use busdma for DMA buffers
    
    Program DMA buffer registers with the bus addresses returned by busdma.
    Require one full-size contiguous DMA segment per direction and validate
    its alignment and 32-bit address range.
    
    Synchronize buffer ownership around playback, capture, clearing, and
    teardown.  Disable channels before clearing their DMA buffers, and unwind
    partially allocated resources on failure.
    
    This mirrors snd_hdspe's DMA handling, adapted to HDSP's single base
    address per direction.
    
    MFC after:      3 days
    Sponsored by:   BBOX.io
---
 sys/dev/sound/pci/hdsp-pcm.c |  24 +++++++--
 sys/dev/sound/pci/hdsp.c     | 113 ++++++++++++++++++++++++++++++++-----------
 sys/dev/sound/pci/hdsp.h     |   3 ++
 3 files changed, 107 insertions(+), 33 deletions(-)

diff --git a/sys/dev/sound/pci/hdsp-pcm.c b/sys/dev/sound/pci/hdsp-pcm.c
index 24969f0e22b0..ad5a040398db 100644
--- a/sys/dev/sound/pci/hdsp-pcm.c
+++ b/sys/dev/sound/pci/hdsp-pcm.c
@@ -564,15 +564,14 @@ buffer_copy(struct sc_chinfo *ch)
 			length -= offset;
 		}
 	} else {
-		/* Buffer position scaled down to a single channel. */
+		/* Snapshot the write position before synchronizing the buffer. */
 		pos = sndbuf_getfreeptr(ch->buffer) / channels;
-		/* Get DMA buffer write position. */
 		dma_pos = hdsp_read_2(sc, HDSP_STATUS_REG);
 		dma_pos &= HDSP_BUF_POSITION_MASK;
 		dma_pos %= buffer_size;
-		/* Copy what is newly available. */
 		length = (dma_pos + buffer_size) - pos;
 		length %= buffer_size;
+		bus_dmamap_sync(sc->dmat, sc->rmap, BUS_DMASYNC_POSTREAD);
 	}
 
 	/* Position and length in samples (4 bytes). */
@@ -607,6 +606,12 @@ buffer_copy(struct sc_chinfo *ch)
 		row = hdsp_slot_first_row(slots);
 	}
 
+	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) % buffer_size;
 }
 
@@ -626,6 +631,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 all of the channel's slots. */
 	slots = hdsp_port_slot_map(ch->ports, sc->speed);
 	slot = hdsp_slot_first(slots);
@@ -638,6 +648,12 @@ clean(struct sc_chinfo *ch)
 		slot = hdsp_slot_first(slots);
 	}
 
+	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);
@@ -775,8 +791,8 @@ hdspchan_trigger(kobj_t obj, void *data, int go)
 #if 0
 		device_printf(scp->dev, "hdspchan_trigger(): stop or abort\n");
 #endif
-		clean(ch);
 		hdspchan_enable(ch, 0);
+		clean(ch);
 		hdsp_stop_audio(sc);
 		break;
 
diff --git a/sys/dev/sound/pci/hdsp.c b/sys/dev/sound/pci/hdsp.c
index b52b53231e50..3c92eb3e319a 100644
--- a/sys/dev/sound/pci/hdsp.c
+++ b/sys/dev/sound/pci/hdsp.c
@@ -127,17 +127,43 @@ hdsp_intr(void *p)
 	mtx_unlock(&sc->lock);
 }
 
+struct hdsp_dma_map {
+	uint32_t	addr;
+	int		error;
+};
+
 static void
-hdsp_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+hdsp_dma_map_buffer(void *arg, bus_dma_segment_t *segs, int nseg, int error)
 {
-#if 0
-	device_printf(sc->dev, "hdsp_dmapsetmap()\n");
-#endif
+	struct hdsp_dma_map *map;
+
+	map = arg;
+	map->error = error;
+	if (error != 0)
+		return;
+
+	/* The hardware accepts one contiguous DMA buffer per direction. */
+	if (nseg != 1 || segs[0].ds_len != HDSP_DMASEGSIZE) {
+		map->error = EFBIG;
+		return;
+	}
+	if (segs[0].ds_addr % 4 != 0 ||
+	    segs[0].ds_addr > BUS_SPACE_MAXADDR_32BIT - (HDSP_DMASEGSIZE - 1)) {
+		map->error = EINVAL;
+		return;
+	}
+	map->addr = (uint32_t)segs[0].ds_addr;
 }
 
 static int
 hdsp_alloc_resources(struct sc_info *sc)
 {
+	struct hdsp_dma_map map;
+	bool pmap_loaded, rmap_loaded;
+	int error;
+
+	pmap_loaded = false;
+	rmap_loaded = false;
 
 	/* Allocate resource. */
 	sc->csid = PCIR_BAR(0);
@@ -161,10 +187,10 @@ hdsp_alloc_resources(struct sc_info *sc)
 	    bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV,
 		NULL, hdsp_intr, sc, &sc->ih)) {
 		device_printf(sc->dev, "Unable to alloc interrupt resource.\n");
-		return (ENXIO);
+		goto fail;
 	}
 
-	/* Allocate DMA resources. */
+	/* Allocate one contiguous DMA segment for each direction. */
 	if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev),
 		/*alignment*/4,
 		/*boundary*/0,
@@ -172,61 +198,87 @@ hdsp_alloc_resources(struct sc_info *sc)
 		/*highaddr*/BUS_SPACE_MAXADDR,
 		/*filter*/NULL,
 		/*filterarg*/NULL,
-		/*maxsize*/2 * HDSP_DMASEGSIZE,
-		/*nsegments*/2,
+		/*maxsize*/HDSP_DMASEGSIZE,
+		/*nsegments*/1,
 		/*maxsegsz*/HDSP_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 = HDSP_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,
-	    hdsp_dmapsetmap, sc, BUS_DMA_NOWAIT)) {
+	map.error = 0;
+	error = bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize,
+	    hdsp_dma_map_buffer, &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;
 	}
+	sc->paddr = map.addr;
 
 	/* 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,
-	    hdsp_dmapsetmap, sc, BUS_DMA_NOWAIT)) {
+	map.error = 0;
+	error = bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize,
+	    hdsp_dma_map_buffer, &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;
 	}
+	sc->raddr = map.addr;
 
-	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
 hdsp_map_dmabuf(struct sc_info *sc)
 {
-	uint32_t paddr, raddr;
-
-	paddr = vtophys(sc->pbuf);
-	raddr = vtophys(sc->rbuf);
 
-	hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_OUT, paddr);
-	hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_IN, raddr);
+	hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_OUT, sc->paddr);
+	hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_IN, sc->raddr);
 }
 
 static const char *
@@ -878,6 +930,7 @@ hdsp_attach(device_t dev)
 	err = hdsp_alloc_resources(sc);
 	if (err) {
 		device_printf(dev, "Unable to allocate system resources.\n");
+		mtx_destroy(&sc->lock);
 		return (ENXIO);
 	}
 
@@ -965,6 +1018,8 @@ static void
 hdsp_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/hdsp.h b/sys/dev/sound/pci/hdsp.h
index 81a0be7dc1e6..b30845fb4bb9 100644
--- a/sys/dev/sound/pci/hdsp.h
+++ b/sys/dev/sound/pci/hdsp.h
@@ -248,6 +248,9 @@ struct sc_info {
 	uint32_t		bufsize;
 	bus_dmamap_t		pmap;
 	bus_dmamap_t		rmap;
+	/* DMA buffer bus addresses. */
+	uint32_t		paddr;
+	uint32_t		raddr;
 	uint32_t		period;
 	uint32_t		speed;
 	uint32_t		force_period;