1139749Simp/*-
2193640Sariff * Copyright (c) 2006-2009 Ariff Abdullah <ariff@FreeBSD.org>
3164614Sariff * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
477269Scg * All rights reserved.
577269Scg *
677269Scg * Redistribution and use in source and binary forms, with or without
777269Scg * modification, are permitted provided that the following conditions
877269Scg * are met:
977269Scg * 1. Redistributions of source code must retain the above copyright
1077269Scg *    notice, this list of conditions and the following disclaimer.
1177269Scg * 2. Redistributions in binary form must reproduce the above copyright
1277269Scg *    notice, this list of conditions and the following disclaimer in the
1377269Scg *    documentation and/or other materials provided with the distribution.
1477269Scg *
1577269Scg * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1677269Scg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1777269Scg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1877269Scg * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1977269Scg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2077269Scg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2177269Scg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2277269Scg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2377269Scg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2477269Scg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2577269Scg * SUCH DAMAGE.
2677269Scg */
2777269Scg
28170206Sjoel/* Almost entirely rewritten to add multi-format/channels mixing support. */
29170206Sjoel
30193640Sariff#ifdef HAVE_KERNEL_OPTION_HEADERS
31193640Sariff#include "opt_snd.h"
32193640Sariff#endif
33193640Sariff
3477269Scg#include <dev/sound/pcm/sound.h>
3577269Scg#include <dev/sound/pcm/vchan.h>
3677269Scg
3782180ScgSND_DECLARE_FILE("$FreeBSD: releng/10.3/sys/dev/sound/pcm/vchan.c 193640 2009-06-07 19:12:08Z ariff $");
3882180Scg
39193640Sariff/*
40193640Sariff * [ac3 , dts , linear , 0, linear, 0]
41193640Sariff */
42193640Sariff#define FMTLIST_MAX		6
43193640Sariff#define FMTLIST_OFFSET		4
44193640Sariff#define DIGFMTS_MAX		2
45164614Sariff
46193640Sariff#ifdef SND_DEBUG
47193640Sariffstatic int snd_passthrough_verbose = 0;
48193640SariffSYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RW,
49193640Sariff	&snd_passthrough_verbose, 0, "passthrough verbosity");
50148606Snetchild
51193640Sariff#endif
52193640Sariff
53193640Sariffstruct vchan_info {
54170161Sariff	struct pcm_channel *channel;
5577269Scg	struct pcmchan_caps caps;
56193640Sariff	uint32_t fmtlist[FMTLIST_MAX];
57170161Sariff	int trigger;
5877269Scg};
5977269Scg
6077269Scgstatic void *
61170161Sariffvchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
62170161Sariff    struct pcm_channel *c, int dir)
6377269Scg{
64193640Sariff	struct vchan_info *info;
65193640Sariff	struct pcm_channel *p;
66193640Sariff	uint32_t i, j, *fmtlist;
6777269Scg
68170161Sariff	KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
69170161Sariff	    ("vchan_init: bad direction"));
70170161Sariff	KASSERT(c != NULL && c->parentchannel != NULL,
71170161Sariff	    ("vchan_init: bad channels"));
72170161Sariff
73193640Sariff	info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
74193640Sariff	info->channel = c;
75193640Sariff	info->trigger = PCMTRIG_STOP;
76193640Sariff	p = c->parentchannel;
7777269Scg
78193640Sariff	CHN_LOCK(p);
79193640Sariff
80193640Sariff	fmtlist = chn_getcaps(p)->fmtlist;
81193640Sariff	for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
82193640Sariff		if (fmtlist[i] & AFMT_PASSTHROUGH)
83193640Sariff			info->fmtlist[j++] = fmtlist[i];
84193640Sariff	}
85193640Sariff	if (p->format & AFMT_VCHAN)
86193640Sariff		info->fmtlist[j] = p->format;
87193640Sariff	else
88193640Sariff		info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
89193640Sariff	info->caps.fmtlist = info->fmtlist +
90193640Sariff	    ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
91193640Sariff
92193640Sariff	CHN_UNLOCK(p);
93193640Sariff
9477269Scg	c->flags |= CHN_F_VIRTUAL;
9577269Scg
96193640Sariff	return (info);
9777269Scg}
9877269Scg
9977269Scgstatic int
10077269Scgvchan_free(kobj_t obj, void *data)
10177269Scg{
102193640Sariff
103166392Sariff	free(data, M_DEVBUF);
104170161Sariff
105170161Sariff	return (0);
10677269Scg}
10777269Scg
10877269Scgstatic int
109164614Sariffvchan_setformat(kobj_t obj, void *data, uint32_t format)
11077269Scg{
111193640Sariff	struct vchan_info *info;
11277269Scg
113193640Sariff	info = data;
114193640Sariff
115193640Sariff	CHN_LOCKASSERT(info->channel);
116193640Sariff
117193640Sariff	if (!snd_fmtvalid(format, info->caps.fmtlist))
118170161Sariff		return (-1);
119170161Sariff
120170161Sariff	return (0);
12177269Scg}
12277269Scg
123193640Sariffstatic uint32_t
124164614Sariffvchan_setspeed(kobj_t obj, void *data, uint32_t speed)
12577269Scg{
126193640Sariff	struct vchan_info *info;
12777269Scg
128193640Sariff	info = data;
129193640Sariff
130193640Sariff	CHN_LOCKASSERT(info->channel);
131193640Sariff
132193640Sariff	return (info->caps.maxspeed);
13377269Scg}
13477269Scg
13577269Scgstatic int
136170161Sariffvchan_trigger(kobj_t obj, void *data, int go)
13777269Scg{
138193640Sariff	struct vchan_info *info;
139170161Sariff	struct pcm_channel *c, *p;
140193640Sariff	int ret, otrigger;
14177269Scg
142193640Sariff	info = data;
143193640Sariff
144193640Sariff	if (!PCMTRIG_COMMON(go) || go == info->trigger)
145170161Sariff		return (0);
14677882Scg
147193640Sariff	c = info->channel;
148170161Sariff	p = c->parentchannel;
149193640Sariff	otrigger = info->trigger;
150193640Sariff	info->trigger = go;
15177882Scg
152193640Sariff	CHN_LOCKASSERT(c);
153193640Sariff
154170161Sariff	CHN_UNLOCK(c);
155170161Sariff	CHN_LOCK(p);
15677269Scg
157170161Sariff	switch (go) {
158170161Sariff	case PCMTRIG_START:
159193640Sariff		if (otrigger != PCMTRIG_START)
160170161Sariff			CHN_INSERT_HEAD(p, c, children.busy);
161170161Sariff		break;
162170161Sariff	case PCMTRIG_STOP:
163170161Sariff	case PCMTRIG_ABORT:
164193640Sariff		if (otrigger == PCMTRIG_START)
165170161Sariff			CHN_REMOVE(p, c, children.busy);
166170161Sariff		break;
167170161Sariff	default:
168170161Sariff		break;
169170161Sariff	}
17077269Scg
171193640Sariff	ret = chn_notify(p, CHN_N_TRIGGER);
172193640Sariff
173193640Sariff	CHN_LOCK(c);
174193640Sariff
175193640Sariff	if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
176193640Sariff		ret = vchan_sync(c);
177193640Sariff
178193640Sariff	CHN_UNLOCK(c);
179170161Sariff	CHN_UNLOCK(p);
180170161Sariff	CHN_LOCK(c);
18177269Scg
182193640Sariff	return (ret);
18377269Scg}
18477269Scg
18577269Scgstatic struct pcmchan_caps *
18677269Scgvchan_getcaps(kobj_t obj, void *data)
18777269Scg{
188193640Sariff	struct vchan_info *info;
189193640Sariff	struct pcm_channel *c;
190193640Sariff	uint32_t pformat, pspeed, pflags, i;
19177269Scg
192193640Sariff	info = data;
193193640Sariff	c = info->channel;
194193640Sariff	pformat = c->parentchannel->format;
195193640Sariff	pspeed = c->parentchannel->speed;
196193640Sariff	pflags = c->parentchannel->flags;
197193640Sariff
198193640Sariff	CHN_LOCKASSERT(c);
199193640Sariff
200193640Sariff	if (pflags & CHN_F_VCHAN_DYNAMIC) {
201193640Sariff		info->caps.fmtlist = info->fmtlist;
202193640Sariff		if (pformat & AFMT_VCHAN) {
203193640Sariff			for (i = 0; info->caps.fmtlist[i] != 0; i++) {
204193640Sariff				if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
205193640Sariff					continue;
206193640Sariff				break;
207193640Sariff			}
208193640Sariff			info->caps.fmtlist[i] = pformat;
209193640Sariff		}
210193640Sariff		if (c->format & AFMT_PASSTHROUGH)
211193640Sariff			info->caps.minspeed = c->speed;
212193640Sariff		else
213193640Sariff			info->caps.minspeed = pspeed;
214193640Sariff		info->caps.maxspeed = info->caps.minspeed;
215193640Sariff	} else {
216193640Sariff		info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
217193640Sariff		if (pformat & AFMT_VCHAN)
218193640Sariff			info->caps.fmtlist[0] = pformat;
219193640Sariff		else {
220193640Sariff			device_printf(c->dev,
221193640Sariff			    "%s(): invalid vchan format 0x%08x",
222193640Sariff			    __func__, pformat);
223193640Sariff			info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
224193640Sariff		}
225193640Sariff		info->caps.minspeed = pspeed;
226193640Sariff		info->caps.maxspeed = info->caps.minspeed;
227164614Sariff	}
22877269Scg
229193640Sariff	return (&info->caps);
23077269Scg}
23177269Scg
232193640Sariffstatic struct pcmchan_matrix *
233193640Sariffvchan_getmatrix(kobj_t obj, void *data, uint32_t format)
234193640Sariff{
235193640Sariff
236193640Sariff	return (feeder_matrix_format_map(format));
237193640Sariff}
238193640Sariff
23977269Scgstatic kobj_method_t vchan_methods[] = {
240164614Sariff	KOBJMETHOD(channel_init,		vchan_init),
241164614Sariff	KOBJMETHOD(channel_free,		vchan_free),
242164614Sariff	KOBJMETHOD(channel_setformat,		vchan_setformat),
243164614Sariff	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
244164614Sariff	KOBJMETHOD(channel_trigger,		vchan_trigger),
245164614Sariff	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
246193640Sariff	KOBJMETHOD(channel_getmatrix,		vchan_getmatrix),
247193640Sariff	KOBJMETHOD_END
24877269Scg};
24977269ScgCHANNEL_DECLARE(vchan);
25077269Scg
251193640Sariffstatic void
252193640Sariffpcm_getparentchannel(struct snddev_info *d,
253193640Sariff    struct pcm_channel **wrch, struct pcm_channel **rdch)
254193640Sariff{
255193640Sariff	struct pcm_channel **ch, *wch, *rch, *c;
256193640Sariff
257193640Sariff	KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__));
258193640Sariff
259193640Sariff	PCM_BUSYASSERT(d);
260193640Sariff	PCM_UNLOCKASSERT(d);
261193640Sariff
262193640Sariff	wch = NULL;
263193640Sariff	rch = NULL;
264193640Sariff
265193640Sariff	CHN_FOREACH(c, d, channels.pcm) {
266193640Sariff		CHN_LOCK(c);
267193640Sariff		ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch;
268193640Sariff		if (c->flags & CHN_F_VIRTUAL) {
269193640Sariff			/* Sanity check */
270193640Sariff			if (*ch != NULL && *ch != c->parentchannel) {
271193640Sariff				CHN_UNLOCK(c);
272193640Sariff				*ch = NULL;
273193640Sariff				break;
274193640Sariff			}
275193640Sariff		} else if (c->flags & CHN_F_HAS_VCHAN) {
276193640Sariff			/* No way!! */
277193640Sariff			if (*ch != NULL) {
278193640Sariff				CHN_UNLOCK(c);
279193640Sariff				*ch = NULL;
280193640Sariff				break;
281193640Sariff			}
282193640Sariff			*ch = c;
283193640Sariff		}
284193640Sariff		CHN_UNLOCK(c);
285193640Sariff	}
286193640Sariff
287193640Sariff	if (wrch != NULL)
288193640Sariff		*wrch = wch;
289193640Sariff	if (rdch != NULL)
290193640Sariff		*rdch = rch;
291193640Sariff}
292193640Sariff
293193640Sariffstatic int
294193640Sariffsysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
295193640Sariff{
296193640Sariff	struct snddev_info *d;
297193640Sariff	int direction, vchancount;
298193640Sariff	int err, cnt;
299193640Sariff
300193640Sariff	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
301193640Sariff	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
302193640Sariff		return (EINVAL);
303193640Sariff
304193640Sariff	PCM_LOCK(d);
305193640Sariff	PCM_WAIT(d);
306193640Sariff
307193640Sariff	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
308193640Sariff	case VCHAN_PLAY:
309193640Sariff		direction = PCMDIR_PLAY;
310193640Sariff		vchancount = d->pvchancount;
311193640Sariff		cnt = d->playcount;
312193640Sariff		break;
313193640Sariff	case VCHAN_REC:
314193640Sariff		direction = PCMDIR_REC;
315193640Sariff		vchancount = d->rvchancount;
316193640Sariff		cnt = d->reccount;
317193640Sariff		break;
318193640Sariff	default:
319193640Sariff		PCM_UNLOCK(d);
320193640Sariff		return (EINVAL);
321193640Sariff		break;
322193640Sariff	}
323193640Sariff
324193640Sariff	if (cnt < 1) {
325193640Sariff		PCM_UNLOCK(d);
326193640Sariff		return (ENODEV);
327193640Sariff	}
328193640Sariff
329193640Sariff	PCM_ACQUIRE(d);
330193640Sariff	PCM_UNLOCK(d);
331193640Sariff
332193640Sariff	cnt = vchancount;
333193640Sariff	err = sysctl_handle_int(oidp, &cnt, 0, req);
334193640Sariff
335193640Sariff	if (err == 0 && req->newptr != NULL && vchancount != cnt) {
336193640Sariff		if (cnt < 0)
337193640Sariff			cnt = 0;
338193640Sariff		if (cnt > SND_MAXVCHANS)
339193640Sariff			cnt = SND_MAXVCHANS;
340193640Sariff		err = pcm_setvchans(d, direction, cnt, -1);
341193640Sariff	}
342193640Sariff
343193640Sariff	PCM_RELEASE_QUICK(d);
344193640Sariff
345193640Sariff	return err;
346193640Sariff}
347193640Sariff
348193640Sariffstatic int
349193640Sariffsysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
350193640Sariff{
351193640Sariff	struct snddev_info *d;
352193640Sariff	struct pcm_channel *c;
353193640Sariff	uint32_t dflags;
354193640Sariff	int direction, ret;
355193640Sariff	char dtype[16];
356193640Sariff
357193640Sariff	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
358193640Sariff	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
359193640Sariff		return (EINVAL);
360193640Sariff
361193640Sariff	PCM_LOCK(d);
362193640Sariff	PCM_WAIT(d);
363193640Sariff
364193640Sariff	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
365193640Sariff	case VCHAN_PLAY:
366193640Sariff		direction = PCMDIR_PLAY;
367193640Sariff		break;
368193640Sariff	case VCHAN_REC:
369193640Sariff		direction = PCMDIR_REC;
370193640Sariff		break;
371193640Sariff	default:
372193640Sariff		PCM_UNLOCK(d);
373193640Sariff		return (EINVAL);
374193640Sariff		break;
375193640Sariff	}
376193640Sariff
377193640Sariff	PCM_ACQUIRE(d);
378193640Sariff	PCM_UNLOCK(d);
379193640Sariff
380193640Sariff	if (direction == PCMDIR_PLAY)
381193640Sariff		pcm_getparentchannel(d, &c, NULL);
382193640Sariff	else
383193640Sariff		pcm_getparentchannel(d, NULL, &c);
384193640Sariff
385193640Sariff	if (c == NULL) {
386193640Sariff		PCM_RELEASE_QUICK(d);
387193640Sariff		return (EINVAL);
388193640Sariff	}
389193640Sariff
390193640Sariff	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
391193640Sariff	    __func__, direction, c->direction));
392193640Sariff
393193640Sariff	CHN_LOCK(c);
394193640Sariff	if (c->flags & CHN_F_VCHAN_PASSTHROUGH)
395193640Sariff		strlcpy(dtype, "passthrough", sizeof(dtype));
396193640Sariff	else if (c->flags & CHN_F_VCHAN_ADAPTIVE)
397193640Sariff		strlcpy(dtype, "adaptive", sizeof(dtype));
398193640Sariff	else
399193640Sariff		strlcpy(dtype, "fixed", sizeof(dtype));
400193640Sariff	CHN_UNLOCK(c);
401193640Sariff
402193640Sariff	ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
403193640Sariff	if (ret == 0 && req->newptr != NULL) {
404193640Sariff		if (strcasecmp(dtype, "passthrough") == 0 ||
405193640Sariff		    strcmp(dtype, "1") == 0)
406193640Sariff			dflags = CHN_F_VCHAN_PASSTHROUGH;
407193640Sariff		else if (strcasecmp(dtype, "adaptive") == 0 ||
408193640Sariff		    strcmp(dtype, "2") == 0)
409193640Sariff			dflags = CHN_F_VCHAN_ADAPTIVE;
410193640Sariff		else if (strcasecmp(dtype, "fixed") == 0 ||
411193640Sariff		    strcmp(dtype, "0") == 0)
412193640Sariff			dflags = 0;
413193640Sariff		else {
414193640Sariff			PCM_RELEASE_QUICK(d);
415193640Sariff			return (EINVAL);
416193640Sariff		}
417193640Sariff		CHN_LOCK(c);
418193640Sariff		if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
419193640Sariff		    (c->flags & CHN_F_PASSTHROUGH)) {
420193640Sariff			CHN_UNLOCK(c);
421193640Sariff			PCM_RELEASE_QUICK(d);
422193640Sariff			return (0);
423193640Sariff		}
424193640Sariff		c->flags &= ~CHN_F_VCHAN_DYNAMIC;
425193640Sariff		c->flags |= dflags;
426193640Sariff		CHN_UNLOCK(c);
427193640Sariff	}
428193640Sariff
429193640Sariff	PCM_RELEASE_QUICK(d);
430193640Sariff
431193640Sariff	return (ret);
432193640Sariff}
433193640Sariff
434148606Snetchild/*
435193640Sariff * On the fly vchan rate/format settings
436148606Snetchild */
437193640Sariff
438193640Sariff#define VCHAN_ACCESSIBLE(c)	(!((c)->flags & (CHN_F_PASSTHROUGH |	\
439193640Sariff				 CHN_F_EXCLUSIVE)) &&			\
440193640Sariff				 (((c)->flags & CHN_F_VCHAN_DYNAMIC) ||	\
441193640Sariff				 CHN_STOPPED(c)))
442148606Snetchildstatic int
443193640Sariffsysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
444148606Snetchild{
445148606Snetchild	struct snddev_info *d;
446193640Sariff	struct pcm_channel *c, *ch;
447148606Snetchild	struct pcmchan_caps *caps;
448193640Sariff	int *vchanrate, vchancount, direction, ret, newspd, restart;
449148606Snetchild
450170161Sariff	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
451170815Sariff	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
452170161Sariff		return (EINVAL);
453170161Sariff
454193640Sariff	PCM_LOCK(d);
455170815Sariff	PCM_WAIT(d);
456170815Sariff
457170161Sariff	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
458170161Sariff	case VCHAN_PLAY:
459170161Sariff		direction = PCMDIR_PLAY;
460170161Sariff		vchancount = d->pvchancount;
461170161Sariff		vchanrate = &d->pvchanrate;
462170161Sariff		break;
463170161Sariff	case VCHAN_REC:
464170161Sariff		direction = PCMDIR_REC;
465170161Sariff		vchancount = d->rvchancount;
466170161Sariff		vchanrate = &d->rvchanrate;
467170161Sariff		break;
468170161Sariff	default:
469193640Sariff		PCM_UNLOCK(d);
470170161Sariff		return (EINVAL);
471170161Sariff		break;
472170161Sariff	}
473170161Sariff
474170815Sariff	if (vchancount < 1) {
475193640Sariff		PCM_UNLOCK(d);
476170161Sariff		return (EINVAL);
477157330Sariff	}
478170815Sariff
479170815Sariff	PCM_ACQUIRE(d);
480193640Sariff	PCM_UNLOCK(d);
481170815Sariff
482193640Sariff	if (direction == PCMDIR_PLAY)
483193640Sariff		pcm_getparentchannel(d, &c, NULL);
484193640Sariff	else
485193640Sariff		pcm_getparentchannel(d, NULL, &c);
486170815Sariff
487193640Sariff	if (c == NULL) {
488193640Sariff		PCM_RELEASE_QUICK(d);
489193640Sariff		return (EINVAL);
490148606Snetchild	}
491193640Sariff
492193640Sariff	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
493193640Sariff	    __func__, direction, c->direction));
494193640Sariff
495193640Sariff	CHN_LOCK(c);
496193640Sariff	newspd = c->speed;
497193640Sariff	CHN_UNLOCK(c);
498193640Sariff
499193640Sariff	ret = sysctl_handle_int(oidp, &newspd, 0, req);
500193640Sariff	if (ret != 0 || req->newptr == NULL) {
501170815Sariff		PCM_RELEASE_QUICK(d);
502193640Sariff		return (ret);
503193640Sariff	}
504193640Sariff
505193640Sariff	if (newspd < 1 || newspd < feeder_rate_min ||
506193640Sariff	    newspd > feeder_rate_max) {
507193640Sariff		PCM_RELEASE_QUICK(d);
508170161Sariff		return (EINVAL);
509148606Snetchild	}
510170815Sariff
511193640Sariff	CHN_LOCK(c);
512193640Sariff
513193640Sariff	if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
514193640Sariff		if (CHN_STARTED(c)) {
515193640Sariff			chn_abort(c);
516193640Sariff			restart = 1;
517193640Sariff		} else
518193640Sariff			restart = 0;
519193640Sariff
520164614Sariff		if (feeder_rate_round) {
521193640Sariff			caps = chn_getcaps(c);
522193640Sariff			RANGE(newspd, caps->minspeed, caps->maxspeed);
523193640Sariff			newspd = CHANNEL_SETSPEED(c->methods,
524193640Sariff			    c->devinfo, newspd);
525148606Snetchild		}
526193640Sariff
527193640Sariff		ret = chn_reset(c, c->format, newspd);
528193640Sariff		if (ret == 0) {
529193640Sariff			*vchanrate = c->speed;
530193640Sariff			if (restart != 0) {
531193640Sariff				CHN_FOREACH(ch, c, children.busy) {
532193640Sariff					CHN_LOCK(ch);
533193640Sariff					if (VCHAN_SYNC_REQUIRED(ch))
534193640Sariff						vchan_sync(ch);
535193640Sariff					CHN_UNLOCK(ch);
536193640Sariff				}
537193640Sariff				c->flags |= CHN_F_DIRTY;
538193640Sariff				ret = chn_start(c, 1);
539151456Sariff			}
540170815Sariff		}
541148606Snetchild	}
542170815Sariff
543193640Sariff	CHN_UNLOCK(c);
544193640Sariff
545170815Sariff	PCM_RELEASE_QUICK(d);
546170815Sariff
547193640Sariff	return (ret);
548148606Snetchild}
549164614Sariff
550164614Sariffstatic int
551193640Sariffsysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
552164614Sariff{
553164614Sariff	struct snddev_info *d;
554193640Sariff	struct pcm_channel *c, *ch;
555193640Sariff	uint32_t newfmt;
556193640Sariff	int *vchanformat, vchancount, direction, ret, restart;
557193640Sariff	char fmtstr[AFMTSTR_LEN];
558164614Sariff
559170161Sariff	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
560170815Sariff	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
561170161Sariff		return (EINVAL);
562170161Sariff
563193640Sariff	PCM_LOCK(d);
564170815Sariff	PCM_WAIT(d);
565170815Sariff
566170161Sariff	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
567170161Sariff	case VCHAN_PLAY:
568170161Sariff		direction = PCMDIR_PLAY;
569170161Sariff		vchancount = d->pvchancount;
570170161Sariff		vchanformat = &d->pvchanformat;
571170161Sariff		break;
572170161Sariff	case VCHAN_REC:
573170161Sariff		direction = PCMDIR_REC;
574170161Sariff		vchancount = d->rvchancount;
575170161Sariff		vchanformat = &d->rvchanformat;
576170161Sariff		break;
577170161Sariff	default:
578193640Sariff		PCM_UNLOCK(d);
579170161Sariff		return (EINVAL);
580170161Sariff		break;
581170161Sariff	}
582170161Sariff
583170815Sariff	if (vchancount < 1) {
584193640Sariff		PCM_UNLOCK(d);
585170161Sariff		return (EINVAL);
586164614Sariff	}
587170815Sariff
588170815Sariff	PCM_ACQUIRE(d);
589193640Sariff	PCM_UNLOCK(d);
590170815Sariff
591193640Sariff	if (direction == PCMDIR_PLAY)
592193640Sariff		pcm_getparentchannel(d, &c, NULL);
593193640Sariff	else
594193640Sariff		pcm_getparentchannel(d, NULL, &c);
595193640Sariff
596193640Sariff	if (c == NULL) {
597193640Sariff		PCM_RELEASE_QUICK(d);
598193640Sariff		return (EINVAL);
599164614Sariff	}
600193640Sariff
601193640Sariff	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
602193640Sariff	    __func__, direction, c->direction));
603193640Sariff
604193640Sariff	CHN_LOCK(c);
605193640Sariff
606193640Sariff	bzero(fmtstr, sizeof(fmtstr));
607193640Sariff
608193640Sariff	if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format)
609193640Sariff		strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
610193640Sariff
611193640Sariff	CHN_UNLOCK(c);
612193640Sariff
613193640Sariff	ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
614193640Sariff	if (ret != 0 || req->newptr == NULL) {
615170815Sariff		PCM_RELEASE_QUICK(d);
616193640Sariff		return (ret);
617193640Sariff	}
618193640Sariff
619193640Sariff	newfmt = snd_str2afmt(fmtstr);
620193640Sariff	if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
621193640Sariff		PCM_RELEASE_QUICK(d);
622170161Sariff		return (EINVAL);
623164614Sariff	}
624170815Sariff
625193640Sariff	CHN_LOCK(c);
626193640Sariff
627193640Sariff	if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
628193640Sariff		if (CHN_STARTED(c)) {
629193640Sariff			chn_abort(c);
630193640Sariff			restart = 1;
631193640Sariff		} else
632193640Sariff			restart = 0;
633193640Sariff
634193640Sariff		ret = chn_reset(c, newfmt, c->speed);
635193640Sariff		if (ret == 0) {
636193640Sariff			*vchanformat = c->format;
637193640Sariff			if (restart != 0) {
638193640Sariff				CHN_FOREACH(ch, c, children.busy) {
639193640Sariff					CHN_LOCK(ch);
640193640Sariff					if (VCHAN_SYNC_REQUIRED(ch))
641193640Sariff						vchan_sync(ch);
642193640Sariff					CHN_UNLOCK(ch);
643193640Sariff				}
644193640Sariff				c->flags |= CHN_F_DIRTY;
645193640Sariff				ret = chn_start(c, 1);
646164614Sariff			}
647164614Sariff		}
648164614Sariff	}
649170815Sariff
650193640Sariff	CHN_UNLOCK(c);
651193640Sariff
652170815Sariff	PCM_RELEASE_QUICK(d);
653170815Sariff
654193640Sariff	return (ret);
655164614Sariff}
656148606Snetchild
65777269Scg/* virtual channel interface */
65877269Scg
659170161Sariff#define VCHAN_FMT_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
660170161Sariff				"play.vchanformat" : "rec.vchanformat"
661170161Sariff#define VCHAN_SPD_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
662170161Sariff				"play.vchanrate" : "rec.vchanrate"
663170161Sariff
66477269Scgint
665170161Sariffvchan_create(struct pcm_channel *parent, int num)
66677269Scg{
667193640Sariff	struct snddev_info *d;
668193640Sariff	struct pcm_channel *ch;
669149953Snetchild	struct pcmchan_caps *parent_caps;
670193640Sariff	uint32_t vchanfmt, vchanspd;
671193640Sariff	int ret, direction, r, save;
67277269Scg
673193640Sariff	d = parent->parentsnddev;
674193640Sariff
675170815Sariff	PCM_BUSYASSERT(d);
676193640Sariff	CHN_LOCKASSERT(parent);
677170815Sariff
678149953Snetchild	if (!(parent->flags & CHN_F_BUSY))
679170161Sariff		return (EBUSY);
680125136Struckman
681193640Sariff	if (!(parent->direction == PCMDIR_PLAY ||
682193640Sariff	    parent->direction == PCMDIR_REC))
683193640Sariff		return (EINVAL);
684193640Sariff
685193640Sariff	d = parent->parentsnddev;
686193640Sariff
687193640Sariff	CHN_UNLOCK(parent);
688193640Sariff	PCM_LOCK(d);
689193640Sariff
690170161Sariff	if (parent->direction == PCMDIR_PLAY) {
691170161Sariff		direction = PCMDIR_PLAY_VIRTUAL;
692170161Sariff		vchanfmt = d->pvchanformat;
693193640Sariff		vchanspd = d->pvchanrate;
694193640Sariff	} else {
695170161Sariff		direction = PCMDIR_REC_VIRTUAL;
696170161Sariff		vchanfmt = d->rvchanformat;
697193640Sariff		vchanspd = d->rvchanrate;
698193640Sariff	}
699149953Snetchild
70077269Scg	/* create a new playback channel */
701170161Sariff	ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent);
702170161Sariff	if (ch == NULL) {
703193640Sariff		PCM_UNLOCK(d);
704164614Sariff		CHN_LOCK(parent);
705170161Sariff		return (ENODEV);
70677269Scg	}
70777269Scg
70877269Scg	/* add us to our grandparent's channel list */
709193640Sariff	ret = pcm_chn_add(d, ch);
710193640Sariff	PCM_UNLOCK(d);
711193640Sariff	if (ret != 0) {
712170161Sariff		pcm_chn_destroy(ch);
713149953Snetchild		CHN_LOCK(parent);
714193640Sariff		return (ret);
71577269Scg	}
71677269Scg
717164614Sariff	CHN_LOCK(parent);
718193640Sariff	/*
719193640Sariff	 * Add us to our parent channel's children in reverse order
720193640Sariff	 * so future destruction will pick the last (biggest number)
721193640Sariff	 * channel.
722193640Sariff	 */
723193640Sariff	CHN_INSERT_SORT_DESCEND(parent, ch, children);
724193640Sariff
725193640Sariff	if (parent->flags & CHN_F_HAS_VCHAN)
726193640Sariff		return (0);
727193640Sariff
728149953Snetchild	parent->flags |= CHN_F_HAS_VCHAN;
729148606Snetchild
730193640Sariff	parent_caps = chn_getcaps(parent);
731193640Sariff	if (parent_caps == NULL)
732193640Sariff		ret = EINVAL;
733148606Snetchild
734193640Sariff	save = 0;
735164614Sariff
736193640Sariff	if (ret == 0 && vchanfmt == 0) {
737193640Sariff		const char *vfmt;
738193640Sariff
739193640Sariff		CHN_UNLOCK(parent);
740193640Sariff		r = resource_string_value(device_get_name(parent->dev),
741193640Sariff		    device_get_unit(parent->dev), VCHAN_FMT_HINT(direction),
742193640Sariff		    &vfmt);
743193640Sariff		CHN_LOCK(parent);
744193640Sariff		if (r != 0)
745193640Sariff			vfmt = NULL;
746193640Sariff		if (vfmt != NULL) {
747193640Sariff			vchanfmt = snd_str2afmt(vfmt);
748193640Sariff			if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN))
749193640Sariff				vchanfmt = 0;
750164614Sariff		}
751193640Sariff		if (vchanfmt == 0)
752193640Sariff			vchanfmt = VCHAN_DEFAULT_FORMAT;
753193640Sariff		save = 1;
754193640Sariff	}
755149953Snetchild
756193640Sariff	if (ret == 0 && vchanspd == 0) {
757193640Sariff		/*
758193640Sariff		 * This is very sad. Few soundcards advertised as being
759193640Sariff		 * able to do (insanely) higher/lower speed, but in
760193640Sariff		 * reality, they simply can't. At least, we give user chance
761193640Sariff		 * to set sane value via kernel hints or sysctl.
762193640Sariff		 */
763193640Sariff		CHN_UNLOCK(parent);
764193640Sariff		r = resource_int_value(device_get_name(parent->dev),
765193640Sariff		    device_get_unit(parent->dev), VCHAN_SPD_HINT(direction),
766193640Sariff		    &vchanspd);
767193640Sariff		CHN_LOCK(parent);
768193640Sariff		if (r != 0) {
769148606Snetchild			/*
770193640Sariff			 * No saved value, no hint, NOTHING.
771193640Sariff			 *
772193640Sariff			 * Workaround for sb16 running
773193640Sariff			 * poorly at 45k / 49k.
774148606Snetchild			 */
775193640Sariff			switch (parent_caps->maxspeed) {
776193640Sariff			case 45000:
777193640Sariff			case 49000:
778193640Sariff				vchanspd = 44100;
779193640Sariff				break;
780193640Sariff			default:
781193640Sariff				vchanspd = VCHAN_DEFAULT_RATE;
782193640Sariff				if (vchanspd > parent_caps->maxspeed)
783193640Sariff					vchanspd = parent_caps->maxspeed;
784193640Sariff				break;
785149953Snetchild			}
786193640Sariff			if (vchanspd < parent_caps->minspeed)
787193640Sariff				vchanspd = parent_caps->minspeed;
788193640Sariff		}
789193640Sariff		save = 1;
790193640Sariff	}
791148606Snetchild
792193640Sariff	if (ret == 0) {
793193640Sariff		/*
794193640Sariff		 * Limit the speed between feeder_rate_min <-> feeder_rate_max.
795193640Sariff		 */
796193640Sariff		if (vchanspd < feeder_rate_min)
797193640Sariff			vchanspd = feeder_rate_min;
798193640Sariff		if (vchanspd > feeder_rate_max)
799193640Sariff			vchanspd = feeder_rate_max;
800148606Snetchild
801193640Sariff		if (feeder_rate_round) {
802193640Sariff			RANGE(vchanspd, parent_caps->minspeed,
803193640Sariff			    parent_caps->maxspeed);
804193640Sariff			vchanspd = CHANNEL_SETSPEED(parent->methods,
805193640Sariff			    parent->devinfo, vchanspd);
806193640Sariff		}
807148606Snetchild
808193640Sariff		ret = chn_reset(parent, vchanfmt, vchanspd);
809193640Sariff	}
810149953Snetchild
811193640Sariff	if (ret == 0 && save) {
812193640Sariff		/*
813193640Sariff		 * Save new value.
814193640Sariff		 */
815193640Sariff		if (direction == PCMDIR_PLAY_VIRTUAL) {
816193640Sariff			d->pvchanformat = parent->format;
817193640Sariff			d->pvchanrate = parent->speed;
818193640Sariff		} else {
819193640Sariff			d->rvchanformat = parent->format;
820193640Sariff			d->rvchanrate = parent->speed;
821148606Snetchild		}
82277269Scg	}
823193640Sariff
824193640Sariff	/*
825193640Sariff	 * If the parent channel supports digital format,
826193640Sariff	 * enable passthrough mode.
827193640Sariff	 */
828193640Sariff	if (ret == 0 && snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
829193640Sariff		parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
830193640Sariff		parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
831193640Sariff	}
83277269Scg
833193640Sariff	if (ret != 0) {
834193640Sariff		CHN_REMOVE(parent, ch, children);
835193640Sariff		parent->flags &= ~CHN_F_HAS_VCHAN;
836193640Sariff		CHN_UNLOCK(parent);
837193640Sariff		PCM_LOCK(d);
838193640Sariff		if (pcm_chn_remove(d, ch) == 0) {
839193640Sariff			PCM_UNLOCK(d);
840193640Sariff			pcm_chn_destroy(ch);
841193640Sariff		} else
842193640Sariff			PCM_UNLOCK(d);
843193640Sariff		CHN_LOCK(parent);
844193640Sariff	}
845193640Sariff
846193640Sariff	return (ret);
84777269Scg}
84877269Scg
84977269Scgint
85077269Scgvchan_destroy(struct pcm_channel *c)
85177269Scg{
852193640Sariff	struct pcm_channel *parent;
853193640Sariff	struct snddev_info *d;
854193640Sariff	int ret;
85577269Scg
856193640Sariff	KASSERT(c != NULL && c->parentchannel != NULL &&
857193640Sariff	    c->parentsnddev != NULL, ("%s(): invalid channel=%p",
858193640Sariff	    __func__, c));
859193640Sariff
860193640Sariff	CHN_LOCKASSERT(c);
861193640Sariff
862193640Sariff	d = c->parentsnddev;
863193640Sariff	parent = c->parentchannel;
864193640Sariff
865170815Sariff	PCM_BUSYASSERT(d);
866193640Sariff	CHN_LOCKASSERT(parent);
867170815Sariff
868193640Sariff	CHN_UNLOCK(c);
869193640Sariff
870193640Sariff	if (!(parent->flags & CHN_F_BUSY))
871170161Sariff		return (EBUSY);
872193640Sariff
873193640Sariff	if (CHN_EMPTY(parent, children))
874170161Sariff		return (EINVAL);
87577882Scg
87677269Scg	/* remove us from our parent's children list */
877170161Sariff	CHN_REMOVE(parent, c, children);
87877269Scg
879170161Sariff	if (CHN_EMPTY(parent, children)) {
880157330Sariff		parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
881193640Sariff		chn_reset(parent, parent->format, parent->speed);
882149953Snetchild	}
88378895Scg
884170161Sariff	CHN_UNLOCK(parent);
885170161Sariff
886124617Sphk	/* remove us from our grandparent's channel list */
887193640Sariff	PCM_LOCK(d);
888193640Sariff	ret = pcm_chn_remove(d, c);
889193640Sariff	PCM_UNLOCK(d);
89078895Scg
89177269Scg	/* destroy ourselves */
892193640Sariff	if (ret == 0)
893193640Sariff		ret = pcm_chn_destroy(c);
89477269Scg
895193640Sariff	CHN_LOCK(parent);
896193640Sariff
897193640Sariff	return (ret);
89877269Scg}
89977269Scg
90082180Scgint
901193640Sariff#ifdef SND_DEBUG
902193640Sariffvchan_passthrough(struct pcm_channel *c, const char *caller)
903193640Sariff#else
904193640Sariffvchan_sync(struct pcm_channel *c)
905193640Sariff#endif
906193640Sariff{
907193640Sariff	int ret;
908193640Sariff
909193640Sariff	KASSERT(c != NULL && c->parentchannel != NULL &&
910193640Sariff	    (c->flags & CHN_F_VIRTUAL),
911193640Sariff	    ("%s(): invalid passthrough", __func__));
912193640Sariff	CHN_LOCKASSERT(c);
913193640Sariff	CHN_LOCKASSERT(c->parentchannel);
914193640Sariff
915193640Sariff	sndbuf_setspd(c->bufhard, c->parentchannel->speed);
916193640Sariff	c->flags |= CHN_F_PASSTHROUGH;
917193640Sariff	ret = feeder_chain(c);
918193640Sariff	c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
919193640Sariff	if (ret != 0)
920193640Sariff		c->flags |= CHN_F_DIRTY;
921193640Sariff
922193640Sariff#ifdef SND_DEBUG
923193640Sariff	if (snd_passthrough_verbose != 0) {
924193640Sariff		char *devname, buf[CHN_NAMELEN];
925193640Sariff
926193640Sariff		devname = dsp_unit2name(buf, sizeof(buf), c->unit);
927193640Sariff		device_printf(c->dev,
928193640Sariff		    "%s(%s/%s) %s() -> re-sync err=%d\n",
929193640Sariff		    __func__, (devname != NULL) ? devname : "dspX", c->comm,
930193640Sariff		    caller, ret);
931193640Sariff	}
932193640Sariff#endif
933193640Sariff
934193640Sariff	return (ret);
935193640Sariff}
936193640Sariff
937193640Sariffvoid
93882180Scgvchan_initsys(device_t dev)
93982180Scg{
94077269Scg	struct snddev_info *d;
941170161Sariff	int unit;
94277269Scg
943170161Sariff	unit = device_get_unit(dev);
944164614Sariff	d = device_get_softc(dev);
945170161Sariff
946170161Sariff	/* Play */
947170161Sariff	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
948170161Sariff	    SYSCTL_CHILDREN(d->play_sysctl_tree),
949170161Sariff	    OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW,
950170161Sariff	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
951193640Sariff	    sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
952170161Sariff	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
953170161Sariff	    SYSCTL_CHILDREN(d->play_sysctl_tree),
954193640Sariff	    OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RW,
955193640Sariff	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
956193640Sariff	    sysctl_dev_pcm_vchanmode, "A",
957193640Sariff	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
958193640Sariff	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
959193640Sariff	    SYSCTL_CHILDREN(d->play_sysctl_tree),
960170161Sariff	    OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW,
961170161Sariff	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
962193640Sariff	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
963170161Sariff	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
964170161Sariff	    SYSCTL_CHILDREN(d->play_sysctl_tree),
965170161Sariff	    OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW,
966170161Sariff	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
967193640Sariff	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
968170161Sariff	/* Rec */
969170161Sariff	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
970170161Sariff	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
971170161Sariff	    OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW,
972170161Sariff	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
973193640Sariff	    sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
974170161Sariff	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
975170161Sariff	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
976193640Sariff	    OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RW,
977193640Sariff	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
978193640Sariff	    sysctl_dev_pcm_vchanmode, "A",
979193640Sariff	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
980193640Sariff	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
981193640Sariff	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
982170161Sariff	    OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW,
983170161Sariff	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
984193640Sariff	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
985170161Sariff	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
986170161Sariff	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
987170161Sariff	    OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW,
988170161Sariff	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
989193640Sariff	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
99077269Scg}
991