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