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