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