diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h index 0452a58dfcbf..92d6e79600d1 100644 --- a/sys/dev/sound/pcm/sound.h +++ b/sys/dev/sound/pcm/sound.h @@ -1,514 +1,514 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005-2009 Ariff Abdullah * Copyright (c) 1999 Cameron Grant * Copyright (c) 1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * first, include kernel header files. */ #ifndef _OS_H_ #define _OS_H_ #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif struct pcm_channel; struct pcm_feeder; struct snd_dbuf; struct snd_mixer; #include #include #include #include #include #include #include #define PCM_SOFTC_SIZE (sizeof(struct snddev_info)) #define SND_STATUSLEN 64 #define SOUND_MODVER 5 #define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER #define SOUND_MAXVER SOUND_MODVER #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 #define SD_F_BUSY 0x00000008 #define SD_F_MPSAFE 0x00000010 #define SD_F_REGISTERED 0x00000020 #define SD_F_BITPERFECT 0x00000040 #define SD_F_VPC 0x00000080 /* volume-per-channel */ #define SD_F_EQ 0x00000100 /* EQ */ #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 | \ SD_F_EQ_BYPASSED | SD_F_EQ_PC) #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_BITS "\020" \ "\001SIMPLEX" \ "\002AUTOVCHAN" \ "\003SOFTPCMVOL" \ "\004BUSY" \ "\005MPSAFE" \ "\006REGISTERED" \ "\007BITPERFECT" \ "\010VPC" \ "\011EQ" \ "\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) /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) enum { SND_DEV_CTL = 0, /* Control port /dev/mixer */ SND_DEV_SEQ, /* Sequencer /dev/sequencer */ SND_DEV_MIDIN, /* Raw midi access */ SND_DEV_DSP, /* Digitized voice /dev/dsp */ SND_DEV_STATUS, /* /dev/sndstat */ }; #define DSP_DEFAULT_SPEED 8000 extern int snd_unit; extern int snd_verbose; extern devclass_t pcm_devclass; extern struct unrhdr *pcmsg_unrhdr; #ifndef DEB #define DEB(x) #endif SYSCTL_DECL(_hw_snd); 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); int pcm_register(device_t dev, char *str); int pcm_unregister(device_t dev); u_int32_t pcm_getflags(device_t dev); void pcm_setflags(device_t dev, u_int32_t val); void *pcm_getdevinfo(device_t dev); int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep); void *snd_mtxcreate(const char *desc, const char *type); void snd_mtxfree(void *m); void snd_mtxassert(void *m); #define snd_mtxlock(m) mtx_lock(m) #define snd_mtxunlock(m) mtx_unlock(m) int sndstat_register(device_t dev, char *str); int sndstat_unregister(device_t dev); /* These are the function codes assigned to the children of sound cards. */ enum { SCF_PCM, SCF_MIDI, SCF_SYNTH, }; /* * This is the device information struct, used by a bridge device to pass the * device function code to the children. */ struct sndcard_func { int func; /* The function code. */ void *varinfo; /* Bridge-specific information. */ }; /* * this is rather kludgey- we need to duplicate these struct def'ns from sound.c * so that the macro versions of pcm_{,un}lock can dereference them. * we also have to do this now makedev() has gone away. */ struct snddev_info { struct { struct { SLIST_HEAD(, pcm_channel) head; struct { SLIST_HEAD(, pcm_channel) head; } busy; struct { SLIST_HEAD(, pcm_channel) head; } opened; struct { SLIST_HEAD(, pcm_channel) head; } primary; } pcm; } channels; unsigned playcount, reccount, pvchancount, rvchancount; unsigned flags; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; struct mtx *lock; struct cdev *mixer_dev; struct cdev *dsp_dev; - uint32_t pvchanrate, pvchanformat; - uint32_t rvchanrate, rvchanformat; + uint32_t pvchanrate, pvchanformat, pvchanmode; + uint32_t rvchanrate, rvchanformat, rvchanmode; int32_t eqpreamp; struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; struct cv cv; struct unrhdr *p_unr; struct unrhdr *vp_unr; struct unrhdr *r_unr; struct unrhdr *vr_unr; }; void sound_oss_sysinfo(oss_sysinfo *); int sound_oss_card_info(oss_card_info *); #define PCM_MODE_MIXER 0x01 #define PCM_MODE_PLAY 0x02 #define PCM_MODE_REC 0x04 #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) #define PCM_LOCK(d) mtx_lock((d)->lock) #define PCM_UNLOCK(d) mtx_unlock((d)->lock) #define PCM_TRYLOCK(d) mtx_trylock((d)->lock) #define PCM_LOCKASSERT(d) mtx_assert((d)->lock, MA_OWNED) #define PCM_UNLOCKASSERT(d) mtx_assert((d)->lock, MA_NOTOWNED) /* * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these * with PCM_LOCK/UNLOCK() sequence, or I'll come to gnaw upon you! */ #ifdef SND_DIAGNOSTIC #define PCM_WAIT(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM WAIT] Mutex not owned!", \ __func__, __LINE__); \ while ((x)->flags & SD_F_BUSY) { \ if (snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [PCM WAIT] calling cv_wait().\n", \ __func__, __LINE__); \ cv_wait(&(x)->cv, (x)->lock); \ } \ } while (0) #define PCM_ACQUIRE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) \ panic("%s(%d): [PCM ACQUIRE] " \ "Trying to acquire BUSY cv!", __func__, __LINE__); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ if (!PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_BUSY) { \ (x)->flags &= ~SD_F_BUSY; \ cv_broadcast(&(x)->cv); \ } else \ panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ __func__, __LINE__); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) do { \ if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ panic("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", __func__, __LINE__, x); \ } while (0) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT ENTER] Giant owned!\n", \ __func__, __LINE__); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ if (PCM_LOCKOWNED(x)) \ panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ __func__, __LINE__); \ if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__); \ if ((x)->flags & SD_F_MPSAFE) { \ if (_pcm_giant == 1) \ panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__); \ if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ device_printf((x)->dev, \ "%s(%d): [GIANT EXIT] Giant owned!\n", \ __func__, __LINE__); \ } \ if (_pcm_giant != 0) { \ if (mtx_owned(&Giant) == 0) \ panic("%s(%d): [GIANT EXIT] Giant not owned!", \ __func__, __LINE__); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #else /* !SND_DIAGNOSTIC */ #define PCM_WAIT(x) do { \ PCM_LOCKASSERT(x); \ while ((x)->flags & SD_F_BUSY) \ cv_wait(&(x)->cv, (x)->lock); \ } while (0) #define PCM_ACQUIRE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT(!((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ __func__, __LINE__)); \ (x)->flags |= SD_F_BUSY; \ } while (0) #define PCM_RELEASE(x) do { \ PCM_LOCKASSERT(x); \ KASSERT((x)->flags & SD_F_BUSY, \ ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ __func__, __LINE__)); \ (x)->flags &= ~SD_F_BUSY; \ cv_broadcast(&(x)->cv); \ } while (0) /* Quick version, for shorter path. */ #define PCM_ACQUIRE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_WAIT(x); \ PCM_ACQUIRE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_RELEASE_QUICK(x) do { \ PCM_UNLOCKASSERT(x); \ PCM_LOCK(x); \ PCM_RELEASE(x); \ PCM_UNLOCK(x); \ } while (0) #define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ ((x)->flags & SD_F_BUSY), \ ("%s(%d): [PCM BUSYASSERT] " \ "Failed, snddev_info=%p", \ __func__, __LINE__, x)) #define PCM_GIANT_ENTER(x) do { \ int _pcm_giant = 0; \ PCM_UNLOCKASSERT(x); \ if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ do { \ mtx_lock(&Giant); \ _pcm_giant = 1; \ } while (0) #define PCM_GIANT_EXIT(x) do { \ PCM_UNLOCKASSERT(x); \ KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ __func__, __LINE__)); \ KASSERT(!((x)->flags & SD_F_MPSAFE) || \ (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ __func__, __LINE__)); \ if (_pcm_giant != 0) { \ mtx_assert(&Giant, MA_OWNED); \ _pcm_giant = 0; \ mtx_unlock(&Giant); \ } \ } while (0) #endif /* SND_DIAGNOSTIC */ #define PCM_GIANT_LEAVE(x) \ PCM_GIANT_EXIT(x); \ } while (0) #endif /* _KERNEL */ /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) #define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) #define AFMT_G711 (AFMT_MU_LAW | AFMT_A_LAW) #define AFMT_8BIT (AFMT_G711 | AFMT_U8 | AFMT_S8) #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) #define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ AFMT_S16_BE | AFMT_U16_BE) #define AFMT_CONVERTIBLE (AFMT_8BIT | AFMT_16BIT | AFMT_24BIT | \ AFMT_32BIT) /* Supported vchan mixing formats */ #define AFMT_VCHAN (AFMT_CONVERTIBLE & ~AFMT_G711) #define AFMT_PASSTHROUGH AFMT_AC3 #define AFMT_PASSTHROUGH_RATE 48000 #define AFMT_PASSTHROUGH_CHANNEL 2 #define AFMT_PASSTHROUGH_EXTCHANNEL 0 /* * We're simply using unused, contiguous bits from various AFMT_ definitions. * ~(0xb00ff7ff) */ #define AFMT_ENCODING_MASK 0xf00fffff #define AFMT_CHANNEL_MASK 0x07f00000 #define AFMT_CHANNEL_SHIFT 20 #define AFMT_CHANNEL_MAX 0x7f #define AFMT_EXTCHANNEL_MASK 0x08000000 #define AFMT_EXTCHANNEL_SHIFT 27 #define AFMT_EXTCHANNEL_MAX 1 #define AFMT_ENCODING(v) ((v) & AFMT_ENCODING_MASK) #define AFMT_EXTCHANNEL(v) (((v) & AFMT_EXTCHANNEL_MASK) >> \ AFMT_EXTCHANNEL_SHIFT) #define AFMT_CHANNEL(v) (((v) & AFMT_CHANNEL_MASK) >> \ AFMT_CHANNEL_SHIFT) #define AFMT_BIT(v) (((v) & AFMT_32BIT) ? 32 : \ (((v) & AFMT_24BIT) ? 24 : \ ((((v) & AFMT_16BIT) || \ ((v) & AFMT_PASSTHROUGH)) ? 16 : 8))) #define AFMT_BPS(v) (AFMT_BIT(v) >> 3) #define AFMT_ALIGN(v) (AFMT_BPS(v) * AFMT_CHANNEL(v)) #define SND_FORMAT(f, c, e) (AFMT_ENCODING(f) | \ (((c) << AFMT_CHANNEL_SHIFT) & \ AFMT_CHANNEL_MASK) | \ (((e) << AFMT_EXTCHANNEL_SHIFT) & \ AFMT_EXTCHANNEL_MASK)) #define AFMT_U8_NE AFMT_U8 #define AFMT_S8_NE AFMT_S8 #define AFMT_SIGNED_NE (AFMT_S8_NE | AFMT_S16_NE | AFMT_S24_NE | AFMT_S32_NE) #define AFMT_NE (AFMT_SIGNED_NE | AFMT_U8_NE | AFMT_U16_NE | \ AFMT_U24_NE | AFMT_U32_NE) #endif /* _OS_H_ */ diff --git a/sys/dev/sound/pcm/vchan.c b/sys/dev/sound/pcm/vchan.c index 297120199fe7..379d647cbcf8 100644 --- a/sys/dev/sound/pcm/vchan.c +++ b/sys/dev/sound/pcm/vchan.c @@ -1,983 +1,984 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006-2009 Ariff Abdullah * Copyright (c) 2001 Cameron Grant * All rights reserved. * Copyright (c) 2024 The FreeBSD Foundation * * Portions of this software were developed by Christos Margiolis * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Almost entirely rewritten to add multi-format/channels mixing support. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include /* * [ac3 , dts , linear , 0, linear, 0] */ #define FMTLIST_MAX 6 #define FMTLIST_OFFSET 4 #define DIGFMTS_MAX 2 #ifdef SND_DEBUG static int snd_passthrough_verbose = 0; SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN, &snd_passthrough_verbose, 0, "passthrough verbosity"); #endif struct vchan_info { struct pcm_channel *channel; struct pcmchan_caps caps; uint32_t fmtlist[FMTLIST_MAX]; int trigger; }; bool snd_vchans_enable = true; static void * vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct vchan_info *info; struct pcm_channel *p; uint32_t i, j, *fmtlist; KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, ("vchan_init: bad direction")); KASSERT(c != NULL && c->parentchannel != NULL, ("vchan_init: bad channels")); info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO); info->channel = c; info->trigger = PCMTRIG_STOP; p = c->parentchannel; CHN_LOCK(p); fmtlist = chn_getcaps(p)->fmtlist; for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) { if (fmtlist[i] & AFMT_PASSTHROUGH) info->fmtlist[j++] = fmtlist[i]; } if (p->format & AFMT_VCHAN) info->fmtlist[j] = p->format; else info->fmtlist[j] = VCHAN_DEFAULT_FORMAT; info->caps.fmtlist = info->fmtlist + ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET); CHN_UNLOCK(p); c->flags |= CHN_F_VIRTUAL; return (info); } static int vchan_free(kobj_t obj, void *data) { free(data, M_DEVBUF); return (0); } static int vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchan_info *info; info = data; CHN_LOCKASSERT(info->channel); if (!snd_fmtvalid(format, info->caps.fmtlist)) return (-1); return (0); } static uint32_t vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchan_info *info; info = data; CHN_LOCKASSERT(info->channel); return (info->caps.maxspeed); } static int vchan_trigger(kobj_t obj, void *data, int go) { struct vchan_info *info; struct pcm_channel *c, *p; int ret, otrigger; info = data; c = info->channel; p = c->parentchannel; CHN_LOCKASSERT(c); if (!PCMTRIG_COMMON(go) || go == info->trigger) return (0); CHN_UNLOCK(c); CHN_LOCK(p); otrigger = info->trigger; info->trigger = go; switch (go) { case PCMTRIG_START: if (otrigger != PCMTRIG_START) CHN_INSERT_HEAD(p, c, children.busy); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: if (otrigger == PCMTRIG_START) CHN_REMOVE(p, c, children.busy); break; default: break; } ret = chn_notify(p, CHN_N_TRIGGER); CHN_LOCK(c); if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c)) ret = vchan_sync(c); CHN_UNLOCK(c); CHN_UNLOCK(p); CHN_LOCK(c); return (ret); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchan_info *info; struct pcm_channel *c; uint32_t pformat, pspeed, pflags, i; info = data; c = info->channel; pformat = c->parentchannel->format; pspeed = c->parentchannel->speed; pflags = c->parentchannel->flags; CHN_LOCKASSERT(c); if (pflags & CHN_F_VCHAN_DYNAMIC) { info->caps.fmtlist = info->fmtlist; if (pformat & AFMT_VCHAN) { for (i = 0; info->caps.fmtlist[i] != 0; i++) { if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH) continue; break; } info->caps.fmtlist[i] = pformat; } if (c->format & AFMT_PASSTHROUGH) info->caps.minspeed = c->speed; else info->caps.minspeed = pspeed; info->caps.maxspeed = info->caps.minspeed; } else { info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET; if (pformat & AFMT_VCHAN) info->caps.fmtlist[0] = pformat; else { device_printf(c->dev, "%s(): invalid vchan format 0x%08x", __func__, pformat); info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT; } info->caps.minspeed = pspeed; info->caps.maxspeed = info->caps.minspeed; } return (&info->caps); } static struct pcmchan_matrix * vchan_getmatrix(kobj_t obj, void *data, uint32_t format) { return (feeder_matrix_format_map(format)); } static kobj_method_t vchan_methods[] = { KOBJMETHOD(channel_init, vchan_init), KOBJMETHOD(channel_free, vchan_free), KOBJMETHOD(channel_setformat, vchan_setformat), KOBJMETHOD(channel_setspeed, vchan_setspeed), KOBJMETHOD(channel_trigger, vchan_trigger), KOBJMETHOD(channel_getcaps, vchan_getcaps), KOBJMETHOD(channel_getmatrix, vchan_getmatrix), KOBJMETHOD_END }; CHANNEL_DECLARE(vchan); static void vchan_getparentchannel(struct snddev_info *d, struct pcm_channel **wrch, struct pcm_channel **rdch) { struct pcm_channel **ch, *wch, *rch, *c; KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__)); PCM_BUSYASSERT(d); PCM_UNLOCKASSERT(d); wch = NULL; rch = NULL; CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch; if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (*ch != NULL && *ch != c->parentchannel) { CHN_UNLOCK(c); *ch = NULL; break; } } else { /* No way!! */ if (*ch != NULL) { CHN_UNLOCK(c); *ch = NULL; break; } *ch = c; } CHN_UNLOCK(c); } if (wrch != NULL) *wrch = wch; if (rdch != NULL) *rdch = rch; } static int sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; 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)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { case VCHAN_PLAY: /* 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: if (d->reccount < 1) { PCM_UNLOCK(d); return (ENODEV); } flag = SD_F_RVCHANS; break; default: PCM_UNLOCK(d); return (EINVAL); } enabled = (d->flags & flag) != 0; PCM_ACQUIRE(d); PCM_UNLOCK(d); 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 (0); } static int sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c; uint32_t dflags; - int direction, ret; + int *vchanmode, direction, ret; char dtype[16]; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); 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; + vchanmode = &d->pvchanmode; break; case VCHAN_REC: if ((d->flags & SD_F_RVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_REC; + vchanmode = &d->rvchanmode; break; default: PCM_UNLOCK(d); return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (direction == PCMDIR_PLAY) vchan_getparentchannel(d, &c, NULL); else vchan_getparentchannel(d, NULL, &c); if (c == NULL) { PCM_RELEASE_QUICK(d); return (EINVAL); } KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); - CHN_LOCK(c); - if (c->flags & CHN_F_VCHAN_PASSTHROUGH) + if (*vchanmode & CHN_F_VCHAN_PASSTHROUGH) strlcpy(dtype, "passthrough", sizeof(dtype)); - else if (c->flags & CHN_F_VCHAN_ADAPTIVE) + else if (*vchanmode & CHN_F_VCHAN_ADAPTIVE) strlcpy(dtype, "adaptive", sizeof(dtype)); else strlcpy(dtype, "fixed", sizeof(dtype)); - CHN_UNLOCK(c); ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req); if (ret == 0 && req->newptr != NULL) { if (strcasecmp(dtype, "passthrough") == 0 || strcmp(dtype, "1") == 0) dflags = CHN_F_VCHAN_PASSTHROUGH; else if (strcasecmp(dtype, "adaptive") == 0 || strcmp(dtype, "2") == 0) dflags = CHN_F_VCHAN_ADAPTIVE; else if (strcasecmp(dtype, "fixed") == 0 || strcmp(dtype, "0") == 0) dflags = 0; else { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_LOCK(c); if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) || (c->flags & CHN_F_PASSTHROUGH)) { CHN_UNLOCK(c); PCM_RELEASE_QUICK(d); return (0); } c->flags &= ~CHN_F_VCHAN_DYNAMIC; c->flags |= dflags; CHN_UNLOCK(c); + *vchanmode = dflags; } PCM_RELEASE_QUICK(d); return (ret); } /* * On the fly vchan rate/format settings */ #define VCHAN_ACCESSIBLE(c) (!((c)->flags & (CHN_F_PASSTHROUGH | \ CHN_F_EXCLUSIVE)) && \ (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \ CHN_STOPPED(c))) static int sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch; struct pcmchan_caps *caps; 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)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); 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; vchanrate = &d->pvchanrate; break; case VCHAN_REC: if ((d->flags & SD_F_RVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_REC; vchanrate = &d->rvchanrate; break; default: PCM_UNLOCK(d); return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (direction == PCMDIR_PLAY) vchan_getparentchannel(d, &c, NULL); else vchan_getparentchannel(d, NULL, &c); if (c == NULL) { PCM_RELEASE_QUICK(d); return (EINVAL); } KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); newspd = *vchanrate; ret = sysctl_handle_int(oidp, &newspd, 0, req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } if (newspd < feeder_rate_min || newspd > feeder_rate_max) { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_LOCK(c); if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) { if (CHN_STARTED(c)) { chn_abort(c); restart = 1; } else restart = 0; if (feeder_rate_round) { caps = chn_getcaps(c); RANGE(newspd, caps->minspeed, caps->maxspeed); newspd = CHANNEL_SETSPEED(c->methods, c->devinfo, newspd); } ret = chn_reset(c, c->format, newspd); if (ret == 0) { if (restart != 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } c->flags |= CHN_F_DIRTY; ret = chn_start(c, 1); } } } *vchanrate = c->speed; CHN_UNLOCK(c); PCM_RELEASE_QUICK(d); return (ret); } static int sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; struct pcm_channel *c, *ch; uint32_t newfmt; int *vchanformat, direction, ret, restart; char fmtstr[AFMTSTR_LEN]; d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) return (EINVAL); PCM_LOCK(d); PCM_WAIT(d); 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; vchanformat = &d->pvchanformat; break; case VCHAN_REC: if ((d->flags & SD_F_RVCHANS) == 0) { PCM_UNLOCK(d); return (ENODEV); } direction = PCMDIR_REC; vchanformat = &d->rvchanformat; break; default: PCM_UNLOCK(d); return (EINVAL); } PCM_ACQUIRE(d); PCM_UNLOCK(d); if (direction == PCMDIR_PLAY) vchan_getparentchannel(d, &c, NULL); else vchan_getparentchannel(d, NULL, &c); if (c == NULL) { PCM_RELEASE_QUICK(d); return (EINVAL); } KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d", __func__, direction, c->direction)); bzero(fmtstr, sizeof(fmtstr)); if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat) strlcpy(fmtstr, "", sizeof(fmtstr)); ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); if (ret != 0 || req->newptr == NULL) { PCM_RELEASE_QUICK(d); return (ret); } newfmt = snd_str2afmt(fmtstr); if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) { PCM_RELEASE_QUICK(d); return (EINVAL); } CHN_LOCK(c); if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) { if (CHN_STARTED(c)) { chn_abort(c); restart = 1; } else restart = 0; ret = chn_reset(c, newfmt, c->speed); if (ret == 0) { if (restart != 0) { CHN_FOREACH(ch, c, children.busy) { CHN_LOCK(ch); if (VCHAN_SYNC_REQUIRED(ch)) vchan_sync(ch); CHN_UNLOCK(ch); } c->flags |= CHN_F_DIRTY; ret = chn_start(c, 1); } } } *vchanformat = c->format; CHN_UNLOCK(c); PCM_RELEASE_QUICK(d); return (ret); } /* virtual channel interface */ #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanformat" : "rec.vchanformat" #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ "play.vchanrate" : "rec.vchanrate" int vchan_create(struct pcm_channel *parent, struct pcm_channel **child) { struct snddev_info *d; struct pcm_channel *ch; struct pcmchan_caps *parent_caps; uint32_t vchanfmt, vchanspd; int ret, direction, r; bool save; ret = 0; save = false; d = parent->parentsnddev; PCM_BUSYASSERT(d); CHN_LOCKASSERT(parent); if (!(parent->direction == PCMDIR_PLAY || parent->direction == PCMDIR_REC)) return (EINVAL); CHN_UNLOCK(parent); PCM_LOCK(d); if (parent->direction == PCMDIR_PLAY) { direction = PCMDIR_PLAY_VIRTUAL; vchanfmt = d->pvchanformat; vchanspd = d->pvchanrate; } else { direction = PCMDIR_REC_VIRTUAL; vchanfmt = d->rvchanformat; vchanspd = d->rvchanrate; } /* create a new playback channel */ ch = chn_init(d, parent, &vchan_class, direction, parent); if (ch == NULL) { PCM_UNLOCK(d); CHN_LOCK(parent); return (ENODEV); } PCM_UNLOCK(d); CHN_LOCK(parent); /* * Add us to our parent channel's children in reverse order * so future destruction will pick the last (biggest number) * channel. */ CHN_INSERT_SORT_DESCEND(parent, ch, children); *child = ch; if (parent->flags & CHN_F_HAS_VCHAN) return (0); parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY; parent_caps = chn_getcaps(parent); if (parent_caps == NULL) { ret = EINVAL; goto fail; } if (vchanfmt == 0) { const char *vfmt; CHN_UNLOCK(parent); r = resource_string_value(device_get_name(parent->dev), device_get_unit(parent->dev), VCHAN_FMT_HINT(direction), &vfmt); CHN_LOCK(parent); if (r != 0) vfmt = NULL; if (vfmt != NULL) { vchanfmt = snd_str2afmt(vfmt); if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN)) vchanfmt = 0; } if (vchanfmt == 0) vchanfmt = VCHAN_DEFAULT_FORMAT; save = true; } if (vchanspd == 0) { /* * This is very sad. Few soundcards advertised as being * able to do (insanely) higher/lower speed, but in * reality, they simply can't. At least, we give user chance * to set sane value via kernel hints or sysctl. */ CHN_UNLOCK(parent); r = resource_int_value(device_get_name(parent->dev), device_get_unit(parent->dev), VCHAN_SPD_HINT(direction), &vchanspd); CHN_LOCK(parent); if (r != 0) { /* No saved value, no hint, NOTHING. */ vchanspd = VCHAN_DEFAULT_RATE; RANGE(vchanspd, parent_caps->minspeed, parent_caps->maxspeed); } save = true; } /* * Limit the speed between feeder_rate_min <-> feeder_rate_max. */ RANGE(vchanspd, feeder_rate_min, feeder_rate_max); if (feeder_rate_round) { RANGE(vchanspd, parent_caps->minspeed, parent_caps->maxspeed); vchanspd = CHANNEL_SETSPEED(parent->methods, parent->devinfo, vchanspd); } if ((ret = chn_reset(parent, vchanfmt, vchanspd)) != 0) goto fail; if (save) { /* * Save new value. */ if (direction == PCMDIR_PLAY_VIRTUAL) { d->pvchanformat = parent->format; d->pvchanrate = parent->speed; } else { d->rvchanformat = parent->format; d->rvchanrate = parent->speed; } } /* * If the parent channel supports digital format, * enable passthrough mode. */ if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) { parent->flags &= ~CHN_F_VCHAN_DYNAMIC; parent->flags |= CHN_F_VCHAN_PASSTHROUGH; } return (ret); fail: CHN_LOCK(ch); vchan_destroy(ch); *child = NULL; return (ret); } int vchan_destroy(struct pcm_channel *c) { struct pcm_channel *parent; KASSERT(c != NULL && c->parentchannel != NULL && c->parentsnddev != NULL, ("%s(): invalid channel=%p", __func__, c)); CHN_LOCKASSERT(c); parent = c->parentchannel; PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(parent); CHN_UNLOCK(c); /* remove us from our parent's children list */ CHN_REMOVE(parent, c, children); if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); chn_reset(parent, parent->format, parent->speed); } CHN_UNLOCK(parent); /* destroy ourselves */ chn_kill(c); CHN_LOCK(parent); return (0); } int #ifdef SND_DEBUG vchan_passthrough(struct pcm_channel *c, const char *caller) #else vchan_sync(struct pcm_channel *c) #endif { int ret; KASSERT(c != NULL && c->parentchannel != NULL && (c->flags & CHN_F_VIRTUAL), ("%s(): invalid passthrough", __func__)); CHN_LOCKASSERT(c); CHN_LOCKASSERT(c->parentchannel); sndbuf_setspd(c->bufhard, c->parentchannel->speed); c->flags |= CHN_F_PASSTHROUGH; ret = feeder_chain(c); c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH); if (ret != 0) c->flags |= CHN_F_DIRTY; #ifdef SND_DEBUG if (snd_passthrough_verbose) { device_printf(c->dev, "%s(%s/%s) %s() -> re-sync err=%d\n", __func__, c->name, c->comm, caller, ret); } #endif return (ret); } static int sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int i, v, error; v = snd_vchans_enable; error = sysctl_handle_int(oidp, &v, 0, req); 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 (0); } SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int), sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch"); void vchan_initsys(device_t dev) { struct snddev_info *d; int unit; unit = device_get_unit(dev); d = device_get_softc(dev); /* Play */ SYSCTL_ADD_PROC(&d->play_sysctl_ctx, 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", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanmode, "A", "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); /* Rec */ SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchans, "I", "virtual channels enabled"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanmode, "A", "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format"); }