]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - sys/dev/sound/pcm/vchan.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / sys / dev / sound / pcm / vchan.c
1 /*-
2  * Copyright (c) 2006-2009 Ariff Abdullah <ariff@FreeBSD.org>
3  * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 /* Almost entirely rewritten to add multi-format/channels mixing support. */
29
30 #ifdef HAVE_KERNEL_OPTION_HEADERS
31 #include "opt_snd.h"
32 #endif
33
34 #include <dev/sound/pcm/sound.h>
35 #include <dev/sound/pcm/vchan.h>
36
37 SND_DECLARE_FILE("$FreeBSD$");
38
39 /*
40  * [ac3 , dts , linear , 0, linear, 0]
41  */
42 #define FMTLIST_MAX             6
43 #define FMTLIST_OFFSET          4
44 #define DIGFMTS_MAX             2
45
46 #ifdef SND_DEBUG
47 static int snd_passthrough_verbose = 0;
48 SYSCTL_INT(_hw_snd, OID_AUTO, passthrough_verbose, CTLFLAG_RW,
49         &snd_passthrough_verbose, 0, "passthrough verbosity");
50
51 #endif
52
53 struct vchan_info {
54         struct pcm_channel *channel;
55         struct pcmchan_caps caps;
56         uint32_t fmtlist[FMTLIST_MAX];
57         int trigger;
58 };
59
60 static void *
61 vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
62     struct pcm_channel *c, int dir)
63 {
64         struct vchan_info *info;
65         struct pcm_channel *p;
66         uint32_t i, j, *fmtlist;
67
68         KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC,
69             ("vchan_init: bad direction"));
70         KASSERT(c != NULL && c->parentchannel != NULL,
71             ("vchan_init: bad channels"));
72
73         info = malloc(sizeof(*info), M_DEVBUF, M_WAITOK | M_ZERO);
74         info->channel = c;
75         info->trigger = PCMTRIG_STOP;
76         p = c->parentchannel;
77
78         CHN_LOCK(p);
79
80         fmtlist = chn_getcaps(p)->fmtlist;
81         for (i = 0, j = 0; fmtlist[i] != 0 && j < DIGFMTS_MAX; i++) {
82                 if (fmtlist[i] & AFMT_PASSTHROUGH)
83                         info->fmtlist[j++] = fmtlist[i];
84         }
85         if (p->format & AFMT_VCHAN)
86                 info->fmtlist[j] = p->format;
87         else
88                 info->fmtlist[j] = VCHAN_DEFAULT_FORMAT;
89         info->caps.fmtlist = info->fmtlist +
90             ((p->flags & CHN_F_VCHAN_DYNAMIC) ? 0 : FMTLIST_OFFSET);
91
92         CHN_UNLOCK(p);
93
94         c->flags |= CHN_F_VIRTUAL;
95
96         return (info);
97 }
98
99 static int
100 vchan_free(kobj_t obj, void *data)
101 {
102
103         free(data, M_DEVBUF);
104
105         return (0);
106 }
107
108 static int
109 vchan_setformat(kobj_t obj, void *data, uint32_t format)
110 {
111         struct vchan_info *info;
112
113         info = data;
114
115         CHN_LOCKASSERT(info->channel);
116
117         if (!snd_fmtvalid(format, info->caps.fmtlist))
118                 return (-1);
119
120         return (0);
121 }
122
123 static uint32_t
124 vchan_setspeed(kobj_t obj, void *data, uint32_t speed)
125 {
126         struct vchan_info *info;
127
128         info = data;
129
130         CHN_LOCKASSERT(info->channel);
131
132         return (info->caps.maxspeed);
133 }
134
135 static int
136 vchan_trigger(kobj_t obj, void *data, int go)
137 {
138         struct vchan_info *info;
139         struct pcm_channel *c, *p;
140         int ret, otrigger;
141
142         info = data;
143
144         if (!PCMTRIG_COMMON(go) || go == info->trigger)
145                 return (0);
146
147         c = info->channel;
148         p = c->parentchannel;
149         otrigger = info->trigger;
150         info->trigger = go;
151
152         CHN_LOCKASSERT(c);
153
154         CHN_UNLOCK(c);
155         CHN_LOCK(p);
156
157         switch (go) {
158         case PCMTRIG_START:
159                 if (otrigger != PCMTRIG_START)
160                         CHN_INSERT_HEAD(p, c, children.busy);
161                 break;
162         case PCMTRIG_STOP:
163         case PCMTRIG_ABORT:
164                 if (otrigger == PCMTRIG_START)
165                         CHN_REMOVE(p, c, children.busy);
166                 break;
167         default:
168                 break;
169         }
170
171         ret = chn_notify(p, CHN_N_TRIGGER);
172
173         CHN_LOCK(c);
174
175         if (ret == 0 && go == PCMTRIG_START && VCHAN_SYNC_REQUIRED(c))
176                 ret = vchan_sync(c);
177
178         CHN_UNLOCK(c);
179         CHN_UNLOCK(p);
180         CHN_LOCK(c);
181
182         return (ret);
183 }
184
185 static struct pcmchan_caps *
186 vchan_getcaps(kobj_t obj, void *data)
187 {
188         struct vchan_info *info;
189         struct pcm_channel *c;
190         uint32_t pformat, pspeed, pflags, i;
191
192         info = data;
193         c = info->channel;
194         pformat = c->parentchannel->format;
195         pspeed = c->parentchannel->speed;
196         pflags = c->parentchannel->flags;
197
198         CHN_LOCKASSERT(c);
199
200         if (pflags & CHN_F_VCHAN_DYNAMIC) {
201                 info->caps.fmtlist = info->fmtlist;
202                 if (pformat & AFMT_VCHAN) {
203                         for (i = 0; info->caps.fmtlist[i] != 0; i++) {
204                                 if (info->caps.fmtlist[i] & AFMT_PASSTHROUGH)
205                                         continue;
206                                 break;
207                         }
208                         info->caps.fmtlist[i] = pformat;
209                 }
210                 if (c->format & AFMT_PASSTHROUGH)
211                         info->caps.minspeed = c->speed;
212                 else 
213                         info->caps.minspeed = pspeed;
214                 info->caps.maxspeed = info->caps.minspeed;
215         } else {
216                 info->caps.fmtlist = info->fmtlist + FMTLIST_OFFSET;
217                 if (pformat & AFMT_VCHAN)
218                         info->caps.fmtlist[0] = pformat;
219                 else {
220                         device_printf(c->dev,
221                             "%s(): invalid vchan format 0x%08x",
222                             __func__, pformat);
223                         info->caps.fmtlist[0] = VCHAN_DEFAULT_FORMAT;
224                 }
225                 info->caps.minspeed = pspeed;
226                 info->caps.maxspeed = info->caps.minspeed;
227         }
228
229         return (&info->caps);
230 }
231
232 static struct pcmchan_matrix *
233 vchan_getmatrix(kobj_t obj, void *data, uint32_t format)
234 {
235
236         return (feeder_matrix_format_map(format));
237 }
238
239 static kobj_method_t vchan_methods[] = {
240         KOBJMETHOD(channel_init,                vchan_init),
241         KOBJMETHOD(channel_free,                vchan_free),
242         KOBJMETHOD(channel_setformat,           vchan_setformat),
243         KOBJMETHOD(channel_setspeed,            vchan_setspeed),
244         KOBJMETHOD(channel_trigger,             vchan_trigger),
245         KOBJMETHOD(channel_getcaps,             vchan_getcaps),
246         KOBJMETHOD(channel_getmatrix,           vchan_getmatrix),
247         KOBJMETHOD_END
248 };
249 CHANNEL_DECLARE(vchan);
250
251 static void
252 pcm_getparentchannel(struct snddev_info *d,
253     struct pcm_channel **wrch, struct pcm_channel **rdch)
254 {
255         struct pcm_channel **ch, *wch, *rch, *c;
256
257         KASSERT(d != NULL, ("%s(): NULL snddev_info", __func__));
258
259         PCM_BUSYASSERT(d);
260         PCM_UNLOCKASSERT(d);
261
262         wch = NULL;
263         rch = NULL;
264
265         CHN_FOREACH(c, d, channels.pcm) {
266                 CHN_LOCK(c);
267                 ch = (c->direction == PCMDIR_PLAY) ? &wch : &rch;
268                 if (c->flags & CHN_F_VIRTUAL) {
269                         /* Sanity check */
270                         if (*ch != NULL && *ch != c->parentchannel) {
271                                 CHN_UNLOCK(c);
272                                 *ch = NULL;
273                                 break;
274                         }
275                 } else if (c->flags & CHN_F_HAS_VCHAN) {
276                         /* No way!! */
277                         if (*ch != NULL) {
278                                 CHN_UNLOCK(c);
279                                 *ch = NULL;
280                                 break;
281                         }
282                         *ch = c;
283                 }
284                 CHN_UNLOCK(c);
285         }
286
287         if (wrch != NULL)
288                 *wrch = wch;
289         if (rdch != NULL)
290                 *rdch = rch;
291 }
292
293 static int
294 sysctl_dev_pcm_vchans(SYSCTL_HANDLER_ARGS)
295 {
296         struct snddev_info *d;
297         int direction, vchancount;
298         int err, cnt;
299
300         d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
301         if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
302                 return (EINVAL);
303
304         PCM_LOCK(d);
305         PCM_WAIT(d);
306
307         switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
308         case VCHAN_PLAY:
309                 direction = PCMDIR_PLAY;
310                 vchancount = d->pvchancount;
311                 cnt = d->playcount;
312                 break;
313         case VCHAN_REC:
314                 direction = PCMDIR_REC;
315                 vchancount = d->rvchancount;
316                 cnt = d->reccount;
317                 break;
318         default:
319                 PCM_UNLOCK(d);
320                 return (EINVAL);
321                 break;
322         }
323
324         if (cnt < 1) {
325                 PCM_UNLOCK(d);
326                 return (ENODEV);
327         }
328
329         PCM_ACQUIRE(d);
330         PCM_UNLOCK(d);
331
332         cnt = vchancount;
333         err = sysctl_handle_int(oidp, &cnt, 0, req);
334
335         if (err == 0 && req->newptr != NULL && vchancount != cnt) {
336                 if (cnt < 0)
337                         cnt = 0;
338                 if (cnt > SND_MAXVCHANS)
339                         cnt = SND_MAXVCHANS;
340                 err = pcm_setvchans(d, direction, cnt, -1);
341         }
342
343         PCM_RELEASE_QUICK(d);
344
345         return err;
346 }
347
348 static int
349 sysctl_dev_pcm_vchanmode(SYSCTL_HANDLER_ARGS)
350 {
351         struct snddev_info *d;
352         struct pcm_channel *c;
353         uint32_t dflags;
354         int direction, ret;
355         char dtype[16];
356
357         d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
358         if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
359                 return (EINVAL);
360
361         PCM_LOCK(d);
362         PCM_WAIT(d);
363
364         switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
365         case VCHAN_PLAY:
366                 direction = PCMDIR_PLAY;
367                 break;
368         case VCHAN_REC:
369                 direction = PCMDIR_REC;
370                 break;
371         default:
372                 PCM_UNLOCK(d);
373                 return (EINVAL);
374                 break;
375         }
376
377         PCM_ACQUIRE(d);
378         PCM_UNLOCK(d);
379
380         if (direction == PCMDIR_PLAY)
381                 pcm_getparentchannel(d, &c, NULL);
382         else
383                 pcm_getparentchannel(d, NULL, &c);
384
385         if (c == NULL) {
386                 PCM_RELEASE_QUICK(d);
387                 return (EINVAL);
388         }
389
390         KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
391             __func__, direction, c->direction));
392
393         CHN_LOCK(c);
394         if (c->flags & CHN_F_VCHAN_PASSTHROUGH)
395                 strlcpy(dtype, "passthrough", sizeof(dtype));
396         else if (c->flags & CHN_F_VCHAN_ADAPTIVE)
397                 strlcpy(dtype, "adaptive", sizeof(dtype));
398         else
399                 strlcpy(dtype, "fixed", sizeof(dtype));
400         CHN_UNLOCK(c);
401
402         ret = sysctl_handle_string(oidp, dtype, sizeof(dtype), req);
403         if (ret == 0 && req->newptr != NULL) {
404                 if (strcasecmp(dtype, "passthrough") == 0 ||
405                     strcmp(dtype, "1") == 0)
406                         dflags = CHN_F_VCHAN_PASSTHROUGH;
407                 else if (strcasecmp(dtype, "adaptive") == 0 ||
408                     strcmp(dtype, "2") == 0)
409                         dflags = CHN_F_VCHAN_ADAPTIVE;
410                 else if (strcasecmp(dtype, "fixed") == 0 ||
411                     strcmp(dtype, "0") == 0)
412                         dflags = 0;
413                 else {
414                         PCM_RELEASE_QUICK(d);
415                         return (EINVAL);
416                 }
417                 CHN_LOCK(c);
418                 if (dflags == (c->flags & CHN_F_VCHAN_DYNAMIC) ||
419                     (c->flags & CHN_F_PASSTHROUGH)) {
420                         CHN_UNLOCK(c);
421                         PCM_RELEASE_QUICK(d);
422                         return (0);
423                 }
424                 c->flags &= ~CHN_F_VCHAN_DYNAMIC;
425                 c->flags |= dflags;
426                 CHN_UNLOCK(c);
427         }
428
429         PCM_RELEASE_QUICK(d);
430
431         return (ret);
432 }
433
434 /* 
435  * On the fly vchan rate/format settings
436  */
437
438 #define VCHAN_ACCESSIBLE(c)     (!((c)->flags & (CHN_F_PASSTHROUGH |    \
439                                  CHN_F_EXCLUSIVE)) &&                   \
440                                  (((c)->flags & CHN_F_VCHAN_DYNAMIC) || \
441                                  CHN_STOPPED(c)))
442 static int
443 sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS)
444 {
445         struct snddev_info *d;
446         struct pcm_channel *c, *ch;
447         struct pcmchan_caps *caps;
448         int *vchanrate, vchancount, direction, ret, newspd, restart;
449
450         d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
451         if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
452                 return (EINVAL);
453
454         PCM_LOCK(d);
455         PCM_WAIT(d);
456
457         switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
458         case VCHAN_PLAY:
459                 direction = PCMDIR_PLAY;
460                 vchancount = d->pvchancount;
461                 vchanrate = &d->pvchanrate;
462                 break;
463         case VCHAN_REC:
464                 direction = PCMDIR_REC;
465                 vchancount = d->rvchancount;
466                 vchanrate = &d->rvchanrate;
467                 break;
468         default:
469                 PCM_UNLOCK(d);
470                 return (EINVAL);
471                 break;
472         }
473
474         if (vchancount < 1) {
475                 PCM_UNLOCK(d);
476                 return (EINVAL);
477         }
478
479         PCM_ACQUIRE(d);
480         PCM_UNLOCK(d);
481
482         if (direction == PCMDIR_PLAY)
483                 pcm_getparentchannel(d, &c, NULL);
484         else
485                 pcm_getparentchannel(d, NULL, &c);
486
487         if (c == NULL) {
488                 PCM_RELEASE_QUICK(d);
489                 return (EINVAL);
490         }
491
492         KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
493             __func__, direction, c->direction));
494
495         CHN_LOCK(c);
496         newspd = c->speed;
497         CHN_UNLOCK(c);
498
499         ret = sysctl_handle_int(oidp, &newspd, 0, req);
500         if (ret != 0 || req->newptr == NULL) {
501                 PCM_RELEASE_QUICK(d);
502                 return (ret);
503         }
504
505         if (newspd < 1 || newspd < feeder_rate_min ||
506             newspd > feeder_rate_max) {
507                 PCM_RELEASE_QUICK(d);
508                 return (EINVAL);
509         }
510
511         CHN_LOCK(c);
512
513         if (newspd != c->speed && VCHAN_ACCESSIBLE(c)) {
514                 if (CHN_STARTED(c)) {
515                         chn_abort(c);
516                         restart = 1;
517                 } else
518                         restart = 0;
519
520                 if (feeder_rate_round) {
521                         caps = chn_getcaps(c);
522                         RANGE(newspd, caps->minspeed, caps->maxspeed);
523                         newspd = CHANNEL_SETSPEED(c->methods,
524                             c->devinfo, newspd);
525                 }
526
527                 ret = chn_reset(c, c->format, newspd);
528                 if (ret == 0) {
529                         *vchanrate = c->speed;
530                         if (restart != 0) {
531                                 CHN_FOREACH(ch, c, children.busy) {
532                                         CHN_LOCK(ch);
533                                         if (VCHAN_SYNC_REQUIRED(ch))
534                                                 vchan_sync(ch);
535                                         CHN_UNLOCK(ch);
536                                 }
537                                 c->flags |= CHN_F_DIRTY;
538                                 ret = chn_start(c, 1);
539                         }
540                 }
541         }
542
543         CHN_UNLOCK(c);
544
545         PCM_RELEASE_QUICK(d);
546
547         return (ret);
548 }
549
550 static int
551 sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS)
552 {
553         struct snddev_info *d;
554         struct pcm_channel *c, *ch;
555         uint32_t newfmt;
556         int *vchanformat, vchancount, direction, ret, restart;
557         char fmtstr[AFMTSTR_LEN];
558
559         d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1));
560         if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN))
561                 return (EINVAL);
562
563         PCM_LOCK(d);
564         PCM_WAIT(d);
565
566         switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) {
567         case VCHAN_PLAY:
568                 direction = PCMDIR_PLAY;
569                 vchancount = d->pvchancount;
570                 vchanformat = &d->pvchanformat;
571                 break;
572         case VCHAN_REC:
573                 direction = PCMDIR_REC;
574                 vchancount = d->rvchancount;
575                 vchanformat = &d->rvchanformat;
576                 break;
577         default:
578                 PCM_UNLOCK(d);
579                 return (EINVAL);
580                 break;
581         }
582
583         if (vchancount < 1) {
584                 PCM_UNLOCK(d);
585                 return (EINVAL);
586         }
587
588         PCM_ACQUIRE(d);
589         PCM_UNLOCK(d);
590
591         if (direction == PCMDIR_PLAY)
592                 pcm_getparentchannel(d, &c, NULL);
593         else
594                 pcm_getparentchannel(d, NULL, &c);
595
596         if (c == NULL) {
597                 PCM_RELEASE_QUICK(d);
598                 return (EINVAL);
599         }
600
601         KASSERT(direction == c->direction, ("%s(): invalid direction %d/%d",
602             __func__, direction, c->direction));
603
604         CHN_LOCK(c);
605
606         bzero(fmtstr, sizeof(fmtstr));
607
608         if (snd_afmt2str(c->format, fmtstr, sizeof(fmtstr)) != c->format)
609                 strlcpy(fmtstr, "<ERROR>", sizeof(fmtstr));
610
611         CHN_UNLOCK(c);
612
613         ret = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req);
614         if (ret != 0 || req->newptr == NULL) {
615                 PCM_RELEASE_QUICK(d);
616                 return (ret);
617         }
618
619         newfmt = snd_str2afmt(fmtstr);
620         if (newfmt == 0 || !(newfmt & AFMT_VCHAN)) {
621                 PCM_RELEASE_QUICK(d);
622                 return (EINVAL);
623         }
624
625         CHN_LOCK(c);
626
627         if (newfmt != c->format && VCHAN_ACCESSIBLE(c)) {
628                 if (CHN_STARTED(c)) {
629                         chn_abort(c);
630                         restart = 1;
631                 } else
632                         restart = 0;
633
634                 ret = chn_reset(c, newfmt, c->speed);
635                 if (ret == 0) {
636                         *vchanformat = c->format;
637                         if (restart != 0) {
638                                 CHN_FOREACH(ch, c, children.busy) {
639                                         CHN_LOCK(ch);
640                                         if (VCHAN_SYNC_REQUIRED(ch))
641                                                 vchan_sync(ch);
642                                         CHN_UNLOCK(ch);
643                                 }
644                                 c->flags |= CHN_F_DIRTY;
645                                 ret = chn_start(c, 1);
646                         }
647                 }
648         }
649
650         CHN_UNLOCK(c);
651
652         PCM_RELEASE_QUICK(d);
653
654         return (ret);
655 }
656
657 /* virtual channel interface */
658
659 #define VCHAN_FMT_HINT(x)       ((x) == PCMDIR_PLAY_VIRTUAL) ?          \
660                                 "play.vchanformat" : "rec.vchanformat"
661 #define VCHAN_SPD_HINT(x)       ((x) == PCMDIR_PLAY_VIRTUAL) ?          \
662                                 "play.vchanrate" : "rec.vchanrate"
663
664 int
665 vchan_create(struct pcm_channel *parent, int num)
666 {
667         struct snddev_info *d;
668         struct pcm_channel *ch;
669         struct pcmchan_caps *parent_caps;
670         uint32_t vchanfmt, vchanspd;
671         int ret, direction, r, save;
672
673         d = parent->parentsnddev;
674
675         PCM_BUSYASSERT(d);
676         CHN_LOCKASSERT(parent);
677
678         if (!(parent->flags & CHN_F_BUSY))
679                 return (EBUSY);
680
681         if (!(parent->direction == PCMDIR_PLAY ||
682             parent->direction == PCMDIR_REC))
683                 return (EINVAL);
684
685         d = parent->parentsnddev;
686
687         CHN_UNLOCK(parent);
688         PCM_LOCK(d);
689
690         if (parent->direction == PCMDIR_PLAY) {
691                 direction = PCMDIR_PLAY_VIRTUAL;
692                 vchanfmt = d->pvchanformat;
693                 vchanspd = d->pvchanrate;
694         } else {
695                 direction = PCMDIR_REC_VIRTUAL;
696                 vchanfmt = d->rvchanformat;
697                 vchanspd = d->rvchanrate;
698         }
699
700         /* create a new playback channel */
701         ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent);
702         if (ch == NULL) {
703                 PCM_UNLOCK(d);
704                 CHN_LOCK(parent);
705                 return (ENODEV);
706         }
707
708         /* add us to our grandparent's channel list */
709         ret = pcm_chn_add(d, ch);
710         PCM_UNLOCK(d);
711         if (ret != 0) {
712                 pcm_chn_destroy(ch);
713                 CHN_LOCK(parent);
714                 return (ret);
715         }
716
717         CHN_LOCK(parent);
718         /*
719          * Add us to our parent channel's children in reverse order
720          * so future destruction will pick the last (biggest number)
721          * channel.
722          */
723         CHN_INSERT_SORT_DESCEND(parent, ch, children);
724
725         if (parent->flags & CHN_F_HAS_VCHAN)
726                 return (0);
727
728         parent->flags |= CHN_F_HAS_VCHAN;
729
730         parent_caps = chn_getcaps(parent);
731         if (parent_caps == NULL)
732                 ret = EINVAL;
733
734         save = 0;
735
736         if (ret == 0 && vchanfmt == 0) {
737                 const char *vfmt;
738
739                 CHN_UNLOCK(parent);
740                 r = resource_string_value(device_get_name(parent->dev),
741                     device_get_unit(parent->dev), VCHAN_FMT_HINT(direction),
742                     &vfmt);
743                 CHN_LOCK(parent);
744                 if (r != 0)
745                         vfmt = NULL;
746                 if (vfmt != NULL) {
747                         vchanfmt = snd_str2afmt(vfmt);
748                         if (vchanfmt != 0 && !(vchanfmt & AFMT_VCHAN))
749                                 vchanfmt = 0;
750                 }
751                 if (vchanfmt == 0)
752                         vchanfmt = VCHAN_DEFAULT_FORMAT;
753                 save = 1;
754         }
755
756         if (ret == 0 && vchanspd == 0) {
757                 /*
758                  * This is very sad. Few soundcards advertised as being
759                  * able to do (insanely) higher/lower speed, but in
760                  * reality, they simply can't. At least, we give user chance
761                  * to set sane value via kernel hints or sysctl.
762                  */
763                 CHN_UNLOCK(parent);
764                 r = resource_int_value(device_get_name(parent->dev),
765                     device_get_unit(parent->dev), VCHAN_SPD_HINT(direction),
766                     &vchanspd);
767                 CHN_LOCK(parent);
768                 if (r != 0) {
769                         /*
770                          * No saved value, no hint, NOTHING.
771                          *
772                          * Workaround for sb16 running
773                          * poorly at 45k / 49k.
774                          */
775                         switch (parent_caps->maxspeed) {
776                         case 45000:
777                         case 49000:
778                                 vchanspd = 44100;
779                                 break;
780                         default:
781                                 vchanspd = VCHAN_DEFAULT_RATE;
782                                 if (vchanspd > parent_caps->maxspeed)
783                                         vchanspd = parent_caps->maxspeed;
784                                 break;
785                         }
786                         if (vchanspd < parent_caps->minspeed)
787                                 vchanspd = parent_caps->minspeed;
788                 }
789                 save = 1;
790         }
791
792         if (ret == 0) {
793                 /*
794                  * Limit the speed between feeder_rate_min <-> feeder_rate_max.
795                  */
796                 if (vchanspd < feeder_rate_min)
797                         vchanspd = feeder_rate_min;
798                 if (vchanspd > feeder_rate_max)
799                         vchanspd = feeder_rate_max;
800
801                 if (feeder_rate_round) {
802                         RANGE(vchanspd, parent_caps->minspeed,
803                             parent_caps->maxspeed);
804                         vchanspd = CHANNEL_SETSPEED(parent->methods,
805                             parent->devinfo, vchanspd);
806                 }
807
808                 ret = chn_reset(parent, vchanfmt, vchanspd);
809         }
810
811         if (ret == 0 && save) {
812                 /*
813                  * Save new value.
814                  */
815                 if (direction == PCMDIR_PLAY_VIRTUAL) {
816                         d->pvchanformat = parent->format;
817                         d->pvchanrate = parent->speed;
818                 } else {
819                         d->rvchanformat = parent->format;
820                         d->rvchanrate = parent->speed;
821                 }
822         }
823         
824         /*
825          * If the parent channel supports digital format,
826          * enable passthrough mode.
827          */
828         if (ret == 0 && snd_fmtvalid(AFMT_PASSTHROUGH, parent_caps->fmtlist)) {
829                 parent->flags &= ~CHN_F_VCHAN_DYNAMIC;
830                 parent->flags |= CHN_F_VCHAN_PASSTHROUGH;
831         }
832
833         if (ret != 0) {
834                 CHN_REMOVE(parent, ch, children);
835                 parent->flags &= ~CHN_F_HAS_VCHAN;
836                 CHN_UNLOCK(parent);
837                 PCM_LOCK(d);
838                 if (pcm_chn_remove(d, ch) == 0) {
839                         PCM_UNLOCK(d);
840                         pcm_chn_destroy(ch);
841                 } else
842                         PCM_UNLOCK(d);
843                 CHN_LOCK(parent);
844         }
845
846         return (ret);
847 }
848
849 int
850 vchan_destroy(struct pcm_channel *c)
851 {
852         struct pcm_channel *parent;
853         struct snddev_info *d;
854         int ret;
855
856         KASSERT(c != NULL && c->parentchannel != NULL &&
857             c->parentsnddev != NULL, ("%s(): invalid channel=%p",
858             __func__, c));
859
860         CHN_LOCKASSERT(c);
861
862         d = c->parentsnddev;
863         parent = c->parentchannel;
864
865         PCM_BUSYASSERT(d);
866         CHN_LOCKASSERT(parent);
867
868         CHN_UNLOCK(c);
869
870         if (!(parent->flags & CHN_F_BUSY))
871                 return (EBUSY);
872
873         if (CHN_EMPTY(parent, children))
874                 return (EINVAL);
875
876         /* remove us from our parent's children list */
877         CHN_REMOVE(parent, c, children);
878
879         if (CHN_EMPTY(parent, children)) {
880                 parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN);
881                 chn_reset(parent, parent->format, parent->speed);
882         }
883
884         CHN_UNLOCK(parent);
885
886         /* remove us from our grandparent's channel list */
887         PCM_LOCK(d);
888         ret = pcm_chn_remove(d, c);
889         PCM_UNLOCK(d);
890
891         /* destroy ourselves */
892         if (ret == 0)
893                 ret = pcm_chn_destroy(c);
894
895         CHN_LOCK(parent);
896
897         return (ret);
898 }
899
900 int
901 #ifdef SND_DEBUG
902 vchan_passthrough(struct pcm_channel *c, const char *caller)
903 #else
904 vchan_sync(struct pcm_channel *c)
905 #endif
906 {
907         int ret;
908
909         KASSERT(c != NULL && c->parentchannel != NULL &&
910             (c->flags & CHN_F_VIRTUAL),
911             ("%s(): invalid passthrough", __func__));
912         CHN_LOCKASSERT(c);
913         CHN_LOCKASSERT(c->parentchannel);
914
915         sndbuf_setspd(c->bufhard, c->parentchannel->speed);
916         c->flags |= CHN_F_PASSTHROUGH;
917         ret = feeder_chain(c);
918         c->flags &= ~(CHN_F_DIRTY | CHN_F_PASSTHROUGH);
919         if (ret != 0)
920                 c->flags |= CHN_F_DIRTY;
921
922 #ifdef SND_DEBUG
923         if (snd_passthrough_verbose != 0) {
924                 char *devname, buf[CHN_NAMELEN];
925
926                 devname = dsp_unit2name(buf, sizeof(buf), c->unit);
927                 device_printf(c->dev,
928                     "%s(%s/%s) %s() -> re-sync err=%d\n",
929                     __func__, (devname != NULL) ? devname : "dspX", c->comm,
930                     caller, ret);
931         }
932 #endif
933
934         return (ret);
935 }
936
937 void
938 vchan_initsys(device_t dev)
939 {
940         struct snddev_info *d;
941         int unit;
942
943         unit = device_get_unit(dev);
944         d = device_get_softc(dev);
945
946         /* Play */
947         SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
948             SYSCTL_CHILDREN(d->play_sysctl_tree),
949             OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW,
950             VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
951             sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
952         SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
953             SYSCTL_CHILDREN(d->play_sysctl_tree),
954             OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RW,
955             VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
956             sysctl_dev_pcm_vchanmode, "A",
957             "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
958         SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
959             SYSCTL_CHILDREN(d->play_sysctl_tree),
960             OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW,
961             VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
962             sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
963         SYSCTL_ADD_PROC(&d->play_sysctl_ctx,
964             SYSCTL_CHILDREN(d->play_sysctl_tree),
965             OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW,
966             VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE,
967             sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
968         /* Rec */
969         SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
970             SYSCTL_CHILDREN(d->rec_sysctl_tree),
971             OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW,
972             VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
973             sysctl_dev_pcm_vchans, "I", "total allocated virtual channel");
974         SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
975             SYSCTL_CHILDREN(d->rec_sysctl_tree),
976             OID_AUTO, "vchanmode", CTLTYPE_STRING | CTLFLAG_RW,
977             VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
978             sysctl_dev_pcm_vchanmode, "A",
979             "vchan format/rate selection: 0=fixed, 1=passthrough, 2=adaptive");
980         SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
981             SYSCTL_CHILDREN(d->rec_sysctl_tree),
982             OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW,
983             VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
984             sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate");
985         SYSCTL_ADD_PROC(&d->rec_sysctl_ctx,
986             SYSCTL_CHILDREN(d->rec_sysctl_tree),
987             OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW,
988             VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE,
989             sysctl_dev_pcm_vchanformat, "A", "virtual channel mixing format");
990 }