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