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 struct pcm_channel *c;
396 struct pcm_feeder *f;
397 struct sbuf sb;
398 uint32_t maxrate, minrate, fmts, minchn, maxchn;
399 nvlist_t *di = NULL, *sound4di = NULL, *diinfo = NULL, *cdi = NULL;
400 int err, nchan;
401
402 di = nvlist_create(0);
403 if (di == NULL) {
404 err = ENOMEM;
405 goto done;
406 }
407 sound4di = nvlist_create(0);
408 if (sound4di == NULL) {
409 err = ENOMEM;
410 goto done;
411 }
412
413 nvlist_add_bool(di, SNDST_DSPS_FROM_USER, false);
414 nvlist_add_stringf(di, SNDST_DSPS_NAMEUNIT, "%s",
415 device_get_nameunit(d->dev));
416 nvlist_add_stringf(di, SNDST_DSPS_DEVNODE, "dsp%d",
417 device_get_unit(d->dev));
418 nvlist_add_string(
419 di, SNDST_DSPS_DESC, device_get_desc(d->dev));
420
421 PCM_ACQUIRE_QUICK(d);
422 nvlist_add_number(di, SNDST_DSPS_PCHAN, d->playcount);
423 nvlist_add_number(di, SNDST_DSPS_RCHAN, d->reccount);
424 if (d->playcount > 0) {
425 sndstat_get_caps(d, true, &minrate, &maxrate, &fmts, &minchn,
426 &maxchn);
427 nvlist_add_number(di, "pminrate", minrate);
428 nvlist_add_number(di, "pmaxrate", maxrate);
429 nvlist_add_number(di, "pfmts", fmts);
430 diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
431 minchn, maxchn);
432 if (diinfo == NULL)
433 nvlist_set_error(di, ENOMEM);
434 else
435 nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
436 }
437 if (d->reccount > 0) {
438 sndstat_get_caps(d, false, &minrate, &maxrate, &fmts, &minchn,
439 &maxchn);
440 nvlist_add_number(di, "rminrate", minrate);
441 nvlist_add_number(di, "rmaxrate", maxrate);
442 nvlist_add_number(di, "rfmts", fmts);
443 diinfo = sndstat_create_diinfo_nv(minrate, maxrate, fmts,
444 minchn, maxchn);
445 if (diinfo == NULL)
446 nvlist_set_error(di, ENOMEM);
447 else
448 nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
449 }
450
451 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_UNIT,
452 device_get_unit(d->dev)); // XXX: I want signed integer here
453 nvlist_add_bool(
454 sound4di, SNDST_DSPS_SOUND4_BITPERFECT, d->flags & SD_F_BITPERFECT);
455 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_PVCHAN, d->pvchancount);
456 nvlist_add_number(sound4di, SNDST_DSPS_SOUND4_RVCHAN, d->rvchancount);
457
458 nchan = 0;
459 CHN_FOREACH(c, d, channels.pcm) {
460 sbuf_new(&sb, NULL, 4096, SBUF_AUTOEXTEND);
461 cdi = nvlist_create(0);
462 if (cdi == NULL) {
463 sbuf_delete(&sb);
464 PCM_RELEASE_QUICK(d);
465 err = ENOMEM;
466 goto done;
467 }
468
469 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_NAME, c->name);
470 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_PARENTCHAN,
471 c->parentchannel != NULL ? c->parentchannel->name : "");
472 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_UNIT, nchan++);
473 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LATENCY,
474 c->latency);
475 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RATE, c->speed);
476 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FORMAT,
477 c->format);
478 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_PID, c->pid);
479 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_COMM, c->comm);
480 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_INTR,
481 c->interrupts);
482 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDCNT,
483 c->feedcount);
484 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_XRUNS, c->xruns);
485 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_LEFTVOL,
486 CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL));
487 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_RIGHTVOL,
488 CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR));
489 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FORMAT,
490 sndbuf_getfmt(c->bufhard));
491 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_SIZE,
492 sndbuf_getsize(c->bufhard));
493 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKSZ,
494 sndbuf_getblksz(c->bufhard));
495 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_BLKCNT,
496 sndbuf_getblkcnt(c->bufhard));
497 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_FREE,
498 sndbuf_getfree(c->bufhard));
499 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_HWBUF_READY,
500 sndbuf_getready(c->bufhard));
501 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FORMAT,
502 sndbuf_getfmt(c->bufsoft));
503 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_SIZE,
504 sndbuf_getsize(c->bufsoft));
505 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKSZ,
506 sndbuf_getblksz(c->bufsoft));
507 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_BLKCNT,
508 sndbuf_getblkcnt(c->bufsoft));
509 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_FREE,
510 sndbuf_getfree(c->bufsoft));
511 nvlist_add_number(cdi, SNDST_DSPS_SOUND4_CHAN_SWBUF_READY,
512 sndbuf_getready(c->bufsoft));
513
514 sbuf_printf(&sb, "[%s",
515 (c->direction == PCMDIR_REC) ? "hardware" : "userland");
516 sbuf_printf(&sb, " -> ");
517 f = c->feeder;
518 while (f->source != NULL)
519 f = f->source;
520 while (f != NULL) {
521 sbuf_printf(&sb, "%s", f->class->name);
522 if (f->desc->type == FEEDER_FORMAT) {
523 sbuf_printf(&sb, "(0x%08x -> 0x%08x)",
524 f->desc->in, f->desc->out);
525 } else if (f->desc->type == FEEDER_MATRIX) {
526 sbuf_printf(&sb, "(%d.%d -> %d.%d)",
527 AFMT_CHANNEL(f->desc->in) -
528 AFMT_EXTCHANNEL(f->desc->in),
529 AFMT_EXTCHANNEL(f->desc->in),
530 AFMT_CHANNEL(f->desc->out) -
531 AFMT_EXTCHANNEL(f->desc->out),
532 AFMT_EXTCHANNEL(f->desc->out));
533 } else if (f->desc->type == FEEDER_RATE) {
534 sbuf_printf(&sb,
535 "(0x%08x q:%d %d -> %d)",
536 f->desc->out,
537 FEEDER_GET(f, FEEDRATE_QUALITY),
538 FEEDER_GET(f, FEEDRATE_SRC),
539 FEEDER_GET(f, FEEDRATE_DST));
540 } else {
541 sbuf_printf(&sb, "(0x%08x)",
542 f->desc->out);
543 }
544 sbuf_printf(&sb, " -> ");
545 f = f->parent;
546 }
547 sbuf_printf(&sb, "%s]",
548 (c->direction == PCMDIR_REC) ? "userland" : "hardware");
549
550 sbuf_finish(&sb);
551 nvlist_add_string(cdi, SNDST_DSPS_SOUND4_CHAN_FEEDERCHAIN,
552 sbuf_data(&sb));
553 sbuf_delete(&sb);
554
555 nvlist_append_nvlist_array(sound4di,
556 SNDST_DSPS_SOUND4_CHAN_INFO, cdi);
557 nvlist_destroy(cdi);
558 err = nvlist_error(sound4di);
559 if (err) {
560 PCM_RELEASE_QUICK(d);
561 goto done;
562 }
563 }
564 nvlist_move_nvlist(di, SNDST_DSPS_PROVIDER_INFO, sound4di);
565 sound4di = NULL;
566
567 PCM_RELEASE_QUICK(d);
568 nvlist_add_string(di, SNDST_DSPS_PROVIDER, SNDST_DSPS_SOUND4_PROVIDER);
569
570 err = nvlist_error(di);
571 if (err)
572 goto done;
573
574 *dip = di;
575
576 done:
577 if (err) {
578 nvlist_destroy(sound4di);
579 nvlist_destroy(di);
580 }
581 return (err);
582 }
583
584 static int
sndstat_build_userland_nvlist(struct sndstat_userdev * ud,nvlist_t ** dip)585 sndstat_build_userland_nvlist(struct sndstat_userdev *ud, nvlist_t **dip)
586 {
587 nvlist_t *di, *diinfo;
588 int err;
589
590 di = nvlist_create(0);
591 if (di == NULL) {
592 err = ENOMEM;
593 goto done;
594 }
595
596 nvlist_add_bool(di, SNDST_DSPS_FROM_USER, true);
597 nvlist_add_number(di, SNDST_DSPS_PCHAN, ud->pchan);
598 nvlist_add_number(di, SNDST_DSPS_RCHAN, ud->rchan);
599 nvlist_add_string(di, SNDST_DSPS_NAMEUNIT, ud->nameunit);
600 nvlist_add_string(
601 di, SNDST_DSPS_DEVNODE, ud->devnode);
602 nvlist_add_string(di, SNDST_DSPS_DESC, ud->desc);
603 if (ud->pchan != 0) {
604 nvlist_add_number(di, "pminrate",
605 ud->info_play.min_rate);
606 nvlist_add_number(di, "pmaxrate",
607 ud->info_play.max_rate);
608 nvlist_add_number(di, "pfmts",
609 ud->info_play.formats);
610 diinfo = sndstat_create_diinfo_nv(ud->info_play.min_rate,
611 ud->info_play.max_rate, ud->info_play.formats,
612 ud->info_play.min_chn, ud->info_play.max_chn);
613 if (diinfo == NULL)
614 nvlist_set_error(di, ENOMEM);
615 else
616 nvlist_move_nvlist(di, SNDST_DSPS_INFO_PLAY, diinfo);
617 }
618 if (ud->rchan != 0) {
619 nvlist_add_number(di, "rminrate",
620 ud->info_rec.min_rate);
621 nvlist_add_number(di, "rmaxrate",
622 ud->info_rec.max_rate);
623 nvlist_add_number(di, "rfmts",
624 ud->info_rec.formats);
625 diinfo = sndstat_create_diinfo_nv(ud->info_rec.min_rate,
626 ud->info_rec.max_rate, ud->info_rec.formats,
627 ud->info_rec.min_chn, ud->info_rec.max_chn);
628 if (diinfo == NULL)
629 nvlist_set_error(di, ENOMEM);
630 else
631 nvlist_move_nvlist(di, SNDST_DSPS_INFO_REC, diinfo);
632 }
633 nvlist_add_string(di, SNDST_DSPS_PROVIDER,
634 (ud->provider != NULL) ? ud->provider : "");
635 if (ud->provider_nvl != NULL)
636 nvlist_add_nvlist(
637 di, SNDST_DSPS_PROVIDER_INFO, ud->provider_nvl);
638
639 err = nvlist_error(di);
640 if (err)
641 goto done;
642
643 *dip = di;
644
645 done:
646 if (err)
647 nvlist_destroy(di);
648 return (err);
649 }
650
651 /*
652 * Should only be called with the following locks held:
653 * * sndstat_lock
654 */
655 static int
sndstat_create_devs_nvlist(nvlist_t ** nvlp)656 sndstat_create_devs_nvlist(nvlist_t **nvlp)
657 {
658 int err;
659 nvlist_t *nvl;
660 struct sndstat_entry *ent;
661 struct sndstat_file *pf;
662
663 nvl = nvlist_create(0);
664 if (nvl == NULL)
665 return (ENOMEM);
666
667 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
668 struct snddev_info *d;
669 nvlist_t *di;
670
671 d = device_get_softc(ent->dev);
672 if (!PCM_REGISTERED(d))
673 continue;
674
675 err = sndstat_build_sound4_nvlist(d, &di);
676 if (err)
677 goto done;
678
679 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
680 nvlist_destroy(di);
681 err = nvlist_error(nvl);
682 if (err)
683 goto done;
684 }
685
686 TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
687 struct sndstat_userdev *ud;
688
689 sx_xlock(&pf->lock);
690
691 TAILQ_FOREACH(ud, &pf->userdev_list, link) {
692 nvlist_t *di;
693
694 err = sndstat_build_userland_nvlist(ud, &di);
695 if (err != 0) {
696 sx_xunlock(&pf->lock);
697 goto done;
698 }
699 nvlist_append_nvlist_array(nvl, SNDST_DSPS, di);
700 nvlist_destroy(di);
701
702 err = nvlist_error(nvl);
703 if (err != 0) {
704 sx_xunlock(&pf->lock);
705 goto done;
706 }
707 }
708
709 sx_xunlock(&pf->lock);
710 }
711
712 *nvlp = nvl;
713
714 done:
715 if (err != 0)
716 nvlist_destroy(nvl);
717 return (err);
718 }
719
720 static int
sndstat_refresh_devs(struct sndstat_file * pf)721 sndstat_refresh_devs(struct sndstat_file *pf)
722 {
723 sx_xlock(&pf->lock);
724 free(pf->devs_nvlbuf, M_NVLIST);
725 pf->devs_nvlbuf = NULL;
726 pf->devs_nbytes = 0;
727 sx_unlock(&pf->lock);
728
729 return (0);
730 }
731
732 static int
sndstat_get_devs(struct sndstat_file * pf,void * arg_buf,size_t * arg_nbytes)733 sndstat_get_devs(struct sndstat_file *pf, void *arg_buf, size_t *arg_nbytes)
734 {
735 int err;
736
737 SNDSTAT_LOCK();
738 sx_xlock(&pf->lock);
739
740 if (pf->devs_nvlbuf == NULL) {
741 nvlist_t *nvl;
742 void *nvlbuf;
743 size_t nbytes;
744 int err;
745
746 sx_xunlock(&pf->lock);
747
748 err = sndstat_create_devs_nvlist(&nvl);
749 if (err) {
750 SNDSTAT_UNLOCK();
751 return (err);
752 }
753
754 sx_xlock(&pf->lock);
755
756 nvlbuf = nvlist_pack(nvl, &nbytes);
757 err = nvlist_error(nvl);
758 nvlist_destroy(nvl);
759 if (nvlbuf == NULL || err != 0) {
760 SNDSTAT_UNLOCK();
761 sx_xunlock(&pf->lock);
762 if (err == 0)
763 return (ENOMEM);
764 return (err);
765 }
766
767 free(pf->devs_nvlbuf, M_NVLIST);
768 pf->devs_nvlbuf = nvlbuf;
769 pf->devs_nbytes = nbytes;
770 }
771
772 SNDSTAT_UNLOCK();
773
774 if (*arg_nbytes == 0) {
775 *arg_nbytes = pf->devs_nbytes;
776 err = 0;
777 goto done;
778 }
779 if (*arg_nbytes < pf->devs_nbytes) {
780 *arg_nbytes = 0;
781 err = 0;
782 goto done;
783 }
784
785 err = copyout(pf->devs_nvlbuf, arg_buf, pf->devs_nbytes);
786 if (err)
787 goto done;
788
789 *arg_nbytes = pf->devs_nbytes;
790
791 free(pf->devs_nvlbuf, M_NVLIST);
792 pf->devs_nvlbuf = NULL;
793 pf->devs_nbytes = 0;
794
795 done:
796 sx_unlock(&pf->lock);
797 return (err);
798 }
799
800 static int
sndstat_unpack_user_nvlbuf(const void * unvlbuf,size_t nbytes,nvlist_t ** nvl)801 sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl)
802 {
803 void *nvlbuf;
804 int err;
805
806 nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK);
807 err = copyin(unvlbuf, nvlbuf, nbytes);
808 if (err != 0) {
809 free(nvlbuf, M_DEVBUF);
810 return (err);
811 }
812 *nvl = nvlist_unpack(nvlbuf, nbytes, 0);
813 free(nvlbuf, M_DEVBUF);
814 if (*nvl == NULL) {
815 return (EINVAL);
816 }
817
818 return (0);
819 }
820
821 static bool
sndstat_diinfo_is_sane(const nvlist_t * diinfo)822 sndstat_diinfo_is_sane(const nvlist_t *diinfo)
823 {
824 if (!(nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_RATE) &&
825 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_RATE) &&
826 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_FORMATS) &&
827 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MIN_CHN) &&
828 nvlist_exists_number(diinfo, SNDST_DSPS_INFO_MAX_CHN)))
829 return (false);
830 return (true);
831 }
832
833 static bool
sndstat_dsp_nvlist_is_sane(const nvlist_t * nvlist)834 sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist)
835 {
836 if (!(nvlist_exists_string(nvlist, SNDST_DSPS_DEVNODE) &&
837 nvlist_exists_string(nvlist, SNDST_DSPS_DESC) &&
838 nvlist_exists_number(nvlist, SNDST_DSPS_PCHAN) &&
839 nvlist_exists_number(nvlist, SNDST_DSPS_RCHAN)))
840 return (false);
841
842 if (nvlist_get_number(nvlist, SNDST_DSPS_PCHAN) > 0) {
843 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
844 if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
845 SNDST_DSPS_INFO_PLAY)))
846 return (false);
847 } else if (!(nvlist_exists_number(nvlist, "pminrate") &&
848 nvlist_exists_number(nvlist, "pmaxrate") &&
849 nvlist_exists_number(nvlist, "pfmts")))
850 return (false);
851 }
852
853 if (nvlist_get_number(nvlist, SNDST_DSPS_RCHAN) > 0) {
854 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
855 if (!sndstat_diinfo_is_sane(nvlist_get_nvlist(nvlist,
856 SNDST_DSPS_INFO_REC)))
857 return (false);
858 } else if (!(nvlist_exists_number(nvlist, "rminrate") &&
859 nvlist_exists_number(nvlist, "rmaxrate") &&
860 nvlist_exists_number(nvlist, "rfmts")))
861 return (false);
862 }
863
864 return (true);
865
866 }
867
868 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)869 sndstat_get_diinfo_nv(const nvlist_t *nv, uint32_t *min_rate,
870 uint32_t *max_rate, uint32_t *formats, uint32_t *min_chn,
871 uint32_t *max_chn)
872 {
873 *min_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_RATE);
874 *max_rate = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_RATE);
875 *formats = nvlist_get_number(nv, SNDST_DSPS_INFO_FORMATS);
876 *min_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MIN_CHN);
877 *max_chn = nvlist_get_number(nv, SNDST_DSPS_INFO_MAX_CHN);
878 }
879
880 static int
sndstat_dsp_unpack_nvlist(const nvlist_t * nvlist,struct sndstat_userdev * ud)881 sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud)
882 {
883 const char *nameunit, *devnode, *desc;
884 unsigned int pchan, rchan;
885 uint32_t pminrate = 0, pmaxrate = 0;
886 uint32_t rminrate = 0, rmaxrate = 0;
887 uint32_t pfmts = 0, rfmts = 0;
888 uint32_t pminchn = 0, pmaxchn = 0;
889 uint32_t rminchn = 0, rmaxchn = 0;
890 nvlist_t *provider_nvl = NULL;
891 const nvlist_t *diinfo;
892 const char *provider;
893
894 devnode = nvlist_get_string(nvlist, SNDST_DSPS_DEVNODE);
895 if (nvlist_exists_string(nvlist, SNDST_DSPS_NAMEUNIT))
896 nameunit = nvlist_get_string(nvlist, SNDST_DSPS_NAMEUNIT);
897 else
898 nameunit = devnode;
899 desc = nvlist_get_string(nvlist, SNDST_DSPS_DESC);
900 pchan = nvlist_get_number(nvlist, SNDST_DSPS_PCHAN);
901 rchan = nvlist_get_number(nvlist, SNDST_DSPS_RCHAN);
902 if (pchan != 0) {
903 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_PLAY)) {
904 diinfo = nvlist_get_nvlist(nvlist,
905 SNDST_DSPS_INFO_PLAY);
906 sndstat_get_diinfo_nv(diinfo, &pminrate, &pmaxrate,
907 &pfmts, &pminchn, &pmaxchn);
908 } else {
909 pminrate = nvlist_get_number(nvlist, "pminrate");
910 pmaxrate = nvlist_get_number(nvlist, "pmaxrate");
911 pfmts = nvlist_get_number(nvlist, "pfmts");
912 }
913 }
914 if (rchan != 0) {
915 if (nvlist_exists_nvlist(nvlist, SNDST_DSPS_INFO_REC)) {
916 diinfo = nvlist_get_nvlist(nvlist,
917 SNDST_DSPS_INFO_REC);
918 sndstat_get_diinfo_nv(diinfo, &rminrate, &rmaxrate,
919 &rfmts, &rminchn, &rmaxchn);
920 } else {
921 rminrate = nvlist_get_number(nvlist, "rminrate");
922 rmaxrate = nvlist_get_number(nvlist, "rmaxrate");
923 rfmts = nvlist_get_number(nvlist, "rfmts");
924 }
925 }
926
927 provider = dnvlist_get_string(nvlist, SNDST_DSPS_PROVIDER, "");
928 if (provider[0] == '\0')
929 provider = NULL;
930
931 if (provider != NULL &&
932 nvlist_exists_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO)) {
933 provider_nvl = nvlist_clone(
934 nvlist_get_nvlist(nvlist, SNDST_DSPS_PROVIDER_INFO));
935 if (provider_nvl == NULL)
936 return (ENOMEM);
937 }
938
939 ud->provider = (provider != NULL) ? strdup(provider, M_DEVBUF) : NULL;
940 ud->devnode = strdup(devnode, M_DEVBUF);
941 ud->nameunit = strdup(nameunit, M_DEVBUF);
942 ud->desc = strdup(desc, M_DEVBUF);
943 ud->pchan = pchan;
944 ud->rchan = rchan;
945 ud->info_play.min_rate = pminrate;
946 ud->info_play.max_rate = pmaxrate;
947 ud->info_play.formats = pfmts;
948 ud->info_play.min_chn = pminchn;
949 ud->info_play.max_chn = pmaxchn;
950 ud->info_rec.min_rate = rminrate;
951 ud->info_rec.max_rate = rmaxrate;
952 ud->info_rec.formats = rfmts;
953 ud->info_rec.min_chn = rminchn;
954 ud->info_rec.max_chn = rmaxchn;
955 ud->provider_nvl = provider_nvl;
956 return (0);
957 }
958
959 static int
sndstat_add_user_devs(struct sndstat_file * pf,void * nvlbuf,size_t nbytes)960 sndstat_add_user_devs(struct sndstat_file *pf, void *nvlbuf, size_t nbytes)
961 {
962 int err;
963 nvlist_t *nvl = NULL;
964 const nvlist_t * const *dsps;
965 size_t i, ndsps;
966
967 if ((pf->fflags & FWRITE) == 0) {
968 err = EPERM;
969 goto done;
970 }
971
972 if (nbytes > SNDST_UNVLBUF_MAX) {
973 err = ENOMEM;
974 goto done;
975 }
976
977 err = sndstat_unpack_user_nvlbuf(nvlbuf, nbytes, &nvl);
978 if (err != 0)
979 goto done;
980
981 if (!nvlist_exists_nvlist_array(nvl, SNDST_DSPS)) {
982 err = EINVAL;
983 goto done;
984 }
985 dsps = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &ndsps);
986 for (i = 0; i < ndsps; i++) {
987 if (!sndstat_dsp_nvlist_is_sane(dsps[i])) {
988 err = EINVAL;
989 goto done;
990 }
991 }
992 sx_xlock(&pf->lock);
993 for (i = 0; i < ndsps; i++) {
994 struct sndstat_userdev *ud =
995 malloc(sizeof(*ud), M_DEVBUF, M_WAITOK);
996 err = sndstat_dsp_unpack_nvlist(dsps[i], ud);
997 if (err) {
998 sx_unlock(&pf->lock);
999 goto done;
1000 }
1001 TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link);
1002 }
1003 sx_unlock(&pf->lock);
1004
1005 done:
1006 nvlist_destroy(nvl);
1007 return (err);
1008 }
1009
1010 static int
sndstat_flush_user_devs(struct sndstat_file * pf)1011 sndstat_flush_user_devs(struct sndstat_file *pf)
1012 {
1013 if ((pf->fflags & FWRITE) == 0)
1014 return (EPERM);
1015
1016 sx_xlock(&pf->lock);
1017 sndstat_remove_all_userdevs(pf);
1018 sx_xunlock(&pf->lock);
1019
1020 return (0);
1021 }
1022
1023 static int
sndstat_ioctl(struct cdev * dev,u_long cmd,caddr_t data,int fflag,struct thread * td)1024 sndstat_ioctl(
1025 struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
1026 {
1027 int err;
1028 struct sndstat_file *pf;
1029 struct sndstioc_nv_arg *arg;
1030 #ifdef COMPAT_FREEBSD32
1031 struct sndstioc_nv_arg32 *arg32;
1032 size_t nbytes;
1033 #endif
1034
1035 err = devfs_get_cdevpriv((void **)&pf);
1036 if (err != 0)
1037 return (err);
1038
1039 switch (cmd) {
1040 case SNDSTIOC_GET_DEVS:
1041 arg = (struct sndstioc_nv_arg *)data;
1042 err = sndstat_get_devs(pf, arg->buf, &arg->nbytes);
1043 break;
1044 #ifdef COMPAT_FREEBSD32
1045 case SNDSTIOC_GET_DEVS32:
1046 arg32 = (struct sndstioc_nv_arg32 *)data;
1047 nbytes = arg32->nbytes;
1048 err = sndstat_get_devs(pf, (void *)(uintptr_t)arg32->buf,
1049 &nbytes);
1050 if (err == 0) {
1051 KASSERT(nbytes < UINT_MAX, ("impossibly many bytes"));
1052 arg32->nbytes = nbytes;
1053 }
1054 break;
1055 #endif
1056 case SNDSTIOC_ADD_USER_DEVS:
1057 arg = (struct sndstioc_nv_arg *)data;
1058 err = sndstat_add_user_devs(pf, arg->buf, arg->nbytes);
1059 break;
1060 #ifdef COMPAT_FREEBSD32
1061 case SNDSTIOC_ADD_USER_DEVS32:
1062 arg32 = (struct sndstioc_nv_arg32 *)data;
1063 err = sndstat_add_user_devs(pf, (void *)(uintptr_t)arg32->buf,
1064 arg32->nbytes);
1065 break;
1066 #endif
1067 case SNDSTIOC_REFRESH_DEVS:
1068 err = sndstat_refresh_devs(pf);
1069 break;
1070 case SNDSTIOC_FLUSH_USER_DEVS:
1071 err = sndstat_flush_user_devs(pf);
1072 break;
1073 default:
1074 err = ENODEV;
1075 }
1076
1077 return (err);
1078 }
1079
1080 static struct sndstat_userdev *
sndstat_line2userdev(struct sndstat_file * pf,const char * line,int n)1081 sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n)
1082 {
1083 struct sndstat_userdev *ud;
1084 const char *e, *m;
1085
1086 ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO);
1087
1088 ud->provider = NULL;
1089 ud->provider_nvl = NULL;
1090 e = strchr(line, ':');
1091 if (e == NULL)
1092 goto fail;
1093 ud->nameunit = strndup(line, e - line, M_DEVBUF);
1094 ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO);
1095 strlcat(ud->devnode, ud->nameunit, e - line + 1);
1096 line = e + 1;
1097
1098 e = strchr(line, '<');
1099 if (e == NULL)
1100 goto fail;
1101 line = e + 1;
1102 e = strrchr(line, '>');
1103 if (e == NULL)
1104 goto fail;
1105 ud->desc = strndup(line, e - line, M_DEVBUF);
1106 line = e + 1;
1107
1108 e = strchr(line, '(');
1109 if (e == NULL)
1110 goto fail;
1111 line = e + 1;
1112 e = strrchr(line, ')');
1113 if (e == NULL)
1114 goto fail;
1115 m = strstr(line, "play");
1116 if (m != NULL && m < e)
1117 ud->pchan = 1;
1118 m = strstr(line, "rec");
1119 if (m != NULL && m < e)
1120 ud->rchan = 1;
1121
1122 return (ud);
1123
1124 fail:
1125 free(ud->nameunit, M_DEVBUF);
1126 free(ud->devnode, M_DEVBUF);
1127 free(ud->desc, M_DEVBUF);
1128 free(ud, M_DEVBUF);
1129 return (NULL);
1130 }
1131
1132 /************************************************************************/
1133
1134 int
sndstat_register(device_t dev,char * str)1135 sndstat_register(device_t dev, char *str)
1136 {
1137 struct sndstat_entry *ent;
1138 struct sndstat_entry *pre;
1139 const char *devtype;
1140 int type, unit;
1141
1142 unit = device_get_unit(dev);
1143 devtype = device_get_name(dev);
1144 if (!strcmp(devtype, "pcm"))
1145 type = SS_TYPE_PCM;
1146 else if (!strcmp(devtype, "midi"))
1147 type = SS_TYPE_MIDI;
1148 else if (!strcmp(devtype, "sequencer"))
1149 type = SS_TYPE_SEQUENCER;
1150 else
1151 return (EINVAL);
1152
1153 ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO);
1154 ent->dev = dev;
1155 ent->str = str;
1156 ent->type = type;
1157 ent->unit = unit;
1158
1159 SNDSTAT_LOCK();
1160 /* sorted list insertion */
1161 TAILQ_FOREACH(pre, &sndstat_devlist, link) {
1162 if (pre->unit > unit)
1163 break;
1164 else if (pre->unit < unit)
1165 continue;
1166 if (pre->type > type)
1167 break;
1168 else if (pre->type < unit)
1169 continue;
1170 }
1171 if (pre == NULL) {
1172 TAILQ_INSERT_TAIL(&sndstat_devlist, ent, link);
1173 } else {
1174 TAILQ_INSERT_BEFORE(pre, ent, link);
1175 }
1176 SNDSTAT_UNLOCK();
1177
1178 return (0);
1179 }
1180
1181 int
sndstat_unregister(device_t dev)1182 sndstat_unregister(device_t dev)
1183 {
1184 struct sndstat_entry *ent;
1185 int error = ENXIO;
1186
1187 SNDSTAT_LOCK();
1188 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1189 if (ent->dev == dev) {
1190 TAILQ_REMOVE(&sndstat_devlist, ent, link);
1191 free(ent, M_DEVBUF);
1192 error = 0;
1193 break;
1194 }
1195 }
1196 SNDSTAT_UNLOCK();
1197
1198 return (error);
1199 }
1200
1201 /************************************************************************/
1202
1203 static int
sndstat_prepare_pcm(struct sbuf * s,device_t dev,int verbose)1204 sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose)
1205 {
1206 struct snddev_info *d;
1207 struct pcm_channel *c;
1208 struct pcm_feeder *f;
1209
1210 d = device_get_softc(dev);
1211 PCM_BUSYASSERT(d);
1212
1213 if (CHN_EMPTY(d, channels.pcm)) {
1214 sbuf_printf(s, " (mixer only)");
1215 return (0);
1216 }
1217
1218 if (verbose < 1) {
1219 sbuf_printf(s, " (%s%s%s",
1220 d->playcount ? "play" : "",
1221 (d->playcount && d->reccount) ? "/" : "",
1222 d->reccount ? "rec" : "");
1223 } else {
1224 sbuf_printf(s, " (%dp:%dv/%dr:%dv",
1225 d->playcount, d->pvchancount,
1226 d->reccount, d->rvchancount);
1227 }
1228 sbuf_printf(s, "%s)%s",
1229 ((d->playcount != 0 && d->reccount != 0) &&
1230 (d->flags & SD_F_SIMPLEX)) ? " simplex" : "",
1231 (device_get_unit(dev) == snd_unit) ? " default" : "");
1232
1233 if (verbose <= 1)
1234 return (0);
1235
1236 sbuf_printf(s, "\n\t");
1237 sbuf_printf(s, "snddev flags=0x%b", d->flags, SD_F_BITS);
1238
1239 CHN_FOREACH(c, d, channels.pcm) {
1240 KASSERT(c->bufhard != NULL && c->bufsoft != NULL,
1241 ("hosed pcm channel setup"));
1242
1243 sbuf_printf(s, "\n\t");
1244
1245 sbuf_printf(s, "%s[%s]: ",
1246 (c->parentchannel != NULL) ?
1247 c->parentchannel->name : "", c->name);
1248 sbuf_printf(s, "spd %d", c->speed);
1249 if (c->speed != sndbuf_getspd(c->bufhard)) {
1250 sbuf_printf(s, "/%d",
1251 sndbuf_getspd(c->bufhard));
1252 }
1253 sbuf_printf(s, ", fmt 0x%08x", c->format);
1254 if (c->format != sndbuf_getfmt(c->bufhard)) {
1255 sbuf_printf(s, "/0x%08x",
1256 sndbuf_getfmt(c->bufhard));
1257 }
1258 sbuf_printf(s, ", flags 0x%08x, 0x%08x",
1259 c->flags, c->feederflags);
1260 if (c->pid != -1) {
1261 sbuf_printf(s, ", pid %d (%s)",
1262 c->pid, c->comm);
1263 }
1264 sbuf_printf(s, "\n\t");
1265
1266 sbuf_printf(s, "interrupts %d, ", c->interrupts);
1267
1268 if (c->direction == PCMDIR_REC) {
1269 sbuf_printf(s,
1270 "overruns %d, feed %u, hfree %d, "
1271 "sfree %d [b:%d/%d/%d|bs:%d/%d/%d]",
1272 c->xruns, c->feedcount,
1273 sndbuf_getfree(c->bufhard),
1274 sndbuf_getfree(c->bufsoft),
1275 sndbuf_getsize(c->bufhard),
1276 sndbuf_getblksz(c->bufhard),
1277 sndbuf_getblkcnt(c->bufhard),
1278 sndbuf_getsize(c->bufsoft),
1279 sndbuf_getblksz(c->bufsoft),
1280 sndbuf_getblkcnt(c->bufsoft));
1281 } else {
1282 sbuf_printf(s,
1283 "underruns %d, feed %u, ready %d "
1284 "[b:%d/%d/%d|bs:%d/%d/%d]",
1285 c->xruns, c->feedcount,
1286 sndbuf_getready(c->bufsoft),
1287 sndbuf_getsize(c->bufhard),
1288 sndbuf_getblksz(c->bufhard),
1289 sndbuf_getblkcnt(c->bufhard),
1290 sndbuf_getsize(c->bufsoft),
1291 sndbuf_getblksz(c->bufsoft),
1292 sndbuf_getblkcnt(c->bufsoft));
1293 }
1294 sbuf_printf(s, "\n\t");
1295
1296 sbuf_printf(s, "channel flags=0x%b", c->flags, CHN_F_BITS);
1297 sbuf_printf(s, "\n\t");
1298
1299 sbuf_printf(s, "{%s}",
1300 (c->direction == PCMDIR_REC) ? "hardware" : "userland");
1301 sbuf_printf(s, " -> ");
1302 f = c->feeder;
1303 while (f->source != NULL)
1304 f = f->source;
1305 while (f != NULL) {
1306 sbuf_printf(s, "%s", f->class->name);
1307 if (f->desc->type == FEEDER_FORMAT) {
1308 sbuf_printf(s, "(0x%08x -> 0x%08x)",
1309 f->desc->in, f->desc->out);
1310 } else if (f->desc->type == FEEDER_MATRIX) {
1311 sbuf_printf(s, "(%d.%d -> %d.%d)",
1312 AFMT_CHANNEL(f->desc->in) -
1313 AFMT_EXTCHANNEL(f->desc->in),
1314 AFMT_EXTCHANNEL(f->desc->in),
1315 AFMT_CHANNEL(f->desc->out) -
1316 AFMT_EXTCHANNEL(f->desc->out),
1317 AFMT_EXTCHANNEL(f->desc->out));
1318 } else if (f->desc->type == FEEDER_RATE) {
1319 sbuf_printf(s,
1320 "(0x%08x q:%d %d -> %d)",
1321 f->desc->out,
1322 FEEDER_GET(f, FEEDRATE_QUALITY),
1323 FEEDER_GET(f, FEEDRATE_SRC),
1324 FEEDER_GET(f, FEEDRATE_DST));
1325 } else {
1326 sbuf_printf(s, "(0x%08x)",
1327 f->desc->out);
1328 }
1329 sbuf_printf(s, " -> ");
1330 f = f->parent;
1331 }
1332 sbuf_printf(s, "{%s}",
1333 (c->direction == PCMDIR_REC) ? "userland" : "hardware");
1334 }
1335
1336 return (0);
1337 }
1338
1339 static int
sndstat_prepare(struct sndstat_file * pf_self)1340 sndstat_prepare(struct sndstat_file *pf_self)
1341 {
1342 struct sbuf *s = &pf_self->sbuf;
1343 struct sndstat_entry *ent;
1344 struct snddev_info *d;
1345 struct sndstat_file *pf;
1346 int k;
1347
1348 /* make sure buffer is reset */
1349 sbuf_clear(s);
1350
1351 if (snd_verbose > 0)
1352 sbuf_printf(s, "FreeBSD Audio Driver\n");
1353
1354 /* generate list of installed devices */
1355 k = 0;
1356 TAILQ_FOREACH(ent, &sndstat_devlist, link) {
1357 d = device_get_softc(ent->dev);
1358 if (!PCM_REGISTERED(d))
1359 continue;
1360 if (!k++)
1361 sbuf_printf(s, "Installed devices:\n");
1362 sbuf_printf(s, "%s:", device_get_nameunit(ent->dev));
1363 sbuf_printf(s, " <%s>", device_get_desc(ent->dev));
1364 if (snd_verbose > 0)
1365 sbuf_printf(s, " %s", ent->str);
1366 /* XXX Need Giant magic entry ??? */
1367 PCM_ACQUIRE_QUICK(d);
1368 sndstat_prepare_pcm(s, ent->dev, snd_verbose);
1369 PCM_RELEASE_QUICK(d);
1370 sbuf_printf(s, "\n");
1371 }
1372 if (k == 0)
1373 sbuf_printf(s, "No devices installed.\n");
1374
1375 /* append any input from userspace */
1376 k = 0;
1377 TAILQ_FOREACH(pf, &sndstat_filelist, entry) {
1378 struct sndstat_userdev *ud;
1379
1380 if (pf == pf_self)
1381 continue;
1382 sx_xlock(&pf->lock);
1383 if (TAILQ_EMPTY(&pf->userdev_list)) {
1384 sx_unlock(&pf->lock);
1385 continue;
1386 }
1387 if (!k++)
1388 sbuf_printf(s, "Installed devices from userspace:\n");
1389 TAILQ_FOREACH(ud, &pf->userdev_list, link) {
1390 const char *caps = (ud->pchan && ud->rchan) ?
1391 "play/rec" :
1392 (ud->pchan ? "play" : (ud->rchan ? "rec" : ""));
1393 sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc);
1394 sbuf_printf(s, " (%s)", caps);
1395 sbuf_printf(s, "\n");
1396 }
1397 sx_unlock(&pf->lock);
1398 }
1399 if (k == 0)
1400 sbuf_printf(s, "No devices installed from userspace.\n");
1401
1402 sbuf_finish(s);
1403 return (sbuf_len(s));
1404 }
1405
1406 static void
sndstat_sysinit(void * p)1407 sndstat_sysinit(void *p)
1408 {
1409 sx_init(&sndstat_lock, "sndstat lock");
1410 sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS,
1411 UID_ROOT, GID_WHEEL, 0644, "sndstat");
1412 }
1413 SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL);
1414
1415 static void
sndstat_sysuninit(void * p)1416 sndstat_sysuninit(void *p)
1417 {
1418 if (sndstat_dev != NULL) {
1419 /* destroy_dev() will wait for all references to go away */
1420 destroy_dev(sndstat_dev);
1421 }
1422 sx_destroy(&sndstat_lock);
1423 }
1424 SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL);
1425