1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <errno.h> 29 #include <string.h> 30 #include <strings.h> 31 #include <locale.h> 32 #include <libintl.h> 33 #include <stdarg.h> 34 #include <stddef.h> 35 #include <sys/types.h> 36 #include <sys/stat.h> 37 #include <sys/mkdev.h> 38 #include <fcntl.h> 39 #include <unistd.h> 40 #include <ctype.h> 41 #include <sys/param.h> 42 #include <sys/soundcard.h> 43 #include <libdevinfo.h> 44 45 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 46 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 47 #endif 48 49 #define _(s) gettext(s) 50 51 #define MAXLINE 1024 52 53 #define AUDIO_CTRL_STEREO_LEFT(v) ((uint8_t)((v) & 0xff)) 54 #define AUDIO_CTRL_STEREO_RIGHT(v) ((uint8_t)(((v) >> 8) & 0xff)) 55 #define AUDIO_CTRL_STEREO_VAL(l, r) (((l) & 0xff) | (((r) & 0xff) << 8)) 56 57 /* 58 * These are borrowed from sys/audio/audio_common.h, where the values 59 * are protected by _KERNEL. 60 */ 61 #define AUDIO_MN_TYPE_NBITS (4) 62 #define AUDIO_MN_TYPE_MASK ((1U << AUDIO_MN_TYPE_NBITS) - 1) 63 #define AUDIO_MINOR_MIXER (0) 64 65 66 /* 67 * Column display information 68 * All are related to the types enumerated in col_t and any change should be 69 * reflected in the corresponding indices and offsets for all the variables 70 * accordingly. Most tweaks to the display can be done by adjusting the 71 * values here. 72 */ 73 74 /* types of columns displayed */ 75 typedef enum { COL_DV = 0, COL_NM, COL_VAL, COL_SEL} col_t; 76 77 /* corresponding sizes of columns; does not include trailing null */ 78 #define COL_DV_SZ 16 79 #define COL_NM_SZ 24 80 #define COL_VAL_SZ 10 81 #define COL_SEL_SZ 20 82 #define COL_MAX_SZ 64 83 84 /* corresponding sizes of columns, indexed by col_t value */ 85 static int col_sz[] = { 86 COL_DV_SZ, COL_NM_SZ, COL_VAL_SZ, COL_SEL_SZ 87 }; 88 89 /* used by callers of the printing function */ 90 typedef struct col_prt { 91 char *col_dv; 92 char *col_nm; 93 char *col_val; 94 char *col_sel; 95 } col_prt_t; 96 97 /* columns displayed in order with vopt = 0 */ 98 static int col_dpy[] = {COL_NM, COL_VAL}; 99 static int col_dpy_len = sizeof (col_dpy) / sizeof (*col_dpy); 100 101 /* tells the printing function what members to use; follows col_dpy[] */ 102 static size_t col_dpy_prt[] = { 103 offsetof(col_prt_t, col_nm), 104 offsetof(col_prt_t, col_val), 105 }; 106 107 /* columns displayed in order with vopt = 1 */ 108 static int col_dpy_vopt[] = { COL_DV, COL_NM, COL_VAL, COL_SEL}; 109 static int col_dpy_vopt_len = sizeof (col_dpy_vopt) / sizeof (*col_dpy_vopt); 110 111 /* tells the printing function what members to use; follows col_dpy_vopt[] */ 112 static size_t col_dpy_prt_vopt[] = { 113 offsetof(col_prt_t, col_dv), 114 offsetof(col_prt_t, col_nm), 115 offsetof(col_prt_t, col_val), 116 offsetof(col_prt_t, col_sel) 117 }; 118 119 /* columns displayed in order with tofile = 1 */ 120 static int col_dpy_tofile[] = { COL_NM, COL_VAL}; 121 static int col_dpy_tofile_len = sizeof (col_dpy_tofile) / 122 sizeof (*col_dpy_tofile); 123 124 /* tells the printing function what members to use; follows col_dpy_tofile[] */ 125 static size_t col_dpy_prt_tofile[] = { 126 offsetof(col_prt_t, col_nm), 127 offsetof(col_prt_t, col_val) 128 }; 129 130 131 /* 132 * mixer and control accounting 133 */ 134 135 typedef struct cinfo { 136 oss_mixext ci; 137 oss_mixer_enuminfo *enump; 138 } cinfo_t; 139 140 typedef struct device { 141 oss_card_info card; 142 oss_mixerinfo mixer; 143 144 int cmax; 145 cinfo_t *controls; 146 147 int mfd; 148 dev_t devt; 149 150 struct device *nextp; 151 } device_t; 152 153 static device_t *devices = NULL; 154 155 /*PRINTFLIKE1*/ 156 static void 157 msg(char *fmt, ...) 158 { 159 va_list ap; 160 161 va_start(ap, fmt); 162 (void) vprintf(fmt, ap); 163 va_end(ap); 164 } 165 166 /*PRINTFLIKE1*/ 167 static void 168 warn(char *fmt, ...) 169 { 170 va_list ap; 171 172 va_start(ap, fmt); 173 (void) vfprintf(stderr, fmt, ap); 174 va_end(ap); 175 } 176 177 static void 178 free_device(device_t *d) 179 { 180 int i; 181 device_t **dpp; 182 183 dpp = &devices; 184 while ((*dpp) && ((*dpp) != d)) { 185 dpp = &((*dpp)->nextp); 186 } 187 if (*dpp) { 188 *dpp = d->nextp; 189 } 190 for (i = 0; i < d->cmax; i++) { 191 if (d->controls[i].enump != NULL) 192 free(d->controls[i].enump); 193 } 194 195 if (d->mfd >= 0) 196 (void) close(d->mfd); 197 198 free(d); 199 } 200 201 static void 202 free_devices(void) 203 { 204 device_t *d = devices; 205 206 while ((d = devices) != NULL) { 207 free_device(d); 208 } 209 210 devices = NULL; 211 } 212 213 214 /* 215 * adds to the end of global devices and returns a pointer to the new entry 216 */ 217 static device_t * 218 alloc_device(void) 219 { 220 device_t *p; 221 device_t *d = calloc(1, sizeof (*d)); 222 223 d->card.card = -1; 224 d->mixer.dev = -1; 225 d->mfd = -1; 226 227 if (devices == NULL) { 228 devices = d; 229 } else { 230 for (p = devices; p->nextp != NULL; p = p->nextp) {} 231 232 p->nextp = d; 233 } 234 return (d); 235 } 236 237 238 /* 239 * cinfop->enump needs to be present 240 * idx should be: >= 0 to < cinfop->ci.maxvalue 241 */ 242 static char * 243 get_enum_str(cinfo_t *cinfop, int idx) 244 { 245 int sz = sizeof (*cinfop->ci.enum_present) * 8; 246 247 if (cinfop->ci.enum_present[idx / sz] & (1 << (idx % sz))) 248 return (cinfop->enump->strings + cinfop->enump->strindex[idx]); 249 250 return (NULL); 251 } 252 253 254 /* 255 * caller fills in d->mixer.devnode; func fills in the rest 256 */ 257 static int 258 get_device_info(device_t *d) 259 { 260 int fd = -1; 261 int i; 262 cinfo_t *ci; 263 264 if ((fd = open(d->mixer.devnode, O_RDWR)) < 0) { 265 perror(_("Error opening device")); 266 return (errno); 267 } 268 d->mfd = fd; 269 270 d->cmax = -1; 271 if (ioctl(fd, SNDCTL_MIX_NREXT, &d->cmax) < 0) { 272 perror(_("Error getting control count")); 273 return (errno); 274 } 275 276 d->controls = calloc(d->cmax, sizeof (*d->controls)); 277 278 for (i = 0; i < d->cmax; i++) { 279 ci = &d->controls[i]; 280 281 ci->ci.dev = -1; 282 ci->ci.ctrl = i; 283 284 if (ioctl(fd, SNDCTL_MIX_EXTINFO, &ci->ci) < 0) { 285 perror(_("Error getting control info")); 286 return (errno); 287 } 288 289 if (ci->ci.type == MIXT_ENUM) { 290 ci->enump = calloc(1, sizeof (*ci->enump)); 291 ci->enump->dev = -1; 292 ci->enump->ctrl = ci->ci.ctrl; 293 294 if (ioctl(fd, SNDCTL_MIX_ENUMINFO, ci->enump) < 0) { 295 perror(_("Error getting enum info")); 296 return (errno); 297 } 298 } 299 } 300 301 return (0); 302 } 303 304 305 static int 306 load_devices(void) 307 { 308 int rv = -1; 309 int fd = -1; 310 int i; 311 oss_sysinfo si; 312 device_t *d; 313 314 if (devices != NULL) { 315 /* already loaded */ 316 return (0); 317 } 318 319 if ((fd = open("/dev/mixer", O_RDWR)) < 0) { 320 rv = errno; 321 warn(_("Error opening mixer\n")); 322 goto OUT; 323 } 324 325 if (ioctl(fd, SNDCTL_SYSINFO, &si) < 0) { 326 rv = errno; 327 perror(_("Error getting system information")); 328 goto OUT; 329 } 330 331 for (i = 0; i < si.nummixers; i++) { 332 333 struct stat sbuf; 334 335 d = alloc_device(); 336 d->mixer.dev = i; 337 338 if (ioctl(fd, SNDCTL_MIXERINFO, &d->mixer) != 0) { 339 continue; 340 } 341 342 d->card.card = d->mixer.card_number; 343 344 if ((ioctl(fd, SNDCTL_CARDINFO, &d->card) != 0) || 345 (stat(d->mixer.devnode, &sbuf) != 0) || 346 ((sbuf.st_mode & S_IFCHR) == 0)) { 347 warn(_("Device present: %s\n"), d->mixer.devnode); 348 free_device(d); 349 continue; 350 } 351 d->devt = makedev(major(sbuf.st_rdev), 352 minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK)); 353 354 if ((rv = get_device_info(d)) != 0) { 355 free_device(d); 356 goto OUT; 357 } 358 } 359 360 rv = 0; 361 362 OUT: 363 if (fd >= 0) 364 (void) close(fd); 365 return (rv); 366 } 367 368 369 static int 370 ctype_valid(int type) 371 { 372 switch (type) { 373 case MIXT_ONOFF: 374 case MIXT_ENUM: 375 case MIXT_MONOSLIDER: 376 case MIXT_STEREOSLIDER: 377 return (1); 378 default: 379 return (0); 380 } 381 } 382 383 384 static void 385 print_control_line(FILE *sfp, col_prt_t *colp, int vopt) 386 { 387 int i; 388 size_t *col_prtp; 389 int *col_dpyp; 390 int col_cnt; 391 int col_type; 392 int width; 393 char *colstr; 394 char cbuf[COL_MAX_SZ + 1]; 395 char line[128]; 396 char *colsep = " "; 397 398 if (sfp != NULL) { 399 col_prtp = col_dpy_prt_tofile; 400 col_dpyp = col_dpy_tofile; 401 col_cnt = col_dpy_tofile_len; 402 } else if (vopt) { 403 col_prtp = col_dpy_prt_vopt; 404 col_dpyp = col_dpy_vopt; 405 col_cnt = col_dpy_vopt_len; 406 } else { 407 col_prtp = col_dpy_prt; 408 col_dpyp = col_dpy; 409 col_cnt = col_dpy_len; 410 } 411 412 line[0] = '\0'; 413 414 for (i = 0; i < col_cnt; i++) { 415 col_type = col_dpyp[i]; 416 width = col_sz[col_type]; 417 colstr = *(char **)(((size_t)colp) + col_prtp[i]); 418 419 (void) snprintf(cbuf, sizeof (cbuf), "%- *s", 420 width > 0 ? width : 1, 421 (colstr == NULL) ? "" : colstr); 422 423 (void) strlcat(line, cbuf, sizeof (line)); 424 if (i < col_cnt - 1) 425 (void) strlcat(line, colsep, sizeof (line)); 426 } 427 428 (void) fprintf(sfp ? sfp : stdout, "%s\n", line); 429 } 430 431 432 static void 433 print_header(FILE *sfp, int vopt) 434 { 435 col_prt_t col; 436 437 if (sfp) { 438 col.col_nm = _("#CONTROL"); 439 col.col_val = _("VALUE"); 440 } else { 441 col.col_dv = _("DEVICE"); 442 col.col_nm = _("CONTROL"); 443 col.col_val = _("VALUE"); 444 col.col_sel = _("POSSIBLE"); 445 } 446 print_control_line(sfp, &col, vopt); 447 } 448 449 450 static int 451 print_control(FILE *sfp, device_t *d, cinfo_t *cinfop, int vopt) 452 { 453 int mfd = d->mfd; 454 char *devnm = d->card.shortname; 455 oss_mixer_value cval; 456 char *str; 457 int i; 458 int idx = -1; 459 int rv = -1; 460 char valbuf[COL_VAL_SZ + 1]; 461 char selbuf[COL_SEL_SZ + 1]; 462 col_prt_t col; 463 464 cval.dev = -1; 465 cval.ctrl = cinfop->ci.ctrl; 466 467 if (ctype_valid(cinfop->ci.type)) { 468 if (ioctl(mfd, SNDCTL_MIX_READ, &cval) < 0) { 469 rv = errno; 470 perror(_("Error reading control\n")); 471 return (rv); 472 } 473 } else { 474 return (0); 475 } 476 477 /* 478 * convert the control value into a string 479 */ 480 switch (cinfop->ci.type) { 481 case MIXT_ONOFF: 482 (void) snprintf(valbuf, sizeof (valbuf), "%s", 483 cval.value ? _("on") : _("off")); 484 break; 485 486 case MIXT_MONOSLIDER: 487 (void) snprintf(valbuf, sizeof (valbuf), "%d", 488 cval.value & 0xff); 489 break; 490 491 case MIXT_STEREOSLIDER: 492 (void) snprintf(valbuf, sizeof (valbuf), "%d:%d", 493 (int)AUDIO_CTRL_STEREO_LEFT(cval.value), 494 (int)AUDIO_CTRL_STEREO_RIGHT(cval.value)); 495 break; 496 497 case MIXT_ENUM: 498 str = get_enum_str(cinfop, cval.value); 499 if (str == NULL) { 500 warn(_("Bad enum index %d for control '%s'\n"), 501 cval.value, cinfop->ci.extname); 502 /* 503 * Apparently the driver gave us bogus data for this 504 * control, so we will ignore it. 505 * 506 * Don't confuse the user by returning an error status. 507 */ 508 return (0); 509 } 510 511 (void) snprintf(valbuf, sizeof (valbuf), "%s", str); 512 break; 513 514 default: 515 return (0); 516 } 517 518 /* 519 * possible control values (range/selection) 520 */ 521 switch (cinfop->ci.type) { 522 case MIXT_ONOFF: 523 (void) snprintf(selbuf, sizeof (selbuf), _("on,off")); 524 break; 525 526 case MIXT_MONOSLIDER: 527 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d", 528 cinfop->ci.minvalue, cinfop->ci.maxvalue); 529 break; 530 case MIXT_STEREOSLIDER: 531 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d:%d-%d", 532 cinfop->ci.minvalue, cinfop->ci.maxvalue, 533 cinfop->ci.minvalue, cinfop->ci.maxvalue); 534 break; 535 536 case MIXT_ENUM: 537 /* 538 * display the first choice on the same line, then display 539 * the rest on multiple lines 540 */ 541 selbuf[0] = 0; 542 for (i = 0; i < cinfop->ci.maxvalue; i++) { 543 str = get_enum_str(cinfop, i); 544 if (str == NULL) 545 continue; 546 547 if ((strlen(str) + 1 + strlen(selbuf)) >= 548 sizeof (selbuf)) { 549 break; 550 } 551 if (strlen(selbuf)) { 552 (void) strlcat(selbuf, ",", sizeof (selbuf)); 553 } 554 555 (void) strlcat(selbuf, str, sizeof (selbuf)); 556 } 557 idx = i; 558 break; 559 560 default: 561 (void) snprintf(selbuf, sizeof (selbuf), "-"); 562 } 563 564 col.col_dv = devnm; 565 col.col_nm = strlen(cinfop->ci.extname) ? 566 cinfop->ci.extname : cinfop->ci.id; 567 while (strchr(col.col_nm, '_') != NULL) { 568 col.col_nm = strchr(col.col_nm, '_') + 1; 569 } 570 col.col_val = valbuf; 571 col.col_sel = selbuf; 572 print_control_line(sfp, &col, vopt); 573 574 /* non-verbose mode prints don't display the enum values */ 575 if ((!vopt) || (sfp != NULL)) { 576 return (0); 577 } 578 579 /* print leftover enum value selections */ 580 while ((idx >= 0) && (idx < cinfop->ci.maxvalue)) { 581 selbuf[0] = 0; 582 for (i = idx; i < cinfop->ci.maxvalue; i++) { 583 str = get_enum_str(cinfop, i); 584 if (str == NULL) 585 continue; 586 587 if ((strlen(str) + 1 + strlen(selbuf)) >= 588 sizeof (selbuf)) { 589 break; 590 } 591 if (strlen(selbuf)) { 592 (void) strlcat(selbuf, ",", sizeof (selbuf)); 593 } 594 595 (void) strlcat(selbuf, str, sizeof (selbuf)); 596 } 597 idx = i; 598 col.col_dv = NULL; 599 col.col_nm = NULL; 600 col.col_val = NULL; 601 col.col_sel = selbuf; 602 print_control_line(sfp, &col, vopt); 603 } 604 605 return (0); 606 } 607 608 609 static int 610 set_device_control(device_t *d, cinfo_t *cinfop, char *wstr, int vopt) 611 { 612 int mfd = d->mfd; 613 oss_mixer_value cval; 614 int wlen = strlen(wstr); 615 int lval, rval; 616 char *lstr, *rstr; 617 char *str; 618 int i; 619 int rv = -1; 620 621 cval.dev = -1; 622 cval.ctrl = cinfop->ci.ctrl; 623 cval.value = 0; 624 625 switch (cinfop->ci.type) { 626 case MIXT_ONOFF: 627 cval.value = (strncmp(_("on"), wstr, wlen) == 0) ? 1 : 0; 628 break; 629 630 case MIXT_MONOSLIDER: 631 cval.value = atoi(wstr); 632 break; 633 634 case MIXT_STEREOSLIDER: 635 lstr = wstr; 636 rstr = strchr(wstr, ':'); 637 if (rstr != NULL) { 638 *rstr = '\0'; 639 rstr++; 640 641 rval = atoi(rstr); 642 lval = atoi(lstr); 643 644 rstr--; 645 *rstr = ':'; 646 } else { 647 lval = atoi(lstr); 648 rval = lval; 649 } 650 651 cval.value = AUDIO_CTRL_STEREO_VAL(lval, rval); 652 break; 653 654 case MIXT_ENUM: 655 for (i = 0; i < cinfop->ci.maxvalue; i++) { 656 str = get_enum_str(cinfop, i); 657 if (str == NULL) 658 continue; 659 660 if (strncmp(wstr, str, wlen) == 0) { 661 cval.value = i; 662 break; 663 } 664 } 665 666 if (i >= cinfop->ci.maxvalue) { 667 warn(_("Invalid enumeration value\n")); 668 return (EINVAL); 669 } 670 break; 671 672 default: 673 warn(_("Unsupported control type: %d\n"), cinfop->ci.type); 674 return (EINVAL); 675 } 676 677 if (vopt) { 678 msg(_("%s: '%s' set to '%s'\n"), d->card.shortname, 679 cinfop->ci.extname, wstr); 680 } 681 682 if (ioctl(mfd, SNDCTL_MIX_WRITE, &cval) < 0) { 683 rv = errno; 684 perror(_("Error writing control")); 685 return (rv); 686 } 687 688 rv = 0; 689 return (rv); 690 } 691 692 693 static void 694 help(void) 695 { 696 #define HELP_STR _( \ 697 "audioctl list-devices\n" \ 698 " list all audio devices\n" \ 699 "\n" \ 700 "audioctl show-device [ -v ] [ -d <device> ]\n" \ 701 " display information about an audio device\n" \ 702 "\n" \ 703 "audioctl show-control [ -v ] [ -d <device> ] [ <control> ... ]\n" \ 704 " get the value of a specific control (all if not specified)\n" \ 705 "\n" \ 706 "audioctl set-control [ -v ] [ -d <device> ] <control> <value>\n" \ 707 " set the value of a specific control\n" \ 708 "\n" \ 709 "audioctl save-controls [ -d <device> ] [ -f ] <file>\n" \ 710 " save all control settings for the device to a file\n" \ 711 "\n" \ 712 "audioctl load-controls [ -d <device> ] <file>\n" \ 713 " restore previously saved control settings to device\n" \ 714 "\n" \ 715 "audioctl help\n" \ 716 " show this message.\n") 717 718 (void) fprintf(stderr, HELP_STR); 719 } 720 721 dev_t 722 device_devt(char *name) 723 { 724 struct stat sbuf; 725 726 if ((stat(name, &sbuf) != 0) || 727 ((sbuf.st_mode & S_IFCHR) == 0)) { 728 /* Not a device node! */ 729 return (0); 730 } 731 732 return (makedev(major(sbuf.st_rdev), 733 minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK))); 734 } 735 736 static device_t * 737 find_device(char *name) 738 { 739 dev_t devt; 740 device_t *d; 741 742 /* 743 * User may have specified: 744 * 745 * /dev/dsp[<num>] 746 * /dev/mixer[<num>] 747 * /dev/audio[<num>9] 748 * /dev/audioctl[<num>] 749 * /dev/sound/<num>{,ctl,dsp,mixer} 750 * /dev/sound/<driver>:<num>{,ctl,dsp,mixer} 751 * 752 * We can canonicalize these by looking at the dev_t though. 753 */ 754 755 if (load_devices() != 0) { 756 return (NULL); 757 } 758 759 if (name == NULL) 760 name = getenv("AUDIODEV"); 761 762 if ((name == NULL) || 763 (strcmp(name, "/dev/mixer") == 0)) { 764 /* /dev/mixer node doesn't point to real hw */ 765 name = "/dev/dsp"; 766 } 767 768 if (*name == '/') { 769 /* if we have a full path, convert to the devt */ 770 if ((devt = device_devt(name)) == 0) { 771 warn(_("No such audio device.\n")); 772 return (NULL); 773 } 774 name = NULL; 775 } 776 777 for (d = devices; d != NULL; d = d->nextp) { 778 oss_card_info *card = &d->card; 779 780 if ((name) && (strcmp(name, card->shortname) == 0)) { 781 return (d); 782 } 783 if (devt == d->devt) { 784 return (d); 785 } 786 } 787 788 warn(_("No such audio device.\n")); 789 return (NULL); 790 } 791 792 int 793 do_list_devices(int argc, char **argv) 794 { 795 int optc; 796 int verbose = 0; 797 device_t *d; 798 799 while ((optc = getopt(argc, argv, "v")) != EOF) { 800 switch (optc) { 801 case 'v': 802 verbose++; 803 break; 804 default: 805 help(); 806 return (-1); 807 } 808 } 809 argc -= optind; 810 argv += optind; 811 if (argc != 0) { 812 help(); 813 return (-1); 814 } 815 816 if (load_devices() != 0) { 817 return (-1); 818 } 819 820 for (d = devices; d != NULL; d = d->nextp) { 821 822 if ((d->mixer.enabled == 0) && (!verbose)) 823 continue; 824 825 if (verbose) { 826 msg(_("%s (%s)\n"), d->card.shortname, 827 d->mixer.devnode); 828 } else { 829 msg(_("%s\n"), d->card.shortname); 830 } 831 } 832 833 return (0); 834 } 835 836 int 837 do_show_device(int argc, char **argv) 838 { 839 int optc; 840 char *devname = NULL; 841 char *p, *n; 842 const char *m; 843 device_t *d; 844 845 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 846 switch (optc) { 847 case 'd': 848 devname = optarg; 849 break; 850 case 'v': 851 break; 852 default: 853 help(); 854 return (-1); 855 } 856 } 857 argc -= optind; 858 argv += optind; 859 if (argc != 0) { 860 help(); 861 return (-1); 862 } 863 864 if ((d = find_device(devname)) == NULL) { 865 return (ENODEV); 866 } 867 868 msg(_("Device: %s\n"), d->mixer.devnode); 869 msg(_(" Name = %s\n"), d->card.shortname); 870 msg(_(" Config = %s\n"), d->card.longname); 871 872 for (m = " HW Info = %s\n", p = d->card.hw_info; 873 p != NULL && strlen(p) != 0; 874 m = "\t %s\n", p = n) { 875 if ((n = strchr(p, '\n')) != NULL) 876 *n++ = '\0'; 877 msg(_(m), p); 878 } 879 880 return (0); 881 } 882 883 int 884 do_show_control(int argc, char **argv) 885 { 886 int optc; 887 int rval = 0; 888 int verbose = 0; 889 device_t *d; 890 char *devname = NULL; 891 int i; 892 int j; 893 int rv; 894 char *n; 895 cinfo_t *cinfop; 896 897 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 898 switch (optc) { 899 case 'd': 900 devname = optarg; 901 break; 902 case 'v': 903 verbose++; 904 break; 905 default: 906 help(); 907 return (-1); 908 } 909 } 910 argc -= optind; 911 argv += optind; 912 913 if ((d = find_device(devname)) == NULL) { 914 return (ENODEV); 915 } 916 917 print_header(NULL, verbose); 918 if (argc == 0) { 919 /* do them all! */ 920 for (i = 0; i < d->cmax; i++) { 921 922 cinfop = &d->controls[i]; 923 rv = print_control(NULL, d, cinfop, verbose); 924 rval = rval ? rval : rv; 925 } 926 return (rval); 927 } 928 929 for (i = 0; i < argc; i++) { 930 for (j = 0; j < d->cmax; j++) { 931 cinfop = &d->controls[j]; 932 n = strrchr(cinfop->ci.extname, '_'); 933 n = n ? n + 1 : cinfop->ci.extname; 934 if (strcmp(argv[i], n) == 0) { 935 rv = print_control(NULL, d, cinfop, verbose); 936 rval = rval ? rval : rv; 937 break; 938 } 939 } 940 /* Didn't find requested control */ 941 if (j == d->cmax) { 942 warn(_("No such control: %s\n"), argv[i]); 943 rval = rval ? rval : ENODEV; 944 } 945 } 946 947 return (rval); 948 } 949 950 int 951 do_set_control(int argc, char **argv) 952 { 953 int optc; 954 int rval = 0; 955 int verbose = 0; 956 device_t *d; 957 char *devname = NULL; 958 char *cname; 959 char *value; 960 int i; 961 int found; 962 int rv; 963 char *n; 964 cinfo_t *cinfop; 965 966 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 967 switch (optc) { 968 case 'd': 969 devname = optarg; 970 break; 971 case 'v': 972 verbose = 1; 973 break; 974 default: 975 help(); 976 return (-1); 977 } 978 } 979 argc -= optind; 980 argv += optind; 981 982 if (argc != 2) { 983 help(); 984 return (-1); 985 } 986 cname = argv[0]; 987 value = argv[1]; 988 989 if ((d = find_device(devname)) == NULL) { 990 return (ENODEV); 991 } 992 993 for (i = 0, found = 0; i < d->cmax; i++) { 994 cinfop = &d->controls[i]; 995 n = strrchr(cinfop->ci.extname, '_'); 996 n = n ? n + 1 : cinfop->ci.extname; 997 if (strcmp(cname, n) != 0) { 998 continue; 999 } 1000 found = 1; 1001 rv = set_device_control(d, cinfop, value, verbose); 1002 rval = rval ? rval : rv; 1003 } 1004 if (!found) { 1005 warn(_("No such control: %s\n"), cname); 1006 } 1007 1008 return (rval); 1009 } 1010 1011 int 1012 do_save_controls(int argc, char **argv) 1013 { 1014 int optc; 1015 int rval = 0; 1016 device_t *d; 1017 char *devname = NULL; 1018 char *fname; 1019 int i; 1020 int rv; 1021 cinfo_t *cinfop; 1022 FILE *fp; 1023 int fd; 1024 int mode; 1025 char *p, *n; 1026 const char *m; 1027 1028 mode = O_WRONLY | O_CREAT | O_EXCL; 1029 1030 while ((optc = getopt(argc, argv, "d:f")) != EOF) { 1031 switch (optc) { 1032 case 'd': 1033 devname = optarg; 1034 break; 1035 case 'f': 1036 mode &= ~O_EXCL; 1037 mode |= O_TRUNC; 1038 break; 1039 default: 1040 help(); 1041 return (-1); 1042 } 1043 } 1044 argc -= optind; 1045 argv += optind; 1046 1047 if (argc != 1) { 1048 help(); 1049 return (-1); 1050 } 1051 fname = argv[0]; 1052 1053 if ((d = find_device(devname)) == NULL) { 1054 return (ENODEV); 1055 } 1056 1057 if ((fd = open(fname, mode, 0666)) < 0) { 1058 perror(_("Failed to create file")); 1059 return (errno); 1060 } 1061 1062 if ((fp = fdopen(fd, "w")) == NULL) { 1063 perror(_("Unable to open file\n")); 1064 (void) close(fd); 1065 (void) unlink(fname); 1066 return (errno); 1067 } 1068 1069 (void) fprintf(fp, "# Device: %s\n", d->mixer.devnode); 1070 (void) fprintf(fp, "# Name = %s\n", d->card.shortname); 1071 (void) fprintf(fp, "# Config = %s\n", d->card.longname); 1072 1073 for (m = "# HW Info = %s\n", p = d->card.hw_info; 1074 p != NULL && strlen(p) != 0; 1075 m = "#\t %s\n", p = n) { 1076 if ((n = strchr(p, '\n')) != NULL) 1077 *n++ = '\0'; 1078 fprintf(fp, m, p); 1079 } 1080 (void) fprintf(fp, "#\n"); 1081 1082 print_header(fp, 0); 1083 1084 for (i = 0; i < d->cmax; i++) { 1085 cinfop = &d->controls[i]; 1086 rv = print_control(fp, d, cinfop, 0); 1087 rval = rval ? rval : rv; 1088 } 1089 1090 (void) fclose(fp); 1091 1092 return (rval); 1093 } 1094 1095 int 1096 do_load_controls(int argc, char **argv) 1097 { 1098 int optc; 1099 int rval = 0; 1100 device_t *d; 1101 char *devname = NULL; 1102 char *fname; 1103 char *cname; 1104 char *value; 1105 int i; 1106 int rv; 1107 cinfo_t *cinfop; 1108 FILE *fp; 1109 char linebuf[MAXLINE]; 1110 int lineno = 0; 1111 int found; 1112 1113 while ((optc = getopt(argc, argv, "d:")) != EOF) { 1114 switch (optc) { 1115 case 'd': 1116 devname = optarg; 1117 break; 1118 default: 1119 help(); 1120 return (-1); 1121 } 1122 } 1123 argc -= optind; 1124 argv += optind; 1125 1126 if (argc != 1) { 1127 help(); 1128 return (-1); 1129 } 1130 fname = argv[0]; 1131 1132 if ((d = find_device(devname)) == NULL) { 1133 return (ENODEV); 1134 } 1135 1136 if ((fp = fopen(fname, "r")) == NULL) { 1137 perror(_("Unable to open file")); 1138 return (errno); 1139 } 1140 1141 while (fgets(linebuf, sizeof (linebuf), fp) != NULL) { 1142 lineno++; 1143 if (linebuf[strlen(linebuf) - 1] != '\n') { 1144 warn(_("Warning: line too long at line %d\n"), lineno); 1145 /* read in the rest of the line and discard it */ 1146 while (fgets(linebuf, sizeof (linebuf), fp) != NULL && 1147 (linebuf[strlen(linebuf) - 1] != '\n')) { 1148 continue; 1149 } 1150 continue; 1151 } 1152 1153 /* we have a good line ... */ 1154 cname = strtok(linebuf, " \t\n"); 1155 /* skip comments and blank lines */ 1156 if ((cname == NULL) || (cname[0] == '#')) { 1157 continue; 1158 } 1159 value = strtok(NULL, " \t\n"); 1160 if ((value == NULL) || (*cname == 0)) { 1161 warn(_("Warning: missing value at line %d\n"), lineno); 1162 continue; 1163 } 1164 1165 for (i = 0, found = 0; i < d->cmax; i++) { 1166 /* save and restore requires an exact match */ 1167 cinfop = &d->controls[i]; 1168 if (strcmp(cinfop->ci.extname, cname) != 0) { 1169 continue; 1170 } 1171 found = 1; 1172 rv = set_device_control(d, cinfop, value, 0); 1173 rval = rval ? rval : rv; 1174 } 1175 if (!found) { 1176 warn(_("No such control: %s\n"), cname); 1177 } 1178 } 1179 (void) fclose(fp); 1180 1181 return (rval); 1182 } 1183 1184 int 1185 mixer_walker(di_devlink_t dlink, void *arg) 1186 { 1187 const char *link; 1188 int num; 1189 int fd; 1190 int verbose = *(int *)arg; 1191 int num_offset; 1192 1193 num_offset = sizeof ("/dev/mixer") - 1; 1194 1195 link = di_devlink_path(dlink); 1196 1197 if ((link == NULL) || 1198 (strncmp(link, "/dev/mixer", num_offset) != 0) || 1199 (!isdigit(link[num_offset]))) { 1200 return (DI_WALK_CONTINUE); 1201 } 1202 1203 num = atoi(link + num_offset); 1204 if ((fd = open(link, O_RDWR)) < 0) { 1205 if (verbose) { 1206 if (errno == ENOENT) { 1207 msg(_("Device %s not present.\n"), link); 1208 } else { 1209 msg(_("Unable to open device %s: %s\n"), 1210 link, strerror(errno)); 1211 } 1212 } 1213 return (DI_WALK_CONTINUE); 1214 } 1215 1216 if (verbose) { 1217 msg(_("Initializing link %s: "), link); 1218 } 1219 if (ioctl(fd, SNDCTL_SUN_SEND_NUMBER, &num) != 0) { 1220 if (verbose) { 1221 msg(_("failed: %s\n"), strerror(errno)); 1222 } 1223 } else { 1224 if (verbose) { 1225 msg(_("done.\n")); 1226 } 1227 } 1228 (void) close(fd); 1229 return (DI_WALK_CONTINUE); 1230 } 1231 1232 int 1233 do_init_devices(int argc, char **argv) 1234 { 1235 int optc; 1236 di_devlink_handle_t dlh; 1237 int verbose = 0; 1238 1239 while ((optc = getopt(argc, argv, "v")) != EOF) { 1240 switch (optc) { 1241 case 'v': 1242 verbose = 1; 1243 break; 1244 default: 1245 help(); 1246 return (-1); 1247 } 1248 } 1249 argc -= optind; 1250 argv += optind; 1251 1252 if (argc != 0) { 1253 help(); 1254 return (-1); 1255 } 1256 1257 dlh = di_devlink_init(NULL, 0); 1258 if (dlh == NULL) { 1259 perror(_("Unable to initialize devlink handle")); 1260 return (-1); 1261 } 1262 1263 if (di_devlink_walk(dlh, "^mixer", NULL, 0, &verbose, 1264 mixer_walker) != 0) { 1265 perror(_("Unable to walk devlinks")); 1266 return (-1); 1267 } 1268 return (0); 1269 } 1270 1271 int 1272 main(int argc, char **argv) 1273 { 1274 int rv = 0; 1275 int opt; 1276 1277 (void) setlocale(LC_ALL, ""); 1278 (void) textdomain(TEXT_DOMAIN); 1279 1280 while ((opt = getopt(argc, argv, "h")) != EOF) { 1281 switch (opt) { 1282 case 'h': 1283 help(); 1284 rv = 0; 1285 goto OUT; 1286 default: 1287 rv = EINVAL; 1288 break; 1289 } 1290 } 1291 1292 if (rv) { 1293 goto OUT; 1294 } 1295 1296 argc -= optind; 1297 argv += optind; 1298 1299 if (argc < 1) { 1300 help(); 1301 rv = EINVAL; 1302 } else if (strcmp(argv[0], "help") == 0) { 1303 help(); 1304 rv = 0; 1305 } else if (strcmp(argv[0], "list-devices") == 0) { 1306 rv = do_list_devices(argc, argv); 1307 } else if (strcmp(argv[0], "show-device") == 0) { 1308 rv = do_show_device(argc, argv); 1309 } else if (strcmp(argv[0], "show-control") == 0) { 1310 rv = do_show_control(argc, argv); 1311 } else if (strcmp(argv[0], "set-control") == 0) { 1312 rv = do_set_control(argc, argv); 1313 } else if (strcmp(argv[0], "load-controls") == 0) { 1314 rv = do_load_controls(argc, argv); 1315 } else if (strcmp(argv[0], "save-controls") == 0) { 1316 rv = do_save_controls(argc, argv); 1317 } else if (strcmp(argv[0], "init-devices") == 0) { 1318 rv = do_init_devices(argc, argv); 1319 } else { 1320 help(); 1321 rv = EINVAL; 1322 } 1323 1324 OUT: 1325 free_devices(); 1326 return (rv); 1327 }