sndstat.c revision 150827
1/*-
2 * Copyright (c) 2001 Cameron Grant <cg@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <dev/sound/pcm/sound.h>
28#include <dev/sound/pcm/vchan.h>
29#ifdef	USING_MUTEX
30#include <sys/sx.h>
31#endif
32
33SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/sndstat.c 150827 2005-10-02 15:43:57Z netchild $");
34
35#define	SS_TYPE_MODULE		0
36#define	SS_TYPE_FIRST		1
37#define	SS_TYPE_PCM		1
38#define	SS_TYPE_MIDI		2
39#define	SS_TYPE_SEQUENCER	3
40#define	SS_TYPE_LAST		3
41
42static d_open_t sndstat_open;
43static d_close_t sndstat_close;
44static d_read_t sndstat_read;
45
46static struct cdevsw sndstat_cdevsw = {
47	.d_version =	D_VERSION,
48	.d_flags =	D_NEEDGIANT,
49	.d_open =	sndstat_open,
50	.d_close =	sndstat_close,
51	.d_read =	sndstat_read,
52	.d_name =	"sndstat",
53};
54
55struct sndstat_entry {
56	SLIST_ENTRY(sndstat_entry) link;
57	device_t dev;
58	char *str;
59	sndstat_handler handler;
60	int type, unit;
61};
62
63#ifdef	USING_MUTEX
64static struct sx sndstat_lock;
65#endif
66static struct sbuf sndstat_sbuf;
67static struct cdev *sndstat_dev = 0;
68static int sndstat_isopen = 0;
69static int sndstat_bufptr;
70static int sndstat_maxunit = -1;
71static int sndstat_files = 0;
72
73static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none);
74
75static int sndstat_verbose = 1;
76#ifdef	USING_MUTEX
77TUNABLE_INT("hw.snd.verbose", &sndstat_verbose);
78#else
79TUNABLE_INT_DECL("hw.snd.verbose", 1, sndstat_verbose);
80#endif
81
82static int sndstat_prepare(struct sbuf *s);
83
84static int
85sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
86{
87	int error, verbose;
88
89	verbose = sndstat_verbose;
90	error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req);
91	if (error == 0 && req->newptr != NULL) {
92		sx_xlock(&sndstat_lock);
93		if (verbose < 0 || verbose > 3)
94			error = EINVAL;
95		else
96			sndstat_verbose = verbose;
97		sx_xunlock(&sndstat_lock);
98	}
99	return error;
100}
101SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW,
102            0, sizeof(int), sysctl_hw_sndverbose, "I", "");
103
104static int
105sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
106{
107	int error;
108
109	sx_xlock(&sndstat_lock);
110	if (sndstat_isopen) {
111		sx_xunlock(&sndstat_lock);
112		return EBUSY;
113	}
114	sndstat_isopen = 1;
115	sx_xunlock(&sndstat_lock);
116	if (sbuf_new(&sndstat_sbuf, NULL, 4096, 0) == NULL) {
117		error = ENXIO;
118		goto out;
119	}
120	sndstat_bufptr = 0;
121	error = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM;
122out:
123	if (error) {
124		sx_xlock(&sndstat_lock);
125		sndstat_isopen = 0;
126		sx_xunlock(&sndstat_lock);
127	}
128	return (error);
129}
130
131static int
132sndstat_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
133{
134	sx_xlock(&sndstat_lock);
135	if (!sndstat_isopen) {
136		sx_xunlock(&sndstat_lock);
137		return EBADF;
138	}
139	sbuf_delete(&sndstat_sbuf);
140	sndstat_isopen = 0;
141
142	sx_xunlock(&sndstat_lock);
143	return 0;
144}
145
146static int
147sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
148{
149	int l, err;
150
151	sx_xlock(&sndstat_lock);
152	if (!sndstat_isopen) {
153		sx_xunlock(&sndstat_lock);
154		return EBADF;
155	}
156    	l = min(buf->uio_resid, sbuf_len(&sndstat_sbuf) - sndstat_bufptr);
157	err = (l > 0)? uiomove(sbuf_data(&sndstat_sbuf) + sndstat_bufptr, l, buf) : 0;
158	sndstat_bufptr += l;
159
160	sx_xunlock(&sndstat_lock);
161	return err;
162}
163
164/************************************************************************/
165
166static struct sndstat_entry *
167sndstat_find(int type, int unit)
168{
169	struct sndstat_entry *ent;
170
171	SLIST_FOREACH(ent, &sndstat_devlist, link) {
172		if (ent->type == type && ent->unit == unit)
173			return ent;
174	}
175
176	return NULL;
177}
178
179int
180sndstat_acquire(void)
181{
182	sx_xlock(&sndstat_lock);
183	if (sndstat_isopen) {
184		sx_xunlock(&sndstat_lock);
185		return EBUSY;
186	}
187	sndstat_isopen = 1;
188	sx_xunlock(&sndstat_lock);
189	return 0;
190}
191
192int
193sndstat_release(void)
194{
195	sx_xlock(&sndstat_lock);
196	if (!sndstat_isopen) {
197		sx_xunlock(&sndstat_lock);
198		return EBADF;
199	}
200	sndstat_isopen = 0;
201	sx_xunlock(&sndstat_lock);
202	return 0;
203}
204
205int
206sndstat_register(device_t dev, char *str, sndstat_handler handler)
207{
208	struct sndstat_entry *ent;
209	const char *devtype;
210	int type, unit;
211
212	if (dev) {
213		unit = device_get_unit(dev);
214		devtype = device_get_name(dev);
215		if (!strcmp(devtype, "pcm"))
216			type = SS_TYPE_PCM;
217		else if (!strcmp(devtype, "midi"))
218			type = SS_TYPE_MIDI;
219		else if (!strcmp(devtype, "sequencer"))
220			type = SS_TYPE_SEQUENCER;
221		else
222			return EINVAL;
223	} else {
224		type = SS_TYPE_MODULE;
225		unit = -1;
226	}
227
228	ent = malloc(sizeof *ent, M_DEVBUF, M_ZERO | M_WAITOK);
229	if (!ent)
230		return ENOSPC;
231
232	ent->dev = dev;
233	ent->str = str;
234	ent->type = type;
235	ent->unit = unit;
236	ent->handler = handler;
237
238	sx_xlock(&sndstat_lock);
239	SLIST_INSERT_HEAD(&sndstat_devlist, ent, link);
240	if (type == SS_TYPE_MODULE)
241		sndstat_files++;
242	sndstat_maxunit = (unit > sndstat_maxunit)? unit : sndstat_maxunit;
243	sx_xunlock(&sndstat_lock);
244
245	return 0;
246}
247
248int
249sndstat_registerfile(char *str)
250{
251	return sndstat_register(NULL, str, NULL);
252}
253
254int
255sndstat_unregister(device_t dev)
256{
257	struct sndstat_entry *ent;
258
259	sx_xlock(&sndstat_lock);
260	SLIST_FOREACH(ent, &sndstat_devlist, link) {
261		if (ent->dev == dev) {
262			SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link);
263			sx_xunlock(&sndstat_lock);
264			free(ent, M_DEVBUF);
265
266			return 0;
267		}
268	}
269	sx_xunlock(&sndstat_lock);
270
271	return ENXIO;
272}
273
274int
275sndstat_unregisterfile(char *str)
276{
277	struct sndstat_entry *ent;
278
279	sx_xlock(&sndstat_lock);
280	SLIST_FOREACH(ent, &sndstat_devlist, link) {
281		if (ent->dev == NULL && ent->str == str) {
282			SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link);
283			sndstat_files--;
284			sx_xunlock(&sndstat_lock);
285			free(ent, M_DEVBUF);
286
287			return 0;
288		}
289	}
290	sx_xunlock(&sndstat_lock);
291
292	return ENXIO;
293}
294
295/************************************************************************/
296
297static int
298sndstat_prepare(struct sbuf *s)
299{
300	struct sndstat_entry *ent;
301    	int i, j;
302
303	sbuf_printf(s, "FreeBSD Audio Driver (newpcm)\n");
304	if (SLIST_EMPTY(&sndstat_devlist)) {
305		sbuf_printf(s, "No devices installed.\n");
306		sbuf_finish(s);
307    		return sbuf_len(s);
308	}
309
310	sbuf_printf(s, "Installed devices:\n");
311
312    	for (i = 0; i <= sndstat_maxunit; i++) {
313		for (j = SS_TYPE_FIRST; j <= SS_TYPE_LAST; j++) {
314			ent = sndstat_find(j, i);
315			if (!ent)
316				continue;
317			sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
318			sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
319			sbuf_printf(s, " %s", ent->str);
320			if (ent->handler)
321				ent->handler(s, ent->dev, sndstat_verbose);
322			else
323				sbuf_printf(s, " [no handler]");
324			sbuf_printf(s, "\n");
325		}
326    	}
327
328	if (sndstat_verbose >= 3 && sndstat_files > 0) {
329		sbuf_printf(s, "\nFile Versions:\n");
330
331		SLIST_FOREACH(ent, &sndstat_devlist, link) {
332			if (ent->dev == NULL && ent->str != NULL)
333				sbuf_printf(s, "%s\n", ent->str);
334		}
335	}
336
337	sbuf_finish(s);
338    	return sbuf_len(s);
339}
340
341static int
342sndstat_init(void)
343{
344	sx_init(&sndstat_lock, "sndstat");
345	sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, UID_ROOT, GID_WHEEL, 0444, "sndstat");
346
347	return (sndstat_dev != 0)? 0 : ENXIO;
348}
349
350static int
351sndstat_uninit(void)
352{
353	sx_xlock(&sndstat_lock);
354	if (sndstat_isopen) {
355		sx_xunlock(&sndstat_lock);
356		return EBUSY;
357	}
358
359	if (sndstat_dev)
360		destroy_dev(sndstat_dev);
361	sndstat_dev = 0;
362
363	sx_xunlock(&sndstat_lock);
364	sx_destroy(&sndstat_lock);
365	return 0;
366}
367
368static void
369sndstat_sysinit(void *p)
370{
371	sndstat_init();
372}
373
374static void
375sndstat_sysuninit(void *p)
376{
377	int error;
378
379	error = sndstat_uninit();
380	KASSERT(error == 0, ("%s: error = %d", __func__, error));
381}
382
383SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
384SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
385
386
387