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