1 /* $NetBSD: oss4_mixer.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ 2 3 /*- 4 * Copyright (c) 2020-2021 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Nia Alarie. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 #include <sys/audioio.h> 32 #include <sys/fcntl.h> 33 #include <sys/stat.h> 34 #include <errno.h> 35 #include <limits.h> 36 #include <stdio.h> 37 #include <unistd.h> 38 #include "internal.h" 39 40 static int get_audio_count(void); 41 static int get_mixer_count(void); 42 static int get_mixer_control_count(int); 43 44 oss_private int 45 _oss4_mixer_ioctl(int fd, unsigned long com, void *argp) 46 { 47 oss_audioinfo *tmpai; 48 oss_card_info *cardinfo; 49 oss_mixext *ext; 50 oss_mixext_root root; 51 oss_mixer_enuminfo *ei; 52 oss_mixer_value *mv; 53 oss_mixerinfo *mi; 54 oss_sysinfo sysinfo; 55 dev_t devno; 56 struct stat tmpstat; 57 struct audio_device dev; 58 struct audio_format_query fmtq; 59 struct mixer_devinfo mdi; 60 struct mixer_ctrl mc; 61 char devname[32]; 62 size_t len; 63 int newfd = -1, tmperrno; 64 int i, noffs; 65 int retval; 66 67 /* 68 * Note: it is difficult to translate the NetBSD concept of a "set" 69 * mixer control type to the OSSv4 API, as far as I can tell. 70 * 71 * This means they are treated like enums, i.e. only one entry in the 72 * set can be selected at a time. 73 */ 74 75 switch (com) { 76 case SNDCTL_AUDIOINFO: 77 /* 78 * SNDCTL_AUDIOINFO_EX is intended for underlying hardware devices 79 * that are to be opened in "exclusive mode" (bypassing the normal 80 * kernel mixer for exclusive control). NetBSD does not support 81 * bypassing the kernel mixer, so it's an alias of SNDCTL_AUDIOINFO. 82 */ 83 case SNDCTL_AUDIOINFO_EX: 84 case SNDCTL_ENGINEINFO: 85 devno = 0; 86 tmpai = (struct oss_audioinfo*)argp; 87 if (tmpai == NULL) { 88 errno = EINVAL; 89 return -1; 90 } 91 92 /* 93 * If the input device is -1, guess the device related to 94 * the open mixer device. 95 */ 96 if (tmpai->dev < 0) { 97 fstat(fd, &tmpstat); 98 if ((tmpstat.st_rdev & 0xff00) == 0x2a00) 99 devno = tmpstat.st_rdev & 0xff; 100 if (devno >= 0x80) 101 tmpai->dev = devno & 0x7f; 102 } 103 if (tmpai->dev < 0) 104 tmpai->dev = 0; 105 106 snprintf(tmpai->devnode, sizeof(tmpai->devnode), 107 "/dev/audio%d", tmpai->dev); 108 109 if ((newfd = open(tmpai->devnode, O_WRONLY)) < 0) { 110 if ((newfd = open(tmpai->devnode, O_RDONLY)) < 0) { 111 return newfd; 112 } 113 } 114 115 retval = ioctl(newfd, AUDIO_GETDEV, &dev); 116 if (retval < 0) { 117 tmperrno = errno; 118 close(newfd); 119 errno = tmperrno; 120 return retval; 121 } 122 if (_oss_get_caps(newfd, &tmpai->caps) < 0) { 123 tmperrno = errno; 124 close(newfd); 125 errno = tmperrno; 126 return retval; 127 } 128 snprintf(tmpai->name, sizeof(tmpai->name), 129 "%s %s", dev.name, dev.version); 130 tmpai->busy = 0; 131 tmpai->pid = -1; 132 _oss_dsp_ioctl(newfd, SNDCTL_DSP_GETFMTS, &tmpai->iformats); 133 tmpai->oformats = tmpai->iformats; 134 tmpai->magic = -1; /* reserved for "internal use" */ 135 memset(tmpai->cmd, 0, sizeof(tmpai->cmd)); 136 tmpai->card_number = -1; 137 memset(tmpai->song_name, 0, 138 sizeof(tmpai->song_name)); 139 memset(tmpai->label, 0, sizeof(tmpai->label)); 140 tmpai->port_number = 0; 141 tmpai->mixer_dev = tmpai->dev; 142 tmpai->legacy_device = tmpai->dev; 143 tmpai->enabled = 1; 144 tmpai->flags = -1; /* reserved for "future versions" */ 145 tmpai->min_rate = 1000; 146 tmpai->max_rate = 192000; 147 tmpai->nrates = 0; 148 tmpai->min_channels = 1; 149 tmpai->max_channels = 2; 150 for (fmtq.index = 0; 151 ioctl(newfd, AUDIO_QUERYFORMAT, &fmtq) != -1; ++fmtq.index) { 152 if (fmtq.fmt.channels > (unsigned)tmpai->max_channels) 153 tmpai->max_channels = fmtq.fmt.channels; 154 } 155 tmpai->binding = -1; /* reserved for "future versions" */ 156 tmpai->rate_source = -1; 157 /* 158 * 'handle' is supposed to be globally unique. The closest 159 * we have to that is probably device nodes. 160 */ 161 strlcpy(tmpai->handle, tmpai->devnode, 162 sizeof(tmpai->handle)); 163 tmpai->next_play_engine = 0; 164 tmpai->next_rec_engine = 0; 165 argp = tmpai; 166 close(newfd); 167 break; 168 case SNDCTL_CARDINFO: 169 cardinfo = (oss_card_info *)argp; 170 if (cardinfo == NULL) { 171 errno = EINVAL; 172 return -1; 173 } 174 if (cardinfo->card != -1) { 175 snprintf(devname, sizeof(devname), 176 "/dev/audio%d", cardinfo->card); 177 newfd = open(devname, O_RDONLY); 178 if (newfd < 0) 179 return newfd; 180 } else { 181 newfd = fd; 182 } 183 retval = ioctl(newfd, AUDIO_GETDEV, &dev); 184 tmperrno = errno; 185 if (newfd != fd) 186 close(newfd); 187 if (retval < 0) { 188 errno = tmperrno; 189 return retval; 190 } 191 strlcpy(cardinfo->shortname, dev.name, 192 sizeof(cardinfo->shortname)); 193 snprintf(cardinfo->longname, sizeof(cardinfo->longname), 194 "%s %s %s", dev.name, dev.version, dev.config); 195 memset(cardinfo->hw_info, 0, sizeof(cardinfo->hw_info)); 196 /* 197 * OSSv4 does not document this ioctl, and claims it should 198 * not be used by applications and is provided for "utiltiy 199 * programs included in OSS". We follow the Solaris 200 * implementation (which is documented) and leave these fields 201 * unset. 202 */ 203 cardinfo->flags = 0; 204 cardinfo->intr_count = 0; 205 cardinfo->ack_count = 0; 206 break; 207 case SNDCTL_SYSINFO: 208 memset(&sysinfo, 0, sizeof(sysinfo)); 209 strlcpy(sysinfo.product, 210 "OSS/NetBSD", sizeof(sysinfo.product)); 211 strlcpy(sysinfo.version, 212 "4.01", sizeof(sysinfo.version)); 213 strlcpy(sysinfo.license, 214 "BSD", sizeof(sysinfo.license)); 215 sysinfo.versionnum = SOUND_VERSION; 216 sysinfo.numaudios = 217 sysinfo.numcards = 218 get_audio_count(); 219 sysinfo.numaudioengines = 1; 220 sysinfo.numsynths = 1; 221 sysinfo.nummidis = -1; 222 sysinfo.numtimers = -1; 223 sysinfo.nummixers = get_mixer_count(); 224 *(struct oss_sysinfo *)argp = sysinfo; 225 break; 226 case SNDCTL_MIXERINFO: 227 mi = (oss_mixerinfo *)argp; 228 if (mi == NULL) { 229 errno = EINVAL; 230 return -1; 231 } 232 snprintf(devname, sizeof(devname), "/dev/mixer%d", mi->dev); 233 if ((newfd = open(devname, O_RDONLY)) < 0) 234 return newfd; 235 retval = ioctl(newfd, AUDIO_GETDEV, &dev); 236 if (retval < 0) { 237 tmperrno = errno; 238 close(newfd); 239 errno = tmperrno; 240 return retval; 241 } 242 strlcpy(mi->id, devname, sizeof(mi->id)); 243 strlcpy(mi->handle, devname, sizeof(mi->handle)); 244 snprintf(mi->name, sizeof(mi->name), 245 "%s %s", dev.name, dev.version); 246 mi->card_number = mi->dev; 247 mi->port_number = 0; 248 mi->magic = 0; 249 mi->enabled = 1; 250 mi->caps = 0; 251 mi->flags = 0; 252 mi->nrext = get_mixer_control_count(newfd) + 1; 253 mi->priority = UCHAR_MAX - mi->dev; 254 strlcpy(mi->devnode, devname, sizeof(mi->devnode)); 255 mi->legacy_device = mi->dev; 256 break; 257 case SNDCTL_MIX_DESCRIPTION: 258 /* No description available. */ 259 errno = ENOSYS; 260 return -1; 261 case SNDCTL_MIX_NRMIX: 262 INTARG = get_mixer_count(); 263 break; 264 case SNDCTL_MIX_NREXT: 265 snprintf(devname, sizeof(devname), "/dev/mixer%d", INTARG); 266 if ((newfd = open(devname, O_RDONLY)) < 0) 267 return newfd; 268 INTARG = get_mixer_control_count(newfd) + 1; 269 close(newfd); 270 break; 271 case SNDCTL_MIX_EXTINFO: 272 ext = (oss_mixext *)argp; 273 snprintf(devname, sizeof(devname), "/dev/mixer%d", ext->dev); 274 if ((newfd = open(devname, O_RDONLY)) < 0) 275 return newfd; 276 if (ext->ctrl == 0) { 277 /* 278 * NetBSD has no concept of a "root mixer control", but 279 * OSSv4 requires one to work. We fake one at 0 and 280 * simply add 1 to all real control indexes. 281 */ 282 retval = ioctl(newfd, AUDIO_GETDEV, &dev); 283 tmperrno = errno; 284 close(newfd); 285 if (retval < 0) { 286 errno = tmperrno; 287 return -1; 288 } 289 memset(&root, 0, sizeof(root)); 290 strlcpy(root.id, devname, sizeof(root.id)); 291 snprintf(root.name, sizeof(root.name), 292 "%s %s", dev.name, dev.version); 293 strlcpy(ext->id, devname, sizeof(ext->id)); 294 snprintf(ext->extname, sizeof(ext->extname), 295 "%s %s", dev.name, dev.version); 296 strlcpy(ext->extname, "root", sizeof(ext->extname)); 297 ext->type = MIXT_DEVROOT; 298 ext->minvalue = 0; 299 ext->maxvalue = 0; 300 ext->flags = 0; 301 ext->parent = -1; 302 ext->control_no = -1; 303 ext->update_counter = 0; 304 ext->rgbcolor = 0; 305 memcpy(&ext->data, &root, 306 sizeof(root) > sizeof(ext->data) ? 307 sizeof(ext->data) : sizeof(root)); 308 return 0; 309 } 310 mdi.index = ext->ctrl - 1; 311 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); 312 if (retval < 0) { 313 tmperrno = errno; 314 close(newfd); 315 errno = tmperrno; 316 return retval; 317 } 318 ext->flags = MIXF_READABLE | MIXF_WRITEABLE | MIXF_POLL; 319 ext->parent = mdi.mixer_class + 1; 320 strlcpy(ext->id, mdi.label.name, sizeof(ext->id)); 321 strlcpy(ext->extname, mdi.label.name, sizeof(ext->extname)); 322 len = strlen(ext->extname); 323 memset(ext->data, 0, sizeof(ext->data)); 324 ext->control_no = -1; 325 ext->update_counter = 0; 326 ext->rgbcolor = 0; 327 switch (mdi.type) { 328 case AUDIO_MIXER_CLASS: 329 ext->type = MIXT_GROUP; 330 ext->parent = 0; 331 ext->minvalue = 0; 332 ext->maxvalue = 0; 333 break; 334 case AUDIO_MIXER_ENUM: 335 ext->maxvalue = mdi.un.e.num_mem; 336 ext->minvalue = 0; 337 for (i = 0; i < mdi.un.e.num_mem; ++i) { 338 ext->enum_present[i / 8] |= (1 << (i % 8)); 339 } 340 if (mdi.un.e.num_mem == 2) { 341 if (!strcmp(mdi.un.e.member[0].label.name, AudioNoff) && 342 !strcmp(mdi.un.e.member[1].label.name, AudioNon)) { 343 ext->type = MIXT_MUTE; 344 } else { 345 ext->type = MIXT_ENUM; 346 } 347 } else { 348 ext->type = MIXT_ENUM; 349 } 350 break; 351 case AUDIO_MIXER_SET: 352 ext->maxvalue = mdi.un.s.num_mem; 353 ext->minvalue = 0; 354 #ifdef notyet 355 /* 356 * XXX: This is actually the correct type for "set" 357 * controls, but it seems no real world software 358 * supports it. The only documentation exists in 359 * the OSSv4 headers and describes it as "reserved 360 * for Sun's implementation". 361 */ 362 ext->type = MIXT_ENUM_MULTI; 363 #else 364 ext->type = MIXT_ENUM; 365 #endif 366 for (i = 0; i < mdi.un.s.num_mem; ++i) { 367 ext->enum_present[i / 8] |= (1 << (i % 8)); 368 } 369 break; 370 case AUDIO_MIXER_VALUE: 371 ext->maxvalue = UCHAR_MAX + 1; 372 ext->minvalue = 0; 373 if (mdi.un.v.num_channels == 2) { 374 ext->type = MIXT_STEREOSLIDER; 375 } else { 376 ext->type = MIXT_MONOSLIDER; 377 } 378 break; 379 } 380 close(newfd); 381 break; 382 case SNDCTL_MIX_ENUMINFO: 383 ei = (oss_mixer_enuminfo *)argp; 384 if (ei == NULL) { 385 errno = EINVAL; 386 return -1; 387 } 388 if (ei->ctrl == 0) { 389 errno = EINVAL; 390 return -1; 391 } 392 snprintf(devname, sizeof(devname), "/dev/mixer%d", ei->dev); 393 if ((newfd = open(devname, O_RDONLY)) < 0) 394 return newfd; 395 mdi.index = ei->ctrl - 1; 396 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); 397 tmperrno = errno; 398 close(newfd); 399 if (retval < 0) { 400 errno = tmperrno; 401 return retval; 402 } 403 ei->version = 0; 404 switch (mdi.type) { 405 case AUDIO_MIXER_ENUM: 406 ei->nvalues = mdi.un.e.num_mem; 407 noffs = 0; 408 for (i = 0; i < ei->nvalues; ++i) { 409 ei->strindex[i] = noffs; 410 len = strlen(mdi.un.e.member[i].label.name) + 1; 411 if ((noffs + len) >= sizeof(ei->strings)) { 412 errno = ENOMEM; 413 return -1; 414 } 415 memcpy(ei->strings + noffs, 416 mdi.un.e.member[i].label.name, len); 417 noffs += len; 418 } 419 break; 420 case AUDIO_MIXER_SET: 421 ei->nvalues = mdi.un.s.num_mem; 422 noffs = 0; 423 for (i = 0; i < ei->nvalues; ++i) { 424 ei->strindex[i] = noffs; 425 len = strlen(mdi.un.s.member[i].label.name) + 1; 426 if ((noffs + len) >= sizeof(ei->strings)) { 427 errno = ENOMEM; 428 return -1; 429 } 430 memcpy(ei->strings + noffs, 431 mdi.un.s.member[i].label.name, len); 432 noffs += len; 433 } 434 break; 435 default: 436 errno = EINVAL; 437 return -1; 438 } 439 break; 440 case SNDCTL_MIX_WRITE: 441 mv = (oss_mixer_value *)argp; 442 if (mv == NULL) { 443 errno = EINVAL; 444 return -1; 445 } 446 if (mv->ctrl == 0) { 447 errno = EINVAL; 448 return -1; 449 } 450 snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev); 451 if ((newfd = open(devname, O_RDWR)) < 0) 452 return newfd; 453 mdi.index = mc.dev = mv->ctrl - 1; 454 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); 455 if (retval < 0) { 456 tmperrno = errno; 457 close(newfd); 458 errno = tmperrno; 459 return retval; 460 } 461 mc.type = mdi.type; 462 switch (mdi.type) { 463 case AUDIO_MIXER_ENUM: 464 if (mv->value >= mdi.un.e.num_mem) { 465 close(newfd); 466 errno = EINVAL; 467 return -1; 468 } 469 mc.un.ord = mdi.un.e.member[mv->value].ord; 470 break; 471 case AUDIO_MIXER_SET: 472 if (mv->value >= mdi.un.s.num_mem) { 473 close(newfd); 474 errno = EINVAL; 475 return -1; 476 } 477 #ifdef notyet 478 mc.un.mask = 0; 479 for (i = 0; i < mdi.un.s.num_mem; ++i) { 480 if (mv->value & (1 << i)) { 481 mc.un.mask |= mdi.un.s.member[mv->value].mask; 482 } 483 } 484 #else 485 mc.un.mask = mdi.un.s.member[mv->value].mask; 486 #endif 487 break; 488 case AUDIO_MIXER_VALUE: 489 mc.un.value.num_channels = mdi.un.v.num_channels; 490 if (mdi.un.v.num_channels != 2) { 491 for (i = 0; i < mdi.un.v.num_channels; ++i) { 492 mc.un.value.level[i] = mv->value; 493 } 494 } else { 495 mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = 496 (mv->value >> 0) & 0xFF; 497 mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = 498 (mv->value >> 8) & 0xFF; 499 } 500 break; 501 } 502 retval = ioctl(newfd, AUDIO_MIXER_WRITE, &mc); 503 if (retval < 0) { 504 tmperrno = errno; 505 close(newfd); 506 errno = tmperrno; 507 return retval; 508 } 509 close(newfd); 510 break; 511 case SNDCTL_MIX_READ: 512 mv = (oss_mixer_value *)argp; 513 if (mv == NULL) { 514 errno = EINVAL; 515 return -1; 516 } 517 if (mv->ctrl == 0) { 518 errno = EINVAL; 519 return -1; 520 } 521 snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev); 522 if ((newfd = open(devname, O_RDWR)) < 0) 523 return newfd; 524 mdi.index = mc.dev = (mv->ctrl - 1); 525 retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); 526 if (retval < 0) { 527 tmperrno = errno; 528 close(newfd); 529 errno = tmperrno; 530 return retval; 531 } 532 mc.dev = mdi.index; 533 mc.type = mdi.type; 534 if (mdi.type == AUDIO_MIXER_VALUE) 535 mc.un.value.num_channels = mdi.un.v.num_channels; 536 retval = ioctl(newfd, AUDIO_MIXER_READ, &mc); 537 if (retval < 0) { 538 tmperrno = errno; 539 close(newfd); 540 errno = tmperrno; 541 return retval; 542 } 543 close(newfd); 544 mv->value = 0; 545 switch (mdi.type) { 546 case AUDIO_MIXER_ENUM: 547 for (i = 0; i < mdi.un.e.num_mem; ++i) { 548 if (mc.un.ord == mdi.un.e.member[i].ord) { 549 mv->value = i; 550 break; 551 } 552 } 553 break; 554 case AUDIO_MIXER_SET: 555 for (i = 0; i < mdi.un.s.num_mem; ++i) { 556 #ifdef notyet 557 if (mc.un.mask & mdi.un.s.member[i].mask) 558 mv->value |= (1 << i); 559 #else 560 if (mc.un.mask == mdi.un.s.member[i].mask) { 561 mv->value = i; 562 break; 563 } 564 #endif 565 } 566 break; 567 case AUDIO_MIXER_VALUE: 568 if (mdi.un.v.num_channels != 2) { 569 mv->value = mc.un.value.level[0]; 570 } else { 571 mv->value = \ 572 ((mc.un.value.level[1] & 0xFF) << 8) | 573 ((mc.un.value.level[0] & 0xFF) << 0); 574 } 575 break; 576 default: 577 errno = EINVAL; 578 return -1; 579 } 580 break; 581 default: 582 errno = EINVAL; 583 return -1; 584 } 585 return 0; 586 } 587 588 static int 589 get_audio_count(void) 590 { 591 char devname[32]; 592 int ndevs = 0; 593 int tmpfd; 594 int tmperrno = errno; 595 596 do { 597 snprintf(devname, sizeof(devname), 598 "/dev/audio%d", ndevs); 599 if ((tmpfd = open(devname, O_RDONLY)) != -1 || 600 (tmpfd = open(devname, O_WRONLY)) != -1) { 601 ndevs++; 602 close(tmpfd); 603 } 604 } while (tmpfd != -1); 605 errno = tmperrno; 606 return ndevs; 607 } 608 609 static int 610 get_mixer_count(void) 611 { 612 char devname[32]; 613 int ndevs = 0; 614 int tmpfd; 615 int tmperrno = errno; 616 617 do { 618 snprintf(devname, sizeof(devname), 619 "/dev/mixer%d", ndevs); 620 if ((tmpfd = open(devname, O_RDONLY)) != -1) { 621 ndevs++; 622 close(tmpfd); 623 } 624 } while (tmpfd != -1); 625 errno = tmperrno; 626 return ndevs; 627 } 628 629 static int 630 get_mixer_control_count(int fd) 631 { 632 struct mixer_devinfo mdi; 633 int ndevs = 0; 634 635 do { 636 mdi.index = ndevs++; 637 } while (ioctl(fd, AUDIO_MIXER_DEVINFO, &mdi) != -1); 638 639 return ndevs > 0 ? ndevs - 1 : 0; 640 } 641