1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2024-2025 The FreeBSD Foundation
5 *
6 * This software was developed by Christos Margiolis <christos@FreeBSD.org>
7 * under sponsorship from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/nv.h>
32 #include <sys/queue.h>
33 #include <sys/sbuf.h>
34 #include <sys/sndstat.h>
35 #include <sys/soundcard.h>
36 #include <sys/sysctl.h>
37
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <libgen.h>
42 #include <limits.h>
43 #include <mixer.h>
44 #include <stddef.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <libxo/xo.h>
50
51 #define SNDCTL_XO_VERSION "1"
52
53 /* Taken from sys/dev/sound/pcm/ */
54 #define STATUS_LEN 64
55 #define FMTSTR_LEN 16
56
57 struct snd_chan {
58 char name[NAME_MAX];
59 char parentchan[NAME_MAX];
60 int unit;
61 #define INPUT 0
62 #define OUTPUT 1
63 int direction;
64 char caps[BUFSIZ];
65 int latency;
66 int rate;
67 char format[FMTSTR_LEN];
68 int pid;
69 char proc[NAME_MAX];
70 int interrupts;
71 int xruns;
72 int feedcount;
73 int volume;
74 struct {
75 char format[FMTSTR_LEN];
76 int rate;
77 int size_bytes;
78 int size_frames;
79 int blksz;
80 int blkcnt;
81 int free;
82 int ready;
83 } hwbuf, swbuf;
84 char feederchain[BUFSIZ];
85 struct snd_dev *dev;
86 TAILQ_ENTRY(snd_chan) next;
87 };
88
89 struct snd_dev {
90 char name[NAME_MAX];
91 char desc[NAME_MAX];
92 char status[BUFSIZ];
93 char devnode[NAME_MAX];
94 int from_user;
95 int unit;
96 char caps[BUFSIZ];
97 int bitperfect;
98 int realtime;
99 int autoconv;
100 int eq;
101 char eq_preamp[BUFSIZ];
102 struct {
103 char format[FMTSTR_LEN];
104 int rate;
105 int pchans;
106 int vchans;
107 int min_rate;
108 int max_rate;
109 int min_chans;
110 int max_chans;
111 char formats[BUFSIZ];
112 } play, rec;
113 TAILQ_HEAD(, snd_chan) chans;
114 };
115
116 struct snd_ctl {
117 const char *name;
118 size_t off;
119 #define STR 0
120 #define NUM 1
121 #define VOL 2
122 #define GRP 3
123 int type;
124 int (*mod)(struct snd_dev *, void *);
125 };
126
127 struct map {
128 int val;
129 const char *str;
130 };
131
132 static int mod_bitperfect(struct snd_dev *, void *);
133 static int mod_autoconv(struct snd_dev *, void *);
134 static int mod_realtime(struct snd_dev *, void *);
135 static int mod_eq(struct snd_dev *, void *);
136 static int mod_eq_preamp(struct snd_dev *, void *);
137 static int mod_play_vchans(struct snd_dev *, void *);
138 static int mod_play_rate(struct snd_dev *, void *);
139 static int mod_play_format(struct snd_dev *, void *);
140 static int mod_rec_vchans(struct snd_dev *, void *);
141 static int mod_rec_rate(struct snd_dev *, void *);
142 static int mod_rec_format(struct snd_dev *, void *);
143
144 static struct snd_ctl dev_ctls[] = {
145 #define F(member) offsetof(struct snd_dev, member)
146 { "name", F(name), STR, NULL },
147 { "desc", F(desc), STR, NULL },
148 { "status", F(status), STR, NULL },
149 { "devnode", F(devnode), STR, NULL },
150 { "from_user", F(from_user), NUM, NULL },
151 { "unit", F(unit), NUM, NULL },
152 { "caps", F(caps), STR, NULL },
153 { "bitperfect", F(bitperfect), NUM, mod_bitperfect },
154 { "autoconv", F(autoconv), NUM, mod_autoconv },
155 { "realtime", F(realtime), NUM, mod_realtime },
156 { "eq", F(eq), NUM, mod_eq },
157 { "eq_preamp", F(eq_preamp), STR, mod_eq_preamp },
158 { "play", F(play), GRP, NULL },
159 { "play.format", F(play.format), STR, mod_play_format },
160 { "play.rate", F(play.rate), NUM, mod_play_rate },
161 /*{ "play.pchans", F(play.pchans), NUM, NULL },*/
162 { "play.vchans", F(play.vchans), NUM, mod_play_vchans },
163 { "play.min_rate", F(play.min_rate), NUM, NULL },
164 { "play.max_rate", F(play.max_rate), NUM, NULL },
165 { "play.min_chans", F(play.min_chans), NUM, NULL },
166 { "play.max_chans", F(play.max_chans), NUM, NULL },
167 { "play.formats", F(play.formats), STR, NULL },
168 { "rec", F(rec), GRP, NULL },
169 { "rec.rate", F(rec.rate), NUM, mod_rec_rate },
170 { "rec.format", F(rec.format), STR, mod_rec_format },
171 /*{ "rec.pchans", F(rec.pchans), NUM, NULL },*/
172 { "rec.vchans", F(rec.vchans), NUM, mod_rec_vchans },
173 { "rec.min_rate", F(rec.min_rate), NUM, NULL },
174 { "rec.max_rate", F(rec.max_rate), NUM, NULL },
175 { "rec.min_chans", F(rec.min_chans), NUM, NULL },
176 { "rec.max_chans", F(rec.max_chans), NUM, NULL },
177 { "rec.formats", F(rec.formats), STR, NULL },
178 { NULL, 0, 0, NULL }
179 #undef F
180 };
181
182 static struct snd_ctl chan_ctls[] = {
183 #define F(member) offsetof(struct snd_chan, member)
184 { "name", F(name), STR, NULL },
185 { "parentchan", F(parentchan), STR, NULL },
186 { "unit", F(unit), NUM, NULL },
187 { "caps", F(caps), STR, NULL },
188 { "latency", F(latency), NUM, NULL },
189 { "rate", F(rate), NUM, NULL },
190 { "format", F(format), STR, NULL },
191 { "pid", F(pid), NUM, NULL },
192 { "proc", F(proc), STR, NULL },
193 { "interrupts", F(interrupts), NUM, NULL },
194 { "xruns", F(xruns), NUM, NULL },
195 { "feedcount", F(feedcount), NUM, NULL },
196 { "volume", F(volume), VOL, NULL },
197 { "hwbuf", F(hwbuf), GRP, NULL },
198 { "hwbuf.format", F(hwbuf.format), STR, NULL },
199 { "hwbuf.rate", F(hwbuf.rate), NUM, NULL },
200 { "hwbuf.size_bytes", F(hwbuf.size_bytes), NUM, NULL },
201 { "hwbuf.size_frames", F(hwbuf.size_frames), NUM, NULL },
202 { "hwbuf.blksz", F(hwbuf.blksz), NUM, NULL },
203 { "hwbuf.blkcnt", F(hwbuf.blkcnt), NUM, NULL },
204 { "hwbuf.free", F(hwbuf.free), NUM, NULL },
205 { "hwbuf.ready", F(hwbuf.ready), NUM, NULL },
206 { "swbuf", F(swbuf), GRP, NULL },
207 { "swbuf.format", F(swbuf.format), STR, NULL },
208 { "swbuf.rate", F(swbuf.rate), NUM, NULL },
209 { "swbuf.size_bytes", F(swbuf.size_bytes), NUM, NULL },
210 { "swbuf.size_frames", F(swbuf.size_frames), NUM, NULL },
211 { "swbuf.blksz", F(swbuf.blksz), NUM, NULL },
212 { "swbuf.blkcnt", F(swbuf.blkcnt), NUM, NULL },
213 { "swbuf.free", F(swbuf.free), NUM, NULL },
214 { "swbuf.ready", F(swbuf.ready), NUM, NULL },
215 { "feederchain", F(feederchain), STR, NULL },
216 { NULL, 0, 0, NULL }
217 #undef F
218 };
219
220 /*
221 * Taken from the OSSv4 manual. Not all of them are supported on FreeBSD
222 * however, and some of them are obsolete.
223 */
224 static struct map capmap[] = {
225 { PCM_CAP_ANALOGIN, "ANALOGIN" },
226 { PCM_CAP_ANALOGOUT, "ANALOGOUT" },
227 { PCM_CAP_BATCH, "BATCH" },
228 { PCM_CAP_BIND, "BIND" },
229 { PCM_CAP_COPROC, "COPROC" },
230 { PCM_CAP_DEFAULT, "DEFAULT" },
231 { PCM_CAP_DIGITALIN, "DIGITALIN" },
232 { PCM_CAP_DIGITALOUT, "DIGITALOUT" },
233 { PCM_CAP_DUPLEX, "DUPLEX" },
234 { PCM_CAP_FREERATE, "FREERATE" },
235 { PCM_CAP_HIDDEN, "HIDDEN" },
236 { PCM_CAP_INPUT, "INPUT" },
237 { PCM_CAP_MMAP, "MMAP" },
238 { PCM_CAP_MODEM, "MODEM" },
239 { PCM_CAP_MULTI, "MULTI" },
240 { PCM_CAP_OUTPUT, "OUTPUT" },
241 { PCM_CAP_REALTIME, "REALTIME" },
242 { PCM_CAP_REVISION, "REVISION" },
243 { PCM_CAP_SHADOW, "SHADOW" },
244 { PCM_CAP_SPECIAL, "SPECIAL" },
245 { PCM_CAP_TRIGGER, "TRIGGER" },
246 { PCM_CAP_VIRTUAL, "VIRTUAL" },
247 { 0, NULL }
248 };
249
250 static struct map fmtmap[] = {
251 { AFMT_A_LAW, "alaw" },
252 { AFMT_MU_LAW, "mulaw" },
253 { AFMT_S8, "s8" },
254 { AFMT_U8, "u8" },
255 { AFMT_AC3, "ac3" },
256 { AFMT_S16_LE, "s16le" },
257 { AFMT_S16_BE, "s16be" },
258 { AFMT_U16_LE, "u16le" },
259 { AFMT_U16_BE, "u16be" },
260 { AFMT_S24_LE, "s24le" },
261 { AFMT_S24_BE, "s24be" },
262 { AFMT_U24_LE, "u24le" },
263 { AFMT_U24_BE, "u24be" },
264 { AFMT_S32_LE, "s32le" },
265 { AFMT_S32_BE, "s32be" },
266 { AFMT_U32_LE, "u32le" },
267 { AFMT_U32_BE, "u32be" },
268 { AFMT_F32_LE, "f32le" },
269 { AFMT_F32_BE, "f32be" },
270 { 0, NULL }
271 };
272
273 static bool oflag = false;
274 static bool vflag = false;
275
276 static void
cap2str(char * buf,size_t size,int caps)277 cap2str(char *buf, size_t size, int caps)
278 {
279 struct map *p;
280
281 for (p = capmap; p->str != NULL; p++) {
282 if ((p->val & caps) == 0)
283 continue;
284 strlcat(buf, p->str, size);
285 strlcat(buf, ",", size);
286 }
287 if (*buf == '\0')
288 strlcpy(buf, "UNKNOWN", size);
289 else
290 buf[strlen(buf) - 1] = '\0';
291 }
292
293 static void
fmt2str(char * buf,size_t size,int fmt)294 fmt2str(char *buf, size_t size, int fmt)
295 {
296 struct map *p;
297 int enc, ch, ext;
298
299 enc = fmt & 0xf00fffff;
300 ch = (fmt & 0x07f00000) >> 20;
301 ext = (fmt & 0x08000000) >> 27;
302
303 for (p = fmtmap; p->str != NULL; p++) {
304 if ((p->val & enc) == 0)
305 continue;
306 strlcat(buf, p->str, size);
307 if (ch) {
308 snprintf(buf + strlen(buf), size,
309 ":%d.%d", ch - ext, ext);
310 }
311 strlcat(buf, ",", size);
312 }
313 if (*buf == '\0')
314 strlcpy(buf, "UNKNOWN", size);
315 else
316 buf[strlen(buf) - 1] = '\0';
317 }
318
319 static int
bytes2frames(int bytes,int fmt)320 bytes2frames(int bytes, int fmt)
321 {
322 int enc, ch, samplesz;
323
324 enc = fmt & 0xf00fffff;
325 ch = (fmt & 0x07f00000) >> 20;
326 /* Add the channel extension if present (e.g 2.1). */
327 ch += (fmt & 0x08000000) >> 27;
328
329 if (enc & (AFMT_S8 | AFMT_U8 | AFMT_MU_LAW | AFMT_A_LAW))
330 samplesz = 1;
331 else if (enc & (AFMT_S16_NE | AFMT_U16_NE))
332 samplesz = 2;
333 else if (enc & (AFMT_S24_NE | AFMT_U24_NE))
334 samplesz = 3;
335 else if (enc & (AFMT_S32_NE | AFMT_U32_NE | AFMT_F32_NE))
336 samplesz = 4;
337 else
338 samplesz = 0;
339
340 if (!samplesz || !ch)
341 return (-1);
342
343 return (bytes / (samplesz * ch));
344 }
345
346 static int
sysctl_int(const char * buf,const char * arg,int * var)347 sysctl_int(const char *buf, const char *arg, int *var)
348 {
349 size_t size;
350 int n, prev;
351
352 size = sizeof(int);
353 /* Read current value. */
354 if (sysctlbyname(buf, &prev, &size, NULL, 0) < 0) {
355 xo_warn("sysctlbyname(%s)", buf);
356 return (-1);
357 }
358
359 /* Read-only. */
360 if (arg != NULL) {
361 errno = 0;
362 n = strtol(arg, NULL, 10);
363 if (errno == EINVAL || errno == ERANGE) {
364 xo_warn("strtol(%s)", arg);
365 return (-1);
366 }
367
368 /* Apply new value. */
369 if (sysctlbyname(buf, NULL, 0, &n, size) < 0) {
370 xo_warn("sysctlbyname(%s, %d)", buf, n);
371 return (-1);
372 }
373 }
374
375 /* Read back applied value for good measure. */
376 if (sysctlbyname(buf, &n, &size, NULL, 0) < 0) {
377 xo_warn("sysctlbyname(%s)", buf);
378 return (-1);
379 }
380
381 if (arg != NULL && xo_get_style(NULL) == XO_STYLE_TEXT)
382 printf("%s: %d -> %d\n", buf, prev, n);
383 if (var != NULL)
384 *var = n;
385
386 return (0);
387 }
388
389 static int
sysctl_str(const char * buf,const char * arg,char * var,size_t varsz)390 sysctl_str(const char *buf, const char *arg, char *var, size_t varsz)
391 {
392 size_t size;
393 char prev[BUFSIZ];
394 char *tmp;
395
396 /* Read current value. */
397 size = sizeof(prev);
398 if (sysctlbyname(buf, prev, &size, NULL, 0) < 0) {
399 xo_warn("sysctlbyname(%s)", buf);
400 return (-1);
401 }
402
403 /* Read-only. */
404 if (arg != NULL) {
405 size = strlen(arg);
406 /* Apply new value. */
407 if (sysctlbyname(buf, NULL, 0, arg, size) < 0) {
408 xo_warn("sysctlbyname(%s, %s)", buf, arg);
409 return (-1);
410 }
411 /* Get size of new string. */
412 if (sysctlbyname(buf, NULL, &size, NULL, 0) < 0) {
413 xo_warn("sysctlbyname(%s)", buf);
414 return (-1);
415 }
416 }
417
418 if ((tmp = calloc(1, size)) == NULL)
419 xo_err(1, "calloc");
420 /* Read back applied value for good measure. */
421 if (sysctlbyname(buf, tmp, &size, NULL, 0) < 0) {
422 xo_warn("sysctlbyname(%s)", buf);
423 free(tmp);
424 return (-1);
425 }
426
427 if (arg != NULL && xo_get_style(NULL) == XO_STYLE_TEXT)
428 printf("%s: %s -> %s\n", buf, prev, tmp);
429 if (var != NULL)
430 strlcpy(var, tmp, varsz);
431 free(tmp);
432
433 return (0);
434 }
435
436 static struct snd_dev *
read_dev(char * path)437 read_dev(char *path)
438 {
439 nvlist_t *nvl;
440 const nvlist_t * const *di;
441 const nvlist_t * const *cdi;
442 struct sndstioc_nv_arg arg;
443 struct snd_dev *dp = NULL;
444 struct snd_chan *ch;
445 char buf[64];
446 size_t nitems, nchans, i, j;
447 int fd, caps, unit, t1, t2, t3;
448
449 if ((fd = open("/dev/sndstat", O_RDONLY)) < 0)
450 xo_err(1, "open(/dev/sndstat)");
451
452 if (ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL) < 0)
453 xo_err(1, "ioctl(SNDSTIOC_REFRESH_DEVS)");
454
455 arg.nbytes = 0;
456 arg.buf = NULL;
457 if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0)
458 xo_err(1, "ioctl(SNDSTIOC_GET_DEVS#1)");
459
460 if ((arg.buf = malloc(arg.nbytes)) == NULL)
461 xo_err(1, "malloc");
462
463 if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0)
464 xo_err(1, "ioctl(SNDSTIOC_GET_DEVS#2)");
465
466 if ((nvl = nvlist_unpack(arg.buf, arg.nbytes, 0)) == NULL)
467 xo_err(1, "nvlist_unpack");
468
469 if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS))
470 xo_errx(1, "no soundcards attached");
471
472 if (path == NULL || (path != NULL && strcmp(basename(path), "dsp") == 0))
473 unit = mixer_get_dunit();
474 else
475 unit = -1;
476
477 /* Find whether the requested device exists */
478 di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems);
479 for (i = 0; i < nitems; i++) {
480 if (unit == -1 && strcmp(basename(path),
481 nvlist_get_string(di[i], SNDST_DSPS_DEVNODE)) == 0)
482 break;
483 else if (nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO) &&
484 (int)nvlist_get_number(nvlist_get_nvlist(di[i],
485 SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_UNIT) == unit)
486 break;;
487 }
488 if (i == nitems)
489 xo_errx(1, "device not found");
490
491 #define NV(type, item) \
492 nvlist_get_ ## type (di[i], SNDST_DSPS_ ## item)
493 if ((dp = calloc(1, sizeof(struct snd_dev))) == NULL)
494 xo_err(1, "calloc");
495
496 dp->unit = -1;
497 strlcpy(dp->name, NV(string, NAMEUNIT), sizeof(dp->name));
498 strlcpy(dp->desc, NV(string, DESC), sizeof(dp->desc));
499 strlcpy(dp->devnode, NV(string, DEVNODE), sizeof(dp->devnode));
500 dp->from_user = NV(bool, FROM_USER);
501 dp->play.pchans = NV(number, PCHAN);
502 dp->rec.pchans = NV(number, RCHAN);
503 #undef NV
504
505 if (dp->play.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_PLAY))
506 xo_errx(1, "%s: playback channel list empty", dp->name);
507 if (dp->rec.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_REC))
508 xo_errx(1, "%s: recording channel list empty", dp->name);
509
510 #define NV(type, mode, item) \
511 nvlist_get_ ## type (nvlist_get_nvlist(di[i], \
512 SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item)
513 if (dp->play.pchans) {
514 dp->play.min_rate = NV(number, PLAY, MIN_RATE);
515 dp->play.max_rate = NV(number, PLAY, MAX_RATE);
516 dp->play.min_chans = NV(number, PLAY, MIN_CHN);
517 dp->play.max_chans = NV(number, PLAY, MAX_CHN);
518 fmt2str(dp->play.formats, sizeof(dp->play.formats),
519 NV(number, PLAY, FORMATS));
520 }
521 if (dp->rec.pchans) {
522 dp->rec.min_rate = NV(number, REC, MIN_RATE);
523 dp->rec.max_rate = NV(number, REC, MAX_RATE);
524 dp->rec.min_chans = NV(number, REC, MIN_CHN);
525 dp->rec.max_chans = NV(number, REC, MAX_CHN);
526 fmt2str(dp->rec.formats, sizeof(dp->rec.formats),
527 NV(number, REC, FORMATS));
528 }
529 #undef NV
530
531 /*
532 * Skip further parsing if the provider is not sound(4), as the
533 * following code is sound(4)-specific.
534 */
535 if (strcmp(nvlist_get_string(di[i], SNDST_DSPS_PROVIDER),
536 SNDST_DSPS_SOUND4_PROVIDER) != 0)
537 goto done;
538
539 if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO))
540 xo_errx(1, "%s: provider_info list empty", dp->name);
541
542 #define NV(type, item) \
543 nvlist_get_ ## type (nvlist_get_nvlist(di[i], \
544 SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item)
545 strlcpy(dp->status, NV(string, STATUS), sizeof(dp->status));
546 dp->unit = NV(number, UNIT);
547 dp->bitperfect = NV(bool, BITPERFECT);
548 dp->play.vchans = NV(bool, PVCHAN);
549 dp->play.rate = NV(number, PVCHANRATE);
550 fmt2str(dp->play.format, sizeof(dp->play.format),
551 NV(number, PVCHANFORMAT));
552 dp->rec.vchans = NV(bool, RVCHAN);
553 dp->rec.rate = NV(number, RVCHANRATE);
554 fmt2str(dp->rec.format, sizeof(dp->rec.format),
555 NV(number, RVCHANFORMAT));
556 #undef NV
557
558 dp->autoconv = (dp->play.vchans || dp->rec.vchans) && !dp->bitperfect;
559
560 if (sysctl_int("hw.snd.latency", NULL, &t1) ||
561 sysctl_int("hw.snd.latency_profile", NULL, &t2) ||
562 sysctl_int("kern.timecounter.alloweddeviation", NULL, &t3))
563 xo_err(1, "%s: sysctl", dp->name);
564 if (t1 == 0 && t2 == 0 && t3 == 0)
565 dp->realtime = 1;
566
567 snprintf(buf, sizeof(buf), "dev.pcm.%d.eq", dp->unit);
568 if (sysctl_int(buf, NULL, &dp->eq))
569 xo_err(1, "%s: sysctl", dp->name);
570
571 snprintf(buf, sizeof(buf), "dev.pcm.%d.eq_preamp", dp->unit);
572 if (sysctl_str(buf, NULL, dp->eq_preamp, sizeof(dp->eq_preamp)))
573 xo_err(1, "%s: sysctl", dp->name);
574
575 if (!nvlist_exists(nvlist_get_nvlist(di[i],
576 SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO))
577 xo_errx(1, "%s: channel info list empty", dp->name);
578
579 cdi = nvlist_get_nvlist_array(
580 nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO),
581 SNDST_DSPS_SOUND4_CHAN_INFO, &nchans);
582
583 TAILQ_INIT(&dp->chans);
584 caps = 0;
585 for (j = 0; j < nchans; j++) {
586 #define NV(type, item) \
587 nvlist_get_ ## type (cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item)
588 if ((ch = calloc(1, sizeof(struct snd_chan))) == NULL)
589 xo_err(1, "calloc");
590
591 strlcpy(ch->name, NV(string, NAME), sizeof(ch->name));
592 strlcpy(ch->parentchan, NV(string, PARENTCHAN),
593 sizeof(ch->parentchan));
594 ch->unit = NV(number, UNIT);
595 ch->direction = (NV(number, CAPS) & PCM_CAP_INPUT) ?
596 INPUT : OUTPUT;
597 cap2str(ch->caps, sizeof(ch->caps), NV(number, CAPS));
598 ch->latency = NV(number, LATENCY);
599 ch->rate = NV(number, RATE);
600 fmt2str(ch->format, sizeof(ch->format), NV(number, FORMAT));
601 ch->pid = NV(number, PID);
602 strlcpy(ch->proc, NV(string, COMM), sizeof(ch->proc));
603 ch->interrupts = NV(number, INTR);
604 ch->xruns = NV(number, XRUNS);
605 ch->feedcount = NV(number, FEEDCNT);
606 ch->volume = NV(number, LEFTVOL) |
607 NV(number, RIGHTVOL) << 8;
608 fmt2str(ch->hwbuf.format, sizeof(ch->hwbuf.format),
609 NV(number, HWBUF_FORMAT));
610 ch->hwbuf.rate = NV(number, HWBUF_RATE);
611 ch->hwbuf.size_bytes = NV(number, HWBUF_SIZE);
612 ch->hwbuf.size_frames =
613 bytes2frames(ch->hwbuf.size_bytes, NV(number, HWBUF_FORMAT));
614 ch->hwbuf.blksz = NV(number, HWBUF_BLKSZ);
615 ch->hwbuf.blkcnt = NV(number, HWBUF_BLKCNT);
616 ch->hwbuf.free = NV(number, HWBUF_FREE);
617 ch->hwbuf.ready = NV(number, HWBUF_READY);
618 fmt2str(ch->swbuf.format, sizeof(ch->swbuf.format),
619 NV(number, SWBUF_FORMAT));
620 ch->swbuf.rate = NV(number, SWBUF_RATE);
621 ch->swbuf.size_bytes = NV(number, SWBUF_SIZE);
622 ch->swbuf.size_frames =
623 bytes2frames(ch->swbuf.size_bytes, NV(number, SWBUF_FORMAT));
624 ch->swbuf.blksz = NV(number, SWBUF_BLKSZ);
625 ch->swbuf.blkcnt = NV(number, SWBUF_BLKCNT);
626 ch->swbuf.free = NV(number, SWBUF_FREE);
627 ch->swbuf.ready = NV(number, SWBUF_READY);
628 strlcpy(ch->feederchain, NV(string, FEEDERCHAIN),
629 sizeof(ch->feederchain));
630 ch->dev = dp;
631
632 caps |= NV(number, CAPS);
633 TAILQ_INSERT_TAIL(&dp->chans, ch, next);
634
635 if (!dp->rec.vchans && ch->direction == INPUT) {
636 strlcpy(dp->rec.format, ch->hwbuf.format,
637 sizeof(dp->rec.format));
638 dp->rec.rate = ch->hwbuf.rate;
639 } else if (!dp->play.vchans && ch->direction == OUTPUT) {
640 strlcpy(dp->play.format, ch->hwbuf.format,
641 sizeof(dp->play.format));
642 dp->play.rate = ch->hwbuf.rate;
643 }
644 #undef NV
645 }
646 cap2str(dp->caps, sizeof(dp->caps), caps);
647
648 done:
649 free(arg.buf);
650 nvlist_destroy(nvl);
651 close(fd);
652
653 return (dp);
654 }
655
656 static void
free_dev(struct snd_dev * dp)657 free_dev(struct snd_dev *dp)
658 {
659 struct snd_chan *ch;
660
661 while (!TAILQ_EMPTY(&dp->chans)) {
662 ch = TAILQ_FIRST(&dp->chans);
663 TAILQ_REMOVE(&dp->chans, ch, next);
664 free(ch);
665 }
666 free(dp);
667 }
668
669 static void
print_dev_ctl(struct snd_dev * dp,struct snd_ctl * ctl,bool simple,bool showgrp)670 print_dev_ctl(struct snd_dev *dp, struct snd_ctl *ctl, bool simple,
671 bool showgrp)
672 {
673 struct snd_ctl *cp;
674 size_t len;
675
676 if (ctl->type != GRP && xo_get_style(NULL) == XO_STYLE_TEXT) {
677 if (simple)
678 printf("%s=", ctl->name);
679 else
680 printf(" %-20s= ", ctl->name);
681 }
682
683 switch (ctl->type) {
684 case STR:
685 xo_emit("{a:%s/%s}\n", ctl->name, (char *)dp + ctl->off);
686 break;
687 case NUM:
688 xo_emit("{a:%s/%d}\n", ctl->name, *(int *)((intptr_t)dp + ctl->off));
689 break;
690 case VOL:
691 break;
692 case GRP:
693 if (!simple || !showgrp)
694 break;
695 for (cp = dev_ctls; cp->name != NULL; cp++) {
696 len = strlen(ctl->name);
697 if (strncmp(ctl->name, cp->name, len) == 0 &&
698 cp->name[len] == '.' && cp->type != GRP)
699 print_dev_ctl(dp, cp, simple, showgrp);
700 }
701 break;
702 }
703 }
704
705 static void
print_chan_ctl(struct snd_chan * ch,struct snd_ctl * ctl,bool simple,bool showgrp)706 print_chan_ctl(struct snd_chan *ch, struct snd_ctl *ctl, bool simple,
707 bool showgrp)
708 {
709 struct snd_ctl *cp;
710 size_t len;
711 int v;
712
713 if (ctl->type != GRP && xo_get_style(NULL) == XO_STYLE_TEXT) {
714 if (simple)
715 printf("%s.%s=", ch->name, ctl->name);
716 else
717 printf(" %-20s= ", ctl->name);
718 }
719
720 switch (ctl->type) {
721 case STR:
722 xo_emit("{a:%s/%s}\n", ctl->name, (char *)ch + ctl->off);
723 break;
724 case NUM:
725 xo_emit("{a:%s/%d}\n", ctl->name, *(int *)((intptr_t)ch + ctl->off));
726 break;
727 case VOL:
728 v = *(int *)((intptr_t)ch + ctl->off);
729 xo_emit("{a:%s/%.2f:%.2f}\n", ctl->name,
730 MIX_VOLNORM(v & 0x00ff), MIX_VOLNORM((v >> 8) & 0x00ff));
731 break;
732 case GRP:
733 if (!simple || !showgrp)
734 break;
735 for (cp = chan_ctls; cp->name != NULL; cp++) {
736 len = strlen(ctl->name);
737 if (strncmp(ctl->name, cp->name, len) == 0 &&
738 cp->name[len] == '.' && cp->type != GRP)
739 print_chan_ctl(ch, cp, simple, showgrp);
740 }
741 break;
742 }
743 }
744
745 static void
print_dev(struct snd_dev * dp)746 print_dev(struct snd_dev *dp)
747 {
748 struct snd_chan *ch;
749 struct snd_ctl *ctl;
750 struct sbuf sb;
751 char buf[16];
752
753 xo_open_instance("devices");
754
755 if (!oflag || xo_get_style(NULL) != XO_STYLE_TEXT) {
756 sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN);
757
758 sbuf_printf(&sb, "(");
759 if (dp->play.pchans)
760 sbuf_printf(&sb, "play");
761 if (dp->play.pchans && dp->rec.pchans)
762 sbuf_printf(&sb, "/");
763 if (dp->rec.pchans)
764 sbuf_printf(&sb, "rec");
765 sbuf_printf(&sb, ")");
766
767 xo_emit("{:header/%s: <%s> %s %s}\n",
768 dp->name, dp->desc, dp->status, sbuf_data(&sb));
769
770 sbuf_delete(&sb);
771 }
772
773 for (ctl = dev_ctls; ctl->name != NULL; ctl++)
774 print_dev_ctl(dp, ctl, oflag, false);
775
776 if (vflag) {
777 xo_open_list("channels");
778 TAILQ_FOREACH(ch, &dp->chans, next) {
779 xo_open_instance("channels");
780 if (!oflag && xo_get_style(NULL) == XO_STYLE_TEXT)
781 printf(" %s\n", ch->name);
782 for (ctl = chan_ctls; ctl->name != NULL; ctl++)
783 print_chan_ctl(ch, ctl, oflag, false);
784 xo_close_instance("channels");
785 }
786 xo_close_list("channels");
787 }
788
789 xo_close_instance("devices");
790 }
791
792 static int
mod_bitperfect(struct snd_dev * dp,void * arg)793 mod_bitperfect(struct snd_dev *dp, void *arg)
794 {
795 char buf[64];
796
797 if (dp->from_user)
798 return (-1);
799
800 snprintf(buf, sizeof(buf), "dev.pcm.%d.bitperfect", dp->unit);
801
802 return (sysctl_int(buf, arg, &dp->bitperfect));
803 }
804
805 static int
mod_autoconv(struct snd_dev * dp,void * arg)806 mod_autoconv(struct snd_dev *dp, void *arg)
807 {
808 const char *val = arg;
809 const char *zero = "0";
810 const char *one = "1";
811 int rc = -1;
812
813 if (dp->from_user)
814 return (rc);
815
816 if (strcmp(val, zero) == 0) {
817 rc = mod_play_vchans(dp, __DECONST(char *, zero)) ||
818 mod_rec_vchans(dp, __DECONST(char *, zero)) ||
819 mod_bitperfect(dp, __DECONST(char *, one));
820 if (rc == 0)
821 dp->autoconv = 0;
822 } else if (strcmp(val, one) == 0) {
823 rc = mod_play_vchans(dp, __DECONST(char *, one)) ||
824 mod_rec_vchans(dp, __DECONST(char *, one)) ||
825 mod_bitperfect(dp, __DECONST(char *, zero));
826 if (rc == 0)
827 dp->autoconv = 1;
828 }
829
830 return (rc);
831 }
832
833 static int
mod_realtime(struct snd_dev * dp,void * arg)834 mod_realtime(struct snd_dev *dp, void *arg)
835 {
836 const char *val = arg;
837 int rc = -1;
838
839 if (dp->from_user)
840 return (-1);
841
842 if (strcmp(val, "0") == 0) {
843 /* TODO */
844 rc = sysctl_int("hw.snd.latency", "2", NULL) ||
845 sysctl_int("hw.snd.latency_profile", "1", NULL) ||
846 sysctl_int("kern.timecounter.alloweddeviation", "5", NULL);
847 if (rc == 0)
848 dp->realtime = 0;
849 } else if (strcmp(val, "1") == 0) {
850 rc = sysctl_int("hw.snd.latency", "0", NULL) ||
851 sysctl_int("hw.snd.latency_profile", "0", NULL) ||
852 sysctl_int("kern.timecounter.alloweddeviation", "0", NULL);
853 if (rc == 0)
854 dp->realtime = 1;
855 }
856
857 return (rc);
858 }
859
860 static int
mod_eq(struct snd_dev * dp,void * arg)861 mod_eq(struct snd_dev *dp, void *arg)
862 {
863 char buf[64];
864
865 if (dp->from_user)
866 return (-1);
867
868 snprintf(buf, sizeof(buf), "dev.pcm.%d.eq", dp->unit);
869
870 return (sysctl_int(buf, arg, &dp->eq));
871 }
872
873 static int
mod_eq_preamp(struct snd_dev * dp,void * arg)874 mod_eq_preamp(struct snd_dev *dp, void *arg)
875 {
876 char buf[64];
877
878 if (dp->from_user)
879 return (-1);
880
881 snprintf(buf, sizeof(buf), "dev.pcm.%d.eq_preamp", dp->unit);
882
883 return (sysctl_str(buf, arg, dp->eq_preamp, sizeof(dp->eq_preamp)));
884 }
885
886 static int
mod_play_vchans(struct snd_dev * dp,void * arg)887 mod_play_vchans(struct snd_dev *dp, void *arg)
888 {
889 char buf[64];
890
891 if (dp->from_user)
892 return (-1);
893 if (!dp->play.pchans)
894 return (0);
895
896 snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchans", dp->unit);
897
898 return (sysctl_int(buf, arg, &dp->play.vchans));
899 }
900
901 static int
mod_play_rate(struct snd_dev * dp,void * arg)902 mod_play_rate(struct snd_dev *dp, void *arg)
903 {
904 char buf[64];
905
906 if (dp->from_user)
907 return (-1);
908 if (!dp->play.vchans)
909 return (0);
910
911 snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchanrate", dp->unit);
912
913 return (sysctl_int(buf, arg, &dp->play.rate));
914 }
915
916 static int
mod_play_format(struct snd_dev * dp,void * arg)917 mod_play_format(struct snd_dev *dp, void *arg)
918 {
919 char buf[64];
920
921 if (dp->from_user)
922 return (-1);
923 if (!dp->play.vchans)
924 return (0);
925
926 snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchanformat", dp->unit);
927
928 return (sysctl_str(buf, arg, dp->play.format, sizeof(dp->play.format)));
929 }
930
931 static int
mod_rec_vchans(struct snd_dev * dp,void * arg)932 mod_rec_vchans(struct snd_dev *dp, void *arg)
933 {
934 char buf[64];
935
936 if (dp->from_user)
937 return (-1);
938 if (!dp->rec.pchans)
939 return (0);
940
941 snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchans", dp->unit);
942
943 return (sysctl_int(buf, arg, &dp->rec.vchans));
944 }
945
946 static int
mod_rec_rate(struct snd_dev * dp,void * arg)947 mod_rec_rate(struct snd_dev *dp, void *arg)
948 {
949 char buf[64];
950
951 if (dp->from_user)
952 return (-1);
953 if (!dp->rec.vchans)
954 return (0);
955
956 snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchanrate", dp->unit);
957
958 return (sysctl_int(buf, arg, &dp->rec.rate));
959 }
960
961 static int
mod_rec_format(struct snd_dev * dp,void * arg)962 mod_rec_format(struct snd_dev *dp, void *arg)
963 {
964 char buf[64];
965
966 if (dp->from_user)
967 return (-1);
968 if (!dp->rec.vchans)
969 return (0);
970
971 snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchanformat", dp->unit);
972
973 return (sysctl_str(buf, arg, dp->rec.format, sizeof(dp->rec.format)));
974 }
975
976 static void __dead2
usage(void)977 usage(void)
978 {
979 xo_error("usage: %s [--libxo] [-f device] [-hov] [control[=value] ...]\n",
980 getprogname());
981 xo_finish();
982 exit(1);
983 }
984
985 int
main(int argc,char * argv[])986 main(int argc, char *argv[])
987 {
988 struct snd_dev *dp;
989 struct snd_chan *ch;
990 struct snd_ctl *ctl;
991 char *path = NULL;
992 char *s, *propstr;
993 bool show = true, found;
994 int c;
995
996 argc = xo_parse_args(argc, argv);
997 if (argc < 0)
998 exit(1);
999
1000 while ((c = getopt(argc, argv, "f:hov")) != -1) {
1001 switch (c) {
1002 case 'f':
1003 path = optarg;
1004 break;
1005 case 'o':
1006 oflag = true;
1007 break;
1008 case 'v':
1009 vflag = true;
1010 break;
1011 case 'h': /* FALLTHROUGH */
1012 case '?':
1013 default:
1014 usage();
1015 }
1016 }
1017 argc -= optind;
1018 argv += optind;
1019
1020 xo_set_version(SNDCTL_XO_VERSION);
1021 xo_open_container("sndctl");
1022
1023 dp = read_dev(path);
1024
1025 xo_open_container("executed_controls");
1026 while (argc > 0) {
1027 if ((s = strdup(*argv)) == NULL) {
1028 xo_close_container("executed_controls");
1029 xo_close_container("sndctl");
1030 if (xo_finish() < 0)
1031 xo_err(1, "xo_finish");
1032 xo_err(1, "strdup(%s)", *argv);
1033 }
1034
1035 propstr = strsep(&s, "=");
1036 if (propstr == NULL)
1037 goto next;
1038
1039 found = false;
1040 for (ctl = dev_ctls; ctl->name != NULL; ctl++) {
1041 if (strcmp(ctl->name, propstr) != 0)
1042 continue;
1043 if (s == NULL)
1044 show = false;
1045 else if (ctl->mod != NULL && ctl->mod(dp, s) < 0)
1046 xo_warnx("%s(%s) failed", ctl->name, s);
1047 if (s == NULL || xo_get_style(NULL) != XO_STYLE_TEXT) {
1048 /*
1049 * Print the control in libxo mode in all
1050 * cases, otherwise we'll not be printing any
1051 * controls that were modified or whose
1052 * ctl->mod() failed.
1053 */
1054 print_dev_ctl(dp, ctl, true, true);
1055 }
1056 found = true;
1057 break;
1058 }
1059 TAILQ_FOREACH(ch, &dp->chans, next) {
1060 for (ctl = chan_ctls; ctl->name != NULL; ctl++) {
1061 if (strcmp(ctl->name, propstr) != 0)
1062 continue;
1063 print_chan_ctl(ch, ctl, true, true);
1064 show = false;
1065 found = true;
1066 break;
1067 }
1068 }
1069 if (!found)
1070 xo_warnx("%s: no such property", propstr);
1071 next:
1072 free(s);
1073 argc--;
1074 argv++;
1075 }
1076 xo_close_container("executed_controls");
1077
1078 if (show || xo_get_style(NULL) != XO_STYLE_TEXT) {
1079 xo_open_list("devices");
1080 print_dev(dp);
1081 xo_close_list("devices");
1082 }
1083 free_dev(dp);
1084
1085
1086 xo_close_container("sndctl");
1087 if (xo_finish() < 0)
1088 xo_err(1, "xo_finish");
1089
1090 return (0);
1091 }
1092