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