xref: /freebsd/sys/dev/sound/pcm/sound.c (revision 1b6c76a2fe091c74f08427e6c870851025a9cf67)
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$
28  */
29 
30 #include <dev/sound/pcm/sound.h>
31 #include <dev/sound/pcm/vchan.h>
32 #include <sys/sysctl.h>
33 
34 devclass_t pcm_devclass;
35 
36 #ifdef USING_DEVFS
37 int snd_unit = 0;
38 TUNABLE_INT("hw.snd.unit", &snd_unit);
39 #endif
40 int snd_autovchans = 0;
41 TUNABLE_INT("hw.snd.autovchans", &snd_autovchans);
42 int snd_maxvchans = 0;
43 TUNABLE_INT("hw.snd.maxvchans", &snd_maxvchans);
44 
45 SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver");
46 
47 void *
48 snd_mtxcreate(const char *desc)
49 {
50 #ifdef USING_MUTEX
51 	struct mtx *m;
52 
53 	m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO);
54 	if (m == NULL)
55 		return NULL;
56 	mtx_init(m, desc, MTX_RECURSE);
57 	return m;
58 #else
59 	return (void *)0xcafebabe;
60 #endif
61 }
62 
63 void
64 snd_mtxfree(void *m)
65 {
66 #ifdef USING_MUTEX
67 	struct mtx *mtx = m;
68 
69 	mtx_assert(mtx, MA_OWNED);
70 	mtx_destroy(mtx);
71 	free(mtx, M_DEVBUF);
72 #endif
73 }
74 
75 void
76 snd_mtxassert(void *m)
77 {
78 #ifdef USING_MUTEX
79 #ifdef INVARIANTS
80 	struct mtx *mtx = m;
81 
82 	mtx_assert(mtx, MA_OWNED);
83 #endif
84 #endif
85 }
86 
87 void
88 snd_mtxlock(void *m)
89 {
90 #ifdef USING_MUTEX
91 	struct mtx *mtx = m;
92 
93 	mtx_lock(mtx);
94 #endif
95 }
96 
97 void
98 snd_mtxunlock(void *m)
99 {
100 #ifdef USING_MUTEX
101 	struct mtx *mtx = m;
102 
103 	mtx_unlock(mtx);
104 #endif
105 }
106 
107 int
108 snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep)
109 {
110 #ifdef USING_MUTEX
111 	flags &= INTR_MPSAFE;
112 	flags |= INTR_TYPE_AV;
113 #else
114 	flags = INTR_TYPE_AV;
115 #endif
116 	return bus_setup_intr(dev, res, flags, hand, param, cookiep);
117 }
118 
119 /* return a locked channel */
120 struct pcm_channel *
121 pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid)
122 {
123 	struct pcm_channel *c;
124     	struct snddev_channel *sce;
125 	int err;
126 
127 	snd_mtxassert(d->lock);
128 
129 	/* scan for a free channel */
130 	SLIST_FOREACH(sce, &d->channels, link) {
131 		c = sce->channel;
132 		CHN_LOCK(c);
133 		if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) {
134 			c->flags |= CHN_F_BUSY;
135 			c->pid = pid;
136 			return c;
137 		}
138 		CHN_UNLOCK(c);
139 	}
140 
141 	/* no channel available */
142 	if (direction == PCMDIR_PLAY) {
143 		if ((d->vchancount > 0) && (d->vchancount < snd_maxvchans)) {
144 			/* try to create a vchan */
145 			SLIST_FOREACH(sce, &d->channels, link) {
146 				c = sce->channel;
147 				if (!SLIST_EMPTY(&c->children)) {
148 					err = vchan_create(c);
149 					if (!err)
150 						return pcm_chnalloc(d, direction, pid);
151 					else
152 						device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err);
153 				}
154 			}
155 		}
156 	}
157 
158 	return NULL;
159 }
160 
161 /* release a locked channel and unlock it */
162 int
163 pcm_chnrelease(struct pcm_channel *c)
164 {
165 	CHN_LOCKASSERT(c);
166 	c->flags &= ~CHN_F_BUSY;
167 	c->pid = -1;
168 	CHN_UNLOCK(c);
169 	return 0;
170 }
171 
172 int
173 pcm_chnref(struct pcm_channel *c, int ref)
174 {
175 	int r;
176 
177 	CHN_LOCKASSERT(c);
178 	c->refcount += ref;
179 	r = c->refcount;
180 	return r;
181 }
182 
183 #ifdef USING_DEVFS
184 static int
185 sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS)
186 {
187 	struct snddev_info *d;
188 	int error, unit;
189 
190 	unit = snd_unit;
191 	error = sysctl_handle_int(oidp, &unit, sizeof(unit), req);
192 	if (error == 0 && req->newptr != NULL) {
193 		if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass))
194 			return EINVAL;
195 		d = devclass_get_softc(pcm_devclass, unit);
196 		if (d == NULL || SLIST_EMPTY(&d->channels))
197 			return EINVAL;
198 		snd_unit = unit;
199 	}
200 	return (error);
201 }
202 SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW,
203             0, sizeof(int), sysctl_hw_snd_unit, "I", "");
204 #endif
205 
206 static int
207 sysctl_hw_snd_autovchans(SYSCTL_HANDLER_ARGS)
208 {
209 	int v, error;
210 
211 	v = snd_autovchans;
212 	error = sysctl_handle_int(oidp, &v, sizeof(v), req);
213 	if (error == 0 && req->newptr != NULL) {
214 		if (v < 0 || v >= SND_MAXVCHANS)
215 			return EINVAL;
216 		snd_autovchans = v;
217 	}
218 	return (error);
219 }
220 SYSCTL_PROC(_hw_snd, OID_AUTO, autovchans, CTLTYPE_INT | CTLFLAG_RW,
221             0, sizeof(int), sysctl_hw_snd_autovchans, "I", "");
222 
223 static int
224 sysctl_hw_snd_maxvchans(SYSCTL_HANDLER_ARGS)
225 {
226 	int v, error;
227 
228 	v = snd_maxvchans;
229 	error = sysctl_handle_int(oidp, &v, sizeof(v), req);
230 	if (error == 0 && req->newptr != NULL) {
231 		if (v < 0 || v >= SND_MAXVCHANS)
232 			return EINVAL;
233 		snd_maxvchans = v;
234 	}
235 	return (error);
236 }
237 SYSCTL_PROC(_hw_snd, OID_AUTO, maxvchans, CTLTYPE_INT | CTLFLAG_RW,
238             0, sizeof(int), sysctl_hw_snd_maxvchans, "I", "");
239 
240 struct pcm_channel *
241 pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo)
242 {
243 	struct pcm_channel *ch;
244 	char *dirs;
245     	int err;
246 
247 	switch(dir) {
248 	case PCMDIR_PLAY:
249 		dirs = "play";
250 		break;
251 	case PCMDIR_REC:
252 		dirs = "record";
253 		break;
254 	case PCMDIR_VIRTUAL:
255 		dirs = "virtual";
256 		dir = PCMDIR_PLAY;
257 		break;
258 	default:
259 		return NULL;
260 	}
261 
262 	ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
263 	if (!ch)
264 		return NULL;
265 
266 	ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK);
267 	if (!ch->methods) {
268 		free(ch, M_DEVBUF);
269 		return NULL;
270 	}
271 
272 	ch->pid = -1;
273 	ch->parentsnddev = d;
274 	ch->parentchannel = parent;
275 	snprintf(ch->name, 32, "%s:%d:%s", device_get_nameunit(d->dev), d->chancount, dirs);
276 
277 	err = chn_init(ch, devinfo, dir);
278 	if (err) {
279 		device_printf(d->dev, "chn_init() for channel %d (%s) failed: err = %d\n", d->chancount, dirs, err);
280 		kobj_delete(ch->methods, M_DEVBUF);
281 		free(ch, M_DEVBUF);
282 		return NULL;
283 	}
284 
285 	return ch;
286 }
287 
288 int
289 pcm_chn_destroy(struct pcm_channel *ch)
290 {
291 	int err;
292 
293 	err = chn_kill(ch);
294 	if (err) {
295 		device_printf(ch->parentsnddev->dev, "chn_kill() for %s failed, err = %d\n", ch->name, err);
296 		return err;
297 	}
298 
299 	kobj_delete(ch->methods, M_DEVBUF);
300 	free(ch, M_DEVBUF);
301 
302 	return 0;
303 }
304 
305 int
306 pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch, int mkdev)
307 {
308     	struct snddev_channel *sce;
309     	int unit = device_get_unit(d->dev);
310 
311 	snd_mtxlock(d->lock);
312 
313 	sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO);
314 	if (!sce) {
315 		snd_mtxunlock(d->lock);
316 		return ENOMEM;
317 	}
318 
319 	sce->channel = ch;
320 	SLIST_INSERT_HEAD(&d->channels, sce, link);
321 
322 	if (mkdev)
323 		dsp_register(unit, d->devcount++);
324     	d->chancount++;
325 	if (ch->flags & CHN_F_VIRTUAL)
326 		d->vchancount++;
327 
328 	snd_mtxunlock(d->lock);
329 
330 	return 0;
331 }
332 
333 int
334 pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch, int rmdev)
335 {
336     	struct snddev_channel *sce;
337     	int unit = device_get_unit(d->dev);
338 
339 	snd_mtxlock(d->lock);
340 	SLIST_FOREACH(sce, &d->channels, link) {
341 		if (sce->channel == ch)
342 			goto gotit;
343 	}
344 	snd_mtxunlock(d->lock);
345 	return EINVAL;
346 gotit:
347 	if (ch->flags & CHN_F_VIRTUAL)
348 		d->vchancount--;
349 	d->chancount--;
350 	SLIST_REMOVE(&d->channels, sce, snddev_channel, link);
351 	free(sce, M_DEVBUF);
352 
353 	if (rmdev)
354 		dsp_unregister(unit, --d->devcount);
355 	snd_mtxunlock(d->lock);
356 
357 	return 0;
358 }
359 
360 int
361 pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo)
362 {
363     	struct snddev_info *d = device_get_softc(dev);
364 	struct pcm_channel *ch, *child;
365 	struct pcmchan_children *pce;
366     	int i, err;
367 
368 	ch = pcm_chn_create(d, NULL, cls, dir, devinfo);
369 	if (!ch) {
370 		device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo);
371 		return ENODEV;
372 	}
373 
374 	err = pcm_chn_add(d, ch, 1);
375 	if (err) {
376 		device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err);
377 		pcm_chn_destroy(ch);
378 		return err;
379 	}
380 
381 	if ((dir == PCMDIR_PLAY) && (d->flags & SD_F_AUTOVCHAN) && (snd_autovchans > 0)) {
382 		ch->flags |= CHN_F_BUSY;
383 		for (i = 0; err == 0 && i < snd_autovchans; i++)
384 			err = vchan_create(ch);
385 		if (err) {
386 			device_printf(d->dev, "vchan_create(%d) failed, err=%d\n", i - 1, err);
387 			SLIST_FOREACH(pce, &ch->children, link) {
388 				child = pce->channel;
389 				vchan_destroy(child);
390 			}
391 			return err;
392 		}
393 	}
394 
395 	return err;
396 }
397 
398 static int
399 pcm_killchan(device_t dev)
400 {
401     	struct snddev_info *d = device_get_softc(dev);
402     	struct snddev_channel *sce;
403 
404 	snd_mtxlock(d->lock);
405 	sce = SLIST_FIRST(&d->channels);
406 	snd_mtxunlock(d->lock);
407 
408 	return pcm_chn_remove(d, sce->channel, 1);
409 }
410 
411 int
412 pcm_setstatus(device_t dev, char *str)
413 {
414     	struct snddev_info *d = device_get_softc(dev);
415 
416 	snd_mtxlock(d->lock);
417 	strncpy(d->status, str, SND_STATUSLEN);
418 	snd_mtxunlock(d->lock);
419 	return 0;
420 }
421 
422 u_int32_t
423 pcm_getflags(device_t dev)
424 {
425     	struct snddev_info *d = device_get_softc(dev);
426 
427 	return d->flags;
428 }
429 
430 void
431 pcm_setflags(device_t dev, u_int32_t val)
432 {
433     	struct snddev_info *d = device_get_softc(dev);
434 
435 	d->flags = val;
436 }
437 
438 void *
439 pcm_getdevinfo(device_t dev)
440 {
441     	struct snddev_info *d = device_get_softc(dev);
442 
443 	return d->devinfo;
444 }
445 
446 /* This is the generic init routine */
447 int
448 pcm_register(device_t dev, void *devinfo, int numplay, int numrec)
449 {
450     	struct snddev_info *d = device_get_softc(dev);
451 
452 	d->lock = snd_mtxcreate(device_get_nameunit(dev));
453 	snd_mtxlock(d->lock);
454 
455 	d->flags = 0;
456 	d->dev = dev;
457 	d->devinfo = devinfo;
458 	d->devcount = 0;
459 	d->chancount = 0;
460 	d->vchancount = 0;
461 	d->inprog = 0;
462 
463 	if (((numplay == 0) || (numrec == 0)) && (numplay != numrec))
464 		d->flags |= SD_F_SIMPLEX;
465 
466 	d->fakechan = fkchan_setup(dev);
467 	chn_init(d->fakechan, NULL, 0);
468 
469 #ifdef SND_DYNSYSCTL
470 	sysctl_ctx_init(&d->sysctl_tree);
471 	d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree,
472 				 SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO,
473 				 device_get_nameunit(dev), CTLFLAG_RD, 0, "");
474 	if (d->sysctl_tree_top == NULL) {
475 		sysctl_ctx_free(&d->sysctl_tree);
476 		goto no;
477 	}
478 #endif
479 	if (numplay > 0)
480 		vchan_initsys(d);
481 	if (numplay == 1)
482 		d->flags |= SD_F_AUTOVCHAN;
483 
484 	snd_mtxunlock(d->lock);
485     	return 0;
486 no:
487 	snd_mtxfree(d->lock);
488 	return ENXIO;
489 }
490 
491 int
492 pcm_unregister(device_t dev)
493 {
494     	struct snddev_info *d = device_get_softc(dev);
495     	struct snddev_channel *sce;
496 
497 	snd_mtxlock(d->lock);
498 	if (d->inprog) {
499 		device_printf(dev, "unregister: operation in progress");
500 		snd_mtxunlock(d->lock);
501 		return EBUSY;
502 	}
503 	SLIST_FOREACH(sce, &d->channels, link) {
504 		if (sce->channel->refcount > 0) {
505 			device_printf(dev, "unregister: channel busy");
506 			snd_mtxunlock(d->lock);
507 			return EBUSY;
508 		}
509 	}
510 	if (mixer_uninit(dev)) {
511 		device_printf(dev, "unregister: mixer busy");
512 		snd_mtxunlock(d->lock);
513 		return EBUSY;
514 	}
515 
516 #ifdef SND_DYNSYSCTL
517 	d->sysctl_tree_top = NULL;
518 	sysctl_ctx_free(&d->sysctl_tree);
519 #endif
520 	while (!SLIST_EMPTY(&d->channels))
521 		pcm_killchan(dev);
522 
523 	chn_kill(d->fakechan);
524 	fkchan_kill(d->fakechan);
525 
526 	snd_mtxfree(d->lock);
527 	return 0;
528 }
529 
530 static moduledata_t sndpcm_mod = {
531 	"snd_pcm",
532 	NULL,
533 	NULL
534 };
535 DECLARE_MODULE(snd_pcm, sndpcm_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
536 MODULE_VERSION(snd_pcm, PCM_MODVER);
537