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