xref: /freebsd/sys/dev/sound/pcm/sound.c (revision b52b9d56d4e96089873a75f9e29062eec19fabba)
1 /*
2  * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
3  * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it)
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 #include <dev/sound/pcm/sound.h>
29 #include <dev/sound/pcm/vchan.h>
30 #include <sys/sysctl.h>
31 
32 #include "feeder_if.h"
33 
34 SND_DECLARE_FILE("$FreeBSD$");
35 
36 struct snddev_channel {
37 	SLIST_ENTRY(snddev_channel) link;
38 	struct pcm_channel *channel;
39 };
40 
41 struct snddev_info {
42 	SLIST_HEAD(, snddev_channel) channels;
43 	struct pcm_channel *fakechan;
44 	unsigned devcount, playcount, reccount, vchancount;
45 	unsigned flags;
46 	int inprog;
47 	unsigned int bufsz;
48 	void *devinfo;
49 	device_t dev;
50 	char status[SND_STATUSLEN];
51 	struct sysctl_ctx_list sysctl_tree;
52 	struct sysctl_oid *sysctl_tree_top;
53 	void *lock;
54 };
55 
56 devclass_t pcm_devclass;
57 
58 int pcm_veto_load = 1;
59 
60 #ifdef USING_DEVFS
61 int snd_unit = 0;
62 TUNABLE_INT("hw.snd.unit", &snd_unit);
63 #endif
64 
65 int snd_maxautovchans = 0;
66 TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans);
67 
68 SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver");
69 
70 static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose);
71 
72 struct sysctl_ctx_list *
73 snd_sysctl_tree(device_t dev)
74 {
75     	struct snddev_info *d = device_get_softc(dev);
76 
77 	return &d->sysctl_tree;
78 }
79 
80 struct sysctl_oid *
81 snd_sysctl_tree_top(device_t dev)
82 {
83     	struct snddev_info *d = device_get_softc(dev);
84 
85 	return d->sysctl_tree_top;
86 }
87 
88 void *
89 snd_mtxcreate(const char *desc, const char *type)
90 {
91 #ifdef USING_MUTEX
92 	struct mtx *m;
93 
94 	m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
95 	if (m == NULL)
96 		return NULL;
97 	mtx_init(m, desc, type, MTX_RECURSE);
98 	return m;
99 #else
100 	return (void *)0xcafebabe;
101 #endif
102 }
103 
104 void
105 snd_mtxfree(void *m)
106 {
107 #ifdef USING_MUTEX
108 	struct mtx *mtx = m;
109 
110 	mtx_assert(mtx, MA_OWNED);
111 	mtx_destroy(mtx);
112 	free(mtx, M_DEVBUF);
113 #endif
114 }
115 
116 void
117 snd_mtxassert(void *m)
118 {
119 #ifdef USING_MUTEX
120 #ifdef INVARIANTS
121 	struct mtx *mtx = m;
122 
123 	mtx_assert(mtx, MA_OWNED);
124 #endif
125 #endif
126 }
127 
128 void
129 snd_mtxlock(void *m)
130 {
131 #ifdef USING_MUTEX
132 	struct mtx *mtx = m;
133 
134 	mtx_lock(mtx);
135 #endif
136 }
137 
138 void
139 snd_mtxunlock(void *m)
140 {
141 #ifdef USING_MUTEX
142 	struct mtx *mtx = m;
143 
144 	mtx_unlock(mtx);
145 #endif
146 }
147 
148 int
149 snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep)
150 {
151 #ifdef USING_MUTEX
152 	flags &= INTR_MPSAFE;
153 	flags |= INTR_TYPE_AV;
154 #else
155 	flags = INTR_TYPE_AV;
156 #endif
157 	return bus_setup_intr(dev, res, flags, hand, param, cookiep);
158 }
159 
160 void
161 pcm_lock(struct snddev_info *d)
162 {
163 	snd_mtxlock(d->lock);
164 }
165 
166 void
167 pcm_unlock(struct snddev_info *d)
168 {
169 	snd_mtxunlock(d->lock);
170 }
171 
172 struct pcm_channel *
173 pcm_getfakechan(struct snddev_info *d)
174 {
175 	return d->fakechan;
176 }
177 
178 /* return a locked channel */
179 struct pcm_channel *
180 pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum)
181 {
182 	struct pcm_channel *c;
183     	struct snddev_channel *sce;
184 	int err;
185 
186 	snd_mtxassert(d->lock);
187 
188 	/* scan for a free channel */
189 	SLIST_FOREACH(sce, &d->channels, link) {
190 		c = sce->channel;
191 		CHN_LOCK(c);
192 		if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) {
193 			if (chnum == -1 || c->num == chnum) {
194 				c->flags |= CHN_F_BUSY;
195 				c->pid = pid;
196 				return c;
197 			}
198 		}
199 		CHN_UNLOCK(c);
200 	}
201 
202 	/* no channel available */
203 	if (direction == PCMDIR_PLAY) {
204 		if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) {
205 			/* try to create a vchan */
206 			SLIST_FOREACH(sce, &d->channels, link) {
207 				c = sce->channel;
208 				if (!SLIST_EMPTY(&c->children)) {
209 					err = vchan_create(c);
210 					if (!err)
211 						return pcm_chnalloc(d, direction, pid, -1);
212 					else
213 						device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
214 				}
215 			}
216 		}
217 	}
218 
219 	return NULL;
220 }
221 
222 /* release a locked channel and unlock it */
223 int
224 pcm_chnrelease(struct pcm_channel *c)
225 {
226 	CHN_LOCKASSERT(c);
227 	c->flags &= ~CHN_F_BUSY;
228 	c->pid = -1;
229 	CHN_UNLOCK(c);
230 	return 0;
231 }
232 
233 int
234 pcm_chnref(struct pcm_channel *c, int ref)
235 {
236 	int r;
237 
238 	CHN_LOCKASSERT(c);
239 	c->refcount += ref;
240 	r = c->refcount;
241 	return r;
242 }
243 
244 int
245 pcm_inprog(struct snddev_info *d, int delta)
246 {
247 	d->inprog += delta;
248 	return d->inprog;
249 }
250 
251 static void
252 pcm_setmaxautovchans(struct snddev_info *d, int num)
253 {
254 	struct pcm_channel *c;
255     	struct snddev_channel *sce;
256 	int err, done;
257 
258 	if (num > 0 && d->vchancount == 0) {
259 		SLIST_FOREACH(sce, &d->channels, link) {
260 			c = sce->channel;
261 			if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) {
262 				c->flags |= CHN_F_BUSY;
263 				err = vchan_create(c);
264 				if (err) {
265 					device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
266 					c->flags &= ~CHN_F_BUSY;
267 				}
268 				return;
269 			}
270 		}
271 	}
272 	if (num == 0 && d->vchancount > 0) {
273 		done = 0;
274 		while (!done) {
275 			done = 1;
276 			SLIST_FOREACH(sce, &d->channels, link) {
277 				c = sce->channel;
278 				if ((c->flags & CHN_F_VIRTUAL) && !(c->flags & CHN_F_BUSY)) {
279 					done = 0;
280 					err = vchan_destroy(c);
281 					if (err)
282 						device_printf(d->dev, "vchan_destroy(%s) == %d\n", c->name, err);
283 					break;		/* restart */
284 				}
285 			}
286 		}
287 	}
288 }
289 
290 #ifdef USING_DEVFS
291 static int
292 sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS)
293 {
294 	struct snddev_info *d;
295 	int error, unit;
296 
297 	unit = snd_unit;
298 	error = sysctl_handle_int(oidp, &unit, sizeof(unit), req);
299 	if (error == 0 && req->newptr != NULL) {
300 		if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass))
301 			return EINVAL;
302 		d = devclass_get_softc(pcm_devclass, unit);
303 		if (d == NULL || SLIST_EMPTY(&d->channels))
304 			return EINVAL;
305 		snd_unit = unit;
306 	}
307 	return (error);
308 }
309 SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW,
310             0, sizeof(int), sysctl_hw_snd_unit, "I", "");
311 #endif
312 
313 static int
314 sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
315 {
316 	struct snddev_info *d;
317 	int i, v, error;
318 
319 	v = snd_maxautovchans;
320 	error = sysctl_handle_int(oidp, &v, sizeof(v), req);
321 	if (error == 0 && req->newptr != NULL) {
322 		if (v < 0 || v >= SND_MAXVCHANS)
323 			return EINVAL;
324 		if (v != snd_maxautovchans) {
325 			for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
326 				d = devclass_get_softc(pcm_devclass, i);
327 				if (!d)
328 					continue;
329 				pcm_setmaxautovchans(d, v);
330 			}
331 		}
332 		snd_maxautovchans = v;
333 	}
334 	return (error);
335 }
336 SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW,
337             0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "");
338 
339 struct pcm_channel *
340 pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo)
341 {
342 	struct pcm_channel *ch;
343 	char *dirs;
344     	int err, *pnum;
345 
346 	switch(dir) {
347 	case PCMDIR_PLAY:
348 		dirs = "play";
349 		pnum = &d->playcount;
350 		break;
351 
352 	case PCMDIR_REC:
353 		dirs = "record";
354 		pnum = &d->reccount;
355 		break;
356 
357 	case PCMDIR_VIRTUAL:
358 		dirs = "virtual";
359 		dir = PCMDIR_PLAY;
360 		pnum = &d->vchancount;
361 		break;
362 
363 	default:
364 		return NULL;
365 	}
366 
367 	ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
368 	if (!ch)
369 		return NULL;
370 
371 	ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK);
372 	if (!ch->methods) {
373 		free(ch, M_DEVBUF);
374 
375 		return NULL;
376 	}
377 
378 	ch->num = (*pnum)++;
379 
380 	ch->pid = -1;
381 	ch->parentsnddev = d;
382 	ch->parentchannel = parent;
383 	ch->dev = d->dev;
384 	snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(d->dev), dirs, ch->num);
385 
386 	err = chn_init(ch, devinfo, dir);
387 	if (err) {
388 		device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err);
389 		kobj_delete(ch->methods, M_DEVBUF);
390 		free(ch, M_DEVBUF);
391 		(*pnum)--;
392 
393 		return NULL;
394 	}
395 
396 	return ch;
397 }
398 
399 int
400 pcm_chn_destroy(struct pcm_channel *ch)
401 {
402 	struct snddev_info *d;
403 	int err;
404 
405 	d = ch->parentsnddev;
406 	err = chn_kill(ch);
407 	if (err) {
408 		device_printf(d->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err);
409 		return err;
410 	}
411 
412 	if (ch->direction == PCMDIR_REC)
413 		d->reccount--;
414 	else if (ch->flags & CHN_F_VIRTUAL)
415 		d->vchancount--;
416 	else
417 		d->playcount--;
418 
419 	kobj_delete(ch->methods, M_DEVBUF);
420 	free(ch, M_DEVBUF);
421 
422 	return 0;
423 }
424 
425 int
426 pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch, int mkdev)
427 {
428     	struct snddev_channel *sce, *tmp, *after;
429     	int unit = device_get_unit(d->dev);
430 
431 	sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO);
432 	if (!sce) {
433 		return ENOMEM;
434 	}
435 
436 	snd_mtxlock(d->lock);
437 
438 	sce->channel = ch;
439 	if (SLIST_EMPTY(&d->channels)) {
440 		SLIST_INSERT_HEAD(&d->channels, sce, link);
441 	} else {
442 		after = NULL;
443 		SLIST_FOREACH(tmp, &d->channels, link) {
444 			after = tmp;
445 		}
446 		SLIST_INSERT_AFTER(after, sce, link);
447 	}
448 
449 	if (mkdev) {
450 		dsp_register(unit, d->devcount++);
451 		if (ch->direction == PCMDIR_REC)
452 			dsp_registerrec(unit, ch->num);
453 	}
454 
455 	snd_mtxunlock(d->lock);
456 
457 	return 0;
458 }
459 
460 int
461 pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch, int rmdev)
462 {
463     	struct snddev_channel *sce;
464     	int unit = device_get_unit(d->dev);
465 
466 	snd_mtxlock(d->lock);
467 	SLIST_FOREACH(sce, &d->channels, link) {
468 		if (sce->channel == ch)
469 			goto gotit;
470 	}
471 	snd_mtxunlock(d->lock);
472 	return EINVAL;
473 gotit:
474 	SLIST_REMOVE(&d->channels, sce, snddev_channel, link);
475 	free(sce, M_DEVBUF);
476 
477 	if (rmdev) {
478 		dsp_unregister(unit, --d->devcount);
479 		if (ch->direction == PCMDIR_REC)
480 			dsp_unregisterrec(unit, ch->num);
481 	}
482 	snd_mtxunlock(d->lock);
483 
484 	return 0;
485 }
486 
487 int
488 pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
489 {
490     	struct snddev_info *d = device_get_softc(dev);
491 	struct pcm_channel *ch;
492     	int err;
493 
494 	ch = pcm_chn_create(d, NULL, cls, dir, devinfo);
495 	if (!ch) {
496 		device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo);
497 		return ENODEV;
498 	}
499 
500 	err = pcm_chn_add(d, ch, 1);
501 	if (err) {
502 		device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
503 		pcm_chn_destroy(ch);
504 		return err;
505 	}
506 
507 	if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) &&
508 	    ch->direction == PCMDIR_PLAY && d->vchancount == 0) {
509 		ch->flags |= CHN_F_BUSY;
510 		err = vchan_create(ch);
511 		if (err) {
512 			device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err);
513 			ch->flags &= ~CHN_F_BUSY;
514 		}
515 	}
516 
517 	return err;
518 }
519 
520 static int
521 pcm_killchan(device_t dev)
522 {
523     	struct snddev_info *d = device_get_softc(dev);
524     	struct snddev_channel *sce;
525 
526 	snd_mtxlock(d->lock);
527 	sce = SLIST_FIRST(&d->channels);
528 	snd_mtxunlock(d->lock);
529 
530 	return pcm_chn_remove(d, sce->channel, 1);
531 }
532 
533 int
534 pcm_setstatus(device_t dev, char *str)
535 {
536     	struct snddev_info *d = device_get_softc(dev);
537 
538 	snd_mtxlock(d->lock);
539 	strncpy(d->status, str, SND_STATUSLEN);
540 	snd_mtxunlock(d->lock);
541 	return 0;
542 }
543 
544 u_int32_t
545 pcm_getflags(device_t dev)
546 {
547     	struct snddev_info *d = device_get_softc(dev);
548 
549 	return d->flags;
550 }
551 
552 void
553 pcm_setflags(device_t dev, u_int32_t val)
554 {
555     	struct snddev_info *d = device_get_softc(dev);
556 
557 	d->flags = val;
558 }
559 
560 void *
561 pcm_getdevinfo(device_t dev)
562 {
563     	struct snddev_info *d = device_get_softc(dev);
564 
565 	return d->devinfo;
566 }
567 
568 unsigned int
569 pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max)
570 {
571     	struct snddev_info *d = device_get_softc(dev);
572 	int sz, x;
573 
574 	sz = 0;
575 	if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) {
576 		x = sz;
577 		RANGE(sz, min, max);
578 		if (x != sz)
579 			device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz);
580 		x = min;
581 		while (x < sz)
582 			x <<= 1;
583 		if (x > sz)
584 			x >>= 1;
585 		if (x != sz) {
586 			device_printf(dev, "'buffersize=%d' hint is not a power of 2, using %d\n", sz, x);
587 			sz = x;
588 		}
589 	} else {
590 		sz = deflt;
591 	}
592 
593 	d->bufsz = sz;
594 
595 	return sz;
596 }
597 
598 int
599 pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
600 {
601     	struct snddev_info *d = device_get_softc(dev);
602 
603 	if (pcm_veto_load) {
604 		device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load);
605 
606 		return EINVAL;
607 	}
608 
609 	d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev");
610 	snd_mtxlock(d->lock);
611 
612 	d->flags = 0;
613 	d->dev = dev;
614 	d->devinfo = devinfo;
615 	d->devcount = 0;
616 	d->reccount = 0;
617 	d->playcount = 0;
618 	d->vchancount = 0;
619 	d->inprog = 0;
620 
621 	if (((numplay == 0) || (numrec == 0)) && (numplay != numrec))
622 		d->flags |= SD_F_SIMPLEX;
623 
624 	d->fakechan = fkchan_setup(dev);
625 	chn_init(d->fakechan, NULL, 0);
626 
627 #ifdef SND_DYNSYSCTL
628 	sysctl_ctx_init(&d->sysctl_tree);
629 	d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree,
630 				 SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO,
631 				 device_get_nameunit(dev), CTLFLAG_RD, 0, "");
632 	if (d->sysctl_tree_top == NULL) {
633 		sysctl_ctx_free(&d->sysctl_tree);
634 		goto no;
635 	}
636 	SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
637             OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "");
638 #endif
639 	if (numplay > 0)
640 		vchan_initsys(dev);
641 	if (numplay == 1)
642 		d->flags |= SD_F_AUTOVCHAN;
643 
644 	snd_mtxunlock(d->lock);
645 	sndstat_register(dev, d->status, sndstat_prepare_pcm);
646     	return 0;
647 no:
648 	snd_mtxfree(d->lock);
649 	return ENXIO;
650 }
651 
652 int
653 pcm_unregister(device_t dev)
654 {
655     	struct snddev_info *d = device_get_softc(dev);
656     	struct snddev_channel *sce;
657 	struct pcm_channel *ch;
658 
659 	snd_mtxlock(d->lock);
660 	if (d->inprog) {
661 		device_printf(dev, "unregister: operation in progress\n");
662 		snd_mtxunlock(d->lock);
663 		return EBUSY;
664 	}
665 	if (sndstat_busy() != 0) {
666 		device_printf(dev, "unregister: sndstat busy\n");
667 		snd_mtxunlock(d->lock);
668 		return EBUSY;
669 	}
670 	SLIST_FOREACH(sce, &d->channels, link) {
671 		ch = sce->channel;
672 		if (ch->refcount > 0) {
673 			device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid);
674 			snd_mtxunlock(d->lock);
675 			return EBUSY;
676 		}
677 	}
678 	if (mixer_uninit(dev)) {
679 		device_printf(dev, "unregister: mixer busy\n");
680 		snd_mtxunlock(d->lock);
681 		return EBUSY;
682 	}
683 
684 #ifdef SND_DYNSYSCTL
685 	d->sysctl_tree_top = NULL;
686 	sysctl_ctx_free(&d->sysctl_tree);
687 #endif
688 	while (!SLIST_EMPTY(&d->channels))
689 		pcm_killchan(dev);
690 
691 	chn_kill(d->fakechan);
692 	fkchan_kill(d->fakechan);
693 
694 	snd_mtxfree(d->lock);
695 	return 0;
696 }
697 
698 /************************************************************************/
699 
700 static int
701 sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
702 {
703     	struct snddev_info *d;
704     	struct snddev_channel *sce;
705 	struct pcm_channel *c;
706 	struct pcm_feeder *f;
707     	int pc, rc, vc;
708 
709 	if (verbose < 1)
710 		return 0;
711 
712 	d = device_get_softc(dev);
713 	if (!d)
714 		return ENXIO;
715 
716 	snd_mtxlock(d->lock);
717 	if (!SLIST_EMPTY(&d->channels)) {
718 		pc = rc = vc = 0;
719 		SLIST_FOREACH(sce, &d->channels, link) {
720 			c = sce->channel;
721 			if (c->direction == PCMDIR_PLAY) {
722 				if (c->flags & CHN_F_VIRTUAL)
723 					vc++;
724 				else
725 					pc++;
726 			} else
727 				rc++;
728 		}
729 		sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount,
730 				(d->flags & SD_F_SIMPLEX)? "" : " duplex",
731 #ifdef USING_DEVFS
732 				(device_get_unit(dev) == snd_unit)? " default" : ""
733 #else
734 				""
735 #endif
736 				);
737 		if (verbose <= 1)
738 			goto skipverbose;
739 		SLIST_FOREACH(sce, &d->channels, link) {
740 			c = sce->channel;
741 			sbuf_printf(s, "\n\t");
742 
743 			sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name);
744 			sbuf_printf(s, "spd %d", c->speed);
745 			if (c->speed != sndbuf_getspd(c->bufhard))
746 				sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard));
747 			sbuf_printf(s, ", fmt 0x%08x", c->format);
748 			if (c->format != sndbuf_getfmt(c->bufhard))
749 				sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard));
750 			sbuf_printf(s, ", flags %08x", c->flags);
751 			if (c->pid != -1)
752 				sbuf_printf(s, ", pid %d", c->pid);
753 			sbuf_printf(s, "\n\t");
754 
755 			if (c->bufhard != NULL && c->bufsoft != NULL) {
756 				sbuf_printf(s, "interrupts %d, ", c->interrupts);
757 				if (c->direction == PCMDIR_REC)
758 					sbuf_printf(s, "overruns %d, hfree %d, sfree %d",
759 						c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft));
760 				else
761 					sbuf_printf(s, "underruns %d, ready %d",
762 						c->xruns, sndbuf_getready(c->bufsoft));
763 				sbuf_printf(s, "\n\t");
764 			}
765 
766 			sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland");
767 			sbuf_printf(s, " -> ");
768 			f = c->feeder;
769 			while (f->source != NULL)
770 				f = f->source;
771 			while (f != NULL) {
772 				sbuf_printf(s, "%s", f->class->name);
773 				if (f->desc->type == FEEDER_FMT)
774 					sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out);
775 				if (f->desc->type == FEEDER_RATE)
776 					sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST));
777 				if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER)
778 					sbuf_printf(s, "(0x%08x)", f->desc->out);
779 				sbuf_printf(s, " -> ");
780 				f = f->parent;
781 			}
782 			sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware");
783 		}
784 	} else
785 		sbuf_printf(s, " (mixer only)");
786 skipverbose:
787 	snd_mtxunlock(d->lock);
788 
789 	return 0;
790 }
791 
792 /************************************************************************/
793 
794 #ifdef SND_DYNSYSCTL
795 int
796 sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
797 {
798 	struct snddev_info *d;
799     	struct snddev_channel *sce;
800 	struct pcm_channel *c;
801 	int err, newcnt, cnt;
802 
803 	d = oidp->oid_arg1;
804 
805 	pcm_lock(d);
806 	cnt = 0;
807 	SLIST_FOREACH(sce, &d->channels, link) {
808 		c = sce->channel;
809 		if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL))
810 			cnt++;
811 	}
812 	newcnt = cnt;
813 
814 	pcm_unlock(d);
815 	err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req);
816 	pcm_lock(d);
817 	/*
818 	 * Since we dropped the pcm_lock, reload cnt now as it may
819 	 * have changed.
820 	 */
821 	cnt = 0;
822 	SLIST_FOREACH(sce, &d->channels, link) {
823 		c = sce->channel;
824 		if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL))
825 			cnt++;
826 	}
827 	if (err == 0 && req->newptr != NULL) {
828 		if (newcnt < 0 || newcnt > SND_MAXVCHANS) {
829 			pcm_unlock(d);
830 			return EINVAL;
831 		}
832 
833 		if (newcnt > cnt) {
834 			/* add new vchans - find a parent channel first */
835 			SLIST_FOREACH(sce, &d->channels, link) {
836 				c = sce->channel;
837 				/* not a candidate if not a play channel */
838 				if (c->direction != PCMDIR_PLAY)
839 					continue;
840 				/* not a candidate if a virtual channel */
841 				if (c->flags & CHN_F_VIRTUAL)
842 					continue;
843 				/* not a candidate if it's in use */
844 				if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children)))
845 					continue;
846 				/*
847 				 * if we get here we're a nonvirtual play channel, and either
848 				 * 1) not busy
849 				 * 2) busy with children, not directly open
850 				 *
851 				 * thus we can add children
852 				 */
853 				goto addok;
854 			}
855 			pcm_unlock(d);
856 			return EBUSY;
857 addok:
858 			c->flags |= CHN_F_BUSY;
859 			while (err == 0 && newcnt > cnt) {
860 				err = vchan_create(c);
861 				if (err == 0)
862 					cnt++;
863 			}
864 			if (SLIST_EMPTY(&c->children))
865 				c->flags &= ~CHN_F_BUSY;
866 		} else if (newcnt < cnt) {
867 			while (err == 0 && newcnt < cnt) {
868 				SLIST_FOREACH(sce, &d->channels, link) {
869 					c = sce->channel;
870 					if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL)
871 						goto remok;
872 				}
873 				pcm_unlock(d);
874 				return EINVAL;
875 remok:
876 				err = vchan_destroy(c);
877 				if (err == 0)
878 					cnt--;
879 			}
880 		}
881 	}
882 
883 	pcm_unlock(d);
884 	return err;
885 }
886 #endif
887 
888 /************************************************************************/
889 
890 static moduledata_t sndpcm_mod = {
891 	"snd_pcm",
892 	NULL,
893 	NULL
894 };
895 DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
896 MODULE_VERSION(snd_pcm, PCM_MODVER);
897