xref: /freebsd/sys/dev/sound/pcm/vchan.c (revision 7029da5c36f2d3cf6bb6c81bf551229f416399e8)
1098ca2bdSWarner Losh /*-
2718cf2ccSPedro F. Giffuni  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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.
7285648f9SCameron Grant  *
8285648f9SCameron Grant  * Redistribution and use in source and binary forms, with or without
9285648f9SCameron Grant  * modification, are permitted provided that the following conditions
10285648f9SCameron Grant  * are met:
11285648f9SCameron Grant  * 1. Redistributions of source code must retain the above copyright
12285648f9SCameron Grant  *    notice, this list of conditions and the following disclaimer.
13285648f9SCameron Grant  * 2. Redistributions in binary form must reproduce the above copyright
14285648f9SCameron Grant  *    notice, this list of conditions and the following disclaimer in the
15285648f9SCameron Grant  *    documentation and/or other materials provided with the distribution.
16285648f9SCameron Grant  *
17285648f9SCameron Grant  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18285648f9SCameron Grant  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19285648f9SCameron Grant  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20285648f9SCameron Grant  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21285648f9SCameron Grant  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22285648f9SCameron Grant  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23285648f9SCameron Grant  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24285648f9SCameron Grant  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25285648f9SCameron Grant  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26285648f9SCameron Grant  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27285648f9SCameron Grant  * SUCH DAMAGE.
28285648f9SCameron Grant  */
29285648f9SCameron Grant 
30e9577a5cSJoel Dahl /* Almost entirely rewritten to add multi-format/channels mixing support. */
31e9577a5cSJoel Dahl 
3290da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
3390da2b28SAriff Abdullah #include "opt_snd.h"
3490da2b28SAriff Abdullah #endif
3590da2b28SAriff Abdullah 
36285648f9SCameron Grant #include <dev/sound/pcm/sound.h>
37285648f9SCameron Grant #include <dev/sound/pcm/vchan.h>
38285648f9SCameron Grant 
3967b1dce3SCameron Grant SND_DECLARE_FILE("$FreeBSD$");
4067b1dce3SCameron Grant 
4190da2b28SAriff Abdullah /*
4290da2b28SAriff Abdullah  * [ac3 , dts , linear , 0, linear, 0]
4390da2b28SAriff Abdullah  */
4490da2b28SAriff Abdullah #define FMTLIST_MAX		6
4590da2b28SAriff Abdullah #define FMTLIST_OFFSET		4
4690da2b28SAriff Abdullah #define DIGFMTS_MAX		2
47a580b31aSAriff Abdullah 
4890da2b28SAriff Abdullah #ifdef SND_DEBUG
4990da2b28SAriff Abdullah static int snd_passthrough_verbose = 0;
5032a0e5d5SHans Petter Selasky SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN,
5190da2b28SAriff Abdullah 	&snd_passthrough_verbose, 0, "passthrough verbosity");
5287506547SAlexander Leidinger 
5390da2b28SAriff Abdullah #endif
5490da2b28SAriff Abdullah 
5590da2b28SAriff Abdullah struct vchan_info {
56bba4862cSAriff Abdullah 	struct pcm_channel *channel;
57285648f9SCameron Grant 	struct pcmchan_caps caps;
5890da2b28SAriff Abdullah 	uint32_t fmtlist[FMTLIST_MAX];
59bba4862cSAriff Abdullah 	int trigger;
60285648f9SCameron Grant };
61285648f9SCameron Grant 
62285648f9SCameron Grant static void *
63bba4862cSAriff Abdullah vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
64bba4862cSAriff Abdullah     struct pcm_channel *c, int dir)
65285648f9SCameron Grant {
6690da2b28SAriff Abdullah 	struct vchan_info *info;
6790da2b28SAriff Abdullah 	struct pcm_channel *p;
6890da2b28SAriff Abdullah 	uint32_t i, j, *fmtlist;
69285648f9SCameron Grant 
70bba4862cSAriff Abdullah 	KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
71bba4862cSAriff Abdullah 	    ("vchan_init: bad direction"));
72bba4862cSAriff Abdullah 	KASSERT(c != NULL && c->parentchannel != NULL,
73bba4862cSAriff Abdullah 	    ("vchan_init: bad channels"));
74bba4862cSAriff Abdullah 
7590da2b28SAriff Abdullah 	info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
7690da2b28SAriff Abdullah 	info->channel = c;
7790da2b28SAriff Abdullah 	info->trigger = PCMTRIG_STOP;
7890da2b28SAriff Abdullah 	p = c->parentchannel;
7990da2b28SAriff Abdullah 
8090da2b28SAriff Abdullah 	CHN_LOCK(p);
8190da2b28SAriff Abdullah 
8290da2b28SAriff Abdullah 	fmtlist = chn_getcaps(p)->fmtlist;
8390da2b28SAriff Abdullah 	for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
8490da2b28SAriff Abdullah 		if (fmtlist[i] & AFMT_PASSTHROUGH)
8590da2b28SAriff Abdullah 			info->fmtlist[j++] = fmtlist[i];
8690da2b28SAriff Abdullah 	}
8790da2b28SAriff Abdullah 	if (p->format & AFMT_VCHAN)
8890da2b28SAriff Abdullah 		info->fmtlist[j] = p->format;
8990da2b28SAriff Abdullah 	else
9090da2b28SAriff Abdullah 		info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
9190da2b28SAriff Abdullah 	info->caps.fmtlist = info->fmtlist +
9290da2b28SAriff Abdullah 	    ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
9390da2b28SAriff Abdullah 
9490da2b28SAriff Abdullah 	CHN_UNLOCK(p);
95285648f9SCameron Grant 
96285648f9SCameron Grant 	c->flags |= CHN_F_VIRTUAL;
97285648f9SCameron Grant 
9890da2b28SAriff Abdullah 	return (info);
99285648f9SCameron Grant }
100285648f9SCameron Grant 
101285648f9SCameron Grant static int
102285648f9SCameron Grant vchan_free(kobj_t obj, void *data)
103285648f9SCameron Grant {
10490da2b28SAriff Abdullah 
105e444a209SAriff Abdullah 	free(data, M_DEVBUF);
106bba4862cSAriff Abdullah 
107bba4862cSAriff Abdullah 	return (0);
108285648f9SCameron Grant }
109285648f9SCameron Grant 
110285648f9SCameron Grant static int
111a580b31aSAriff Abdullah vchan_setformat(kobj_t obj, void *data, uint32_t format)
112285648f9SCameron Grant {
11390da2b28SAriff Abdullah 	struct vchan_info *info;
114285648f9SCameron Grant 
11590da2b28SAriff Abdullah 	info = data;
11690da2b28SAriff Abdullah 
11790da2b28SAriff Abdullah 	CHN_LOCKASSERT(info->channel);
11890da2b28SAriff Abdullah 
11990da2b28SAriff Abdullah 	if (!snd_fmtvalid(format, info->caps.fmtlist))
120bba4862cSAriff Abdullah 		return (-1);
121bba4862cSAriff Abdullah 
122bba4862cSAriff Abdullah 	return (0);
123285648f9SCameron Grant }
124285648f9SCameron Grant 
12590da2b28SAriff Abdullah static uint32_t
126a580b31aSAriff Abdullah vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
127285648f9SCameron Grant {
12890da2b28SAriff Abdullah 	struct vchan_info *info;
129285648f9SCameron Grant 
13090da2b28SAriff Abdullah 	info = data;
13190da2b28SAriff Abdullah 
13290da2b28SAriff Abdullah 	CHN_LOCKASSERT(info->channel);
13390da2b28SAriff Abdullah 
13490da2b28SAriff Abdullah 	return (info->caps.maxspeed);
135285648f9SCameron Grant }
136285648f9SCameron Grant 
137285648f9SCameron Grant static int
138285648f9SCameron Grant vchan_trigger(kobj_t obj, void *data, int go)
139285648f9SCameron Grant {
14090da2b28SAriff Abdullah 	struct vchan_info *info;
141bba4862cSAriff Abdullah 	struct pcm_channel *c, *p;
14290da2b28SAriff Abdullah 	int ret, otrigger;
143285648f9SCameron Grant 
14490da2b28SAriff Abdullah 	info = data;
14590da2b28SAriff Abdullah 
14690da2b28SAriff Abdullah 	if (!PCMTRIG_COMMON(go) || go == info->trigger)
147bba4862cSAriff Abdullah 		return (0);
148285648f9SCameron Grant 
14990da2b28SAriff Abdullah 	c = info->channel;
150bba4862cSAriff Abdullah 	p = c->parentchannel;
15190da2b28SAriff Abdullah 	otrigger = info->trigger;
15290da2b28SAriff Abdullah 	info->trigger = go;
15390da2b28SAriff Abdullah 
15490da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
155285648f9SCameron Grant 
156bba4862cSAriff Abdullah 	CHN_UNLOCK(c);
157bba4862cSAriff Abdullah 	CHN_LOCK(p);
158bba4862cSAriff Abdullah 
159bba4862cSAriff Abdullah 	switch (go) {
160bba4862cSAriff Abdullah 	case PCMTRIG_START:
16190da2b28SAriff Abdullah 		if (otrigger != PCMTRIG_START)
162bba4862cSAriff Abdullah 			CHN_INSERT_HEAD(p, c, children.busy);
163bba4862cSAriff Abdullah 		break;
164bba4862cSAriff Abdullah 	case PCMTRIG_STOP:
165bba4862cSAriff Abdullah 	case PCMTRIG_ABORT:
16690da2b28SAriff Abdullah 		if (otrigger == PCMTRIG_START)
167bba4862cSAriff Abdullah 			CHN_REMOVE(p, c, children.busy);
168bba4862cSAriff Abdullah 		break;
169bba4862cSAriff Abdullah 	default:
170bba4862cSAriff Abdullah 		break;
171bba4862cSAriff Abdullah 	}
172bba4862cSAriff Abdullah 
17390da2b28SAriff Abdullah 	ret = chn_notify(p, CHN_N_TRIGGER);
17490da2b28SAriff Abdullah 
17590da2b28SAriff Abdullah 	CHN_LOCK(c);
17690da2b28SAriff Abdullah 
17790da2b28SAriff Abdullah 	if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
17890da2b28SAriff Abdullah 		ret = vchan_sync(c);
17990da2b28SAriff Abdullah 
18090da2b28SAriff Abdullah 	CHN_UNLOCK(c);
181bba4862cSAriff Abdullah 	CHN_UNLOCK(p);
182bba4862cSAriff Abdullah 	CHN_LOCK(c);
183bba4862cSAriff Abdullah 
18490da2b28SAriff Abdullah 	return (ret);
185285648f9SCameron Grant }
186285648f9SCameron Grant 
187285648f9SCameron Grant static struct pcmchan_caps *
188285648f9SCameron Grant vchan_getcaps(kobj_t obj, void *data)
189285648f9SCameron Grant {
19090da2b28SAriff Abdullah 	struct vchan_info *info;
19190da2b28SAriff Abdullah 	struct pcm_channel *c;
19290da2b28SAriff Abdullah 	uint32_t pformat, pspeed, pflags, i;
193285648f9SCameron Grant 
19490da2b28SAriff Abdullah 	info = data;
19590da2b28SAriff Abdullah 	c = info->channel;
19690da2b28SAriff Abdullah 	pformat = c->parentchannel->format;
19790da2b28SAriff Abdullah 	pspeed = c->parentchannel->speed;
19890da2b28SAriff Abdullah 	pflags = c->parentchannel->flags;
19990da2b28SAriff Abdullah 
20090da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
20190da2b28SAriff Abdullah 
20290da2b28SAriff Abdullah 	if (pflags & CHN_F_VCHAN_DYNAMIC) {
20390da2b28SAriff Abdullah 		info->caps.fmtlist = info->fmtlist;
20490da2b28SAriff Abdullah 		if (pformat & AFMT_VCHAN) {
20590da2b28SAriff Abdullah 			for (i = 0; info->caps.fmtlist[i] != 0; i++) {
20690da2b28SAriff Abdullah 				if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
20790da2b28SAriff Abdullah 					continue;
20890da2b28SAriff Abdullah 				break;
209a580b31aSAriff Abdullah 			}
21090da2b28SAriff Abdullah 			info->caps.fmtlist[i] = pformat;
21190da2b28SAriff Abdullah 		}
21290da2b28SAriff Abdullah 		if (c->format & AFMT_PASSTHROUGH)
21390da2b28SAriff Abdullah 			info->caps.minspeed = c->speed;
21490da2b28SAriff Abdullah 		else
21590da2b28SAriff Abdullah 			info->caps.minspeed = pspeed;
21690da2b28SAriff Abdullah 		info->caps.maxspeed = info->caps.minspeed;
21790da2b28SAriff Abdullah 	} else {
21890da2b28SAriff Abdullah 		info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
21990da2b28SAriff Abdullah 		if (pformat & AFMT_VCHAN)
22090da2b28SAriff Abdullah 			info->caps.fmtlist[0] = pformat;
22190da2b28SAriff Abdullah 		else {
22290da2b28SAriff Abdullah 			device_printf(c->dev,
22390da2b28SAriff Abdullah 			    "%s(): invalid vchan format 0x%08x",
22490da2b28SAriff Abdullah 			    __func__, pformat);
22590da2b28SAriff Abdullah 			info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
22690da2b28SAriff Abdullah 		}
22790da2b28SAriff Abdullah 		info->caps.minspeed = pspeed;
22890da2b28SAriff Abdullah 		info->caps.maxspeed = info->caps.minspeed;
22990da2b28SAriff Abdullah 	}
230285648f9SCameron Grant 
23190da2b28SAriff Abdullah 	return (&info->caps);
23290da2b28SAriff Abdullah }
23390da2b28SAriff Abdullah 
23490da2b28SAriff Abdullah static struct pcmchan_matrix *
23590da2b28SAriff Abdullah vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
23690da2b28SAriff Abdullah {
23790da2b28SAriff Abdullah 
23890da2b28SAriff Abdullah 	return (feeder_matrix_format_map(format));
239285648f9SCameron Grant }
240285648f9SCameron Grant 
241285648f9SCameron Grant static kobj_method_t vchan_methods[] = {
242285648f9SCameron Grant 	KOBJMETHOD(channel_init,		vchan_init),
243285648f9SCameron Grant 	KOBJMETHOD(channel_free,		vchan_free),
244285648f9SCameron Grant 	KOBJMETHOD(channel_setformat,		vchan_setformat),
245285648f9SCameron Grant 	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
246285648f9SCameron Grant 	KOBJMETHOD(channel_trigger,		vchan_trigger),
247285648f9SCameron Grant 	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
24890da2b28SAriff Abdullah 	KOBJMETHOD(channel_getmatrix,		vchan_getmatrix),
24990da2b28SAriff Abdullah 	KOBJMETHOD_END
250285648f9SCameron Grant };
251285648f9SCameron Grant CHANNEL_DECLARE(vchan);
252285648f9SCameron Grant 
25390da2b28SAriff Abdullah static void
25490da2b28SAriff Abdullah pcm_getparentchannel(struct snddev_info *d,
25590da2b28SAriff Abdullah     struct pcm_channel **wrch, struct pcm_channel **rdch)
25690da2b28SAriff Abdullah {
25790da2b28SAriff Abdullah 	struct pcm_channel **ch, *wch, *rch, *c;
25890da2b28SAriff Abdullah 
25990da2b28SAriff Abdullah 	KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__));
26090da2b28SAriff Abdullah 
26190da2b28SAriff Abdullah 	PCM_BUSYASSERT(d);
26290da2b28SAriff Abdullah 	PCM_UNLOCKASSERT(d);
26390da2b28SAriff Abdullah 
26490da2b28SAriff Abdullah 	wch = NULL;
26590da2b28SAriff Abdullah 	rch = NULL;
26690da2b28SAriff Abdullah 
26790da2b28SAriff Abdullah 	CHN_FOREACH(c, d, channels.pcm) {
26890da2b28SAriff Abdullah 		CHN_LOCK(c);
26990da2b28SAriff Abdullah 		ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch;
27090da2b28SAriff Abdullah 		if (c->flags & CHN_F_VIRTUAL) {
27190da2b28SAriff Abdullah 			/* Sanity check */
27290da2b28SAriff Abdullah 			if (*ch != NULL && *ch != c->parentchannel) {
27390da2b28SAriff Abdullah 				CHN_UNLOCK(c);
27490da2b28SAriff Abdullah 				*ch = NULL;
27590da2b28SAriff Abdullah 				break;
27690da2b28SAriff Abdullah 			}
27790da2b28SAriff Abdullah 		} else if (c->flags & CHN_F_HAS_VCHAN) {
27890da2b28SAriff Abdullah 			/* No way!! */
27990da2b28SAriff Abdullah 			if (*ch != NULL) {
28090da2b28SAriff Abdullah 				CHN_UNLOCK(c);
28190da2b28SAriff Abdullah 				*ch = NULL;
28290da2b28SAriff Abdullah 				break;
28390da2b28SAriff Abdullah 			}
28490da2b28SAriff Abdullah 			*ch = c;
28590da2b28SAriff Abdullah 		}
28690da2b28SAriff Abdullah 		CHN_UNLOCK(c);
28790da2b28SAriff Abdullah 	}
28890da2b28SAriff Abdullah 
28990da2b28SAriff Abdullah 	if (wrch != NULL)
29090da2b28SAriff Abdullah 		*wrch = wch;
29190da2b28SAriff Abdullah 	if (rdch != NULL)
29290da2b28SAriff Abdullah 		*rdch = rch;
29390da2b28SAriff Abdullah }
29490da2b28SAriff Abdullah 
29587506547SAlexander Leidinger static int
29690da2b28SAriff Abdullah sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
29787506547SAlexander Leidinger {
29887506547SAlexander Leidinger 	struct snddev_info *d;
29990da2b28SAriff Abdullah 	int direction, vchancount;
30090da2b28SAriff Abdullah 	int err, cnt;
30187506547SAlexander Leidinger 
302bba4862cSAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
303e4e61333SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
304bba4862cSAriff Abdullah 		return (EINVAL);
305bba4862cSAriff Abdullah 
30690da2b28SAriff Abdullah 	PCM_LOCK(d);
30790da2b28SAriff Abdullah 	PCM_WAIT(d);
30890da2b28SAriff Abdullah 
30990da2b28SAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
31090da2b28SAriff Abdullah 	case VCHAN_PLAY:
31190da2b28SAriff Abdullah 		direction = PCMDIR_PLAY;
31290da2b28SAriff Abdullah 		vchancount = d->pvchancount;
31390da2b28SAriff Abdullah 		cnt = d->playcount;
31490da2b28SAriff Abdullah 		break;
31590da2b28SAriff Abdullah 	case VCHAN_REC:
31690da2b28SAriff Abdullah 		direction = PCMDIR_REC;
31790da2b28SAriff Abdullah 		vchancount = d->rvchancount;
31890da2b28SAriff Abdullah 		cnt = d->reccount;
31990da2b28SAriff Abdullah 		break;
32090da2b28SAriff Abdullah 	default:
32190da2b28SAriff Abdullah 		PCM_UNLOCK(d);
32290da2b28SAriff Abdullah 		return (EINVAL);
32390da2b28SAriff Abdullah 		break;
32490da2b28SAriff Abdullah 	}
32590da2b28SAriff Abdullah 
32690da2b28SAriff Abdullah 	if (cnt < 1) {
32790da2b28SAriff Abdullah 		PCM_UNLOCK(d);
32890da2b28SAriff Abdullah 		return (ENODEV);
32990da2b28SAriff Abdullah 	}
33090da2b28SAriff Abdullah 
33190da2b28SAriff Abdullah 	PCM_ACQUIRE(d);
33290da2b28SAriff Abdullah 	PCM_UNLOCK(d);
33390da2b28SAriff Abdullah 
33490da2b28SAriff Abdullah 	cnt = vchancount;
33590da2b28SAriff Abdullah 	err = sysctl_handle_int(oidp, &cnt, 0, req);
33690da2b28SAriff Abdullah 
33790da2b28SAriff Abdullah 	if (err == 0 && req->newptr != NULL && vchancount != cnt) {
33890da2b28SAriff Abdullah 		if (cnt < 0)
33990da2b28SAriff Abdullah 			cnt = 0;
34090da2b28SAriff Abdullah 		if (cnt > SND_MAXVCHANS)
34190da2b28SAriff Abdullah 			cnt = SND_MAXVCHANS;
34290da2b28SAriff Abdullah 		err = pcm_setvchans(d, direction, cnt, -1);
34390da2b28SAriff Abdullah 	}
34490da2b28SAriff Abdullah 
34590da2b28SAriff Abdullah 	PCM_RELEASE_QUICK(d);
34690da2b28SAriff Abdullah 
34790da2b28SAriff Abdullah 	return err;
34890da2b28SAriff Abdullah }
34990da2b28SAriff Abdullah 
35090da2b28SAriff Abdullah static int
35190da2b28SAriff Abdullah sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
35290da2b28SAriff Abdullah {
35390da2b28SAriff Abdullah 	struct snddev_info *d;
35490da2b28SAriff Abdullah 	struct pcm_channel *c;
35590da2b28SAriff Abdullah 	uint32_t dflags;
35690da2b28SAriff Abdullah 	int direction, ret;
35790da2b28SAriff Abdullah 	char dtype[16];
35890da2b28SAriff Abdullah 
35990da2b28SAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
36090da2b28SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
36190da2b28SAriff Abdullah 		return (EINVAL);
36290da2b28SAriff Abdullah 
36390da2b28SAriff Abdullah 	PCM_LOCK(d);
36490da2b28SAriff Abdullah 	PCM_WAIT(d);
36590da2b28SAriff Abdullah 
36690da2b28SAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
36790da2b28SAriff Abdullah 	case VCHAN_PLAY:
36890da2b28SAriff Abdullah 		direction = PCMDIR_PLAY;
36990da2b28SAriff Abdullah 		break;
37090da2b28SAriff Abdullah 	case VCHAN_REC:
37190da2b28SAriff Abdullah 		direction = PCMDIR_REC;
37290da2b28SAriff Abdullah 		break;
37390da2b28SAriff Abdullah 	default:
37490da2b28SAriff Abdullah 		PCM_UNLOCK(d);
37590da2b28SAriff Abdullah 		return (EINVAL);
37690da2b28SAriff Abdullah 		break;
37790da2b28SAriff Abdullah 	}
37890da2b28SAriff Abdullah 
37990da2b28SAriff Abdullah 	PCM_ACQUIRE(d);
38090da2b28SAriff Abdullah 	PCM_UNLOCK(d);
38190da2b28SAriff Abdullah 
38290da2b28SAriff Abdullah 	if (direction == PCMDIR_PLAY)
38390da2b28SAriff Abdullah 		pcm_getparentchannel(d, &c, NULL);
38490da2b28SAriff Abdullah 	else
38590da2b28SAriff Abdullah 		pcm_getparentchannel(d, NULL, &c);
38690da2b28SAriff Abdullah 
38790da2b28SAriff Abdullah 	if (c == NULL) {
38890da2b28SAriff Abdullah 		PCM_RELEASE_QUICK(d);
38990da2b28SAriff Abdullah 		return (EINVAL);
39090da2b28SAriff Abdullah 	}
39190da2b28SAriff Abdullah 
39290da2b28SAriff Abdullah 	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
39390da2b28SAriff Abdullah 	    __func__, direction, c->direction));
39490da2b28SAriff Abdullah 
39590da2b28SAriff Abdullah 	CHN_LOCK(c);
39690da2b28SAriff Abdullah 	if (c->flags & CHN_F_VCHAN_PASSTHROUGH)
39790da2b28SAriff Abdullah 		strlcpy(dtype, "passthrough", sizeof(dtype));
39890da2b28SAriff Abdullah 	else if (c->flags & CHN_F_VCHAN_ADAPTIVE)
39990da2b28SAriff Abdullah 		strlcpy(dtype, "adaptive", sizeof(dtype));
40090da2b28SAriff Abdullah 	else
40190da2b28SAriff Abdullah 		strlcpy(dtype, "fixed", sizeof(dtype));
40290da2b28SAriff Abdullah 	CHN_UNLOCK(c);
40390da2b28SAriff Abdullah 
40490da2b28SAriff Abdullah 	ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
40590da2b28SAriff Abdullah 	if (ret == 0 && req->newptr != NULL) {
40690da2b28SAriff Abdullah 		if (strcasecmp(dtype, "passthrough") == 0 ||
40790da2b28SAriff Abdullah 		    strcmp(dtype, "1") == 0)
40890da2b28SAriff Abdullah 			dflags = CHN_F_VCHAN_PASSTHROUGH;
40990da2b28SAriff Abdullah 		else if (strcasecmp(dtype, "adaptive") == 0 ||
41090da2b28SAriff Abdullah 		    strcmp(dtype, "2") == 0)
41190da2b28SAriff Abdullah 			dflags = CHN_F_VCHAN_ADAPTIVE;
41290da2b28SAriff Abdullah 		else if (strcasecmp(dtype, "fixed") == 0 ||
41390da2b28SAriff Abdullah 		    strcmp(dtype, "0") == 0)
41490da2b28SAriff Abdullah 			dflags = 0;
41590da2b28SAriff Abdullah 		else {
41690da2b28SAriff Abdullah 			PCM_RELEASE_QUICK(d);
41790da2b28SAriff Abdullah 			return (EINVAL);
41890da2b28SAriff Abdullah 		}
41990da2b28SAriff Abdullah 		CHN_LOCK(c);
42090da2b28SAriff Abdullah 		if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
42190da2b28SAriff Abdullah 		    (c->flags & CHN_F_PASSTHROUGH)) {
42290da2b28SAriff Abdullah 			CHN_UNLOCK(c);
42390da2b28SAriff Abdullah 			PCM_RELEASE_QUICK(d);
42490da2b28SAriff Abdullah 			return (0);
42590da2b28SAriff Abdullah 		}
42690da2b28SAriff Abdullah 		c->flags &= ~CHN_F_VCHAN_DYNAMIC;
42790da2b28SAriff Abdullah 		c->flags |= dflags;
42890da2b28SAriff Abdullah 		CHN_UNLOCK(c);
42990da2b28SAriff Abdullah 	}
43090da2b28SAriff Abdullah 
43190da2b28SAriff Abdullah 	PCM_RELEASE_QUICK(d);
43290da2b28SAriff Abdullah 
43390da2b28SAriff Abdullah 	return (ret);
43490da2b28SAriff Abdullah }
43590da2b28SAriff Abdullah 
43690da2b28SAriff Abdullah /*
43790da2b28SAriff Abdullah  * On the fly vchan rate/format settings
43890da2b28SAriff Abdullah  */
43990da2b28SAriff Abdullah 
44090da2b28SAriff Abdullah #define VCHAN_ACCESSIBLE(c)	(!((c)->flags & (CHN_F_PASSTHROUGH |	\
44190da2b28SAriff Abdullah 				 CHN_F_EXCLUSIVE)) &&			\
44290da2b28SAriff Abdullah 				 (((c)->flags & CHN_F_VCHAN_DYNAMIC) ||	\
44390da2b28SAriff Abdullah 				 CHN_STOPPED(c)))
44490da2b28SAriff Abdullah static int
44590da2b28SAriff Abdullah sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
44690da2b28SAriff Abdullah {
44790da2b28SAriff Abdullah 	struct snddev_info *d;
44890da2b28SAriff Abdullah 	struct pcm_channel *c, *ch;
44990da2b28SAriff Abdullah 	struct pcmchan_caps *caps;
45090da2b28SAriff Abdullah 	int *vchanrate, vchancount, direction, ret, newspd, restart;
45190da2b28SAriff Abdullah 
45290da2b28SAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
45390da2b28SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
45490da2b28SAriff Abdullah 		return (EINVAL);
45590da2b28SAriff Abdullah 
45690da2b28SAriff Abdullah 	PCM_LOCK(d);
457e4e61333SAriff Abdullah 	PCM_WAIT(d);
458e4e61333SAriff Abdullah 
459bba4862cSAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
460bba4862cSAriff Abdullah 	case VCHAN_PLAY:
461bba4862cSAriff Abdullah 		direction = PCMDIR_PLAY;
462bba4862cSAriff Abdullah 		vchancount = d->pvchancount;
463bba4862cSAriff Abdullah 		vchanrate = &d->pvchanrate;
464bba4862cSAriff Abdullah 		break;
465bba4862cSAriff Abdullah 	case VCHAN_REC:
466bba4862cSAriff Abdullah 		direction = PCMDIR_REC;
467bba4862cSAriff Abdullah 		vchancount = d->rvchancount;
468bba4862cSAriff Abdullah 		vchanrate = &d->rvchanrate;
469bba4862cSAriff Abdullah 		break;
470bba4862cSAriff Abdullah 	default:
47190da2b28SAriff Abdullah 		PCM_UNLOCK(d);
472bba4862cSAriff Abdullah 		return (EINVAL);
473bba4862cSAriff Abdullah 		break;
474bba4862cSAriff Abdullah 	}
475bba4862cSAriff Abdullah 
476e4e61333SAriff Abdullah 	if (vchancount < 1) {
47790da2b28SAriff Abdullah 		PCM_UNLOCK(d);
478bba4862cSAriff Abdullah 		return (EINVAL);
4799c271ebaSAriff Abdullah 	}
480e4e61333SAriff Abdullah 
481e4e61333SAriff Abdullah 	PCM_ACQUIRE(d);
48290da2b28SAriff Abdullah 	PCM_UNLOCK(d);
483e4e61333SAriff Abdullah 
48490da2b28SAriff Abdullah 	if (direction == PCMDIR_PLAY)
48590da2b28SAriff Abdullah 		pcm_getparentchannel(d, &c, NULL);
48690da2b28SAriff Abdullah 	else
48790da2b28SAriff Abdullah 		pcm_getparentchannel(d, NULL, &c);
488e4e61333SAriff Abdullah 
48990da2b28SAriff Abdullah 	if (c == NULL) {
49090da2b28SAriff Abdullah 		PCM_RELEASE_QUICK(d);
49190da2b28SAriff Abdullah 		return (EINVAL);
49290da2b28SAriff Abdullah 	}
49390da2b28SAriff Abdullah 
49490da2b28SAriff Abdullah 	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
49590da2b28SAriff Abdullah 	    __func__, direction, c->direction));
49690da2b28SAriff Abdullah 
49787506547SAlexander Leidinger 	CHN_LOCK(c);
49890da2b28SAriff Abdullah 	newspd = c->speed;
4999c271ebaSAriff Abdullah 	CHN_UNLOCK(c);
50090da2b28SAriff Abdullah 
50190da2b28SAriff Abdullah 	ret = sysctl_handle_int(oidp, &newspd, 0, req);
50290da2b28SAriff Abdullah 	if (ret != 0 || req->newptr == NULL) {
503e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
50490da2b28SAriff Abdullah 		return (ret);
50587506547SAlexander Leidinger 	}
506e4e61333SAriff Abdullah 
507a580b31aSAriff Abdullah 	if (newspd < 1 || newspd < feeder_rate_min ||
508a580b31aSAriff Abdullah 	    newspd > feeder_rate_max) {
509e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
510bba4862cSAriff Abdullah 		return (EINVAL);
51197d69a96SAlexander Leidinger 	}
51290da2b28SAriff Abdullah 
51390da2b28SAriff Abdullah 	CHN_LOCK(c);
51490da2b28SAriff Abdullah 
51590da2b28SAriff Abdullah 	if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
51690da2b28SAriff Abdullah 		if (CHN_STARTED(c)) {
51790da2b28SAriff Abdullah 			chn_abort(c);
51890da2b28SAriff Abdullah 			restart = 1;
51990da2b28SAriff Abdullah 		} else
52090da2b28SAriff Abdullah 			restart = 0;
52190da2b28SAriff Abdullah 
522a580b31aSAriff Abdullah 		if (feeder_rate_round) {
52390da2b28SAriff Abdullah 			caps = chn_getcaps(c);
52490da2b28SAriff Abdullah 			RANGE(newspd, caps->minspeed, caps->maxspeed);
52590da2b28SAriff Abdullah 			newspd = CHANNEL_SETSPEED(c->methods,
52690da2b28SAriff Abdullah 			    c->devinfo, newspd);
52787506547SAlexander Leidinger 		}
52890da2b28SAriff Abdullah 
52990da2b28SAriff Abdullah 		ret = chn_reset(c, c->format, newspd);
53090da2b28SAriff Abdullah 		if (ret == 0) {
53190da2b28SAriff Abdullah 			*vchanrate = c->speed;
53290da2b28SAriff Abdullah 			if (restart != 0) {
53390da2b28SAriff Abdullah 				CHN_FOREACH(ch, c, children.busy) {
53490da2b28SAriff Abdullah 					CHN_LOCK(ch);
53590da2b28SAriff Abdullah 					if (VCHAN_SYNC_REQUIRED(ch))
53690da2b28SAriff Abdullah 						vchan_sync(ch);
53797d69a96SAlexander Leidinger 					CHN_UNLOCK(ch);
53897d69a96SAlexander Leidinger 				}
53990da2b28SAriff Abdullah 				c->flags |= CHN_F_DIRTY;
54090da2b28SAriff Abdullah 				ret = chn_start(c, 1);
54190da2b28SAriff Abdullah 			}
54290da2b28SAriff Abdullah 		}
54390da2b28SAriff Abdullah 	}
54490da2b28SAriff Abdullah 
54590da2b28SAriff Abdullah 	CHN_UNLOCK(c);
546e4e61333SAriff Abdullah 
547e4e61333SAriff Abdullah 	PCM_RELEASE_QUICK(d);
548e4e61333SAriff Abdullah 
54990da2b28SAriff Abdullah 	return (ret);
55087506547SAlexander Leidinger }
551a580b31aSAriff Abdullah 
552a580b31aSAriff Abdullah static int
55390da2b28SAriff Abdullah sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
554a580b31aSAriff Abdullah {
555a580b31aSAriff Abdullah 	struct snddev_info *d;
55690da2b28SAriff Abdullah 	struct pcm_channel *c, *ch;
55790da2b28SAriff Abdullah 	uint32_t newfmt;
55890da2b28SAriff Abdullah 	int *vchanformat, vchancount, direction, ret, restart;
55990da2b28SAriff Abdullah 	char fmtstr[AFMTSTR_LEN];
560a580b31aSAriff Abdullah 
561bba4862cSAriff Abdullah 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
562e4e61333SAriff Abdullah 	if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
563bba4862cSAriff Abdullah 		return (EINVAL);
564bba4862cSAriff Abdullah 
56590da2b28SAriff Abdullah 	PCM_LOCK(d);
566e4e61333SAriff Abdullah 	PCM_WAIT(d);
567e4e61333SAriff Abdullah 
568bba4862cSAriff Abdullah 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
569bba4862cSAriff Abdullah 	case VCHAN_PLAY:
570bba4862cSAriff Abdullah 		direction = PCMDIR_PLAY;
571bba4862cSAriff Abdullah 		vchancount = d->pvchancount;
572bba4862cSAriff Abdullah 		vchanformat = &d->pvchanformat;
573bba4862cSAriff Abdullah 		break;
574bba4862cSAriff Abdullah 	case VCHAN_REC:
575bba4862cSAriff Abdullah 		direction = PCMDIR_REC;
576bba4862cSAriff Abdullah 		vchancount = d->rvchancount;
577bba4862cSAriff Abdullah 		vchanformat = &d->rvchanformat;
578bba4862cSAriff Abdullah 		break;
579bba4862cSAriff Abdullah 	default:
58090da2b28SAriff Abdullah 		PCM_UNLOCK(d);
581bba4862cSAriff Abdullah 		return (EINVAL);
582bba4862cSAriff Abdullah 		break;
583bba4862cSAriff Abdullah 	}
584bba4862cSAriff Abdullah 
585e4e61333SAriff Abdullah 	if (vchancount < 1) {
58690da2b28SAriff Abdullah 		PCM_UNLOCK(d);
587bba4862cSAriff Abdullah 		return (EINVAL);
588a580b31aSAriff Abdullah 	}
589e4e61333SAriff Abdullah 
590e4e61333SAriff Abdullah 	PCM_ACQUIRE(d);
59190da2b28SAriff Abdullah 	PCM_UNLOCK(d);
592e4e61333SAriff Abdullah 
59390da2b28SAriff Abdullah 	if (direction == PCMDIR_PLAY)
59490da2b28SAriff Abdullah 		pcm_getparentchannel(d, &c, NULL);
59590da2b28SAriff Abdullah 	else
59690da2b28SAriff Abdullah 		pcm_getparentchannel(d, NULL, &c);
59790da2b28SAriff Abdullah 
59890da2b28SAriff Abdullah 	if (c == NULL) {
59990da2b28SAriff Abdullah 		PCM_RELEASE_QUICK(d);
60090da2b28SAriff Abdullah 		return (EINVAL);
60190da2b28SAriff Abdullah 	}
60290da2b28SAriff Abdullah 
60390da2b28SAriff Abdullah 	KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
60490da2b28SAriff Abdullah 	    __func__, direction, c->direction));
60590da2b28SAriff Abdullah 
606a580b31aSAriff Abdullah 	CHN_LOCK(c);
60790da2b28SAriff Abdullah 
60890da2b28SAriff Abdullah 	bzero(fmtstr, sizeof(fmtstr));
60990da2b28SAriff Abdullah 
61090da2b28SAriff Abdullah 	if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format)
61190da2b28SAriff Abdullah 		strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
61290da2b28SAriff Abdullah 
613a580b31aSAriff Abdullah 	CHN_UNLOCK(c);
61490da2b28SAriff Abdullah 
61590da2b28SAriff Abdullah 	ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
61690da2b28SAriff Abdullah 	if (ret != 0 || req->newptr == NULL) {
617e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
61890da2b28SAriff Abdullah 		return (ret);
619a580b31aSAriff Abdullah 	}
62090da2b28SAriff Abdullah 
62190da2b28SAriff Abdullah 	newfmt = snd_str2afmt(fmtstr);
62290da2b28SAriff Abdullah 	if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
623e4e61333SAriff Abdullah 		PCM_RELEASE_QUICK(d);
624bba4862cSAriff Abdullah 		return (EINVAL);
625a580b31aSAriff Abdullah 	}
626e4e61333SAriff Abdullah 
62790da2b28SAriff Abdullah 	CHN_LOCK(c);
62890da2b28SAriff Abdullah 
62990da2b28SAriff Abdullah 	if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
63090da2b28SAriff Abdullah 		if (CHN_STARTED(c)) {
63190da2b28SAriff Abdullah 			chn_abort(c);
63290da2b28SAriff Abdullah 			restart = 1;
63390da2b28SAriff Abdullah 		} else
63490da2b28SAriff Abdullah 			restart = 0;
63590da2b28SAriff Abdullah 
63690da2b28SAriff Abdullah 		ret = chn_reset(c, newfmt, c->speed);
63790da2b28SAriff Abdullah 		if (ret == 0) {
63890da2b28SAriff Abdullah 			*vchanformat = c->format;
63990da2b28SAriff Abdullah 			if (restart != 0) {
64090da2b28SAriff Abdullah 				CHN_FOREACH(ch, c, children.busy) {
641a580b31aSAriff Abdullah 					CHN_LOCK(ch);
64290da2b28SAriff Abdullah 					if (VCHAN_SYNC_REQUIRED(ch))
64390da2b28SAriff Abdullah 						vchan_sync(ch);
644a580b31aSAriff Abdullah 					CHN_UNLOCK(ch);
645a580b31aSAriff Abdullah 				}
64690da2b28SAriff Abdullah 				c->flags |= CHN_F_DIRTY;
64790da2b28SAriff Abdullah 				ret = chn_start(c, 1);
64890da2b28SAriff Abdullah 			}
64990da2b28SAriff Abdullah 		}
65090da2b28SAriff Abdullah 	}
65190da2b28SAriff Abdullah 
65290da2b28SAriff Abdullah 	CHN_UNLOCK(c);
653e4e61333SAriff Abdullah 
654e4e61333SAriff Abdullah 	PCM_RELEASE_QUICK(d);
655e4e61333SAriff Abdullah 
65690da2b28SAriff Abdullah 	return (ret);
657a580b31aSAriff Abdullah }
65887506547SAlexander Leidinger 
659285648f9SCameron Grant /* virtual channel interface */
660285648f9SCameron Grant 
661bba4862cSAriff Abdullah #define VCHAN_FMT_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
662bba4862cSAriff Abdullah 				"play.vchanformat" : "rec.vchanformat"
663bba4862cSAriff Abdullah #define VCHAN_SPD_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
664bba4862cSAriff Abdullah 				"play.vchanrate" : "rec.vchanrate"
665bba4862cSAriff Abdullah 
666285648f9SCameron Grant int
667bba4862cSAriff Abdullah vchan_create(struct pcm_channel *parent, int num)
668285648f9SCameron Grant {
66990da2b28SAriff Abdullah 	struct snddev_info *d;
67090da2b28SAriff Abdullah 	struct pcm_channel *ch;
67197d69a96SAlexander Leidinger 	struct pcmchan_caps *parent_caps;
67290da2b28SAriff Abdullah 	uint32_t vchanfmt, vchanspd;
67390da2b28SAriff Abdullah 	int ret, direction, r, save;
67490da2b28SAriff Abdullah 
67590da2b28SAriff Abdullah 	d = parent->parentsnddev;
67697d69a96SAlexander Leidinger 
677e4e61333SAriff Abdullah 	PCM_BUSYASSERT(d);
67890da2b28SAriff Abdullah 	CHN_LOCKASSERT(parent);
679e4e61333SAriff Abdullah 
68097d69a96SAlexander Leidinger 	if (!(parent->flags & CHN_F_BUSY))
681bba4862cSAriff Abdullah 		return (EBUSY);
68297d69a96SAlexander Leidinger 
68390da2b28SAriff Abdullah 	if (!(parent->direction == PCMDIR_PLAY ||
68490da2b28SAriff Abdullah 	    parent->direction == PCMDIR_REC))
68590da2b28SAriff Abdullah 		return (EINVAL);
68690da2b28SAriff Abdullah 
68790da2b28SAriff Abdullah 	d = parent->parentsnddev;
68890da2b28SAriff Abdullah 
68990da2b28SAriff Abdullah 	CHN_UNLOCK(parent);
69090da2b28SAriff Abdullah 	PCM_LOCK(d);
69190da2b28SAriff Abdullah 
692bba4862cSAriff Abdullah 	if (parent->direction == PCMDIR_PLAY) {
693bba4862cSAriff Abdullah 		direction = PCMDIR_PLAY_VIRTUAL;
694bba4862cSAriff Abdullah 		vchanfmt = d->pvchanformat;
69590da2b28SAriff Abdullah 		vchanspd = d->pvchanrate;
69690da2b28SAriff Abdullah 	} else {
697bba4862cSAriff Abdullah 		direction = PCMDIR_REC_VIRTUAL;
698bba4862cSAriff Abdullah 		vchanfmt = d->rvchanformat;
69990da2b28SAriff Abdullah 		vchanspd = d->rvchanrate;
70090da2b28SAriff Abdullah 	}
70112e524a2SDon Lewis 
702285648f9SCameron Grant 	/* create a new playback channel */
703bba4862cSAriff Abdullah 	ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent);
704bba4862cSAriff Abdullah 	if (ch == NULL) {
70590da2b28SAriff Abdullah 		PCM_UNLOCK(d);
70612e524a2SDon Lewis 		CHN_LOCK(parent);
707bba4862cSAriff Abdullah 		return (ENODEV);
708285648f9SCameron Grant 	}
709285648f9SCameron Grant 
710285648f9SCameron Grant 	/* add us to our grandparent's channel list */
71190da2b28SAriff Abdullah 	ret = pcm_chn_add(d, ch);
71290da2b28SAriff Abdullah 	PCM_UNLOCK(d);
71390da2b28SAriff Abdullah 	if (ret != 0) {
714bba4862cSAriff Abdullah 		pcm_chn_destroy(ch);
71597d69a96SAlexander Leidinger 		CHN_LOCK(parent);
71690da2b28SAriff Abdullah 		return (ret);
717285648f9SCameron Grant 	}
718285648f9SCameron Grant 
71912e524a2SDon Lewis 	CHN_LOCK(parent);
72090da2b28SAriff Abdullah 	/*
72190da2b28SAriff Abdullah 	 * Add us to our parent channel's children in reverse order
72290da2b28SAriff Abdullah 	 * so future destruction will pick the last (biggest number)
72390da2b28SAriff Abdullah 	 * channel.
72490da2b28SAriff Abdullah 	 */
72590da2b28SAriff Abdullah 	CHN_INSERT_SORT_DESCEND(parent, ch, children);
72690da2b28SAriff Abdullah 
72790da2b28SAriff Abdullah 	if (parent->flags & CHN_F_HAS_VCHAN)
72890da2b28SAriff Abdullah 		return (0);
72990da2b28SAriff Abdullah 
73097d69a96SAlexander Leidinger 	parent->flags |= CHN_F_HAS_VCHAN;
73187506547SAlexander Leidinger 
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) {
77197efeca3SAriff Abdullah 			/*
772bba4862cSAriff Abdullah 			 * No saved value, no hint, NOTHING.
773a580b31aSAriff Abdullah 			 *
77497efeca3SAriff Abdullah 			 * Workaround for sb16 running
7759ca45dd7SAriff Abdullah 			 * poorly at 45k / 49k.
77697efeca3SAriff Abdullah 			 */
7779ca45dd7SAriff Abdullah 			switch (parent_caps->maxspeed) {
7789ca45dd7SAriff Abdullah 			case 45000:
7799ca45dd7SAriff Abdullah 			case 49000:
78090da2b28SAriff Abdullah 				vchanspd = 44100;
7819ca45dd7SAriff Abdullah 				break;
7829ca45dd7SAriff Abdullah 			default:
78390da2b28SAriff Abdullah 				vchanspd = VCHAN_DEFAULT_RATE;
78490da2b28SAriff Abdullah 				if (vchanspd > parent_caps->maxspeed)
78590da2b28SAriff Abdullah 					vchanspd = parent_caps->maxspeed;
7869ca45dd7SAriff Abdullah 				break;
7879ca45dd7SAriff Abdullah 			}
78890da2b28SAriff Abdullah 			if (vchanspd < parent_caps->minspeed)
78990da2b28SAriff Abdullah 				vchanspd = parent_caps->minspeed;
79097efeca3SAriff Abdullah 		}
79190da2b28SAriff Abdullah 		save = 1;
79287506547SAlexander Leidinger 	}
79387506547SAlexander Leidinger 
79490da2b28SAriff Abdullah 	if (ret == 0) {
79590da2b28SAriff Abdullah 		/*
79690da2b28SAriff Abdullah 		 * Limit the speed between feeder_rate_min <-> feeder_rate_max.
79790da2b28SAriff Abdullah 		 */
79890da2b28SAriff Abdullah 		if (vchanspd < feeder_rate_min)
79990da2b28SAriff Abdullah 			vchanspd = feeder_rate_min;
80090da2b28SAriff Abdullah 		if (vchanspd > feeder_rate_max)
80190da2b28SAriff Abdullah 			vchanspd = feeder_rate_max;
80290da2b28SAriff Abdullah 
803a580b31aSAriff Abdullah 		if (feeder_rate_round) {
80490da2b28SAriff Abdullah 			RANGE(vchanspd, parent_caps->minspeed,
80590da2b28SAriff Abdullah 			    parent_caps->maxspeed);
80690da2b28SAriff Abdullah 			vchanspd = CHANNEL_SETSPEED(parent->methods,
80790da2b28SAriff Abdullah 			    parent->devinfo, vchanspd);
808a580b31aSAriff Abdullah 		}
80987506547SAlexander Leidinger 
81090da2b28SAriff Abdullah 		ret = chn_reset(parent, vchanfmt, vchanspd);
811d45d1f20SAriff Abdullah 	}
81297d69a96SAlexander Leidinger 
81390da2b28SAriff Abdullah 	if (ret == 0 && save) {
81487506547SAlexander Leidinger 		/*
815bba4862cSAriff Abdullah 		 * Save new value.
81687506547SAlexander Leidinger 		 */
817bba4862cSAriff Abdullah 		if (direction == PCMDIR_PLAY_VIRTUAL) {
81890da2b28SAriff Abdullah 			d->pvchanformat = parent->format;
81990da2b28SAriff Abdullah 			d->pvchanrate = parent->speed;
820bba4862cSAriff Abdullah 		} else {
82190da2b28SAriff Abdullah 			d->rvchanformat = parent->format;
82290da2b28SAriff Abdullah 			d->rvchanrate = parent->speed;
82387506547SAlexander Leidinger 		}
824285648f9SCameron Grant 	}
825285648f9SCameron Grant 
82690da2b28SAriff Abdullah 	/*
82790da2b28SAriff Abdullah 	 * If the parent channel supports digital format,
82890da2b28SAriff Abdullah 	 * enable passthrough mode.
82990da2b28SAriff Abdullah 	 */
83090da2b28SAriff Abdullah 	if (ret == 0 && snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
83190da2b28SAriff Abdullah 		parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
83290da2b28SAriff Abdullah 		parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
83390da2b28SAriff Abdullah 	}
83490da2b28SAriff Abdullah 
83590da2b28SAriff Abdullah 	if (ret != 0) {
836bba4862cSAriff Abdullah 		CHN_REMOVE(parent, ch, children);
83797d69a96SAlexander Leidinger 		parent->flags &= ~CHN_F_HAS_VCHAN;
83897d69a96SAlexander Leidinger 		CHN_UNLOCK(parent);
83990da2b28SAriff Abdullah 		PCM_LOCK(d);
840bba4862cSAriff Abdullah 		if (pcm_chn_remove(d, ch) == 0) {
84190da2b28SAriff Abdullah 			PCM_UNLOCK(d);
842bba4862cSAriff Abdullah 			pcm_chn_destroy(ch);
843bba4862cSAriff Abdullah 		} else
84490da2b28SAriff Abdullah 			PCM_UNLOCK(d);
84597d69a96SAlexander Leidinger 		CHN_LOCK(parent);
84697d69a96SAlexander Leidinger 	}
84797d69a96SAlexander Leidinger 
84890da2b28SAriff Abdullah 	return (ret);
84997d69a96SAlexander Leidinger }
850285648f9SCameron Grant 
851285648f9SCameron Grant int
852285648f9SCameron Grant vchan_destroy(struct pcm_channel *c)
853285648f9SCameron Grant {
85490da2b28SAriff Abdullah 	struct pcm_channel *parent;
85590da2b28SAriff Abdullah 	struct snddev_info *d;
85690da2b28SAriff Abdullah 	int ret;
85790da2b28SAriff Abdullah 
85890da2b28SAriff Abdullah 	KASSERT(c != NULL && c->parentchannel != NULL &&
85990da2b28SAriff Abdullah 	    c->parentsnddev != NULL, ("%s(): invalid channel=%p",
86090da2b28SAriff Abdullah 	    __func__, c));
86190da2b28SAriff Abdullah 
86290da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
86390da2b28SAriff Abdullah 
86490da2b28SAriff Abdullah 	d = c->parentsnddev;
86590da2b28SAriff Abdullah 	parent = c->parentchannel;
866285648f9SCameron Grant 
867e4e61333SAriff Abdullah 	PCM_BUSYASSERT(d);
86890da2b28SAriff Abdullah 	CHN_LOCKASSERT(parent);
869e4e61333SAriff Abdullah 
87090da2b28SAriff Abdullah 	CHN_UNLOCK(c);
87190da2b28SAriff Abdullah 
87290da2b28SAriff Abdullah 	if (!(parent->flags & CHN_F_BUSY))
873bba4862cSAriff Abdullah 		return (EBUSY);
87490da2b28SAriff Abdullah 
87590da2b28SAriff Abdullah 	if (CHN_EMPTY(parent, children))
876bba4862cSAriff Abdullah 		return (EINVAL);
87749c5e6e2SCameron Grant 
878285648f9SCameron Grant 	/* remove us from our parent's children list */
879bba4862cSAriff Abdullah 	CHN_REMOVE(parent, c, children);
880285648f9SCameron Grant 
881bba4862cSAriff Abdullah 	if (CHN_EMPTY(parent, children)) {
8829c271ebaSAriff Abdullah 		parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
88390da2b28SAriff Abdullah 		chn_reset(parent, parent->format, parent->speed);
88497d69a96SAlexander Leidinger 	}
885f637a36cSCameron Grant 
88649c5e6e2SCameron Grant 	CHN_UNLOCK(parent);
887bba4862cSAriff Abdullah 
888bba4862cSAriff Abdullah 	/* remove us from our grandparent's channel list */
88990da2b28SAriff Abdullah 	PCM_LOCK(d);
89090da2b28SAriff Abdullah 	ret = pcm_chn_remove(d, c);
89190da2b28SAriff Abdullah 	PCM_UNLOCK(d);
892bba4862cSAriff Abdullah 
893285648f9SCameron Grant 	/* destroy ourselves */
89490da2b28SAriff Abdullah 	if (ret == 0)
89590da2b28SAriff Abdullah 		ret = pcm_chn_destroy(c);
896285648f9SCameron Grant 
89790da2b28SAriff Abdullah 	CHN_LOCK(parent);
89890da2b28SAriff Abdullah 
89990da2b28SAriff Abdullah 	return (ret);
900285648f9SCameron Grant }
901285648f9SCameron Grant 
902285648f9SCameron Grant int
90390da2b28SAriff Abdullah #ifdef SND_DEBUG
90490da2b28SAriff Abdullah vchan_passthrough(struct pcm_channel *c, const char *caller)
90590da2b28SAriff Abdullah #else
90690da2b28SAriff Abdullah vchan_sync(struct pcm_channel *c)
90790da2b28SAriff Abdullah #endif
90890da2b28SAriff Abdullah {
90990da2b28SAriff Abdullah 	int ret;
91090da2b28SAriff Abdullah 
91190da2b28SAriff Abdullah 	KASSERT(c != NULL && c->parentchannel != NULL &&
91290da2b28SAriff Abdullah 	    (c->flags & CHN_F_VIRTUAL),
91390da2b28SAriff Abdullah 	    ("%s(): invalid passthrough", __func__));
91490da2b28SAriff Abdullah 	CHN_LOCKASSERT(c);
91590da2b28SAriff Abdullah 	CHN_LOCKASSERT(c->parentchannel);
91690da2b28SAriff Abdullah 
91790da2b28SAriff Abdullah 	sndbuf_setspd(c->bufhard, c->parentchannel->speed);
91890da2b28SAriff Abdullah 	c->flags |= CHN_F_PASSTHROUGH;
91990da2b28SAriff Abdullah 	ret = feeder_chain(c);
92090da2b28SAriff Abdullah 	c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
92190da2b28SAriff Abdullah 	if (ret != 0)
92290da2b28SAriff Abdullah 		c->flags |= CHN_F_DIRTY;
92390da2b28SAriff Abdullah 
92490da2b28SAriff Abdullah #ifdef SND_DEBUG
92590da2b28SAriff Abdullah 	if (snd_passthrough_verbose != 0) {
92690da2b28SAriff Abdullah 		char *devname, buf[CHN_NAMELEN];
92790da2b28SAriff Abdullah 
92890da2b28SAriff Abdullah 		devname = dsp_unit2name(buf, sizeof(buf), c->unit);
92990da2b28SAriff Abdullah 		device_printf(c->dev,
93090da2b28SAriff Abdullah 		    "%s(%s/%s) %s() -> re-sync err=%d\n",
93190da2b28SAriff Abdullah 		    __func__, (devname != NULL) ? devname : "dspX", c->comm,
93290da2b28SAriff Abdullah 		    caller, ret);
93390da2b28SAriff Abdullah 	}
93490da2b28SAriff Abdullah #endif
93590da2b28SAriff Abdullah 
93690da2b28SAriff Abdullah 	return (ret);
93790da2b28SAriff Abdullah }
93890da2b28SAriff Abdullah 
93990da2b28SAriff Abdullah void
94067b1dce3SCameron Grant vchan_initsys(device_t dev)
941285648f9SCameron Grant {
94267b1dce3SCameron Grant 	struct snddev_info *d;
943bba4862cSAriff Abdullah 	int unit;
94467b1dce3SCameron Grant 
945bba4862cSAriff Abdullah 	unit = device_get_unit(dev);
94667b1dce3SCameron Grant 	d = device_get_softc(dev);
947bba4862cSAriff Abdullah 
948bba4862cSAriff Abdullah 	/* Play */
949bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
950bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
951*7029da5cSPawel Biernacki 	    OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
952bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
95390da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
95490da2b28SAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
95590da2b28SAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
956*7029da5cSPawel Biernacki 	    OID_AUTO, "vchanmode",
957*7029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
95890da2b28SAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
95990da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanmode, "A",
96090da2b28SAriff Abdullah 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
961bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
962bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
963*7029da5cSPawel Biernacki 	    OID_AUTO, "vchanrate",
964*7029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
965bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
96690da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
967bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
968bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
969*7029da5cSPawel Biernacki 	    OID_AUTO, "vchanformat",
970*7029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
971bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
97290da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
973bba4862cSAriff Abdullah 	/* Rec */
974bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
975bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
976*7029da5cSPawel Biernacki 	    OID_AUTO, "vchans",
977*7029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
978bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
97990da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
98090da2b28SAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
98190da2b28SAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
982*7029da5cSPawel Biernacki 	    OID_AUTO, "vchanmode",
983*7029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
98490da2b28SAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
98590da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanmode, "A",
98690da2b28SAriff Abdullah 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
987bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
988bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
989*7029da5cSPawel Biernacki 	    OID_AUTO, "vchanrate",
990*7029da5cSPawel Biernacki 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
991bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
99290da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
993bba4862cSAriff Abdullah 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
994bba4862cSAriff Abdullah 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
995*7029da5cSPawel Biernacki 	    OID_AUTO, "vchanformat",
996*7029da5cSPawel Biernacki 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
997bba4862cSAriff Abdullah 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
99890da2b28SAriff Abdullah 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
999285648f9SCameron Grant }
1000