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