1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2005-2009 Ariff Abdullah <[email protected]>
5 * Copyright (c) 2001 Cameron Grant <[email protected]>
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
sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)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,
104 CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int),
105 sysctl_hw_sndverbose, "I",
106 "verbosity level");
107
108 static int
sndstat_open(struct cdev * i_dev,int flags,int mode,struct thread * td)109 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
110 {
111 struct sndstat_file *pf;
112
113 pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
114
115 SNDSTAT_LOCK();
116 if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
117 SNDSTAT_UNLOCK();
118 free(pf, M_DEVBUF);
119 return (ENOMEM);
120 }
121 TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
122 SNDSTAT_UNLOCK();
123
124 devfs_set_cdevpriv(pf, &sndstat_close);
125
126 return (0);
127 }
128
129 static void
sndstat_close(void * sndstat_file)130 sndstat_close(void *sndstat_file)
131 {
132 struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
133
134 SNDSTAT_LOCK();
135 sbuf_delete(&pf->sbuf);
136 TAILQ_REMOVE(&sndstat_filelist, pf, entry);
137 SNDSTAT_UNLOCK();
138
139 free(pf, M_DEVBUF);
140 }
141
142 static int
sndstat_read(struct cdev * i_dev,struct uio * buf,int flag)143 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
144 {
145 struct sndstat_file *pf;
146 int err;
147 int len;
148
149 err = devfs_get_cdevpriv((void **)&pf);
150 if (err != 0)
151 return (err);
152
153 /* skip zero-length reads */
154 if (buf->uio_resid == 0)
155 return (0);
156
157 SNDSTAT_LOCK();
158 if (pf->out_offset != 0) {
159 /* don't allow both reading and writing */
160 err = EINVAL;
161 goto done;
162 } else if (pf->in_offset == 0) {
163 err = sndstat_prepare(pf);
164 if (err <= 0) {
165 err = ENOMEM;
166 goto done;
167 }
168 }
169 len = sbuf_len(&pf->sbuf) - pf->in_offset;
170 if (len > buf->uio_resid)
171 len = buf->uio_resid;
172 if (len > 0)
173 err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
174 pf->in_offset += len;
175 done:
176 SNDSTAT_UNLOCK();
177 return (err);
178 }
179
180 static int
sndstat_write(struct cdev * i_dev,struct uio * buf,int flag)181 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
182 {
183 struct sndstat_file *pf;
184 uint8_t temp[64];
185 int err;
186 int len;
187
188 err = devfs_get_cdevpriv((void **)&pf);
189 if (err != 0)
190 return (err);
191
192 /* skip zero-length writes */
193 if (buf->uio_resid == 0)
194 return (0);
195
196 /* don't allow writing more than 64Kbytes */
197 if (buf->uio_resid > 65536)
198 return (ENOMEM);
199
200 SNDSTAT_LOCK();
201 if (pf->in_offset != 0) {
202 /* don't allow both reading and writing */
203 err = EINVAL;
204 } else {
205 /* only remember the last write - allows for updates */
206 sbuf_clear(&pf->sbuf);
207 while (1) {
208 len = sizeof(temp);
209 if (len > buf->uio_resid)
210 len = buf->uio_resid;
211 if (len > 0) {
212 err = uiomove(temp, len, buf);
213 if (err)
214 break;
215 } else {
216 break;
217 }
218 if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
219 err = ENOMEM;
220 break;
221 }
222 }
223 sbuf_finish(&pf->sbuf);
224 if (err == 0)
225 pf->out_offset = sbuf_len(&pf->sbuf);
226 else
227 pf->out_offset = 0;
228 }
229 SNDSTAT_UNLOCK();
230 return (err);
231 }
232
233 /************************************************************************/
234
235 int
sndstat_register(device_t dev,char * str,sndstat_handler handler)236 sndstat_register(device_t dev, char *str, sndstat_handler handler)
237 {
238 struct sndstat_entry *ent;
239 struct sndstat_entry *pre;
240 const char *devtype;
241 int type, unit;
242
243 if (dev) {
244 unit = device_get_unit(dev);
245 devtype = device_get_name(dev);
246 if (!strcmp(devtype, "pcm"))
247 type = SS_TYPE_PCM;
248 else if (!strcmp(devtype, "midi"))
249 type = SS_TYPE_MIDI;
250 else if (!strcmp(devtype, "sequencer"))
251 type = SS_TYPE_SEQUENCER;
252 else
253 return (EINVAL);
254 } else {
255 type = SS_TYPE_MODULE;
256 unit = -1;
257 }
258
259 ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
260 ent->dev = dev;
261 ent->str = str;
262 ent->type = type;
263 ent->unit = unit;
264 ent->handler = handler;
265
266 SNDSTAT_LOCK();
267 /* sorted list insertion */
268 TAILQ_FOREACH(pre, &sndstat_devlist, link) {
269 if (pre->unit > unit)
270 break;
271 else if (pre->unit < unit)
272 continue;
273 if (pre->type > type)
274 break;
275 else if (pre->type < unit)
276 continue;
277 }
278 if (pre == NULL) {
279 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
280 } else {
281 TAILQ_INSERT_BEFORE(pre, ent, link);
282 }
283 SNDSTAT_UNLOCK();
284
285 return (0);
286 }
287
288 int
sndstat_registerfile(char * str)289 sndstat_registerfile(char *str)
290 {
291 return (sndstat_register(NULL, str, NULL));
292 }
293
294 int
sndstat_unregister(device_t dev)295 sndstat_unregister(device_t dev)
296 {
297 struct sndstat_entry *ent;
298 int error = ENXIO;
299
300 SNDSTAT_LOCK();
301 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
302 if (ent->dev == dev) {
303 TAILQ_REMOVE(&sndstat_devlist, ent, link);
304 free(ent, M_DEVBUF);
305 error = 0;
306 break;
307 }
308 }
309 SNDSTAT_UNLOCK();
310
311 return (error);
312 }
313
314 int
sndstat_unregisterfile(char * str)315 sndstat_unregisterfile(char *str)
316 {
317 struct sndstat_entry *ent;
318 int error = ENXIO;
319
320 SNDSTAT_LOCK();
321 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
322 if (ent->dev == NULL && ent->str == str) {
323 TAILQ_REMOVE(&sndstat_devlist, ent, link);
324 free(ent, M_DEVBUF);
325 error = 0;
326 break;
327 }
328 }
329 SNDSTAT_UNLOCK();
330
331 return (error);
332 }
333
334 /************************************************************************/
335
336 static int
sndstat_prepare(struct sndstat_file * pf_self)337 sndstat_prepare(struct sndstat_file *pf_self)
338 {
339 struct sbuf *s = &pf_self->sbuf;
340 struct sndstat_entry *ent;
341 struct snddev_info *d;
342 struct sndstat_file *pf;
343 int k;
344
345 /* make sure buffer is reset */
346 sbuf_clear(s);
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 /* generate list of installed devices */
355 k = 0;
356 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
357 if (ent->dev == NULL)
358 continue;
359 d = device_get_softc(ent->dev);
360 if (!PCM_REGISTERED(d))
361 continue;
362 if (!k++)
363 sbuf_printf(s, "Installed devices:\n");
364 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
365 sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
366 if (snd_verbose > 0)
367 sbuf_printf(s, " %s", ent->str);
368 if (ent->handler) {
369 /* XXX Need Giant magic entry ??? */
370 PCM_ACQUIRE_QUICK(d);
371 ent->handler(s, ent->dev, snd_verbose);
372 PCM_RELEASE_QUICK(d);
373 }
374 sbuf_printf(s, "\n");
375 }
376 if (k == 0)
377 sbuf_printf(s, "No devices installed.\n");
378
379 /* append any input from userspace */
380 k = 0;
381 TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
382 if (pf == pf_self)
383 continue;
384 if (pf->out_offset == 0)
385 continue;
386 if (!k++)
387 sbuf_printf(s, "Installed devices from userspace:\n");
388 sbuf_bcat(s, sbuf_data(&pf->sbuf),
389 sbuf_len(&pf->sbuf));
390 }
391 if (k == 0)
392 sbuf_printf(s, "No devices installed from userspace.\n");
393
394 /* append any file versions */
395 if (snd_verbose >= 3) {
396 k = 0;
397 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
398 if (ent->dev == NULL && ent->str != NULL) {
399 if (!k++)
400 sbuf_printf(s, "\nFile Versions:\n");
401 sbuf_printf(s, "%s\n", ent->str);
402 }
403 }
404 if (k == 0)
405 sbuf_printf(s, "\nNo file versions.\n");
406 }
407 sbuf_finish(s);
408 return (sbuf_len(s));
409 }
410
411 static void
sndstat_sysinit(void * p)412 sndstat_sysinit(void *p)
413 {
414 sx_init(&sndstat_lock, "sndstat lock");
415 sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
416 UID_ROOT, GID_WHEEL, 0644, "sndstat");
417 }
418 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
419
420 static void
sndstat_sysuninit(void * p)421 sndstat_sysuninit(void *p)
422 {
423 if (sndstat_dev != NULL) {
424 /* destroy_dev() will wait for all references to go away */
425 destroy_dev(sndstat_dev);
426 }
427 sx_destroy(&sndstat_lock);
428 }
429 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
430