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 March 24, 2024 +.Dd December 4, 2024 .Dt SOUND 4 .Os .Sh NAME @@ -360,14 +360,12 @@ in possible underruns if the application cannot keep up with a rapid irq rate, especially during high workload. The default value is 1, which is considered a moderate/safe latency profile. -.It Va hw.snd.maxautovchans -Global VCHAN setting that only affects devices with at least one playback or -recording channel available. -The sound system will dynamically create up to this many VCHANs. -Set to -.Dq 0 -if no VCHANs are desired. -Maximum value is 256. +.It Va hw.snd.vchans_enable +Global VCHAN setting to enable (1) or disable (0) VCHANs. +This setting can be overridden for an individual device by using the +.Va dev.pcm.%d.[play|rec].vchans +tunables. +Default is enabled. .It Va hw.snd.report_soft_formats Controls the internal format conversion if it is available transparently to the application software. @@ -434,11 +432,8 @@ sysctl. Default is disabled. .It Va dev.pcm.%d.[play|rec].vchans -The current number of VCHANs allocated per device. -This can be set to preallocate a certain number of VCHANs. -Setting this value to -.Dq 0 -will disable VCHANs for this device. +Enable (1) or disable (0) VCHANs. +Default is enabled. .It Va dev.pcm.%d.[play|rec].vchanformat Format for VCHAN mixing. All playback paths will be converted to this format before the mixing diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h --- a/sys/dev/sound/pcm/channel.h +++ b/sys/dev/sound/pcm/channel.h @@ -160,6 +160,9 @@ struct { SLIST_ENTRY(pcm_channel) link; } opened; + struct { + SLIST_ENTRY(pcm_channel) link; + } primary; } pcm; } channels; diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c --- a/sys/dev/sound/pcm/channel.c +++ b/sys/dev/sound/pcm/channel.c @@ -1176,7 +1176,7 @@ struct feeder_class *fc; struct snd_dbuf *b, *bs; char buf[CHN_NAMELEN]; - int i, direction; + int err, i, direction, fmt, spd; PCM_BUSYASSERT(d); PCM_LOCKASSERT(d); @@ -1185,10 +1185,14 @@ case PCMDIR_PLAY: case PCMDIR_PLAY_VIRTUAL: direction = PCMDIR_PLAY; + fmt = d->pvchanformat; + spd = d->pvchanrate; break; case PCMDIR_REC: case PCMDIR_REC_VIRTUAL: direction = PCMDIR_REC; + fmt = d->rvchanformat; + spd = d->rvchanrate; break; default: device_printf(d->dev, @@ -1209,8 +1213,8 @@ c->direction = direction; c->type = dir; c->unit = alloc_unr(chn_getunr(d, c->type)); - c->format = SND_FORMAT(AFMT_U8, 1, 0); - c->speed = DSP_DEFAULT_SPEED; + c->format = fmt; + c->speed = spd; c->pid = -1; c->latency = -1; c->timeout = 1; @@ -1283,8 +1287,19 @@ bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_WAITOK); } + /* vchans get a chn_reset() from vchan_create(). */ + if ((c->flags & CHN_F_VIRTUAL) == 0) { + CHN_LOCK(c); + err = chn_reset(c, c->format, c->speed); + CHN_UNLOCK(c); + if (err != 0) + goto fail; + } + PCM_LOCK(d); CHN_INSERT_SORT_ASCEND(d, c, channels.pcm); + if ((c->flags & CHN_F_VIRTUAL) == 0) + CHN_INSERT_SORT_ASCEND(d, c, channels.pcm.primary); switch (c->type) { case PCMDIR_PLAY: @@ -1336,6 +1351,8 @@ PCM_LOCK(d); CHN_REMOVE(d, c, channels.pcm); + if ((c->flags & CHN_F_VIRTUAL) == 0) + CHN_REMOVE(d, c, channels.pcm.primary); switch (c->type) { case PCMDIR_PLAY: 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 @@ -37,6 +37,7 @@ #endif #include +#include #include #include #include @@ -158,6 +159,79 @@ CHN_UNLOCK(priv->wrch); } +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, fmt, spd; + + 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); + + fmt = SND_FORMAT(AFMT_U8, 1, 0); + spd = DSP_DEFAULT_SPEED; + pid = td->td_proc->p_pid; + comm = td->td_proc->p_comm; + + *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 || c->flags & CHN_F_HAS_VCHAN) + break; + CHN_UNLOCK(c); + } + if (c == NULL) + return (EBUSY); + + /* + * We can have the following cases: + * - vchans are enabled, use primary channel to add a new vchan to. + * - vchans are disabled, use the primary channel directly. + */ + if (((direction == PCMDIR_PLAY && d->flags & SD_F_PVCHANS) || + (direction == PCMDIR_REC && d->flags & SD_F_RVCHANS)) && + ((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, fmt, spd)) != 0) + return (err); + chn_vpc_reset(*ch, SND_VOL_C_PCM, 0); + + CHN_UNLOCK(*ch); + + 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)) @@ -168,7 +242,7 @@ dsp_close(void *data) { struct dsp_cdevpriv *priv = data; - struct pcm_channel *rdch, *wrch; + struct pcm_channel *rdch, *wrch, *parent; struct snddev_info *d; int sg_ids; @@ -214,12 +288,20 @@ if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); - CHN_LOCK(rdch); - chn_abort(rdch); /* won't sleep */ - rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | - CHN_F_DEAD | CHN_F_EXCLUSIVE); - 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); + } else { + CHN_LOCK(rdch); + chn_abort(rdch); /* won't sleep */ + rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | + CHN_F_DEAD | CHN_F_EXCLUSIVE); + chn_reset(rdch, 0, 0); + chn_release(rdch); + } } if (wrch != NULL) { /* @@ -231,12 +313,20 @@ if (sg_ids != 0) free_unr(pcmsg_unrhdr, sg_ids); - CHN_LOCK(wrch); - chn_flush(wrch); /* may sleep */ - wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | - CHN_F_DEAD | CHN_F_EXCLUSIVE); - 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); + } else { + CHN_LOCK(wrch); + chn_flush(wrch); /* may sleep */ + wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MMAP | + CHN_F_DEAD | CHN_F_EXCLUSIVE); + chn_reset(wrch, 0, 0); + chn_release(wrch); + } } PCM_LOCK(d); } @@ -254,10 +344,9 @@ dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct dsp_cdevpriv *priv; - struct pcm_channel *rdch, *wrch, *ch; + struct pcm_channel *ch; struct snddev_info *d; - uint32_t fmt, spd; - int error, rderror, wrerror, dir; + int error, dir; /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) @@ -267,6 +356,9 @@ 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; @@ -334,94 +426,21 @@ 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_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, and release previously created - * record channel - */ - CHN_LOCK(rdch); - 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_vpc_reset(wrch, SND_VOL_C_PCM, 0); - CHN_UNLOCK(wrch); - } + if ((DSP_F_WRITE(flags) && (error = dsp_chn_alloc(d, &priv->wrch, + PCMDIR_PLAY, flags, td)) != 0) || + (DSP_F_READ(flags) && (error = dsp_chn_alloc(d, &priv->rdch, + PCMDIR_REC, flags, td)) != 0)) { + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (error); } PCM_LOCK(d); - if (wrch == NULL && rdch == NULL) { - PCM_RELEASE(d); - 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; + if (priv->rdch != NULL) + CHN_INSERT_HEAD(d, priv->rdch, channels.pcm.opened); + if (priv->wrch != NULL) + CHN_INSERT_HEAD(d, priv->wrch, channels.pcm.opened); PCM_RELEASE(d); PCM_UNLOCK(d); diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -98,8 +98,6 @@ #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER -#define SND_MAXVCHANS 256 - #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 @@ -112,6 +110,8 @@ #define SD_F_EQ_ENABLED 0x00000200 /* EQ enabled */ #define SD_F_EQ_BYPASSED 0x00000400 /* EQ bypassed */ #define SD_F_EQ_PC 0x00000800 /* EQ per-channel */ +#define SD_F_PVCHANS 0x00001000 /* Playback vchans enabled */ +#define SD_F_RVCHANS 0x00002000 /* Recording vchans enabled */ #define SD_F_EQ_DEFAULT (SD_F_EQ | SD_F_EQ_ENABLED) #define SD_F_EQ_MASK (SD_F_EQ | SD_F_EQ_ENABLED | \ @@ -133,12 +133,15 @@ "\012EQ_ENABLED" \ "\013EQ_BYPASSED" \ "\014EQ_PC" \ + "\015PVCHANS" \ + "\016RVCHANS" \ "\035PRIO_RD" \ "\036PRIO_WR" #define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL) #define PCM_REGISTERED(x) (PCM_ALIVE(x) && ((x)->flags & SD_F_REGISTERED)) +#define PCM_MAXCHANS 10000 #define PCM_CHANCOUNT(d) \ (d->playcount + d->pvchancount + d->reccount + d->rvchancount) @@ -167,9 +170,6 @@ SYSCTL_DECL(_hw_snd); -int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, - pid_t pid, char *comm); - int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); void pcm_init(device_t dev, void *devinfo); @@ -223,6 +223,9 @@ struct { SLIST_HEAD(, pcm_channel) head; } opened; + struct { + SLIST_HEAD(, pcm_channel) head; + } primary; } pcm; } channels; unsigned playcount, reccount, pvchancount, rvchancount; diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c --- a/sys/dev/sound/pcm/sound.c +++ b/sys/dev/sound/pcm/sound.c @@ -107,62 +107,6 @@ return bus_setup_intr(dev, res, flags, NULL, hand, param, cookiep); } -int -pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, - pid_t pid, char *comm) -{ - struct pcm_channel *c; - int err, vchancount; - bool retry; - - KASSERT(d != NULL && ch != NULL && - (direction == PCMDIR_PLAY || direction == PCMDIR_REC), - ("%s(): invalid d=%p ch=%p direction=%d pid=%d", - __func__, d, ch, direction, pid)); - PCM_BUSYASSERT(d); - - *ch = NULL; - vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount : - d->rvchancount; - retry = false; - -retry_chnalloc: - /* Scan for a free channel. */ - CHN_FOREACH(c, d, channels.pcm) { - CHN_LOCK(c); - if (c->direction != direction) { - CHN_UNLOCK(c); - continue; - } - if (!(c->flags & CHN_F_BUSY)) { - c->flags |= CHN_F_BUSY; - c->pid = pid; - strlcpy(c->comm, (comm != NULL) ? comm : - CHN_COMM_UNKNOWN, sizeof(c->comm)); - *ch = c; - - return (0); - } - CHN_UNLOCK(c); - } - /* Maybe next time... */ - if (retry) - return (EBUSY); - - /* No channel available. We also cannot create more VCHANs. */ - if (!(vchancount > 0 && vchancount < snd_maxautovchans)) - return (ENOTSUP); - - /* Increase the VCHAN count and try to get the new channel. */ - err = vchan_setnew(d, direction, vchancount + 1); - if (err == 0) { - retry = true; - goto retry_chnalloc; - } - - return (err); -} - static int sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) { @@ -460,10 +404,10 @@ d->playcount = 0; d->pvchancount = 0; d->rvchancount = 0; - d->pvchanrate = 0; - d->pvchanformat = 0; - d->rvchanrate = 0; - d->rvchanformat = 0; + d->pvchanrate = VCHAN_DEFAULT_RATE; + d->pvchanformat = VCHAN_DEFAULT_FORMAT;; + d->rvchanrate = VCHAN_DEFAULT_RATE; + d->rvchanformat = VCHAN_DEFAULT_FORMAT; d->p_unr = new_unrhdr(0, INT_MAX, NULL); d->vp_unr = new_unrhdr(0, INT_MAX, NULL); d->r_unr = new_unrhdr(0, INT_MAX, NULL); @@ -472,6 +416,7 @@ CHN_INIT(d, channels.pcm); CHN_INIT(d, channels.pcm.busy); CHN_INIT(d, channels.pcm.opened); + CHN_INIT(d, channels.pcm.primary); } int @@ -491,7 +436,10 @@ if (d->playcount > 0 || d->reccount > 0) d->flags |= SD_F_AUTOVCHAN; - vchan_setmaxauto(d, snd_maxautovchans); + if (d->playcount > 0) + d->flags |= SD_F_PVCHANS; + if (d->reccount > 0) + d->flags |= SD_F_RVCHANS; strlcpy(d->status, str, SND_STATUSLEN); sndstat_register(dev, d->status); @@ -733,9 +681,7 @@ if (snd_unit < 0) snd_unit = -1; - if (snd_maxautovchans < 0 || - snd_maxautovchans > SND_MAXVCHANS) - snd_maxautovchans = 0; + snd_vchans_enable = true; if (chn_latency < CHN_LATENCY_MIN || chn_latency > CHN_LATENCY_MAX) @@ -759,11 +705,11 @@ feeder_rate_round = FEEDRATE_ROUNDHZ; if (bootverbose) - printf("%s: snd_unit=%d snd_maxautovchans=%d " + printf("%s: snd_unit=%d snd_vchans_enable=%d " "latency=%d " "feeder_rate_min=%d feeder_rate_max=%d " "feeder_rate_round=%d\n", - __func__, snd_unit, snd_maxautovchans, + __func__, snd_unit, snd_vchans_enable, chn_latency, feeder_rate_min, feeder_rate_max, feeder_rate_round); diff --git a/sys/dev/sound/pcm/vchan.h b/sys/dev/sound/pcm/vchan.h --- a/sys/dev/sound/pcm/vchan.h +++ b/sys/dev/sound/pcm/vchan.h @@ -30,9 +30,9 @@ #ifndef _SND_VCHAN_H_ #define _SND_VCHAN_H_ -extern int snd_maxautovchans; +extern bool snd_vchans_enable; -int vchan_create(struct pcm_channel *); +int vchan_create(struct pcm_channel *, struct pcm_channel **); int vchan_destroy(struct pcm_channel *); #ifdef SND_DEBUG @@ -47,9 +47,6 @@ sndbuf_getfmt((c)->bufhard) != (c)->parentchannel->format || \ sndbuf_getspd((c)->bufhard) != (c)->parentchannel->speed)) -int vchan_setnew(struct snddev_info *, int, int); -void vchan_setmaxauto(struct snddev_info *, int); - void vchan_initsys(device_t); /* diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -61,7 +61,7 @@ int trigger; }; -int snd_maxautovchans = 16; +bool snd_vchans_enable = true; static void * vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, @@ -277,7 +277,7 @@ *ch = NULL; break; } - } else if (c->flags & CHN_F_HAS_VCHAN) { + } else { /* No way!! */ if (*ch != NULL) { CHN_UNLOCK(c); @@ -299,8 +299,7 @@ sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - int direction, vchancount; - int err, cnt; + int err, enabled, flag; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) @@ -311,43 +310,44 @@ switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: - direction = PCMDIR_PLAY; - vchancount = d->pvchancount; - cnt = d->playcount; + /* Exit if we do not support this direction. */ + if (d->playcount < 1) { + PCM_UNLOCK(d); + return (ENODEV); + } + flag = SD_F_PVCHANS; break; case VCHAN_REC: - direction = PCMDIR_REC; - vchancount = d->rvchancount; - cnt = d->reccount; + if (d->reccount < 1) { + PCM_UNLOCK(d); + return (ENODEV); + } + flag = SD_F_RVCHANS; break; default: PCM_UNLOCK(d); return (EINVAL); - break; } - if (cnt < 1) { - PCM_UNLOCK(d); - return (ENODEV); - } + enabled = (d->flags & flag) != 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); - cnt = vchancount; - err = sysctl_handle_int(oidp, &cnt, 0, req); - - if (err == 0 && req->newptr != NULL && vchancount != cnt) { - if (cnt < 0) - cnt = 0; - if (cnt > SND_MAXVCHANS) - cnt = SND_MAXVCHANS; - err = vchan_setnew(d, direction, cnt); + err = sysctl_handle_int(oidp, &enabled, 0, req); + if (err != 0 || req->newptr == NULL) { + PCM_RELEASE_QUICK(d); + return (err); } + if (enabled <= 0) + d->flags &= ~flag; + else + d->flags |= flag; + PCM_RELEASE_QUICK(d); - return err; + return (0); } static int @@ -368,15 +368,22 @@ switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: + if ((d->flags & SD_F_PVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_PLAY; break; case VCHAN_REC: + if ((d->flags & SD_F_RVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_REC; break; default: PCM_UNLOCK(d); return (EINVAL); - break; } PCM_ACQUIRE(d); @@ -450,7 +457,7 @@ struct snddev_info *d; struct pcm_channel *c, *ch; struct pcmchan_caps *caps; - int *vchanrate, vchancount, direction, ret, newspd, restart; + int *vchanrate, direction, ret, newspd, restart; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) @@ -461,24 +468,24 @@ switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: + if ((d->flags & SD_F_PVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_PLAY; - vchancount = d->pvchancount; vchanrate = &d->pvchanrate; break; case VCHAN_REC: + if ((d->flags & SD_F_RVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_REC; - vchancount = d->rvchancount; vchanrate = &d->rvchanrate; break; default: PCM_UNLOCK(d); return (EINVAL); - break; - } - - if (vchancount < 1) { - PCM_UNLOCK(d); - return (EINVAL); } PCM_ACQUIRE(d); @@ -497,9 +504,7 @@ KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); - CHN_LOCK(c); - newspd = c->speed; - CHN_UNLOCK(c); + newspd = *vchanrate; ret = sysctl_handle_int(oidp, &newspd, 0, req); if (ret != 0 || req->newptr == NULL) { @@ -557,7 +562,7 @@ struct snddev_info *d; struct pcm_channel *c, *ch; uint32_t newfmt; - int *vchanformat, vchancount, direction, ret, restart; + int *vchanformat, direction, ret, restart; char fmtstr[AFMTSTR_LEN]; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); @@ -569,24 +574,24 @@ switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: + if ((d->flags & SD_F_PVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_PLAY; - vchancount = d->pvchancount; vchanformat = &d->pvchanformat; break; case VCHAN_REC: + if ((d->flags & SD_F_RVCHANS) == 0) { + PCM_UNLOCK(d); + return (ENODEV); + } direction = PCMDIR_REC; - vchancount = d->rvchancount; vchanformat = &d->rvchanformat; break; default: PCM_UNLOCK(d); return (EINVAL); - break; - } - - if (vchancount < 1) { - PCM_UNLOCK(d); - return (EINVAL); } PCM_ACQUIRE(d); @@ -605,15 +610,11 @@ KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); - CHN_LOCK(c); - bzero(fmtstr, sizeof(fmtstr)); - if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format) + if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat) strlcpy(fmtstr, "", sizeof(fmtstr)); - CHN_UNLOCK(c); - ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); @@ -666,7 +667,7 @@ "play.vchanrate" : "rec.vchanrate" int -vchan_create(struct pcm_channel *parent) +vchan_create(struct pcm_channel *parent, struct pcm_channel **child) { struct snddev_info *d; struct pcm_channel *ch; @@ -682,9 +683,6 @@ PCM_BUSYASSERT(d); CHN_LOCKASSERT(parent); - if (!(parent->flags & CHN_F_BUSY)) - return (EBUSY); - if (!(parent->direction == PCMDIR_PLAY || parent->direction == PCMDIR_REC)) return (EINVAL); @@ -719,10 +717,12 @@ */ CHN_INSERT_SORT_DESCEND(parent, ch, children); + *child = ch; + if (parent->flags & CHN_F_HAS_VCHAN) return (0); - parent->flags |= CHN_F_HAS_VCHAN; + parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY; parent_caps = chn_getcaps(parent); if (parent_caps == NULL) { @@ -891,166 +891,40 @@ return (ret); } -int -vchan_setnew(struct snddev_info *d, int direction, int newcnt) -{ - struct pcm_channel *c, *ch, *nch; - struct pcmchan_caps *caps; - int i, err, vcnt; - - PCM_BUSYASSERT(d); - - if ((direction == PCMDIR_PLAY && d->playcount < 1) || - (direction == PCMDIR_REC && d->reccount < 1)) - return (ENODEV); - - if (!(d->flags & SD_F_AUTOVCHAN)) - return (EINVAL); - - if (newcnt < 0 || newcnt > SND_MAXVCHANS) - return (E2BIG); - - if (direction == PCMDIR_PLAY) - vcnt = d->pvchancount; - else if (direction == PCMDIR_REC) - vcnt = d->rvchancount; - else - return (EINVAL); - - if (newcnt > vcnt) { - /* add new vchans - find a parent channel first */ - ch = NULL; - CHN_FOREACH(c, d, channels.pcm) { - CHN_LOCK(c); - if (c->direction == direction && - ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 && - !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) { - /* - * Reuse hw channel with vchans already - * created. - */ - if (c->flags & CHN_F_HAS_VCHAN) { - ch = c; - break; - } - /* - * No vchans ever created, look for - * channels with supported formats. - */ - caps = chn_getcaps(c); - if (caps == NULL) { - CHN_UNLOCK(c); - continue; - } - for (i = 0; caps->fmtlist[i] != 0; i++) { - if (caps->fmtlist[i] & AFMT_CONVERTIBLE) - break; - } - if (caps->fmtlist[i] != 0) { - ch = c; - break; - } - } - CHN_UNLOCK(c); - } - if (ch == NULL) - return (EBUSY); - ch->flags |= CHN_F_BUSY; - err = 0; - while (err == 0 && newcnt > vcnt) { - err = vchan_create(ch); - if (err == 0) - vcnt++; - else if (err == E2BIG && newcnt > vcnt) - device_printf(d->dev, - "%s: err=%d Maximum channel reached.\n", - __func__, err); - } - if (vcnt == 0) - ch->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(ch); - if (err != 0) - return (err); - } else if (newcnt < vcnt) { - CHN_FOREACH(c, d, channels.pcm) { - CHN_LOCK(c); - if (c->direction != direction || - CHN_EMPTY(c, children) || - !(c->flags & CHN_F_HAS_VCHAN)) { - CHN_UNLOCK(c); - continue; - } - CHN_FOREACH_SAFE(ch, c, nch, children) { - CHN_LOCK(ch); - if (vcnt == 1 && ch->flags & CHN_F_BUSY) { - CHN_UNLOCK(ch); - break; - } - if (!(ch->flags & CHN_F_BUSY)) { - err = vchan_destroy(ch); - if (err == 0) - vcnt--; - } else - CHN_UNLOCK(ch); - if (vcnt == newcnt) - break; - } - CHN_UNLOCK(c); - break; - } - } - - return (0); -} - -void -vchan_setmaxauto(struct snddev_info *d, int num) -{ - PCM_BUSYASSERT(d); - - if (num < 0) - return; - - if (num >= 0 && d->pvchancount > num) - (void)vchan_setnew(d, PCMDIR_PLAY, num); - else if (num > 0 && d->pvchancount == 0) - (void)vchan_setnew(d, PCMDIR_PLAY, 1); - - if (num >= 0 && d->rvchancount > num) - (void)vchan_setnew(d, PCMDIR_REC, num); - else if (num > 0 && d->rvchancount == 0) - (void)vchan_setnew(d, PCMDIR_REC, 1); -} - static int -sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, v, error; - v = snd_maxautovchans; + v = snd_vchans_enable; error = sysctl_handle_int(oidp, &v, 0, req); - if (error == 0 && req->newptr != NULL) { - if (v < 0) - v = 0; - if (v > SND_MAXVCHANS) - v = SND_MAXVCHANS; - snd_maxautovchans = v; - for (i = 0; pcm_devclass != NULL && - i < devclass_get_maxunit(pcm_devclass); i++) { - d = devclass_get_softc(pcm_devclass, i); - if (!PCM_REGISTERED(d)) - continue; - PCM_ACQUIRE_QUICK(d); - vchan_setmaxauto(d, v); - PCM_RELEASE_QUICK(d); - } + if (error != 0 || req->newptr == NULL) + return (error); + + snd_vchans_enable = v >= 1; + + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + PCM_ACQUIRE_QUICK(d); + if (snd_vchans_enable) { + if (d->playcount > 0) + d->flags |= SD_F_PVCHANS; + if (d->reccount > 0) + d->flags |= SD_F_RVCHANS; + } else + d->flags &= ~(SD_F_PVCHANS | SD_F_RVCHANS); + PCM_RELEASE_QUICK(d); } - return (error); + + return (0); } -SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, +SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), - sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); + sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch"); void vchan_initsys(device_t dev) @@ -1066,7 +940,7 @@ SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, - sysctl_dev_pcm_vchans, "I", "total allocated virtual channel"); + sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanmode", @@ -1092,7 +966,7 @@ OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, - sysctl_dev_pcm_vchans, "I", "total allocated virtual channel"); + sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanmode",