xref: /freebsd/sys/dev/sound/pcm/vchan.c (revision c824383b269d8abe175ea4751194660716d5600e)
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 #ifdef SND_DEBUG
51 static int snd_passthrough_verbose = 0;
52 SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RWTUN,
53 	&snd_passthrough_verbose, 0, "passthrough verbosity");
54 
55 #endif
56 
57 struct vchan_info {
58 	struct pcm_channel *channel;
59 	struct pcmchan_caps caps;
60 	uint32_t fmtlist[FMTLIST_MAX];
61 	int trigger;
62 };
63 
64 bool snd_vchans_enable = true;
65 
66 static void *
vchan_init(kobj_t obj,void * devinfo,struct snd_dbuf * b,struct pcm_channel * c,int dir)67 vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
68     struct pcm_channel *c, int dir)
69 {
70 	struct vchan_info *info;
71 	struct pcm_channel *p;
72 	uint32_t i, j, *fmtlist;
73 
74 	KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
75 	    ("vchan_init: bad direction"));
76 	KASSERT(c != NULL && c->parentchannel != NULL,
77 	    ("vchan_init: bad channels"));
78 
79 	info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
80 	info->channel = c;
81 	info->trigger = PCMTRIG_STOP;
82 	p = c->parentchannel;
83 
84 	CHN_LOCK(p);
85 
86 	fmtlist = chn_getcaps(p)->fmtlist;
87 	for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
88 		if (fmtlist[i] & AFMT_PASSTHROUGH)
89 			info->fmtlist[j++] = fmtlist[i];
90 	}
91 	if (p->format & AFMT_VCHAN)
92 		info->fmtlist[j] = p->format;
93 	else
94 		info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
95 	info->caps.fmtlist = info->fmtlist +
96 	    ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
97 
98 	CHN_UNLOCK(p);
99 
100 	c->flags |= CHN_F_VIRTUAL;
101 
102 	return (info);
103 }
104 
105 static int
vchan_free(kobj_t obj,void * data)106 vchan_free(kobj_t obj, void *data)
107 {
108 
109 	free(data, M_DEVBUF);
110 
111 	return (0);
112 }
113 
114 static int
vchan_setformat(kobj_t obj,void * data,uint32_t format)115 vchan_setformat(kobj_t obj, void *data, uint32_t format)
116 {
117 	struct vchan_info *info;
118 
119 	info = data;
120 
121 	CHN_LOCKASSERT(info->channel);
122 
123 	if (!snd_fmtvalid(format, info->caps.fmtlist))
124 		return (-1);
125 
126 	return (0);
127 }
128 
129 static uint32_t
vchan_setspeed(kobj_t obj,void * data,uint32_t speed)130 vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
131 {
132 	struct vchan_info *info;
133 
134 	info = data;
135 
136 	CHN_LOCKASSERT(info->channel);
137 
138 	return (info->caps.maxspeed);
139 }
140 
141 static int
vchan_trigger(kobj_t obj,void * data,int go)142 vchan_trigger(kobj_t obj, void *data, int go)
143 {
144 	struct vchan_info *info;
145 	struct pcm_channel *c, *p;
146 	int ret, otrigger;
147 
148 	info = data;
149 	c = info->channel;
150 	p = c->parentchannel;
151 
152 	CHN_LOCKASSERT(c);
153 	if (!PCMTRIG_COMMON(go) || go == info->trigger)
154 		return (0);
155 
156 	CHN_UNLOCK(c);
157 	CHN_LOCK(p);
158 
159 	otrigger = info->trigger;
160 	info->trigger = go;
161 
162 	switch (go) {
163 	case PCMTRIG_START:
164 		if (otrigger != PCMTRIG_START)
165 			CHN_INSERT_HEAD(p, c, children.busy);
166 		break;
167 	case PCMTRIG_STOP:
168 	case PCMTRIG_ABORT:
169 		if (otrigger == PCMTRIG_START)
170 			CHN_REMOVE(p, c, children.busy);
171 		break;
172 	default:
173 		break;
174 	}
175 
176 	ret = chn_notify(p, CHN_N_TRIGGER);
177 
178 	CHN_LOCK(c);
179 
180 	if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
181 		ret = vchan_sync(c);
182 
183 	CHN_UNLOCK(c);
184 	CHN_UNLOCK(p);
185 	CHN_LOCK(c);
186 
187 	return (ret);
188 }
189 
190 static struct pcmchan_caps *
vchan_getcaps(kobj_t obj,void * data)191 vchan_getcaps(kobj_t obj, void *data)
192 {
193 	struct vchan_info *info;
194 	struct pcm_channel *c;
195 	uint32_t pformat, pspeed, pflags, i;
196 
197 	info = data;
198 	c = info->channel;
199 	pformat = c->parentchannel->format;
200 	pspeed = c->parentchannel->speed;
201 	pflags = c->parentchannel->flags;
202 
203 	CHN_LOCKASSERT(c);
204 
205 	if (pflags & CHN_F_VCHAN_DYNAMIC) {
206 		info->caps.fmtlist = info->fmtlist;
207 		if (pformat & AFMT_VCHAN) {
208 			for (i = 0; info->caps.fmtlist[i] != 0; i++) {
209 				if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
210 					continue;
211 				break;
212 			}
213 			info->caps.fmtlist[i] = pformat;
214 		}
215 		if (c->format & AFMT_PASSTHROUGH)
216 			info->caps.minspeed = c->speed;
217 		else
218 			info->caps.minspeed = pspeed;
219 		info->caps.maxspeed = info->caps.minspeed;
220 	} else {
221 		info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
222 		if (pformat & AFMT_VCHAN)
223 			info->caps.fmtlist[0] = pformat;
224 		else {
225 			device_printf(c->dev,
226 			    "%s(): invalid vchan format 0x%08x",
227 			    __func__, pformat);
228 			info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
229 		}
230 		info->caps.minspeed = pspeed;
231 		info->caps.maxspeed = info->caps.minspeed;
232 	}
233 
234 	return (&info->caps);
235 }
236 
237 static struct pcmchan_matrix *
vchan_getmatrix(kobj_t obj,void * data,uint32_t format)238 vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
239 {
240 
241 	return (feeder_matrix_format_map(format));
242 }
243 
244 static kobj_method_t vchan_methods[] = {
245 	KOBJMETHOD(channel_init,		vchan_init),
246 	KOBJMETHOD(channel_free,		vchan_free),
247 	KOBJMETHOD(channel_setformat,		vchan_setformat),
248 	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
249 	KOBJMETHOD(channel_trigger,		vchan_trigger),
250 	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
251 	KOBJMETHOD(channel_getmatrix,		vchan_getmatrix),
252 	KOBJMETHOD_END
253 };
254 CHANNEL_DECLARE(vchan);
255 
256 static int
sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)257 sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
258 {
259 	struct snddev_info *d;
260 	int err, enabled, flag;
261 
262 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
263 	if (!PCM_REGISTERED(d))
264 		return (EINVAL);
265 
266 	PCM_LOCK(d);
267 	PCM_WAIT(d);
268 
269 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
270 	case VCHAN_PLAY:
271 		/* Exit if we do not support this direction. */
272 		if (d->playcount < 1) {
273 			PCM_UNLOCK(d);
274 			return (ENODEV);
275 		}
276 		flag = SD_F_PVCHANS;
277 		break;
278 	case VCHAN_REC:
279 		if (d->reccount < 1) {
280 			PCM_UNLOCK(d);
281 			return (ENODEV);
282 		}
283 		flag = SD_F_RVCHANS;
284 		break;
285 	default:
286 		PCM_UNLOCK(d);
287 		return (EINVAL);
288 	}
289 
290 	enabled = (d->flags & flag) != 0;
291 
292 	PCM_ACQUIRE(d);
293 	PCM_UNLOCK(d);
294 
295 	err = sysctl_handle_int(oidp, &enabled, 0, req);
296 	if (err != 0 || req->newptr == NULL) {
297 		PCM_RELEASE_QUICK(d);
298 		return (err);
299 	}
300 
301 	if (enabled <= 0)
302 		d->flags &= ~flag;
303 	else
304 		d->flags |= flag;
305 
306 	PCM_RELEASE_QUICK(d);
307 
308 	return (0);
309 }
310 
311 static int
sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)312 sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
313 {
314 	struct snddev_info *d;
315 	struct pcm_channel *c;
316 	uint32_t dflags;
317 	int *vchanmode, direction, ret;
318 	char dtype[16];
319 
320 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
321 	if (!PCM_REGISTERED(d))
322 		return (EINVAL);
323 
324 	PCM_LOCK(d);
325 	PCM_WAIT(d);
326 
327 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
328 	case VCHAN_PLAY:
329 		if ((d->flags & SD_F_PVCHANS) == 0) {
330 			PCM_UNLOCK(d);
331 			return (ENODEV);
332 		}
333 		direction = PCMDIR_PLAY;
334 		vchanmode = &d->pvchanmode;
335 		break;
336 	case VCHAN_REC:
337 		if ((d->flags & SD_F_RVCHANS) == 0) {
338 			PCM_UNLOCK(d);
339 			return (ENODEV);
340 		}
341 		direction = PCMDIR_REC;
342 		vchanmode = &d->rvchanmode;
343 		break;
344 	default:
345 		PCM_UNLOCK(d);
346 		return (EINVAL);
347 	}
348 
349 	PCM_ACQUIRE(d);
350 	PCM_UNLOCK(d);
351 
352 	if (*vchanmode & CHN_F_VCHAN_PASSTHROUGH)
353 		strlcpy(dtype, "passthrough", sizeof(dtype));
354 	else if (*vchanmode & CHN_F_VCHAN_ADAPTIVE)
355 		strlcpy(dtype, "adaptive", sizeof(dtype));
356 	else
357 		strlcpy(dtype, "fixed", sizeof(dtype));
358 
359 	ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
360 	if (ret != 0 || req->newptr == NULL) {
361 		PCM_RELEASE_QUICK(d);
362 		return (ret);
363 	}
364 
365 	if (strcasecmp(dtype, "passthrough") == 0 || strcmp(dtype, "1") == 0)
366 		dflags = CHN_F_VCHAN_PASSTHROUGH;
367 	else if (strcasecmp(dtype, "adaptive") == 0 || strcmp(dtype, "2") == 0)
368 		dflags = CHN_F_VCHAN_ADAPTIVE;
369 	else if (strcasecmp(dtype, "fixed") == 0 || strcmp(dtype, "0") == 0)
370 		dflags = 0;
371 	else {
372 		PCM_RELEASE_QUICK(d);
373 		return (EINVAL);
374 	}
375 
376 	CHN_FOREACH(c, d, channels.pcm.primary) {
377 		CHN_LOCK(c);
378 		if (c->direction != direction ||
379 		    dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
380 		    (c->flags & CHN_F_PASSTHROUGH)) {
381 			CHN_UNLOCK(c);
382 			continue;
383 		}
384 		c->flags &= ~CHN_F_VCHAN_DYNAMIC;
385 		c->flags |= dflags;
386 		CHN_UNLOCK(c);
387 		*vchanmode = dflags;
388 	}
389 
390 	PCM_RELEASE_QUICK(d);
391 
392 	return (ret);
393 }
394 
395 /*
396  * On the fly vchan rate/format settings
397  */
398 
399 #define VCHAN_ACCESSIBLE(c)	(!((c)->flags & (CHN_F_PASSTHROUGH |	\
400 				 CHN_F_EXCLUSIVE)) &&			\
401 				 (((c)->flags & CHN_F_VCHAN_DYNAMIC) ||	\
402 				 CHN_STOPPED(c)))
403 static int
sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)404 sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
405 {
406 	struct snddev_info *d;
407 	struct pcm_channel *c, *ch;
408 	struct pcmchan_caps *caps;
409 	int *vchanrate, direction, ret, newspd, restart;
410 
411 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
412 	if (!PCM_REGISTERED(d))
413 		return (EINVAL);
414 
415 	PCM_LOCK(d);
416 	PCM_WAIT(d);
417 
418 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
419 	case VCHAN_PLAY:
420 		if ((d->flags & SD_F_PVCHANS) == 0) {
421 			PCM_UNLOCK(d);
422 			return (ENODEV);
423 		}
424 		direction = PCMDIR_PLAY;
425 		vchanrate = &d->pvchanrate;
426 		break;
427 	case VCHAN_REC:
428 		if ((d->flags & SD_F_RVCHANS) == 0) {
429 			PCM_UNLOCK(d);
430 			return (ENODEV);
431 		}
432 		direction = PCMDIR_REC;
433 		vchanrate = &d->rvchanrate;
434 		break;
435 	default:
436 		PCM_UNLOCK(d);
437 		return (EINVAL);
438 	}
439 
440 	PCM_ACQUIRE(d);
441 	PCM_UNLOCK(d);
442 
443 	newspd = *vchanrate;
444 
445 	ret = sysctl_handle_int(oidp, &newspd, 0, req);
446 	if (ret != 0 || req->newptr == NULL) {
447 		PCM_RELEASE_QUICK(d);
448 		return (ret);
449 	}
450 
451 	if (newspd < feeder_rate_min || newspd > feeder_rate_max) {
452 		PCM_RELEASE_QUICK(d);
453 		return (EINVAL);
454 	}
455 
456 	CHN_FOREACH(c, d, channels.pcm.primary) {
457 		CHN_LOCK(c);
458 		if (c->direction != direction) {
459 			CHN_UNLOCK(c);
460 			continue;
461 		}
462 
463 		if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
464 			if (CHN_STARTED(c)) {
465 				chn_abort(c);
466 				restart = 1;
467 			} else
468 				restart = 0;
469 
470 			if (feeder_rate_round) {
471 				caps = chn_getcaps(c);
472 				RANGE(newspd, caps->minspeed, caps->maxspeed);
473 				newspd = CHANNEL_SETSPEED(c->methods,
474 				    c->devinfo, newspd);
475 			}
476 
477 			ret = chn_reset(c, c->format, newspd);
478 			if (ret == 0) {
479 				if (restart != 0) {
480 					CHN_FOREACH(ch, c, children.busy) {
481 						CHN_LOCK(ch);
482 						if (VCHAN_SYNC_REQUIRED(ch))
483 							vchan_sync(ch);
484 						CHN_UNLOCK(ch);
485 					}
486 					c->flags |= CHN_F_DIRTY;
487 					ret = chn_start(c, 1);
488 				}
489 			}
490 		}
491 		*vchanrate = c->speed;
492 
493 		CHN_UNLOCK(c);
494 	}
495 
496 	PCM_RELEASE_QUICK(d);
497 
498 	return (ret);
499 }
500 
501 static int
sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)502 sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
503 {
504 	struct snddev_info *d;
505 	struct pcm_channel *c, *ch;
506 	uint32_t newfmt;
507 	int *vchanformat, direction, ret, restart;
508 	char fmtstr[AFMTSTR_LEN];
509 
510 	d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
511 	if (!PCM_REGISTERED(d))
512 		return (EINVAL);
513 
514 	PCM_LOCK(d);
515 	PCM_WAIT(d);
516 
517 	switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
518 	case VCHAN_PLAY:
519 		if ((d->flags & SD_F_PVCHANS) == 0) {
520 			PCM_UNLOCK(d);
521 			return (ENODEV);
522 		}
523 		direction = PCMDIR_PLAY;
524 		vchanformat = &d->pvchanformat;
525 		break;
526 	case VCHAN_REC:
527 		if ((d->flags & SD_F_RVCHANS) == 0) {
528 			PCM_UNLOCK(d);
529 			return (ENODEV);
530 		}
531 		direction = PCMDIR_REC;
532 		vchanformat = &d->rvchanformat;
533 		break;
534 	default:
535 		PCM_UNLOCK(d);
536 		return (EINVAL);
537 	}
538 
539 	PCM_ACQUIRE(d);
540 	PCM_UNLOCK(d);
541 
542 	bzero(fmtstr, sizeof(fmtstr));
543 
544 	if (snd_afmt2str(*vchanformat, fmtstr, sizeof(fmtstr)) != *vchanformat)
545 		strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
546 
547 	ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
548 	if (ret != 0 || req->newptr == NULL) {
549 		PCM_RELEASE_QUICK(d);
550 		return (ret);
551 	}
552 
553 	newfmt = snd_str2afmt(fmtstr);
554 	if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
555 		PCM_RELEASE_QUICK(d);
556 		return (EINVAL);
557 	}
558 
559 	CHN_FOREACH(c, d, channels.pcm.primary) {
560 		CHN_LOCK(c);
561 		if (c->direction != direction) {
562 			CHN_UNLOCK(c);
563 			continue;
564 		}
565 		if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
566 			if (CHN_STARTED(c)) {
567 				chn_abort(c);
568 				restart = 1;
569 			} else
570 				restart = 0;
571 
572 			ret = chn_reset(c, newfmt, c->speed);
573 			if (ret == 0) {
574 				if (restart != 0) {
575 					CHN_FOREACH(ch, c, children.busy) {
576 						CHN_LOCK(ch);
577 						if (VCHAN_SYNC_REQUIRED(ch))
578 							vchan_sync(ch);
579 						CHN_UNLOCK(ch);
580 					}
581 					c->flags |= CHN_F_DIRTY;
582 					ret = chn_start(c, 1);
583 				}
584 			}
585 		}
586 		*vchanformat = c->format;
587 
588 		CHN_UNLOCK(c);
589 	}
590 
591 	PCM_RELEASE_QUICK(d);
592 
593 	return (ret);
594 }
595 
596 /* virtual channel interface */
597 
598 #define VCHAN_FMT_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
599 				"play.vchanformat" : "rec.vchanformat"
600 #define VCHAN_SPD_HINT(x)	((x) == PCMDIR_PLAY_VIRTUAL) ?		\
601 				"play.vchanrate" : "rec.vchanrate"
602 
603 int
vchan_create(struct pcm_channel * parent,struct pcm_channel ** child)604 vchan_create(struct pcm_channel *parent, struct pcm_channel **child)
605 {
606 	struct snddev_info *d;
607 	struct pcm_channel *ch;
608 	struct pcmchan_caps *parent_caps;
609 	uint32_t vchanfmt, vchanspd;
610 	int ret, direction, r;
611 	bool save;
612 
613 	ret = 0;
614 	save = false;
615 	d = parent->parentsnddev;
616 
617 	PCM_BUSYASSERT(d);
618 	CHN_LOCKASSERT(parent);
619 
620 	if (!(parent->direction == PCMDIR_PLAY ||
621 	    parent->direction == PCMDIR_REC))
622 		return (EINVAL);
623 
624 	CHN_UNLOCK(parent);
625 	PCM_LOCK(d);
626 
627 	if (parent->direction == PCMDIR_PLAY) {
628 		direction = PCMDIR_PLAY_VIRTUAL;
629 		vchanfmt = d->pvchanformat;
630 		vchanspd = d->pvchanrate;
631 	} else {
632 		direction = PCMDIR_REC_VIRTUAL;
633 		vchanfmt = d->rvchanformat;
634 		vchanspd = d->rvchanrate;
635 	}
636 
637 	/* create a new playback channel */
638 	ch = chn_init(d, parent, &vchan_class, direction, parent);
639 	if (ch == NULL) {
640 		PCM_UNLOCK(d);
641 		CHN_LOCK(parent);
642 		return (ENODEV);
643 	}
644 	PCM_UNLOCK(d);
645 
646 	CHN_LOCK(parent);
647 	CHN_INSERT_SORT_ASCEND(parent, ch, children);
648 
649 	*child = ch;
650 
651 	if (parent->flags & CHN_F_HAS_VCHAN)
652 		return (0);
653 
654 	parent->flags |= CHN_F_HAS_VCHAN | CHN_F_BUSY;
655 
656 	parent_caps = chn_getcaps(parent);
657 	if (parent_caps == NULL) {
658 		ret = EINVAL;
659 		goto fail;
660 	}
661 
662 	if (vchanfmt == 0) {
663 		const char *vfmt;
664 
665 		CHN_UNLOCK(parent);
666 		r = resource_string_value(device_get_name(parent->dev),
667 		    device_get_unit(parent->dev), VCHAN_FMT_HINT(direction),
668 		    &vfmt);
669 		CHN_LOCK(parent);
670 		if (r != 0)
671 			vfmt = NULL;
672 		if (vfmt != NULL) {
673 			vchanfmt = snd_str2afmt(vfmt);
674 			if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN))
675 				vchanfmt = 0;
676 		}
677 		if (vchanfmt == 0)
678 			vchanfmt = VCHAN_DEFAULT_FORMAT;
679 		save = true;
680 	}
681 
682 	if (vchanspd == 0) {
683 		/*
684 		 * This is very sad. Few soundcards advertised as being
685 		 * able to do (insanely) higher/lower speed, but in
686 		 * reality, they simply can't. At least, we give user chance
687 		 * to set sane value via kernel hints or sysctl.
688 		 */
689 		CHN_UNLOCK(parent);
690 		r = resource_int_value(device_get_name(parent->dev),
691 		    device_get_unit(parent->dev), VCHAN_SPD_HINT(direction),
692 		    &vchanspd);
693 		CHN_LOCK(parent);
694 		if (r != 0) {
695 			/* No saved value, no hint, NOTHING. */
696 			vchanspd = VCHAN_DEFAULT_RATE;
697 			RANGE(vchanspd, parent_caps->minspeed,
698 			    parent_caps->maxspeed);
699 		}
700 		save = true;
701 	}
702 
703 	/*
704 	 * Limit the speed between feeder_rate_min <-> feeder_rate_max.
705 	 */
706 	RANGE(vchanspd, feeder_rate_min, feeder_rate_max);
707 
708 	if (feeder_rate_round) {
709 		RANGE(vchanspd, parent_caps->minspeed,
710 		    parent_caps->maxspeed);
711 		vchanspd = CHANNEL_SETSPEED(parent->methods,
712 		    parent->devinfo, vchanspd);
713 	}
714 
715 	if ((ret = chn_reset(parent, vchanfmt, vchanspd)) != 0)
716 		goto fail;
717 
718 	if (save) {
719 		/*
720 		 * Save new value.
721 		 */
722 		if (direction == PCMDIR_PLAY_VIRTUAL) {
723 			d->pvchanformat = parent->format;
724 			d->pvchanrate = parent->speed;
725 		} else {
726 			d->rvchanformat = parent->format;
727 			d->rvchanrate = parent->speed;
728 		}
729 	}
730 
731 	/*
732 	 * If the parent channel supports digital format,
733 	 * enable passthrough mode.
734 	 */
735 	if (snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
736 		parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
737 		parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
738 	}
739 
740 	return (ret);
741 
742 fail:
743 	CHN_LOCK(ch);
744 	vchan_destroy(ch);
745 	*child = NULL;
746 
747 	return (ret);
748 }
749 
750 int
vchan_destroy(struct pcm_channel * c)751 vchan_destroy(struct pcm_channel *c)
752 {
753 	struct pcm_channel *parent;
754 
755 	KASSERT(c != NULL && c->parentchannel != NULL &&
756 	    c->parentsnddev != NULL, ("%s(): invalid channel=%p",
757 	    __func__, c));
758 
759 	CHN_LOCKASSERT(c);
760 
761 	parent = c->parentchannel;
762 
763 	PCM_BUSYASSERT(c->parentsnddev);
764 	CHN_LOCKASSERT(parent);
765 
766 	CHN_UNLOCK(c);
767 
768 	/* remove us from our parent's children list */
769 	CHN_REMOVE(parent, c, children);
770 	if (CHN_EMPTY(parent, children)) {
771 		parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
772 		chn_reset(parent, parent->format, parent->speed);
773 	}
774 
775 	CHN_UNLOCK(parent);
776 
777 	/* destroy ourselves */
778 	chn_kill(c);
779 
780 	CHN_LOCK(parent);
781 
782 	return (0);
783 }
784 
785 int
786 #ifdef SND_DEBUG
vchan_passthrough(struct pcm_channel * c,const char * caller)787 vchan_passthrough(struct pcm_channel *c, const char *caller)
788 #else
789 vchan_sync(struct pcm_channel *c)
790 #endif
791 {
792 	int ret;
793 
794 	KASSERT(c != NULL && c->parentchannel != NULL &&
795 	    (c->flags & CHN_F_VIRTUAL),
796 	    ("%s(): invalid passthrough", __func__));
797 	CHN_LOCKASSERT(c);
798 	CHN_LOCKASSERT(c->parentchannel);
799 
800 	sndbuf_setspd(c->bufhard, c->parentchannel->speed);
801 	c->flags |= CHN_F_PASSTHROUGH;
802 	ret = feeder_chain(c);
803 	c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
804 	if (ret != 0)
805 		c->flags |= CHN_F_DIRTY;
806 
807 #ifdef SND_DEBUG
808 	if (snd_passthrough_verbose) {
809 		device_printf(c->dev, "%s(%s/%s) %s() -> re-sync err=%d\n",
810 		    __func__, c->name, c->comm, caller, ret);
811 	}
812 #endif
813 
814 	return (ret);
815 }
816 
817 static int
sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS)818 sysctl_hw_snd_vchans_enable(SYSCTL_HANDLER_ARGS)
819 {
820 	struct snddev_info *d;
821 	int i, v, error;
822 
823 	v = snd_vchans_enable;
824 	error = sysctl_handle_int(oidp, &v, 0, req);
825 	if (error != 0 || req->newptr == NULL)
826 		return (error);
827 
828 	snd_vchans_enable = v >= 1;
829 
830 	for (i = 0; pcm_devclass != NULL &&
831 	    i < devclass_get_maxunit(pcm_devclass); i++) {
832 		d = devclass_get_softc(pcm_devclass, i);
833 		if (!PCM_REGISTERED(d))
834 			continue;
835 		PCM_ACQUIRE_QUICK(d);
836 		if (snd_vchans_enable) {
837 			if (d->playcount > 0)
838 				d->flags |= SD_F_PVCHANS;
839 			if (d->reccount > 0)
840 				d->flags |= SD_F_RVCHANS;
841 		} else
842 			d->flags &= ~(SD_F_PVCHANS | SD_F_RVCHANS);
843 		PCM_RELEASE_QUICK(d);
844 	}
845 
846 	return (0);
847 }
848 SYSCTL_PROC(_hw_snd, OID_AUTO, vchans_enable,
849     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, 0, sizeof(int),
850     sysctl_hw_snd_vchans_enable, "I", "global virtual channel switch");
851 
852 void
vchan_initsys(device_t dev)853 vchan_initsys(device_t dev)
854 {
855 	struct snddev_info *d;
856 	int unit;
857 
858 	unit = device_get_unit(dev);
859 	d = device_get_softc(dev);
860 
861 	/* Play */
862 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
863 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
864 	    OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
865 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
866 	    sysctl_dev_pcm_vchans, "I", "virtual channels enabled");
867 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
868 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
869 	    OID_AUTO, "vchanmode",
870 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
871 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
872 	    sysctl_dev_pcm_vchanmode, "A",
873 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
874 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
875 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
876 	    OID_AUTO, "vchanrate",
877 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
878 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
879 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
880 	SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
881 	    SYSCTL_CHILDREN(d->play_sysctl_tree),
882 	    OID_AUTO, "vchanformat",
883 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
884 	    VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
885 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
886 	/* Rec */
887 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
888 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
889 	    OID_AUTO, "vchans",
890 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
891 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
892 	    sysctl_dev_pcm_vchans, "I", "virtual channels enabled");
893 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
894 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
895 	    OID_AUTO, "vchanmode",
896 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
897 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
898 	    sysctl_dev_pcm_vchanmode, "A",
899 	    "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
900 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
901 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
902 	    OID_AUTO, "vchanrate",
903 	    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
904 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
905 	    sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
906 	SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
907 	    SYSCTL_CHILDREN(d->rec_sysctl_tree),
908 	    OID_AUTO, "vchanformat",
909 	    CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
910 	    VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
911 	    sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
912 }
913