1 /* 2 * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 /* 17 * the way the sun mixer is designed doesn't let us representing 18 * it easily with the sioctl api. For now expose only few 19 * white-listed controls the same way as we do in kernel 20 * for the wskbd volume keys. 21 */ 22 #include <sys/types.h> 23 #include <sys/ioctl.h> 24 #include <sys/audioio.h> 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <limits.h> 28 #include <poll.h> 29 #include <sndio.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include "debug.h" 36 #include "sioctl_priv.h" 37 38 #define DEVPATH_PREFIX "/dev/audioctl" 39 #define DEVPATH_MAX (1 + \ 40 sizeof(DEVPATH_PREFIX) - 1 + \ 41 sizeof(int) * 3) 42 43 struct volume 44 { 45 int nch; /* channels in the level control */ 46 int level_idx; /* index of the level control */ 47 int level_val[8]; /* current value */ 48 int mute_idx; /* index of the mute control */ 49 int mute_val; /* per channel state of mute control */ 50 int base_addr; 51 char *name; 52 }; 53 54 struct sioctl_sun_hdl { 55 struct sioctl_hdl sioctl; 56 char display[SIOCTL_DISPLAYMAX]; 57 int display_addr; 58 struct volume output, input; 59 int fd, events; 60 }; 61 62 static void sioctl_sun_close(struct sioctl_hdl *); 63 static int sioctl_sun_nfds(struct sioctl_hdl *); 64 static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int); 65 static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *); 66 static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int); 67 static int sioctl_sun_onval(struct sioctl_hdl *); 68 static int sioctl_sun_ondesc(struct sioctl_hdl *); 69 70 /* 71 * operations every device should support 72 */ 73 struct sioctl_ops sioctl_sun_ops = { 74 sioctl_sun_close, 75 sioctl_sun_nfds, 76 sioctl_sun_pollfd, 77 sioctl_sun_revents, 78 sioctl_sun_setctl, 79 sioctl_sun_onval, 80 sioctl_sun_ondesc 81 }; 82 83 static int 84 initmute(struct sioctl_sun_hdl *hdl, struct mixer_devinfo *info) 85 { 86 struct mixer_devinfo mi; 87 char name[MAX_AUDIO_DEV_LEN]; 88 89 for (mi.index = info->next; mi.index != -1; mi.index = mi.next) { 90 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0) 91 break; 92 if (strcmp(mi.label.name, AudioNmute) == 0) 93 return mi.index; 94 } 95 96 /* try "_mute" suffix */ 97 snprintf(name, sizeof(name), "%s_mute", info->label.name); 98 for (mi.index = 0; ; mi.index++) { 99 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0) 100 break; 101 if (info->mixer_class == mi.mixer_class && 102 strcmp(mi.label.name, name) == 0) 103 return mi.index; 104 } 105 return -1; 106 } 107 108 static int 109 initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn) 110 { 111 struct mixer_devinfo dev, cls; 112 113 for (dev.index = 0; ; dev.index++) { 114 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0) 115 break; 116 if (dev.type != AUDIO_MIXER_VALUE) 117 continue; 118 cls.index = dev.mixer_class; 119 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0) 120 break; 121 if (strcmp(cls.label.name, cn) == 0 && 122 strcmp(dev.label.name, dn) == 0) { 123 vol->nch = dev.un.v.num_channels; 124 vol->level_idx = dev.index; 125 vol->mute_idx = initmute(hdl, &dev); 126 DPRINTF("using %s.%s, %d channels, %s\n", cn, dn, 127 vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute"); 128 return 1; 129 } 130 } 131 vol->level_idx = vol->mute_idx = -1; 132 return 0; 133 } 134 135 static void 136 init(struct sioctl_sun_hdl *hdl) 137 { 138 static struct { 139 char *cn, *dn; 140 } output_names[] = { 141 {AudioCoutputs, AudioNmaster}, 142 {AudioCinputs, AudioNdac}, 143 {AudioCoutputs, AudioNdac}, 144 {AudioCoutputs, AudioNoutput} 145 }, input_names[] = { 146 {AudioCrecord, AudioNrecord}, 147 {AudioCrecord, AudioNvolume}, 148 {AudioCinputs, AudioNrecord}, 149 {AudioCinputs, AudioNvolume}, 150 {AudioCinputs, AudioNinput} 151 }; 152 struct audio_device getdev; 153 int i; 154 155 for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) { 156 if (initvol(hdl, &hdl->output, 157 output_names[i].cn, output_names[i].dn)) { 158 hdl->output.name = "output"; 159 hdl->output.base_addr = 0; 160 break; 161 } 162 } 163 for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) { 164 if (initvol(hdl, &hdl->input, 165 input_names[i].cn, input_names[i].dn)) { 166 hdl->input.name = "input"; 167 hdl->input.base_addr = 64; 168 break; 169 } 170 } 171 172 hdl->display_addr = 128; 173 if (ioctl(hdl->fd, AUDIO_GETDEV, &getdev) == -1) 174 strlcpy(hdl->display, "unknown", SIOCTL_DISPLAYMAX); 175 else 176 strlcpy(hdl->display, getdev.name, SIOCTL_DISPLAYMAX); 177 DPRINTF("init: server.device: display = %s\n", hdl->display); 178 } 179 180 static int 181 setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val) 182 { 183 struct mixer_ctrl ctrl; 184 int i; 185 186 addr -= vol->base_addr; 187 if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) { 188 if (vol->level_val[addr] == val) { 189 DPRINTF("level %d, no change\n", val); 190 return 1; 191 } 192 vol->level_val[addr] = val; 193 ctrl.dev = vol->level_idx; 194 ctrl.type = AUDIO_MIXER_VALUE; 195 ctrl.un.value.num_channels = vol->nch; 196 for (i = 0; i < vol->nch; i++) 197 ctrl.un.value.level[i] = vol->level_val[i]; 198 DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]); 199 if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) { 200 DPRINTF("level write failed\n"); 201 return 0; 202 } 203 _sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val); 204 return 1; 205 } 206 207 addr -= 32; 208 if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) { 209 val = val ? 1 : 0; 210 if (vol->mute_val == val) { 211 DPRINTF("mute %d, no change\n", val); 212 return 1; 213 } 214 vol->mute_val = val; 215 ctrl.dev = vol->mute_idx; 216 ctrl.type = AUDIO_MIXER_ENUM; 217 ctrl.un.ord = val; 218 DPRINTF("mute setting to %d\n", val); 219 if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) { 220 DPERROR("mute write\n"); 221 return 0; 222 } 223 for (i = 0; i < vol->nch; i++) { 224 _sioctl_onval_cb(&hdl->sioctl, 225 vol->base_addr + 32 + i, val); 226 } 227 return 1; 228 } 229 return 1; 230 } 231 232 static int 233 scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol) 234 { 235 struct sioctl_desc desc; 236 struct mixer_ctrl ctrl; 237 int i, val; 238 239 memset(&desc, 0, sizeof(struct sioctl_desc)); 240 if (vol->level_idx >= 0) { 241 ctrl.dev = vol->level_idx; 242 ctrl.type = AUDIO_MIXER_VALUE; 243 ctrl.un.value.num_channels = vol->nch; 244 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) { 245 DPRINTF("level read failed\n"); 246 return 0; 247 } 248 desc.type = SIOCTL_NUM; 249 desc.maxval = AUDIO_MAX_GAIN; 250 desc.node1.name[0] = 0; 251 desc.node1.unit = -1; 252 strlcpy(desc.func, "level", SIOCTL_NAMEMAX); 253 strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX); 254 for (i = 0; i < vol->nch; i++) { 255 desc.node0.unit = i; 256 desc.addr = vol->base_addr + i; 257 val = ctrl.un.value.level[i]; 258 vol->level_val[i] = val; 259 _sioctl_ondesc_cb(&hdl->sioctl, &desc, val); 260 } 261 } 262 if (vol->mute_idx >= 0) { 263 ctrl.dev = vol->mute_idx; 264 ctrl.type = AUDIO_MIXER_ENUM; 265 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) { 266 DPRINTF("mute read failed\n"); 267 return 0; 268 } 269 desc.type = SIOCTL_SW; 270 desc.maxval = 1; 271 desc.node1.name[0] = 0; 272 desc.node1.unit = -1; 273 strlcpy(desc.func, "mute", SIOCTL_NAMEMAX); 274 strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX); 275 val = ctrl.un.ord ? 1 : 0; 276 vol->mute_val = val; 277 for (i = 0; i < vol->nch; i++) { 278 desc.node0.unit = i; 279 desc.addr = vol->base_addr + 32 + i; 280 _sioctl_ondesc_cb(&hdl->sioctl, &desc, val); 281 } 282 } 283 return 1; 284 } 285 286 static int 287 updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx) 288 { 289 struct mixer_ctrl ctrl; 290 int val, i; 291 292 if (idx == vol->mute_idx) 293 ctrl.type = AUDIO_MIXER_ENUM; 294 else { 295 ctrl.type = AUDIO_MIXER_VALUE; 296 ctrl.un.value.num_channels = vol->nch; 297 } 298 ctrl.dev = idx; 299 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) { 300 DPERROR("sioctl_sun_revents: ioctl\n"); 301 hdl->sioctl.eof = 1; 302 return 0; 303 } 304 if (idx == vol->mute_idx) { 305 val = ctrl.un.ord ? 1 : 0; 306 if (vol->mute_val == val) 307 return 1; 308 vol->mute_val = val; 309 for (i = 0; i < vol->nch; i++) { 310 _sioctl_onval_cb(&hdl->sioctl, 311 vol->base_addr + 32 + i, val); 312 } 313 } else { 314 for (i = 0; i < vol->nch; i++) { 315 val = ctrl.un.value.level[i]; 316 if (vol->level_val[i] == val) 317 continue; 318 vol->level_val[i] = val; 319 _sioctl_onval_cb(&hdl->sioctl, 320 vol->base_addr + i, val); 321 } 322 } 323 return 1; 324 } 325 326 int 327 sioctl_sun_getfd(const char *str, unsigned int mode, int nbio) 328 { 329 const char *p; 330 char path[DEVPATH_MAX]; 331 unsigned int devnum; 332 int fd, flags; 333 334 #ifdef DEBUG 335 _sndio_debug_init(); 336 #endif 337 p = _sndio_parsetype(str, "rsnd"); 338 if (p == NULL) { 339 DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str); 340 return -1; 341 } 342 switch (*p) { 343 case '/': 344 p++; 345 break; 346 default: 347 DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str); 348 return -1; 349 } 350 if (strcmp(p, "default") == 0) { 351 devnum = 0; 352 } else { 353 p = _sndio_parsenum(p, &devnum, 255); 354 if (p == NULL || *p != '\0') { 355 DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str); 356 return -1; 357 } 358 } 359 snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum); 360 if (mode == (SIOCTL_READ | SIOCTL_WRITE)) 361 flags = O_RDWR; 362 else 363 flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY; 364 while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) { 365 if (errno == EINTR) 366 continue; 367 DPERROR(path); 368 return -1; 369 } 370 return fd; 371 } 372 373 struct sioctl_hdl * 374 sioctl_sun_fdopen(int fd, unsigned int mode, int nbio) 375 { 376 struct sioctl_sun_hdl *hdl; 377 378 #ifdef DEBUG 379 _sndio_debug_init(); 380 #endif 381 hdl = malloc(sizeof(struct sioctl_sun_hdl)); 382 if (hdl == NULL) 383 return NULL; 384 _sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio); 385 hdl->fd = fd; 386 init(hdl); 387 return (struct sioctl_hdl *)hdl; 388 } 389 390 struct sioctl_hdl * 391 _sioctl_sun_open(const char *str, unsigned int mode, int nbio) 392 { 393 struct sioctl_hdl *hdl; 394 int fd; 395 396 fd = sioctl_sun_getfd(str, mode, nbio); 397 if (fd < 0) 398 return NULL; 399 hdl = sioctl_sun_fdopen(fd, mode, nbio); 400 if (hdl != NULL) 401 return hdl; 402 while (close(fd) < 0 && errno == EINTR) 403 ; /* retry */ 404 return NULL; 405 } 406 407 static void 408 sioctl_sun_close(struct sioctl_hdl *addr) 409 { 410 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr; 411 412 close(hdl->fd); 413 free(hdl); 414 } 415 416 static int 417 sioctl_sun_ondesc(struct sioctl_hdl *addr) 418 { 419 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr; 420 struct sioctl_desc desc; 421 422 if (!scanvol(hdl, &hdl->output) || 423 !scanvol(hdl, &hdl->input)) { 424 hdl->sioctl.eof = 1; 425 return 0; 426 } 427 428 /* report "server.device" control */ 429 memset(&desc, 0, sizeof(struct sioctl_desc)); 430 desc.type = SIOCTL_SEL; 431 desc.maxval = 1; 432 strlcpy(desc.func, "device", SIOCTL_NAMEMAX); 433 strlcpy(desc.node0.name, "server", SIOCTL_NAMEMAX); 434 desc.node0.unit = -1; 435 strlcpy(desc.node1.name, "0", SIOCTL_NAMEMAX); 436 desc.node1.unit = -1; 437 strlcpy(desc.display, hdl->display, SIOCTL_DISPLAYMAX); 438 desc.addr = hdl->display_addr; 439 _sioctl_ondesc_cb(&hdl->sioctl, &desc, 1); 440 441 _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0); 442 return 1; 443 } 444 445 static int 446 sioctl_sun_onval(struct sioctl_hdl *addr) 447 { 448 return 1; 449 } 450 451 static int 452 sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val) 453 { 454 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg; 455 456 if (!setvol(hdl, &hdl->output, addr, val) || 457 !setvol(hdl, &hdl->input, addr, val)) { 458 hdl->sioctl.eof = 1; 459 return 0; 460 } 461 return 1; 462 } 463 464 static int 465 sioctl_sun_nfds(struct sioctl_hdl *addr) 466 { 467 return 1; 468 } 469 470 static int 471 sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events) 472 { 473 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr; 474 475 hdl->events = events; 476 477 /* 478 * The audio(4) driver doesn't support POLLOUT, so if it is 479 * requested, don't set the struct pollfd. The AUDIO_MIXER_WRITE 480 * ioctl never blocks, so just return POLLOUT in sioctl_sun_revents(). 481 */ 482 if (events & POLLOUT) 483 return 0; 484 485 pfd->fd = hdl->fd; 486 pfd->events = POLLIN; 487 return 1; 488 } 489 490 static int 491 sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd) 492 { 493 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg; 494 struct volume *vol; 495 int idx, n; 496 497 if (hdl->events & POLLOUT) 498 return POLLOUT; 499 500 if (pfd->revents & POLLIN) { 501 while (1) { 502 n = read(hdl->fd, &idx, sizeof(int)); 503 if (n == -1) { 504 if (errno == EINTR || errno == EAGAIN) 505 break; 506 DPERROR("read"); 507 hdl->sioctl.eof = 1; 508 return POLLHUP; 509 } 510 if (n < sizeof(int)) { 511 DPRINTF("sioctl_sun_revents: short read\n"); 512 hdl->sioctl.eof = 1; 513 return POLLHUP; 514 } 515 516 if (idx == hdl->output.level_idx || 517 idx == hdl->output.mute_idx) { 518 vol = &hdl->output; 519 } else if (idx == hdl->input.level_idx || 520 idx == hdl->input.mute_idx) { 521 vol = &hdl->input; 522 } else 523 continue; 524 525 if (!updatevol(hdl, vol, idx)) 526 return POLLHUP; 527 } 528 } 529 return 0; 530 } 531