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 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 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 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 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 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 * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 913 usage(void) 914 { 915 fprintf(stderr, "usage: %s [-f device] [-hov] [control[=value] ...]\n", 916 getprogname()); 917 exit(1); 918 } 919 920 int 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