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 3, 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,22 @@ .Nm drivers may create the following device nodes: .Pp -.Bl -tag -width ".Pa /dev/sndstat" -compact +.Bl -tag -width ".Pa /dev/dsp_spdifout" -compact .It Pa /dev/dsp%d Audio device. The number represents the unit number of the device. .It Pa /dev/dsp -Alias of +Virtual device which dispatches to .Pa /dev/dsp${hw.snd.default_unit} . -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/dsp_ac3 +.It Pa /dev/dsp_mmap +.It Pa /dev/dsp_multich +.It Pa /dev/dsp_spdifout +.It Pa /dev/dsp_spdifin +OSSv4 compatibility devices, implemented as symlinks to +.Pa /dev/dsp . .It Pa /dev/sndstat Current .Nm @@ -592,7 +590,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; @@ -60,13 +67,14 @@ &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility (-1=force disable 0=auto 1=force enable)"); -static int dsp_basename_clone = 1; -SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN, - &dsp_basename_clone, 0, - "DSP basename cloning (0: Disable; 1: Enabled)"); - #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,7 +97,221 @@ .d_name = "dsp", }; -static eventhandler_tag dsp_ehtag = NULL; +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 = "dsp", +}; + +static struct cdev *vdsp_cdev; + +static int +vdsp_getdev(struct snddev_info **dev) +{ + struct dsp_cdevpriv *priv; + struct snddev_info *d; + int err = 0, flags, mode; + uint32_t pfmt, rfmt, pspd, rspd; + + *dev = NULL; /* XXX */ + + /* No device attached at the moment. */ + if (snd_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 == snd_unit) { + d = devclass_get_softc(pcm_devclass, snd_unit); + if (!DSP_REGISTERED(d)) { + /* The default device is not registered anymore. */ + priv->sc = NULL; + return (EBADF); + } else if (d == priv->sc) { + /* + * The cdevpriv already points to the default device + * and is ready to use. + */ + *dev = priv->sc; + return (0); + } + /* + * 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/dsp 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. */ + d = devclass_get_softc(pcm_devclass, snd_unit); + if (!DSP_REGISTERED(d)) + return (EBADF); + + /* 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); + /* XXX Should we check for REGISTERED again? */ + if (DSP_F_READ(priv->flags)) { + CHN_LOCK(priv->rdch); + err = chn_reset(priv->rdch, rfmt, rspd); + CHN_UNLOCK(priv->rdch); + if (err != 0) + return (err); + } + if (DSP_F_WRITE(priv->flags)) { + 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/dsp 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; + + d = devclass_get_softc(pcm_devclass, snd_unit); + + /* + * 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) == 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) == 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) == 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; + + if (vdsp_getdev(&d) == 0) + (void)dsp_cdevsw.d_poll(d->dsp_dev, events, td); + + return (0); +} + +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) == 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) == 0) { + (void)dsp_cdevsw.d_mmap_single(d->dsp_dev, offset, + size, object, nprot); + } + + return (0); +} static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); static int dsp_oss_syncstart(int sg_id); @@ -234,12 +456,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 +664,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 +1988,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 (DSP_F_READ(priv->flags)) { + CHN_LOCK(priv->rdch); + priv->rfmt = sndbuf_getfmt(priv->rdch->bufsoft); + priv->rspd = sndbuf_getspd(priv->rdch->bufsoft); + CHN_UNLOCK(priv->rdch); + } + if (DSP_F_WRITE(priv->flags)) { + 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); @@ -1902,61 +2139,22 @@ return (0); } -static const char *dsp_aliases[] = { - "dsp_ac3", - "dsp_mmap", - "dsp_multich", - "dsp_spdifout", - "dsp_spdifin", -}; - -static void -dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) -{ - struct snddev_info *d; - size_t i; - - if (*dev != NULL) - return; - if (strcmp(name, "dsp") == 0 && dsp_basename_clone) - goto found; - for (i = 0; i < nitems(dsp_aliases); i++) { - if (strcmp(name, dsp_aliases[i]) == 0) - goto found; - } - return; -found: - SND_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); - } - SND_UNLOCK(); -} - 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, "dsp"); + /* OSSv4 compatibility aliases. */ + make_dev_alias(vdsp_cdev, "dsp_ac3"); + make_dev_alias(vdsp_cdev, "dsp_mmap"); + make_dev_alias(vdsp_cdev, "dsp_multich"); + make_dev_alias(vdsp_cdev, "dsp_spdifout"); + make_dev_alias(vdsp_cdev, "dsp_spdifin"); } static void dsp_sysuninit(void *p) { - if (dsp_ehtag == NULL) - return; - 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);