1 /* $OpenBSD: sio.c,v 1.25 2020/11/19 08:14:19 ratchov Exp $ */ 2 /* 3 * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/types.h> 19 20 #include <errno.h> 21 #include <fcntl.h> 22 #include <poll.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <time.h> 27 #include <unistd.h> 28 29 #include "debug.h" 30 #include "sio_priv.h" 31 32 #define SIO_PAR_MAGIC 0x83b905a4 33 34 void 35 sio_initpar(struct sio_par *par) 36 { 37 memset(par, 0xff, sizeof(struct sio_par)); 38 par->__magic = SIO_PAR_MAGIC; 39 } 40 41 struct sio_hdl * 42 sio_open(const char *str, unsigned int mode, int nbio) 43 { 44 static char devany[] = SIO_DEVANY; 45 struct sio_hdl *hdl; 46 47 #ifdef DEBUG 48 _sndio_debug_init(); 49 #endif 50 if ((mode & (SIO_PLAY | SIO_REC)) == 0) 51 return NULL; 52 if (str == NULL) /* backward compat */ 53 str = devany; 54 if (strcmp(str, devany) == 0 && !issetugid()) { 55 if ((mode & SIO_PLAY) == 0) 56 str = getenv("AUDIORECDEVICE"); 57 if ((mode & SIO_REC) == 0) 58 str = getenv("AUDIOPLAYDEVICE"); 59 if (mode == (SIO_PLAY | SIO_REC) || str == NULL) 60 str = getenv("AUDIODEVICE"); 61 if (str == NULL) 62 str = devany; 63 } 64 if (strcmp(str, devany) == 0) { 65 hdl = _sio_aucat_open("snd/0", mode, nbio); 66 if (hdl != NULL) 67 return hdl; 68 return _sio_sun_open("rsnd/0", mode, nbio); 69 } 70 if (_sndio_parsetype(str, "snd")) 71 return _sio_aucat_open(str, mode, nbio); 72 if (_sndio_parsetype(str, "rsnd")) 73 return _sio_sun_open(str, mode, nbio); 74 DPRINTF("sio_open: %s: unknown device type\n", str); 75 return NULL; 76 } 77 78 void 79 _sio_create(struct sio_hdl *hdl, struct sio_ops *ops, 80 unsigned int mode, int nbio) 81 { 82 hdl->ops = ops; 83 hdl->mode = mode; 84 hdl->nbio = nbio; 85 hdl->started = 0; 86 hdl->eof = 0; 87 hdl->move_cb = NULL; 88 hdl->vol_cb = NULL; 89 } 90 91 void 92 sio_close(struct sio_hdl *hdl) 93 { 94 hdl->ops->close(hdl); 95 } 96 97 int 98 sio_start(struct sio_hdl *hdl) 99 { 100 #ifdef DEBUG 101 struct timespec ts; 102 #endif 103 104 if (hdl->eof) { 105 DPRINTF("sio_start: eof\n"); 106 return 0; 107 } 108 if (hdl->started) { 109 DPRINTF("sio_start: already started\n"); 110 hdl->eof = 1; 111 return 0; 112 } 113 hdl->cpos = 0; 114 hdl->rused = hdl->wused = 0; 115 if (!sio_getpar(hdl, &hdl->par)) 116 return 0; 117 #ifdef DEBUG 118 hdl->pollcnt = 0; 119 clock_gettime(CLOCK_MONOTONIC, &ts); 120 hdl->start_nsec = 1000000000LL * ts.tv_sec + ts.tv_nsec; 121 #endif 122 hdl->rdrop = hdl->wsil = 0; 123 if (!hdl->ops->start(hdl)) 124 return 0; 125 hdl->started = 1; 126 return 1; 127 } 128 129 int 130 sio_stop(struct sio_hdl *hdl) 131 { 132 if (hdl->eof) { 133 DPRINTF("sio_stop: eof\n"); 134 return 0; 135 } 136 if (!hdl->started) { 137 DPRINTF("sio_stop: not started\n"); 138 hdl->eof = 1; 139 return 0; 140 } 141 if (!hdl->ops->stop(hdl)) 142 return 0; 143 #ifdef DEBUG 144 DPRINTFN(2, "libsndio: polls: %llu, samples = %llu\n", 145 hdl->pollcnt, hdl->cpos); 146 #endif 147 hdl->started = 0; 148 return 1; 149 } 150 151 int 152 sio_setpar(struct sio_hdl *hdl, struct sio_par *par) 153 { 154 if (hdl->eof) { 155 DPRINTF("sio_setpar: eof\n"); 156 return 0; 157 } 158 if (par->__magic != SIO_PAR_MAGIC) { 159 DPRINTF("sio_setpar: uninitialized sio_par structure\n"); 160 hdl->eof = 1; 161 return 0; 162 } 163 if (hdl->started) { 164 DPRINTF("sio_setpar: already started\n"); 165 hdl->eof = 1; 166 return 0; 167 } 168 if (par->bufsz != ~0U) { 169 DPRINTF("sio_setpar: setting bufsz is deprecated\n"); 170 par->appbufsz = par->bufsz; 171 par->bufsz = ~0U; 172 } 173 if (par->rate != ~0U && par->appbufsz == ~0U) 174 par->appbufsz = par->rate * 200 / 1000; 175 return hdl->ops->setpar(hdl, par); 176 } 177 178 int 179 sio_getpar(struct sio_hdl *hdl, struct sio_par *par) 180 { 181 if (hdl->eof) { 182 DPRINTF("sio_getpar: eof\n"); 183 return 0; 184 } 185 if (hdl->started) { 186 DPRINTF("sio_getpar: already started\n"); 187 hdl->eof = 1; 188 return 0; 189 } 190 if (!hdl->ops->getpar(hdl, par)) { 191 par->__magic = 0; 192 return 0; 193 } 194 par->__magic = 0; 195 return 1; 196 } 197 198 int 199 sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap) 200 { 201 if (hdl->eof) { 202 DPRINTF("sio_getcap: eof\n"); 203 return 0; 204 } 205 if (hdl->started) { 206 DPRINTF("sio_getcap: already started\n"); 207 hdl->eof = 1; 208 return 0; 209 } 210 return hdl->ops->getcap(hdl, cap); 211 } 212 213 static int 214 sio_psleep(struct sio_hdl *hdl, int event) 215 { 216 struct pollfd pfd[SIO_MAXNFDS]; 217 int revents; 218 int nfds; 219 220 nfds = sio_nfds(hdl); 221 if (nfds > SIO_MAXNFDS) { 222 DPRINTF("sio_psleep: %d: too many descriptors\n", nfds); 223 hdl->eof = 1; 224 return 0; 225 } 226 for (;;) { 227 nfds = sio_pollfd(hdl, pfd, event); 228 while (poll(pfd, nfds, -1) == -1) { 229 if (errno == EINTR) 230 continue; 231 DPERROR("sio_psleep: poll"); 232 hdl->eof = 1; 233 return 0; 234 } 235 revents = sio_revents(hdl, pfd); 236 if (revents & POLLHUP) { 237 DPRINTF("sio_psleep: hang-up\n"); 238 return 0; 239 } 240 if (revents & event) 241 break; 242 } 243 return 1; 244 } 245 246 static int 247 sio_rdrop(struct sio_hdl *hdl) 248 { 249 #define DROP_NMAX 0x1000 250 static char dummy[DROP_NMAX]; 251 ssize_t n, todo; 252 253 while (hdl->rdrop > 0) { 254 todo = hdl->rdrop; 255 if (todo > DROP_NMAX) 256 todo = DROP_NMAX; 257 n = hdl->ops->read(hdl, dummy, todo); 258 if (n == 0) 259 return 0; 260 hdl->rdrop -= n; 261 DPRINTF("sio_rdrop: dropped %zu bytes\n", n); 262 } 263 return 1; 264 } 265 266 static int 267 sio_wsil(struct sio_hdl *hdl) 268 { 269 #define ZERO_NMAX 0x1000 270 static char zero[ZERO_NMAX]; 271 ssize_t n, todo; 272 273 while (hdl->wsil > 0) { 274 todo = hdl->wsil; 275 if (todo > ZERO_NMAX) 276 todo = ZERO_NMAX; 277 n = hdl->ops->write(hdl, zero, todo); 278 if (n == 0) 279 return 0; 280 hdl->wsil -= n; 281 DPRINTF("sio_wsil: inserted %zu bytes\n", n); 282 } 283 return 1; 284 } 285 286 size_t 287 sio_read(struct sio_hdl *hdl, void *buf, size_t len) 288 { 289 unsigned int n; 290 char *data = buf; 291 size_t todo = len, maxread; 292 293 if (hdl->eof) { 294 DPRINTF("sio_read: eof\n"); 295 return 0; 296 } 297 if (!hdl->started || !(hdl->mode & SIO_REC)) { 298 DPRINTF("sio_read: recording not started\n"); 299 hdl->eof = 1; 300 return 0; 301 } 302 while (todo > 0) { 303 if (!sio_rdrop(hdl)) 304 return 0; 305 maxread = hdl->rused; 306 if (maxread > todo) 307 maxread = todo; 308 n = maxread > 0 ? hdl->ops->read(hdl, data, maxread) : 0; 309 if (n == 0) { 310 if (hdl->nbio || hdl->eof || todo < len) 311 break; 312 if (!sio_psleep(hdl, POLLIN)) 313 break; 314 continue; 315 } 316 data += n; 317 todo -= n; 318 hdl->rused -= n; 319 } 320 return len - todo; 321 } 322 323 size_t 324 sio_write(struct sio_hdl *hdl, const void *buf, size_t len) 325 { 326 unsigned int n; 327 const unsigned char *data = buf; 328 size_t todo = len, maxwrite; 329 330 if (hdl->eof) { 331 DPRINTF("sio_write: eof\n"); 332 return 0; 333 } 334 if (!hdl->started || !(hdl->mode & SIO_PLAY)) { 335 DPRINTF("sio_write: playback not started\n"); 336 hdl->eof = 1; 337 return 0; 338 } 339 while (todo > 0) { 340 if (!sio_wsil(hdl)) 341 return 0; 342 maxwrite = hdl->par.bufsz * hdl->par.pchan * hdl->par.bps - 343 hdl->wused; 344 if (maxwrite > todo) 345 maxwrite = todo; 346 n = maxwrite > 0 ? hdl->ops->write(hdl, data, maxwrite) : 0; 347 if (n == 0) { 348 if (hdl->nbio || hdl->eof) 349 break; 350 if (!sio_psleep(hdl, POLLOUT)) 351 break; 352 continue; 353 } 354 data += n; 355 todo -= n; 356 hdl->wused += n; 357 } 358 return len - todo; 359 } 360 361 int 362 sio_nfds(struct sio_hdl *hdl) 363 { 364 return hdl->ops->nfds(hdl); 365 } 366 367 int 368 sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events) 369 { 370 if (hdl->eof) 371 return 0; 372 if (!hdl->started) 373 events = 0; 374 return hdl->ops->pollfd(hdl, pfd, events); 375 } 376 377 int 378 sio_revents(struct sio_hdl *hdl, struct pollfd *pfd) 379 { 380 int revents; 381 #ifdef DEBUG 382 struct timespec ts0, ts1; 383 384 if (_sndio_debug >= 4) 385 clock_gettime(CLOCK_MONOTONIC, &ts0); 386 #endif 387 if (hdl->eof) 388 return POLLHUP; 389 #ifdef DEBUG 390 hdl->pollcnt++; 391 #endif 392 revents = hdl->ops->revents(hdl, pfd); 393 if (!hdl->started) 394 return revents & POLLHUP; 395 #ifdef DEBUG 396 if (_sndio_debug >= 4) { 397 clock_gettime(CLOCK_MONOTONIC, &ts1); 398 DPRINTF("%09lld: sio_revents: revents = 0x%x, took %lldns\n", 399 1000000000LL * ts0.tv_sec + 400 ts0.tv_nsec - hdl->start_nsec, 401 revents, 402 1000000000LL * (ts1.tv_sec - ts0.tv_sec) + 403 ts1.tv_nsec - ts0.tv_nsec); 404 } 405 #endif 406 if ((hdl->mode & SIO_PLAY) && !sio_wsil(hdl)) 407 revents &= ~POLLOUT; 408 if ((hdl->mode & SIO_REC) && !sio_rdrop(hdl)) 409 revents &= ~POLLIN; 410 return revents; 411 } 412 413 int 414 sio_eof(struct sio_hdl *hdl) 415 { 416 return hdl->eof; 417 } 418 419 void 420 sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr) 421 { 422 if (hdl->started) { 423 DPRINTF("sio_onmove: already started\n"); 424 hdl->eof = 1; 425 return; 426 } 427 hdl->move_cb = cb; 428 hdl->move_addr = addr; 429 } 430 431 #ifdef DEBUG 432 void 433 _sio_printpos(struct sio_hdl *hdl) 434 { 435 struct timespec ts; 436 long long rpos, rdiff; 437 long long cpos, cdiff; 438 long long wpos, wdiff; 439 unsigned rbpf, wbpf, rround, wround; 440 441 clock_gettime(CLOCK_MONOTONIC, &ts); 442 rbpf = (hdl->mode & SIO_REC) ? hdl->par.bps * hdl->par.rchan : 1; 443 wbpf = (hdl->mode & SIO_PLAY) ? hdl->par.bps * hdl->par.pchan : 1; 444 rround = hdl->par.round * rbpf; 445 wround = hdl->par.round * wbpf; 446 447 rpos = (hdl->mode & SIO_REC) ? 448 hdl->cpos * rbpf - hdl->rused : 0; 449 wpos = (hdl->mode & SIO_PLAY) ? 450 hdl->cpos * wbpf + hdl->wused : 0; 451 452 cdiff = hdl->cpos % hdl->par.round; 453 cpos = hdl->cpos / hdl->par.round; 454 if (cdiff > hdl->par.round / 2) { 455 cpos++; 456 cdiff = cdiff - hdl->par.round; 457 } 458 rdiff = rpos % rround; 459 rpos = rpos / rround; 460 if (rdiff > rround / 2) { 461 rpos++; 462 rdiff = rdiff - rround; 463 } 464 wdiff = wpos % wround; 465 wpos = wpos / wround; 466 if (wdiff > wround / 2) { 467 wpos++; 468 wdiff = wdiff - wround; 469 } 470 DPRINTF("%011lld: " 471 "clk %+5lld%+5lld, wr %+5lld%+5lld rd: %+5lld%+5lld\n", 472 1000000000LL * ts.tv_sec + ts.tv_nsec - hdl->start_nsec, 473 cpos, cdiff, wpos, wdiff, rpos, rdiff); 474 } 475 #endif 476 477 void 478 _sio_onmove_cb(struct sio_hdl *hdl, int delta) 479 { 480 hdl->cpos += delta; 481 if (hdl->mode & SIO_REC) 482 hdl->rused += delta * (hdl->par.bps * hdl->par.rchan); 483 if (hdl->mode & SIO_PLAY) 484 hdl->wused -= delta * (hdl->par.bps * hdl->par.pchan); 485 #ifdef DEBUG 486 if (_sndio_debug >= 3) 487 _sio_printpos(hdl); 488 if ((hdl->mode & SIO_PLAY) && hdl->wused < 0) { 489 DPRINTFN(1, "sndio: h/w failure: negative buffer usage\n"); 490 hdl->eof = 1; 491 return; 492 } 493 #endif 494 if (hdl->move_cb) 495 hdl->move_cb(hdl->move_addr, delta); 496 } 497 498 int 499 sio_setvol(struct sio_hdl *hdl, unsigned int ctl) 500 { 501 if (hdl->eof) 502 return 0; 503 if (!hdl->ops->setvol) 504 return 1; 505 if (!hdl->ops->setvol(hdl, ctl)) 506 return 0; 507 hdl->ops->getvol(hdl); 508 return 1; 509 } 510 511 int 512 sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned int), void *addr) 513 { 514 if (hdl->started) { 515 DPRINTF("sio_onvol: already started\n"); 516 hdl->eof = 1; 517 return 0; 518 } 519 if (!hdl->ops->setvol) 520 return 0; 521 hdl->vol_cb = cb; 522 hdl->vol_addr = addr; 523 hdl->ops->getvol(hdl); 524 return 1; 525 } 526 527 void 528 _sio_onvol_cb(struct sio_hdl *hdl, unsigned int ctl) 529 { 530 if (hdl->vol_cb) 531 hdl->vol_cb(hdl->vol_addr, ctl); 532 } 533