xref: /freebsd/sys/dev/sound/pcm/vchan.c (revision 3612ef642f511a1bd9f759da87abeafe7d6ff110)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2006-2009 Ariff Abdullah <ariff@FreeBSD.org>
5  * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6  * All rights reserved.
7  * Copyright (c) 2024-2025 The FreeBSD Foundation
8  *
9  * Portions of this software were developed by Christos Margiolis
10  * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 /* Almost entirely rewritten to add multi-format/channels mixing support. */
35 
36 #ifdef HAVE_KERNEL_OPTION_HEADERS
37 #include "opt_snd.h"
38 #endif
39 
40 #include <dev/sound/pcm/sound.h>
41 #include <dev/sound/pcm/vchan.h>
42 
43 /*
44  * [ac3 , dts , linear , 0, linear, 0]
45  */
46 #define FMTLIST_MAX		6
47 #define FMTLIST_OFFSET		4
48 #define DIGFMTS_MAX		2
49 
50 struct vchan_info {
51 	struct pcm_channel *channel;
52 	struct pcmchan_caps caps;
53 	uint32_t fmtlist[FMTLIST_MAX];
54 	int trigger;
55 };
56 
57 bool snd_vchans_enable = true;
58 
59 static void *
vchan_init(kobj_t obj,void * devinfo,struct snd_dbuf * b,struct pcm_channel * c,int dir)60 vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
61     struct pcm_channel *c, int dir)
62 {
63 	struct vchan_info *info;
64 	struct pcm_channel *p;
65 	uint32_t i, j, *fmtlist;
66 
67 	KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
68 	    ("vchan_init: bad direction"));
69 	KASSERT(c != NULL && c->parentchannel != NULL,
70 	    ("vchan_init: bad channels"));
71 
72 	info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
73 	info->channel = c;
74 	info->trigger = PCMTRIG_STOP;
75 	p = c->parentchannel;
76 
77 	CHN_LOCK(p);
78 
79 	fmtlist = chn_getcaps(p)->fmtlist;
80 	for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
81 		if (fmtlist[i] & AFMT_PASSTHROUGH)
82 			info->fmtlist[j++] = fmtlist[i];
83 	}
84 	if (p->format & AFMT_VCHAN)
85 		info->fmtlist[j] = p->format;
86 	else
87 		info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
88 	info->caps.fmtlist = info->fmtlist +
89 	    ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
90 
91 	CHN_UNLOCK(p);
92 
93 	c->flags |= CHN_F_VIRTUAL;
94 
95 	return (info);
96 }
97 
98 static int
vchan_free(kobj_t obj,void * data)99 vchan_free(kobj_t obj, void *data)
100 {
101 
102 	free(data, M_DEVBUF);
103 
104 	return (0);
105 }
106 
107 static int
vchan_setformat(kobj_t obj,void * data,uint32_t format)108 vchan_setformat(kobj_t obj, void *data, uint32_t format)
109 {
110 	struct vchan_info *info;
111 
112 	info = data;
113 
114 	CHN_LOCKASSERT(info->channel);
115 
116 	if (!snd_fmtvalid(format, info->caps.fmtlist))
117 		return (-1);
118 
119 	return (0);
120 }
121 
122 static uint32_t
vchan_setspeed(kobj_t obj,void * data,uint32_t speed)123 vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
124 {
125 	struct vchan_info *info;
126 
127 	info = data;
128 
129 	CHN_LOCKASSERT(info->channel);
130 
131 	return (info->caps.maxspeed);
132 }
133 
134 static int
vchan_trigger(kobj_t obj,void * data,int go)135 vchan_trigger(kobj_t obj, void *data, int go)
136 {
137 	struct vchan_info *info;
138 	struct pcm_channel *c, *p;
139 	int ret, otrigger;
140 
141 	info = data;
142 	c = info->channel;
143 	p = c->parentchannel;
144 
145 	CHN_LOCKASSERT(c);
146 	if (!PCMTRIG_COMMON(go) || go == info->trigger)
147 		return (0);
148 
149 	CHN_UNLOCK(c);
150 	CHN_LOCK(p);
151 
152 	otrigger = info->trigger;
153 	info->trigger = go;
154 
155 	switch (go) {
156 	case PCMTRIG_START:
157 		if (otrigger != PCMTRIG_START)
158 			CHN_INSERT_HEAD(p, c, children.busy);
159 		break;
160 	case PCMTRIG_STOP:
161 	case PCMTRIG_ABORT:
162 		if (otrigger == PCMTRIG_START)
163 			CHN_REMOVE(p, c, children.busy);
164 		break;
165 	default:
166 		break;
167 	}
168 
169 	ret = chn_notify(p, CHN_N_TRIGGER);
170 
171 	CHN_LOCK(c);
172 
173 	if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
174 		ret = vchan_sync(c);
175 
176 	CHN_UNLOCK(c);
177 	CHN_UNLOCK(p);
178 	CHN_LOCK(c);
179 
180 	return (ret);
181 }
182 
183 static struct pcmchan_caps *
vchan_getcaps(kobj_t obj,void * data)184 vchan_getcaps(kobj_t obj, void *data)
185 {
186 	struct vchan_info *info;
187 	struct pcm_channel *c;
188 	uint32_t pformat, pspeed, pflags, i;
189 
190 	info = data;
191 	c = info->channel;
192 	pformat = c->parentchannel->format;
193 	pspeed = c->parentchannel->speed;
194 	pflags = c->parentchannel->flags;
195 
196 	CHN_LOCKASSERT(c);
197 
198 	if (pflags & CHN_F_VCHAN_DYNAMIC) {
199 		info->caps.fmtlist = info->fmtlist;
200 		if (pformat & AFMT_VCHAN) {
201 			for (i = 0; info->caps.fmtlist[i] != 0; i++) {
202 				if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
203 					continue;
204 				break;
205 			}
206 			info->caps.fmtlist[i] = pformat;
207 		}
208 		if (c->format & AFMT_PASSTHROUGH)
209 			info->caps.minspeed = c->speed;
210 		else
211 			info->caps.minspeed = pspeed;
212 		info->caps.maxspeed = info->caps.minspeed;
213 	} else {
214 		info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
215 		if (pformat & AFMT_VCHAN)
216 			info->caps.fmtlist[0] = pformat;
217 		else {
218 			device_printf(c->dev,
219 			    "%s(): invalid vchan format 0x%08x",
220 			    __func__, pformat);
221 			info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
222 		}
223 		info->caps.minspeed = pspeed;
224 		info->caps.maxspeed = info->caps.minspeed;
225 	}
226 
227 	return (&info->caps);
228 }
229 
230 static struct pcmchan_matrix *
vchan_getmatrix(kobj_t obj,void * data,uint32_t format)231 vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
232 {
233 
234 	return (feeder_matrix_format_map(format));
235 }
236 
237 static kobj_method_t vchan_methods[] = {
238 	KOBJMETHOD(channel_init,		vchan_init),
239 	KOBJMETHOD(channel_free,		vchan_free),
240 	KOBJMETHOD(channel_setformat,		vchan_setformat),
241 	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
242 	KOBJMETHOD(channel_trigger,		vchan_trigger),
243 	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
244 	KOBJMETHOD(channel_getmatrix,		vchan_getmatrix),
245 	KOBJMETHOD_END
246 };
247 CHANNEL_DECLARE(vchan);
248 
249 static int
sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)250 sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
251 {
252 	struct snddev_info *d;
253 	int err, enabled, flag;
254 
255 	bus_topo_lock();
256 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
257 	if (!PCM_REGISTERED(d)) {
258 		bus_topo_unlock();
259 		return (EINVAL);
260 	}
261 	bus_topo_unlock();
262 
263 	PCM_LOCK(d);
264 	PCM_WAIT(d);
265 
266 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
267 	case VCHAN_PLAY:
268 		/* Exit if we do not support this direction. */
269 		if (d->playcount < 1) {
270 			PCM_UNLOCK(d);
271 			return (ENODEV);
272 		}
273 		flag = SD_F_PVCHANS;
274 		break;
275 	case VCHAN_REC:
276 		if (d->reccount < 1) {
277 			PCM_UNLOCK(d);
278 			return (ENODEV);
279 		}
280 		flag = SD_F_RVCHANS;
281 		break;
282 	default:
283 		PCM_UNLOCK(d);
284 		return (EINVAL);
285 	}
286 
287 	enabled = (d->flags & flag) != 0;
288 
289 	PCM_ACQUIRE(d);
290 	PCM_UNLOCK(d);
291 
292 	err = sysctl_handle_int(oidp, &enabled, 0, req);
293 	if (err != 0 || req->newptr == NULL) {
294 		PCM_RELEASE_QUICK(d);
295 		return (err);
296 	}
297 
298 	if (enabled <= 0)
299 		d->flags &= ~flag;
300 	else
301 		d->flags |= flag;
302 
303 	PCM_RELEASE_QUICK(d);
304 
305 	return (0);
306 }
307 
308 static int
sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)309 sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
310 {
311 	struct snddev_info *d;
312 	struct pcm_channel *c;
313 	uint32_t dflags;
314 	int *vchanmode, direction, ret;
315 	char dtype[16];
316 
317 	bus_topo_lock();
318 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
319 	if (!PCM_REGISTERED(d)) {
320 		bus_topo_unlock();
321 		return (EINVAL);
322 	}
323 	bus_topo_unlock();
324 
325 	PCM_LOCK(d);
326 	PCM_WAIT(d);
327 
328 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
329 	case VCHAN_PLAY:
330 		if ((d->flags & SD_F_PVCHANS) == 0) {
331 			PCM_UNLOCK(d);
332 			return (ENODEV);
333 		}
334 		direction = PCMDIR_PLAY;
335 		vchanmode = &d->pvchanmode;
336 		break;
337 	case VCHAN_REC:
338 		if ((d->flags & SD_F_RVCHANS) == 0) {
339 			PCM_UNLOCK(d);
340 			return (ENODEV);
341 		}
342 		direction = PCMDIR_REC;
343 		vchanmode = &d->rvchanmode;
344 		break;
345 	default:
346 		PCM_UNLOCK(d);
347 		return (EINVAL);
348 	}
349 
350 	PCM_ACQUIRE(d);
351 	PCM_UNLOCK(d);
352 
353 	if (*vchanmode & CHN_F_VCHAN_PASSTHROUGH)
354 		strlcpy(dtype, "passthrough", sizeof(dtype));
355 	else if (*vchanmode & CHN_F_VCHAN_ADAPTIVE)
356 		strlcpy(dtype, "adaptive", sizeof(dtype));
357 	else
358 		strlcpy(dtype, "fixed", sizeof(dtype));
359 
360 	ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
361 	if (ret != 0 || req->newptr == NULL) {
362 		PCM_RELEASE_QUICK(d);
363 		return (ret);
364 	}
365 
366 	if (strcasecmp(dtype, "passthrough") == 0 || strcmp(dtype, "1") == 0)
367 		dflags = CHN_F_VCHAN_PASSTHROUGH;
368 	else if (strcasecmp(dtype, "adaptive") == 0 || strcmp(dtype, "2") == 0)
369 		dflags = CHN_F_VCHAN_ADAPTIVE;
370 	else if (strcasecmp(dtype, "fixed") == 0 || strcmp(dtype, "0") == 0)
371 		dflags = 0;
372 	else {
373 		PCM_RELEASE_QUICK(d);
374 		return (EINVAL);
375 	}
376 
377 	CHN_FOREACH(c, d, channels.pcm.primary) {
378 		CHN_LOCK(c);
379 		if (c->direction != direction ||
380 		    dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
381 		    (c->flags & CHN_F_PASSTHROUGH)) {
382 			CHN_UNLOCK(c);
383 			continue;
384 		}
385 		c->flags &= ~CHN_F_VCHAN_DYNAMIC;
386 		c->flags |= dflags;
387 		CHN_UNLOCK(c);
388 		*vchanmode = dflags;
389 	}
390 
391 	PCM_RELEASE_QUICK(d);
392 
393 	return (ret);
394 }
395 
396 /*
397  * On the fly vchan rate/format settings
398  */
399 
400 #define VCHAN_ACCESSIBLE(c)	(!((c)->flags & (CHN_F_PASSTHROUGH |	\
401 				 CHN_F_EXCLUSIVE)) &&			\
402 				 (((c)->flags & CHN_F_VCHAN_DYNAMIC) ||	\
403 				 CHN_STOPPED(c)))
404 static int
sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)405 sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
406 {
407 	struct snddev_info *d;
408 	struct pcm_channel *c, *ch;
409 	int *vchanrate, direction, ret, newspd, restart;
410 
411 	bus_topo_lock();
412 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
413 	if (!PCM_REGISTERED(d)) {
414 		bus_topo_unlock();
415 		return (EINVAL);
416 	}
417 	bus_topo_unlock();
418 
419 	PCM_LOCK(d);
420 	PCM_WAIT(d);
421 
422 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
423 	case VCHAN_PLAY:
424 		if ((d->flags & SD_F_PVCHANS) == 0) {
425 			PCM_UNLOCK(d);
426 			return (ENODEV);
427 		}
428 		direction = PCMDIR_PLAY;
429 		vchanrate = &d->pvchanrate;
430 		break;
431 	case VCHAN_REC:
432 		if ((d->flags & SD_F_RVCHANS) == 0) {
433 			PCM_UNLOCK(d);
434 			return (ENODEV);
435 		}
436 		direction = PCMDIR_REC;
437 		vchanrate = &d->rvchanrate;
438 		break;
439 	default:
440 		PCM_UNLOCK(d);
441 		return (EINVAL);
442 	}
443 
444 	PCM_ACQUIRE(d);
445 	PCM_UNLOCK(d);
446 
447 	newspd = *vchanrate;
448 
449 	ret = sysctl_handle_int(oidp, &newspd, 0, req);
450 	if (ret != 0 || req->newptr == NULL) {
451 		PCM_RELEASE_QUICK(d);
452 		return (ret);
453 	}
454 
455 	if (newspd < feeder_rate_min || newspd > feeder_rate_max) {
456 		PCM_RELEASE_QUICK(d);
457 		return (EINVAL);
458 	}
459 
460 	CHN_FOREACH(c, d, channels.pcm.primary) {
461 		CHN_LOCK(c);
462 		if (c->direction != direction) {
463 			CHN_UNLOCK(c);
464 			continue;
465 		}
466 
467 		if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
468 			if (CHN_STARTED(c)) {
469 				chn_abort(c);
470 				restart = 1;
471 			} else
472 				restart = 0;
473 
474 			ret = chn_reset(c, c->format, newspd);
475 			if (ret == 0) {
476 				if (restart != 0) {
477 					CHN_FOREACH(ch, c, children.busy) {
478 						CHN_LOCK(ch);
479 						if (VCHAN_SYNC_REQUIRED(ch))
480 							vchan_sync(ch);
481 						CHN_UNLOCK(ch);
482 					}
483 					c->flags |= CHN_F_DIRTY;
484 					ret = chn_start(c, 1);
485 				}
486 			}
487 		}
488 		*vchanrate = c->bufsoft->spd;
489 
490 		CHN_UNLOCK(c);
491 	}
492 
493 	PCM_RELEASE_QUICK(d);
494 
495 	return (ret);
496 }
497 
498 static int
sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)499 sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
500 {
501 	struct snddev_info *d;
502 	struct pcm_channel *c, *ch;
503 	uint32_t newfmt;
504 	int *vchanformat, direction, ret, restart;
505 	char fmtstr[AFMTSTR_LEN];
506 
507 	bus_topo_lock();
508 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
509 	if (!PCM_REGISTERED(d)) {
510 		bus_topo_unlock();
511 		return (EINVAL);
512 	}
513 	bus_topo_unlock();
514 
515 	PCM_LOCK(d);
516 	PCM_WAIT(d);
517 
518 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
519 	case VCHAN_PLAY:
520 		if ((d->flags & SD_F_PVCHANS) == 0) {
521 			PCM_UNLOCK(d);
522 			return (ENODEV);
523 		}
524 		direction = PCMDIR_PLAY;
525 		vchanformat = &d->pvchanformat;
526 		break;
527 	case VCHAN_REC:
528 		if ((d->flags & SD_F_RVCHANS) == 0) {
529 			PCM_UNLOCK(d);
530 			return (ENODEV);
531 		}
532 		direction = PCMDIR_REC;
533 		vchanformat = &d->rvchanformat;
534 		break;
535 	default:
536 		PCM_UNLOCK(d);
537 		return (EINVAL);
538 	}
539 
540 	PCM_ACQUIRE(d);
541 	PCM_UNLOCK(d);
542 
543 	bzero(fmtstr, sizeof(fmtstr));
544 
545 	if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat)
546 		strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
547 
548 	ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
549 	if (ret != 0 || req->newptr == NULL) {
550 		PCM_RELEASE_QUICK(d);
551 		return (ret);
552 	}
553 
554 	newfmt = snd_str2afmt(fmtstr);
555 	if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
556 		PCM_RELEASE_QUICK(d);
557 		return (EINVAL);
558 	}
559 
560 	CHN_FOREACH(c, d, channels.pcm.primary) {
561 		CHN_LOCK(c);
562 		if (c->direction != direction) {
563 			CHN_UNLOCK(c);
564 			continue;
565 		}
566 		if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
567 			if (CHN_STARTED(c)) {
568 				chn_abort(c);
569 				restart = 1;
570 			} else
571 				restart = 0;
572 
573 			ret = chn_reset(c, newfmt, c->speed);
574 			if (ret == 0) {
575 				if (restart != 0) {
576 					CHN_FOREACH(ch, c, children.busy) {
577 						CHN_LOCK(ch);
578 						if (VCHAN_SYNC_REQUIRED(ch))
579 							vchan_sync(ch);
580 						CHN_UNLOCK(ch);
581 					}
582 					c->flags |= CHN_F_DIRTY;
583 					ret = chn_start(c, 1);
584 				}
585 			}
586 		}
587 		*vchanformat = c->bufsoft->fmt;
588 
589 		CHN_UNLOCK(c);
590 	}
591 
592 	PCM_RELEASE_QUICK(d);
593 
594 	return (ret);
595 }
596 
597 /* virtual channel interface */
598 
599 #define VCHAN_FMT_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
600 				"play.vchanformat" : "rec.vchanformat"
601 #define VCHAN_SPD_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
602 				"play.vchanrate" : "rec.vchanrate"
603 
604 int
vchan_create(struct pcm_channel * parent,struct pcm_channel ** child)605 vchan_create(struct pcm_channel *parent, struct pcm_channel **child)
606 {
607 	struct snddev_info *d;
608 	struct pcm_channel *ch;
609 	struct pcmchan_caps *parent_caps;
610 	uint32_t vchanfmt, vchanspd;
611 	int ret, direction;
612 
613 	ret = 0;
614 	d = parent->parentsnddev;
615 
616 	PCM_BUSYASSERT(d);
617 	CHN_LOCKASSERT(parent);
618 
619 	if (!(parent->direction == PCMDIR_PLAY ||
620 	    parent->direction == PCMDIR_REC))
621 		return (EINVAL);
622 
623 	CHN_UNLOCK(parent);
624 	PCM_LOCK(d);
625 
626 	if (parent->direction == PCMDIR_PLAY) {
627 		direction = PCMDIR_PLAY_VIRTUAL;
628 		vchanfmt = d->pvchanformat;
629 		vchanspd = d->pvchanrate;
630 	} else {
631 		direction = PCMDIR_REC_VIRTUAL;
632 		vchanfmt = d->rvchanformat;
633 		vchanspd = d->rvchanrate;
634 	}
635 
636 	/* create a new playback channel */
637 	ch = chn_init(d, parent, &vchan_class, direction, parent);
638 	if (ch == NULL) {
639 		PCM_UNLOCK(d);
640 		CHN_LOCK(parent);
641 		return (ENODEV);
642 	}
643 	PCM_UNLOCK(d);
644 
645 	CHN_LOCK(parent);
646 	CHN_INSERT_SORT_ASCEND(parent, ch, children);
647 
648 	*child = ch;
649 
650 	if (parent->flags & CHN_F_HAS_VCHAN)
651 		return (0);
652 
653 	parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY;
654 
655 	parent_caps = chn_getcaps(parent);
656 	if (parent_caps == NULL) {
657 		ret = EINVAL;
658 		goto fail;
659 	}
660 
661 	if ((ret = chn_reset(parent, vchanfmt, vchanspd)) != 0)
662 		goto fail;
663 
664 	/*
665 	 * If the parent channel supports digital format,
666 	 * enable passthrough mode.
667 	 */
668 	if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
669 		parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
670 		parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
671 	}
672 
673 	return (ret);
674 
675 fail:
676 	CHN_LOCK(ch);
677 	vchan_destroy(ch);
678 	*child = NULL;
679 
680 	return (ret);
681 }
682 
683 int
vchan_destroy(struct pcm_channel * c)684 vchan_destroy(struct pcm_channel *c)
685 {
686 	struct pcm_channel *parent;
687 
688 	KASSERT(c != NULL && c->parentchannel != NULL &&
689 	    c->parentsnddev != NULL, ("%s(): invalid channel=%p",
690 	    __func__, c));
691 
692 	CHN_LOCKASSERT(c);
693 
694 	parent = c->parentchannel;
695 
696 	PCM_BUSYASSERT(c->parentsnddev);
697 	CHN_LOCKASSERT(parent);
698 
699 	CHN_UNLOCK(c);
700 
701 	/* remove us from our parent's children list */
702 	CHN_REMOVE(parent, c, children);
703 	if (CHN_EMPTY(parent, children)) {
704 		parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
705 		chn_reset(parent, parent->format, parent->speed);
706 	}
707 
708 	CHN_UNLOCK(parent);
709 
710 	/* destroy ourselves */
711 	chn_kill(c);
712 
713 	CHN_LOCK(parent);
714 
715 	return (0);
716 }
717 
718 int
vchan_sync(struct pcm_channel * c)719 vchan_sync(struct pcm_channel *c)
720 {
721 	int ret;
722 
723 	KASSERT(c != NULL && c->parentchannel != NULL &&
724 	    (c->flags & CHN_F_VIRTUAL),
725 	    ("%s(): invalid passthrough", __func__));
726 	CHN_LOCKASSERT(c);
727 	CHN_LOCKASSERT(c->parentchannel);
728 
729 	sndbuf_setspd(c->bufhard, c->parentchannel->speed);
730 	c->flags |= CHN_F_PASSTHROUGH;
731 	ret = feeder_chain(c);
732 	c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
733 	if (ret != 0)
734 		c->flags |= CHN_F_DIRTY;
735 
736 	return (ret);
737 }
738 
739 static int
sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS)740 sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS)
741 {
742 	struct snddev_info *d;
743 	int i, v, error;
744 
745 	v = snd_vchans_enable;
746 	error = sysctl_handle_int(oidp, &v, 0, req);
747 	if (error != 0 || req->newptr == NULL)
748 		return (error);
749 
750 	bus_topo_lock();
751 	snd_vchans_enable = v >= 1;
752 
753 	for (i = 0; pcm_devclass != NULL &&
754 	    i < devclass_get_maxunit(pcm_devclass); i++) {
755 		d = devclass_get_softc(pcm_devclass, i);
756 		if (!PCM_REGISTERED(d))
757 			continue;
758 		PCM_ACQUIRE_QUICK(d);
759 		if (snd_vchans_enable) {
760 			if (d->playcount > 0)
761 				d->flags |= SD_F_PVCHANS;
762 			if (d->reccount > 0)
763 				d->flags |= SD_F_RVCHANS;
764 		} else
765 			d->flags &= ~(SD_F_PVCHANS | SD_F_RVCHANS);
766 		PCM_RELEASE_QUICK(d);
767 	}
768 	bus_topo_unlock();
769 
770 	return (0);
771 }
772 SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable,
773     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int),
774     sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch");
775 
776 void
vchan_initsys(device_t dev)777 vchan_initsys(device_t dev)
778 {
779 	struct snddev_info *d;
780 	int unit;
781 
782 	unit = device_get_unit(dev);
783 	d = device_get_softc(dev);
784 
785 	/* Play */
786 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
787 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
788 	    OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
789 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
790 	    sysctl_dev_pcm_vchans, "I", "virtual channels enabled");
791 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
792 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
793 	    OID_AUTO, "vchanmode",
794 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
795 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
796 	    sysctl_dev_pcm_vchanmode, "A",
797 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
798 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
799 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
800 	    OID_AUTO, "vchanrate",
801 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
802 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
803 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
804 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
805 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
806 	    OID_AUTO, "vchanformat",
807 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
808 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
809 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
810 	/* Rec */
811 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
812 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
813 	    OID_AUTO, "vchans",
814 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
815 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
816 	    sysctl_dev_pcm_vchans, "I", "virtual channels enabled");
817 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
818 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
819 	    OID_AUTO, "vchanmode",
820 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
821 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
822 	    sysctl_dev_pcm_vchanmode, "A",
823 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
824 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
825 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
826 	    OID_AUTO, "vchanrate",
827 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
828 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
829 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
830 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
831 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
832 	    OID_AUTO, "vchanformat",
833 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
834 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
835 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
836 }
837