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