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