Date: Fri, 24 Jul 2026 13:59:13 +0000 From: Christos Margiolis <christos@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Kevin Bowling <kbowling@FreeBSD.org> Subject: git: 967e86d1ef2a - main - sound: Scale PCM secondary buffers by byte rate Message-ID: <6a636fb1.26412.26a07c1d@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by christos: URL: https://cgit.FreeBSD.org/src/commit/?id=967e86d1ef2ac8711c0ae7be353a9c08186f4e6f commit 967e86d1ef2ac8711c0ae7be353a9c08186f4e6f Author: Kevin Bowling <kbowling@FreeBSD.org> AuthorDate: 2026-07-24 13:57:27 +0000 Commit: Christos Margiolis <christos@FreeBSD.org> CommitDate: 2026-07-24 13:58:14 +0000 sound: Scale PCM secondary buffers by byte rate The fixed 128 KiB secondary buffer cap dates from stereo-sized streams. High channel-count or high sample-width OSS streams can consume most of that budget in one graph quantum, leaving too little room for capture catch-up or playback headroom. Keep 128 KiB as the low-rate floor, but derive the effective soft-ring cap from the channel byte rate, clamped to 4 MiB. Use that per-channel cap when resizing the soft buffer and when clamping SNDCTL_DSP_SETFRAGMENT requests. Also clamp SNDCTL_DSP_LOW_WATER to the current soft-buffer size so an impossible readiness threshold cannot make poll/select wait forever. MFC after: 3 weeks Reviewed by: christos Differential Revision: https://reviews.freebsd.org/D58064 --- sys/dev/sound/pcm/channel.c | 62 +++++++++++++++++++++++++++++++++------------ sys/dev/sound/pcm/channel.h | 30 +++++++++++++++++----- sys/dev/sound/pcm/dsp.c | 38 +++++++++++++++++++-------- 3 files changed, 96 insertions(+), 34 deletions(-) diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c index ff7620c772c3..21c223c61205 100644 --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -1657,15 +1657,30 @@ round_pow2(u_int32_t v) return ret; } +u_int32_t +chn_2ndbufmaxsize(struct pcm_channel *c) +{ + struct snd_dbuf *bs; + uint64_t maxsize; + + CHN_LOCKASSERT(c); + + bs = c->bufsoft; + maxsize = (uint64_t)bs->align * bs->spd * CHN_2NDBUFTIME_MS / 1000; + RANGE(maxsize, CHN_2NDBUFSIZE_MIN, CHN_2NDBUFSIZE_MAX); + + return ((u_int32_t)maxsize); +} + static u_int32_t -round_blksz(u_int32_t v, int round) +round_blksz(u_int32_t v, int round, u_int32_t maxsize) { u_int32_t ret, tmp; if (round < 1) round = 1; - ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); + ret = min(round_pow2(v), maxsize >> 1); if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) ret >>= 1; @@ -1780,14 +1795,16 @@ chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || bps < 1 || datarate < 1 || !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { + if (max < CHN_2NDBUFSIZE_MIN) + max = CHN_2NDBUFSIZE_MIN; if (rblksz != NULL) - *rblksz = CHN_2NDBUFMAXSIZE >> 1; + *rblksz = max >> 1; if (rblkcnt != NULL) *rblkcnt = 2; printf("%s(): FAILED dir=%d latency=%d bps=%d " "datarate=%u max=%u\n", __func__, dir, latency, bps, datarate, max); - return CHN_2NDBUFMAXSIZE; + return max; } lprofile = chn_latency_profile; @@ -1804,7 +1821,7 @@ chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, datarate)); if (bufsz > max) bufsz = max; - blksz = round_blksz(bufsz >> blkcnt, bps); + blksz = round_blksz(bufsz >> blkcnt, bps, max); if (rblksz != NULL) *rblksz = blksz; @@ -1821,6 +1838,7 @@ chn_resizebuf(struct pcm_channel *c, int latency, struct snd_dbuf *b, *bs, *pb; int sblksz, sblkcnt, hblksz, hblkcnt, limit = 0, nsblksz, nsblkcnt; int ret; + u_int32_t maxsize; CHN_LOCKASSERT(c); @@ -1843,14 +1861,15 @@ chn_resizebuf(struct pcm_channel *c, int latency, bs = c->bufsoft; b = c->bufhard; + maxsize = chn_2ndbufmaxsize(c); if (!(blksz == 0 || blkcnt == -1) && (blksz < 16 || blksz < bs->align || blkcnt < 2 || - (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) + (uint64_t)blksz * blkcnt > maxsize)) return EINVAL; chn_calclatency(c->direction, latency, bs->align, - bs->align * bs->spd, CHN_2NDBUFMAXSIZE, + bs->align * bs->spd, maxsize, &sblksz, &sblkcnt); if (blksz == 0 || blkcnt == -1) { @@ -1871,7 +1890,7 @@ chn_resizebuf(struct pcm_channel *c, int latency, * defeat the purpose of having custom control. The least * we can do is round it to the nearest ^2 and align it. */ - sblksz = round_blksz(blksz, bs->align); + sblksz = round_blksz(blksz, bs->align, maxsize); sblkcnt = round_pow2(blkcnt); } @@ -1890,18 +1909,28 @@ chn_resizebuf(struct pcm_channel *c, int latency, sndbuf_xbytes(pb->blksz, pb, bs) * 2 : 0; } } else { + /* + * The byte-rate-scaled cap applies to the secondary buffer + * only. It exists to absorb userland scheduling latency, + * which the secondary buffer alone must cover; hardware + * buffer geometry keeps the historical cap, since enlarging + * it would change fragment sizes and interrupt cadence + * visible to drivers, and remains bounded by b->maxsize + * below. + */ hblkcnt = 2; if (c->flags & CHN_F_HAS_SIZE) { hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), - b->align); + b->align, CHN_2NDBUFSIZE_MIN); hblkcnt = round_pow2(bs->blkcnt); } else chn_calclatency(c->direction, latency, b->align, b->align * b->spd, - CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); + CHN_2NDBUFSIZE_MIN, &hblksz, &hblkcnt); if ((hblksz << 1) > b->maxsize) - hblksz = round_blksz(b->maxsize >> 1, b->align); + hblksz = round_blksz(b->maxsize >> 1, b->align, + CHN_2NDBUFSIZE_MIN); while ((hblksz * hblkcnt) > b->maxsize) { if (hblkcnt < 4) @@ -1922,7 +1951,8 @@ chn_resizebuf(struct pcm_channel *c, int latency, if (!CHN_EMPTY(c, children)) { nsblksz = round_blksz( - sndbuf_xbytes(b->blksz, b, bs), bs->align); + sndbuf_xbytes(b->blksz, b, bs), bs->align, + maxsize); nsblkcnt = b->blkcnt; if (c->direction == PCMDIR_PLAY) { do { @@ -1938,13 +1968,13 @@ chn_resizebuf(struct pcm_channel *c, int latency, limit = sndbuf_xbytes(b->blksz, b, bs) * 2; } - if (limit > CHN_2NDBUFMAXSIZE) - limit = CHN_2NDBUFMAXSIZE; + if ((u_int32_t)limit > maxsize) + limit = maxsize; - while ((sblksz * sblkcnt) < limit) + while ((uint64_t)sblksz * sblkcnt < (uint64_t)limit) sblkcnt <<= 1; - while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { + while ((uint64_t)sblksz * sblkcnt > maxsize) { if (sblkcnt < 4) sblksz >>= 1; else diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h index c7f5bf93b8e5..c78c25dac13b 100644 --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -430,15 +430,31 @@ enum { #define CHN_TIMEOUT_MIN 1 #define CHN_TIMEOUT_MAX 10 -/* - * This should be large enough to hold all pcm data between - * tsleeps in chn_{read,write} at the highest sample rate. - * (which is usually 48kHz * 16bit * stereo = 192000 bytes/sec) - */ +/* Default block size for the secondary buffer. */ #define CHN_2NDBUFBLKSIZE (2 * 1024) /* The total number of blocks per secondary bufhard. */ #define CHN_2NDBUFBLKNUM (32) -/* The size of a whole secondary bufhard. */ -#define CHN_2NDBUFMAXSIZE (131072) +/* + * The secondary buffer cap scales with the channel byte rate, targeting + * CHN_2NDBUFTIME_MS of stream so that all pcm data between tsleeps in + * chn_{read,write} fits, clamped to [CHN_2NDBUFSIZE_MIN, + * CHN_2NDBUFSIZE_MAX]. + * + * The floor is the historical secondary buffer size and preserves memory + * use for low byte-rate streams; it holds ~680 ms at the once-typical + * 48kHz * 16bit * stereo rate (192000 bytes/sec), well above the target + * (~38 KiB there). + * + * The ceiling bounds per-channel buffer memory. Buffers are allocated at + * the derived size, so only streams that actually run at high byte rates + * approach it. It holds the full target through MADI-class streams + * (64ch * 32-bit * 48kHz, ~12.3 MB/s); beyond that, coverage shrinks + * proportionally (e.g. ~85 ms at 64ch * 32-bit * 192kHz). + */ +#define CHN_2NDBUFSIZE_MIN (131072) +#define CHN_2NDBUFSIZE_MAX (4 * 1024 * 1024) +#define CHN_2NDBUFTIME_MS 200 + +u_int32_t chn_2ndbufmaxsize(struct pcm_channel *); #define CHANNEL_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 0a5063410d24..f49df6e8a8c6 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -109,6 +109,25 @@ static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, o static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif +static uint32_t +dsp_clamp_fragments(uint32_t maxfrags, uint32_t fragsz, uint32_t maxsize) +{ + if (maxfrags == 0) + maxfrags = maxsize / fragsz; + if (maxfrags < 2) + maxfrags = 2; + if ((uint64_t)maxfrags * fragsz > maxsize) + maxfrags = maxsize / fragsz; + return (maxfrags); +} + +static unsigned int +dsp_low_water(struct pcm_channel *ch, int lw) +{ + RANGE(lw, 1, ch->bufsoft->bufsize); + return ((unsigned int)lw); +} + int dsp_make_dev(device_t dev) { @@ -1244,18 +1263,13 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, RANGE(fragln, 4, 16); fragsz = 1 << fragln; - if (maxfrags == 0) - maxfrags = CHN_2NDBUFMAXSIZE / fragsz; - if (maxfrags < 2) - maxfrags = 2; - if (maxfrags * fragsz > CHN_2NDBUFMAXSIZE) - maxfrags = CHN_2NDBUFMAXSIZE / fragsz; - DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); - ret = chn_setblocksize(rdch, maxfrags, fragsz); + ret = chn_setblocksize(rdch, + dsp_clamp_fragments(maxfrags, fragsz, + chn_2ndbufmaxsize(rdch)), fragsz); r_maxfrags = rdch->bufsoft->blkcnt; r_fragsz = rdch->bufsoft->blksz; CHN_UNLOCK(rdch); @@ -1265,7 +1279,9 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, } if (wrch && ret == 0) { CHN_LOCK(wrch); - ret = chn_setblocksize(wrch, maxfrags, fragsz); + ret = chn_setblocksize(wrch, + dsp_clamp_fragments(maxfrags, fragsz, + chn_2ndbufmaxsize(wrch)), fragsz); maxfrags = wrch->bufsoft->blkcnt; fragsz = wrch->bufsoft->blksz; CHN_UNLOCK(wrch); @@ -1653,12 +1669,12 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, */ if (wrch != NULL) { CHN_LOCK(wrch); - wrch->lw = (*arg_i > 1) ? *arg_i : 1; + wrch->lw = dsp_low_water(wrch, *arg_i); CHN_UNLOCK(wrch); } if (rdch != NULL) { CHN_LOCK(rdch); - rdch->lw = (*arg_i > 1) ? *arg_i : 1; + rdch->lw = dsp_low_water(rdch, *arg_i); CHN_UNLOCK(rdch); } break;home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a636fb1.26412.26a07c1d>
