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