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