dbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) if (bufsize > b->allocsize || bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + if (refcount_load(&b->refcount) > 1 || + (b->flags & SNDBUF_F_DETACHED) != 0) { + CHN_UNLOCK(b->channel); + return (EBUSY); + } allocsize = round_page(bufsize); CHN_UNLOCK(b->channel); tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); @@ -218,10 +247,15 @@ sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) if (blkcnt < 2 || blksz < 16) return EINVAL; + CHN_LOCKASSERT(b->channel); + bufsize = blksz * blkcnt; if (bufsize > b->allocsize || bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + if (refcount_load(&b->refcount) > 1 || + (b->flags & SNDBUF_F_DETACHED) != 0) + return (EBUSY); allocsize = round_page(bufsize); CHN_UNLOCK(b->channel); buf = malloc(allocsize, M_DEVBUF, M_WAITOK); diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h index ddf4083ec19f..99bc6c0611d2 100644 --- a/sys/dev/sound/pcm/buffer.h +++ b/sys/dev/sound/pcm/buffer.h @@ -27,6 +27,7 @@ */ #define SNDBUF_F_MANAGED 0x00000008 +#define SNDBUF_F_DETACHED 0x00000010 #define SNDBUF_NAMELEN 48 @@ -50,6 +51,7 @@ struct snd_dbuf { bus_dma_tag_t dmatag; bus_addr_t buf_addr; int dmaflags; + unsigned int refcount; struct selinfo sel; struct pcm_channel *channel; char name[SNDBUF_NAMELEN]; @@ -57,6 +59,8 @@ struct snd_dbuf { struct snd_dbuf *sndbuf_create(device_t dev, char *drv, char *desc, struct pcm_channel *channel); void sndbuf_destroy(struct snd_dbuf *b); +void sndbuf_ref(struct snd_dbuf *b); +void sndbuf_rele(struct snd_dbuf *b); void sndbuf_dump(struct snd_dbuf *b, char *s, u_int32_t what); diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 72bde9c1066f..bf76345827f5 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -79,7 +79,6 @@ static d_read_t dsp_read; static d_write_t dsp_write; static d_ioctl_t dsp_ioctl; static d_poll_t dsp_poll; -static d_mmap_t dsp_mmap; static d_mmap_single_t dsp_mmap_single; struct cdevsw dsp_cdevsw = { @@ -89,7 +88,6 @@ struct cdevsw dsp_cdevsw = { .d_write = dsp_write, .d_ioctl = dsp_ioctl, .d_poll = dsp_poll, - .d_mmap = dsp_mmap, .d_mmap_single = dsp_mmap_single, .d_name = "dsp", }; @@ -1931,23 +1929,72 @@ dsp_poll(struct cdev *i_dev, int events, struct thread *td) return (ret); } +struct dsp_mmap_handle { + struct cdev *cdev; + struct snd_dbuf *buf; +}; + static int -dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, - int nprot, vm_memattr_t *memattr) +dsp_dev_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot, + vm_ooffset_t foff, struct ucred *cred, u_short *color) { + struct dsp_mmap_handle *h = handle; - /* - * offset is in range due to checks in dsp_mmap_single(). - * XXX memattr is not honored. - */ - *paddr = vtophys(offset); + dev_ref(h->cdev); + sndbuf_ref(h->buf); return (0); } +static void +dsp_dev_pager_dtor(void *handle) +{ + struct dsp_mmap_handle *h = handle; + + sndbuf_rele(h->buf); + dev_rel(h->cdev); + free(h, M_DEVBUF); +} + static int -dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, +dsp_dev_pager_fault(vm_object_t object, vm_ooffset_t offset, int prot, + vm_page_t *mres) +{ + struct dsp_mmap_handle *h = object->handle; + vm_page_t m; + uintptr_t addr; + vm_paddr_t paddr; + + addr = (uintptr_t)offset; + if (addr < (uintptr_t)h->buf->buf || + addr >= (uintptr_t)h->buf->buf + h->buf->allocsize) + return (VM_PAGER_ERROR); + paddr = vtophys((void *)addr); + + if (((*mres)->flags & PG_FICTITIOUS) != 0) { + m = *mres; + vm_page_updatefake(m, paddr, object->memattr); + } else { + VM_OBJECT_WUNLOCK(object); + m = vm_page_getfake(paddr, object->memattr); + VM_OBJECT_WLOCK(object); + vm_page_replace(m, object, (*mres)->pindex, *mres); + *mres = m; + } + m->valid = VM_PAGE_BITS_ALL; + return (VM_PAGER_OK); +} + +static const struct cdev_pager_ops dsp_dev_pager_ops = { + .cdev_pg_ctor = dsp_dev_pager_ctor, + .cdev_pg_dtor = dsp_dev_pager_dtor, + .cdev_pg_fault = dsp_dev_pager_fault, +}; + +static int +dsp_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t size, struct vm_object **object, int nprot) { + struct dsp_mmap_handle *handle; struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch, *c; @@ -2010,13 +2057,18 @@ dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); dsp_unlock_chans(priv, FREAD | FWRITE); - *object = vm_pager_allocate(OBJT_DEVICE, i_dev, - size, nprot, *offset, curthread->td_ucred); + handle = malloc(sizeof(*handle), M_DEVBUF, M_WAITOK); + handle->cdev = cdev; + handle->buf = c->bufsoft; + *object = cdev_pager_allocate(handle, OBJT_DEVICE, &dsp_dev_pager_ops, + size, nprot, *offset, curthread->td_ucred); PCM_GIANT_LEAVE(d); + if (*object == NULL) { + free(handle, M_DEVBUF); + return (EINVAL); + } - if (*object == NULL) - return (EINVAL); return (0); } diff --git a/tests/sys/sound/mmap.c b/tests/sys/sound/mmap.c index ab203a39194c..f775c0f8da8a 100644 --- a/tests/sys/sound/mmap.c +++ b/tests/sys/sound/mmap.c @@ -4,12 +4,14 @@ * Copyright (c) 2026 The FreeBSD Foundation */ +#include #include #include #include #include #include +#include #include #define FMT_ERR(s) s ": %s", strerror(errno) @@ -43,9 +45,67 @@ ATF_TC_BODY(mmap_offset_overflow, tc) close(fd); } +/* + * Verify that a MAP_SHARED mapping of a DSP device's software buffer remains + * valid after the file descriptor is closed. + */ +ATF_TC(mmap_buffer_lifetime); +ATF_TC_HEAD(mmap_buffer_lifetime, tc) +{ + atf_tc_set_md_var(tc, "descr", "mmap data survives close()"); + atf_tc_set_md_var(tc, "require.kmods", "snd_dummy"); +} +ATF_TC_BODY(mmap_buffer_lifetime, tc) +{ + audio_buf_info abi; + uint8_t *buf; + size_t len; + int fd, arg; + + fd = open("/dev/dsp0", O_RDWR); + ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("open")); + + arg = (2 << 16) | 14; /* 2*16KB */ + ATF_REQUIRE_MSG(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) == 0, + FMT_ERR("SNDCTL_DSP_SETFRAGMENT")); + ATF_REQUIRE_MSG(ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi) == 0, + FMT_ERR("SNDCTL_DSP_GETOSPACE")); + + len = abi.bytes; + ATF_REQUIRE_MSG(len >= PAGE_SIZE, "buffer too small: %zu", len); + + buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ATF_REQUIRE_MSG(buf != MAP_FAILED, FMT_ERR("mmap")); + + for (size_t i = 0; i < len; i++) { + ATF_REQUIRE_MSG(buf[i] == 0, + "mmap data corrupted at offset %zu: want 0 got 0x%02x", + i, buf[i]); + } + + memset(buf, 0xa5, len); + for (size_t i = 0; i < len; i++) { + ATF_REQUIRE_MSG(buf[i] == 0xa5, + "mmap data corrupted at offset %zu: want 0xa5 got 0x%02x", + i, buf[i]); + } + + ATF_REQUIRE(close(fd) == 0); + + /* Closing the device causes the buffer to be reset. */ + for (size_t i = 0; i < len; i++) { + ATF_REQUIRE_MSG(buf[i] == 0 || buf[i] == 0xa5, + "mmap data corrupted at offset %zu: got 0x%02x", i, buf[i]); + } + memset(buf, 0xa5, len); + + ATF_REQUIRE(munmap(buf, len) == 0); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, mmap_offset_overflow); + ATF_TP_ADD_TC(tp, mmap_buffer_lifetime); return (atf_no_error()); }