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