1113835Simp/* 2113835Simp * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org> 3113835Simp * 4113835Simp * Permission to use, copy, modify, and distribute this software for any 5113835Simp * purpose with or without fee is hereby granted, provided that the above 6113835Simp * copyright notice and this permission notice appear in all copies. 7113835Simp * 8113835Simp * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9113835Simp * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10113835Simp * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11113835Simp * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12113835Simp * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13113835Simp * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14113835Simp * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15113835Simp */ 16113835Simp/* 17113835Simp * the way the sun mixer is designed doesn't let us representing 18113835Simp * it easily with the sioctl api. For now expose only few 19113835Simp * white-listed controls the same way as we do in kernel 20113835Simp * for the wskbd volume keys. 21113835Simp */ 22113835Simp#include <sys/types.h> 23113835Simp#include <sys/ioctl.h> 24113835Simp#include <sys/audioio.h> 25113835Simp#include <errno.h> 26113835Simp#include <fcntl.h> 27113835Simp#include <limits.h> 28113835Simp#include <poll.h> 29113835Simp#include <sndio.h> 30113835Simp#include <stdio.h> 31113835Simp#include <stdlib.h> 32113835Simp#include <string.h> 33113835Simp#include <unistd.h> 34113835Simp 35113835Simp#include "debug.h" 36113835Simp#include "sioctl_priv.h" 37113835Simp 38115418Sru#define DEVPATH_PREFIX "/dev/audioctl" 39113835Simp#define DEVPATH_MAX (1 + \ 40113835Simp sizeof(DEVPATH_PREFIX) - 1 + \ 41115418Sru sizeof(int) * 3) 42113835Simp 43113835Simpstruct volume 44113835Simp{ 45113835Simp int nch; /* channels in the level control */ 46115418Sru int level_idx; /* index of the level control */ 47113835Simp int level_val[8]; /* current value */ 48113835Simp int mute_idx; /* index of the mute control */ 49115418Sru int mute_val; /* per channel state of mute control */ 50115418Sru int base_addr; 51113835Simp char *name; 52113835Simp}; 53113835Simp 54113835Simpstruct sioctl_sun_hdl { 55113835Simp struct sioctl_hdl sioctl; 56113835Simp char display[SIOCTL_DISPLAYMAX]; 57113835Simp int display_addr; 58113835Simp struct volume output, input; 59 int fd, events; 60}; 61 62static void sioctl_sun_close(struct sioctl_hdl *); 63static int sioctl_sun_nfds(struct sioctl_hdl *); 64static int sioctl_sun_pollfd(struct sioctl_hdl *, struct pollfd *, int); 65static int sioctl_sun_revents(struct sioctl_hdl *, struct pollfd *); 66static int sioctl_sun_setctl(struct sioctl_hdl *, unsigned int, unsigned int); 67static int sioctl_sun_onval(struct sioctl_hdl *); 68static int sioctl_sun_ondesc(struct sioctl_hdl *); 69 70/* 71 * operations every device should support 72 */ 73struct 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 83static int 84initmute(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 108static int 109initvol(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 135static void 136init(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 180static int 181setvol(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 232static int 233scanvol(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 286static int 287updatevol(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 326int 327sioctl_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 373struct sioctl_hdl * 374sioctl_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 390struct 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 407static void 408sioctl_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 416static int 417sioctl_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 445static int 446sioctl_sun_onval(struct sioctl_hdl *addr) 447{ 448 return 1; 449} 450 451static int 452sioctl_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 464static int 465sioctl_sun_nfds(struct sioctl_hdl *addr) 466{ 467 return 1; 468} 469 470static int 471sioctl_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 pfd->fd = hdl->fd; 476 pfd->events = POLLIN; 477 hdl->events = events; 478 return 1; 479} 480 481static int 482sioctl_sun_revents(struct sioctl_hdl *arg, struct pollfd *pfd) 483{ 484 struct sioctl_sun_hdl *hdl = (struct sioctl_sun_hdl *)arg; 485 struct volume *vol; 486 int idx, n; 487 488 if (pfd->revents & POLLIN) { 489 while (1) { 490 n = read(hdl->fd, &idx, sizeof(int)); 491 if (n == -1) { 492 if (errno == EINTR || errno == EAGAIN) 493 break; 494 DPERROR("read"); 495 hdl->sioctl.eof = 1; 496 return POLLHUP; 497 } 498 if (n < sizeof(int)) { 499 DPRINTF("sioctl_sun_revents: short read\n"); 500 hdl->sioctl.eof = 1; 501 return POLLHUP; 502 } 503 504 if (idx == hdl->output.level_idx || 505 idx == hdl->output.mute_idx) { 506 vol = &hdl->output; 507 } else if (idx == hdl->input.level_idx || 508 idx == hdl->input.mute_idx) { 509 vol = &hdl->input; 510 } else 511 continue; 512 513 if (!updatevol(hdl, vol, idx)) 514 return POLLHUP; 515 } 516 } 517 return hdl->events & POLLOUT; 518} 519