2 * Copyright (c) 2006-2009 Ariff Abdullah <ariff@FreeBSD.org>
3 * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 /* Almost entirely rewritten to add multi-format/channels mixing support. */
30 #ifdef HAVE_KERNEL_OPTION_HEADERS
34 #include <dev/sound/pcm/sound.h>
35 #include <dev/sound/pcm/vchan.h>
37 SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/vchan.c 193640 2009-06-07 19:12:08Z ariff $");
40 * [ac3 , dts , linear , 0, linear, 0]
43 #define FMTLIST_OFFSET 4
47 static int snd_passthrough_verbose = 0;
48 SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RW,
49 &snd_passthrough_verbose, 0, "passthrough verbosity");
54 struct pcm_channel *channel;
55 struct pcmchan_caps caps;
56 uint32_t fmtlist[FMTLIST_MAX];
61 vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
62 struct pcm_channel *c, int dir)
64 struct vchan_info *info;
65 struct pcm_channel *p;
66 uint32_t i, j, *fmtlist;
68 KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
69 ("vchan_init: bad direction"));
70 KASSERT(c != NULL && c->parentchannel != NULL,
71 ("vchan_init: bad channels"));
73 info = kmalloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
75 info->trigger = PCMTRIG_STOP;
80 fmtlist = chn_getcaps(p)->fmtlist;
81 for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
82 if (fmtlist[i] & AFMT_PASSTHROUGH)
83 info->fmtlist[j++] = fmtlist[i];
85 if (p->format & AFMT_VCHAN)
86 info->fmtlist[j] = p->format;
88 info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
89 info->caps.fmtlist = info->fmtlist +
90 ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
94 c->flags |= CHN_F_VIRTUAL;
100 vchan_free(kobj_t obj, void *data)
103 kfree(data, M_DEVBUF);
109 vchan_setformat(kobj_t obj, void *data, uint32_t format)
111 struct vchan_info *info;
115 CHN_LOCKASSERT(info->channel);
117 if (!snd_fmtvalid(format, info->caps.fmtlist))
124 vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
126 struct vchan_info *info;
130 CHN_LOCKASSERT(info->channel);
132 return (info->caps.maxspeed);
136 vchan_trigger(kobj_t obj, void *data, int go)
138 struct vchan_info *info;
139 struct pcm_channel *c, *p;
144 if (!PCMTRIG_COMMON(go) || go == info->trigger)
148 p = c->parentchannel;
149 otrigger = info->trigger;
159 if (otrigger != PCMTRIG_START)
160 CHN_INSERT_HEAD(p, c, children.busy);
164 if (otrigger == PCMTRIG_START)
165 CHN_REMOVE(p, c, children.busy);
171 ret = chn_notify(p, CHN_N_TRIGGER);
175 if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
185 static struct pcmchan_caps *
186 vchan_getcaps(kobj_t obj, void *data)
188 struct vchan_info *info;
189 struct pcm_channel *c;
190 uint32_t pformat, pspeed, pflags, i;
194 pformat = c->parentchannel->format;
195 pspeed = c->parentchannel->speed;
196 pflags = c->parentchannel->flags;
200 if (pflags & CHN_F_VCHAN_DYNAMIC) {
201 info->caps.fmtlist = info->fmtlist;
202 if (pformat & AFMT_VCHAN) {
203 for (i = 0; info->caps.fmtlist[i] != 0; i++) {
204 if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
208 info->caps.fmtlist[i] = pformat;
210 if (c->format & AFMT_PASSTHROUGH)
211 info->caps.minspeed = c->speed;
213 info->caps.minspeed = pspeed;
214 info->caps.maxspeed = info->caps.minspeed;
216 info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
217 if (pformat & AFMT_VCHAN)
218 info->caps.fmtlist[0] = pformat;
220 device_printf(c->dev,
221 "%s(): invalid vchan format 0x%08x",
223 info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
225 info->caps.minspeed = pspeed;
226 info->caps.maxspeed = info->caps.minspeed;
229 return (&info->caps);
232 static struct pcmchan_matrix *
233 vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
236 return (feeder_matrix_format_map(format));
239 static kobj_method_t vchan_methods[] = {
240 KOBJMETHOD(channel_init, vchan_init),
241 KOBJMETHOD(channel_free, vchan_free),
242 KOBJMETHOD(channel_setformat, vchan_setformat),
243 KOBJMETHOD(channel_setspeed, vchan_setspeed),
244 KOBJMETHOD(channel_trigger, vchan_trigger),
245 KOBJMETHOD(channel_getcaps, vchan_getcaps),
246 KOBJMETHOD(channel_getmatrix, vchan_getmatrix),
249 CHANNEL_DECLARE(vchan);
252 pcm_getparentchannel(struct snddev_info *d,
253 struct pcm_channel **wrch, struct pcm_channel **rdch)
255 struct pcm_channel **ch, *wch, *rch, *c;
257 KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__));
265 CHN_FOREACH(c, d, channels.pcm) {
267 ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch;
268 if (c->flags & CHN_F_VIRTUAL) {
270 if (*ch != NULL && *ch != c->parentchannel) {
275 } else if (c->flags & CHN_F_HAS_VCHAN) {
294 sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
296 struct snddev_info *d;
297 int direction, vchancount;
300 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
301 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
307 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
309 direction = PCMDIR_PLAY;
310 vchancount = d->pvchancount;
314 direction = PCMDIR_REC;
315 vchancount = d->rvchancount;
333 err = sysctl_handle_int(oidp, &cnt, 0, req);
335 if (err == 0 && req->newptr != NULL && vchancount != cnt) {
338 if (cnt > SND_MAXVCHANS)
340 err = pcm_setvchans(d, direction, cnt, -1);
343 PCM_RELEASE_QUICK(d);
349 sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
351 struct snddev_info *d;
352 struct pcm_channel *c;
357 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
358 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
364 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
366 direction = PCMDIR_PLAY;
369 direction = PCMDIR_REC;
380 if (direction == PCMDIR_PLAY)
381 pcm_getparentchannel(d, &c, NULL);
383 pcm_getparentchannel(d, NULL, &c);
386 PCM_RELEASE_QUICK(d);
390 KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
391 __func__, direction, c->direction));
394 if (c->flags & CHN_F_VCHAN_PASSTHROUGH)
395 strlcpy(dtype, "passthrough", sizeof(dtype));
396 else if (c->flags & CHN_F_VCHAN_ADAPTIVE)
397 strlcpy(dtype, "adaptive", sizeof(dtype));
399 strlcpy(dtype, "fixed", sizeof(dtype));
402 ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
403 if (ret == 0 && req->newptr != NULL) {
404 if (strcasecmp(dtype, "passthrough") == 0 ||
405 strcmp(dtype, "1") == 0)
406 dflags = CHN_F_VCHAN_PASSTHROUGH;
407 else if (strcasecmp(dtype, "adaptive") == 0 ||
408 strcmp(dtype, "2") == 0)
409 dflags = CHN_F_VCHAN_ADAPTIVE;
410 else if (strcasecmp(dtype, "fixed") == 0 ||
411 strcmp(dtype, "0") == 0)
414 PCM_RELEASE_QUICK(d);
418 if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
419 (c->flags & CHN_F_PASSTHROUGH)) {
421 PCM_RELEASE_QUICK(d);
424 c->flags &= ~CHN_F_VCHAN_DYNAMIC;
429 PCM_RELEASE_QUICK(d);
435 * On the fly vchan rate/format settings
438 #define VCHAN_ACCESSIBLE(c) (!((c)->flags & (CHN_F_PASSTHROUGH | \
439 CHN_F_EXCLUSIVE)) && \
440 (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \
443 sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
445 struct snddev_info *d;
446 struct pcm_channel *c, *ch;
447 struct pcmchan_caps *caps;
448 int *vchanrate, vchancount, direction, ret, newspd, restart;
450 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
451 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
457 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
459 direction = PCMDIR_PLAY;
460 vchancount = d->pvchancount;
461 vchanrate = &d->pvchanrate;
464 direction = PCMDIR_REC;
465 vchancount = d->rvchancount;
466 vchanrate = &d->rvchanrate;
474 if (vchancount < 1) {
482 if (direction == PCMDIR_PLAY)
483 pcm_getparentchannel(d, &c, NULL);
485 pcm_getparentchannel(d, NULL, &c);
488 PCM_RELEASE_QUICK(d);
492 KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
493 __func__, direction, c->direction));
499 ret = sysctl_handle_int(oidp, &newspd, 0, req);
500 if (ret != 0 || req->newptr == NULL) {
501 PCM_RELEASE_QUICK(d);
505 if (newspd < 1 || newspd < feeder_rate_min ||
506 newspd > feeder_rate_max) {
507 PCM_RELEASE_QUICK(d);
513 if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
514 if (CHN_STARTED(c)) {
520 if (feeder_rate_round) {
521 caps = chn_getcaps(c);
522 RANGE(newspd, caps->minspeed, caps->maxspeed);
523 newspd = CHANNEL_SETSPEED(c->methods,
527 ret = chn_reset(c, c->format, newspd);
529 *vchanrate = c->speed;
531 CHN_FOREACH(ch, c, children.busy) {
533 if (VCHAN_SYNC_REQUIRED(ch))
537 c->flags |= CHN_F_DIRTY;
538 ret = chn_start(c, 1);
545 PCM_RELEASE_QUICK(d);
551 sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
553 struct snddev_info *d;
554 struct pcm_channel *c, *ch;
556 int *vchanformat, vchancount, direction, ret, restart;
557 char fmtstr[AFMTSTR_LEN];
559 d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
560 if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
566 switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
568 direction = PCMDIR_PLAY;
569 vchancount = d->pvchancount;
570 vchanformat = &d->pvchanformat;
573 direction = PCMDIR_REC;
574 vchancount = d->rvchancount;
575 vchanformat = &d->rvchanformat;
583 if (vchancount < 1) {
591 if (direction == PCMDIR_PLAY)
592 pcm_getparentchannel(d, &c, NULL);
594 pcm_getparentchannel(d, NULL, &c);
597 PCM_RELEASE_QUICK(d);
601 KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
602 __func__, direction, c->direction));
606 bzero(fmtstr, sizeof(fmtstr));
608 if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format)
609 strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
613 ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
614 if (ret != 0 || req->newptr == NULL) {
615 PCM_RELEASE_QUICK(d);
619 newfmt = snd_str2afmt(fmtstr);
620 if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
621 PCM_RELEASE_QUICK(d);
627 if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
628 if (CHN_STARTED(c)) {
634 ret = chn_reset(c, newfmt, c->speed);
636 *vchanformat = c->format;
638 CHN_FOREACH(ch, c, children.busy) {
640 if (VCHAN_SYNC_REQUIRED(ch))
644 c->flags |= CHN_F_DIRTY;
645 ret = chn_start(c, 1);
652 PCM_RELEASE_QUICK(d);
657 /* virtual channel interface */
659 #define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \
660 "play.vchanformat" : "rec.vchanformat"
661 #define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \
662 "play.vchanrate" : "rec.vchanrate"
665 vchan_create(struct pcm_channel *parent, int num)
667 struct snddev_info *d;
668 struct pcm_channel *ch;
669 struct pcmchan_caps *parent_caps;
670 uint32_t vchanfmt, vchanspd;
671 int ret, direction, r, save;
673 d = parent->parentsnddev;
676 CHN_LOCKASSERT(parent);
678 if (!(parent->flags & CHN_F_BUSY))
681 if (!(parent->direction == PCMDIR_PLAY ||
682 parent->direction == PCMDIR_REC))
685 d = parent->parentsnddev;
690 if (parent->direction == PCMDIR_PLAY) {
691 direction = PCMDIR_PLAY_VIRTUAL;
692 vchanfmt = d->pvchanformat;
693 vchanspd = d->pvchanrate;
695 direction = PCMDIR_REC_VIRTUAL;
696 vchanfmt = d->rvchanformat;
697 vchanspd = d->rvchanrate;
700 /* create a new playback channel */
701 ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent);
708 /* add us to our grandparent's channel list */
709 ret = pcm_chn_add(d, ch);
719 * Add us to our parent channel's children in reverse order
720 * so future destruction will pick the last (biggest number)
723 CHN_INSERT_SORT_DESCEND(parent, ch, children);
725 if (parent->flags & CHN_F_HAS_VCHAN)
728 parent->flags |= CHN_F_HAS_VCHAN;
730 parent_caps = chn_getcaps(parent);
731 if (parent_caps == NULL)
736 if (ret == 0 && vchanfmt == 0) {
740 r = resource_string_value(device_get_name(parent->dev),
741 device_get_unit(parent->dev), VCHAN_FMT_HINT(direction),
747 vchanfmt = snd_str2afmt(vfmt);
748 if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN))
752 vchanfmt = VCHAN_DEFAULT_FORMAT;
756 if (ret == 0 && vchanspd == 0) {
758 * This is very sad. Few soundcards advertised as being
759 * able to do (insanely) higher/lower speed, but in
760 * reality, they simply can't. At least, we give user chance
761 * to set sane value via kernel hints or sysctl.
764 r = resource_int_value(device_get_name(parent->dev),
765 device_get_unit(parent->dev), VCHAN_SPD_HINT(direction),
770 * No saved value, no hint, NOTHING.
772 * Workaround for sb16 running
773 * poorly at 45k / 49k.
775 switch (parent_caps->maxspeed) {
781 vchanspd = VCHAN_DEFAULT_RATE;
782 if (vchanspd > parent_caps->maxspeed)
783 vchanspd = parent_caps->maxspeed;
786 if (vchanspd < parent_caps->minspeed)
787 vchanspd = parent_caps->minspeed;
794 * Limit the speed between feeder_rate_min <-> feeder_rate_max.
796 if (vchanspd < feeder_rate_min)
797 vchanspd = feeder_rate_min;
798 if (vchanspd > feeder_rate_max)
799 vchanspd = feeder_rate_max;
801 if (feeder_rate_round) {
802 RANGE(vchanspd, parent_caps->minspeed,
803 parent_caps->maxspeed);
804 vchanspd = CHANNEL_SETSPEED(parent->methods,
805 parent->devinfo, vchanspd);
808 ret = chn_reset(parent, vchanfmt, vchanspd);
811 if (ret == 0 && save) {
815 if (direction == PCMDIR_PLAY_VIRTUAL) {
816 d->pvchanformat = parent->format;
817 d->pvchanrate = parent->speed;
819 d->rvchanformat = parent->format;
820 d->rvchanrate = parent->speed;
825 * If the parent channel supports digital format,
826 * enable passthrough mode.
828 if (ret == 0 && snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
829 parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
830 parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
834 CHN_REMOVE(parent, ch, children);
835 parent->flags &= ~CHN_F_HAS_VCHAN;
838 if (pcm_chn_remove(d, ch) == 0) {
850 vchan_destroy(struct pcm_channel *c)
852 struct pcm_channel *parent;
853 struct snddev_info *d;
856 KASSERT(c != NULL && c->parentchannel != NULL &&
857 c->parentsnddev != NULL, ("%s(): invalid channel=%p",
863 parent = c->parentchannel;
866 CHN_LOCKASSERT(parent);
870 if (!(parent->flags & CHN_F_BUSY))
873 if (CHN_EMPTY(parent, children))
876 /* remove us from our parent's children list */
877 CHN_REMOVE(parent, c, children);
879 if (CHN_EMPTY(parent, children)) {
880 parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
881 chn_reset(parent, parent->format, parent->speed);
886 /* remove us from our grandparent's channel list */
888 ret = pcm_chn_remove(d, c);
891 /* destroy ourselves */
893 ret = pcm_chn_destroy(c);
902 vchan_passthrough(struct pcm_channel *c, const char *caller)
904 vchan_sync(struct pcm_channel *c)
909 KASSERT(c != NULL && c->parentchannel != NULL &&
910 (c->flags & CHN_F_VIRTUAL),
911 ("%s(): invalid passthrough", __func__));
913 CHN_LOCKASSERT(c->parentchannel);
915 sndbuf_setspd(c->bufhard, c->parentchannel->speed);
916 c->flags |= CHN_F_PASSTHROUGH;
917 ret = feeder_chain(c);
918 c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
920 c->flags |= CHN_F_DIRTY;
923 if (snd_passthrough_verbose != 0) {
924 char *devname, buf[CHN_NAMELEN];
926 devname = dsp_unit2name(buf, sizeof(buf), c->unit);
927 device_printf(c->dev,
928 "%s(%s/%s) %s() -> re-sync err=%d\n",
929 __func__, (devname != NULL) ? devname : "dspX", c->comm,
938 vchan_initsys(device_t dev)
940 struct snddev_info *d;
943 unit = device_get_unit(dev);
944 d = device_get_softc(dev);
947 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
948 SYSCTL_CHILDREN(d->play_sysctl_tree),
949 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW,
950 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
951 sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
952 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
953 SYSCTL_CHILDREN(d->play_sysctl_tree),
954 OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RW,
955 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
956 sysctl_dev_pcm_vchanmode, "A",
957 "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
958 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
959 SYSCTL_CHILDREN(d->play_sysctl_tree),
960 OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW,
961 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
962 sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
963 SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
964 SYSCTL_CHILDREN(d->play_sysctl_tree),
965 OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW,
966 VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
967 sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
969 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
970 SYSCTL_CHILDREN(d->rec_sysctl_tree),
971 OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW,
972 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
973 sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
974 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
975 SYSCTL_CHILDREN(d->rec_sysctl_tree),
976 OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RW,
977 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
978 sysctl_dev_pcm_vchanmode, "A",
979 "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
980 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
981 SYSCTL_CHILDREN(d->rec_sysctl_tree),
982 OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW,
983 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
984 sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
985 SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
986 SYSCTL_CHILDREN(d->rec_sysctl_tree),
987 OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW,
988 VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
989 sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");