xref: /freebsd/usr.sbin/sndctl/sndctl.c (revision 8b8dbc6bbe391fd95a19bd1d9f0aac47bac085f8)
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