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