diff options
Diffstat (limited to 'sys/dev/sound/pcm/dsp.c')
-rw-r--r-- | sys/dev/sound/pcm/dsp.c | 1132 |
1 files changed, 651 insertions, 481 deletions
diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 9c31fff7e8cd..fe5576baf017 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -5,7 +5,7 @@ * Portions Copyright (c) Ryan Beasley <ryan.beasley@gmail.com> - GSoC 2006 * Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org> * All rights reserved. - * Copyright (c) 2024 The FreeBSD Foundation + * Copyright (c) 2024-2025 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation. @@ -37,6 +37,7 @@ #endif #include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/vchan.h> #include <sys/ctype.h> #include <sys/lock.h> #include <sys/rwlock.h> @@ -51,8 +52,6 @@ struct dsp_cdevpriv { struct snddev_info *sc; struct pcm_channel *rdch; struct pcm_channel *wrch; - struct pcm_channel *volch; - int simplex; }; static int dsp_mmap_allow_prot_exec = 0; @@ -67,6 +66,12 @@ SYSCTL_INT(_hw_snd, OID_AUTO, basename_clone, CTLFLAG_RWTUN, #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; @@ -138,147 +143,116 @@ dsp_destroy_dev(device_t dev) struct snddev_info *d; d = device_get_softc(dev); - destroy_dev_sched(d->dsp_dev); + destroy_dev(d->dsp_dev); } static void -getchns(struct dsp_cdevpriv *priv, uint32_t prio) +dsp_lock_chans(struct dsp_cdevpriv *priv, uint32_t prio) { - struct snddev_info *d; - struct pcm_channel *ch; - uint32_t flags; - - if (priv->simplex) { - d = priv->sc; - if (!PCM_REGISTERED(d)) - return; - PCM_LOCK(d); - PCM_WAIT(d); - PCM_ACQUIRE(d); - /* - * Note: order is important - - * pcm flags -> prio query flags -> wild guess - */ - ch = NULL; - flags = pcm_getflags(d->dev); - if (flags & SD_F_PRIO_WR) { - ch = priv->rdch; - } else if (flags & SD_F_PRIO_RD) { - ch = priv->wrch; - } else if (prio & SD_F_PRIO_WR) { - ch = priv->rdch; - flags |= SD_F_PRIO_WR; - } else if (prio & SD_F_PRIO_RD) { - ch = priv->wrch; - flags |= SD_F_PRIO_RD; - } else if (priv->wrch != NULL) { - ch = priv->rdch; - flags |= SD_F_PRIO_WR; - } else if (priv->rdch != NULL) { - ch = priv->wrch; - flags |= SD_F_PRIO_RD; - } - pcm_setflags(d->dev, flags); - if (ch != NULL) { - CHN_LOCK(ch); - chn_ref(ch, -1); - chn_release(ch); - } - PCM_RELEASE(d); - PCM_UNLOCK(d); - } - - if (priv->rdch != NULL && (prio & SD_F_PRIO_RD)) + if (priv->rdch != NULL && DSP_F_READ(prio)) CHN_LOCK(priv->rdch); - if (priv->wrch != NULL && (prio & SD_F_PRIO_WR)) + if (priv->wrch != NULL && DSP_F_WRITE(prio)) CHN_LOCK(priv->wrch); } static void -relchns(struct dsp_cdevpriv *priv, uint32_t prio) +dsp_unlock_chans(struct dsp_cdevpriv *priv, uint32_t prio) { - if (priv->rdch != NULL && (prio & SD_F_PRIO_RD)) + if (priv->rdch != NULL && DSP_F_READ(prio)) CHN_UNLOCK(priv->rdch); - if (priv->wrch != NULL && (prio & SD_F_PRIO_WR)) + if (priv->wrch != NULL && DSP_F_WRITE(prio)) CHN_UNLOCK(priv->wrch); } -/* duplex / simplex cdev type */ -enum { - DSP_CDEV_TYPE_RDONLY, /* simplex read-only (record) */ - DSP_CDEV_TYPE_WRONLY, /* simplex write-only (play) */ - DSP_CDEV_TYPE_RDWR /* duplex read, write, or both */ -}; +static int +dsp_chn_alloc(struct snddev_info *d, struct pcm_channel **ch, int direction, + int flags, struct thread *td) +{ + struct pcm_channel *c; + char *comm; + pid_t pid; + int err; + bool vdir_enabled; -#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) + KASSERT(d != NULL && ch != NULL && + (direction == PCMDIR_PLAY || direction == PCMDIR_REC), + ("%s(): invalid d=%p ch=%p direction=%d", + __func__, d, ch, direction)); + PCM_BUSYASSERT(d); -static const struct { - int type; - char *name; - char *sep; - char *alias; - int use_sep; - int hw; - int max; - int volctl; - uint32_t fmt, spd; - int query; -} dsp_cdevs[] = { - { SND_DEV_DSP, "dsp", ".", NULL, 0, 0, 0, 0, - SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_AUDIO, "audio", ".", NULL, 0, 0, 0, 0, - SND_FORMAT(AFMT_MU_LAW, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_DSP16, "dspW", ".", NULL, 0, 0, 0, 0, - SND_FORMAT(AFMT_S16_LE, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_DSPHW_PLAY, "dsp", ".p", NULL, 1, 1, SND_MAXHWCHAN, 1, - SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY }, - { SND_DEV_DSPHW_VPLAY, "dsp", ".vp", NULL, 1, 1, SND_MAXVCHANS, 1, - SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_WRONLY }, - { SND_DEV_DSPHW_REC, "dsp", ".r", NULL, 1, 1, SND_MAXHWCHAN, 1, - SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY }, - { SND_DEV_DSPHW_VREC, "dsp", ".vr", NULL, 1, 1, SND_MAXVCHANS, 1, - SND_FORMAT(AFMT_S16_LE, 2, 0), 48000, DSP_CDEV_TYPE_RDONLY }, - { SND_DEV_DSPHW_CD, "dspcd", ".", NULL, 0, 0, 0, 0, - SND_FORMAT(AFMT_S16_LE, 2, 0), 44100, DSP_CDEV_TYPE_RDWR }, - /* Low priority, OSSv4 aliases. */ - { SND_DEV_DSP, "dsp_ac3", ".", "dsp", 0, 0, 0, 0, - SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_DSP, "dsp_mmap", ".", "dsp", 0, 0, 0, 0, - SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_DSP, "dsp_multich", ".", "dsp", 0, 0, 0, 0, - SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_DSP, "dsp_spdifout", ".", "dsp", 0, 0, 0, 0, - SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, - { SND_DEV_DSP, "dsp_spdifin", ".", "dsp", 0, 0, 0, 0, - SND_FORMAT(AFMT_U8, 1, 0), DSP_DEFAULT_SPEED, - DSP_CDEV_TYPE_RDWR }, -}; + pid = td->td_proc->p_pid; + comm = td->td_proc->p_comm; + + vdir_enabled = (direction == PCMDIR_PLAY && d->flags & SD_F_PVCHANS) || + (direction == PCMDIR_REC && d->flags & SD_F_RVCHANS); + + *ch = NULL; + CHN_FOREACH(c, d, channels.pcm.primary) { + CHN_LOCK(c); + if (c->direction != direction) { + CHN_UNLOCK(c); + continue; + } + /* Find an available primary channel to use. */ + if ((c->flags & CHN_F_BUSY) == 0 || + (vdir_enabled && (c->flags & CHN_F_HAS_VCHAN))) + break; + CHN_UNLOCK(c); + } + if (c == NULL) + return (EBUSY); + + /* + * We can have the following cases: + * - vchans are enabled, add a new vchan to the primary channel. + * - vchans are disabled, use the primary channel directly. + */ + if (vdir_enabled && ((c->flags & CHN_F_BUSY) == 0 || + c->flags & CHN_F_HAS_VCHAN)) { + err = vchan_create(c, ch); + CHN_UNLOCK(c); + if (err != 0) + return (err); + CHN_LOCK(*ch); + } else if ((c->flags & CHN_F_BUSY) == 0) { + *ch = c; + } else { + CHN_UNLOCK(c); + return (ENODEV); + } + + (*ch)->flags |= CHN_F_BUSY; + if (flags & O_NONBLOCK) + (*ch)->flags |= CHN_F_NBIO; + if (flags & O_EXCL) + (*ch)->flags |= CHN_F_EXCLUSIVE; + (*ch)->pid = pid; + strlcpy((*ch)->comm, (comm != NULL) ? comm : CHN_COMM_UNKNOWN, + sizeof((*ch)->comm)); + + if ((err = chn_reset(*ch, (*ch)->format, (*ch)->speed)) != 0) + return (err); + chn_vpc_reset(*ch, SND_VOL_C_PCM, 0); + + CHN_UNLOCK(*ch); + + return (0); +} static void dsp_close(void *data) { struct dsp_cdevpriv *priv = data; - struct pcm_channel *rdch, *wrch, *volch; + struct pcm_channel *rdch, *wrch, *parent; struct snddev_info *d; - int sg_ids, rdref, wdref; + int sg_ids; if (priv == NULL) return; d = priv->sc; /* At this point pcm_unregister() will destroy all channels anyway. */ - if (PCM_DETACHING(d)) + if (!DSP_REGISTERED(d)) goto skip; PCM_GIANT_ENTER(d); @@ -289,22 +263,6 @@ dsp_close(void *data) rdch = priv->rdch; wrch = priv->wrch; - volch = priv->volch; - - rdref = -1; - wdref = -1; - - if (volch != NULL) { - if (volch == rdch) - rdref--; - else if (volch == wrch) - wdref--; - else { - CHN_LOCK(volch); - chn_ref(volch, -1); - CHN_UNLOCK(volch); - } - } if (rdch != NULL) CHN_REMOVE(d, rdch, channels.pcm.opened); @@ -331,13 +289,26 @@ dsp_close(void *data) if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); + /* + * Go through the channel abort/flush path for both + * primary and virtual channels to ensure that, in the + * case of vchans, the stream is always properly + * stopped, and the primary channels do not keep being + * interrupted even if all vchans are gone. + */ CHN_LOCK(rdch); - chn_ref(rdch, rdref); chn_abort(rdch); /* won't sleep */ rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | - CHN_F_DEAD | CHN_F_EXCLUSIVE); + CHN_F_DEAD | CHN_F_EXCLUSIVE | CHN_F_NBIO); chn_reset(rdch, 0, 0); chn_release(rdch); + if (rdch->flags & CHN_F_VIRTUAL) { + parent = rdch->parentchannel; + CHN_LOCK(parent); + CHN_LOCK(rdch); + vchan_destroy(rdch); + CHN_UNLOCK(parent); + } } if (wrch != NULL) { /* @@ -350,12 +321,18 @@ dsp_close(void *data) free_unr(pcmsg_unrhdr, sg_ids); CHN_LOCK(wrch); - chn_ref(wrch, wdref); chn_flush(wrch); /* may sleep */ wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | - CHN_F_DEAD | CHN_F_EXCLUSIVE); + CHN_F_DEAD | CHN_F_EXCLUSIVE | CHN_F_NBIO); chn_reset(wrch, 0, 0); chn_release(wrch); + if (wrch->flags & CHN_F_VIRTUAL) { + parent = wrch->parentchannel; + CHN_LOCK(parent); + CHN_LOCK(wrch); + vchan_destroy(wrch); + CHN_UNLOCK(parent); + } } PCM_LOCK(d); } @@ -369,43 +346,27 @@ skip: priv = NULL; } -#define DSP_FIXUP_ERROR() do { \ - prio = pcm_getflags(d->dev); \ - if (!DSP_F_VALID(flags)) \ - error = EINVAL; \ - if (!DSP_F_DUPLEX(flags) && \ - ((DSP_F_READ(flags) && d->reccount == 0) || \ - (DSP_F_WRITE(flags) && d->playcount == 0))) \ - error = ENOTSUP; \ - else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) && \ - ((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) || \ - (DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD)))) \ - error = EBUSY; \ -} while (0) - static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct dsp_cdevpriv *priv; - struct pcm_channel *rdch, *wrch; + struct pcm_channel *ch; struct snddev_info *d; - uint32_t fmt, spd, prio; - int error, rderror, wrerror; + int error, dir; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) return (ENODEV); d = i_dev->si_drv1; - if (PCM_DETACHING(d) || !PCM_REGISTERED(d)) + if (!DSP_REGISTERED(d)) return (EBADF); + if (PCM_CHANCOUNT(d) >= PCM_MAXCHANS) + return (ENOMEM); + priv = malloc(sizeof(*priv), M_DEVBUF, M_WAITOK | M_ZERO); priv->sc = d; - priv->rdch = NULL; - priv->wrch = NULL; - priv->volch = NULL; - priv->simplex = (pcm_getflags(d->dev) & SD_F_SIMPLEX) ? 1 : 0; error = devfs_set_cdevpriv(priv, dsp_close); if (error != 0) @@ -418,7 +379,42 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) PCM_WAIT(d); error = 0; - DSP_FIXUP_ERROR(); + if (!DSP_F_VALID(flags)) + error = EINVAL; + else if (!DSP_F_DUPLEX(flags) && + ((DSP_F_READ(flags) && d->reccount == 0) || + (DSP_F_WRITE(flags) && d->playcount == 0))) + error = ENOTSUP; + if (pcm_getflags(d->dev) & SD_F_SIMPLEX) { + if (DSP_F_DUPLEX(flags)) { + /* + * If no channels are opened yet, and we request + * DUPLEX, limit to playback only, otherwise open one + * channel in a direction that already exists. + */ + if (CHN_EMPTY(d, channels.pcm.opened)) { + if (d->playcount > 0) + flags &= ~FREAD; + else if (d->reccount > 0) + flags &= ~FWRITE; + } else { + ch = CHN_FIRST(d, channels.pcm.opened); + if (ch->direction == PCMDIR_PLAY) + flags &= ~FREAD; + else if (ch->direction == PCMDIR_REC) + flags &= ~FWRITE; + } + } else if (!CHN_EMPTY(d, channels.pcm.opened)) { + /* + * If we requested SIMPLEX, make sure we do not open a + * channel in the opposite direction. + */ + ch = CHN_FIRST(d, channels.pcm.opened); + dir = DSP_F_READ(flags) ? PCMDIR_REC : PCMDIR_PLAY; + if (ch->direction != dir) + error = ENOTSUP; + } + } if (error != 0) { PCM_UNLOCK(d); PCM_GIANT_EXIT(d); @@ -433,101 +429,30 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) PCM_ACQUIRE(d); PCM_UNLOCK(d); - fmt = SND_FORMAT(AFMT_U8, 1, 0); - spd = DSP_DEFAULT_SPEED; - - rdch = NULL; - wrch = NULL; - rderror = 0; - wrerror = 0; - - if (DSP_F_READ(flags)) { - /* open for read */ - rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC, - td->td_proc->p_pid, td->td_proc->p_comm); - - if (rderror == 0 && chn_reset(rdch, fmt, spd) != 0) - rderror = ENXIO; - - if (rderror != 0) { - if (rdch != NULL) - chn_release(rdch); - if (!DSP_F_DUPLEX(flags)) { - PCM_RELEASE_QUICK(d); - PCM_GIANT_EXIT(d); - return (rderror); - } - rdch = NULL; - } else { - if (flags & O_NONBLOCK) - rdch->flags |= CHN_F_NBIO; - if (flags & O_EXCL) - rdch->flags |= CHN_F_EXCLUSIVE; - chn_ref(rdch, 1); - chn_vpc_reset(rdch, SND_VOL_C_PCM, 0); - CHN_UNLOCK(rdch); - } - } - if (DSP_F_WRITE(flags)) { - /* open for write */ - wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, - td->td_proc->p_pid, td->td_proc->p_comm); - - if (wrerror == 0 && chn_reset(wrch, fmt, spd) != 0) - wrerror = ENXIO; - - if (wrerror != 0) { - if (wrch != NULL) - chn_release(wrch); - if (!DSP_F_DUPLEX(flags)) { - if (rdch != NULL) { - /* - * Lock, deref and release previously - * created record channel - */ - CHN_LOCK(rdch); - chn_ref(rdch, -1); - chn_release(rdch); - } - PCM_RELEASE_QUICK(d); - PCM_GIANT_EXIT(d); - return (wrerror); - } - wrch = NULL; - } else { - if (flags & O_NONBLOCK) - wrch->flags |= CHN_F_NBIO; - if (flags & O_EXCL) - wrch->flags |= CHN_F_EXCLUSIVE; - chn_ref(wrch, 1); - chn_vpc_reset(wrch, SND_VOL_C_PCM, 0); - CHN_UNLOCK(wrch); + error = dsp_chn_alloc(d, &priv->wrch, PCMDIR_PLAY, flags, td); + if (error != 0) { + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (error); } + PCM_LOCK(d); + CHN_INSERT_HEAD(d, priv->wrch, channels.pcm.opened); + PCM_UNLOCK(d); } - - PCM_LOCK(d); - - if (wrch == NULL && rdch == NULL) { - PCM_RELEASE(d); + if (DSP_F_READ(flags)) { + error = dsp_chn_alloc(d, &priv->rdch, PCMDIR_REC, flags, td); + if (error != 0) { + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (error); + } + PCM_LOCK(d); + CHN_INSERT_HEAD(d, priv->rdch, channels.pcm.opened); PCM_UNLOCK(d); - PCM_GIANT_EXIT(d); - if (wrerror != 0) - return (wrerror); - if (rderror != 0) - return (rderror); - return (EINVAL); } - if (rdch != NULL) - CHN_INSERT_HEAD(d, rdch, channels.pcm.opened); - if (wrch != NULL) - CHN_INSERT_HEAD(d, wrch, channels.pcm.opened); - priv->rdch = rdch; - priv->wrch = wrch; - - PCM_RELEASE(d); - PCM_UNLOCK(d); + PCM_RELEASE_QUICK(d); PCM_GIANT_LEAVE(d); return (0); @@ -547,19 +472,19 @@ dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf) ("%s(): io train wreck!", __func__)); d = priv->sc; - if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) + if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); switch (buf->uio_rw) { case UIO_READ: - prio = SD_F_PRIO_RD; + prio = FREAD; ch = &priv->rdch; chn_io = chn_read; break; case UIO_WRITE: - prio = SD_F_PRIO_WR; + prio = FWRITE; ch = &priv->wrch; chn_io = chn_write; break; @@ -570,18 +495,18 @@ dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf) runpid = buf->uio_td->td_proc->p_pid; - getchns(priv, prio); + dsp_lock_chans(priv, prio); if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { if (priv->rdch != NULL || priv->wrch != NULL) - relchns(priv, prio); + dsp_unlock_chans(priv, prio); PCM_GIANT_EXIT(d); return (EBADF); } if (((*ch)->flags & (CHN_F_MMAP | CHN_F_DEAD)) || (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { - relchns(priv, prio); + dsp_unlock_chans(priv, prio); PCM_GIANT_EXIT(d); return (EINVAL); } else if (!((*ch)->flags & CHN_F_RUNNING)) { @@ -600,7 +525,7 @@ dsp_io_ops(struct dsp_cdevpriv *priv, struct uio *buf) CHN_BROADCAST(&(*ch)->cv); - relchns(priv, prio); + dsp_unlock_chans(priv, prio); PCM_GIANT_LEAVE(d); @@ -630,7 +555,7 @@ dsp_write(struct cdev *i_dev, struct uio *buf, int flag) } static int -dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, +dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *ch, u_long cmd, caddr_t arg) { struct snddev_info *d; @@ -648,25 +573,19 @@ dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, rdch = priv->rdch; wrch = priv->wrch; - /* No specific channel, look into cache */ - if (volch == NULL) - volch = priv->volch; - - /* Look harder */ - if (volch == NULL) { + if (ch == NULL) { if (j == SOUND_MIXER_RECLEV && rdch != NULL) - volch = rdch; + ch = rdch; else if (j == SOUND_MIXER_PCM && wrch != NULL) - volch = wrch; + ch = wrch; } - /* Final validation */ - if (volch == NULL) + if (ch == NULL) return (EINVAL); - CHN_LOCK(volch); - if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { - CHN_UNLOCK(volch); + CHN_LOCK(ch); + if (!(ch->feederflags & (1 << FEEDER_VOLUME))) { + CHN_UNLOCK(ch); return (EINVAL); } @@ -674,28 +593,28 @@ dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, case MIXER_WRITE(0): switch (j) { case SOUND_MIXER_MUTE: - if (volch->direction == PCMDIR_REC) { - chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); + if (ch->direction == PCMDIR_REC) { + chn_setmute_multi(ch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); } else { - chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); + chn_setmute_multi(ch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); } break; case SOUND_MIXER_PCM: - if (volch->direction != PCMDIR_PLAY) + if (ch->direction != PCMDIR_PLAY) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; - chn_setvolume_multi(volch, SND_VOL_C_PCM, + chn_setvolume_multi(ch, SND_VOL_C_PCM, left, right, center); break; case SOUND_MIXER_RECLEV: - if (volch->direction != PCMDIR_REC) + if (ch->direction != PCMDIR_REC) break; left = *(int *)arg & 0x7f; right = ((*(int *)arg) >> 8) & 0x7f; center = (left + right) >> 1; - chn_setvolume_multi(volch, SND_VOL_C_PCM, + chn_setvolume_multi(ch, SND_VOL_C_PCM, left, right, center); break; default: @@ -707,34 +626,34 @@ dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, case MIXER_READ(0): switch (j) { case SOUND_MIXER_MUTE: - mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) || - CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR); - if (volch->direction == PCMDIR_REC) { + mute = CHN_GETMUTE(ch, SND_VOL_C_PCM, SND_CHN_T_FL) || + CHN_GETMUTE(ch, SND_VOL_C_PCM, SND_CHN_T_FR); + if (ch->direction == PCMDIR_REC) { *(int *)arg = mute << SOUND_MIXER_RECLEV; } else { *(int *)arg = mute << SOUND_MIXER_PCM; } break; case SOUND_MIXER_PCM: - if (volch->direction != PCMDIR_PLAY) + if (ch->direction != PCMDIR_PLAY) break; - *(int *)arg = CHN_GETVOLUME(volch, + *(int *)arg = CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FL); - *(int *)arg |= CHN_GETVOLUME(volch, + *(int *)arg |= CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_RECLEV: - if (volch->direction != PCMDIR_REC) + if (ch->direction != PCMDIR_REC) break; - *(int *)arg = CHN_GETVOLUME(volch, + *(int *)arg = CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FL); - *(int *)arg |= CHN_GETVOLUME(volch, + *(int *)arg |= CHN_GETVOLUME(ch, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; break; case SOUND_MIXER_DEVMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_STEREODEVS: - if (volch->direction == PCMDIR_REC) + if (ch->direction == PCMDIR_REC) *(int *)arg = SOUND_MASK_RECLEV; else *(int *)arg = SOUND_MASK_PCM; @@ -748,10 +667,47 @@ dsp_ioctl_channel(struct dsp_cdevpriv *priv, struct pcm_channel *volch, default: break; } - CHN_UNLOCK(volch); + CHN_UNLOCK(ch); return (0); } +#ifdef COMPAT_FREEBSD32 +typedef struct _snd_chan_param32 { + uint32_t play_rate; + uint32_t rec_rate; + uint32_t play_format; + uint32_t rec_format; +} snd_chan_param32; +#define AIOGFMT32 _IOC_NEWTYPE(AIOGFMT, snd_chan_param32) +#define AIOSFMT32 _IOC_NEWTYPE(AIOSFMT, snd_chan_param32) + +typedef struct _snd_capabilities32 { + uint32_t rate_min, rate_max; + uint32_t formats; + uint32_t bufsize; + uint32_t mixers; + uint32_t inputs; + uint16_t left, right; +} snd_capabilities32; +#define AIOGCAP32 _IOC_NEWTYPE(AIOGCAP, snd_capabilities32) + +typedef struct audio_errinfo32 +{ + int32_t play_underruns; + int32_t rec_overruns; + uint32_t play_ptradjust; + uint32_t rec_ptradjust; + int32_t play_errorcount; + int32_t rec_errorcount; + int32_t play_lasterror; + int32_t rec_lasterror; + int32_t play_errorparm; + int32_t rec_errorparm; + int32_t filler[16]; +} audio_errinfo32; +#define SNDCTL_DSP_GETERROR32 _IOC_NEWTYPE(SNDCTL_DSP_GETERROR, audio_errinfo32) +#endif + static int dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) @@ -766,7 +722,7 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, return (err); d = priv->sc; - if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) + if (!DSP_REGISTERED(d)) return (EBADF); PCM_GIANT_ENTER(d); @@ -782,7 +738,7 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, PCM_GIANT_EXIT(d); return (0); } - ret = dsp_ioctl_channel(priv, priv->volch, cmd, arg); + ret = dsp_ioctl_channel(priv, NULL, cmd, arg); if (ret != -1) { PCM_GIANT_EXIT(d); return (ret); @@ -815,9 +771,15 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, ret = sound_oss_card_info((oss_card_info *)arg); break; case SNDCTL_AUDIOINFO: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, + false); + break; case SNDCTL_AUDIOINFO_EX: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg, + true); + break; case SNDCTL_ENGINEINFO: - ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); + ret = dsp_oss_engineinfo(i_dev, (oss_audioinfo *)arg); break; case SNDCTL_MIXERINFO: ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); @@ -830,7 +792,6 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, return (ret); } - getchns(priv, 0); rdch = priv->rdch; wrch = priv->wrch; @@ -905,9 +866,25 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, case AIOSFMT: case AIOGFMT: +#ifdef COMPAT_FREEBSD32 + case AIOSFMT32: + case AIOGFMT32: +#endif { snd_chan_param *p = (snd_chan_param *)arg; +#ifdef COMPAT_FREEBSD32 + snd_chan_param32 *p32 = (snd_chan_param32 *)arg; + snd_chan_param param; + + if (cmd == AIOSFMT32) { + p = ¶m; + p->play_rate = p32->play_rate; + p->rec_rate = p32->rec_rate; + p->play_format = p32->play_format; + p->rec_format = p32->rec_format; + } +#endif if (cmd == AIOSFMT && ((p->play_format != 0 && p->play_rate == 0) || (p->rec_format != 0 && p->rec_rate == 0))) { @@ -948,15 +925,41 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, p->rec_format = 0; } PCM_RELEASE_QUICK(d); +#ifdef COMPAT_FREEBSD32 + if (cmd == AIOSFMT32 || cmd == AIOGFMT32) { + p32->play_rate = p->play_rate; + p32->rec_rate = p->rec_rate; + p32->play_format = p->play_format; + p32->rec_format = p->rec_format; + } +#endif } break; case AIOGCAP: /* get capabilities */ +#ifdef COMPAT_FREEBSD32 + case AIOGCAP32: +#endif { snd_capabilities *p = (snd_capabilities *)arg; struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; - +#ifdef COMPAT_FREEBSD32 + snd_capabilities32 *p32 = (snd_capabilities32 *)arg; + snd_capabilities capabilities; + + if (cmd == AIOGCAP32) { + p = &capabilities; + p->rate_min = p32->rate_min; + p->rate_max = p32->rate_max; + p->formats = p32->formats; + p->bufsize = p32->bufsize; + p->mixers = p32->mixers; + p->inputs = p32->inputs; + p->left = p32->left; + p->right = p32->right; + } +#endif PCM_LOCK(d); if (rdch) { CHN_LOCK(rdch); @@ -989,6 +992,18 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, if (rdch) CHN_UNLOCK(rdch); PCM_UNLOCK(d); +#ifdef COMPAT_FREEBSD32 + if (cmd == AIOGCAP32) { + p32->rate_min = p->rate_min; + p32->rate_max = p->rate_max; + p32->formats = p->formats; + p32->bufsize = p->bufsize; + p32->mixers = p->mixers; + p32->inputs = p->inputs; + p32->left = p->left; + p32->right = p->right; + } +#endif } break; @@ -1354,7 +1369,6 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_getfree(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); @@ -1372,7 +1386,6 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); - /* XXX abusive DMA update: chn_rdupdate(rdch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getfreeptr(bs); @@ -1390,7 +1403,6 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); @@ -1482,7 +1494,6 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - /* XXX abusive DMA update: chn_wrupdate(wrch); */ *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else @@ -1679,14 +1690,8 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, CHN_LOCK(chn); bs = chn->bufsoft; -#if 0 - tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b); - oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getalign(b); - oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getalign(b); -#else oc->samples = sndbuf_gettotal(bs) / sndbuf_getalign(bs); oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getalign(bs); -#endif CHN_UNLOCK(chn); } break; @@ -1721,6 +1726,9 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, break; case SNDCTL_DSP_GETERROR: +#ifdef COMPAT_FREEBSD32 + case SNDCTL_DSP_GETERROR32: +#endif /* * OSSv4 docs: "All errors and counters will automatically be * cleared to zeroes after the call so each call will return only @@ -1730,6 +1738,14 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, */ { audio_errinfo *ei = (audio_errinfo *)arg; +#ifdef COMPAT_FREEBSD32 + audio_errinfo errinfo; + audio_errinfo32 *ei32 = (audio_errinfo32 *)arg; + + if (cmd == SNDCTL_DSP_GETERROR32) { + ei = &errinfo; + } +#endif bzero((void *)ei, sizeof(*ei)); @@ -1745,6 +1761,21 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, rdch->xruns = 0; CHN_UNLOCK(rdch); } +#ifdef COMPAT_FREEBSD32 + if (cmd == SNDCTL_DSP_GETERROR32) { + bzero((void *)ei32, sizeof(*ei32)); + ei32->play_underruns = ei->play_underruns; + ei32->rec_overruns = ei->rec_overruns; + ei32->play_ptradjust = ei->play_ptradjust; + ei32->rec_ptradjust = ei->rec_ptradjust; + ei32->play_errorcount = ei->play_errorcount; + ei32->rec_errorcount = ei->rec_errorcount; + ei32->play_lasterror = ei->play_lasterror; + ei32->rec_lasterror = ei->rec_lasterror; + ei32->play_errorparm = ei->play_errorparm; + ei32->rec_errorparm = ei->rec_errorparm; + } +#endif } break; @@ -1835,18 +1866,6 @@ dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, case SNDCTL_SETNAME: ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); break; -#if 0 - /** - * @note The S/PDIF interface ioctls, @c SNDCTL_DSP_READCTL and - * @c SNDCTL_DSP_WRITECTL have been omitted at the suggestion of - * 4Front Technologies. - */ - case SNDCTL_DSP_READCTL: - case SNDCTL_DSP_WRITECTL: - ret = EINVAL; - break; -#endif /* !0 (explicitly omitted ioctls) */ - #endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: @@ -1880,7 +1899,7 @@ dsp_poll(struct cdev *i_dev, int events, struct thread *td) if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; - if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) { + if (!DSP_REGISTERED(d)) { /* XXX many clients don't understand POLLNVAL */ return (events & (POLLHUP | POLLPRI | POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM)); @@ -1889,7 +1908,7 @@ dsp_poll(struct cdev *i_dev, int events, struct thread *td) ret = 0; - getchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); + dsp_lock_chans(priv, FREAD | FWRITE); wrch = priv->wrch; rdch = priv->rdch; @@ -1905,7 +1924,7 @@ dsp_poll(struct cdev *i_dev, int events, struct thread *td) ret |= chn_poll(rdch, e, td); } - relchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); + dsp_unlock_chans(priv, FREAD | FWRITE); PCM_GIANT_LEAVE(d); @@ -1962,12 +1981,12 @@ dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, if ((err = devfs_get_cdevpriv((void **)&priv)) != 0) return (err); d = priv->sc; - if (PCM_DETACHING(d) || !DSP_REGISTERED(d)) + if (!DSP_REGISTERED(d)) return (EINVAL); PCM_GIANT_ENTER(d); - getchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); + dsp_lock_chans(priv, FREAD | FWRITE); wrch = priv->wrch; rdch = priv->rdch; @@ -1976,7 +1995,7 @@ dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, (*offset + size) > sndbuf_getallocsize(c->bufsoft) || (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { - relchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); + dsp_unlock_chans(priv, FREAD | FWRITE); PCM_GIANT_EXIT(d); return (EINVAL); } @@ -1987,7 +2006,7 @@ dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, rdch->flags |= CHN_F_MMAP; *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); - relchns(priv, SD_F_PRIO_RD | SD_F_PRIO_WR); + dsp_unlock_chans(priv, FREAD | FWRITE); *object = vm_pager_allocate(OBJT_DEVICE, i_dev, size, nprot, *offset, curthread->td_ucred); @@ -1998,20 +2017,27 @@ dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, 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; - int i; + size_t i; if (*dev != NULL) return; if (strcmp(name, "dsp") == 0 && dsp_basename_clone) goto found; - for (i = 0; i < nitems(dsp_cdevs); i++) { - if (dsp_cdevs[i].alias != NULL && - strcmp(name, dsp_cdevs[i].name) == 0) + for (i = 0; i < nitems(dsp_aliases); i++) { + if (strcmp(name, dsp_aliases[i]) == 0) goto found; } return; @@ -2024,7 +2050,7 @@ found: * have returned already, meaning it will have set snd_unit to -1, and * thus devclass_get_softc() will return NULL here. */ - if (d != NULL && PCM_REGISTERED(d) && d->dsp_dev != NULL) { + if (DSP_REGISTERED(d)) { *dev = d->dsp_dev; dev_ref(*dev); } @@ -2051,28 +2077,189 @@ dsp_sysuninit(void *p) SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); -char * -dsp_unit2name(char *buf, size_t len, struct pcm_channel *ch) +static void +dsp_oss_audioinfo_unavail(oss_audioinfo *ai, int unit) { - int i; + bzero(ai, sizeof(*ai)); + ai->dev = unit; + snprintf(ai->name, sizeof(ai->name), "pcm%d (unavailable)", unit); + ai->pid = -1; + strlcpy(ai->cmd, CHN_COMM_UNUSED, sizeof(ai->cmd)); + ai->card_number = unit; + ai->port_number = unit; + ai->mixer_dev = -1; + ai->legacy_device = unit; +} - KASSERT(buf != NULL && len != 0, - ("bogus buf=%p len=%ju", buf, (uintmax_t)len)); +/** + * @brief Handler for SNDCTL_AUDIOINFO. + * + * Gathers information about the audio device specified in ai->dev. If + * ai->dev == -1, then this function gathers information about the current + * device. If the call comes in on a non-audio device and ai->dev == -1, + * return EINVAL. + * + * This routine is supposed to go practically straight to the hardware, + * getting capabilities directly from the sound card driver, side-stepping + * the intermediate channel interface. + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @param dev device on which the ioctl was issued + * @param ai ioctl request data container + * @param ex flag to distinguish between SNDCTL_AUDIOINFO from + * SNDCTL_AUDIOINFO_EX + * + * @retval 0 success + * @retval EINVAL ai->dev specifies an invalid device + */ +int +dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai, bool ex) +{ + struct pcmchan_caps *caps; + struct pcm_channel *ch; + struct snddev_info *d; + uint32_t fmts; + int i, minch, maxch, unit; - for (i = 0; i < nitems(dsp_cdevs); i++) { - if (ch->type != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) + /* + * If probing the device that received the ioctl, make sure it's a + * DSP device. (Users may use this ioctl with /dev/mixer and + * /dev/midi.) + */ + if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) + return (EINVAL); + + bus_topo_lock(); + for (unit = 0; pcm_devclass != NULL && + unit < devclass_get_maxunit(pcm_devclass); unit++) { + d = devclass_get_softc(pcm_devclass, unit); + if (!PCM_REGISTERED(d)) { + if ((ai->dev == -1 && unit == snd_unit) || + ai->dev == unit) { + dsp_oss_audioinfo_unavail(ai, unit); + bus_topo_unlock(); + return (0); + } else { + d = NULL; + continue; + } + } + + PCM_UNLOCKASSERT(d); + PCM_LOCK(d); + if ((ai->dev == -1 && d->dsp_dev == i_dev) || + (ai->dev == unit)) { + PCM_UNLOCK(d); + break; + } else { + PCM_UNLOCK(d); + d = NULL; + } + } + bus_topo_unlock(); + + /* Exhausted the search -- nothing is locked, so return. */ + if (d == NULL) + return (EINVAL); + + /* XXX Need Giant magic entry ??? */ + + PCM_UNLOCKASSERT(d); + PCM_LOCK(d); + + bzero((void *)ai, sizeof(oss_audioinfo)); + ai->dev = unit; + strlcpy(ai->name, device_get_desc(d->dev), sizeof(ai->name)); + ai->pid = -1; + strlcpy(ai->cmd, CHN_COMM_UNKNOWN, sizeof(ai->cmd)); + ai->card_number = unit; + ai->port_number = unit; + ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; + ai->legacy_device = unit; + snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); + ai->enabled = device_is_attached(d->dev) ? 1 : 0; + ai->next_play_engine = 0; + ai->next_rec_engine = 0; + ai->busy = 0; + ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER; + ai->iformats = 0; + ai->oformats = 0; + ai->min_rate = INT_MAX; + ai->max_rate = 0; + ai->min_channels = INT_MAX; + ai->max_channels = 0; + + /* Gather global information about the device. */ + CHN_FOREACH(ch, d, channels.pcm) { + CHN_UNLOCKASSERT(ch); + CHN_LOCK(ch); + + /* + * Skip physical channels if we are servicing SNDCTL_AUDIOINFO, + * or VCHANs if we are servicing SNDCTL_AUDIOINFO_EX. + * + * For SNDCTL_AUDIOINFO do not skip the physical channels if + * there are no VCHANs. + */ + if ((ex && (ch->flags & CHN_F_VIRTUAL) != 0) || + ((!ex && (ch->flags & CHN_F_VIRTUAL) == 0) && + (d->pvchancount > 0 || d->rvchancount > 0))) { + CHN_UNLOCK(ch); continue; - snprintf(buf, len, "%s%d%s%d", - dsp_cdevs[i].name, device_get_unit(ch->dev), - dsp_cdevs[i].sep, ch->unit); - return (buf); + } + + if ((ch->flags & CHN_F_BUSY) == 0) { + ai->busy |= (ch->direction == PCMDIR_PLAY) ? + OPEN_WRITE : OPEN_READ; + } + + ai->caps |= + ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | + ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : + PCM_CAP_INPUT); + + caps = chn_getcaps(ch); + + minch = INT_MAX; + maxch = 0; + fmts = 0; + for (i = 0; caps->fmtlist[i]; i++) { + fmts |= AFMT_ENCODING(caps->fmtlist[i]); + minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); + maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); + } + + if (ch->direction == PCMDIR_PLAY) + ai->oformats |= fmts; + else + ai->iformats |= fmts; + + if (ex || (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { + ai->min_rate = min(ai->min_rate, caps->minspeed); + ai->max_rate = max(ai->max_rate, caps->maxspeed); + } else { + ai->min_rate = min(ai->min_rate, feeder_rate_min); + ai->max_rate = max(ai->max_rate, feeder_rate_max); + } + ai->min_channels = min(ai->min_channels, minch); + ai->max_channels = max(ai->max_channels, maxch); + + CHN_UNLOCK(ch); } + if (ai->min_rate == INT_MAX) + ai->min_rate = 0; + if (ai->min_channels == INT_MAX) + ai->min_channels = 0; - return (NULL); + PCM_UNLOCK(d); + + return (0); } static int -dsp_oss_audioinfo_cb(void *data, void *arg) +dsp_oss_engineinfo_cb(void *data, void *arg) { struct dsp_cdevpriv *priv = data; struct pcm_channel *ch = arg; @@ -2084,10 +2271,10 @@ dsp_oss_audioinfo_cb(void *data, void *arg) } /** - * @brief Handler for SNDCTL_AUDIOINFO. + * @brief Handler for SNDCTL_ENGINEINFO * - * Gathers information about the audio device specified in ai->dev. If - * ai->dev == -1, then this function gathers information about the current + * Gathers information about the audio device's engine specified in ai->dev. + * If ai->dev == -1, then this function gathers information about the current * device. If the call comes in on a non-audio device and ai->dev == -1, * return EINVAL. * @@ -2103,18 +2290,15 @@ dsp_oss_audioinfo_cb(void *data, void *arg) * * @retval 0 success * @retval EINVAL ai->dev specifies an invalid device - * - * @todo Verify correctness of Doxygen tags. ;) */ int -dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) +dsp_oss_engineinfo(struct cdev *i_dev, oss_audioinfo *ai) { struct pcmchan_caps *caps; struct pcm_channel *ch; struct snddev_info *d; uint32_t fmts; int i, nchan, *rates, minch, maxch, unit; - char *devname, buf[CHN_NAMELEN]; /* * If probing the device that received the ioctl, make sure it's a @@ -2125,14 +2309,13 @@ dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) return (EINVAL); ch = NULL; - devname = NULL; nchan = 0; - bzero(buf, sizeof(buf)); /* * Search for the requested audio device (channel). Start by * iterating over pcm devices. */ + bus_topo_lock(); for (unit = 0; pcm_devclass != NULL && unit < devclass_get_maxunit(pcm_devclass); unit++) { d = devclass_get_softc(pcm_devclass, unit); @@ -2148,158 +2331,145 @@ dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) CHN_FOREACH(ch, d, channels.pcm) { CHN_UNLOCKASSERT(ch); CHN_LOCK(ch); - if (ai->dev == -1) { - if (devfs_foreach_cdevpriv(i_dev, - dsp_oss_audioinfo_cb, ch) != 0) { - devname = dsp_unit2name(buf, - sizeof(buf), ch); - } - } else if (ai->dev == nchan) - devname = dsp_unit2name(buf, sizeof(buf), ch); - if (devname != NULL) + if ((ai->dev == -1 && devfs_foreach_cdevpriv( + i_dev, dsp_oss_engineinfo_cb, ch) != 0) || + ai->dev == nchan) break; CHN_UNLOCK(ch); ++nchan; } - if (devname != NULL) { - /* - * At this point, the following synchronization stuff - * has happened: - * - a specific PCM device is locked. - * - a specific audio channel has been locked, so be - * sure to unlock when exiting; - */ + if (ch == NULL) { + PCM_UNLOCK(d); + continue; + } - caps = chn_getcaps(ch); + /* + * At this point, the following synchronization stuff + * has happened: + * - a specific PCM device is locked. + * - a specific audio channel has been locked, so be + * sure to unlock when exiting; + */ - /* - * With all handles collected, zero out the user's - * container and begin filling in its fields. - */ - bzero((void *)ai, sizeof(oss_audioinfo)); + caps = chn_getcaps(ch); - ai->dev = nchan; - strlcpy(ai->name, ch->name, sizeof(ai->name)); + /* + * With all handles collected, zero out the user's + * container and begin filling in its fields. + */ + bzero((void *)ai, sizeof(oss_audioinfo)); - if ((ch->flags & CHN_F_BUSY) == 0) - ai->busy = 0; - else - ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; - - /** - * @note - * @c cmd - OSSv4 docs: "Only supported under Linux at - * this moment." Cop-out, I know, but I'll save - * running around in the process table for later. - * Is there a risk of leaking information? - */ - ai->pid = ch->pid; + ai->dev = nchan; + strlcpy(ai->name, ch->name, sizeof(ai->name)); - /* - * These flags stolen from SNDCTL_DSP_GETCAPS handler. - * Note, however, that a single channel operates in - * only one direction, so PCM_CAP_DUPLEX is out. - */ - /** - * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep - * these in pcmchan::caps? - */ - ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | - ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | - ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); + if ((ch->flags & CHN_F_BUSY) == 0) + ai->busy = 0; + else + ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; - /* - * Collect formats supported @b natively by the - * device. Also determine min/max channels. (I.e., - * mono, stereo, or both?) - * - * If any channel is stereo, maxch = 2; - * if all channels are stereo, minch = 2, too; - * if any channel is mono, minch = 1; - * and if all channels are mono, maxch = 1. - */ - minch = 0; - maxch = 0; - fmts = 0; - for (i = 0; caps->fmtlist[i]; i++) { - fmts |= caps->fmtlist[i]; - if (AFMT_CHANNEL(caps->fmtlist[i]) > 1) { - minch = (minch == 0) ? 2 : minch; - maxch = 2; - } else { - minch = 1; - maxch = (maxch == 0) ? 1 : maxch; - } - } + ai->pid = ch->pid; + strlcpy(ai->cmd, ch->comm, sizeof(ai->cmd)); - if (ch->direction == PCMDIR_PLAY) - ai->oformats = fmts; - else - ai->iformats = fmts; - - /** - * @note - * @c magic - OSSv4 docs: "Reserved for internal use - * by OSS." - * - * @par - * @c card_number - OSSv4 docs: "Number of the sound - * card where this device belongs or -1 if this - * information is not available. Applications - * should normally not use this field for any - * purpose." - */ - ai->card_number = -1; - /** - * @todo @c song_name - depends first on - * SNDCTL_[GS]ETSONG @todo @c label - depends - * on SNDCTL_[GS]ETLABEL - * @todo @c port_number - routing information? - */ - ai->port_number = -1; - ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; - /** - * @note - * @c real_device - OSSv4 docs: "Obsolete." - */ - ai->real_device = -1; - snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); - ai->enabled = device_is_attached(d->dev) ? 1 : 0; - /** - * @note - * @c flags - OSSv4 docs: "Reserved for future use." - * - * @note - * @c binding - OSSv4 docs: "Reserved for future use." - * - * @todo @c handle - haven't decided how to generate - * this yet; bus, vendor, device IDs? - */ + /* + * These flags stolen from SNDCTL_DSP_GETCAPS handler. + * Note, however, that a single channel operates in + * only one direction, so PCM_CAP_DUPLEX is out. + */ + /** + * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep + * these in pcmchan::caps? + */ + ai->caps = PCM_CAP_REALTIME | PCM_CAP_MMAP | PCM_CAP_TRIGGER | + ((ch->flags & CHN_F_VIRTUAL) ? PCM_CAP_VIRTUAL : 0) | + ((ch->direction == PCMDIR_PLAY) ? PCM_CAP_OUTPUT : PCM_CAP_INPUT); + + /* + * Collect formats supported @b natively by the + * device. Also determine min/max channels. + */ + minch = INT_MAX; + maxch = 0; + fmts = 0; + for (i = 0; caps->fmtlist[i]; i++) { + fmts |= AFMT_ENCODING(caps->fmtlist[i]); + minch = min(AFMT_CHANNEL(caps->fmtlist[i]), minch); + maxch = max(AFMT_CHANNEL(caps->fmtlist[i]), maxch); + } + + if (ch->direction == PCMDIR_PLAY) + ai->oformats = fmts; + else + ai->iformats = fmts; + + /** + * @note + * @c magic - OSSv4 docs: "Reserved for internal use + * by OSS." + * + * @par + * @c card_number - OSSv4 docs: "Number of the sound + * card where this device belongs or -1 if this + * information is not available. Applications + * should normally not use this field for any + * purpose." + */ + ai->card_number = unit; + /** + * @todo @c song_name - depends first on + * SNDCTL_[GS]ETSONG @todo @c label - depends + * on SNDCTL_[GS]ETLABEL + * @todo @c port_number - routing information? + */ + ai->port_number = unit; + ai->mixer_dev = (d->mixer_dev != NULL) ? unit : -1; + /** + * @note + * @c legacy_device - OSSv4 docs: "Obsolete." + */ + ai->legacy_device = unit; + snprintf(ai->devnode, sizeof(ai->devnode), "/dev/dsp%d", unit); + ai->enabled = device_is_attached(d->dev) ? 1 : 0; + /** + * @note + * @c flags - OSSv4 docs: "Reserved for future use." + * + * @note + * @c binding - OSSv4 docs: "Reserved for future use." + * + * @todo @c handle - haven't decided how to generate + * this yet; bus, vendor, device IDs? + */ + + if ((ch->flags & CHN_F_EXCLUSIVE) || + (pcm_getflags(d->dev) & SD_F_BITPERFECT)) { ai->min_rate = caps->minspeed; ai->max_rate = caps->maxspeed; + } else { + ai->min_rate = feeder_rate_min; + ai->max_rate = feeder_rate_max; + } - ai->min_channels = minch; - ai->max_channels = maxch; + ai->min_channels = minch; + ai->max_channels = maxch; - ai->nrates = chn_getrates(ch, &rates); - if (ai->nrates > OSS_MAX_SAMPLE_RATES) - ai->nrates = OSS_MAX_SAMPLE_RATES; + ai->nrates = chn_getrates(ch, &rates); + if (ai->nrates > OSS_MAX_SAMPLE_RATES) + ai->nrates = OSS_MAX_SAMPLE_RATES; - for (i = 0; i < ai->nrates; i++) - ai->rates[i] = rates[i]; - - ai->next_play_engine = 0; - ai->next_rec_engine = 0; + for (i = 0; i < ai->nrates; i++) + ai->rates[i] = rates[i]; - CHN_UNLOCK(ch); - } + ai->next_play_engine = 0; + ai->next_rec_engine = 0; + CHN_UNLOCK(ch); PCM_UNLOCK(d); + bus_topo_unlock(); - if (devname != NULL) - return (0); + return (0); } + bus_topo_unlock(); /* Exhausted the search -- nothing is locked, so return. */ return (EINVAL); @@ -2384,7 +2554,7 @@ dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgr * syncgroup. */ if (group->id == 0) { - sg = (struct pcmchan_syncgroup *)malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); + sg = malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); if (sg != NULL) { SLIST_INIT(&sg->members); sg->id = alloc_unr(pcmsg_unrhdr); @@ -2411,7 +2581,7 @@ dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgr * insert into syncgroup. */ if (group->mode & PCM_ENABLE_INPUT) { - smrd = (struct pcmchan_syncmember *)malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); + smrd = malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); if (smrd == NULL) { ret = ENOMEM; goto out; @@ -2427,7 +2597,7 @@ dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgr } if (group->mode & PCM_ENABLE_OUTPUT) { - smwr = (struct pcmchan_syncmember *)malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); + smwr = malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); if (smwr == NULL) { ret = ENOMEM; goto out; |