sndstat.c revision 302408
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
37SND_DECLARE_FILE("$FreeBSD: stable/11/sys/dev/sound/pcm/sndstat.c 295440 2016-02-09 17:09:14Z hselasky $");
38
39#define	SS_TYPE_MODULE		0
40#define	SS_TYPE_PCM		1
41#define	SS_TYPE_MIDI		2
42#define	SS_TYPE_SEQUENCER	3
43
44static d_open_t sndstat_open;
45static void sndstat_close(void *);
46static d_read_t sndstat_read;
47static d_write_t sndstat_write;
48
49static struct cdevsw sndstat_cdevsw = {
50	.d_version =	D_VERSION,
51	.d_open =	sndstat_open,
52	.d_read =	sndstat_read,
53	.d_write =	sndstat_write,
54	.d_name =	"sndstat",
55	.d_flags =	D_TRACKCLOSE,
56};
57
58struct sndstat_entry {
59	TAILQ_ENTRY(sndstat_entry) link;
60	device_t dev;
61	char *str;
62	sndstat_handler handler;
63	int type, unit;
64};
65
66struct sndstat_file {
67	TAILQ_ENTRY(sndstat_file) entry;
68	struct sbuf sbuf;
69	int out_offset;
70  	int in_offset;
71};
72
73static struct sx sndstat_lock;
74static struct cdev *sndstat_dev;
75
76#define	SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
77#define	SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
78
79static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
80static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
81
82int snd_verbose = 0;
83
84static int sndstat_prepare(struct sndstat_file *);
85
86static int
87sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
88{
89	int error, verbose;
90
91	verbose = snd_verbose;
92	error = sysctl_handle_int(oidp, &verbose, 0, req);
93	if (error == 0 && req->newptr != NULL) {
94		if (verbose < 0 || verbose > 4)
95			error = EINVAL;
96		else
97			snd_verbose = verbose;
98	}
99	return (error);
100}
101SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RWTUN,
102            0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level");
103
104static int
105sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
106{
107	struct sndstat_file *pf;
108
109	pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
110
111	SNDSTAT_LOCK();
112	if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
113	  	SNDSTAT_UNLOCK();
114		free(pf, M_DEVBUF);
115		return (ENOMEM);
116	}
117	TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
118	SNDSTAT_UNLOCK();
119
120	devfs_set_cdevpriv(pf, &sndstat_close);
121
122	return (0);
123}
124
125static void
126sndstat_close(void *sndstat_file)
127{
128	struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
129
130	SNDSTAT_LOCK();
131	sbuf_delete(&pf->sbuf);
132	TAILQ_REMOVE(&sndstat_filelist, pf, entry);
133	SNDSTAT_UNLOCK();
134
135	free(pf, M_DEVBUF);
136}
137
138static int
139sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
140{
141	struct sndstat_file *pf;
142	int err;
143	int len;
144
145	err = devfs_get_cdevpriv((void **)&pf);
146	if (err != 0)
147		return (err);
148
149	/* skip zero-length reads */
150	if (buf->uio_resid == 0)
151		return (0);
152
153	SNDSTAT_LOCK();
154	if (pf->out_offset != 0) {
155		/* don't allow both reading and writing */
156		err = EINVAL;
157		goto done;
158	} else if (pf->in_offset == 0) {
159		err = sndstat_prepare(pf);
160		if (err <= 0) {
161			err = ENOMEM;
162			goto done;
163		}
164	}
165	len = sbuf_len(&pf->sbuf) - pf->in_offset;
166	if (len > buf->uio_resid)
167		len = buf->uio_resid;
168	if (len > 0)
169		err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
170	pf->in_offset += len;
171done:
172	SNDSTAT_UNLOCK();
173	return (err);
174}
175
176static int
177sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
178{
179	struct sndstat_file *pf;
180	uint8_t temp[64];
181	int err;
182	int len;
183
184	err = devfs_get_cdevpriv((void **)&pf);
185	if (err != 0)
186		return (err);
187
188	/* skip zero-length writes */
189	if (buf->uio_resid == 0)
190		return (0);
191
192	/* don't allow writing more than 64Kbytes */
193	if (buf->uio_resid > 65536)
194		return (ENOMEM);
195
196	SNDSTAT_LOCK();
197	if (pf->in_offset != 0) {
198		/* don't allow both reading and writing */
199		err = EINVAL;
200	} else {
201		/* only remember the last write - allows for updates */
202		sbuf_clear(&pf->sbuf);
203		while (1) {
204			len = sizeof(temp);
205			if (len > buf->uio_resid)
206				len = buf->uio_resid;
207			if (len > 0) {
208				err = uiomove(temp, len, buf);
209				if (err)
210					break;
211			} else {
212				break;
213			}
214			if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
215				err = ENOMEM;
216				break;
217			}
218		}
219		sbuf_finish(&pf->sbuf);
220		if (err == 0)
221			pf->out_offset = sbuf_len(&pf->sbuf);
222		else
223			pf->out_offset = 0;
224	}
225	SNDSTAT_UNLOCK();
226	return (err);
227}
228
229/************************************************************************/
230
231int
232sndstat_register(device_t dev, char *str, sndstat_handler handler)
233{
234	struct sndstat_entry *ent;
235	struct sndstat_entry *pre;
236	const char *devtype;
237	int type, unit;
238
239	if (dev) {
240		unit = device_get_unit(dev);
241		devtype = device_get_name(dev);
242		if (!strcmp(devtype, "pcm"))
243			type = SS_TYPE_PCM;
244		else if (!strcmp(devtype, "midi"))
245			type = SS_TYPE_MIDI;
246		else if (!strcmp(devtype, "sequencer"))
247			type = SS_TYPE_SEQUENCER;
248		else
249			return (EINVAL);
250	} else {
251		type = SS_TYPE_MODULE;
252		unit = -1;
253	}
254
255	ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
256	ent->dev = dev;
257	ent->str = str;
258	ent->type = type;
259	ent->unit = unit;
260	ent->handler = handler;
261
262	SNDSTAT_LOCK();
263	/* sorted list insertion */
264	TAILQ_FOREACH(pre, &sndstat_devlist, link) {
265		if (pre->unit > unit)
266			break;
267		else if (pre->unit < unit)
268			continue;
269		if (pre->type > type)
270			break;
271		else if (pre->type < unit)
272			continue;
273	}
274	if (pre == NULL) {
275		TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
276	} else {
277		TAILQ_INSERT_BEFORE(pre, ent, link);
278	}
279	SNDSTAT_UNLOCK();
280
281	return (0);
282}
283
284int
285sndstat_registerfile(char *str)
286{
287	return (sndstat_register(NULL, str, NULL));
288}
289
290int
291sndstat_unregister(device_t dev)
292{
293	struct sndstat_entry *ent;
294	int error = ENXIO;
295
296	SNDSTAT_LOCK();
297	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
298		if (ent->dev == dev) {
299			TAILQ_REMOVE(&sndstat_devlist, ent, link);
300			free(ent, M_DEVBUF);
301			error = 0;
302			break;
303		}
304	}
305	SNDSTAT_UNLOCK();
306
307	return (error);
308}
309
310int
311sndstat_unregisterfile(char *str)
312{
313	struct sndstat_entry *ent;
314	int error = ENXIO;
315
316	SNDSTAT_LOCK();
317	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
318		if (ent->dev == NULL && ent->str == str) {
319			TAILQ_REMOVE(&sndstat_devlist, ent, link);
320			free(ent, M_DEVBUF);
321			error = 0;
322			break;
323		}
324	}
325	SNDSTAT_UNLOCK();
326
327	return (error);
328}
329
330/************************************************************************/
331
332static int
333sndstat_prepare(struct sndstat_file *pf_self)
334{
335	struct sbuf *s = &pf_self->sbuf;
336	struct sndstat_entry *ent;
337	struct snddev_info *d;
338	struct sndstat_file *pf;
339    	int k;
340
341	/* make sure buffer is reset */
342	sbuf_clear(s);
343
344	if (snd_verbose > 0) {
345		sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
346		    (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
347		    MACHINE_ARCH);
348	}
349
350	/* generate list of installed devices */
351	k = 0;
352	TAILQ_FOREACH(ent, &sndstat_devlist, link) {
353		if (ent->dev == NULL)
354			continue;
355		d = device_get_softc(ent->dev);
356		if (!PCM_REGISTERED(d))
357			continue;
358		if (!k++)
359			sbuf_printf(s, "Installed devices:\n");
360		sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
361		sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
362		if (snd_verbose > 0)
363			sbuf_printf(s, " %s", ent->str);
364		if (ent->handler) {
365			/* XXX Need Giant magic entry ??? */
366			PCM_ACQUIRE_QUICK(d);
367			ent->handler(s, ent->dev, snd_verbose);
368			PCM_RELEASE_QUICK(d);
369		}
370		sbuf_printf(s, "\n");
371	}
372	if (k == 0)
373		sbuf_printf(s, "No devices installed.\n");
374
375	/* append any input from userspace */
376	k = 0;
377	TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
378		if (pf == pf_self)
379			continue;
380		if (pf->out_offset == 0)
381			continue;
382		if (!k++)
383			sbuf_printf(s, "Installed devices from userspace:\n");
384		sbuf_bcat(s, sbuf_data(&pf->sbuf),
385		    sbuf_len(&pf->sbuf));
386	}
387	if (k == 0)
388		sbuf_printf(s, "No devices installed from userspace.\n");
389
390	/* append any file versions */
391	if (snd_verbose >= 3) {
392		k = 0;
393		TAILQ_FOREACH(ent, &sndstat_devlist, link) {
394			if (ent->dev == NULL && ent->str != NULL) {
395				if (!k++)
396					sbuf_printf(s, "\nFile Versions:\n");
397				sbuf_printf(s, "%s\n", ent->str);
398			}
399		}
400		if (k == 0)
401			sbuf_printf(s, "\nNo file versions.\n");
402	}
403	sbuf_finish(s);
404    	return (sbuf_len(s));
405}
406
407static void
408sndstat_sysinit(void *p)
409{
410	sx_init(&sndstat_lock, "sndstat lock");
411	sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
412	    UID_ROOT, GID_WHEEL, 0644, "sndstat");
413}
414SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
415
416static void
417sndstat_sysuninit(void *p)
418{
419	if (sndstat_dev != NULL) {
420		/* destroy_dev() will wait for all references to go away */
421		destroy_dev(sndstat_dev);
422	}
423	sx_destroy(&sndstat_lock);
424}
425SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
426