xref: /freebsd/sys/dev/sound/pcm/vchan.c (revision d6d4586b0b7e3b01812e6c26818af78bf9b680a3)
1098ca2bdSWarner Losh /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3718cf2ccSPedro F. Giffuni  *
490da2b28SAriff Abdullah  * Copyright (c) 2006-2009 Ariff Abdullah <ariff@FreeBSD.org>
5a580b31aSAriff Abdullah  * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6285648f9SCameron Grant  * All rights reserved.
72b14465fSChristos Margiolis  * Copyright (c) 2024 The FreeBSD Foundation
82b14465fSChristos Margiolis  *
92b14465fSChristos Margiolis  * Portions of this software were developed by Christos Margiolis
102b14465fSChristos Margiolis  * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
11285648f9SCameron Grant  *
12285648f9SCameron Grant  * Redistribution and use in source and binary forms, with or without
13285648f9SCameron Grant  * modification, are permitted provided that the following conditions
14285648f9SCameron Grant  * are met:
15285648f9SCameron Grant  * 1. Redistributions of source code must retain the above copyright
16285648f9SCameron Grant  *    notice, this list of conditions and the following disclaimer.
17285648f9SCameron Grant  * 2. Redistributions in binary form must reproduce the above copyright
18285648f9SCameron Grant  *    notice, this list of conditions and the following disclaimer in the
19285648f9SCameron Grant  *    documentation and/or other materials provided with the distribution.
20285648f9SCameron Grant  *
21285648f9SCameron Grant  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22285648f9SCameron Grant  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23285648f9SCameron Grant  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24285648f9SCameron Grant  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25285648f9SCameron Grant  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26285648f9SCameron Grant  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27285648f9SCameron Grant  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28285648f9SCameron Grant  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29285648f9SCameron Grant  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30285648f9SCameron Grant  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31285648f9SCameron Grant  * SUCH DAMAGE.
32285648f9SCameron Grant  */
33285648f9SCameron Grant 
34e9577a5cSJoel Dahl /* Almost entirely rewritten to add multi-format/channels mixing support. */
35e9577a5cSJoel Dahl 
3690da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
3790da2b28SAriff Abdullah #include "opt_snd.h"
3890da2b28SAriff Abdullah #endif
3990da2b28SAriff Abdullah 
40285648f9SCameron Grant #include <dev/sound/pcm/sound.h>
41285648f9SCameron Grant #include <dev/sound/pcm/vchan.h>
42285648f9SCameron Grant 
4390da2b28SAriff Abdullah /*
4490da2b28SAriff Abdullah  * [ac3 , dts , linear , 0, linear, 0]
4590da2b28SAriff Abdullah  */
4690da2b28SAriff Abdullah #define FMTLIST_MAX		6
4790da2b28SAriff Abdullah #define FMTLIST_OFFSET		4
4890da2b28SAriff Abdullah #define DIGFMTS_MAX		2
49a580b31aSAriff Abdullah 
5090da2b28SAriff Abdullah #ifdef SND_DEBUG
5190da2b28SAriff Abdullah static int snd_passthrough_verbose = 0;
5232a0e5d5SHans Petter Selasky SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN,
5390da2b28SAriff Abdullah 	&snd_passthrough_verbose, 0, "passthrough verbosity");
5487506547SAlexander Leidinger 
5590da2b28SAriff Abdullah #endif
5690da2b28SAriff Abdullah 
5790da2b28SAriff Abdullah struct vchan_info {
58bba4862cSAriff Abdullah 	struct pcm_channel *channel;
59285648f9SCameron Grant 	struct pcmchan_caps caps;
6090da2b28SAriff Abdullah 	uint32_t fmtlist[FMTLIST_MAX];
61bba4862cSAriff Abdullah 	int trigger;
62285648f9SCameron Grant };
63285648f9SCameron Grant 
647ad5f383SChristos Margiolis int snd_maxautovchans = 16;
657ad5f383SChristos Margiolis 
66285648f9SCameron Grant static void *
67bba4862cSAriff Abdullah vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
68bba4862cSAriff Abdullah     struct pcm_channel *c, int dir)
69285648f9SCameron Grant {
7090da2b28SAriff Abdullah 	struct vchan_info *info;
7190da2b28SAriff Abdullah 	struct pcm_channel *p;
7290da2b28SAriff Abdullah 	uint32_t i, j, *fmtlist;
73285648f9SCameron Grant 
74bba4862cSAriff Abdullah 	KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
75bba4862cSAriff Abdullah 	    ("vchan_init: bad direction"));
76bba4862cSAriff Abdullah 	KASSERT(c != NULL && c->parentchannel != NULL,
77bba4862cSAriff Abdullah 	    ("vchan_init: bad channels"));
78bba4862cSAriff Abdullah 
7990da2b28SAriff Abdullah 	info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
8090da2b28SAriff Abdullah 	info->channel = c;
8190da2b28SAriff Abdullah 	info->trigger = PCMTRIG_STOP;
8290da2b28SAriff Abdullah 	p = c->parentchannel;
8390da2b28SAriff Abdullah 
8490da2b28SAriff Abdullah 	CHN_LOCK(p);
8590da2b28SAriff Abdullah 
8690da2b28SAriff Abdullah 	fmtlist = chn_getcaps(p)->fmtlist;
8790da2b28SAriff Abdullah 	for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
8890da2b28SAriff Abdullah 		if (fmtlist[i] & AFMT_PASSTHROUGH)
8990da2b28SAriff Abdullah 			info->fmtlist[j++] = fmtlist[i];
9090da2b28SAriff Abdullah 	}
9190da2b28SAriff Abdullah 	if (p->format & AFMT_VCHAN)
9290da2b28SAriff Abdullah 		info->fmtlist[j] = p->format;
9390da2b28SAriff Abdullah 	else
9490da2b28SAriff Abdullah 		info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
9590da2b28SAriff Abdullah 	info->caps.fmtlist = info->fmtlist +
9690da2b28SAriff Abdullah 	    ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
9790da2b28SAriff Abdullah 
9890da2b28SAriff Abdullah 	CHN_UNLOCK(p);
99285648f9SCameron Grant 
100285648f9SCameron Grant 	c->flags |= CHN_F_VIRTUAL;
101285648f9SCameron Grant 
10290da2b28SAriff Abdullah 	return (info);
103285648f9SCameron Grant }
104285648f9SCameron Grant 
105285648f9SCameron Grant static int
106285648f9SCameron Grant vchan_free(kobj_t obj, void *data)
107285648f9SCameron Grant {
10890da2b28SAriff Abdullah 
109e444a209SAriff Abdullah 	free(data, M_DEVBUF);
110bba4862cSAriff Abdullah 
111bba4862cSAriff Abdullah 	return (0);
112285648f9SCameron Grant }
113285648f9SCameron Grant 
114285648f9SCameron Grant static int
115a580b31aSAriff Abdullah vchan_setformat(kobj_t obj, void *data, uint32_t format)
116285648f9SCameron Grant {
11790da2b28SAriff Abdullah 	struct vchan_info *info;
118285648f9SCameron Grant 
11990da2b28SAriff Abdullah 	info = data;
12090da2b28SAriff Abdullah 
12190da2b28SAriff Abdullah 	CHN_LOCKASSERT(info->channel);
12290da2b28SAriff Abdullah 
12390da2b28SAriff Abdullah 	if (!snd_fmtvalid(format, info->caps.fmtlist))
124bba4862cSAriff Abdullah 		return (-1);
125bba4862cSAriff Abdullah 
126bba4862cSAriff Abdullah 	return (0);
127285648f9SCameron Grant }
128285648f9SCameron Grant 
12990da2b28SAriff Abdullah static uint32_t
130a580b31aSAriff Abdullah vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
131285648f9SCameron Grant {
13290da2b28SAriff Abdullah 	struct vchan_info *info;
133285648f9SCameron Grant 
13490da2b28SAriff Abdullah 	info = data;
13590da2b28SAriff Abdullah 
13690da2b28SAriff Abdullah 	CHN_LOCKASSERT(info->channel);
13790da2b28SAriff Abdullah 
13890da2b28SAriff Abdullah 	return (info->caps.maxspeed);
139285648f9SCameron Grant }
140285648f9SCameron Grant 
141285648f9SCameron Grant static int
142285648f9SCameron Grant vchan_trigger(kobj_t obj, void *data, int go)
143285648f9SCameron Grant {
14490da2b28SAriff Abdullah 	struct vchan_info *info;
145bba4862cSAriff Abdullah 	struct pcm_channel *c, *p;
14690da2b28SAriff Abdullah 	int ret, otrigger;
147285648f9SCameron Grant 
14890da2b28SAriff Abdullah 	info = data;
14990da2b28SAriff Abdullah 
15090da2b28SAriff Abdullah 	if (!PCMTRIG_COMMON(go) || go == info->trigger)
151bba4862cSAriff Abdullah 		return (0);
152285648f9SCameron Grant 
15390da2b28SAriff Abdullah 	c = info->channel;
154bba4862cSAriff Abdullah 	p = c->parentchannel;
15590da2b28SAriff Abdullah 	otrigger = info->trigger;
15690da2b28SAriff Abdullah 	info->trigger = go;
15790da2b28SAriff Abdullah 
15890da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
159285648f9SCameron Grant 
160bba4862cSAriff Abdullah 	CHN_UNLOCK(c);
161bba4862cSAriff Abdullah 	CHN_LOCK(p);
162bba4862cSAriff Abdullah 
163bba4862cSAriff Abdullah 	switch (go) {
164bba4862cSAriff Abdullah 	case PCMTRIG_START:
16590da2b28SAriff Abdullah 		if (otrigger != PCMTRIG_START)
166bba4862cSAriff Abdullah 			CHN_INSERT_HEAD(p, c, children.busy);
167bba4862cSAriff Abdullah 		break;
168bba4862cSAriff Abdullah 	case PCMTRIG_STOP:
169bba4862cSAriff Abdullah 	case PCMTRIG_ABORT:
17090da2b28SAriff Abdullah 		if (otrigger == PCMTRIG_START)
171bba4862cSAriff Abdullah 			CHN_REMOVE(p, c, children.busy);
172bba4862cSAriff Abdullah 		break;
173bba4862cSAriff Abdullah 	default:
174bba4862cSAriff Abdullah 		break;
175bba4862cSAriff Abdullah 	}
176bba4862cSAriff Abdullah 
17790da2b28SAriff Abdullah 	ret = chn_notify(p, CHN_N_TRIGGER);
17890da2b28SAriff Abdullah 
17990da2b28SAriff Abdullah 	CHN_LOCK(c);
18090da2b28SAriff Abdullah 
18190da2b28SAriff Abdullah 	if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
18290da2b28SAriff Abdullah 		ret = vchan_sync(c);
18390da2b28SAriff Abdullah 
18490da2b28SAriff Abdullah 	CHN_UNLOCK(c);
185bba4862cSAriff Abdullah 	CHN_UNLOCK(p);
186bba4862cSAriff Abdullah 	CHN_LOCK(c);
187bba4862cSAriff Abdullah 
18890da2b28SAriff Abdullah 	return (ret);
189285648f9SCameron Grant }
190285648f9SCameron Grant 
191285648f9SCameron Grant static struct pcmchan_caps *
192285648f9SCameron Grant vchan_getcaps(kobj_t obj, void *data)
193285648f9SCameron Grant {
19490da2b28SAriff Abdullah 	struct vchan_info *info;
19590da2b28SAriff Abdullah 	struct pcm_channel *c;
19690da2b28SAriff Abdullah 	uint32_t pformat, pspeed, pflags, i;
197285648f9SCameron Grant 
19890da2b28SAriff Abdullah 	info = data;
19990da2b28SAriff Abdullah 	c = info->channel;
20090da2b28SAriff Abdullah 	pformat = c->parentchannel->format;
20190da2b28SAriff Abdullah 	pspeed = c->parentchannel->speed;
20290da2b28SAriff Abdullah 	pflags = c->parentchannel->flags;
20390da2b28SAriff Abdullah 
20490da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
20590da2b28SAriff Abdullah 
20690da2b28SAriff Abdullah 	if (pflags & CHN_F_VCHAN_DYNAMIC) {
20790da2b28SAriff Abdullah 		info->caps.fmtlist = info->fmtlist;
20890da2b28SAriff Abdullah 		if (pformat & AFMT_VCHAN) {
20990da2b28SAriff Abdullah 			for (i = 0; info->caps.fmtlist[i] != 0; i++) {
21090da2b28SAriff Abdullah 				if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
21190da2b28SAriff Abdullah 					continue;
21290da2b28SAriff Abdullah 				break;
213a580b31aSAriff Abdullah 			}
21490da2b28SAriff Abdullah 			info->caps.fmtlist[i] = pformat;
21590da2b28SAriff Abdullah 		}
21690da2b28SAriff Abdullah 		if (c->format & AFMT_PASSTHROUGH)
21790da2b28SAriff Abdullah 			info->caps.minspeed = c->speed;
21890da2b28SAriff Abdullah 		else
21990da2b28SAriff Abdullah 			info->caps.minspeed = pspeed;
22090da2b28SAriff Abdullah 		info->caps.maxspeed = info->caps.minspeed;
22190da2b28SAriff Abdullah 	} else {
22290da2b28SAriff Abdullah 		info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
22390da2b28SAriff Abdullah 		if (pformat & AFMT_VCHAN)
22490da2b28SAriff Abdullah 			info->caps.fmtlist[0] = pformat;
22590da2b28SAriff Abdullah 		else {
22690da2b28SAriff Abdullah 			device_printf(c->dev,
22790da2b28SAriff Abdullah 			    "%s(): invalid vchan format 0x%08x",
22890da2b28SAriff Abdullah 			    __func__, pformat);
22990da2b28SAriff Abdullah 			info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
23090da2b28SAriff Abdullah 		}
23190da2b28SAriff Abdullah 		info->caps.minspeed = pspeed;
23290da2b28SAriff Abdullah 		info->caps.maxspeed = info->caps.minspeed;
23390da2b28SAriff Abdullah 	}
234285648f9SCameron Grant 
23590da2b28SAriff Abdullah 	return (&info->caps);
23690da2b28SAriff Abdullah }
23790da2b28SAriff Abdullah 
23890da2b28SAriff Abdullah static struct pcmchan_matrix *
23990da2b28SAriff Abdullah vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
24090da2b28SAriff Abdullah {
24190da2b28SAriff Abdullah 
24290da2b28SAriff Abdullah 	return (feeder_matrix_format_map(format));
243285648f9SCameron Grant }
244285648f9SCameron Grant 
245285648f9SCameron Grant static kobj_method_t vchan_methods[] = {
246285648f9SCameron Grant 	KOBJMETHOD(channel_init,		vchan_init),
247285648f9SCameron Grant 	KOBJMETHOD(channel_free,		vchan_free),
248285648f9SCameron Grant 	KOBJMETHOD(channel_setformat,		vchan_setformat),
249285648f9SCameron Grant 	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
250285648f9SCameron Grant 	KOBJMETHOD(channel_trigger,		vchan_trigger),
251285648f9SCameron Grant 	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
25290da2b28SAriff Abdullah 	KOBJMETHOD(channel_getmatrix,		vchan_getmatrix),
25390da2b28SAriff Abdullah 	KOBJMETHOD_END
254285648f9SCameron Grant };
255285648f9SCameron Grant CHANNEL_DECLARE(vchan);
256285648f9SCameron Grant 
25790da2b28SAriff Abdullah static void
25877ab4263SChristos Margiolis vchan_getparentchannel(struct snddev_info *d,
25990da2b28SAriff Abdullah     struct pcm_channel **wrch, struct pcm_channel **rdch)
26090da2b28SAriff Abdullah {
26190da2b28SAriff Abdullah 	struct pcm_channel **ch, *wch, *rch, *c;
26290da2b28SAriff Abdullah 
26390da2b28SAriff Abdullah 	KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__));
26490da2b28SAriff Abdullah 
26590da2b28SAriff Abdullah 	PCM_BUSYASSERT(d);
26690da2b28SAriff Abdullah 	PCM_UNLOCKASSERT(d);
26790da2b28SAriff Abdullah 
26890da2b28SAriff Abdullah 	wch = NULL;
26990da2b28SAriff Abdullah 	rch = NULL;
27090da2b28SAriff Abdullah 
27190da2b28SAriff Abdullah 	CHN_FOREACH(c, d, channels.pcm) {
27290da2b28SAriff Abdullah 		CHN_LOCK(c);
27390da2b28SAriff Abdullah 		ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch;
27490da2b28SAriff Abdullah 		if (c->flags & CHN_F_VIRTUAL) {
27590da2b28SAriff Abdullah 			/* Sanity check */
27690da2b28SAriff Abdullah 			if (*ch != NULL && *ch != c->parentchannel) {
27790da2b28SAriff Abdullah 				CHN_UNLOCK(c);
27890da2b28SAriff Abdullah 				*ch = NULL;
27990da2b28SAriff Abdullah 				break;
28090da2b28SAriff Abdullah 			}
28190da2b28SAriff Abdullah 		} else if (c->flags & CHN_F_HAS_VCHAN) {
28290da2b28SAriff Abdullah 			/* No way!! */
28390da2b28SAriff Abdullah 			if (*ch != NULL) {
28490da2b28SAriff Abdullah 				CHN_UNLOCK(c);
28590da2b28SAriff Abdullah 				*ch = NULL;
28690da2b28SAriff Abdullah 				break;
28790da2b28SAriff Abdullah 			}
28890da2b28SAriff Abdullah 			*ch = c;
28990da2b28SAriff Abdullah 		}
29090da2b28SAriff Abdullah 		CHN_UNLOCK(c);
29190da2b28SAriff Abdullah 	}
29290da2b28SAriff Abdullah 
29390da2b28SAriff Abdullah 	if (wrch != NULL)
29490da2b28SAriff Abdullah 		*wrch = wch;
29590da2b28SAriff Abdullah 	if (rdch != NULL)
29690da2b28SAriff Abdullah 		*rdch = rch;
29790da2b28SAriff Abdullah }
29890da2b28SAriff Abdullah 
29987506547SAlexander Leidinger static int
30090da2b28SAriff Abdullah sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
30187506547SAlexander Leidinger {
30287506547SAlexander Leidinger 	struct snddev_info *d;
30390da2b28SAriff Abdullah 	int direction, vchancount;
30490da2b28SAriff Abdullah 	int err, cnt;
30587506547SAlexander Leidinger 
306bba4862cSAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
307e4e61333SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
308bba4862cSAriff Abdullah 		return (EINVAL);
309bba4862cSAriff Abdullah 
31090da2b28SAriff Abdullah 	PCM_LOCK(d);
31190da2b28SAriff Abdullah 	PCM_WAIT(d);
31290da2b28SAriff Abdullah 
31390da2b28SAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
31490da2b28SAriff Abdullah 	case VCHAN_PLAY:
31590da2b28SAriff Abdullah 		direction = PCMDIR_PLAY;
31690da2b28SAriff Abdullah 		vchancount = d->pvchancount;
31790da2b28SAriff Abdullah 		cnt = d->playcount;
31890da2b28SAriff Abdullah 		break;
31990da2b28SAriff Abdullah 	case VCHAN_REC:
32090da2b28SAriff Abdullah 		direction = PCMDIR_REC;
32190da2b28SAriff Abdullah 		vchancount = d->rvchancount;
32290da2b28SAriff Abdullah 		cnt = d->reccount;
32390da2b28SAriff Abdullah 		break;
32490da2b28SAriff Abdullah 	default:
32590da2b28SAriff Abdullah 		PCM_UNLOCK(d);
32690da2b28SAriff Abdullah 		return (EINVAL);
32790da2b28SAriff Abdullah 		break;
32890da2b28SAriff Abdullah 	}
32990da2b28SAriff Abdullah 
33090da2b28SAriff Abdullah 	if (cnt < 1) {
33190da2b28SAriff Abdullah 		PCM_UNLOCK(d);
33290da2b28SAriff Abdullah 		return (ENODEV);
33390da2b28SAriff Abdullah 	}
33490da2b28SAriff Abdullah 
33590da2b28SAriff Abdullah 	PCM_ACQUIRE(d);
33690da2b28SAriff Abdullah 	PCM_UNLOCK(d);
33790da2b28SAriff Abdullah 
33890da2b28SAriff Abdullah 	cnt = vchancount;
33990da2b28SAriff Abdullah 	err = sysctl_handle_int(oidp, &cnt, 0, req);
34090da2b28SAriff Abdullah 
34190da2b28SAriff Abdullah 	if (err == 0 && req->newptr != NULL && vchancount != cnt) {
34290da2b28SAriff Abdullah 		if (cnt < 0)
34390da2b28SAriff Abdullah 			cnt = 0;
34490da2b28SAriff Abdullah 		if (cnt > SND_MAXVCHANS)
34590da2b28SAriff Abdullah 			cnt = SND_MAXVCHANS;
3463af2beb8SChristos Margiolis 		err = vchan_setnew(d, direction, cnt);
34790da2b28SAriff Abdullah 	}
34890da2b28SAriff Abdullah 
34990da2b28SAriff Abdullah 	PCM_RELEASE_QUICK(d);
35090da2b28SAriff Abdullah 
35190da2b28SAriff Abdullah 	return err;
35290da2b28SAriff Abdullah }
35390da2b28SAriff Abdullah 
35490da2b28SAriff Abdullah static int
35590da2b28SAriff Abdullah sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
35690da2b28SAriff Abdullah {
35790da2b28SAriff Abdullah 	struct snddev_info *d;
35890da2b28SAriff Abdullah 	struct pcm_channel *c;
35990da2b28SAriff Abdullah 	uint32_t dflags;
36090da2b28SAriff Abdullah 	int direction, ret;
36190da2b28SAriff Abdullah 	char dtype[16];
36290da2b28SAriff Abdullah 
36390da2b28SAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
36490da2b28SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
36590da2b28SAriff Abdullah 		return (EINVAL);
36690da2b28SAriff Abdullah 
36790da2b28SAriff Abdullah 	PCM_LOCK(d);
36890da2b28SAriff Abdullah 	PCM_WAIT(d);
36990da2b28SAriff Abdullah 
37090da2b28SAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
37190da2b28SAriff Abdullah 	case VCHAN_PLAY:
37290da2b28SAriff Abdullah 		direction = PCMDIR_PLAY;
37390da2b28SAriff Abdullah 		break;
37490da2b28SAriff Abdullah 	case VCHAN_REC:
37590da2b28SAriff Abdullah 		direction = PCMDIR_REC;
37690da2b28SAriff Abdullah 		break;
37790da2b28SAriff Abdullah 	default:
37890da2b28SAriff Abdullah 		PCM_UNLOCK(d);
37990da2b28SAriff Abdullah 		return (EINVAL);
38090da2b28SAriff Abdullah 		break;
38190da2b28SAriff Abdullah 	}
38290da2b28SAriff Abdullah 
38390da2b28SAriff Abdullah 	PCM_ACQUIRE(d);
38490da2b28SAriff Abdullah 	PCM_UNLOCK(d);
38590da2b28SAriff Abdullah 
38690da2b28SAriff Abdullah 	if (direction == PCMDIR_PLAY)
38777ab4263SChristos Margiolis 		vchan_getparentchannel(d, &c, NULL);
38890da2b28SAriff Abdullah 	else
38977ab4263SChristos Margiolis 		vchan_getparentchannel(d, NULL, &c);
39090da2b28SAriff Abdullah 
39190da2b28SAriff Abdullah 	if (c == NULL) {
39290da2b28SAriff Abdullah 		PCM_RELEASE_QUICK(d);
39390da2b28SAriff Abdullah 		return (EINVAL);
39490da2b28SAriff Abdullah 	}
39590da2b28SAriff Abdullah 
39690da2b28SAriff Abdullah 	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
39790da2b28SAriff Abdullah 	    __func__, direction, c->direction));
39890da2b28SAriff Abdullah 
39990da2b28SAriff Abdullah 	CHN_LOCK(c);
40090da2b28SAriff Abdullah 	if (c->flags & CHN_F_VCHAN_PASSTHROUGH)
40190da2b28SAriff Abdullah 		strlcpy(dtype, "passthrough", sizeof(dtype));
40290da2b28SAriff Abdullah 	else if (c->flags & CHN_F_VCHAN_ADAPTIVE)
40390da2b28SAriff Abdullah 		strlcpy(dtype, "adaptive", sizeof(dtype));
40490da2b28SAriff Abdullah 	else
40590da2b28SAriff Abdullah 		strlcpy(dtype, "fixed", sizeof(dtype));
40690da2b28SAriff Abdullah 	CHN_UNLOCK(c);
40790da2b28SAriff Abdullah 
40890da2b28SAriff Abdullah 	ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
40990da2b28SAriff Abdullah 	if (ret == 0 && req->newptr != NULL) {
41090da2b28SAriff Abdullah 		if (strcasecmp(dtype, "passthrough") == 0 ||
41190da2b28SAriff Abdullah 		    strcmp(dtype, "1") == 0)
41290da2b28SAriff Abdullah 			dflags = CHN_F_VCHAN_PASSTHROUGH;
41390da2b28SAriff Abdullah 		else if (strcasecmp(dtype, "adaptive") == 0 ||
41490da2b28SAriff Abdullah 		    strcmp(dtype, "2") == 0)
41590da2b28SAriff Abdullah 			dflags = CHN_F_VCHAN_ADAPTIVE;
41690da2b28SAriff Abdullah 		else if (strcasecmp(dtype, "fixed") == 0 ||
41790da2b28SAriff Abdullah 		    strcmp(dtype, "0") == 0)
41890da2b28SAriff Abdullah 			dflags = 0;
41990da2b28SAriff Abdullah 		else {
42090da2b28SAriff Abdullah 			PCM_RELEASE_QUICK(d);
42190da2b28SAriff Abdullah 			return (EINVAL);
42290da2b28SAriff Abdullah 		}
42390da2b28SAriff Abdullah 		CHN_LOCK(c);
42490da2b28SAriff Abdullah 		if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
42590da2b28SAriff Abdullah 		    (c->flags & CHN_F_PASSTHROUGH)) {
42690da2b28SAriff Abdullah 			CHN_UNLOCK(c);
42790da2b28SAriff Abdullah 			PCM_RELEASE_QUICK(d);
42890da2b28SAriff Abdullah 			return (0);
42990da2b28SAriff Abdullah 		}
43090da2b28SAriff Abdullah 		c->flags &= ~CHN_F_VCHAN_DYNAMIC;
43190da2b28SAriff Abdullah 		c->flags |= dflags;
43290da2b28SAriff Abdullah 		CHN_UNLOCK(c);
43390da2b28SAriff Abdullah 	}
43490da2b28SAriff Abdullah 
43590da2b28SAriff Abdullah 	PCM_RELEASE_QUICK(d);
43690da2b28SAriff Abdullah 
43790da2b28SAriff Abdullah 	return (ret);
43890da2b28SAriff Abdullah }
43990da2b28SAriff Abdullah 
44090da2b28SAriff Abdullah /*
44190da2b28SAriff Abdullah  * On the fly vchan rate/format settings
44290da2b28SAriff Abdullah  */
44390da2b28SAriff Abdullah 
44490da2b28SAriff Abdullah #define VCHAN_ACCESSIBLE(c)	(!((c)->flags & (CHN_F_PASSTHROUGH |	\
44590da2b28SAriff Abdullah 				 CHN_F_EXCLUSIVE)) &&			\
44690da2b28SAriff Abdullah 				 (((c)->flags & CHN_F_VCHAN_DYNAMIC) ||	\
44790da2b28SAriff Abdullah 				 CHN_STOPPED(c)))
44890da2b28SAriff Abdullah static int
44990da2b28SAriff Abdullah sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
45090da2b28SAriff Abdullah {
45190da2b28SAriff Abdullah 	struct snddev_info *d;
45290da2b28SAriff Abdullah 	struct pcm_channel *c, *ch;
45390da2b28SAriff Abdullah 	struct pcmchan_caps *caps;
45490da2b28SAriff Abdullah 	int *vchanrate, vchancount, direction, ret, newspd, restart;
45590da2b28SAriff Abdullah 
45690da2b28SAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
45790da2b28SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
45890da2b28SAriff Abdullah 		return (EINVAL);
45990da2b28SAriff Abdullah 
46090da2b28SAriff Abdullah 	PCM_LOCK(d);
461e4e61333SAriff Abdullah 	PCM_WAIT(d);
462e4e61333SAriff Abdullah 
463bba4862cSAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
464bba4862cSAriff Abdullah 	case VCHAN_PLAY:
465bba4862cSAriff Abdullah 		direction = PCMDIR_PLAY;
466bba4862cSAriff Abdullah 		vchancount = d->pvchancount;
467bba4862cSAriff Abdullah 		vchanrate = &d->pvchanrate;
468bba4862cSAriff Abdullah 		break;
469bba4862cSAriff Abdullah 	case VCHAN_REC:
470bba4862cSAriff Abdullah 		direction = PCMDIR_REC;
471bba4862cSAriff Abdullah 		vchancount = d->rvchancount;
472bba4862cSAriff Abdullah 		vchanrate = &d->rvchanrate;
473bba4862cSAriff Abdullah 		break;
474bba4862cSAriff Abdullah 	default:
47590da2b28SAriff Abdullah 		PCM_UNLOCK(d);
476bba4862cSAriff Abdullah 		return (EINVAL);
477bba4862cSAriff Abdullah 		break;
478bba4862cSAriff Abdullah 	}
479bba4862cSAriff Abdullah 
480e4e61333SAriff Abdullah 	if (vchancount < 1) {
48190da2b28SAriff Abdullah 		PCM_UNLOCK(d);
482bba4862cSAriff Abdullah 		return (EINVAL);
4839c271ebaSAriff Abdullah 	}
484e4e61333SAriff Abdullah 
485e4e61333SAriff Abdullah 	PCM_ACQUIRE(d);
48690da2b28SAriff Abdullah 	PCM_UNLOCK(d);
487e4e61333SAriff Abdullah 
48890da2b28SAriff Abdullah 	if (direction == PCMDIR_PLAY)
48977ab4263SChristos Margiolis 		vchan_getparentchannel(d, &c, NULL);
49090da2b28SAriff Abdullah 	else
49177ab4263SChristos Margiolis 		vchan_getparentchannel(d, NULL, &c);
492e4e61333SAriff Abdullah 
49390da2b28SAriff Abdullah 	if (c == NULL) {
49490da2b28SAriff Abdullah 		PCM_RELEASE_QUICK(d);
49590da2b28SAriff Abdullah 		return (EINVAL);
49690da2b28SAriff Abdullah 	}
49790da2b28SAriff Abdullah 
49890da2b28SAriff Abdullah 	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
49990da2b28SAriff Abdullah 	    __func__, direction, c->direction));
50090da2b28SAriff Abdullah 
50187506547SAlexander Leidinger 	CHN_LOCK(c);
50290da2b28SAriff Abdullah 	newspd = c->speed;
5039c271ebaSAriff Abdullah 	CHN_UNLOCK(c);
50490da2b28SAriff Abdullah 
50590da2b28SAriff Abdullah 	ret = sysctl_handle_int(oidp, &newspd, 0, req);
50690da2b28SAriff Abdullah 	if (ret != 0 || req->newptr == NULL) {
507e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
50890da2b28SAriff Abdullah 		return (ret);
50987506547SAlexander Leidinger 	}
510e4e61333SAriff Abdullah 
511a580b31aSAriff Abdullah 	if (newspd < 1 || newspd < feeder_rate_min ||
512a580b31aSAriff Abdullah 	    newspd > feeder_rate_max) {
513e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
514bba4862cSAriff Abdullah 		return (EINVAL);
51597d69a96SAlexander Leidinger 	}
51690da2b28SAriff Abdullah 
51790da2b28SAriff Abdullah 	CHN_LOCK(c);
51890da2b28SAriff Abdullah 
51990da2b28SAriff Abdullah 	if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
52090da2b28SAriff Abdullah 		if (CHN_STARTED(c)) {
52190da2b28SAriff Abdullah 			chn_abort(c);
52290da2b28SAriff Abdullah 			restart = 1;
52390da2b28SAriff Abdullah 		} else
52490da2b28SAriff Abdullah 			restart = 0;
52590da2b28SAriff Abdullah 
526a580b31aSAriff Abdullah 		if (feeder_rate_round) {
52790da2b28SAriff Abdullah 			caps = chn_getcaps(c);
52890da2b28SAriff Abdullah 			RANGE(newspd, caps->minspeed, caps->maxspeed);
52990da2b28SAriff Abdullah 			newspd = CHANNEL_SETSPEED(c->methods,
53090da2b28SAriff Abdullah 			    c->devinfo, newspd);
53187506547SAlexander Leidinger 		}
53290da2b28SAriff Abdullah 
53390da2b28SAriff Abdullah 		ret = chn_reset(c, c->format, newspd);
53490da2b28SAriff Abdullah 		if (ret == 0) {
53590da2b28SAriff Abdullah 			*vchanrate = c->speed;
53690da2b28SAriff Abdullah 			if (restart != 0) {
53790da2b28SAriff Abdullah 				CHN_FOREACH(ch, c, children.busy) {
53890da2b28SAriff Abdullah 					CHN_LOCK(ch);
53990da2b28SAriff Abdullah 					if (VCHAN_SYNC_REQUIRED(ch))
54090da2b28SAriff Abdullah 						vchan_sync(ch);
54197d69a96SAlexander Leidinger 					CHN_UNLOCK(ch);
54297d69a96SAlexander Leidinger 				}
54390da2b28SAriff Abdullah 				c->flags |= CHN_F_DIRTY;
54490da2b28SAriff Abdullah 				ret = chn_start(c, 1);
54590da2b28SAriff Abdullah 			}
54690da2b28SAriff Abdullah 		}
54790da2b28SAriff Abdullah 	}
54890da2b28SAriff Abdullah 
54990da2b28SAriff Abdullah 	CHN_UNLOCK(c);
550e4e61333SAriff Abdullah 
551e4e61333SAriff Abdullah 	PCM_RELEASE_QUICK(d);
552e4e61333SAriff Abdullah 
55390da2b28SAriff Abdullah 	return (ret);
55487506547SAlexander Leidinger }
555a580b31aSAriff Abdullah 
556a580b31aSAriff Abdullah static int
55790da2b28SAriff Abdullah sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
558a580b31aSAriff Abdullah {
559a580b31aSAriff Abdullah 	struct snddev_info *d;
56090da2b28SAriff Abdullah 	struct pcm_channel *c, *ch;
56190da2b28SAriff Abdullah 	uint32_t newfmt;
56290da2b28SAriff Abdullah 	int *vchanformat, vchancount, direction, ret, restart;
56390da2b28SAriff Abdullah 	char fmtstr[AFMTSTR_LEN];
564a580b31aSAriff Abdullah 
565bba4862cSAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
566e4e61333SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
567bba4862cSAriff Abdullah 		return (EINVAL);
568bba4862cSAriff Abdullah 
56990da2b28SAriff Abdullah 	PCM_LOCK(d);
570e4e61333SAriff Abdullah 	PCM_WAIT(d);
571e4e61333SAriff Abdullah 
572bba4862cSAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
573bba4862cSAriff Abdullah 	case VCHAN_PLAY:
574bba4862cSAriff Abdullah 		direction = PCMDIR_PLAY;
575bba4862cSAriff Abdullah 		vchancount = d->pvchancount;
576bba4862cSAriff Abdullah 		vchanformat = &d->pvchanformat;
577bba4862cSAriff Abdullah 		break;
578bba4862cSAriff Abdullah 	case VCHAN_REC:
579bba4862cSAriff Abdullah 		direction = PCMDIR_REC;
580bba4862cSAriff Abdullah 		vchancount = d->rvchancount;
581bba4862cSAriff Abdullah 		vchanformat = &d->rvchanformat;
582bba4862cSAriff Abdullah 		break;
583bba4862cSAriff Abdullah 	default:
58490da2b28SAriff Abdullah 		PCM_UNLOCK(d);
585bba4862cSAriff Abdullah 		return (EINVAL);
586bba4862cSAriff Abdullah 		break;
587bba4862cSAriff Abdullah 	}
588bba4862cSAriff Abdullah 
589e4e61333SAriff Abdullah 	if (vchancount < 1) {
59090da2b28SAriff Abdullah 		PCM_UNLOCK(d);
591bba4862cSAriff Abdullah 		return (EINVAL);
592a580b31aSAriff Abdullah 	}
593e4e61333SAriff Abdullah 
594e4e61333SAriff Abdullah 	PCM_ACQUIRE(d);
59590da2b28SAriff Abdullah 	PCM_UNLOCK(d);
596e4e61333SAriff Abdullah 
59790da2b28SAriff Abdullah 	if (direction == PCMDIR_PLAY)
59877ab4263SChristos Margiolis 		vchan_getparentchannel(d, &c, NULL);
59990da2b28SAriff Abdullah 	else
60077ab4263SChristos Margiolis 		vchan_getparentchannel(d, NULL, &c);
60190da2b28SAriff Abdullah 
60290da2b28SAriff Abdullah 	if (c == NULL) {
60390da2b28SAriff Abdullah 		PCM_RELEASE_QUICK(d);
60490da2b28SAriff Abdullah 		return (EINVAL);
60590da2b28SAriff Abdullah 	}
60690da2b28SAriff Abdullah 
60790da2b28SAriff Abdullah 	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
60890da2b28SAriff Abdullah 	    __func__, direction, c->direction));
60990da2b28SAriff Abdullah 
610a580b31aSAriff Abdullah 	CHN_LOCK(c);
61190da2b28SAriff Abdullah 
61290da2b28SAriff Abdullah 	bzero(fmtstr, sizeof(fmtstr));
61390da2b28SAriff Abdullah 
61490da2b28SAriff Abdullah 	if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format)
61590da2b28SAriff Abdullah 		strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
61690da2b28SAriff Abdullah 
617a580b31aSAriff Abdullah 	CHN_UNLOCK(c);
61890da2b28SAriff Abdullah 
61990da2b28SAriff Abdullah 	ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
62090da2b28SAriff Abdullah 	if (ret != 0 || req->newptr == NULL) {
621e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
62290da2b28SAriff Abdullah 		return (ret);
623a580b31aSAriff Abdullah 	}
62490da2b28SAriff Abdullah 
62590da2b28SAriff Abdullah 	newfmt = snd_str2afmt(fmtstr);
62690da2b28SAriff Abdullah 	if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
627e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
628bba4862cSAriff Abdullah 		return (EINVAL);
629a580b31aSAriff Abdullah 	}
630e4e61333SAriff Abdullah 
63190da2b28SAriff Abdullah 	CHN_LOCK(c);
63290da2b28SAriff Abdullah 
63390da2b28SAriff Abdullah 	if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
63490da2b28SAriff Abdullah 		if (CHN_STARTED(c)) {
63590da2b28SAriff Abdullah 			chn_abort(c);
63690da2b28SAriff Abdullah 			restart = 1;
63790da2b28SAriff Abdullah 		} else
63890da2b28SAriff Abdullah 			restart = 0;
63990da2b28SAriff Abdullah 
64090da2b28SAriff Abdullah 		ret = chn_reset(c, newfmt, c->speed);
64190da2b28SAriff Abdullah 		if (ret == 0) {
64290da2b28SAriff Abdullah 			*vchanformat = c->format;
64390da2b28SAriff Abdullah 			if (restart != 0) {
64490da2b28SAriff Abdullah 				CHN_FOREACH(ch, c, children.busy) {
645a580b31aSAriff Abdullah 					CHN_LOCK(ch);
64690da2b28SAriff Abdullah 					if (VCHAN_SYNC_REQUIRED(ch))
64790da2b28SAriff Abdullah 						vchan_sync(ch);
648a580b31aSAriff Abdullah 					CHN_UNLOCK(ch);
649a580b31aSAriff Abdullah 				}
65090da2b28SAriff Abdullah 				c->flags |= CHN_F_DIRTY;
65190da2b28SAriff Abdullah 				ret = chn_start(c, 1);
65290da2b28SAriff Abdullah 			}
65390da2b28SAriff Abdullah 		}
65490da2b28SAriff Abdullah 	}
65590da2b28SAriff Abdullah 
65690da2b28SAriff Abdullah 	CHN_UNLOCK(c);
657e4e61333SAriff Abdullah 
658e4e61333SAriff Abdullah 	PCM_RELEASE_QUICK(d);
659e4e61333SAriff Abdullah 
66090da2b28SAriff Abdullah 	return (ret);
661a580b31aSAriff Abdullah }
66287506547SAlexander Leidinger 
663285648f9SCameron Grant /* virtual channel interface */
664285648f9SCameron Grant 
665bba4862cSAriff Abdullah #define VCHAN_FMT_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
666bba4862cSAriff Abdullah 				"play.vchanformat" : "rec.vchanformat"
667bba4862cSAriff Abdullah #define VCHAN_SPD_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
668bba4862cSAriff Abdullah 				"play.vchanrate" : "rec.vchanrate"
669bba4862cSAriff Abdullah 
670285648f9SCameron Grant int
6713af2beb8SChristos Margiolis vchan_create(struct pcm_channel *parent)
672285648f9SCameron Grant {
67390da2b28SAriff Abdullah 	struct snddev_info *d;
67490da2b28SAriff Abdullah 	struct pcm_channel *ch;
67597d69a96SAlexander Leidinger 	struct pcmchan_caps *parent_caps;
67690da2b28SAriff Abdullah 	uint32_t vchanfmt, vchanspd;
67790da2b28SAriff Abdullah 	int ret, direction, r, save;
67890da2b28SAriff Abdullah 
67990da2b28SAriff Abdullah 	d = parent->parentsnddev;
68097d69a96SAlexander Leidinger 
681e4e61333SAriff Abdullah 	PCM_BUSYASSERT(d);
68290da2b28SAriff Abdullah 	CHN_LOCKASSERT(parent);
683e4e61333SAriff Abdullah 
68497d69a96SAlexander Leidinger 	if (!(parent->flags & CHN_F_BUSY))
685bba4862cSAriff Abdullah 		return (EBUSY);
68697d69a96SAlexander Leidinger 
68790da2b28SAriff Abdullah 	if (!(parent->direction == PCMDIR_PLAY ||
68890da2b28SAriff Abdullah 	    parent->direction == PCMDIR_REC))
68990da2b28SAriff Abdullah 		return (EINVAL);
69090da2b28SAriff Abdullah 
69190da2b28SAriff Abdullah 	d = parent->parentsnddev;
69290da2b28SAriff Abdullah 
69390da2b28SAriff Abdullah 	CHN_UNLOCK(parent);
69490da2b28SAriff Abdullah 	PCM_LOCK(d);
69590da2b28SAriff Abdullah 
696bba4862cSAriff Abdullah 	if (parent->direction == PCMDIR_PLAY) {
697bba4862cSAriff Abdullah 		direction = PCMDIR_PLAY_VIRTUAL;
698bba4862cSAriff Abdullah 		vchanfmt = d->pvchanformat;
69990da2b28SAriff Abdullah 		vchanspd = d->pvchanrate;
70090da2b28SAriff Abdullah 	} else {
701bba4862cSAriff Abdullah 		direction = PCMDIR_REC_VIRTUAL;
702bba4862cSAriff Abdullah 		vchanfmt = d->rvchanformat;
70390da2b28SAriff Abdullah 		vchanspd = d->rvchanrate;
70490da2b28SAriff Abdullah 	}
70512e524a2SDon Lewis 
706285648f9SCameron Grant 	/* create a new playback channel */
7073af2beb8SChristos Margiolis 	ch = chn_init(d, parent, &vchan_class, direction, parent);
708bba4862cSAriff Abdullah 	if (ch == NULL) {
70990da2b28SAriff Abdullah 		PCM_UNLOCK(d);
71012e524a2SDon Lewis 		CHN_LOCK(parent);
711bba4862cSAriff Abdullah 		return (ENODEV);
712285648f9SCameron Grant 	}
713285648f9SCameron Grant 
714285648f9SCameron Grant 	/* add us to our grandparent's channel list */
715139bcec8SChristos Margiolis 	pcm_chn_add(d, ch);
71690da2b28SAriff Abdullah 	PCM_UNLOCK(d);
717285648f9SCameron Grant 
71812e524a2SDon Lewis 	CHN_LOCK(parent);
71990da2b28SAriff Abdullah 	/*
72090da2b28SAriff Abdullah 	 * Add us to our parent channel's children in reverse order
72190da2b28SAriff Abdullah 	 * so future destruction will pick the last (biggest number)
72290da2b28SAriff Abdullah 	 * channel.
72390da2b28SAriff Abdullah 	 */
72490da2b28SAriff Abdullah 	CHN_INSERT_SORT_DESCEND(parent, ch, children);
72590da2b28SAriff Abdullah 
72690da2b28SAriff Abdullah 	if (parent->flags & CHN_F_HAS_VCHAN)
72790da2b28SAriff Abdullah 		return (0);
72890da2b28SAriff Abdullah 
72997d69a96SAlexander Leidinger 	parent->flags |= CHN_F_HAS_VCHAN;
73087506547SAlexander Leidinger 
731139bcec8SChristos Margiolis 	ret = 0;
73297d69a96SAlexander Leidinger 	parent_caps = chn_getcaps(parent);
73397d69a96SAlexander Leidinger 	if (parent_caps == NULL)
73490da2b28SAriff Abdullah 		ret = EINVAL;
73597d69a96SAlexander Leidinger 
73690da2b28SAriff Abdullah 	save = 0;
73790da2b28SAriff Abdullah 
73890da2b28SAriff Abdullah 	if (ret == 0 && vchanfmt == 0) {
739a580b31aSAriff Abdullah 		const char *vfmt;
740a580b31aSAriff Abdullah 
741a580b31aSAriff Abdullah 		CHN_UNLOCK(parent);
74290da2b28SAriff Abdullah 		r = resource_string_value(device_get_name(parent->dev),
74390da2b28SAriff Abdullah 		    device_get_unit(parent->dev), VCHAN_FMT_HINT(direction),
744bba4862cSAriff Abdullah 		    &vfmt);
745a580b31aSAriff Abdullah 		CHN_LOCK(parent);
746a580b31aSAriff Abdullah 		if (r != 0)
747a580b31aSAriff Abdullah 			vfmt = NULL;
748a580b31aSAriff Abdullah 		if (vfmt != NULL) {
74990da2b28SAriff Abdullah 			vchanfmt = snd_str2afmt(vfmt);
75090da2b28SAriff Abdullah 			if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN))
75190da2b28SAriff Abdullah 				vchanfmt = 0;
752a580b31aSAriff Abdullah 		}
753a580b31aSAriff Abdullah 		if (vchanfmt == 0)
75490da2b28SAriff Abdullah 			vchanfmt = VCHAN_DEFAULT_FORMAT;
75590da2b28SAriff Abdullah 		save = 1;
756a580b31aSAriff Abdullah 	}
757a580b31aSAriff Abdullah 
75890da2b28SAriff Abdullah 	if (ret == 0 && vchanspd == 0) {
75987506547SAlexander Leidinger 		/*
76087506547SAlexander Leidinger 		 * This is very sad. Few soundcards advertised as being
76187506547SAlexander Leidinger 		 * able to do (insanely) higher/lower speed, but in
76287506547SAlexander Leidinger 		 * reality, they simply can't. At least, we give user chance
76397d69a96SAlexander Leidinger 		 * to set sane value via kernel hints or sysctl.
76487506547SAlexander Leidinger 		 */
76597d69a96SAlexander Leidinger 		CHN_UNLOCK(parent);
76690da2b28SAriff Abdullah 		r = resource_int_value(device_get_name(parent->dev),
76790da2b28SAriff Abdullah 		    device_get_unit(parent->dev), VCHAN_SPD_HINT(direction),
76890da2b28SAriff Abdullah 		    &vchanspd);
76997d69a96SAlexander Leidinger 		CHN_LOCK(parent);
77097efeca3SAriff Abdullah 		if (r != 0) {
771*d6d4586bSChristos Margiolis 			/* No saved value, no hint, NOTHING. */
77290da2b28SAriff Abdullah 			vchanspd = VCHAN_DEFAULT_RATE;
773*d6d4586bSChristos Margiolis 			RANGE(vchanspd, parent_caps->minspeed,
774*d6d4586bSChristos Margiolis 			    parent_caps->maxspeed);
77597efeca3SAriff Abdullah 		}
77690da2b28SAriff Abdullah 		save = 1;
77787506547SAlexander Leidinger 	}
77887506547SAlexander Leidinger 
77990da2b28SAriff Abdullah 	if (ret == 0) {
78090da2b28SAriff Abdullah 		/*
78190da2b28SAriff Abdullah 		 * Limit the speed between feeder_rate_min <-> feeder_rate_max.
78290da2b28SAriff Abdullah 		 */
783*d6d4586bSChristos Margiolis 		RANGE(vchanspd, feeder_rate_min, feeder_rate_max);
78490da2b28SAriff Abdullah 
785a580b31aSAriff Abdullah 		if (feeder_rate_round) {
78690da2b28SAriff Abdullah 			RANGE(vchanspd, parent_caps->minspeed,
78790da2b28SAriff Abdullah 			    parent_caps->maxspeed);
78890da2b28SAriff Abdullah 			vchanspd = CHANNEL_SETSPEED(parent->methods,
78990da2b28SAriff Abdullah 			    parent->devinfo, vchanspd);
790a580b31aSAriff Abdullah 		}
79187506547SAlexander Leidinger 
79290da2b28SAriff Abdullah 		ret = chn_reset(parent, vchanfmt, vchanspd);
793d45d1f20SAriff Abdullah 	}
79497d69a96SAlexander Leidinger 
79590da2b28SAriff Abdullah 	if (ret == 0 && save) {
79687506547SAlexander Leidinger 		/*
797bba4862cSAriff Abdullah 		 * Save new value.
79887506547SAlexander Leidinger 		 */
799bba4862cSAriff Abdullah 		if (direction == PCMDIR_PLAY_VIRTUAL) {
80090da2b28SAriff Abdullah 			d->pvchanformat = parent->format;
80190da2b28SAriff Abdullah 			d->pvchanrate = parent->speed;
802bba4862cSAriff Abdullah 		} else {
80390da2b28SAriff Abdullah 			d->rvchanformat = parent->format;
80490da2b28SAriff Abdullah 			d->rvchanrate = parent->speed;
80587506547SAlexander Leidinger 		}
806285648f9SCameron Grant 	}
807285648f9SCameron Grant 
80890da2b28SAriff Abdullah 	/*
80990da2b28SAriff Abdullah 	 * If the parent channel supports digital format,
81090da2b28SAriff Abdullah 	 * enable passthrough mode.
81190da2b28SAriff Abdullah 	 */
81290da2b28SAriff Abdullah 	if (ret == 0 && snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
81390da2b28SAriff Abdullah 		parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
81490da2b28SAriff Abdullah 		parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
81590da2b28SAriff Abdullah 	}
81690da2b28SAriff Abdullah 
81790da2b28SAriff Abdullah 	if (ret != 0) {
818bba4862cSAriff Abdullah 		CHN_REMOVE(parent, ch, children);
81997d69a96SAlexander Leidinger 		parent->flags &= ~CHN_F_HAS_VCHAN;
82097d69a96SAlexander Leidinger 		CHN_UNLOCK(parent);
82190da2b28SAriff Abdullah 		PCM_LOCK(d);
822bba4862cSAriff Abdullah 		if (pcm_chn_remove(d, ch) == 0) {
82390da2b28SAriff Abdullah 			PCM_UNLOCK(d);
824b3ea087cSChristos Margiolis 			chn_kill(ch);
825bba4862cSAriff Abdullah 		} else
82690da2b28SAriff Abdullah 			PCM_UNLOCK(d);
82797d69a96SAlexander Leidinger 		CHN_LOCK(parent);
82897d69a96SAlexander Leidinger 	}
82997d69a96SAlexander Leidinger 
83090da2b28SAriff Abdullah 	return (ret);
83197d69a96SAlexander Leidinger }
832285648f9SCameron Grant 
833285648f9SCameron Grant int
834285648f9SCameron Grant vchan_destroy(struct pcm_channel *c)
835285648f9SCameron Grant {
83690da2b28SAriff Abdullah 	struct pcm_channel *parent;
83790da2b28SAriff Abdullah 	struct snddev_info *d;
83890da2b28SAriff Abdullah 	int ret;
83990da2b28SAriff Abdullah 
84090da2b28SAriff Abdullah 	KASSERT(c != NULL && c->parentchannel != NULL &&
84190da2b28SAriff Abdullah 	    c->parentsnddev != NULL, ("%s(): invalid channel=%p",
84290da2b28SAriff Abdullah 	    __func__, c));
84390da2b28SAriff Abdullah 
84490da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
84590da2b28SAriff Abdullah 
84690da2b28SAriff Abdullah 	d = c->parentsnddev;
84790da2b28SAriff Abdullah 	parent = c->parentchannel;
848285648f9SCameron Grant 
849e4e61333SAriff Abdullah 	PCM_BUSYASSERT(d);
85090da2b28SAriff Abdullah 	CHN_LOCKASSERT(parent);
851e4e61333SAriff Abdullah 
85290da2b28SAriff Abdullah 	CHN_UNLOCK(c);
85390da2b28SAriff Abdullah 
85490da2b28SAriff Abdullah 	if (!(parent->flags & CHN_F_BUSY))
855bba4862cSAriff Abdullah 		return (EBUSY);
85690da2b28SAriff Abdullah 
85790da2b28SAriff Abdullah 	if (CHN_EMPTY(parent, children))
858bba4862cSAriff Abdullah 		return (EINVAL);
85949c5e6e2SCameron Grant 
860285648f9SCameron Grant 	/* remove us from our parent's children list */
861bba4862cSAriff Abdullah 	CHN_REMOVE(parent, c, children);
862285648f9SCameron Grant 
863bba4862cSAriff Abdullah 	if (CHN_EMPTY(parent, children)) {
8649c271ebaSAriff Abdullah 		parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
86590da2b28SAriff Abdullah 		chn_reset(parent, parent->format, parent->speed);
86697d69a96SAlexander Leidinger 	}
867f637a36cSCameron Grant 
86849c5e6e2SCameron Grant 	CHN_UNLOCK(parent);
869bba4862cSAriff Abdullah 
870bba4862cSAriff Abdullah 	/* remove us from our grandparent's channel list */
87190da2b28SAriff Abdullah 	PCM_LOCK(d);
87290da2b28SAriff Abdullah 	ret = pcm_chn_remove(d, c);
87390da2b28SAriff Abdullah 	PCM_UNLOCK(d);
874bba4862cSAriff Abdullah 
875285648f9SCameron Grant 	/* destroy ourselves */
87690da2b28SAriff Abdullah 	if (ret == 0)
877b3ea087cSChristos Margiolis 		chn_kill(c);
878285648f9SCameron Grant 
87990da2b28SAriff Abdullah 	CHN_LOCK(parent);
88090da2b28SAriff Abdullah 
88190da2b28SAriff Abdullah 	return (ret);
882285648f9SCameron Grant }
883285648f9SCameron Grant 
884285648f9SCameron Grant int
88590da2b28SAriff Abdullah #ifdef SND_DEBUG
88690da2b28SAriff Abdullah vchan_passthrough(struct pcm_channel *c, const char *caller)
88790da2b28SAriff Abdullah #else
88890da2b28SAriff Abdullah vchan_sync(struct pcm_channel *c)
88990da2b28SAriff Abdullah #endif
89090da2b28SAriff Abdullah {
89190da2b28SAriff Abdullah 	int ret;
89290da2b28SAriff Abdullah 
89390da2b28SAriff Abdullah 	KASSERT(c != NULL && c->parentchannel != NULL &&
89490da2b28SAriff Abdullah 	    (c->flags & CHN_F_VIRTUAL),
89590da2b28SAriff Abdullah 	    ("%s(): invalid passthrough", __func__));
89690da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
89790da2b28SAriff Abdullah 	CHN_LOCKASSERT(c->parentchannel);
89890da2b28SAriff Abdullah 
89990da2b28SAriff Abdullah 	sndbuf_setspd(c->bufhard, c->parentchannel->speed);
90090da2b28SAriff Abdullah 	c->flags |= CHN_F_PASSTHROUGH;
90190da2b28SAriff Abdullah 	ret = feeder_chain(c);
90290da2b28SAriff Abdullah 	c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
90390da2b28SAriff Abdullah 	if (ret != 0)
90490da2b28SAriff Abdullah 		c->flags |= CHN_F_DIRTY;
90590da2b28SAriff Abdullah 
90690da2b28SAriff Abdullah #ifdef SND_DEBUG
90790da2b28SAriff Abdullah 	if (snd_passthrough_verbose != 0) {
90890da2b28SAriff Abdullah 		char *devname, buf[CHN_NAMELEN];
90990da2b28SAriff Abdullah 
91025723d66SChristos Margiolis 		devname = dsp_unit2name(buf, sizeof(buf), c);
91190da2b28SAriff Abdullah 		device_printf(c->dev,
91290da2b28SAriff Abdullah 		    "%s(%s/%s) %s() -> re-sync err=%d\n",
91390da2b28SAriff Abdullah 		    __func__, (devname != NULL) ? devname : "dspX", c->comm,
91490da2b28SAriff Abdullah 		    caller, ret);
91590da2b28SAriff Abdullah 	}
91690da2b28SAriff Abdullah #endif
91790da2b28SAriff Abdullah 
91890da2b28SAriff Abdullah 	return (ret);
91990da2b28SAriff Abdullah }
92090da2b28SAriff Abdullah 
9217ad5f383SChristos Margiolis int
9223af2beb8SChristos Margiolis vchan_setnew(struct snddev_info *d, int direction, int newcnt)
9237ad5f383SChristos Margiolis {
9247ad5f383SChristos Margiolis 	struct pcm_channel *c, *ch, *nch;
9257ad5f383SChristos Margiolis 	struct pcmchan_caps *caps;
9267ad5f383SChristos Margiolis 	int i, err, vcnt;
9277ad5f383SChristos Margiolis 
9287ad5f383SChristos Margiolis 	PCM_BUSYASSERT(d);
9297ad5f383SChristos Margiolis 
9307ad5f383SChristos Margiolis 	if ((direction == PCMDIR_PLAY && d->playcount < 1) ||
9317ad5f383SChristos Margiolis 	    (direction == PCMDIR_REC && d->reccount < 1))
9327ad5f383SChristos Margiolis 		return (ENODEV);
9337ad5f383SChristos Margiolis 
9347ad5f383SChristos Margiolis 	if (!(d->flags & SD_F_AUTOVCHAN))
9357ad5f383SChristos Margiolis 		return (EINVAL);
9367ad5f383SChristos Margiolis 
9377ad5f383SChristos Margiolis 	if (newcnt < 0 || newcnt > SND_MAXVCHANS)
9387ad5f383SChristos Margiolis 		return (E2BIG);
9397ad5f383SChristos Margiolis 
9407ad5f383SChristos Margiolis 	if (direction == PCMDIR_PLAY)
9417ad5f383SChristos Margiolis 		vcnt = d->pvchancount;
9427ad5f383SChristos Margiolis 	else if (direction == PCMDIR_REC)
9437ad5f383SChristos Margiolis 		vcnt = d->rvchancount;
9447ad5f383SChristos Margiolis 	else
9457ad5f383SChristos Margiolis 		return (EINVAL);
9467ad5f383SChristos Margiolis 
9477ad5f383SChristos Margiolis 	if (newcnt > vcnt) {
9483af2beb8SChristos Margiolis 		KASSERT((newcnt - 1) == vcnt,
9493af2beb8SChristos Margiolis 		    ("bogus vchan_create() request newcnt=%d vcnt=%d",
9503af2beb8SChristos Margiolis 		    newcnt, vcnt));
9517ad5f383SChristos Margiolis 		/* add new vchans - find a parent channel first */
9527ad5f383SChristos Margiolis 		ch = NULL;
9537ad5f383SChristos Margiolis 		CHN_FOREACH(c, d, channels.pcm) {
9547ad5f383SChristos Margiolis 			CHN_LOCK(c);
9557ad5f383SChristos Margiolis 			if (c->direction == direction &&
9567ad5f383SChristos Margiolis 			    ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 &&
9577ad5f383SChristos Margiolis 			    c->refcount < 1 &&
9587ad5f383SChristos Margiolis 			    !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) {
9597ad5f383SChristos Margiolis 				/*
9607ad5f383SChristos Margiolis 				 * Reuse hw channel with vchans already
9617ad5f383SChristos Margiolis 				 * created.
9627ad5f383SChristos Margiolis 				 */
9637ad5f383SChristos Margiolis 				if (c->flags & CHN_F_HAS_VCHAN) {
9647ad5f383SChristos Margiolis 					ch = c;
9657ad5f383SChristos Margiolis 					break;
9667ad5f383SChristos Margiolis 				}
9677ad5f383SChristos Margiolis 				/*
9687ad5f383SChristos Margiolis 				 * No vchans ever created, look for
9697ad5f383SChristos Margiolis 				 * channels with supported formats.
9707ad5f383SChristos Margiolis 				 */
9717ad5f383SChristos Margiolis 				caps = chn_getcaps(c);
9727ad5f383SChristos Margiolis 				if (caps == NULL) {
9737ad5f383SChristos Margiolis 					CHN_UNLOCK(c);
9747ad5f383SChristos Margiolis 					continue;
9757ad5f383SChristos Margiolis 				}
9767ad5f383SChristos Margiolis 				for (i = 0; caps->fmtlist[i] != 0; i++) {
9777ad5f383SChristos Margiolis 					if (caps->fmtlist[i] & AFMT_CONVERTIBLE)
9787ad5f383SChristos Margiolis 						break;
9797ad5f383SChristos Margiolis 				}
9807ad5f383SChristos Margiolis 				if (caps->fmtlist[i] != 0) {
9817ad5f383SChristos Margiolis 					ch = c;
9827ad5f383SChristos Margiolis 					break;
9837ad5f383SChristos Margiolis 				}
9847ad5f383SChristos Margiolis 			}
9857ad5f383SChristos Margiolis 			CHN_UNLOCK(c);
9867ad5f383SChristos Margiolis 		}
9877ad5f383SChristos Margiolis 		if (ch == NULL)
9887ad5f383SChristos Margiolis 			return (EBUSY);
9897ad5f383SChristos Margiolis 		ch->flags |= CHN_F_BUSY;
9907ad5f383SChristos Margiolis 		err = 0;
9917ad5f383SChristos Margiolis 		while (err == 0 && newcnt > vcnt) {
9923af2beb8SChristos Margiolis 			err = vchan_create(ch);
9937ad5f383SChristos Margiolis 			if (err == 0)
9947ad5f383SChristos Margiolis 				vcnt++;
9957ad5f383SChristos Margiolis 			else if (err == E2BIG && newcnt > vcnt)
9967ad5f383SChristos Margiolis 				device_printf(d->dev,
9977ad5f383SChristos Margiolis 				    "%s: err=%d Maximum channel reached.\n",
9987ad5f383SChristos Margiolis 				    __func__, err);
9997ad5f383SChristos Margiolis 		}
10007ad5f383SChristos Margiolis 		if (vcnt == 0)
10017ad5f383SChristos Margiolis 			ch->flags &= ~CHN_F_BUSY;
10027ad5f383SChristos Margiolis 		CHN_UNLOCK(ch);
10037ad5f383SChristos Margiolis 		if (err != 0)
10047ad5f383SChristos Margiolis 			return (err);
10057ad5f383SChristos Margiolis 	} else if (newcnt < vcnt) {
10067ad5f383SChristos Margiolis 		CHN_FOREACH(c, d, channels.pcm) {
10077ad5f383SChristos Margiolis 			CHN_LOCK(c);
10087ad5f383SChristos Margiolis 			if (c->direction != direction ||
10097ad5f383SChristos Margiolis 			    CHN_EMPTY(c, children) ||
10107ad5f383SChristos Margiolis 			    !(c->flags & CHN_F_HAS_VCHAN)) {
10117ad5f383SChristos Margiolis 				CHN_UNLOCK(c);
10127ad5f383SChristos Margiolis 				continue;
10137ad5f383SChristos Margiolis 			}
10147ad5f383SChristos Margiolis 			CHN_FOREACH_SAFE(ch, c, nch, children) {
10157ad5f383SChristos Margiolis 				CHN_LOCK(ch);
10167ad5f383SChristos Margiolis 				if (vcnt == 1 && c->refcount > 0) {
10177ad5f383SChristos Margiolis 					CHN_UNLOCK(ch);
10187ad5f383SChristos Margiolis 					break;
10197ad5f383SChristos Margiolis 				}
10207ad5f383SChristos Margiolis 				if (!(ch->flags & CHN_F_BUSY) &&
10217ad5f383SChristos Margiolis 				    ch->refcount < 1) {
10227ad5f383SChristos Margiolis 					err = vchan_destroy(ch);
10237ad5f383SChristos Margiolis 					if (err == 0)
10247ad5f383SChristos Margiolis 						vcnt--;
10257ad5f383SChristos Margiolis 				} else
10267ad5f383SChristos Margiolis 					CHN_UNLOCK(ch);
10277ad5f383SChristos Margiolis 				if (vcnt == newcnt)
10287ad5f383SChristos Margiolis 					break;
10297ad5f383SChristos Margiolis 			}
10307ad5f383SChristos Margiolis 			CHN_UNLOCK(c);
10317ad5f383SChristos Margiolis 			break;
10327ad5f383SChristos Margiolis 		}
10337ad5f383SChristos Margiolis 	}
10347ad5f383SChristos Margiolis 
10357ad5f383SChristos Margiolis 	return (0);
10367ad5f383SChristos Margiolis }
10377ad5f383SChristos Margiolis 
10387ad5f383SChristos Margiolis void
10397ad5f383SChristos Margiolis vchan_setmaxauto(struct snddev_info *d, int num)
10407ad5f383SChristos Margiolis {
10417ad5f383SChristos Margiolis 	PCM_BUSYASSERT(d);
10427ad5f383SChristos Margiolis 
10437ad5f383SChristos Margiolis 	if (num < 0)
10447ad5f383SChristos Margiolis 		return;
10457ad5f383SChristos Margiolis 
10467ad5f383SChristos Margiolis 	if (num >= 0 && d->pvchancount > num)
10473af2beb8SChristos Margiolis 		(void)vchan_setnew(d, PCMDIR_PLAY, num);
10487ad5f383SChristos Margiolis 	else if (num > 0 && d->pvchancount == 0)
10493af2beb8SChristos Margiolis 		(void)vchan_setnew(d, PCMDIR_PLAY, 1);
10507ad5f383SChristos Margiolis 
10517ad5f383SChristos Margiolis 	if (num >= 0 && d->rvchancount > num)
10523af2beb8SChristos Margiolis 		(void)vchan_setnew(d, PCMDIR_REC, num);
10537ad5f383SChristos Margiolis 	else if (num > 0 && d->rvchancount == 0)
10543af2beb8SChristos Margiolis 		(void)vchan_setnew(d, PCMDIR_REC, 1);
10557ad5f383SChristos Margiolis }
10567ad5f383SChristos Margiolis 
10577ad5f383SChristos Margiolis static int
10587ad5f383SChristos Margiolis sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
10597ad5f383SChristos Margiolis {
10607ad5f383SChristos Margiolis 	struct snddev_info *d;
10617ad5f383SChristos Margiolis 	int i, v, error;
10627ad5f383SChristos Margiolis 
10637ad5f383SChristos Margiolis 	v = snd_maxautovchans;
10647ad5f383SChristos Margiolis 	error = sysctl_handle_int(oidp, &v, 0, req);
10657ad5f383SChristos Margiolis 	if (error == 0 && req->newptr != NULL) {
10667ad5f383SChristos Margiolis 		if (v < 0)
10677ad5f383SChristos Margiolis 			v = 0;
10687ad5f383SChristos Margiolis 		if (v > SND_MAXVCHANS)
10697ad5f383SChristos Margiolis 			v = SND_MAXVCHANS;
10707ad5f383SChristos Margiolis 		snd_maxautovchans = v;
10717ad5f383SChristos Margiolis 		for (i = 0; pcm_devclass != NULL &&
10727ad5f383SChristos Margiolis 		    i < devclass_get_maxunit(pcm_devclass); i++) {
10737ad5f383SChristos Margiolis 			d = devclass_get_softc(pcm_devclass, i);
10747ad5f383SChristos Margiolis 			if (!PCM_REGISTERED(d))
10757ad5f383SChristos Margiolis 				continue;
10767ad5f383SChristos Margiolis 			PCM_ACQUIRE_QUICK(d);
10777ad5f383SChristos Margiolis 			vchan_setmaxauto(d, v);
10787ad5f383SChristos Margiolis 			PCM_RELEASE_QUICK(d);
10797ad5f383SChristos Margiolis 		}
10807ad5f383SChristos Margiolis 	}
10817ad5f383SChristos Margiolis 	return (error);
10827ad5f383SChristos Margiolis }
10837ad5f383SChristos Margiolis SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans,
10847ad5f383SChristos Margiolis     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
10857ad5f383SChristos Margiolis     sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel");
10867ad5f383SChristos Margiolis 
108790da2b28SAriff Abdullah void
108867b1dce3SCameron Grant vchan_initsys(device_t dev)
1089285648f9SCameron Grant {
109067b1dce3SCameron Grant 	struct snddev_info *d;
1091bba4862cSAriff Abdullah 	int unit;
109267b1dce3SCameron Grant 
1093bba4862cSAriff Abdullah 	unit = device_get_unit(dev);
109467b1dce3SCameron Grant 	d = device_get_softc(dev);
1095bba4862cSAriff Abdullah 
1096bba4862cSAriff Abdullah 	/* Play */
1097bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
1098bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
10997029da5cSPawel Biernacki 	    OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
1100bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
110190da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
110290da2b28SAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
110390da2b28SAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
11047029da5cSPawel Biernacki 	    OID_AUTO, "vchanmode",
11057029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
110690da2b28SAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
110790da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanmode, "A",
110890da2b28SAriff Abdullah 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
1109bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
1110bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
11117029da5cSPawel Biernacki 	    OID_AUTO, "vchanrate",
11127029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
1113bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
111490da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
1115bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
1116bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
11177029da5cSPawel Biernacki 	    OID_AUTO, "vchanformat",
11187029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
1119bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
112090da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
1121bba4862cSAriff Abdullah 	/* Rec */
1122bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
1123bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
11247029da5cSPawel Biernacki 	    OID_AUTO, "vchans",
11257029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
1126bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
112790da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
112890da2b28SAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
112990da2b28SAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
11307029da5cSPawel Biernacki 	    OID_AUTO, "vchanmode",
11317029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
113290da2b28SAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
113390da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanmode, "A",
113490da2b28SAriff Abdullah 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
1135bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
1136bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
11377029da5cSPawel Biernacki 	    OID_AUTO, "vchanrate",
11387029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
1139bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
114090da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
1141bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
1142bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
11437029da5cSPawel Biernacki 	    OID_AUTO, "vchanformat",
11447029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
1145bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
114690da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
1147285648f9SCameron Grant }
1148