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