1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org>
5 * Copyright (c) 2001 Cameron Grant <cg@FreeBSD.org>
6 * Copyright (c) 2020 The FreeBSD Foundation
7 * All rights reserved.
8 * Copyright (c) 2024 The FreeBSD Foundation
9 *
10 * Portions of this software were developed by Christos Margiolis
11 * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
12 *
13 * Portions of this software were developed by Ka Ho Ng
14 * under sponsorship from the FreeBSD Foundation.
15 *
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
21 * 2. Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38 #ifdef HAVE_KERNEL_OPTION_HEADERS
39 #include "opt_snd.h"
40 #endif
41
42 #include <sys/param.h>
43 #include <sys/lock.h>
44 #include <sys/malloc.h>
45 #include <sys/nv.h>
46 #include <sys/dnv.h>
47 #include <sys/sx.h>
48
49 #include <dev/sound/pcm/sound.h>
50 #include <dev/sound/pcm/pcm.h>
51
52 #include "feeder_if.h"
53
54 #define SS_TYPE_PCM 1
55 #define SS_TYPE_MIDI 2
56 #define SS_TYPE_SEQUENCER 3
57
58 static d_open_t sndstat_open;
59 static void sndstat_close(void *);
60 static d_read_t sndstat_read;
61 static d_write_t sndstat_write;
62 static d_ioctl_t sndstat_ioctl;
63
64 static struct cdevsw sndstat_cdevsw = {
65 .d_version = D_VERSION,
66 .d_open = sndstat_open,
67 .d_read = sndstat_read,
68 .d_write = sndstat_write,
69 .d_ioctl = sndstat_ioctl,
70 .d_name = "sndstat",
71 .d_flags = D_TRACKCLOSE,
72 };
73
74 struct sndstat_entry {
75 TAILQ_ENTRY(sndstat_entry) link;
76 device_t dev;
77 char *str;
78 int type, unit;
79 };
80
81 struct sndstat_userdev {
82 TAILQ_ENTRY(sndstat_userdev) link;
83 char *provider;
84 char *nameunit;
85 char *devnode;
86 char *desc;
87 unsigned int pchan;
88 unsigned int rchan;
89 struct {
90 uint32_t min_rate;
91 uint32_t max_rate;
92 uint32_t formats;
93 uint32_t min_chn;
94 uint32_t max_chn;
95 } info_play, info_rec;
96 nvlist_t *provider_nvl;
97 };
98
99 struct sndstat_file {
100 TAILQ_ENTRY(sndstat_file) entry;
101 struct sbuf sbuf;
102 struct sx lock;
103 void *devs_nvlbuf; /* (l) */
104 size_t devs_nbytes; /* (l) */
105 TAILQ_HEAD(, sndstat_userdev) userdev_list; /* (l) */
106 int out_offset;
107 int in_offset;
108 int fflags;
109 };
110
111 static struct sx sndstat_lock;
112 static struct cdev *sndstat_dev;
113
114 #define SNDSTAT_LOCK() sx_xlock(&sndstat_lock)
115 #define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock)
116
117 static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist);
118 static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist);
119
120 int snd_verbose = 0;
121
122 static int sndstat_prepare(struct sndstat_file *);
123 static struct sndstat_userdev *
124 sndstat_line2userdev(struct sndstat_file *, const char *, int);
125
126 static int
sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)127 sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS)
128 {
129 int error, verbose;
130
131 verbose = snd_verbose;
132 error = sysctl_handle_int(oidp, &verbose, 0, req);
133 if (error == 0 && req->newptr != NULL) {
134 if (verbose < 0 || verbose > 4)
135 error = EINVAL;
136 else
137 snd_verbose = verbose;
138 }
139 return (error);
140 }
141 SYSCTL_PROC(_hw_snd, OID_AUTO, verbose,
142 CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int),
143 sysctl_hw_sndverbose, "I",
144 "verbosity level");
145
146 static int
sndstat_open(struct cdev * i_dev,int flags,int mode,struct thread * td)147 sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
148 {
149 struct sndstat_file *pf;
150
151 pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO);
152
153 if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) {
154 free(pf, M_DEVBUF);
155 return (ENOMEM);
156 }
157
158 pf->fflags = flags;
159 TAILQ_INIT(&pf->userdev_list);
160 sx_init(&pf->lock, "sndstat_file");
161
162 SNDSTAT_LOCK();
163 TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry);
164 SNDSTAT_UNLOCK();
165
166 devfs_set_cdevpriv(pf, &sndstat_close);
167
168 return (0);
169 }
170
171 /*
172 * Should only be called either when:
173 * * Closing
174 * * pf->lock held
175 */
176 static void
sndstat_remove_all_userdevs(struct sndstat_file * pf)177 sndstat_remove_all_userdevs(struct sndstat_file *pf)
178 {
179 struct sndstat_userdev *ud;
180
181 KASSERT(
182 sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__));
183 while ((ud = TAILQ_FIRST(&pf->userdev_list)) != NULL) {
184 TAILQ_REMOVE(&pf->userdev_list, ud, link);
185 free(ud->provider, M_DEVBUF);
186 free(ud->desc, M_DEVBUF);
187 free(ud->devnode, M_DEVBUF);
188 free(ud->nameunit, M_DEVBUF);
189 nvlist_destroy(ud->provider_nvl);
190 free(ud, M_DEVBUF);
191 }
192 }
193
194 static void
sndstat_close(void * sndstat_file)195 sndstat_close(void *sndstat_file)
196 {
197 struct sndstat_file *pf = (struct sndstat_file *)sndstat_file;
198
199 SNDSTAT_LOCK();
200 sbuf_delete(&pf->sbuf);
201 TAILQ_REMOVE(&sndstat_filelist, pf, entry);
202 SNDSTAT_UNLOCK();
203
204 free(pf->devs_nvlbuf, M_NVLIST);
205 sx_xlock(&pf->lock);
206 sndstat_remove_all_userdevs(pf);
207 sx_xunlock(&pf->lock);
208 sx_destroy(&pf->lock);
209
210 free(pf, M_DEVBUF);
211 }
212
213 static int
sndstat_read(struct cdev * i_dev,struct uio * buf,int flag)214 sndstat_read(struct cdev *i_dev, struct uio *buf, int flag)
215 {
216 struct sndstat_file *pf;
217 int err;
218 int len;
219
220 err = devfs_get_cdevpriv((void **)&pf);
221 if (err != 0)
222 return (err);
223
224 /* skip zero-length reads */
225 if (buf->uio_resid == 0)
226 return (0);
227
228 SNDSTAT_LOCK();
229 if (pf->out_offset != 0) {
230 /* don't allow both reading and writing */
231 err = EINVAL;
232 goto done;
233 } else if (pf->in_offset == 0) {
234 err = sndstat_prepare(pf);
235 if (err <= 0) {
236 err = ENOMEM;
237 goto done;
238 }
239 }
240 len = sbuf_len(&pf->sbuf) - pf->in_offset;
241 if (len > buf->uio_resid)
242 len = buf->uio_resid;
243 if (len > 0)
244 err = uiomove(sbuf_data(&pf->sbuf) + pf->in_offset, len, buf);
245 pf->in_offset += len;
246 done:
247 SNDSTAT_UNLOCK();
248 return (err);
249 }
250
251 static int
sndstat_write(struct cdev * i_dev,struct uio * buf,int flag)252 sndstat_write(struct cdev *i_dev, struct uio *buf, int flag)
253 {
254 struct sndstat_file *pf;
255 uint8_t temp[64];
256 int err;
257 int len;
258
259 err = devfs_get_cdevpriv((void **)&pf);
260 if (err != 0)
261 return (err);
262
263 /* skip zero-length writes */
264 if (buf->uio_resid == 0)
265 return (0);
266
267 /* don't allow writing more than 64Kbytes */
268 if (buf->uio_resid > 65536)
269 return (ENOMEM);
270
271 SNDSTAT_LOCK();
272 if (pf->in_offset != 0) {
273 /* don't allow both reading and writing */
274 err = EINVAL;
275 } else {
276 /* only remember the last write - allows for updates */
277 sx_xlock(&pf->lock);
278 sndstat_remove_all_userdevs(pf);
279 sx_xunlock(&pf->lock);
280
281 while (1) {
282 len = sizeof(temp);
283 if (len > buf->uio_resid)
284 len = buf->uio_resid;
285 if (len > 0) {
286 err = uiomove(temp, len, buf);
287 if (err)
288 break;
289 } else {
290 break;
291 }
292 if (sbuf_bcat(&pf->sbuf, temp, len) < 0) {
293 err = ENOMEM;
294 break;
295 }
296 }
297 sbuf_finish(&pf->sbuf);
298
299 if (err == 0) {
300 char *line, *str;
301
302 str = sbuf_data(&pf->sbuf);
303 while ((line = strsep(&str, "\n")) != NULL) {
304 struct sndstat_userdev *ud;
305
306 ud = sndstat_line2userdev(pf, line, strlen(line));
307 if (ud == NULL)
308 continue;
309
310 sx_xlock(&pf->lock);
311 TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
312 sx_xunlock(&pf->lock);
313 }
314
315 pf->out_offset = sbuf_len(&pf->sbuf);
316 } else
317 pf->out_offset = 0;
318
319 sbuf_clear(&pf->sbuf);
320 }
321 SNDSTAT_UNLOCK();
322 return (err);
323 }
324
325 static void
sndstat_get_caps(struct snddev_info * d,bool play,uint32_t * min_rate,uint32_t * max_rate,uint32_t * fmts,uint32_t * minchn,uint32_t * maxchn)326 sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate,
327 uint32_t *max_rate, uint32_t *fmts, uint32_t *minchn, uint32_t *maxchn)
328 {
329 struct pcm_channel *c;
330 int dir;
331
332 dir = play ? PCMDIR_PLAY : PCMDIR_REC;
333
334 if (play && d->pvchancount > 0) {
335 *min_rate = *max_rate = d->pvchanrate;
336 *fmts = AFMT_ENCODING(d->pvchanformat);
337 *minchn = *maxchn = AFMT_CHANNEL(d->pvchanformat);
338 return;
339 } else if (!play && d->rvchancount > 0) {
340 *min_rate = *max_rate = d->rvchanrate;
341 *fmts = AFMT_ENCODING(d->rvchanformat);
342 *minchn = *maxchn = AFMT_CHANNEL(d->rvchanformat);
343 return;
344 }
345
346 *fmts = 0;
347 *min_rate = UINT32_MAX;
348 *max_rate = 0;
349 *minchn = UINT32_MAX;
350 *maxchn = 0;
351 CHN_FOREACH(c, d, channels.pcm) {
352 struct pcmchan_caps *caps;
353 int i;
354
355 if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0)
356 continue;
357
358 CHN_LOCK(c);
359 caps = chn_getcaps(c);
360 *min_rate = min(caps->minspeed, *min_rate);
361 *max_rate = max(caps->maxspeed, *max_rate);
362 for (i = 0; caps->fmtlist[i]; i++) {
363 *fmts |= AFMT_ENCODING(caps->fmtlist[i]);
364 *minchn = min(AFMT_CHANNEL(caps->fmtlist[i]), *minchn);
365 *maxchn = max(AFMT_CHANNEL(caps->fmtlist[i]), *maxchn);
366 }
367 CHN_UNLOCK(c);
368 }
369 if (*min_rate == UINT32_MAX)
370 *min_rate = 0;
371 if (*minchn == UINT32_MAX)
372 *minchn = 0;
373 }
374
375 static nvlist_t *
sndstat_create_diinfo_nv(uint32_t min_rate,uint32_t max_rate,uint32_t formats,uint32_t min_chn,uint32_t max_chn)376 sndstat_create_diinfo_nv(uint32_t min_rate, uint32_t max_rate, uint32_t formats,
377 uint32_t min_chn, uint32_t max_chn)
378 {
379 nvlist_t *nv;
380
381 nv = nvlist_create(0);
382 if (nv == NULL)
383 return (NULL);
384 nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_RATE, min_rate);
385 nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_RATE, max_rate);
386 nvlist_add_number(nv, SNDST_DSPS_INFO_FORMATS, formats);
387 nvlist_add_number(nv, SNDST_DSPS_INFO_MIN_CHN, min_chn);
388 nvlist_add_number(nv, SNDST_DSPS_INFO_MAX_CHN, max_chn);
389 return (nv);
390 }
391
392 static int
sndstat_build_sound4_nvlist(struct snddev_info * d,nvlist_t ** dip)393 sndstat_build_sound4_nvlist(struct snddev_info *d, nvlist_t **dip)
394 {
395 uint32_t maxrate, minrate, fmts, minchn, maxchn;
396 nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL;
397 int err;
398
399 di = nvlist_create(0);
400 if (di == NULL) {
401 err = ENOMEM;
402 goto done;
403 }
404 sound4di = nvlist_create(0);
405 if (sound4di == NULL) {
406 err = ENOMEM;
407 goto done;
408 }
409
410 nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
411 nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
412 device_get_nameunit(d->dev));
413 nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
414 device_get_unit(d->dev));
415 nvlist_add_string(
416 di, SNDST_DSPS_DESC, device_get_desc(d->dev));
417
418 PCM_ACQUIRE_QUICK(d);
419 nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
420 nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
421 if (d->playcount > 0) {
422 sndstat_get_caps(d, true, &minrate, &maxrate, &fmts, &minchn,
423 &maxchn);
424 nvlist_add_number(di, "pminrate", minrate);
425 nvlist_add_number(di, "pmaxrate", maxrate);
426 nvlist_add_number(di, "pfmts", fmts);
427 diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
428 minchn, maxchn);
429 if (diinfo == NULL)
430 nvlist_set_error(di, ENOMEM);
431 else
432 nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
433 }
434 if (d->reccount > 0) {
435 sndstat_get_caps(d, false, &minrate, &maxrate, &fmts, &minchn,
436 &maxchn);
437 nvlist_add_number(di, "rminrate", minrate);
438 nvlist_add_number(di, "rmaxrate", maxrate);
439 nvlist_add_number(di, "rfmts", fmts);
440 diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
441 minchn, maxchn);
442 if (diinfo == NULL)
443 nvlist_set_error(di, ENOMEM);
444 else
445 nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
446 }
447
448 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
449 device_get_unit(d->dev)); // XXX: I want signed integer here
450 nvlist_add_bool(
451 sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
452 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
453 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
454 nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
455 sound4di = NULL;
456 PCM_RELEASE_QUICK(d);
457 nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
458
459 err = nvlist_error(di);
460 if (err)
461 goto done;
462
463 *dip = di;
464
465 done:
466 if (err) {
467 nvlist_destroy(sound4di);
468 nvlist_destroy(di);
469 }
470 return (err);
471 }
472
473 static int
sndstat_build_userland_nvlist(struct sndstat_userdev * ud,nvlist_t ** dip)474 sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
475 {
476 nvlist_t *di, *diinfo;
477 int err;
478
479 di = nvlist_create(0);
480 if (di == NULL) {
481 err = ENOMEM;
482 goto done;
483 }
484
485 nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
486 nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
487 nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
488 nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
489 nvlist_add_string(
490 di, SNDST_DSPS_DEVNODE, ud->devnode);
491 nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
492 if (ud->pchan != 0) {
493 nvlist_add_number(di, "pminrate",
494 ud->info_play.min_rate);
495 nvlist_add_number(di, "pmaxrate",
496 ud->info_play.max_rate);
497 nvlist_add_number(di, "pfmts",
498 ud->info_play.formats);
499 diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
500 ud->info_play.max_rate, ud->info_play.formats,
501 ud->info_play.min_chn, ud->info_play.max_chn);
502 if (diinfo == NULL)
503 nvlist_set_error(di, ENOMEM);
504 else
505 nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
506 }
507 if (ud->rchan != 0) {
508 nvlist_add_number(di, "rminrate",
509 ud->info_rec.min_rate);
510 nvlist_add_number(di, "rmaxrate",
511 ud->info_rec.max_rate);
512 nvlist_add_number(di, "rfmts",
513 ud->info_rec.formats);
514 diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
515 ud->info_rec.max_rate, ud->info_rec.formats,
516 ud->info_rec.min_chn, ud->info_rec.max_chn);
517 if (diinfo == NULL)
518 nvlist_set_error(di, ENOMEM);
519 else
520 nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
521 }
522 nvlist_add_string(di, SNDST_DSPS_PROVIDER,
523 (ud->provider != NULL) ? ud->provider : "");
524 if (ud->provider_nvl != NULL)
525 nvlist_add_nvlist(
526 di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
527
528 err = nvlist_error(di);
529 if (err)
530 goto done;
531
532 *dip = di;
533
534 done:
535 if (err)
536 nvlist_destroy(di);
537 return (err);
538 }
539
540 /*
541 * Should only be called with the following locks held:
542 * * sndstat_lock
543 */
544 static int
sndstat_create_devs_nvlist(nvlist_t ** nvlp)545 sndstat_create_devs_nvlist(nvlist_t **nvlp)
546 {
547 int err;
548 nvlist_t *nvl;
549 struct sndstat_entry *ent;
550 struct sndstat_file *pf;
551
552 nvl = nvlist_create(0);
553 if (nvl == NULL)
554 return (ENOMEM);
555
556 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
557 struct snddev_info *d;
558 nvlist_t *di;
559
560 d = device_get_softc(ent->dev);
561 if (!PCM_REGISTERED(d))
562 continue;
563
564 err = sndstat_build_sound4_nvlist(d, &di);
565 if (err)
566 goto done;
567
568 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
569 nvlist_destroy(di);
570 err = nvlist_error(nvl);
571 if (err)
572 goto done;
573 }
574
575 TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
576 struct sndstat_userdev *ud;
577
578 sx_xlock(&pf->lock);
579
580 TAILQ_FOREACH(ud, &pf->userdev_list, link) {
581 nvlist_t *di;
582
583 err = sndstat_build_userland_nvlist(ud, &di);
584 if (err != 0) {
585 sx_xunlock(&pf->lock);
586 goto done;
587 }
588 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
589 nvlist_destroy(di);
590
591 err = nvlist_error(nvl);
592 if (err != 0) {
593 sx_xunlock(&pf->lock);
594 goto done;
595 }
596 }
597
598 sx_xunlock(&pf->lock);
599 }
600
601 *nvlp = nvl;
602
603 done:
604 if (err != 0)
605 nvlist_destroy(nvl);
606 return (err);
607 }
608
609 static int
sndstat_refresh_devs(struct sndstat_file * pf)610 sndstat_refresh_devs(struct sndstat_file *pf)
611 {
612 sx_xlock(&pf->lock);
613 free(pf->devs_nvlbuf, M_NVLIST);
614 pf->devs_nvlbuf = NULL;
615 pf->devs_nbytes = 0;
616 sx_unlock(&pf->lock);
617
618 return (0);
619 }
620
621 static int
sndstat_get_devs(struct sndstat_file * pf,void * arg_buf,size_t * arg_nbytes)622 sndstat_get_devs(struct sndstat_file *pf, void *arg_buf, size_t *arg_nbytes)
623 {
624 int err;
625
626 SNDSTAT_LOCK();
627 sx_xlock(&pf->lock);
628
629 if (pf->devs_nvlbuf == NULL) {
630 nvlist_t *nvl;
631 void *nvlbuf;
632 size_t nbytes;
633 int err;
634
635 sx_xunlock(&pf->lock);
636
637 err = sndstat_create_devs_nvlist(&nvl);
638 if (err) {
639 SNDSTAT_UNLOCK();
640 return (err);
641 }
642
643 sx_xlock(&pf->lock);
644
645 nvlbuf = nvlist_pack(nvl, &nbytes);
646 err = nvlist_error(nvl);
647 nvlist_destroy(nvl);
648 if (nvlbuf == NULL || err != 0) {
649 SNDSTAT_UNLOCK();
650 sx_xunlock(&pf->lock);
651 if (err == 0)
652 return (ENOMEM);
653 return (err);
654 }
655
656 free(pf->devs_nvlbuf, M_NVLIST);
657 pf->devs_nvlbuf = nvlbuf;
658 pf->devs_nbytes = nbytes;
659 }
660
661 SNDSTAT_UNLOCK();
662
663 if (*arg_nbytes == 0) {
664 *arg_nbytes = pf->devs_nbytes;
665 err = 0;
666 goto done;
667 }
668 if (*arg_nbytes < pf->devs_nbytes) {
669 *arg_nbytes = 0;
670 err = 0;
671 goto done;
672 }
673
674 err = copyout(pf->devs_nvlbuf, arg_buf, pf->devs_nbytes);
675 if (err)
676 goto done;
677
678 *arg_nbytes = pf->devs_nbytes;
679
680 free(pf->devs_nvlbuf, M_NVLIST);
681 pf->devs_nvlbuf = NULL;
682 pf->devs_nbytes = 0;
683
684 done:
685 sx_unlock(&pf->lock);
686 return (err);
687 }
688
689 static int
sndstat_unpack_user_nvlbuf(const void * unvlbuf,size_t nbytes,nvlist_t ** nvl)690 sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
691 {
692 void *nvlbuf;
693 int err;
694
695 nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK);
696 err = copyin(unvlbuf, nvlbuf, nbytes);
697 if (err != 0) {
698 free(nvlbuf, M_DEVBUF);
699 return (err);
700 }
701 *nvl = nvlist_unpack(nvlbuf, nbytes, 0);
702 free(nvlbuf, M_DEVBUF);
703 if (*nvl == NULL) {
704 return (EINVAL);
705 }
706
707 return (0);
708 }
709
710 static bool
sndstat_diinfo_is_sane(const nvlist_t * diinfo)711 sndstat_diinfo_is_sane(const nvlist_t *diinfo)
712 {
713 if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
714 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
715 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
716 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
717 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
718 return (false);
719 return (true);
720 }
721
722 static bool
sndstat_dsp_nvlist_is_sane(const nvlist_t * nvlist)723 sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
724 {
725 if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
726 nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
727 nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
728 nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
729 return (false);
730
731 if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
732 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
733 if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
734 SNDST_DSPS_INFO_PLAY)))
735 return (false);
736 } else if (!(nvlist_exists_number(nvlist, "pminrate") &&
737 nvlist_exists_number(nvlist, "pmaxrate") &&
738 nvlist_exists_number(nvlist, "pfmts")))
739 return (false);
740 }
741
742 if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
743 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
744 if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
745 SNDST_DSPS_INFO_REC)))
746 return (false);
747 } else if (!(nvlist_exists_number(nvlist, "rminrate") &&
748 nvlist_exists_number(nvlist, "rmaxrate") &&
749 nvlist_exists_number(nvlist, "rfmts")))
750 return (false);
751 }
752
753 return (true);
754
755 }
756
757 static void
sndstat_get_diinfo_nv(const nvlist_t * nv,uint32_t * min_rate,uint32_t * max_rate,uint32_t * formats,uint32_t * min_chn,uint32_t * max_chn)758 sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
759 uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
760 uint32_t *max_chn)
761 {
762 *min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
763 *max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
764 *formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
765 *min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
766 *max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
767 }
768
769 static int
sndstat_dsp_unpack_nvlist(const nvlist_t * nvlist,struct sndstat_userdev * ud)770 sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
771 {
772 const char *nameunit, *devnode, *desc;
773 unsigned int pchan, rchan;
774 uint32_t pminrate = 0, pmaxrate = 0;
775 uint32_t rminrate = 0, rmaxrate = 0;
776 uint32_t pfmts = 0, rfmts = 0;
777 uint32_t pminchn = 0, pmaxchn = 0;
778 uint32_t rminchn = 0, rmaxchn = 0;
779 nvlist_t *provider_nvl = NULL;
780 const nvlist_t *diinfo;
781 const char *provider;
782
783 devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
784 if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
785 nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
786 else
787 nameunit = devnode;
788 desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
789 pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
790 rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
791 if (pchan != 0) {
792 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
793 diinfo = nvlist_get_nvlist(nvlist,
794 SNDST_DSPS_INFO_PLAY);
795 sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
796 &pfmts, &pminchn, &pmaxchn);
797 } else {
798 pminrate = nvlist_get_number(nvlist, "pminrate");
799 pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
800 pfmts = nvlist_get_number(nvlist, "pfmts");
801 }
802 }
803 if (rchan != 0) {
804 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
805 diinfo = nvlist_get_nvlist(nvlist,
806 SNDST_DSPS_INFO_REC);
807 sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
808 &rfmts, &rminchn, &rmaxchn);
809 } else {
810 rminrate = nvlist_get_number(nvlist, "rminrate");
811 rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
812 rfmts = nvlist_get_number(nvlist, "rfmts");
813 }
814 }
815
816 provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
817 if (provider[0] == '\0')
818 provider = NULL;
819
820 if (provider != NULL &&
821 nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
822 provider_nvl = nvlist_clone(
823 nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
824 if (provider_nvl == NULL)
825 return (ENOMEM);
826 }
827
828 ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL;
829 ud->devnode = strdup(devnode, M_DEVBUF);
830 ud->nameunit = strdup(nameunit, M_DEVBUF);
831 ud->desc = strdup(desc, M_DEVBUF);
832 ud->pchan = pchan;
833 ud->rchan = rchan;
834 ud->info_play.min_rate = pminrate;
835 ud->info_play.max_rate = pmaxrate;
836 ud->info_play.formats = pfmts;
837 ud->info_play.min_chn = pminchn;
838 ud->info_play.max_chn = pmaxchn;
839 ud->info_rec.min_rate = rminrate;
840 ud->info_rec.max_rate = rmaxrate;
841 ud->info_rec.formats = rfmts;
842 ud->info_rec.min_chn = rminchn;
843 ud->info_rec.max_chn = rmaxchn;
844 ud->provider_nvl = provider_nvl;
845 return (0);
846 }
847
848 static int
sndstat_add_user_devs(struct sndstat_file * pf,void * nvlbuf,size_t nbytes)849 sndstat_add_user_devs(struct sndstat_file *pf, void *nvlbuf, size_t nbytes)
850 {
851 int err;
852 nvlist_t *nvl = NULL;
853 const nvlist_t * const *dsps;
854 size_t i, ndsps;
855
856 if ((pf->fflags & FWRITE) == 0) {
857 err = EPERM;
858 goto done;
859 }
860
861 if (nbytes > SNDST_UNVLBUF_MAX) {
862 err = ENOMEM;
863 goto done;
864 }
865
866 err = sndstat_unpack_user_nvlbuf(nvlbuf, nbytes, &nvl);
867 if (err != 0)
868 goto done;
869
870 if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
871 err = EINVAL;
872 goto done;
873 }
874 dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
875 for (i = 0; i < ndsps; i++) {
876 if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
877 err = EINVAL;
878 goto done;
879 }
880 }
881 sx_xlock(&pf->lock);
882 for (i = 0; i < ndsps; i++) {
883 struct sndstat_userdev *ud =
884 malloc(sizeof(*ud), M_DEVBUF, M_WAITOK);
885 err = sndstat_dsp_unpack_nvlist(dsps[i], ud);
886 if (err) {
887 sx_unlock(&pf->lock);
888 goto done;
889 }
890 TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
891 }
892 sx_unlock(&pf->lock);
893
894 done:
895 nvlist_destroy(nvl);
896 return (err);
897 }
898
899 static int
sndstat_flush_user_devs(struct sndstat_file * pf)900 sndstat_flush_user_devs(struct sndstat_file *pf)
901 {
902 if ((pf->fflags & FWRITE) == 0)
903 return (EPERM);
904
905 sx_xlock(&pf->lock);
906 sndstat_remove_all_userdevs(pf);
907 sx_xunlock(&pf->lock);
908
909 return (0);
910 }
911
912 static int
sndstat_ioctl(struct cdev * dev,u_long cmd,caddr_t data,int fflag,struct thread * td)913 sndstat_ioctl(
914 struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
915 {
916 int err;
917 struct sndstat_file *pf;
918 struct sndstioc_nv_arg *arg;
919 #ifdef COMPAT_FREEBSD32
920 struct sndstioc_nv_arg32 *arg32;
921 size_t nbytes;
922 #endif
923
924 err = devfs_get_cdevpriv((void **)&pf);
925 if (err != 0)
926 return (err);
927
928 switch (cmd) {
929 case SNDSTIOC_GET_DEVS:
930 arg = (struct sndstioc_nv_arg *)data;
931 err = sndstat_get_devs(pf, arg->buf, &arg->nbytes);
932 break;
933 #ifdef COMPAT_FREEBSD32
934 case SNDSTIOC_GET_DEVS32:
935 arg32 = (struct sndstioc_nv_arg32 *)data;
936 nbytes = arg32->nbytes;
937 err = sndstat_get_devs(pf, (void *)(uintptr_t)arg32->buf,
938 &nbytes);
939 if (err == 0) {
940 KASSERT(nbytes < UINT_MAX, ("impossibly many bytes"));
941 arg32->nbytes = nbytes;
942 }
943 break;
944 #endif
945 case SNDSTIOC_ADD_USER_DEVS:
946 arg = (struct sndstioc_nv_arg *)data;
947 err = sndstat_add_user_devs(pf, arg->buf, arg->nbytes);
948 break;
949 #ifdef COMPAT_FREEBSD32
950 case SNDSTIOC_ADD_USER_DEVS32:
951 arg32 = (struct sndstioc_nv_arg32 *)data;
952 err = sndstat_add_user_devs(pf, (void *)(uintptr_t)arg32->buf,
953 arg32->nbytes);
954 break;
955 #endif
956 case SNDSTIOC_REFRESH_DEVS:
957 err = sndstat_refresh_devs(pf);
958 break;
959 case SNDSTIOC_FLUSH_USER_DEVS:
960 err = sndstat_flush_user_devs(pf);
961 break;
962 default:
963 err = ENODEV;
964 }
965
966 return (err);
967 }
968
969 static struct sndstat_userdev *
sndstat_line2userdev(struct sndstat_file * pf,const char * line,int n)970 sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n)
971 {
972 struct sndstat_userdev *ud;
973 const char *e, *m;
974
975 ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO);
976
977 ud->provider = NULL;
978 ud->provider_nvl = NULL;
979 e = strchr(line, ':');
980 if (e == NULL)
981 goto fail;
982 ud->nameunit = strndup(line, e - line, M_DEVBUF);
983 ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO);
984 strlcat(ud->devnode, ud->nameunit, e - line + 1);
985 line = e + 1;
986
987 e = strchr(line, '<');
988 if (e == NULL)
989 goto fail;
990 line = e + 1;
991 e = strrchr(line, '>');
992 if (e == NULL)
993 goto fail;
994 ud->desc = strndup(line, e - line, M_DEVBUF);
995 line = e + 1;
996
997 e = strchr(line, '(');
998 if (e == NULL)
999 goto fail;
1000 line = e + 1;
1001 e = strrchr(line, ')');
1002 if (e == NULL)
1003 goto fail;
1004 m = strstr(line, "play");
1005 if (m != NULL && m < e)
1006 ud->pchan = 1;
1007 m = strstr(line, "rec");
1008 if (m != NULL && m < e)
1009 ud->rchan = 1;
1010
1011 return (ud);
1012
1013 fail:
1014 free(ud->nameunit, M_DEVBUF);
1015 free(ud->devnode, M_DEVBUF);
1016 free(ud->desc, M_DEVBUF);
1017 free(ud, M_DEVBUF);
1018 return (NULL);
1019 }
1020
1021 /************************************************************************/
1022
1023 int
sndstat_register(device_t dev,char * str)1024 sndstat_register(device_t dev, char *str)
1025 {
1026 struct sndstat_entry *ent;
1027 struct sndstat_entry *pre;
1028 const char *devtype;
1029 int type, unit;
1030
1031 unit = device_get_unit(dev);
1032 devtype = device_get_name(dev);
1033 if (!strcmp(devtype, "pcm"))
1034 type = SS_TYPE_PCM;
1035 else if (!strcmp(devtype, "midi"))
1036 type = SS_TYPE_MIDI;
1037 else if (!strcmp(devtype, "sequencer"))
1038 type = SS_TYPE_SEQUENCER;
1039 else
1040 return (EINVAL);
1041
1042 ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
1043 ent->dev = dev;
1044 ent->str = str;
1045 ent->type = type;
1046 ent->unit = unit;
1047
1048 SNDSTAT_LOCK();
1049 /* sorted list insertion */
1050 TAILQ_FOREACH(pre, &sndstat_devlist, link) {
1051 if (pre->unit > unit)
1052 break;
1053 else if (pre->unit < unit)
1054 continue;
1055 if (pre->type > type)
1056 break;
1057 else if (pre->type < unit)
1058 continue;
1059 }
1060 if (pre == NULL) {
1061 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
1062 } else {
1063 TAILQ_INSERT_BEFORE(pre, ent, link);
1064 }
1065 SNDSTAT_UNLOCK();
1066
1067 return (0);
1068 }
1069
1070 int
sndstat_unregister(device_t dev)1071 sndstat_unregister(device_t dev)
1072 {
1073 struct sndstat_entry *ent;
1074 int error = ENXIO;
1075
1076 SNDSTAT_LOCK();
1077 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1078 if (ent->dev == dev) {
1079 TAILQ_REMOVE(&sndstat_devlist, ent, link);
1080 free(ent, M_DEVBUF);
1081 error = 0;
1082 break;
1083 }
1084 }
1085 SNDSTAT_UNLOCK();
1086
1087 return (error);
1088 }
1089
1090 /************************************************************************/
1091
1092 static int
sndstat_prepare_pcm(struct sbuf * s,device_t dev,int verbose)1093 sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
1094 {
1095 struct snddev_info *d;
1096 struct pcm_channel *c;
1097 struct pcm_feeder *f;
1098
1099 d = device_get_softc(dev);
1100 PCM_BUSYASSERT(d);
1101
1102 if (CHN_EMPTY(d, channels.pcm)) {
1103 sbuf_printf(s, " (mixer only)");
1104 return (0);
1105 }
1106
1107 if (verbose < 1) {
1108 sbuf_printf(s, " (%s%s%s",
1109 d->playcount ? "play" : "",
1110 (d->playcount && d->reccount) ? "/" : "",
1111 d->reccount ? "rec" : "");
1112 } else {
1113 sbuf_printf(s, " (%dp:%dv/%dr:%dv",
1114 d->playcount, d->pvchancount,
1115 d->reccount, d->rvchancount);
1116 }
1117 sbuf_printf(s, "%s)%s",
1118 ((d->playcount != 0 && d->reccount != 0) &&
1119 (d->flags & SD_F_SIMPLEX)) ? " simplex" : "",
1120 (device_get_unit(dev) == snd_unit) ? " default" : "");
1121
1122 if (verbose <= 1)
1123 return (0);
1124
1125 sbuf_printf(s, "\n\t");
1126 sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS);
1127
1128 CHN_FOREACH(c, d, channels.pcm) {
1129 KASSERT(c->bufhard != NULL && c->bufsoft != NULL,
1130 ("hosed pcm channel setup"));
1131
1132 sbuf_printf(s, "\n\t");
1133
1134 sbuf_printf(s, "%s[%s]: ",
1135 (c->parentchannel != NULL) ?
1136 c->parentchannel->name : "", c->name);
1137 sbuf_printf(s, "spd %d", c->speed);
1138 if (c->speed != sndbuf_getspd(c->bufhard)) {
1139 sbuf_printf(s, "/%d",
1140 sndbuf_getspd(c->bufhard));
1141 }
1142 sbuf_printf(s, ", fmt 0x%08x", c->format);
1143 if (c->format != sndbuf_getfmt(c->bufhard)) {
1144 sbuf_printf(s, "/0x%08x",
1145 sndbuf_getfmt(c->bufhard));
1146 }
1147 sbuf_printf(s, ", flags 0x%08x, 0x%08x",
1148 c->flags, c->feederflags);
1149 if (c->pid != -1) {
1150 sbuf_printf(s, ", pid %d (%s)",
1151 c->pid, c->comm);
1152 }
1153 sbuf_printf(s, "\n\t");
1154
1155 sbuf_printf(s, "interrupts %d, ", c->interrupts);
1156
1157 if (c->direction == PCMDIR_REC) {
1158 sbuf_printf(s,
1159 "overruns %d, feed %u, hfree %d, "
1160 "sfree %d [b:%d/%d/%d|bs:%d/%d/%d]",
1161 c->xruns, c->feedcount,
1162 sndbuf_getfree(c->bufhard),
1163 sndbuf_getfree(c->bufsoft),
1164 sndbuf_getsize(c->bufhard),
1165 sndbuf_getblksz(c->bufhard),
1166 sndbuf_getblkcnt(c->bufhard),
1167 sndbuf_getsize(c->bufsoft),
1168 sndbuf_getblksz(c->bufsoft),
1169 sndbuf_getblkcnt(c->bufsoft));
1170 } else {
1171 sbuf_printf(s,
1172 "underruns %d, feed %u, ready %d "
1173 "[b:%d/%d/%d|bs:%d/%d/%d]",
1174 c->xruns, c->feedcount,
1175 sndbuf_getready(c->bufsoft),
1176 sndbuf_getsize(c->bufhard),
1177 sndbuf_getblksz(c->bufhard),
1178 sndbuf_getblkcnt(c->bufhard),
1179 sndbuf_getsize(c->bufsoft),
1180 sndbuf_getblksz(c->bufsoft),
1181 sndbuf_getblkcnt(c->bufsoft));
1182 }
1183 sbuf_printf(s, "\n\t");
1184
1185 sbuf_printf(s, "channel flags=0x%b", c->flags, CHN_F_BITS);
1186 sbuf_printf(s, "\n\t");
1187
1188 sbuf_printf(s, "{%s}",
1189 (c->direction == PCMDIR_REC) ? "hardware" : "userland");
1190 sbuf_printf(s, " -> ");
1191 f = c->feeder;
1192 while (f->source != NULL)
1193 f = f->source;
1194 while (f != NULL) {
1195 sbuf_printf(s, "%s", f->class->name);
1196 if (f->desc->type == FEEDER_FORMAT) {
1197 sbuf_printf(s, "(0x%08x -> 0x%08x)",
1198 f->desc->in, f->desc->out);
1199 } else if (f->desc->type == FEEDER_MATRIX) {
1200 sbuf_printf(s, "(%d.%d -> %d.%d)",
1201 AFMT_CHANNEL(f->desc->in) -
1202 AFMT_EXTCHANNEL(f->desc->in),
1203 AFMT_EXTCHANNEL(f->desc->in),
1204 AFMT_CHANNEL(f->desc->out) -
1205 AFMT_EXTCHANNEL(f->desc->out),
1206 AFMT_EXTCHANNEL(f->desc->out));
1207 } else if (f->desc->type == FEEDER_RATE) {
1208 sbuf_printf(s,
1209 "(0x%08x q:%d %d -> %d)",
1210 f->desc->out,
1211 FEEDER_GET(f, FEEDRATE_QUALITY),
1212 FEEDER_GET(f, FEEDRATE_SRC),
1213 FEEDER_GET(f, FEEDRATE_DST));
1214 } else {
1215 sbuf_printf(s, "(0x%08x)",
1216 f->desc->out);
1217 }
1218 sbuf_printf(s, " -> ");
1219 f = f->parent;
1220 }
1221 sbuf_printf(s, "{%s}",
1222 (c->direction == PCMDIR_REC) ? "userland" : "hardware");
1223 }
1224
1225 return (0);
1226 }
1227
1228 static int
sndstat_prepare(struct sndstat_file * pf_self)1229 sndstat_prepare(struct sndstat_file *pf_self)
1230 {
1231 struct sbuf *s = &pf_self->sbuf;
1232 struct sndstat_entry *ent;
1233 struct snddev_info *d;
1234 struct sndstat_file *pf;
1235 int k;
1236
1237 /* make sure buffer is reset */
1238 sbuf_clear(s);
1239
1240 if (snd_verbose > 0)
1241 sbuf_printf(s, "FreeBSD Audio Driver\n");
1242
1243 /* generate list of installed devices */
1244 k = 0;
1245 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1246 d = device_get_softc(ent->dev);
1247 if (!PCM_REGISTERED(d))
1248 continue;
1249 if (!k++)
1250 sbuf_printf(s, "Installed devices:\n");
1251 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
1252 sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
1253 if (snd_verbose > 0)
1254 sbuf_printf(s, " %s", ent->str);
1255 /* XXX Need Giant magic entry ??? */
1256 PCM_ACQUIRE_QUICK(d);
1257 sndstat_prepare_pcm(s, ent->dev, snd_verbose);
1258 PCM_RELEASE_QUICK(d);
1259 sbuf_printf(s, "\n");
1260 }
1261 if (k == 0)
1262 sbuf_printf(s, "No devices installed.\n");
1263
1264 /* append any input from userspace */
1265 k = 0;
1266 TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
1267 struct sndstat_userdev *ud;
1268
1269 if (pf == pf_self)
1270 continue;
1271 sx_xlock(&pf->lock);
1272 if (TAILQ_EMPTY(&pf->userdev_list)) {
1273 sx_unlock(&pf->lock);
1274 continue;
1275 }
1276 if (!k++)
1277 sbuf_printf(s, "Installed devices from userspace:\n");
1278 TAILQ_FOREACH(ud, &pf->userdev_list, link) {
1279 const char *caps = (ud->pchan && ud->rchan) ?
1280 "play/rec" :
1281 (ud->pchan ? "play" : (ud->rchan ? "rec" : ""));
1282 sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc);
1283 sbuf_printf(s, " (%s)", caps);
1284 sbuf_printf(s, "\n");
1285 }
1286 sx_unlock(&pf->lock);
1287 }
1288 if (k == 0)
1289 sbuf_printf(s, "No devices installed from userspace.\n");
1290
1291 sbuf_finish(s);
1292 return (sbuf_len(s));
1293 }
1294
1295 static void
sndstat_sysinit(void * p)1296 sndstat_sysinit(void *p)
1297 {
1298 sx_init(&sndstat_lock, "sndstat lock");
1299 sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
1300 UID_ROOT, GID_WHEEL, 0644, "sndstat");
1301 }
1302 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
1303
1304 static void
sndstat_sysuninit(void * p)1305 sndstat_sysuninit(void *p)
1306 {
1307 if (sndstat_dev != NULL) {
1308 /* destroy_dev() will wait for all references to go away */
1309 destroy_dev(sndstat_dev);
1310 }
1311 sx_destroy(&sndstat_lock);
1312 }
1313 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
1314