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