sound.c revision 82492
1162856Sdes/*
276259Sgreen * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
376259Sgreen * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it)
476259Sgreen * All rights reserved.
576259Sgreen *
676259Sgreen * Redistribution and use in source and binary forms, with or without
776259Sgreen * modification, are permitted provided that the following conditions
876259Sgreen * are met:
976259Sgreen * 1. Redistributions of source code must retain the above copyright
1076259Sgreen *    notice, this list of conditions and the following disclaimer.
1176259Sgreen * 2. Redistributions in binary form must reproduce the above copyright
1276259Sgreen *    notice, this list of conditions and the following disclaimer in the
1376259Sgreen *    documentation and/or other materials provided with the distribution.
1476259Sgreen *
15162856Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16103134Sume * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1798937Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18255767Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1976259Sgreen * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2098937Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2198937Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22103134Sume * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2376259Sgreen * 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 82492 2001-08-29 09:17:43Z 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, chancount, vchancount;
45	unsigned flags;
46	int inprog;
47	void *devinfo;
48	device_t dev;
49	char status[SND_STATUSLEN];
50	struct sysctl_ctx_list sysctl_tree;
51	struct sysctl_oid *sysctl_tree_top;
52	void *lock;
53};
54
55devclass_t pcm_devclass;
56
57#ifdef USING_DEVFS
58int snd_unit = 0;
59TUNABLE_INT("hw.snd.unit", &snd_unit);
60#endif
61
62int snd_maxautovchans = 0;
63TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans);
64
65SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver");
66
67static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose);
68
69struct sysctl_ctx_list *
70snd_sysctl_tree(device_t dev)
71{
72    	struct snddev_info *d = device_get_softc(dev);
73
74	return &d->sysctl_tree;
75}
76
77struct sysctl_oid *
78snd_sysctl_tree_top(device_t dev)
79{
80    	struct snddev_info *d = device_get_softc(dev);
81
82	return d->sysctl_tree_top;
83}
84
85void *
86snd_mtxcreate(const char *desc)
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, MTX_RECURSE);
95	return m;
96#else
97	return (void *)0xcafebabe;
98#endif
99}
100
101void
102snd_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
113void
114snd_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
125void
126snd_mtxlock(void *m)
127{
128#ifdef USING_MUTEX
129	struct mtx *mtx = m;
130
131	mtx_lock(mtx);
132#endif
133}
134
135void
136snd_mtxunlock(void *m)
137{
138#ifdef USING_MUTEX
139	struct mtx *mtx = m;
140
141	mtx_unlock(mtx);
142#endif
143}
144
145int
146snd_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
157void
158pcm_lock(struct snddev_info *d)
159{
160	snd_mtxlock(d->lock);
161}
162
163void
164pcm_unlock(struct snddev_info *d)
165{
166	snd_mtxunlock(d->lock);
167}
168
169struct pcm_channel *
170pcm_getfakechan(struct snddev_info *d)
171{
172	return d->fakechan;
173}
174
175/* return a locked channel */
176struct pcm_channel *
177pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid)
178{
179	struct pcm_channel *c;
180    	struct snddev_channel *sce;
181	int err;
182
183	snd_mtxassert(d->lock);
184
185	/* scan for a free channel */
186	SLIST_FOREACH(sce, &d->channels, link) {
187		c = sce->channel;
188		CHN_LOCK(c);
189		if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) {
190			c->flags |= CHN_F_BUSY;
191			c->pid = pid;
192			return c;
193		}
194		CHN_UNLOCK(c);
195	}
196
197	/* no channel available */
198	if (direction == PCMDIR_PLAY) {
199		if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) {
200			/* try to create a vchan */
201			SLIST_FOREACH(sce, &d->channels, link) {
202				c = sce->channel;
203				if (!SLIST_EMPTY(&c->children)) {
204					err = vchan_create(c);
205					if (!err)
206						return pcm_chnalloc(d, direction, pid);
207					else
208						device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
209				}
210			}
211		}
212	}
213
214	return NULL;
215}
216
217/* release a locked channel and unlock it */
218int
219pcm_chnrelease(struct pcm_channel *c)
220{
221	CHN_LOCKASSERT(c);
222	c->flags &= ~CHN_F_BUSY;
223	c->pid = -1;
224	CHN_UNLOCK(c);
225	return 0;
226}
227
228int
229pcm_chnref(struct pcm_channel *c, int ref)
230{
231	int r;
232
233	CHN_LOCKASSERT(c);
234	c->refcount += ref;
235	r = c->refcount;
236	return r;
237}
238
239int
240pcm_inprog(struct snddev_info *d, int delta)
241{
242	d->inprog += delta;
243	return d->inprog;
244}
245
246static void
247pcm_setmaxautovchans(struct snddev_info *d, int num)
248{
249	struct pcm_channel *c;
250    	struct snddev_channel *sce;
251	int err, done;
252
253	if (num > 0 && d->vchancount == 0) {
254		SLIST_FOREACH(sce, &d->channels, link) {
255			c = sce->channel;
256			if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) {
257				c->flags |= CHN_F_BUSY;
258				err = vchan_create(c);
259				if (err) {
260					device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
261					c->flags &= ~CHN_F_BUSY;
262				}
263				return;
264			}
265		}
266	}
267	if (num == 0 && d->vchancount > 0) {
268		done = 0;
269		while (!done) {
270			done = 1;
271			SLIST_FOREACH(sce, &d->channels, link) {
272				c = sce->channel;
273				if ((c->flags & CHN_F_VIRTUAL) && !(c->flags & CHN_F_BUSY)) {
274					done = 0;
275					err = vchan_destroy(c);
276					if (err)
277						device_printf(d->dev, "vchan_destroy(%s) == %d\n", c->name, err);
278					goto restart;
279				}
280			}
281restart:
282		}
283	}
284}
285
286#ifdef USING_DEVFS
287static int
288sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS)
289{
290	struct snddev_info *d;
291	int error, unit;
292
293	unit = snd_unit;
294	error = sysctl_handle_int(oidp, &unit, sizeof(unit), req);
295	if (error == 0 && req->newptr != NULL) {
296		if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass))
297			return EINVAL;
298		d = devclass_get_softc(pcm_devclass, unit);
299		if (d == NULL || SLIST_EMPTY(&d->channels))
300			return EINVAL;
301		snd_unit = unit;
302	}
303	return (error);
304}
305SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW,
306            0, sizeof(int), sysctl_hw_snd_unit, "I", "");
307#endif
308
309static int
310sysctl_hw_snd_maxautovchans(SYSCTL_HANDLER_ARGS)
311{
312	struct snddev_info *d;
313	int i, v, error;
314
315	v = snd_maxautovchans;
316	error = sysctl_handle_int(oidp, &v, sizeof(v), req);
317	if (error == 0 && req->newptr != NULL) {
318		if (v < 0 || v >= SND_MAXVCHANS)
319			return EINVAL;
320		if (v != snd_maxautovchans) {
321			for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) {
322				d = devclass_get_softc(pcm_devclass, i);
323				if (!d)
324					continue;
325				pcm_setmaxautovchans(d, v);
326			}
327		}
328		snd_maxautovchans = v;
329	}
330	return (error);
331}
332SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW,
333            0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "");
334
335struct pcm_channel *
336pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo)
337{
338	struct pcm_channel *ch;
339	char *dirs;
340    	int err;
341
342	switch(dir) {
343	case PCMDIR_PLAY:
344		dirs = "play";
345		break;
346	case PCMDIR_REC:
347		dirs = "record";
348		break;
349	case PCMDIR_VIRTUAL:
350		dirs = "virtual";
351		dir = PCMDIR_PLAY;
352		break;
353	default:
354		return NULL;
355	}
356
357	ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
358	if (!ch)
359		return NULL;
360
361	ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK);
362	if (!ch->methods) {
363		free(ch, M_DEVBUF);
364		return NULL;
365	}
366
367	ch->pid = -1;
368	ch->parentsnddev = d;
369	ch->parentchannel = parent;
370	snprintf(ch->name, 32, "%s:%d:%s", device_get_nameunit(d->dev), d->chancount, dirs);
371
372	err = chn_init(ch, devinfo, dir);
373	if (err) {
374		device_printf(d->dev, "chn_init() for channel %d (%s) failed: err = %d\n", d->chancount, dirs, err);
375		kobj_delete(ch->methods, M_DEVBUF);
376		free(ch, M_DEVBUF);
377		return NULL;
378	}
379
380	return ch;
381}
382
383int
384pcm_chn_destroy(struct pcm_channel *ch)
385{
386	int err;
387
388	err = chn_kill(ch);
389	if (err) {
390		device_printf(ch->parentsnddev->dev, "chn_kill() for %s failed, err = %d\n", ch->name, err);
391		return err;
392	}
393
394	kobj_delete(ch->methods, M_DEVBUF);
395	free(ch, M_DEVBUF);
396
397	return 0;
398}
399
400int
401pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch, int mkdev)
402{
403    	struct snddev_channel *sce, *tmp, *after;
404    	int unit = device_get_unit(d->dev);
405
406	snd_mtxlock(d->lock);
407
408	sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO);
409	if (!sce) {
410		snd_mtxunlock(d->lock);
411		return ENOMEM;
412	}
413
414	sce->channel = ch;
415	if (SLIST_EMPTY(&d->channels)) {
416		SLIST_INSERT_HEAD(&d->channels, sce, link);
417	} else {
418		after = NULL;
419		SLIST_FOREACH(tmp, &d->channels, link) {
420			after = tmp;
421		}
422		SLIST_INSERT_AFTER(after, sce, link);
423	}
424
425	if (mkdev)
426		dsp_register(unit, d->devcount++);
427    	d->chancount++;
428	if (ch->flags & CHN_F_VIRTUAL)
429		d->vchancount++;
430
431	snd_mtxunlock(d->lock);
432
433	return 0;
434}
435
436int
437pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch, int rmdev)
438{
439    	struct snddev_channel *sce;
440    	int unit = device_get_unit(d->dev);
441
442	snd_mtxlock(d->lock);
443	SLIST_FOREACH(sce, &d->channels, link) {
444		if (sce->channel == ch)
445			goto gotit;
446	}
447	snd_mtxunlock(d->lock);
448	return EINVAL;
449gotit:
450	if (ch->flags & CHN_F_VIRTUAL)
451		d->vchancount--;
452	d->chancount--;
453	SLIST_REMOVE(&d->channels, sce, snddev_channel, link);
454	free(sce, M_DEVBUF);
455
456	if (rmdev)
457		dsp_unregister(unit, --d->devcount);
458	snd_mtxunlock(d->lock);
459
460	return 0;
461}
462
463int
464pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
465{
466    	struct snddev_info *d = device_get_softc(dev);
467	struct pcm_channel *ch;
468    	int err;
469
470	ch = pcm_chn_create(d, NULL, cls, dir, devinfo);
471	if (!ch) {
472		device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo);
473		return ENODEV;
474	}
475
476	err = pcm_chn_add(d, ch, 1);
477	if (err) {
478		device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
479		pcm_chn_destroy(ch);
480		return err;
481	}
482
483	if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN)) {
484		ch->flags |= CHN_F_BUSY;
485		err = vchan_create(ch);
486		if (err) {
487			device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err);
488			ch->flags &= ~CHN_F_BUSY;
489		}
490	}
491
492	return err;
493}
494
495static int
496pcm_killchan(device_t dev)
497{
498    	struct snddev_info *d = device_get_softc(dev);
499    	struct snddev_channel *sce;
500
501	snd_mtxlock(d->lock);
502	sce = SLIST_FIRST(&d->channels);
503	snd_mtxunlock(d->lock);
504
505	return pcm_chn_remove(d, sce->channel, 1);
506}
507
508int
509pcm_setstatus(device_t dev, char *str)
510{
511    	struct snddev_info *d = device_get_softc(dev);
512
513	snd_mtxlock(d->lock);
514	strncpy(d->status, str, SND_STATUSLEN);
515	snd_mtxunlock(d->lock);
516	return 0;
517}
518
519u_int32_t
520pcm_getflags(device_t dev)
521{
522    	struct snddev_info *d = device_get_softc(dev);
523
524	return d->flags;
525}
526
527void
528pcm_setflags(device_t dev, u_int32_t val)
529{
530    	struct snddev_info *d = device_get_softc(dev);
531
532	d->flags = val;
533}
534
535void *
536pcm_getdevinfo(device_t dev)
537{
538    	struct snddev_info *d = device_get_softc(dev);
539
540	return d->devinfo;
541}
542
543int
544pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
545{
546    	struct snddev_info *d = device_get_softc(dev);
547
548	d->lock = snd_mtxcreate(device_get_nameunit(dev));
549	snd_mtxlock(d->lock);
550
551	d->flags = 0;
552	d->dev = dev;
553	d->devinfo = devinfo;
554	d->devcount = 0;
555	d->chancount = 0;
556	d->vchancount = 0;
557	d->inprog = 0;
558
559	if (((numplay == 0) || (numrec == 0)) && (numplay != numrec))
560		d->flags |= SD_F_SIMPLEX;
561
562	d->fakechan = fkchan_setup(dev);
563	chn_init(d->fakechan, NULL, 0);
564
565#ifdef SND_DYNSYSCTL
566	sysctl_ctx_init(&d->sysctl_tree);
567	d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree,
568				 SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO,
569				 device_get_nameunit(dev), CTLFLAG_RD, 0, "");
570	if (d->sysctl_tree_top == NULL) {
571		sysctl_ctx_free(&d->sysctl_tree);
572		goto no;
573	}
574#endif
575	if (numplay > 0)
576		vchan_initsys(dev);
577	if (numplay == 1)
578		d->flags |= SD_F_AUTOVCHAN;
579
580	snd_mtxunlock(d->lock);
581	sndstat_register(dev, d->status, sndstat_prepare_pcm);
582    	return 0;
583no:
584	snd_mtxfree(d->lock);
585	return ENXIO;
586}
587
588int
589pcm_unregister(device_t dev)
590{
591    	struct snddev_info *d = device_get_softc(dev);
592    	struct snddev_channel *sce;
593
594	snd_mtxlock(d->lock);
595	if (d->inprog) {
596		device_printf(dev, "unregister: operation in progress");
597		snd_mtxunlock(d->lock);
598		return EBUSY;
599	}
600	SLIST_FOREACH(sce, &d->channels, link) {
601		if (sce->channel->refcount > 0) {
602			device_printf(dev, "unregister: channel busy");
603			snd_mtxunlock(d->lock);
604			return EBUSY;
605		}
606	}
607	if (mixer_uninit(dev)) {
608		device_printf(dev, "unregister: mixer busy");
609		snd_mtxunlock(d->lock);
610		return EBUSY;
611	}
612
613#ifdef SND_DYNSYSCTL
614	d->sysctl_tree_top = NULL;
615	sysctl_ctx_free(&d->sysctl_tree);
616#endif
617	while (!SLIST_EMPTY(&d->channels))
618		pcm_killchan(dev);
619
620	chn_kill(d->fakechan);
621	fkchan_kill(d->fakechan);
622
623	snd_mtxfree(d->lock);
624	return 0;
625}
626
627/************************************************************************/
628
629static int
630sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
631{
632    	struct snddev_info *d;
633    	struct snddev_channel *sce;
634	struct pcm_channel *c;
635	struct pcm_feeder *f;
636	char *fsep;
637    	int pc, rc, vc;
638
639	if (verbose < 1)
640		return 0;
641
642	d = device_get_softc(dev);
643	if (!d)
644		return ENXIO;
645
646	snd_mtxlock(d->lock);
647	if (!SLIST_EMPTY(&d->channels)) {
648		pc = rc = vc = 0;
649		SLIST_FOREACH(sce, &d->channels, link) {
650			c = sce->channel;
651			if (c->direction == PCMDIR_PLAY) {
652				if (c->flags & CHN_F_VIRTUAL)
653					vc++;
654				else
655					pc++;
656			} else
657				rc++;
658		}
659		sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", pc, rc, vc,
660				(d->flags & SD_F_SIMPLEX)? "" : " duplex",
661#ifdef USING_DEVFS
662				(device_get_unit(dev) == snd_unit)? " default" : ""
663#else
664				""
665#endif
666				);
667		if (verbose <= 1)
668			goto skipverbose;
669		SLIST_FOREACH(sce, &d->channels, link) {
670			c = sce->channel;
671			sbuf_printf(s, "\n\t");
672
673			sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name);
674			sbuf_printf(s, "speed %d, format %08x, flags %08x", c->speed, c->format, c->flags);
675			if (c->pid != -1)
676				sbuf_printf(s, ", pid %d", c->pid);
677			sbuf_printf(s, "\n\t");
678			if (c->bufhard != NULL && c->bufsoft != NULL) {
679				sbuf_printf(s, "interrupts %d, ", c->interrupts);
680				if (c->direction == PCMDIR_REC)
681					sbuf_printf(s, "overruns %d, hfree %d, sfree %d",
682						c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft));
683				else
684					sbuf_printf(s, "underruns %d, ready %d",
685						c->xruns, sndbuf_getready(c->bufsoft));
686				sbuf_printf(s, "\n\t");
687			}
688			fsep = (c->direction == PCMDIR_REC)? " -> " : " <- ";
689			sbuf_printf(s, "{hardware}%s", fsep);
690			f = c->feeder;
691			while (f) {
692				sbuf_printf(s, "%s", f->class->name);
693				if (f->desc->type == FEEDER_FMT)
694					sbuf_printf(s, "(%08x%s%08x)", f->desc->out, fsep, f->desc->in);
695				if (f->desc->type == FEEDER_RATE)
696					sbuf_printf(s, "(%d%s%d)", FEEDER_GET(f, FEEDRATE_DST), fsep, FEEDER_GET(f, FEEDRATE_SRC));
697				if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER)
698					sbuf_printf(s, "(%08x)", f->desc->out);
699				sbuf_printf(s, "%s", fsep);
700				f = f->source;
701			}
702			sbuf_printf(s, "{userland}");
703		}
704skipverbose:
705	} else
706		sbuf_printf(s, " (mixer only)");
707	snd_mtxunlock(d->lock);
708
709	return 0;
710}
711
712/************************************************************************/
713
714#ifdef SND_DYNSYSCTL
715int
716sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS)
717{
718	struct snddev_info *d;
719    	struct snddev_channel *sce;
720	struct pcm_channel *c;
721	int err, oldcnt, newcnt, cnt;
722
723	d = oidp->oid_arg1;
724
725	pcm_lock(d);
726	cnt = 0;
727	SLIST_FOREACH(sce, &d->channels, link) {
728		c = sce->channel;
729		if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL))
730			cnt++;
731	}
732	oldcnt = cnt;
733	newcnt = cnt;
734
735	err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req);
736	if (err == 0 && req->newptr != NULL) {
737		if (newcnt < 0 || newcnt > SND_MAXVCHANS) {
738			pcm_unlock(d);
739			return EINVAL;
740		}
741
742		if (newcnt > cnt) {
743			/* add new vchans - find a parent channel first */
744			SLIST_FOREACH(sce, &d->channels, link) {
745				c = sce->channel;
746				/* not a candidate if not a play channel */
747				if (c->direction != PCMDIR_PLAY)
748					goto addskip;
749				/* not a candidate if a virtual channel */
750				if (c->flags & CHN_F_VIRTUAL)
751					goto addskip;
752				/* not a candidate if it's in use */
753				if ((c->flags & CHN_F_BUSY) && (SLIST_EMPTY(&c->children)))
754					goto addskip;
755				/*
756				 * if we get here we're a nonvirtual play channel, and either
757				 * 1) not busy
758				 * 2) busy with children, not directly open
759				 *
760				 * thus we can add children
761				 */
762				goto addok;
763addskip:
764			}
765			pcm_unlock(d);
766			return EBUSY;
767addok:
768			c->flags |= CHN_F_BUSY;
769			while (err == 0 && newcnt > cnt) {
770				err = vchan_create(c);
771				if (err == 0)
772					cnt++;
773			}
774			if (SLIST_EMPTY(&c->children))
775				c->flags &= ~CHN_F_BUSY;
776		} else if (newcnt < cnt) {
777			while (err == 0 && newcnt < cnt) {
778				SLIST_FOREACH(sce, &d->channels, link) {
779					c = sce->channel;
780					if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL)
781						goto remok;
782				}
783				pcm_unlock(d);
784				return EINVAL;
785remok:
786				err = vchan_destroy(c);
787				if (err == 0)
788					cnt--;
789			}
790		}
791	}
792
793	pcm_unlock(d);
794	return err;
795}
796#endif
797
798/************************************************************************/
799
800static moduledata_t sndpcm_mod = {
801	"snd_pcm",
802	NULL,
803	NULL
804};
805DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
806MODULE_VERSION(snd_pcm, PCM_MODVER);
807