From: Alexandre Ratchov Subject: audio: Use the full audio ring buffer for DMA To: tech@openbsd.org Date: Sat, 15 Nov 2025 09:05:37 +0100 The diff below is to use the full audio ring buffer for DMA instead of a small portion of it. This makes underrun detection much more reliable. Certain audio interfaces expose their reader/writer pointer within the DMA buffer (ex. azalia, envy), which is used by the interrupt handler to track the hardware position and detect underruns. This works as long as the interrupt is not delayed enough for the pointer to wrap. Userland still uses the same buffer size as before, which now corresponds to a sliding window within the DMA ring buffer. The audio latency is unchanged and there's no user-visible behavior change during normal operation. For instance, with 16-bit/stereo/48kHz this change gives around 340ms for the interrupt to fire before the pointer wraps, which is very safe on today kernels. Any tests for regressions are welcome, especially on busy systems and/or low-latency setups. Index: audio.c =================================================================== RCS file: /cvs/src/sys/dev/audio.c,v diff -u -p -r1.212 audio.c --- audio.c 2 Nov 2025 14:33:06 -0000 1.212 +++ audio.c 15 Nov 2025 07:18:24 -0000 @@ -76,7 +76,8 @@ struct audio_buf { unsigned char *data; /* DMA memory block */ size_t datalen; /* size of DMA memory block */ - size_t len; /* size of DMA FIFO */ + size_t klen; /* size of DMA FIFO */ + size_t ulen; /* size of the userland FIFO */ size_t start; /* first byte used in the FIFO */ size_t used; /* bytes used in the FIFO */ size_t blksz; /* DMA block size */ @@ -345,7 +346,7 @@ audio_buf_rgetblk(struct audio_buf *buf, { size_t count; - count = buf->len - buf->start; + count = buf->ulen - buf->start; if (count > buf->used) count = buf->used; *rsize = count; @@ -366,8 +367,8 @@ audio_buf_rdiscard(struct audio_buf *buf #endif buf->used -= count; buf->start += count; - if (buf->start >= buf->len) - buf->start -= buf->len; + if (buf->start >= buf->klen) + buf->start -= buf->klen; } /* @@ -377,7 +378,7 @@ void audio_buf_wcommit(struct audio_buf *buf, size_t count) { #ifdef AUDIO_DEBUG - if (count > (buf->len - buf->used)) { + if (count > (buf->klen - buf->used)) { panic("audio_buf_wcommit: bad count = %zu, " "start = %zu, used = %zu", count, buf->start, buf->used); } @@ -394,10 +395,10 @@ audio_buf_wgetblk(struct audio_buf *buf, size_t end, avail, count; end = buf->start + buf->used; - if (end >= buf->len) - end -= buf->len; - avail = buf->len - buf->used; - count = buf->len - end; + if (end >= buf->klen) + end -= buf->klen; + avail = buf->ulen - buf->used; + count = buf->klen - end; if (count > avail) count = avail; *rsize = count; @@ -470,12 +471,12 @@ audio_clear(struct audio_softc *sc) if (sc->mode & AUMODE_PLAY) { sc->play.used = sc->play.start = 0; sc->play.pos = sc->play.xrun = 0; - audio_fill_sil(sc, sc->play.data, sc->play.len); + audio_fill_sil(sc, sc->play.data, sc->play.klen); } if (sc->mode & AUMODE_RECORD) { sc->rec.used = sc->rec.start = 0; sc->rec.pos = sc->rec.xrun = 0; - audio_fill_sil(sc, sc->rec.data, sc->rec.len); + audio_fill_sil(sc, sc->rec.data, sc->rec.klen); } } @@ -506,7 +507,7 @@ audio_pintr(void *addr) */ if ((sc->mode & AUMODE_RECORD) && sc->ops->underrun == NULL) { sc->offs--; - nblk = sc->rec.len / sc->rec.blksz; + nblk = sc->rec.klen / sc->rec.blksz; todo = -sc->offs; if (todo >= nblk) { todo -= todo % nblk; @@ -544,7 +545,7 @@ audio_pintr(void *addr) } } - if (sc->play.used < sc->play.len) { + if (sc->play.used < sc->play.ulen) { DPRINTFN(1, "%s: play wakeup, chan = %d\n", DEVNAME(sc), sc->play.blocking); audio_buf_wakeup(&sc->play); @@ -585,7 +586,7 @@ audio_rintr(void *addr) */ if ((sc->mode & AUMODE_PLAY) && sc->ops->underrun == NULL) { sc->offs++; - nblk = sc->play.len / sc->play.blksz; + nblk = sc->play.klen / sc->play.blksz; todo = sc->offs; if (todo >= nblk) { todo -= todo % nblk; @@ -604,7 +605,7 @@ audio_rintr(void *addr) audio_fill_sil(sc, ptr, sc->rec.blksz); } audio_buf_wcommit(&sc->rec, sc->rec.blksz); - if (sc->rec.used > sc->rec.len - sc->rec.blksz) { + if (sc->rec.used > sc->rec.ulen - sc->rec.blksz) { DPRINTFN(1, "%s: rec overrun\n", DEVNAME(sc)); sc->rec.xrun += sc->rec.blksz; audio_buf_rdiscard(&sc->rec, sc->rec.blksz); @@ -650,7 +651,7 @@ audio_start_do(struct audio_softc *sc) p.channels = sc->pchan; error = sc->ops->trigger_output(sc->arg, sc->play.data, - sc->play.data + sc->play.len, + sc->play.data + sc->play.klen, sc->play.blksz, audio_pintr, sc, &p); } else { @@ -673,7 +674,7 @@ audio_start_do(struct audio_softc *sc) p.channels = sc->rchan; error = sc->ops->trigger_input(sc->arg, sc->rec.data, - sc->rec.data + sc->rec.len, + sc->rec.data + sc->rec.klen, sc->rec.blksz, audio_rintr, sc, &p); } else { @@ -728,7 +729,7 @@ audio_canstart(struct audio_softc *sc) return 0; if ((sc->mode & AUMODE_RECORD) && sc->rec.used != 0) return 0; - if ((sc->mode & AUMODE_PLAY) && sc->play.used != sc->play.len) + if ((sc->mode & AUMODE_PLAY) && sc->play.used != sc->play.ulen) return 0; return 1; } @@ -1031,11 +1032,13 @@ audio_setpar(struct audio_softc *sc) */ if (sc->mode & AUMODE_PLAY) { sc->play.blksz = sc->round * sc->pchan * sc->bps; - sc->play.len = sc->play.nblks * sc->play.blksz; + sc->play.ulen = sc->play.nblks * sc->play.blksz; + sc->play.klen = sc->play.datalen - sc->play.datalen % sc->play.blksz; } if (sc->mode & AUMODE_RECORD) { sc->rec.blksz = sc->round * sc->rchan * sc->bps; - sc->rec.len = sc->rec.nblks * sc->rec.blksz; + sc->rec.ulen = sc->rec.nblks * sc->rec.blksz; + sc->rec.klen = sc->rec.datalen - sc->rec.datalen % sc->rec.blksz; } DPRINTF("%s: setpar: new enc=%d bits=%d, bps=%d, msb=%d " @@ -1052,7 +1055,7 @@ audio_ioc_start(struct audio_softc *sc) DPRINTF("%s: can't start: already started\n", DEVNAME(sc)); return EBUSY; } - if ((sc->mode & AUMODE_PLAY) && sc->play.used != sc->play.len) { + if ((sc->mode & AUMODE_PLAY) && sc->play.used != sc->play.ulen) { DPRINTF("%s: play buffer not ready\n", DEVNAME(sc)); return EBUSY; } @@ -1161,13 +1164,13 @@ audio_ioc_setpar(struct audio_softc *sc, audio_clear(sc); if ((sc->mode & AUMODE_PLAY) && sc->ops->init_output) { error = sc->ops->init_output(sc->arg, - sc->play.data, sc->play.len); + sc->play.data, sc->play.klen); if (error) return error; } if ((sc->mode & AUMODE_RECORD) && sc->ops->init_input) { error = sc->ops->init_input(sc->arg, - sc->rec.data, sc->rec.len); + sc->rec.data, sc->rec.klen); if (error) return error; } @@ -1362,11 +1365,11 @@ audio_activate(struct device *self, int break; if (sc->mode & AUMODE_PLAY) { sc->play.start = 0; - audio_fill_sil(sc, sc->play.data, sc->play.len); + audio_fill_sil(sc, sc->play.data, sc->play.klen); } if (sc->mode & AUMODE_RECORD) { - sc->rec.start = sc->rec.len - sc->rec.used; - audio_fill_sil(sc, sc->rec.data, sc->rec.len); + sc->rec.start = sc->rec.ulen - sc->rec.used; + audio_fill_sil(sc, sc->rec.data, sc->rec.klen); } if (sc->active) audio_start_do(sc); @@ -1672,7 +1675,7 @@ audio_write(struct audio_softc *sc, stru */ mtx_enter(&audio_lock); if (uio->uio_resid > 0 && (ioflag & IO_NDELAY)) { - if (sc->play.used == sc->play.len) { + if (sc->play.used == sc->play.ulen) { mtx_leave(&audio_lock); return EWOULDBLOCK; } @@ -2226,7 +2229,7 @@ filt_audiowrite(struct knote *kn, long h MUTEX_ASSERT_LOCKED(&audio_lock); - return (sc->mode & AUMODE_PLAY) && (sc->play.used < sc->play.len); + return (sc->mode & AUMODE_PLAY) && (sc->play.used < sc->play.ulen); } void