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