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 2009 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 return (EINVAL); 503 } 504 505 (void) snprintf(valbuf, sizeof (valbuf), "%s", str); 506 break; 507 508 default: 509 return (0); 510 } 511 512 /* 513 * possible control values (range/selection) 514 */ 515 switch (cinfop->ci.type) { 516 case MIXT_ONOFF: 517 (void) snprintf(selbuf, sizeof (selbuf), _("on,off")); 518 break; 519 520 case MIXT_MONOSLIDER: 521 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d", 522 cinfop->ci.minvalue, cinfop->ci.maxvalue); 523 break; 524 case MIXT_STEREOSLIDER: 525 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d:%d-%d", 526 cinfop->ci.minvalue, cinfop->ci.maxvalue, 527 cinfop->ci.minvalue, cinfop->ci.maxvalue); 528 break; 529 530 case MIXT_ENUM: 531 /* 532 * display the first choice on the same line, then display 533 * the rest on multiple lines 534 */ 535 selbuf[0] = 0; 536 for (i = 0; i < cinfop->ci.maxvalue; i++) { 537 str = get_enum_str(cinfop, i); 538 if (str == NULL) 539 continue; 540 541 if ((strlen(str) + 1 + strlen(selbuf)) >= 542 sizeof (selbuf)) { 543 break; 544 } 545 if (strlen(selbuf)) { 546 (void) strlcat(selbuf, ",", sizeof (selbuf)); 547 } 548 549 (void) strlcat(selbuf, str, sizeof (selbuf)); 550 } 551 idx = i; 552 break; 553 554 default: 555 (void) snprintf(selbuf, sizeof (selbuf), "-"); 556 } 557 558 col.col_dv = devnm; 559 col.col_nm = strlen(cinfop->ci.extname) ? 560 cinfop->ci.extname : cinfop->ci.id; 561 while (strchr(col.col_nm, '_') != NULL) { 562 col.col_nm = strchr(col.col_nm, '_') + 1; 563 } 564 col.col_val = valbuf; 565 col.col_sel = selbuf; 566 print_control_line(sfp, &col, vopt); 567 568 /* print leftover enum value selections */ 569 while ((sfp == NULL) && (idx >= 0) && (idx < cinfop->ci.maxvalue)) { 570 selbuf[0] = 0; 571 for (i = idx; i < cinfop->ci.maxvalue; i++) { 572 str = get_enum_str(cinfop, i); 573 if (str == NULL) 574 continue; 575 576 if ((strlen(str) + 1 + strlen(selbuf)) >= 577 sizeof (selbuf)) { 578 break; 579 } 580 if (strlen(selbuf)) { 581 (void) strlcat(selbuf, ",", sizeof (selbuf)); 582 } 583 584 (void) strlcat(selbuf, str, sizeof (selbuf)); 585 } 586 idx = i; 587 col.col_dv = NULL; 588 col.col_nm = NULL; 589 col.col_val = NULL; 590 col.col_sel = selbuf; 591 print_control_line(sfp, &col, vopt); 592 } 593 594 return (0); 595 } 596 597 598 static int 599 set_device_control(device_t *d, cinfo_t *cinfop, char *wstr, int vopt) 600 { 601 int mfd = d->mfd; 602 oss_mixer_value cval; 603 int wlen = strlen(wstr); 604 int lval, rval; 605 char *lstr, *rstr; 606 char *str; 607 int i; 608 int rv = -1; 609 610 cval.dev = -1; 611 cval.ctrl = cinfop->ci.ctrl; 612 cval.value = 0; 613 614 switch (cinfop->ci.type) { 615 case MIXT_ONOFF: 616 cval.value = (strncmp(_("on"), wstr, wlen) == 0) ? 1 : 0; 617 break; 618 619 case MIXT_MONOSLIDER: 620 cval.value = atoi(wstr); 621 break; 622 623 case MIXT_STEREOSLIDER: 624 lstr = wstr; 625 rstr = strchr(wstr, ':'); 626 if (rstr != NULL) { 627 *rstr = '\0'; 628 rstr++; 629 630 rval = atoi(rstr); 631 lval = atoi(lstr); 632 633 rstr--; 634 *rstr = ':'; 635 } else { 636 lval = atoi(lstr); 637 rval = lval; 638 } 639 640 cval.value = AUDIO_CTRL_STEREO_VAL(lval, rval); 641 break; 642 643 case MIXT_ENUM: 644 for (i = 0; i < cinfop->ci.maxvalue; i++) { 645 str = get_enum_str(cinfop, i); 646 if (str == NULL) 647 continue; 648 649 if (strncmp(wstr, str, wlen) == 0) { 650 cval.value = i; 651 break; 652 } 653 } 654 655 if (i >= cinfop->ci.maxvalue) { 656 warn(_("Invalid enumeration value\n")); 657 return (EINVAL); 658 } 659 break; 660 661 default: 662 warn(_("Unsupported control type: %d\n"), cinfop->ci.type); 663 return (EINVAL); 664 } 665 666 if (vopt) { 667 msg(_("%s: '%s' set to '%s'\n"), d->card.shortname, 668 cinfop->ci.extname, wstr); 669 } 670 671 if (ioctl(mfd, SNDCTL_MIX_WRITE, &cval) < 0) { 672 rv = errno; 673 perror(_("Error writing control")); 674 return (rv); 675 } 676 677 rv = 0; 678 return (rv); 679 } 680 681 682 static void 683 help(void) 684 { 685 #define HELP_STR _( \ 686 "audioctl list-devices\n" \ 687 " list all audio devices\n" \ 688 "\n" \ 689 "audioctl show-device [ -v ] [ -d <device> ]\n" \ 690 " display information about an audio device\n" \ 691 "\n" \ 692 "audioctl show-control [ -v ] [ -d <device> ] [ <control> ... ]\n" \ 693 " get the value of a specific control (all if not specified)\n" \ 694 "\n" \ 695 "audioctl set-control [ -v ] [ -d <device> ] <control> <value>\n" \ 696 " set the value of a specific control\n" \ 697 "\n" \ 698 "audioctl save-controls [ -d <device> ] [ -f ] <file>\n" \ 699 " save all control settings for the device to a file\n" \ 700 "\n" \ 701 "audioctl load-controls [ -d <device> ] <file>\n" \ 702 " restore previously saved control settings to device\n" \ 703 "\n" \ 704 "audioctl help\n" \ 705 " show this message.\n") 706 707 (void) fprintf(stderr, HELP_STR); 708 } 709 710 dev_t 711 device_devt(char *name) 712 { 713 struct stat sbuf; 714 715 if ((stat(name, &sbuf) != 0) || 716 ((sbuf.st_mode & S_IFCHR) == 0)) { 717 /* Not a device node! */ 718 return (0); 719 } 720 721 return (makedev(major(sbuf.st_rdev), 722 minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK))); 723 } 724 725 static device_t * 726 find_device(char *name) 727 { 728 dev_t devt; 729 device_t *d; 730 731 /* 732 * User may have specified: 733 * 734 * /dev/dsp[<num>] 735 * /dev/mixer[<num>] 736 * /dev/audio[<num>9] 737 * /dev/audioctl[<num>] 738 * /dev/sound/<num>{,ctl,dsp,mixer} 739 * /dev/sound/<driver>:<num>{,ctl,dsp,mixer} 740 * 741 * We can canonicalize these by looking at the dev_t though. 742 */ 743 744 if (load_devices() != 0) { 745 return (NULL); 746 } 747 748 if (name == NULL) 749 name = getenv("AUDIODEV"); 750 751 if ((name == NULL) || 752 (strcmp(name, "/dev/mixer") == 0)) { 753 /* /dev/mixer node doesn't point to real hw */ 754 name = "/dev/dsp"; 755 } 756 757 if (*name == '/') { 758 /* if we have a full path, convert to the devt */ 759 if ((devt = device_devt(name)) == 0) { 760 warn(_("No such audio device.\n")); 761 return (NULL); 762 } 763 name = NULL; 764 } 765 766 for (d = devices; d != NULL; d = d->nextp) { 767 oss_card_info *card = &d->card; 768 769 if ((name) && (strcmp(name, card->shortname) == 0)) { 770 return (d); 771 } 772 if (devt == d->devt) { 773 return (d); 774 } 775 } 776 777 warn(_("No such audio device.\n")); 778 return (NULL); 779 } 780 781 int 782 do_list_devices(int argc, char **argv) 783 { 784 int optc; 785 int verbose = 0; 786 device_t *d; 787 788 while ((optc = getopt(argc, argv, "v")) != EOF) { 789 switch (optc) { 790 case 'v': 791 verbose++; 792 break; 793 default: 794 help(); 795 return (-1); 796 } 797 } 798 argc -= optind; 799 argv += optind; 800 if (argc != 0) { 801 help(); 802 return (-1); 803 } 804 805 if (load_devices() != 0) { 806 return (-1); 807 } 808 809 for (d = devices; d != NULL; d = d->nextp) { 810 811 if ((d->mixer.enabled == 0) && (!verbose)) 812 continue; 813 814 if (verbose) { 815 msg(_("%s (%s)\n"), d->card.shortname, 816 d->mixer.devnode); 817 } else { 818 msg(_("%s\n"), d->card.shortname); 819 } 820 } 821 822 return (0); 823 } 824 825 int 826 do_show_device(int argc, char **argv) 827 { 828 int optc; 829 char *devname = NULL; 830 device_t *d; 831 832 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 833 switch (optc) { 834 case 'd': 835 devname = optarg; 836 break; 837 case 'v': 838 break; 839 default: 840 help(); 841 return (-1); 842 } 843 } 844 argc -= optind; 845 argv += optind; 846 if (argc != 0) { 847 help(); 848 return (-1); 849 } 850 851 if ((d = find_device(devname)) == NULL) { 852 return (ENODEV); 853 } 854 855 msg(_("Device: %s\n"), d->mixer.devnode); 856 msg(_(" Name = %s\n"), d->card.shortname); 857 msg(_(" Config = %s\n"), d->card.longname); 858 859 if (strlen(d->card.hw_info)) { 860 msg(_(" HW Info = %s"), d->card.hw_info); 861 } 862 863 return (0); 864 } 865 866 int 867 do_show_control(int argc, char **argv) 868 { 869 int optc; 870 int rval = 0; 871 int verbose = 0; 872 device_t *d; 873 char *devname = NULL; 874 int i; 875 int j; 876 int rv; 877 char *n; 878 cinfo_t *cinfop; 879 880 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 881 switch (optc) { 882 case 'd': 883 devname = optarg; 884 break; 885 case 'v': 886 verbose++; 887 break; 888 default: 889 help(); 890 return (-1); 891 } 892 } 893 argc -= optind; 894 argv += optind; 895 896 if ((d = find_device(devname)) == NULL) { 897 return (ENODEV); 898 } 899 900 print_header(NULL, verbose); 901 if (argc == 0) { 902 /* do them all! */ 903 for (i = 0; i < d->cmax; i++) { 904 905 cinfop = &d->controls[i]; 906 rv = print_control(NULL, d, cinfop, verbose); 907 rval = rval ? rval : rv; 908 } 909 return (rval); 910 } 911 912 for (i = 0; i < argc; i++) { 913 for (j = 0; j < d->cmax; j++) { 914 cinfop = &d->controls[j]; 915 n = strrchr(cinfop->ci.extname, '_'); 916 n = n ? n + 1 : cinfop->ci.extname; 917 if (strcmp(argv[i], n) == 0) { 918 rv = print_control(NULL, d, cinfop, verbose); 919 rval = rval ? rval : rv; 920 break; 921 } 922 } 923 /* Didn't find requested control */ 924 if (j == d->cmax) { 925 warn(_("No such control: %s\n"), argv[i]); 926 rval = rval ? rval : ENODEV; 927 } 928 } 929 930 return (rval); 931 } 932 933 int 934 do_set_control(int argc, char **argv) 935 { 936 int optc; 937 int rval = 0; 938 int verbose = 0; 939 device_t *d; 940 char *devname = NULL; 941 char *cname; 942 char *value; 943 int i; 944 int found; 945 int rv; 946 char *n; 947 cinfo_t *cinfop; 948 949 while ((optc = getopt(argc, argv, "d:v")) != EOF) { 950 switch (optc) { 951 case 'd': 952 devname = optarg; 953 break; 954 case 'v': 955 verbose = 1; 956 break; 957 default: 958 help(); 959 return (-1); 960 } 961 } 962 argc -= optind; 963 argv += optind; 964 965 if (argc != 2) { 966 help(); 967 return (-1); 968 } 969 cname = argv[0]; 970 value = argv[1]; 971 972 if ((d = find_device(devname)) == NULL) { 973 return (ENODEV); 974 } 975 976 for (i = 0, found = 0; i < d->cmax; i++) { 977 cinfop = &d->controls[i]; 978 n = strrchr(cinfop->ci.extname, '_'); 979 n = n ? n + 1 : cinfop->ci.extname; 980 if (strcmp(cname, n) != 0) { 981 continue; 982 } 983 found = 1; 984 rv = set_device_control(d, cinfop, value, verbose); 985 rval = rval ? rval : rv; 986 } 987 if (!found) { 988 warn(_("No such control: %s\n"), cname); 989 } 990 991 return (rval); 992 } 993 994 int 995 do_save_controls(int argc, char **argv) 996 { 997 int optc; 998 int rval = 0; 999 device_t *d; 1000 char *devname = NULL; 1001 char *fname; 1002 int i; 1003 int rv; 1004 cinfo_t *cinfop; 1005 FILE *fp; 1006 int fd; 1007 int mode; 1008 1009 mode = O_WRONLY | O_CREAT | O_EXCL; 1010 1011 while ((optc = getopt(argc, argv, "d:f")) != EOF) { 1012 switch (optc) { 1013 case 'd': 1014 devname = optarg; 1015 break; 1016 case 'f': 1017 mode &= ~O_EXCL; 1018 mode |= O_TRUNC; 1019 break; 1020 default: 1021 help(); 1022 return (-1); 1023 } 1024 } 1025 argc -= optind; 1026 argv += optind; 1027 1028 if (argc != 1) { 1029 help(); 1030 return (-1); 1031 } 1032 fname = argv[0]; 1033 1034 if ((d = find_device(devname)) == NULL) { 1035 return (ENODEV); 1036 } 1037 1038 if ((fd = open(fname, mode, 0666)) < 0) { 1039 perror(_("Failed to create file")); 1040 return (errno); 1041 } 1042 1043 if ((fp = fdopen(fd, "w")) == NULL) { 1044 perror(_("Unable to open file\n")); 1045 (void) close(fd); 1046 (void) unlink(fname); 1047 return (errno); 1048 } 1049 1050 (void) fprintf(fp, "# Device: %s\n", d->mixer.devnode); 1051 (void) fprintf(fp, "# Name = %s\n", d->card.shortname); 1052 (void) fprintf(fp, "# Config = %s\n", d->card.longname); 1053 1054 if (strlen(d->card.hw_info)) { 1055 (void) fprintf(fp, "# HW Info = %s", d->card.hw_info); 1056 } 1057 (void) fprintf(fp, "#\n"); 1058 1059 print_header(fp, 0); 1060 1061 for (i = 0; i < d->cmax; i++) { 1062 cinfop = &d->controls[i]; 1063 rv = print_control(fp, d, cinfop, 0); 1064 rval = rval ? rval : rv; 1065 } 1066 1067 (void) fclose(fp); 1068 1069 return (rval); 1070 } 1071 1072 int 1073 do_load_controls(int argc, char **argv) 1074 { 1075 int optc; 1076 int rval = 0; 1077 device_t *d; 1078 char *devname = NULL; 1079 char *fname; 1080 char *cname; 1081 char *value; 1082 int i; 1083 int rv; 1084 cinfo_t *cinfop; 1085 FILE *fp; 1086 char linebuf[MAXLINE]; 1087 int lineno = 0; 1088 int found; 1089 1090 while ((optc = getopt(argc, argv, "d:")) != EOF) { 1091 switch (optc) { 1092 case 'd': 1093 devname = optarg; 1094 break; 1095 default: 1096 help(); 1097 return (-1); 1098 } 1099 } 1100 argc -= optind; 1101 argv += optind; 1102 1103 if (argc != 1) { 1104 help(); 1105 return (-1); 1106 } 1107 fname = argv[0]; 1108 1109 if ((d = find_device(devname)) == NULL) { 1110 return (ENODEV); 1111 } 1112 1113 if ((fp = fopen(fname, "r")) == NULL) { 1114 perror(_("Unable to open file")); 1115 return (errno); 1116 } 1117 1118 while (fgets(linebuf, sizeof (linebuf), fp) != NULL) { 1119 lineno++; 1120 if (linebuf[strlen(linebuf) - 1] != '\n') { 1121 warn(_("Warning: line too long at line %d\n"), lineno); 1122 /* read in the rest of the line and discard it */ 1123 while (fgets(linebuf, sizeof (linebuf), fp) != NULL && 1124 (linebuf[strlen(linebuf) - 1] != '\n')) { 1125 continue; 1126 } 1127 continue; 1128 } 1129 1130 /* we have a good line ... */ 1131 cname = strtok(linebuf, " \t\n"); 1132 /* skip comments and blank lines */ 1133 if ((cname == NULL) || (cname[0] == '#')) { 1134 continue; 1135 } 1136 value = strtok(NULL, " \t\n"); 1137 if ((value == NULL) || (*cname == 0)) { 1138 warn(_("Warning: missing value at line %d\n"), lineno); 1139 continue; 1140 } 1141 1142 for (i = 0, found = 0; i < d->cmax; i++) { 1143 /* save and restore requires an exact match */ 1144 cinfop = &d->controls[i]; 1145 if (strcmp(cinfop->ci.extname, cname) != 0) { 1146 continue; 1147 } 1148 found = 1; 1149 rv = set_device_control(d, cinfop, value, 0); 1150 rval = rval ? rval : rv; 1151 } 1152 if (!found) { 1153 warn(_("No such control: %s\n"), cname); 1154 } 1155 } 1156 (void) fclose(fp); 1157 1158 return (rval); 1159 } 1160 1161 int 1162 mixer_walker(di_devlink_t dlink, void *arg) 1163 { 1164 const char *link; 1165 int num; 1166 int fd; 1167 int verbose = *(int *)arg; 1168 int num_offset; 1169 1170 num_offset = sizeof ("/dev/mixer") - 1; 1171 1172 link = di_devlink_path(dlink); 1173 1174 if ((link == NULL) || 1175 (strncmp(link, "/dev/mixer", num_offset) != 0) || 1176 (!isdigit(link[num_offset]))) { 1177 return (DI_WALK_CONTINUE); 1178 } 1179 1180 num = atoi(link + num_offset); 1181 if ((fd = open(link, O_RDWR)) < 0) { 1182 if (verbose) { 1183 if (errno == ENOENT) { 1184 msg(_("Device %s not present.\n"), link); 1185 } else { 1186 msg(_("Unable to open device %s: %s\n"), 1187 link, strerror(errno)); 1188 } 1189 } 1190 return (DI_WALK_CONTINUE); 1191 } 1192 1193 if (verbose) { 1194 msg(_("Initializing link %s: "), link); 1195 } 1196 if (ioctl(fd, SNDCTL_SUN_SEND_NUMBER, &num) != 0) { 1197 if (verbose) { 1198 msg(_("failed: %s\n"), strerror(errno)); 1199 } 1200 } else { 1201 if (verbose) { 1202 msg(_("done.\n")); 1203 } 1204 } 1205 (void) close(fd); 1206 return (DI_WALK_CONTINUE); 1207 } 1208 1209 int 1210 do_init_devices(int argc, char **argv) 1211 { 1212 int optc; 1213 di_devlink_handle_t dlh; 1214 int verbose = 0; 1215 1216 while ((optc = getopt(argc, argv, "v")) != EOF) { 1217 switch (optc) { 1218 case 'v': 1219 verbose = 1; 1220 break; 1221 default: 1222 help(); 1223 return (-1); 1224 } 1225 } 1226 argc -= optind; 1227 argv += optind; 1228 1229 if (argc != 0) { 1230 help(); 1231 return (-1); 1232 } 1233 1234 dlh = di_devlink_init(NULL, 0); 1235 if (dlh == NULL) { 1236 perror(_("Unable to initialize devlink handle")); 1237 return (-1); 1238 } 1239 1240 if (di_devlink_walk(dlh, "^mixer", NULL, 0, &verbose, 1241 mixer_walker) != 0) { 1242 perror(_("Unable to walk devlinks")); 1243 return (-1); 1244 } 1245 return (0); 1246 } 1247 1248 int 1249 main(int argc, char **argv) 1250 { 1251 int rv = 0; 1252 int opt; 1253 1254 (void) setlocale(LC_ALL, ""); 1255 (void) textdomain(TEXT_DOMAIN); 1256 1257 while ((opt = getopt(argc, argv, "h")) != EOF) { 1258 switch (opt) { 1259 case 'h': 1260 help(); 1261 rv = 0; 1262 goto OUT; 1263 default: 1264 rv = EINVAL; 1265 break; 1266 } 1267 } 1268 1269 if (rv) { 1270 goto OUT; 1271 } 1272 1273 argc -= optind; 1274 argv += optind; 1275 1276 if (argc < 1) { 1277 help(); 1278 rv = EINVAL; 1279 } else if (strcmp(argv[0], "help") == 0) { 1280 help(); 1281 rv = 0; 1282 } else if (strcmp(argv[0], "list-devices") == 0) { 1283 rv = do_list_devices(argc, argv); 1284 } else if (strcmp(argv[0], "show-device") == 0) { 1285 rv = do_show_device(argc, argv); 1286 } else if (strcmp(argv[0], "show-control") == 0) { 1287 rv = do_show_control(argc, argv); 1288 } else if (strcmp(argv[0], "set-control") == 0) { 1289 rv = do_set_control(argc, argv); 1290 } else if (strcmp(argv[0], "load-controls") == 0) { 1291 rv = do_load_controls(argc, argv); 1292 } else if (strcmp(argv[0], "save-controls") == 0) { 1293 rv = do_save_controls(argc, argv); 1294 } else if (strcmp(argv[0], "init-devices") == 0) { 1295 rv = do_init_devices(argc, argv); 1296 } else { 1297 help(); 1298 rv = EINVAL; 1299 } 1300 1301 OUT: 1302 free_devices(); 1303 return (rv); 1304 } 1305