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