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