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