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 struct volume output, input; 57 int fd, events; 58 }; 59 60 static void sioctl_sun_close(struct sioctl_hdl *); 61 static int sioctl_sun_nfds(struct sioctl_hdl *); 62 static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int); 63 static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *); 64 static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int); 65 static int sioctl_sun_onval(struct sioctl_hdl *); 66 static int sioctl_sun_ondesc(struct sioctl_hdl *); 67 68 /* 69 * operations every device should support 70 */ 71 struct sioctl_ops sioctl_sun_ops = { 72 sioctl_sun_close, 73 sioctl_sun_nfds, 74 sioctl_sun_pollfd, 75 sioctl_sun_revents, 76 sioctl_sun_setctl, 77 sioctl_sun_onval, 78 sioctl_sun_ondesc 79 }; 80 81 static int 82 initmute(struct sioctl_sun_hdl *hdl, struct mixer_devinfo *info) 83 { 84 struct mixer_devinfo mi; 85 86 mi.index = info->next; 87 for (mi.index = info->next; mi.index != -1; mi.index = mi.next) { 88 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &mi) < 0) 89 break; 90 if (strcmp(mi.label.name, AudioNmute) == 0) 91 return mi.index; 92 } 93 return -1; 94 } 95 96 static int 97 initvol(struct sioctl_sun_hdl *hdl, struct volume *vol, char *cn, char *dn) 98 { 99 struct mixer_devinfo dev, cls; 100 101 for (dev.index = 0; ; dev.index++) { 102 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &dev) < 0) 103 break; 104 if (dev.type != AUDIO_MIXER_VALUE) 105 continue; 106 cls.index = dev.mixer_class; 107 if (ioctl(hdl->fd, AUDIO_MIXER_DEVINFO, &cls) < 0) 108 break; 109 if (strcmp(cls.label.name, cn) == 0 && 110 strcmp(dev.label.name, dn) == 0) { 111 vol->nch = dev.un.v.num_channels; 112 vol->level_idx = dev.index; 113 vol->mute_idx = initmute(hdl, &dev); 114 DPRINTF("using %s.%s, %d channels, %s\n", cn, dn, 115 vol->nch, vol->mute_idx >= 0 ? "mute" : "no mute"); 116 return 1; 117 } 118 } 119 vol->level_idx = vol->mute_idx = -1; 120 return 0; 121 } 122 123 static void 124 init(struct sioctl_sun_hdl *hdl) 125 { 126 static struct { 127 char *cn, *dn; 128 } output_names[] = { 129 {AudioCoutputs, AudioNmaster}, 130 {AudioCinputs, AudioNdac}, 131 {AudioCoutputs, AudioNdac}, 132 {AudioCoutputs, AudioNoutput} 133 }, input_names[] = { 134 {AudioCrecord, AudioNrecord}, 135 {AudioCrecord, AudioNvolume}, 136 {AudioCinputs, AudioNrecord}, 137 {AudioCinputs, AudioNvolume}, 138 {AudioCinputs, AudioNinput} 139 }; 140 int i; 141 142 for (i = 0; i < sizeof(output_names) / sizeof(output_names[0]); i++) { 143 if (initvol(hdl, &hdl->output, 144 output_names[i].cn, output_names[i].dn)) { 145 hdl->output.name = "output"; 146 hdl->output.base_addr = 0; 147 break; 148 } 149 } 150 for (i = 0; i < sizeof(input_names) / sizeof(input_names[0]); i++) { 151 if (initvol(hdl, &hdl->input, 152 input_names[i].cn, input_names[i].dn)) { 153 hdl->input.name = "input"; 154 hdl->input.base_addr = 64; 155 break; 156 } 157 } 158 } 159 160 static int 161 setvol(struct sioctl_sun_hdl *hdl, struct volume *vol, int addr, int val) 162 { 163 struct mixer_ctrl ctrl; 164 int i; 165 166 addr -= vol->base_addr; 167 if (vol->level_idx >= 0 && addr >= 0 && addr < vol->nch) { 168 if (vol->level_val[addr] == val) { 169 DPRINTF("level %d, no change\n", val); 170 return 1; 171 } 172 vol->level_val[addr] = val; 173 ctrl.dev = vol->level_idx; 174 ctrl.type = AUDIO_MIXER_VALUE; 175 ctrl.un.value.num_channels = vol->nch; 176 for (i = 0; i < vol->nch; i++) 177 ctrl.un.value.level[i] = vol->level_val[i]; 178 DPRINTF("vol %d setting to %d\n", addr, vol->level_val[addr]); 179 if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) { 180 DPRINTF("level write failed\n"); 181 return 0; 182 } 183 _sioctl_onval_cb(&hdl->sioctl, vol->base_addr + addr, val); 184 return 1; 185 } 186 187 addr -= 32; 188 if (vol->mute_idx >= 0 && addr >= 0 && addr < vol->nch) { 189 val = val ? 1 : 0; 190 if (vol->mute_val == val) { 191 DPRINTF("mute %d, no change\n", val); 192 return 1; 193 } 194 vol->mute_val = val; 195 ctrl.dev = vol->mute_idx; 196 ctrl.type = AUDIO_MIXER_ENUM; 197 ctrl.un.ord = val; 198 DPRINTF("mute setting to %d\n", val); 199 if (ioctl(hdl->fd, AUDIO_MIXER_WRITE, &ctrl) < 0) { 200 DPERROR("mute write\n"); 201 return 0; 202 } 203 for (i = 0; i < vol->nch; i++) { 204 _sioctl_onval_cb(&hdl->sioctl, 205 vol->base_addr + 32 + i, val); 206 } 207 return 1; 208 } 209 return 1; 210 } 211 212 static int 213 scanvol(struct sioctl_sun_hdl *hdl, struct volume *vol) 214 { 215 struct sioctl_desc desc; 216 struct mixer_ctrl ctrl; 217 int i, val; 218 219 memset(&desc, 0, sizeof(struct sioctl_desc)); 220 if (vol->level_idx >= 0) { 221 ctrl.dev = vol->level_idx; 222 ctrl.type = AUDIO_MIXER_VALUE; 223 ctrl.un.value.num_channels = vol->nch; 224 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) { 225 DPRINTF("level read failed\n"); 226 return 0; 227 } 228 desc.type = SIOCTL_NUM; 229 desc.maxval = AUDIO_MAX_GAIN; 230 desc.node1.name[0] = 0; 231 desc.node1.unit = -1; 232 strlcpy(desc.func, "level", SIOCTL_NAMEMAX); 233 strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX); 234 for (i = 0; i < vol->nch; i++) { 235 desc.node0.unit = i; 236 desc.addr = vol->base_addr + i; 237 val = ctrl.un.value.level[i]; 238 vol->level_val[i] = val; 239 _sioctl_ondesc_cb(&hdl->sioctl, &desc, val); 240 } 241 } 242 if (vol->mute_idx >= 0) { 243 ctrl.dev = vol->mute_idx; 244 ctrl.type = AUDIO_MIXER_ENUM; 245 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) < 0) { 246 DPRINTF("mute read failed\n"); 247 return 0; 248 } 249 desc.type = SIOCTL_SW; 250 desc.maxval = 1; 251 desc.node1.name[0] = 0; 252 desc.node1.unit = -1; 253 strlcpy(desc.func, "mute", SIOCTL_NAMEMAX); 254 strlcpy(desc.node0.name, vol->name, SIOCTL_NAMEMAX); 255 val = ctrl.un.ord ? 1 : 0; 256 vol->mute_val = val; 257 for (i = 0; i < vol->nch; i++) { 258 desc.node0.unit = i; 259 desc.addr = vol->base_addr + 32 + i; 260 _sioctl_ondesc_cb(&hdl->sioctl, &desc, val); 261 } 262 } 263 return 1; 264 } 265 266 static int 267 updatevol(struct sioctl_sun_hdl *hdl, struct volume *vol, int idx) 268 { 269 struct mixer_ctrl ctrl; 270 int val, i; 271 272 if (idx == vol->mute_idx) 273 ctrl.type = AUDIO_MIXER_ENUM; 274 else { 275 ctrl.type = AUDIO_MIXER_VALUE; 276 ctrl.un.value.num_channels = vol->nch; 277 } 278 ctrl.dev = idx; 279 if (ioctl(hdl->fd, AUDIO_MIXER_READ, &ctrl) == -1) { 280 DPERROR("sioctl_sun_revents: ioctl\n"); 281 hdl->sioctl.eof = 1; 282 return 0; 283 } 284 if (idx == vol->mute_idx) { 285 val = ctrl.un.ord ? 1 : 0; 286 if (vol->mute_val == val) 287 return 1; 288 vol->mute_val = val; 289 for (i = 0; i < vol->nch; i++) { 290 _sioctl_onval_cb(&hdl->sioctl, 291 vol->base_addr + 32 + i, val); 292 } 293 } else { 294 for (i = 0; i < vol->nch; i++) { 295 val = ctrl.un.value.level[i]; 296 if (vol->level_val[i] == val) 297 continue; 298 vol->level_val[i] = val; 299 _sioctl_onval_cb(&hdl->sioctl, 300 vol->base_addr + i, val); 301 } 302 } 303 return 1; 304 } 305 306 int 307 sioctl_sun_getfd(const char *str, unsigned int mode, int nbio) 308 { 309 const char *p; 310 char path[DEVPATH_MAX]; 311 unsigned int devnum; 312 int fd, flags; 313 314 #ifdef DEBUG 315 _sndio_debug_init(); 316 #endif 317 p = _sndio_parsetype(str, "rsnd"); 318 if (p == NULL) { 319 DPRINTF("sioctl_sun_getfd: %s: \"rsnd\" expected\n", str); 320 return -1; 321 } 322 switch (*p) { 323 case '/': 324 p++; 325 break; 326 default: 327 DPRINTF("sioctl_sun_getfd: %s: '/' expected\n", str); 328 return -1; 329 } 330 if (strcmp(p, "default") == 0) { 331 devnum = 0; 332 } else { 333 p = _sndio_parsenum(p, &devnum, 255); 334 if (p == NULL || *p != '\0') { 335 DPRINTF("sioctl_sun_getfd: %s: number expected after '/'\n", str); 336 return -1; 337 } 338 } 339 snprintf(path, sizeof(path), DEVPATH_PREFIX "%u", devnum); 340 if (mode == (SIOCTL_READ | SIOCTL_WRITE)) 341 flags = O_RDWR; 342 else 343 flags = (mode & SIOCTL_WRITE) ? O_WRONLY : O_RDONLY; 344 while ((fd = open(path, flags | O_NONBLOCK | O_CLOEXEC)) < 0) { 345 if (errno == EINTR) 346 continue; 347 DPERROR(path); 348 return -1; 349 } 350 return fd; 351 } 352 353 struct sioctl_hdl * 354 sioctl_sun_fdopen(int fd, unsigned int mode, int nbio) 355 { 356 struct sioctl_sun_hdl *hdl; 357 358 #ifdef DEBUG 359 _sndio_debug_init(); 360 #endif 361 hdl = malloc(sizeof(struct sioctl_sun_hdl)); 362 if (hdl == NULL) 363 return NULL; 364 _sioctl_create(&hdl->sioctl, &sioctl_sun_ops, mode, nbio); 365 hdl->fd = fd; 366 init(hdl); 367 return (struct sioctl_hdl *)hdl; 368 } 369 370 struct sioctl_hdl * 371 _sioctl_sun_open(const char *str, unsigned int mode, int nbio) 372 { 373 struct sioctl_hdl *hdl; 374 int fd; 375 376 fd = sioctl_sun_getfd(str, mode, nbio); 377 if (fd < 0) 378 return NULL; 379 hdl = sioctl_sun_fdopen(fd, mode, nbio); 380 if (hdl != NULL) 381 return hdl; 382 while (close(fd) < 0 && errno == EINTR) 383 ; /* retry */ 384 return NULL; 385 } 386 387 static void 388 sioctl_sun_close(struct sioctl_hdl *addr) 389 { 390 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr; 391 392 close(hdl->fd); 393 free(hdl); 394 } 395 396 static int 397 sioctl_sun_ondesc(struct sioctl_hdl *addr) 398 { 399 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr; 400 401 if (!scanvol(hdl, &hdl->output) || 402 !scanvol(hdl, &hdl->input)) { 403 hdl->sioctl.eof = 1; 404 return 0; 405 } 406 _sioctl_ondesc_cb(&hdl->sioctl, NULL, 0); 407 return 1; 408 } 409 410 static int 411 sioctl_sun_onval(struct sioctl_hdl *addr) 412 { 413 return 1; 414 } 415 416 static int 417 sioctl_sun_setctl(struct sioctl_hdl *arg, unsigned int addr, unsigned int val) 418 { 419 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg; 420 421 if (!setvol(hdl, &hdl->output, addr, val) || 422 !setvol(hdl, &hdl->input, addr, val)) { 423 hdl->sioctl.eof = 1; 424 return 0; 425 } 426 return 1; 427 } 428 429 static int 430 sioctl_sun_nfds(struct sioctl_hdl *addr) 431 { 432 return 1; 433 } 434 435 static int 436 sioctl_sun_pollfd(struct sioctl_hdl *addr, struct pollfd *pfd, int events) 437 { 438 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)addr; 439 440 pfd->fd = hdl->fd; 441 pfd->events = POLLIN; 442 hdl->events = events; 443 return 1; 444 } 445 446 static int 447 sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd) 448 { 449 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg; 450 struct volume *vol; 451 int idx, n; 452 453 if (pfd->revents & POLLIN) { 454 while (1) { 455 n = read(hdl->fd, &idx, sizeof(int)); 456 if (n == -1) { 457 if (errno == EINTR || errno == EAGAIN) 458 break; 459 DPERROR("read"); 460 hdl->sioctl.eof = 1; 461 return POLLHUP; 462 } 463 if (n < sizeof(int)) { 464 DPRINTF("sioctl_sun_revents: short read\n"); 465 hdl->sioctl.eof = 1; 466 return POLLHUP; 467 } 468 469 if (idx == hdl->output.level_idx || 470 idx == hdl->output.mute_idx) { 471 vol = &hdl->output; 472 } else if (idx == hdl->input.level_idx || 473 idx == hdl->input.mute_idx) { 474 vol = &hdl->input; 475 } else 476 continue; 477 478 if (!updatevol(hdl, vol, idx)) 479 return POLLHUP; 480 } 481 } 482 return hdl->events & POLLOUT; 483 } 484