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