xref: /freebsd/sys/dev/sound/pcm/sndstat.c (revision 595e514d0df2bac5b813d35f83e32875dbf16a83)
1 /*-
2  * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
3  * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
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 
28 #ifdef HAVE_KERNEL_OPTION_HEADERS
29 #include "opt_snd.h"
30 #endif
31 
32 #include <dev/sound/pcm/sound.h>
33 #include <dev/sound/pcm/pcm.h>
34 #include <dev/sound/version.h>
35 #include <sys/sx.h>
36 
37 SND_DECLARE_FILE("$FreeBSD$");
38 
39 #define	SS_TYPE_MODULE		0
40 #define	SS_TYPE_FIRST		1
41 #define	SS_TYPE_PCM		1
42 #define	SS_TYPE_MIDI		2
43 #define	SS_TYPE_SEQUENCER	3
44 #define	SS_TYPE_LAST		3
45 
46 static d_open_t sndstat_open;
47 static d_close_t sndstat_close;
48 static d_read_t sndstat_read;
49 
50 static struct cdevsw sndstat_cdevsw = {
51 	.d_version =	D_VERSION,
52 	.d_open =	sndstat_open,
53 	.d_close =	sndstat_close,
54 	.d_read =	sndstat_read,
55 	.d_name =	"sndstat",
56 	.d_flags =	D_TRACKCLOSE,
57 };
58 
59 struct sndstat_entry {
60 	SLIST_ENTRY(sndstat_entry) link;
61 	device_t dev;
62 	char *str;
63 	sndstat_handler handler;
64 	int type, unit;
65 };
66 
67 static struct sx sndstat_lock;
68 static struct sbuf sndstat_sbuf;
69 static struct cdev *sndstat_dev = NULL;
70 static int sndstat_bufptr = -1;
71 static int sndstat_maxunit = -1;
72 static int sndstat_files = 0;
73 
74 #define SNDSTAT_PID(x)		((pid_t)((intptr_t)((x)->si_drv1)))
75 #define SNDSTAT_PID_SET(x, y)	(x)->si_drv1 = (void *)((intptr_t)(y))
76 #define SNDSTAT_FLUSH()		do {					\
77 	if (sndstat_bufptr != -1) {					\
78 		sbuf_delete(&sndstat_sbuf);				\
79 		sndstat_bufptr = -1;					\
80 	}								\
81 } while (0)
82 
83 static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(sndstat_devlist);
84 
85 int snd_verbose = 0;
86 TUNABLE_INT("hw.snd.verbose", &snd_verbose);
87 
88 #ifdef SND_DEBUG
89 static int
90 sysctl_hw_snd_sndstat_pid(SYSCTL_HANDLER_ARGS)
91 {
92 	int err, val;
93 
94 	if (sndstat_dev == NULL)
95 		return (EINVAL);
96 
97 	sx_xlock(&sndstat_lock);
98 	val = (int)SNDSTAT_PID(sndstat_dev);
99 	err = sysctl_handle_int(oidp, &val, 0, req);
100 	if (err == 0 && req->newptr != NULL && val == 0) {
101 		SNDSTAT_FLUSH();
102 		SNDSTAT_PID_SET(sndstat_dev, 0);
103 	}
104 	sx_unlock(&sndstat_lock);
105 	return (err);
106 }
107 SYSCTL_PROC(_hw_snd, OID_AUTO, sndstat_pid, CTLTYPE_INT | CTLFLAG_RW,
108     0, sizeof(int), sysctl_hw_snd_sndstat_pid, "I", "sndstat busy pid");
109 #endif
110 
111 static int sndstat_prepare(struct sbuf *s);
112 
113 static int
114 sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
115 {
116 	int error, verbose;
117 
118 	verbose = snd_verbose;
119 	error = sysctl_handle_int(oidp, &verbose, 0, req);
120 	if (error == 0 && req->newptr != NULL) {
121 		if (verbose < 0 || verbose > 4)
122 			error = EINVAL;
123 		else
124 			snd_verbose = verbose;
125 	}
126 	return error;
127 }
128 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW,
129             0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level");
130 
131 static int
132 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
133 {
134 	if (sndstat_dev == NULL || i_dev != sndstat_dev)
135 		return EBADF;
136 
137 	sx_xlock(&sndstat_lock);
138 	if (SNDSTAT_PID(i_dev) != 0) {
139 		sx_unlock(&sndstat_lock);
140 		return EBUSY;
141 	}
142 	SNDSTAT_PID_SET(i_dev, td->td_proc->p_pid);
143 	if (sbuf_new(&sndstat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
144 		SNDSTAT_PID_SET(i_dev, 0);
145 		sx_unlock(&sndstat_lock);
146 		return ENXIO;
147 	}
148 	sndstat_bufptr = 0;
149 	sx_unlock(&sndstat_lock);
150 	return 0;
151 }
152 
153 static int
154 sndstat_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
155 {
156 	if (sndstat_dev == NULL || i_dev != sndstat_dev)
157 		return EBADF;
158 
159 	sx_xlock(&sndstat_lock);
160 	if (SNDSTAT_PID(i_dev) == 0) {
161 		sx_unlock(&sndstat_lock);
162 		return EBADF;
163 	}
164 
165 	SNDSTAT_FLUSH();
166 	SNDSTAT_PID_SET(i_dev, 0);
167 
168 	sx_unlock(&sndstat_lock);
169 
170 	return 0;
171 }
172 
173 static int
174 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
175 {
176 	int l, err;
177 
178 	if (sndstat_dev == NULL || i_dev != sndstat_dev)
179 		return EBADF;
180 
181 	sx_xlock(&sndstat_lock);
182 	if (SNDSTAT_PID(i_dev) != buf->uio_td->td_proc->p_pid ||
183 	    sndstat_bufptr == -1) {
184 		sx_unlock(&sndstat_lock);
185 		return EBADF;
186 	}
187 
188 	if (sndstat_bufptr == 0) {
189 		err = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM;
190 		if (err) {
191 			SNDSTAT_FLUSH();
192 			sx_unlock(&sndstat_lock);
193 			return err;
194 		}
195 	}
196 
197     	l = min(buf->uio_resid, sbuf_len(&sndstat_sbuf) - sndstat_bufptr);
198 	err = (l > 0)? uiomove(sbuf_data(&sndstat_sbuf) + sndstat_bufptr, l, buf) : 0;
199 	sndstat_bufptr += l;
200 	sx_unlock(&sndstat_lock);
201 
202 	return err;
203 }
204 
205 /************************************************************************/
206 
207 static struct sndstat_entry *
208 sndstat_find(int type, int unit)
209 {
210 	struct sndstat_entry *ent;
211 
212 	SLIST_FOREACH(ent, &sndstat_devlist, link) {
213 		if (ent->type == type && ent->unit == unit)
214 			return ent;
215 	}
216 
217 	return NULL;
218 }
219 
220 int
221 sndstat_acquire(struct thread *td)
222 {
223 	if (sndstat_dev == NULL)
224 		return EBADF;
225 
226 	sx_xlock(&sndstat_lock);
227 	if (SNDSTAT_PID(sndstat_dev) != 0) {
228 		sx_unlock(&sndstat_lock);
229 		return EBUSY;
230 	}
231 	SNDSTAT_PID_SET(sndstat_dev, td->td_proc->p_pid);
232 	sx_unlock(&sndstat_lock);
233 	return 0;
234 }
235 
236 int
237 sndstat_release(struct thread *td)
238 {
239 	if (sndstat_dev == NULL)
240 		return EBADF;
241 
242 	sx_xlock(&sndstat_lock);
243 	if (SNDSTAT_PID(sndstat_dev) != td->td_proc->p_pid) {
244 		sx_unlock(&sndstat_lock);
245 		return EBADF;
246 	}
247 	SNDSTAT_PID_SET(sndstat_dev, 0);
248 	sx_unlock(&sndstat_lock);
249 	return 0;
250 }
251 
252 int
253 sndstat_register(device_t dev, char *str, sndstat_handler handler)
254 {
255 	struct sndstat_entry *ent;
256 	const char *devtype;
257 	int type, unit;
258 
259 	if (dev) {
260 		unit = device_get_unit(dev);
261 		devtype = device_get_name(dev);
262 		if (!strcmp(devtype, "pcm"))
263 			type = SS_TYPE_PCM;
264 		else if (!strcmp(devtype, "midi"))
265 			type = SS_TYPE_MIDI;
266 		else if (!strcmp(devtype, "sequencer"))
267 			type = SS_TYPE_SEQUENCER;
268 		else
269 			return EINVAL;
270 	} else {
271 		type = SS_TYPE_MODULE;
272 		unit = -1;
273 	}
274 
275 	ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
276 	ent->dev = dev;
277 	ent->str = str;
278 	ent->type = type;
279 	ent->unit = unit;
280 	ent->handler = handler;
281 
282 	sx_xlock(&sndstat_lock);
283 	SLIST_INSERT_HEAD(&sndstat_devlist, ent, link);
284 	if (type == SS_TYPE_MODULE)
285 		sndstat_files++;
286 	sndstat_maxunit = (unit > sndstat_maxunit)? unit : sndstat_maxunit;
287 	sx_unlock(&sndstat_lock);
288 
289 	return 0;
290 }
291 
292 int
293 sndstat_registerfile(char *str)
294 {
295 	return sndstat_register(NULL, str, NULL);
296 }
297 
298 int
299 sndstat_unregister(device_t dev)
300 {
301 	struct sndstat_entry *ent;
302 
303 	sx_xlock(&sndstat_lock);
304 	SLIST_FOREACH(ent, &sndstat_devlist, link) {
305 		if (ent->dev == dev) {
306 			SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link);
307 			sx_unlock(&sndstat_lock);
308 			free(ent, M_DEVBUF);
309 
310 			return 0;
311 		}
312 	}
313 	sx_unlock(&sndstat_lock);
314 
315 	return ENXIO;
316 }
317 
318 int
319 sndstat_unregisterfile(char *str)
320 {
321 	struct sndstat_entry *ent;
322 
323 	sx_xlock(&sndstat_lock);
324 	SLIST_FOREACH(ent, &sndstat_devlist, link) {
325 		if (ent->dev == NULL && ent->str == str) {
326 			SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link);
327 			sndstat_files--;
328 			sx_unlock(&sndstat_lock);
329 			free(ent, M_DEVBUF);
330 
331 			return 0;
332 		}
333 	}
334 	sx_unlock(&sndstat_lock);
335 
336 	return ENXIO;
337 }
338 
339 /************************************************************************/
340 
341 static int
342 sndstat_prepare(struct sbuf *s)
343 {
344 	struct sndstat_entry *ent;
345 	struct snddev_info *d;
346     	int i, j;
347 
348 	if (snd_verbose > 0) {
349 		sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
350 		    (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
351 		    MACHINE_ARCH);
352 	}
353 
354 	if (SLIST_EMPTY(&sndstat_devlist)) {
355 		sbuf_printf(s, "No devices installed.\n");
356 		sbuf_finish(s);
357     		return sbuf_len(s);
358 	}
359 
360 	sbuf_printf(s, "Installed devices:\n");
361 
362     	for (i = 0; i <= sndstat_maxunit; i++) {
363 		for (j = SS_TYPE_FIRST; j <= SS_TYPE_LAST; j++) {
364 			ent = sndstat_find(j, i);
365 			if (!ent)
366 				continue;
367 			d = device_get_softc(ent->dev);
368 			if (!PCM_REGISTERED(d))
369 				continue;
370 			/* XXX Need Giant magic entry ??? */
371 			PCM_ACQUIRE_QUICK(d);
372 			sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
373 			sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
374 			if (snd_verbose > 0)
375 				sbuf_printf(s, " %s", ent->str);
376 			if (ent->handler)
377 				ent->handler(s, ent->dev, snd_verbose);
378 			sbuf_printf(s, "\n");
379 			PCM_RELEASE_QUICK(d);
380 		}
381     	}
382 
383 	if (snd_verbose >= 3 && sndstat_files > 0) {
384 		sbuf_printf(s, "\nFile Versions:\n");
385 
386 		SLIST_FOREACH(ent, &sndstat_devlist, link) {
387 			if (ent->dev == NULL && ent->str != NULL)
388 				sbuf_printf(s, "%s\n", ent->str);
389 		}
390 	}
391 
392 	sbuf_finish(s);
393     	return sbuf_len(s);
394 }
395 
396 static int
397 sndstat_init(void)
398 {
399 	if (sndstat_dev != NULL)
400 		return EINVAL;
401 	sx_init(&sndstat_lock, "sndstat lock");
402 	sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
403 	    UID_ROOT, GID_WHEEL, 0444, "sndstat");
404 	return 0;
405 }
406 
407 static int
408 sndstat_uninit(void)
409 {
410 	if (sndstat_dev == NULL)
411 		return EINVAL;
412 
413 	sx_xlock(&sndstat_lock);
414 	if (SNDSTAT_PID(sndstat_dev) != curthread->td_proc->p_pid) {
415 		sx_unlock(&sndstat_lock);
416 		return EBUSY;
417 	}
418 
419 	/* XXXPHO: use destroy_dev_sched() */
420 	destroy_dev(sndstat_dev);
421 	sndstat_dev = NULL;
422 
423 	SNDSTAT_FLUSH();
424 
425 	sx_unlock(&sndstat_lock);
426 	sx_destroy(&sndstat_lock);
427 	return 0;
428 }
429 
430 static void
431 sndstat_sysinit(void *p)
432 {
433 	sndstat_init();
434 }
435 
436 static void
437 sndstat_sysuninit(void *p)
438 {
439 	int error;
440 
441 	error = sndstat_uninit();
442 	KASSERT(error == 0, ("%s: error = %d", __func__, error));
443 }
444 
445 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
446 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
447