2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2006-2009 Ariff Abdullah <ariff@FreeBSD.org>
5 * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 /* Almost entirely rewritten to add multi-format/channels mixing support. */
32 #ifdef HAVE_KERNEL_OPTION_HEADERS
36 #include <dev/sound/pcm/sound.h>
37 #include <dev/sound/pcm/vchan.h>
39 SND_DECLARE_FILE("$FreeBSD$");
42 * [ac3 , dts , linear , 0, linear, 0]
45 #define FMTLIST_OFFSET 4
49 static int snd_passthrough_verbose = 0;
50 SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN,
51 &snd_passthrough_verbose, 0, "passthrough verbosity");
56 struct pcm_channel *channel;
57 struct pcmchan_caps caps;
58 uint32_t fmtlist[FMTLIST_MAX];
63 vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
64 struct pcm_channel *c, int dir)
66 struct vchan_info *info;
67 struct pcm_channel *p;
68 uint32_t i, j, *fmtlist;
70 KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
71 ("vchan_init: bad direction"));
72 KASSERT(c != NULL && c->parentchannel != NULL,
73 ("vchan_init: bad channels"));
75 info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
77 info->trigger = PCMTRIG_STOP;
82 fmtlist = chn_getcaps(p)->fmtlist;
83 for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
84 if (fmtlist[i] & AFMT_PASSTHROUGH)
85 info->fmtlist[j++] = fmtlist[i];
87 if (p->format & AFMT_VCHAN)
88 info->fmtlist[j] = p->format;
90 info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
91 info->caps.fmtlist = info->fmtlist +
92 ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
96 c->flags |= CHN_F_VIRTUAL;
102 vchan_free(kobj_t obj, void *data)
105 free(data, M_DEVBUF);
111 vchan_setformat(kobj_t obj, void *data, uint32_t format)
113 struct vchan_info *info;
117 CHN_LOCKASSERT(info->channel);
119 if (!snd_fmtvalid(format, info->caps.fmtlist))
126 vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
128 struct vchan_info *info;
132 CHN_LOCKASSERT(info->channel);
134 return (info->caps.maxspeed);
138 vchan_trigger(kobj_t obj, void *data, int go)
140 struct vchan_info *info;
141 struct pcm_channel *c, *p;
146 if (!PCMTRIG_COMMON(go) || go == info->trigger)
150 p = c->parentchannel;
151 otrigger = info->trigger;
161 if (otrigger != PCMTRIG_START)
162 CHN_INSERT_HEAD(p, c, children.busy);
166 if (otrigger == PCMTRIG_START)
167 CHN_REMOVE(p, c, children.busy);
173 ret = chn_notify(p, CHN_N_TRIGGER);
177 if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
187 static struct pcmchan_caps *
188 vchan_getcaps(kobj_t obj, void *data)
190 struct vchan_info *info;
191 struct pcm_channel *c;
192 uint32_t pformat, pspeed, pflags, i;
196 pformat = c->parentchannel->format;
197 pspeed = c->parentchannel->speed;
198 pflags = c->parentchannel->flags;
202 if (pflags & CHN_F_VCHAN_DYNAMIC) {
203 info->caps.fmtlist = info->fmtlist;
204 if (pformat & AFMT_VCHAN) {
205 for (i = 0; info->caps.fmtlist[i] != 0; i++) {
206 if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
210 info->caps.fmtlist[i] = pformat;
212 if (c->format & AFMT_PASSTHROUGH)
213 info->caps.minspeed = c->speed;
215 info->caps.minspeed = pspeed;
216 info->caps.maxspeed = info->caps.minspeed;
218 info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
219 if (pformat & AFMT_VCHAN)
220 info->caps.fmtlist[0] = pformat;
222 device_printf(c->dev,
223 "%s(): invalid vchan format 0x%08x",
225 info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
227 info->caps.minspeed = pspeed;
228 info->caps.maxspeed = info->caps.minspeed;
231 return (&info->caps);
234 static struct pcmchan_matrix *
235 vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
238 return (feeder_matrix_format_map(format));
241 static kobj_method_t vchan_methods[] = {
242 KOBJMETHOD(channel_init, vchan_init),
243 KOBJMETHOD(channel_free, vchan_free),
244 KOBJMETHOD(channel_setformat, vchan_setformat),
245 KOBJMETHOD(channel_setspeed, vchan_setspeed),
246 KOBJMETHOD(channel_trigger, vchan_trigger),
247 KOBJMETHOD(channel_getcaps, vchan_getcaps),
248 KOBJMETHOD(channel_getmatrix, vchan_getmatrix),
251 CHANNEL_DECLARE(vchan);
254 pcm_getparentchannel(struct snddev_info *d,
255 struct pcm_channel **wrch, struct pcm_channel **rdch)
257 struct pcm_channel **ch, *wch, *rch, *c;
259 KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__));
267 CHN_FOREACH(c, d, channels.pcm) {
269 ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch;
270 if (c->flags & CHN_F_VIRTUAL) {
272 if (*ch != NULL && *ch != c->parentchannel) {
277 } else if (c->flags & CHN_F_HAS_VCHAN) {
296 sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
298 struct snddev_info *d;
299 int direction, vchancount;
302 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
303 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
309 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
311 direction = PCMDIR_PLAY;
312 vchancount = d->pvchancount;
316 direction = PCMDIR_REC;
317 vchancount = d->rvchancount;
335 err = sysctl_handle_int(oidp, &cnt, 0, req);
337 if (err == 0 && req->newptr != NULL && vchancount != cnt) {
340 if (cnt > SND_MAXVCHANS)
342 err = pcm_setvchans(d, direction, cnt, -1);
345 PCM_RELEASE_QUICK(d);
351 sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
353 struct snddev_info *d;
354 struct pcm_channel *c;
359 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
360 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
366 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
368 direction = PCMDIR_PLAY;
371 direction = PCMDIR_REC;
382 if (direction == PCMDIR_PLAY)
383 pcm_getparentchannel(d, &c, NULL);
385 pcm_getparentchannel(d, NULL, &c);
388 PCM_RELEASE_QUICK(d);
392 KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
393 __func__, direction, c->direction));
396 if (c->flags & CHN_F_VCHAN_PASSTHROUGH)
397 strlcpy(dtype, "passthrough", sizeof(dtype));
398 else if (c->flags & CHN_F_VCHAN_ADAPTIVE)
399 strlcpy(dtype, "adaptive", sizeof(dtype));
401 strlcpy(dtype, "fixed", sizeof(dtype));
404 ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
405 if (ret == 0 && req->newptr != NULL) {
406 if (strcasecmp(dtype, "passthrough") == 0 ||
407 strcmp(dtype, "1") == 0)
408 dflags = CHN_F_VCHAN_PASSTHROUGH;
409 else if (strcasecmp(dtype, "adaptive") == 0 ||
410 strcmp(dtype, "2") == 0)
411 dflags = CHN_F_VCHAN_ADAPTIVE;
412 else if (strcasecmp(dtype, "fixed") == 0 ||
413 strcmp(dtype, "0") == 0)
416 PCM_RELEASE_QUICK(d);
420 if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
421 (c->flags & CHN_F_PASSTHROUGH)) {
423 PCM_RELEASE_QUICK(d);
426 c->flags &= ~CHN_F_VCHAN_DYNAMIC;
431 PCM_RELEASE_QUICK(d);
437 * On the fly vchan rate/format settings
440 #define VCHAN_ACCESSIBLE(c) (!((c)->flags & (CHN_F_PASSTHROUGH | \
441 CHN_F_EXCLUSIVE)) && \
442 (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \
445 sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
447 struct snddev_info *d;
448 struct pcm_channel *c, *ch;
449 struct pcmchan_caps *caps;
450 int *vchanrate, vchancount, direction, ret, newspd, restart;
452 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
453 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
459 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
461 direction = PCMDIR_PLAY;
462 vchancount = d->pvchancount;
463 vchanrate = &d->pvchanrate;
466 direction = PCMDIR_REC;
467 vchancount = d->rvchancount;
468 vchanrate = &d->rvchanrate;
476 if (vchancount < 1) {
484 if (direction == PCMDIR_PLAY)
485 pcm_getparentchannel(d, &c, NULL);
487 pcm_getparentchannel(d, NULL, &c);
490 PCM_RELEASE_QUICK(d);
494 KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
495 __func__, direction, c->direction));
501 ret = sysctl_handle_int(oidp, &newspd, 0, req);
502 if (ret != 0 || req->newptr == NULL) {
503 PCM_RELEASE_QUICK(d);
507 if (newspd < 1 || newspd < feeder_rate_min ||
508 newspd > feeder_rate_max) {
509 PCM_RELEASE_QUICK(d);
515 if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
516 if (CHN_STARTED(c)) {
522 if (feeder_rate_round) {
523 caps = chn_getcaps(c);
524 RANGE(newspd, caps->minspeed, caps->maxspeed);
525 newspd = CHANNEL_SETSPEED(c->methods,
529 ret = chn_reset(c, c->format, newspd);
531 *vchanrate = c->speed;
533 CHN_FOREACH(ch, c, children.busy) {
535 if (VCHAN_SYNC_REQUIRED(ch))
539 c->flags |= CHN_F_DIRTY;
540 ret = chn_start(c, 1);
547 PCM_RELEASE_QUICK(d);
553 sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
555 struct snddev_info *d;
556 struct pcm_channel *c, *ch;
558 int *vchanformat, vchancount, direction, ret, restart;
559 char fmtstr[AFMTSTR_LEN];
561 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
562 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
568 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
570 direction = PCMDIR_PLAY;
571 vchancount = d->pvchancount;
572 vchanformat = &d->pvchanformat;
575 direction = PCMDIR_REC;
576 vchancount = d->rvchancount;
577 vchanformat = &d->rvchanformat;
585 if (vchancount < 1) {
593 if (direction == PCMDIR_PLAY)
594 pcm_getparentchannel(d, &c, NULL);
596 pcm_getparentchannel(d, NULL, &c);
599 PCM_RELEASE_QUICK(d);
603 KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
604 __func__, direction, c->direction));
608 bzero(fmtstr, sizeof(fmtstr));
610 if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format)
611 strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
615 ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
616 if (ret != 0 || req->newptr == NULL) {
617 PCM_RELEASE_QUICK(d);
621 newfmt = snd_str2afmt(fmtstr);
622 if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
623 PCM_RELEASE_QUICK(d);
629 if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
630 if (CHN_STARTED(c)) {
636 ret = chn_reset(c, newfmt, c->speed);
638 *vchanformat = c->format;
640 CHN_FOREACH(ch, c, children.busy) {
642 if (VCHAN_SYNC_REQUIRED(ch))
646 c->flags |= CHN_F_DIRTY;
647 ret = chn_start(c, 1);
654 PCM_RELEASE_QUICK(d);
659 /* virtual channel interface */
661 #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \
662 "play.vchanformat" : "rec.vchanformat"
663 #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \
664 "play.vchanrate" : "rec.vchanrate"
667 vchan_create(struct pcm_channel *parent, int num)
669 struct snddev_info *d;
670 struct pcm_channel *ch;
671 struct pcmchan_caps *parent_caps;
672 uint32_t vchanfmt, vchanspd;
673 int ret, direction, r, save;
675 d = parent->parentsnddev;
678 CHN_LOCKASSERT(parent);
680 if (!(parent->flags & CHN_F_BUSY))
683 if (!(parent->direction == PCMDIR_PLAY ||
684 parent->direction == PCMDIR_REC))
687 d = parent->parentsnddev;
692 if (parent->direction == PCMDIR_PLAY) {
693 direction = PCMDIR_PLAY_VIRTUAL;
694 vchanfmt = d->pvchanformat;
695 vchanspd = d->pvchanrate;
697 direction = PCMDIR_REC_VIRTUAL;
698 vchanfmt = d->rvchanformat;
699 vchanspd = d->rvchanrate;
702 /* create a new playback channel */
703 ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent);
710 /* add us to our grandparent's channel list */
711 ret = pcm_chn_add(d, ch);
721 * Add us to our parent channel's children in reverse order
722 * so future destruction will pick the last (biggest number)
725 CHN_INSERT_SORT_DESCEND(parent, ch, children);
727 if (parent->flags & CHN_F_HAS_VCHAN)
730 parent->flags |= CHN_F_HAS_VCHAN;
732 parent_caps = chn_getcaps(parent);
733 if (parent_caps == NULL)
738 if (ret == 0 && vchanfmt == 0) {
742 r = resource_string_value(device_get_name(parent->dev),
743 device_get_unit(parent->dev), VCHAN_FMT_HINT(direction),
749 vchanfmt = snd_str2afmt(vfmt);
750 if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN))
754 vchanfmt = VCHAN_DEFAULT_FORMAT;
758 if (ret == 0 && vchanspd == 0) {
760 * This is very sad. Few soundcards advertised as being
761 * able to do (insanely) higher/lower speed, but in
762 * reality, they simply can't. At least, we give user chance
763 * to set sane value via kernel hints or sysctl.
766 r = resource_int_value(device_get_name(parent->dev),
767 device_get_unit(parent->dev), VCHAN_SPD_HINT(direction),
772 * No saved value, no hint, NOTHING.
774 * Workaround for sb16 running
775 * poorly at 45k / 49k.
777 switch (parent_caps->maxspeed) {
783 vchanspd = VCHAN_DEFAULT_RATE;
784 if (vchanspd > parent_caps->maxspeed)
785 vchanspd = parent_caps->maxspeed;
788 if (vchanspd < parent_caps->minspeed)
789 vchanspd = parent_caps->minspeed;
796 * Limit the speed between feeder_rate_min <-> feeder_rate_max.
798 if (vchanspd < feeder_rate_min)
799 vchanspd = feeder_rate_min;
800 if (vchanspd > feeder_rate_max)
801 vchanspd = feeder_rate_max;
803 if (feeder_rate_round) {
804 RANGE(vchanspd, parent_caps->minspeed,
805 parent_caps->maxspeed);
806 vchanspd = CHANNEL_SETSPEED(parent->methods,
807 parent->devinfo, vchanspd);
810 ret = chn_reset(parent, vchanfmt, vchanspd);
813 if (ret == 0 && save) {
817 if (direction == PCMDIR_PLAY_VIRTUAL) {
818 d->pvchanformat = parent->format;
819 d->pvchanrate = parent->speed;
821 d->rvchanformat = parent->format;
822 d->rvchanrate = parent->speed;
827 * If the parent channel supports digital format,
828 * enable passthrough mode.
830 if (ret == 0 && snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
831 parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
832 parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
836 CHN_REMOVE(parent, ch, children);
837 parent->flags &= ~CHN_F_HAS_VCHAN;
840 if (pcm_chn_remove(d, ch) == 0) {
852 vchan_destroy(struct pcm_channel *c)
854 struct pcm_channel *parent;
855 struct snddev_info *d;
858 KASSERT(c != NULL && c->parentchannel != NULL &&
859 c->parentsnddev != NULL, ("%s(): invalid channel=%p",
865 parent = c->parentchannel;
868 CHN_LOCKASSERT(parent);
872 if (!(parent->flags & CHN_F_BUSY))
875 if (CHN_EMPTY(parent, children))
878 /* remove us from our parent's children list */
879 CHN_REMOVE(parent, c, children);
881 if (CHN_EMPTY(parent, children)) {
882 parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
883 chn_reset(parent, parent->format, parent->speed);
888 /* remove us from our grandparent's channel list */
890 ret = pcm_chn_remove(d, c);
893 /* destroy ourselves */
895 ret = pcm_chn_destroy(c);
904 vchan_passthrough(struct pcm_channel *c, const char *caller)
906 vchan_sync(struct pcm_channel *c)
911 KASSERT(c != NULL && c->parentchannel != NULL &&
912 (c->flags & CHN_F_VIRTUAL),
913 ("%s(): invalid passthrough", __func__));
915 CHN_LOCKASSERT(c->parentchannel);
917 sndbuf_setspd(c->bufhard, c->parentchannel->speed);
918 c->flags |= CHN_F_PASSTHROUGH;
919 ret = feeder_chain(c);
920 c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
922 c->flags |= CHN_F_DIRTY;
925 if (snd_passthrough_verbose != 0) {
926 char *devname, buf[CHN_NAMELEN];
928 devname = dsp_unit2name(buf, sizeof(buf), c->unit);
929 device_printf(c->dev,
930 "%s(%s/%s) %s() -> re-sync err=%d\n",
931 __func__, (devname != NULL) ? devname : "dspX", c->comm,
940 vchan_initsys(device_t dev)
942 struct snddev_info *d;
945 unit = device_get_unit(dev);
946 d = device_get_softc(dev);
949 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
950 SYSCTL_CHILDREN(d->play_sysctl_tree),
951 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN,
952 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
953 sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
954 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
955 SYSCTL_CHILDREN(d->play_sysctl_tree),
956 OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN,
957 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
958 sysctl_dev_pcm_vchanmode, "A",
959 "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
960 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
961 SYSCTL_CHILDREN(d->play_sysctl_tree),
962 OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN,
963 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
964 sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
965 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
966 SYSCTL_CHILDREN(d->play_sysctl_tree),
967 OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN,
968 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
969 sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
971 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
972 SYSCTL_CHILDREN(d->rec_sysctl_tree),
973 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN,
974 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
975 sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
976 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
977 SYSCTL_CHILDREN(d->rec_sysctl_tree),
978 OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RWTUN,
979 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
980 sysctl_dev_pcm_vchanmode, "A",
981 "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
982 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
983 SYSCTL_CHILDREN(d->rec_sysctl_tree),
984 OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RWTUN,
985 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
986 sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
987 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
988 SYSCTL_CHILDREN(d->rec_sysctl_tree),
989 OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RWTUN,
990 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
991 sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");