1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
5 * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 #ifdef HAVE_KERNEL_OPTION_HEADERS
31 #include "opt_snd.h"
32 #endif
33
34 #include <dev/sound/pcm/sound.h>
35 #include <dev/sound/pcm/pcm.h>
36 #include <dev/sound/version.h>
37 #include <sys/sx.h>
38
39 SND_DECLARE_FILE("$FreeBSD$");
40
41 #define SS_TYPE_MODULE 0
42 #define SS_TYPE_PCM 1
43 #define SS_TYPE_MIDI 2
44 #define SS_TYPE_SEQUENCER 3
45
46 static d_open_t sndstat_open;
47 static void sndstat_close(void *);
48 static d_read_t sndstat_read;
49 static d_write_t sndstat_write;
50
51 static struct cdevsw sndstat_cdevsw = {
52 .d_version = D_VERSION,
53 .d_open = sndstat_open,
54 .d_read = sndstat_read,
55 .d_write = sndstat_write,
56 .d_name = "sndstat",
57 .d_flags = D_TRACKCLOSE,
58 };
59
60 struct sndstat_entry {
61 TAILQ_ENTRY(sndstat_entry) link;
62 device_t dev;
63 char *str;
64 sndstat_handler handler;
65 int type, unit;
66 };
67
68 struct sndstat_file {
69 TAILQ_ENTRY(sndstat_file) entry;
70 struct sbuf sbuf;
71 int out_offset;
72 int in_offset;
73 };
74
75 static struct sx sndstat_lock;
76 static struct cdev *sndstat_dev;
77
78 #define SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
79 #define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
80
81 static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
82 static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
83
84 int snd_verbose = 0;
85
86 static int sndstat_prepare(struct sndstat_file *);
87
88 static int
89 sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
90 {
91 int error, verbose;
92
93 verbose = snd_verbose;
94 error = sysctl_handle_int(oidp, &verbose, 0, req);
95 if (error == 0 && req->newptr != NULL) {
96 if (verbose < 0 || verbose > 4)
97 error = EINVAL;
98 else
99 snd_verbose = verbose;
100 }
101 return (error);
102 }
103 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RWTUN,
104 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level");
105
106 static int
107 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
108 {
109 struct sndstat_file *pf;
110
111 pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
112
113 SNDSTAT_LOCK();
114 if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
115 SNDSTAT_UNLOCK();
116 free(pf, M_DEVBUF);
117 return (ENOMEM);
118 }
119 TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
120 SNDSTAT_UNLOCK();
121
122 devfs_set_cdevpriv(pf, &sndstat_close);
123
124 return (0);
125 }
126
127 static void
128 sndstat_close(void *sndstat_file)
129 {
130 struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
131
132 SNDSTAT_LOCK();
133 sbuf_delete(&pf->sbuf);
134 TAILQ_REMOVE(&sndstat_filelist, pf, entry);
135 SNDSTAT_UNLOCK();
136
137 free(pf, M_DEVBUF);
138 }
139
140 static int
141 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
142 {
143 struct sndstat_file *pf;
144 int err;
145 int len;
146
147 err = devfs_get_cdevpriv((void **)&pf);
148 if (err != 0)
149 return (err);
150
151 /* skip zero-length reads */
152 if (buf->uio_resid == 0)
153 return (0);
154
155 SNDSTAT_LOCK();
156 if (pf->out_offset != 0) {
157 /* don't allow both reading and writing */
158 err = EINVAL;
159 goto done;
160 } else if (pf->in_offset == 0) {
161 err = sndstat_prepare(pf);
162 if (err <= 0) {
163 err = ENOMEM;
164 goto done;
165 }
166 }
167 len = sbuf_len(&pf->sbuf) - pf->in_offset;
168 if (len > buf->uio_resid)
169 len = buf->uio_resid;
170 if (len > 0)
171 err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
172 pf->in_offset += len;
173 done:
174 SNDSTAT_UNLOCK();
175 return (err);
176 }
177
178 static int
179 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
180 {
181 struct sndstat_file *pf;
182 uint8_t temp[64];
183 int err;
184 int len;
185
186 err = devfs_get_cdevpriv((void **)&pf);
187 if (err != 0)
188 return (err);
189
190 /* skip zero-length writes */
191 if (buf->uio_resid == 0)
192 return (0);
193
194 /* don't allow writing more than 64Kbytes */
195 if (buf->uio_resid > 65536)
196 return (ENOMEM);
197
198 SNDSTAT_LOCK();
199 if (pf->in_offset != 0) {
200 /* don't allow both reading and writing */
201 err = EINVAL;
202 } else {
203 /* only remember the last write - allows for updates */
204 sbuf_clear(&pf->sbuf);
205 while (1) {
206 len = sizeof(temp);
207 if (len > buf->uio_resid)
208 len = buf->uio_resid;
209 if (len > 0) {
210 err = uiomove(temp, len, buf);
211 if (err)
212 break;
213 } else {
214 break;
215 }
216 if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
217 err = ENOMEM;
218 break;
219 }
220 }
221 sbuf_finish(&pf->sbuf);
222 if (err == 0)
223 pf->out_offset = sbuf_len(&pf->sbuf);
224 else
225 pf->out_offset = 0;
226 }
227 SNDSTAT_UNLOCK();
228 return (err);
229 }
230
231 /************************************************************************/
232
233 int
234 sndstat_register(device_t dev, char *str, sndstat_handler handler)
235 {
236 struct sndstat_entry *ent;
237 struct sndstat_entry *pre;
238 const char *devtype;
239 int type, unit;
240
241 if (dev) {
242 unit = device_get_unit(dev);
243 devtype = device_get_name(dev);
244 if (!strcmp(devtype, "pcm"))
245 type = SS_TYPE_PCM;
246 else if (!strcmp(devtype, "midi"))
247 type = SS_TYPE_MIDI;
248 else if (!strcmp(devtype, "sequencer"))
249 type = SS_TYPE_SEQUENCER;
250 else
251 return (EINVAL);
252 } else {
253 type = SS_TYPE_MODULE;
254 unit = -1;
255 }
256
257 ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
258 ent->dev = dev;
259 ent->str = str;
260 ent->type = type;
261 ent->unit = unit;
262 ent->handler = handler;
263
264 SNDSTAT_LOCK();
265 /* sorted list insertion */
266 TAILQ_FOREACH(pre, &sndstat_devlist, link) {
267 if (pre->unit > unit)
268 break;
269 else if (pre->unit < unit)
270 continue;
271 if (pre->type > type)
272 break;
273 else if (pre->type < unit)
274 continue;
275 }
276 if (pre == NULL) {
277 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
278 } else {
279 TAILQ_INSERT_BEFORE(pre, ent, link);
280 }
281 SNDSTAT_UNLOCK();
282
283 return (0);
284 }
285
286 int
287 sndstat_registerfile(char *str)
288 {
289 return (sndstat_register(NULL, str, NULL));
290 }
291
292 int
293 sndstat_unregister(device_t dev)
294 {
295 struct sndstat_entry *ent;
296 int error = ENXIO;
297
298 SNDSTAT_LOCK();
299 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
300 if (ent->dev == dev) {
301 TAILQ_REMOVE(&sndstat_devlist, ent, link);
302 free(ent, M_DEVBUF);
303 error = 0;
304 break;
305 }
306 }
307 SNDSTAT_UNLOCK();
308
309 return (error);
310 }
311
312 int
313 sndstat_unregisterfile(char *str)
314 {
315 struct sndstat_entry *ent;
316 int error = ENXIO;
317
318 SNDSTAT_LOCK();
319 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
320 if (ent->dev == NULL && ent->str == str) {
321 TAILQ_REMOVE(&sndstat_devlist, ent, link);
322 free(ent, M_DEVBUF);
323 error = 0;
324 break;
325 }
326 }
327 SNDSTAT_UNLOCK();
328
329 return (error);
330 }
331
332 /************************************************************************/
333
334 static int
335 sndstat_prepare(struct sndstat_file *pf_self)
336 {
337 struct sbuf *s = &pf_self->sbuf;
338 struct sndstat_entry *ent;
339 struct snddev_info *d;
340 struct sndstat_file *pf;
341 int k;
342
343 /* make sure buffer is reset */
344 sbuf_clear(s);
345
346 if (snd_verbose > 0) {
347 sbuf_printf(s, "FreeBSD Audio Driver (%ubit %d/%s)\n",
348 (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION,
349 MACHINE_ARCH);
350 }
351
352 /* generate list of installed devices */
353 k = 0;
354 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
355 if (ent->dev == NULL)
356 continue;
357 d = device_get_softc(ent->dev);
358 if (!PCM_REGISTERED(d))
359 continue;
360 if (!k++)
361 sbuf_printf(s, "Installed devices:\n");
362 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
363 sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
364 if (snd_verbose > 0)
365 sbuf_printf(s, " %s", ent->str);
366 if (ent->handler) {
367 /* XXX Need Giant magic entry ??? */
368 PCM_ACQUIRE_QUICK(d);
369 ent->handler(s, ent->dev, snd_verbose);
370 PCM_RELEASE_QUICK(d);
371 }
372 sbuf_printf(s, "\n");
373 }
374 if (k == 0)
375 sbuf_printf(s, "No devices installed.\n");
376
377 /* append any input from userspace */
378 k = 0;
379 TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
380 if (pf == pf_self)
381 continue;
382 if (pf->out_offset == 0)
383 continue;
384 if (!k++)
385 sbuf_printf(s, "Installed devices from userspace:\n");
386 sbuf_bcat(s, sbuf_data(&pf->sbuf),
387 sbuf_len(&pf->sbuf));
388 }
389 if (k == 0)
390 sbuf_printf(s, "No devices installed from userspace.\n");
391
392 /* append any file versions */
393 if (snd_verbose >= 3) {
394 k = 0;
395 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
396 if (ent->dev == NULL && ent->str != NULL) {
397 if (!k++)
398 sbuf_printf(s, "\nFile Versions:\n");
399 sbuf_printf(s, "%s\n", ent->str);
400 }
401 }
402 if (k == 0)
403 sbuf_printf(s, "\nNo file versions.\n");
404 }
405 sbuf_finish(s);
406 return (sbuf_len(s));
407 }
408
409 static void
410 sndstat_sysinit(void *p)
411 {
412 sx_init(&sndstat_lock, "sndstat lock");
413 sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
414 UID_ROOT, GID_WHEEL, 0644, "sndstat");
415 }
416 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
417
418 static void
419 sndstat_sysuninit(void *p)
420 {
421 if (sndstat_dev != NULL) {
422 /* destroy_dev() will wait for all references to go away */
423 destroy_dev(sndstat_dev);
424 }
425 sx_destroy(&sndstat_lock);
426 }
427 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
Cache object: fd78bd3ff53e2f0de8c586d4089fbf4a
|