(*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; @@ -2011,13 +2058,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()); }