diff --git a/share/man/man4/pcm.4 b/share/man/man4/pcm.4 --- a/share/man/man4/pcm.4 +++ b/share/man/man4/pcm.4 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 15, 2025 +.Dd March 7, 2025 .Dt SOUND 4 .Os .Sh NAME @@ -284,14 +284,6 @@ .El .It Va hw.snd.default_unit Default sound card for systems with multiple sound cards. -When using -.Xr devfs 4 , -the default device for -.Pa /dev/dsp . -Equivalent to a symlink from -.Pa /dev/dsp -to -.Pa /dev/dsp Ns Va ${hw.snd.default_unit} . .It Va hw.snd.feeder_eq_exact_rate Only certain rates are allowed for precise processing. The default behavior is however to allow sloppy processing for all rates, @@ -533,16 +525,21 @@ .Nm drivers may create the following device nodes: .Pp -.Bl -tag -width ".Pa /dev/sndstat" -compact +.Bl -tag -width "/dev/sndstat" -compact .It Pa /dev/dsp%d Audio device. The number represents the unit number of the device. +.It Pa /dev/vdsp +Virtual device which dispatches to +.Pa /dev/dsp${hw.snd.default_unit} . .It Pa /dev/dsp Alias of -.Pa /dev/dsp${hw.snd.default_unit} . +.Pa /dev/vdsp . Available only if .Pa hw.snd.basename_clone is set. +Applications that want to open the default device should make use of this +device. .It Pa /dev/sndstat Current .Nm @@ -592,7 +589,6 @@ A device node is not created properly. .El .Sh SEE ALSO -.Xr devfs 4 , .Xr snd_ai2s 4 , .Xr snd_als4000 4 , .Xr snd_atiixp 4 , @@ -619,6 +615,7 @@ .Xr snd_via8233 4 , .Xr snd_via82c686 4 , .Xr snd_vibes 4 , +.Xr devfs 5 , .Xr device.hints 5 , .Xr loader.conf 5 , .Xr dmesg 8 , diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -53,6 +53,13 @@ struct pcm_channel *rdch; struct pcm_channel *wrch; struct pcm_channel *volch; + int flags; + int mode; + int unit; + uint32_t rfmt; + uint32_t rspd; + uint32_t pfmt; + uint32_t pspd; }; static int dsp_mmap_allow_prot_exec = 0; @@ -89,6 +96,27 @@ .d_name = "dsp", }; +static d_open_t vdsp_open; +static d_read_t vdsp_read; +static d_write_t vdsp_write; +static d_ioctl_t vdsp_ioctl; +static d_poll_t vdsp_poll; +static d_mmap_t vdsp_mmap; +static d_mmap_single_t vdsp_mmap_single; + +struct cdevsw vdsp_cdevsw = { + .d_version = D_VERSION, + .d_open = vdsp_open, + .d_read = vdsp_read, + .d_write = vdsp_write, + .d_ioctl = vdsp_ioctl, + .d_poll = vdsp_poll, + .d_mmap = vdsp_mmap, + .d_mmap_single = vdsp_mmap_single, + .d_name = "vdsp", +}; + +static struct cdev *vdsp_dev; static eventhandler_tag dsp_ehtag = NULL; static void dsp_close_chan(struct pcm_channel *c); @@ -107,6 +135,282 @@ static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif +static int +vdsp_open_dev(int flags, int mode, struct thread *td) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret, unit; + + unit = snd_unit; + + bus_topo_lock(); + d = devclass_get_softc(pcm_devclass, unit); + if (!DSP_REGISTERED(d)) { + bus_topo_unlock(); + return (EBADF); + } + dev_ref(d->dsp_dev); + bus_topo_unlock(); + + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) { + dev_rel(d->dsp_dev); + return (ENXIO); + } + ret = csw->d_open(d->dsp_dev, flags, mode, td); + dev_relthread(d->dsp_dev, ref); + if (ret != 0) + dev_rel(d->dsp_dev); + + return (0); +} + +static int +vdsp_get_dev(struct snddev_info **dev) +{ + struct dsp_cdevpriv *priv; + struct snddev_info *d; + int ret = 0, unit, flags, mode; + uint32_t pfmt, rfmt, pspd, rspd; + + *dev = NULL; + unit = snd_unit; + + /* No device attached at the moment. */ + if (unit < 0) + return (ENODEV); + + /* Fetch the cdevpriv associated with the FD. */ + if ((ret = devfs_get_cdevpriv((void **)&priv)) != 0) + return (ret); + + /* + * The cdevpriv points to the current default device. Make sure it is + * available for use. + */ + if (priv->unit == unit) { + bus_topo_lock(); + d = devclass_get_softc(pcm_devclass, unit); + if (!DSP_REGISTERED(d)) { + /* The default device is not registered anymore. */ + priv->sc = NULL; + bus_topo_unlock(); + return (EBADF); + } else if (d == priv->sc) { + /* + * The cdevpriv already points to the default device + * and is ready to use. + */ + *dev = priv->sc; + bus_topo_unlock(); + return (0); + } + bus_topo_unlock(); + /* + * The device is registered, but not associated with the + * cdevpriv yet. This can happen as a result of the device + * detaching, and re-attaching later at some point, which means + * that priv->unit might still point to it, but the device + * needs to be re-initialized. + */ + } + + /* + * At this point we are either hot-swapping, or re-opening a device. + * The main idea is that we close the previously used device, fetch the + * new one, and initialize it with the same parameters (open() flags, + * sample rate and format) as the previous one. This is so that + * userland applications can simply open /dev/vdsp once, with certain + * parameters, and these will be carried over across any device + * sound(4) might use. + */ + + /* Save current configuration. */ + d = priv->sc; + flags = priv->flags; + mode = priv->mode; + rfmt = priv->rfmt; + rspd = priv->rspd; + pfmt = priv->pfmt; + pspd = priv->pspd; + + /* + * Clear cdevpriv. Will also eventually call dsp_close() and shutdown + * current channels. + */ + devfs_clear_cdevpriv(); + if (DSP_REGISTERED(d)) + dev_rel(d->dsp_dev); + + /* Open the new default device. */ + if ((ret = vdsp_open_dev(flags, mode, curthread)) != 0) + return (ret); + if ((ret = devfs_get_cdevpriv((void **)&priv)) != 0) + return (ret); + if (!DSP_REGISTERED(priv->sc)) + return (EBADF); + + /* Reconfigure channels with the same format and rate. */ + if (priv->rdch != NULL) { + CHN_LOCK(priv->rdch); + ret = chn_reset(priv->rdch, rfmt, rspd); + CHN_UNLOCK(priv->rdch); + if (ret != 0) { + dev_rel(priv->rdch->parentsnddev->dsp_dev); + return (ret); + } + } + if (priv->wrch != NULL) { + CHN_LOCK(priv->wrch); + ret = chn_reset(priv->wrch, pfmt, pspd); + CHN_UNLOCK(priv->wrch); + if (ret != 0) { + dev_rel(priv->wrch->parentsnddev->dsp_dev); + return (ret); + } + } + + /* + * Preserve original device (flags, mode) and channel (rates, formats) + * configuration across hot-swaps. + */ + priv->flags = flags; + priv->mode = mode; + priv->rfmt = rfmt; + priv->rspd = rspd; + priv->pfmt = pfmt; + priv->pspd = pspd; + + *dev = priv->sc; + + return (0); +} + +static int +vdsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + int ret = 0; + + if ((ret = vdsp_open_dev(flags, mode, td)) != 0) + return (ret); + + return (ret); +} + +static int +vdsp_read(struct cdev *i_dev, struct uio *buf, int flag) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret = 0; + + if (vdsp_get_dev(&d) == 0) { + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) + return (ENXIO); + ret = csw->d_read(d->dsp_dev, buf, flag); + dev_relthread(d->dsp_dev, ref); + } + + return (ret); +} + +static int +vdsp_write(struct cdev *i_dev, struct uio *buf, int flag) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret = 0; + + if (vdsp_get_dev(&d) == 0) { + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) + return (ENXIO); + ret = csw->d_write(d->dsp_dev, buf, flag); + dev_relthread(d->dsp_dev, ref); + } + + return (ret); +} + +static int +vdsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret = 0; + + if (vdsp_get_dev(&d) == 0) { + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) + return (ENXIO); + ret = csw->d_ioctl(d->dsp_dev, cmd, arg, mode, td); + dev_relthread(d->dsp_dev, ref); + } + + return (ret); +} + +static int +vdsp_poll(struct cdev *i_dev, int events, struct thread *td) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret = 0; + + if (vdsp_get_dev(&d) == 0) { + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) + return (ENXIO); + ret = csw->d_poll(d->dsp_dev, events, td); + dev_relthread(d->dsp_dev, ref); + } + + return (ret); +} + +static int +vdsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, + int nprot, vm_memattr_t *memattr) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret = 0; + + if (vdsp_get_dev(&d) == 0) { + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) + return (ENXIO); + ret = csw->d_mmap(d->dsp_dev, offset, paddr, + nprot, memattr); + dev_relthread(d->dsp_dev, ref); + } + + return (ret); +} + +static int +vdsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, + vm_size_t size, struct vm_object **object, int nprot) +{ + struct snddev_info *d; + struct cdevsw *csw; + int ref, ret = 0; + + if (vdsp_get_dev(&d) == 0) { + csw = dev_refthread(d->dsp_dev, &ref); + if (csw == NULL) + return (ENXIO); + ret = csw->d_mmap_single(d->dsp_dev, offset, + size, object, nprot); + dev_relthread(d->dsp_dev, ref); + } + + return (ret); +} + int dsp_make_dev(device_t dev) { @@ -329,12 +633,13 @@ struct dsp_cdevpriv *priv; struct pcm_channel *ch; struct snddev_info *d; - int error, dir; + int error, dir, origflags; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return (ENODEV); + origflags = flags; d = i_dev->si_drv1; if (!DSP_REGISTERED(d)) return (EBADF); @@ -429,6 +734,14 @@ PCM_UNLOCK(d); } + /* + * Because the flags can be modified in case of SIMPLEX, preserve the + * original ones as well. + */ + priv->flags = origflags; + priv->mode = mode; + priv->unit = device_get_unit(d->dev); + PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); @@ -1747,6 +2060,23 @@ break; } + /* + * Cache the play/rec formats and rates so that we can fetch them in + * vdsp_getdev() if we need to hot-swap. + */ + if (priv->rdch != NULL) { + CHN_LOCK(priv->rdch); + priv->rfmt = priv->rdch->format; + priv->rspd = priv->rdch->speed; + CHN_UNLOCK(priv->rdch); + } + if (priv->wrch != NULL) { + CHN_LOCK(priv->wrch); + priv->pfmt = priv->wrch->format; + priv->pspd = priv->wrch->speed; + CHN_UNLOCK(priv->wrch); + } + PCM_GIANT_LEAVE(d); return (ret); @@ -1893,7 +2223,6 @@ dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { - struct snddev_info *d; size_t i; if (*dev != NULL) @@ -1906,36 +2235,26 @@ } return; found: - bus_topo_lock(); - d = devclass_get_softc(pcm_devclass, snd_unit); - /* - * If we only have a single soundcard attached and we detach it right - * before entering dsp_clone(), there is a chance pcm_unregister() will - * have returned already, meaning it will have set snd_unit to -1, and - * thus devclass_get_softc() will return NULL here. - */ - if (DSP_REGISTERED(d)) { - *dev = d->dsp_dev; - dev_ref(*dev); - } - bus_topo_unlock(); + *dev = vdsp_dev; + dev_ref(*dev); } static void dsp_sysinit(void *p) { - if (dsp_ehtag != NULL) - return; - dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); + vdsp_dev = make_dev(&vdsp_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "vdsp"); + if (dsp_ehtag == NULL) + dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { - if (dsp_ehtag == NULL) - return; - EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); - dsp_ehtag = NULL; + if (dsp_ehtag != NULL) { + EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + dsp_ehtag = NULL; + } + destroy_dev(vdsp_dev); } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL);