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 , 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; /* open() flags */ + int mode; /* open() mode */ + int unit; /* device_get_unit(sc->dev) */ + uint32_t rfmt; + uint32_t rspd; + uint32_t pfmt; + uint32_t pspd; }; static int dsp_mmap_allow_prot_exec = 0; @@ -67,6 +74,12 @@ #define DSP_REGISTERED(x) (PCM_REGISTERED(x) && (x)->dsp_dev != NULL) +#define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) +#define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) +#define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) +#define DSP_F_READ(x) ((x) & FREAD) +#define DSP_F_WRITE(x) ((x) & FWRITE) + #define OLDPCM_IOCTL static d_open_t dsp_open; @@ -89,6 +102,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_cdev; static eventhandler_tag dsp_ehtag = NULL; static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); @@ -106,6 +140,216 @@ static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif +static int +vdsp_getdev(struct snddev_info **dev, int unit) +{ + struct dsp_cdevpriv *priv; + struct snddev_info *d; + int err = 0, flags, mode; + uint32_t pfmt, rfmt, pspd, rspd; + + *dev = NULL; + + /* No device attached at the moment. */ + if (unit < 0) + return (ENODEV); + + /* Fetch the cdevpriv associated with the FD. */ + if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) + return (err); + + /* + * 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. */ + 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(); + + /* Fetch the new default device. */ + bus_topo_lock(); + d = devclass_get_softc(pcm_devclass, unit); + if (!DSP_REGISTERED(d)) { + bus_topo_unlock(); + return (EBADF); + } + bus_topo_unlock(); + + /* Open it with the same open() flags and mode. */ + if ((err = dsp_cdevsw.d_open(d->dsp_dev, flags, mode, curthread)) != 0) + return (err); + + /* Reconfigure channels with the same format and rate. */ + if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) + return (err); + if (!DSP_REGISTERED(priv->sc)) + return (EBADF); + if (priv->rdch != NULL) { + CHN_LOCK(priv->rdch); + err = chn_reset(priv->rdch, rfmt, rspd); + CHN_UNLOCK(priv->rdch); + if (err != 0) + return (err); + } + if (priv->wrch != NULL) { + CHN_LOCK(priv->wrch); + err = chn_reset(priv->wrch, pfmt, pspd); + CHN_UNLOCK(priv->wrch); + if (err != 0) + return (err); + } + + *dev = d; + + return (0); +} + +/* + * Because /dev/vdsp is a router device, and we want to it to be persistent, + * the following functions ignore the cdev functions' return values. + */ +static int +vdsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct snddev_info *d; + + bus_topo_lock(); + d = devclass_get_softc(pcm_devclass, snd_unit); + if (!DSP_REGISTERED(d)) { + bus_topo_unlock(); + return (EBADF); + } + bus_topo_unlock(); + + /* + * Only exception to the comment above, however, is open(), since we + * cannot do anything if the device cannot be opened in the first + * place. + */ + return (dsp_cdevsw.d_open(d->dsp_dev, flags, mode, td)); +} + +static int +vdsp_read(struct cdev *i_dev, struct uio *buf, int flag) +{ + struct snddev_info *d; + + if (vdsp_getdev(&d, snd_unit) == 0) + (void)dsp_cdevsw.d_read(d->dsp_dev, buf, flag); + + return (0); +} + +static int +vdsp_write(struct cdev *i_dev, struct uio *buf, int flag) +{ + struct snddev_info *d; + + if (vdsp_getdev(&d, snd_unit) == 0) + (void)dsp_cdevsw.d_write(d->dsp_dev, buf, flag); + + return (0); +} + +static int +vdsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct snddev_info *d; + + if (vdsp_getdev(&d, snd_unit) == 0) + (void)dsp_cdevsw.d_ioctl(d->dsp_dev, cmd, arg, mode, td); + + return (0); +} + +static int +vdsp_poll(struct cdev *i_dev, int events, struct thread *td) +{ + struct snddev_info *d; + int ret = 0; + + if (vdsp_getdev(&d, snd_unit) == 0) + ret = dsp_cdevsw.d_poll(d->dsp_dev, events, td); + + 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; + + if (vdsp_getdev(&d, snd_unit) == 0) { + (void)dsp_cdevsw.d_mmap(d->dsp_dev, offset, paddr, + nprot, memattr); + } + + return (0); +} + +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; + + if (vdsp_getdev(&d, snd_unit) == 0) { + (void)dsp_cdevsw.d_mmap_single(d->dsp_dev, offset, + size, object, nprot); + } + + return (0); +} + int dsp_make_dev(device_t dev) { @@ -234,12 +478,6 @@ return (0); } -#define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) -#define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) -#define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) -#define DSP_F_READ(x) ((x) & FREAD) -#define DSP_F_WRITE(x) ((x) & FWRITE) - static void dsp_close(void *data) { @@ -448,6 +686,10 @@ PCM_UNLOCK(d); } + priv->flags = flags; + priv->mode = mode; + priv->unit = device_get_unit(d->dev); + PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); @@ -1768,6 +2010,23 @@ break; } + /* + * Cache the play/rec formats and rates so that we can fetch them + * safely in vdsp_getdev() if the device goes away in the meantime. + */ + if (priv->rdch != NULL) { + CHN_LOCK(priv->rdch); + priv->rfmt = sndbuf_getfmt(priv->rdch->bufsoft); + priv->rspd = sndbuf_getspd(priv->rdch->bufsoft); + CHN_UNLOCK(priv->rdch); + } + if (priv->wrch != NULL) { + CHN_LOCK(priv->wrch); + priv->pfmt = sndbuf_getfmt(priv->wrch->bufsoft); + priv->pspd = sndbuf_getspd(priv->wrch->bufsoft); + CHN_UNLOCK(priv->wrch); + } + PCM_GIANT_LEAVE(d); return (ret); @@ -1914,7 +2173,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) @@ -1927,36 +2185,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_cdev; + 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_cdev = 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_cdev); } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL);