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