1 /* $OpenBSD: aucat.c,v 1.32 2009/10/26 19:06:28 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 #include <sys/socket.h> 20 #include <sys/un.h> 21 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <poll.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include "amsg.h" 31 #include "sndio_priv.h" 32 33 struct aucat_hdl { 34 struct sio_hdl sio; 35 int fd; /* socket */ 36 struct amsg rmsg, wmsg; /* temporary messages */ 37 size_t wtodo, rtodo; /* bytes to complete the packet */ 38 #define STATE_IDLE 0 /* nothing to do */ 39 #define STATE_MSG 1 /* message being transferred */ 40 #define STATE_DATA 2 /* data being transferred */ 41 unsigned rstate, wstate; /* one of above */ 42 unsigned rbpf, wbpf; /* read and write bytes-per-frame */ 43 int maxwrite; /* latency constraint */ 44 int events; /* events the user requested */ 45 unsigned curvol, reqvol; /* current and requested volume */ 46 unsigned devbufsz; /* server side buffer size (in frames) */ 47 unsigned attached; /* stream attached to device */ 48 int delta; /* some of received deltas */ 49 }; 50 51 static void aucat_close(struct sio_hdl *); 52 static int aucat_start(struct sio_hdl *); 53 static int aucat_stop(struct sio_hdl *); 54 static int aucat_setpar(struct sio_hdl *, struct sio_par *); 55 static int aucat_getpar(struct sio_hdl *, struct sio_par *); 56 static int aucat_getcap(struct sio_hdl *, struct sio_cap *); 57 static size_t aucat_read(struct sio_hdl *, void *, size_t); 58 static size_t aucat_write(struct sio_hdl *, const void *, size_t); 59 static int aucat_pollfd(struct sio_hdl *, struct pollfd *, int); 60 static int aucat_revents(struct sio_hdl *, struct pollfd *); 61 static int aucat_setvol(struct sio_hdl *, unsigned); 62 static void aucat_getvol(struct sio_hdl *); 63 64 static struct sio_ops aucat_ops = { 65 aucat_close, 66 aucat_setpar, 67 aucat_getpar, 68 aucat_getcap, 69 aucat_write, 70 aucat_read, 71 aucat_start, 72 aucat_stop, 73 aucat_pollfd, 74 aucat_revents, 75 aucat_setvol, 76 aucat_getvol 77 }; 78 79 /* 80 * read a message, return 0 if blocked 81 */ 82 static int 83 aucat_rmsg(struct aucat_hdl *hdl) 84 { 85 ssize_t n; 86 unsigned char *data; 87 88 while (hdl->rtodo > 0) { 89 data = (unsigned char *)&hdl->rmsg; 90 data += sizeof(struct amsg) - hdl->rtodo; 91 while ((n = read(hdl->fd, data, hdl->rtodo)) < 0) { 92 if (errno == EINTR) 93 continue; 94 if (errno != EAGAIN) { 95 hdl->sio.eof = 1; 96 DPERROR("aucat_rmsg: read"); 97 } 98 return 0; 99 } 100 if (n == 0) { 101 DPRINTF("aucat_rmsg: eof\n"); 102 hdl->sio.eof = 1; 103 return 0; 104 } 105 hdl->rtodo -= n; 106 } 107 return 1; 108 } 109 110 /* 111 * write a message, return 0 if blocked 112 */ 113 static int 114 aucat_wmsg(struct aucat_hdl *hdl) 115 { 116 ssize_t n; 117 unsigned char *data; 118 119 while (hdl->wtodo > 0) { 120 data = (unsigned char *)&hdl->wmsg; 121 data += sizeof(struct amsg) - hdl->wtodo; 122 while ((n = write(hdl->fd, data, hdl->wtodo)) < 0) { 123 if (errno == EINTR) 124 continue; 125 if (errno != EAGAIN) { 126 hdl->sio.eof = 1; 127 DPERROR("aucat_wmsg: write"); 128 } 129 return 0; 130 } 131 hdl->wtodo -= n; 132 } 133 return 1; 134 } 135 136 /* 137 * execute the next message, return 0 if blocked 138 */ 139 static int 140 aucat_runmsg(struct aucat_hdl *hdl) 141 { 142 if (!aucat_rmsg(hdl)) 143 return 0; 144 switch (hdl->rmsg.cmd) { 145 case AMSG_DATA: 146 if (hdl->rmsg.u.data.size == 0 || 147 hdl->rmsg.u.data.size % hdl->rbpf) { 148 DPRINTF("aucat_runmsg: bad data message\n"); 149 hdl->sio.eof = 1; 150 return 0; 151 } 152 hdl->rstate = STATE_DATA; 153 hdl->rtodo = hdl->rmsg.u.data.size; 154 break; 155 case AMSG_MOVE: 156 if (!hdl->attached) { 157 DPRINTF("aucat_runmsg: attached\n"); 158 hdl->maxwrite += hdl->devbufsz * hdl->wbpf; 159 hdl->attached = 1; 160 } 161 hdl->delta += hdl->rmsg.u.ts.delta; 162 if (hdl->delta >= 0) { 163 hdl->maxwrite += hdl->delta * hdl->wbpf; 164 sio_onmove_cb(&hdl->sio, hdl->delta); 165 hdl->delta = 0; 166 } 167 hdl->rstate = STATE_MSG; 168 hdl->rtodo = sizeof(struct amsg); 169 break; 170 case AMSG_SETVOL: 171 hdl->curvol = hdl->reqvol = hdl->rmsg.u.vol.ctl; 172 sio_onvol_cb(&hdl->sio, hdl->curvol); 173 hdl->rstate = STATE_MSG; 174 hdl->rtodo = sizeof(struct amsg); 175 break; 176 case AMSG_GETPAR: 177 case AMSG_ACK: 178 hdl->rstate = STATE_IDLE; 179 hdl->rtodo = 0xdeadbeef; 180 break; 181 default: 182 DPRINTF("aucat_runmsg: unknown message\n"); 183 hdl->sio.eof = 1; 184 return 0; 185 } 186 return 1; 187 } 188 189 struct sio_hdl * 190 sio_open_aucat(const char *str, unsigned mode, int nbio) 191 { 192 extern char *__progname; 193 int s; 194 char unit[4], *sep, *opt; 195 struct aucat_hdl *hdl; 196 struct sockaddr_un ca; 197 socklen_t len = sizeof(struct sockaddr_un); 198 uid_t uid; 199 200 sep = strchr(str, '.'); 201 if (sep == NULL) { 202 opt = "default"; 203 strlcpy(unit, str, sizeof(unit)); 204 } else { 205 opt = sep + 1; 206 if (sep - str >= sizeof(unit)) { 207 DPRINTF("sio_open_aucat: %s: too long\n", str); 208 return NULL; 209 } 210 strlcpy(unit, str, opt - str); 211 } 212 DPRINTF("sio_open_aucat: trying %s -> %s.%s\n", str, unit, opt); 213 uid = geteuid(); 214 if (strchr(str, '/') != NULL) 215 return NULL; 216 snprintf(ca.sun_path, sizeof(ca.sun_path), 217 "/tmp/aucat-%u/softaudio%s", uid, unit); 218 ca.sun_family = AF_UNIX; 219 220 hdl = malloc(sizeof(struct aucat_hdl)); 221 if (hdl == NULL) 222 return NULL; 223 sio_create(&hdl->sio, &aucat_ops, mode, nbio); 224 225 s = socket(AF_UNIX, SOCK_STREAM, 0); 226 if (s < 0) 227 goto bad_free; 228 while (connect(s, (struct sockaddr *)&ca, len) < 0) { 229 if (errno == EINTR) 230 continue; 231 DPERROR("sio_open_aucat: connect"); 232 goto bad_connect; 233 } 234 if (fcntl(s, F_SETFD, FD_CLOEXEC) < 0) { 235 DPERROR("FD_CLOEXEC"); 236 goto bad_connect; 237 } 238 hdl->fd = s; 239 hdl->rstate = STATE_IDLE; 240 hdl->rtodo = 0xdeadbeef; 241 hdl->wstate = STATE_IDLE; 242 hdl->wtodo = 0xdeadbeef; 243 hdl->curvol = SIO_MAXVOL; 244 hdl->reqvol = SIO_MAXVOL; 245 246 /* 247 * say hello to server 248 */ 249 AMSG_INIT(&hdl->wmsg); 250 hdl->wmsg.cmd = AMSG_HELLO; 251 hdl->wmsg.u.hello.version = AMSG_VERSION; 252 hdl->wmsg.u.hello.proto = 0; 253 if (mode & SIO_PLAY) 254 hdl->wmsg.u.hello.proto |= AMSG_PLAY; 255 if (mode & SIO_REC) 256 hdl->wmsg.u.hello.proto |= AMSG_REC; 257 strlcpy(hdl->wmsg.u.hello.who, __progname, 258 sizeof(hdl->wmsg.u.hello.who)); 259 strlcpy(hdl->wmsg.u.hello.opt, opt, 260 sizeof(hdl->wmsg.u.hello.opt)); 261 hdl->wtodo = sizeof(struct amsg); 262 if (!aucat_wmsg(hdl)) 263 goto bad_connect; 264 hdl->rtodo = sizeof(struct amsg); 265 if (!aucat_rmsg(hdl)) { 266 DPRINTF("sio_open_aucat: mode refused\n"); 267 goto bad_connect; 268 } 269 if (hdl->rmsg.cmd != AMSG_ACK) { 270 DPRINTF("sio_open_aucat: protocol err\n"); 271 goto bad_connect; 272 } 273 return (struct sio_hdl *)hdl; 274 bad_connect: 275 while (close(s) < 0 && errno == EINTR) 276 ; /* retry */ 277 bad_free: 278 free(hdl); 279 return NULL; 280 } 281 282 static void 283 aucat_close(struct sio_hdl *sh) 284 { 285 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 286 char dummy[1]; 287 288 if (!hdl->sio.eof && hdl->sio.started) 289 (void)aucat_stop(&hdl->sio); 290 if (!hdl->sio.eof) { 291 AMSG_INIT(&hdl->wmsg); 292 hdl->wmsg.cmd = AMSG_BYE; 293 hdl->wtodo = sizeof(struct amsg); 294 if (!aucat_wmsg(hdl)) 295 goto bad_close; 296 while (read(hdl->fd, dummy, 1) < 0 && errno == EINTR) 297 ; /* nothing */ 298 } 299 bad_close: 300 while (close(hdl->fd) < 0 && errno == EINTR) 301 ; /* nothing */ 302 free(hdl); 303 } 304 305 static int 306 aucat_start(struct sio_hdl *sh) 307 { 308 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 309 struct sio_par par; 310 311 /* 312 * save bpf 313 */ 314 if (!sio_getpar(&hdl->sio, &par)) 315 return 0; 316 hdl->wbpf = par.bps * par.pchan; 317 hdl->rbpf = par.bps * par.rchan; 318 hdl->maxwrite = hdl->wbpf * par.appbufsz; 319 hdl->devbufsz = par.bufsz - par.appbufsz; 320 hdl->attached = 0; 321 hdl->delta = 0; 322 323 AMSG_INIT(&hdl->wmsg); 324 hdl->wmsg.cmd = AMSG_START; 325 hdl->wtodo = sizeof(struct amsg); 326 if (!aucat_wmsg(hdl)) 327 return 0; 328 hdl->rstate = STATE_MSG; 329 hdl->rtodo = sizeof(struct amsg); 330 if (fcntl(hdl->fd, F_SETFL, O_NONBLOCK) < 0) { 331 DPERROR("aucat_start: fcntl(0)"); 332 hdl->sio.eof = 1; 333 return 0; 334 } 335 return 1; 336 } 337 338 static int 339 aucat_stop(struct sio_hdl *sh) 340 { 341 #define ZERO_MAX 0x400 342 static unsigned char zero[ZERO_MAX]; 343 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 344 unsigned n, count, todo; 345 346 if (fcntl(hdl->fd, F_SETFL, 0) < 0) { 347 DPERROR("aucat_stop: fcntl(0)"); 348 hdl->sio.eof = 1; 349 return 0; 350 } 351 352 /* 353 * complete data block in progress 354 */ 355 if (hdl->wstate != STATE_IDLE) { 356 todo = (hdl->wstate == STATE_MSG) ? 357 hdl->wmsg.u.data.size : hdl->wtodo; 358 hdl->maxwrite = todo; 359 memset(zero, 0, ZERO_MAX); 360 while (todo > 0) { 361 count = todo; 362 if (count > ZERO_MAX) 363 count = ZERO_MAX; 364 n = aucat_write(&hdl->sio, zero, count); 365 if (n == 0) 366 return 0; 367 todo -= n; 368 } 369 } 370 371 /* 372 * send stop message 373 */ 374 AMSG_INIT(&hdl->wmsg); 375 hdl->wmsg.cmd = AMSG_STOP; 376 hdl->wtodo = sizeof(struct amsg); 377 if (!aucat_wmsg(hdl)) 378 return 0; 379 if (hdl->rstate == STATE_IDLE) { 380 hdl->rstate = STATE_MSG; 381 hdl->rtodo = sizeof(struct amsg); 382 } 383 384 /* 385 * wait for the STOP ACK 386 */ 387 while (hdl->rstate != STATE_IDLE) { 388 switch (hdl->rstate) { 389 case STATE_MSG: 390 if (!aucat_runmsg(hdl)) 391 return 0; 392 break; 393 case STATE_DATA: 394 if (!aucat_read(&hdl->sio, zero, ZERO_MAX)) 395 return 0; 396 break; 397 } 398 } 399 return 1; 400 } 401 402 static int 403 aucat_setpar(struct sio_hdl *sh, struct sio_par *par) 404 { 405 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 406 407 AMSG_INIT(&hdl->wmsg); 408 hdl->wmsg.cmd = AMSG_SETPAR; 409 hdl->wmsg.u.par.bits = par->bits; 410 hdl->wmsg.u.par.bps = par->bps; 411 hdl->wmsg.u.par.sig = par->sig; 412 hdl->wmsg.u.par.le = par->le; 413 hdl->wmsg.u.par.msb = par->msb; 414 hdl->wmsg.u.par.rate = par->rate; 415 hdl->wmsg.u.par.appbufsz = par->appbufsz; 416 hdl->wmsg.u.par.xrun = par->xrun; 417 if (hdl->sio.mode & SIO_REC) 418 hdl->wmsg.u.par.rchan = par->rchan; 419 if (hdl->sio.mode & SIO_PLAY) 420 hdl->wmsg.u.par.pchan = par->pchan; 421 hdl->wtodo = sizeof(struct amsg); 422 if (!aucat_wmsg(hdl)) 423 return 0; 424 return 1; 425 } 426 427 static int 428 aucat_getpar(struct sio_hdl *sh, struct sio_par *par) 429 { 430 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 431 432 AMSG_INIT(&hdl->wmsg); 433 hdl->wmsg.cmd = AMSG_GETPAR; 434 hdl->wtodo = sizeof(struct amsg); 435 if (!aucat_wmsg(hdl)) 436 return 0; 437 hdl->rtodo = sizeof(struct amsg); 438 if (!aucat_rmsg(hdl)) 439 return 0; 440 if (hdl->rmsg.cmd != AMSG_GETPAR) { 441 DPRINTF("aucat_getpar: protocol err\n"); 442 hdl->sio.eof = 1; 443 return 0; 444 } 445 par->bits = hdl->rmsg.u.par.bits; 446 par->bps = hdl->rmsg.u.par.bps; 447 par->sig = hdl->rmsg.u.par.sig; 448 par->le = hdl->rmsg.u.par.le; 449 par->msb = hdl->rmsg.u.par.msb; 450 par->rate = hdl->rmsg.u.par.rate; 451 par->bufsz = hdl->rmsg.u.par.bufsz; 452 par->appbufsz = hdl->rmsg.u.par.appbufsz; 453 par->xrun = hdl->rmsg.u.par.xrun; 454 par->round = hdl->rmsg.u.par.round; 455 if (hdl->sio.mode & SIO_PLAY) 456 par->pchan = hdl->rmsg.u.par.pchan; 457 if (hdl->sio.mode & SIO_REC) 458 par->rchan = hdl->rmsg.u.par.rchan; 459 return 1; 460 } 461 462 static int 463 aucat_getcap(struct sio_hdl *sh, struct sio_cap *cap) 464 { 465 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 466 467 AMSG_INIT(&hdl->wmsg); 468 hdl->wmsg.cmd = AMSG_GETCAP; 469 hdl->wtodo = sizeof(struct amsg); 470 if (!aucat_wmsg(hdl)) 471 return 0; 472 hdl->rtodo = sizeof(struct amsg); 473 if (!aucat_rmsg(hdl)) 474 return 0; 475 if (hdl->rmsg.cmd != AMSG_GETCAP) { 476 DPRINTF("aucat_getcap: protocol err\n"); 477 hdl->sio.eof = 1; 478 return 0; 479 } 480 cap->enc[0].bits = hdl->rmsg.u.cap.bits; 481 cap->enc[0].bps = SIO_BPS(hdl->rmsg.u.cap.bits); 482 cap->enc[0].sig = 1; 483 cap->enc[0].le = SIO_LE_NATIVE; 484 cap->enc[0].msb = 1; 485 cap->rchan[0] = hdl->rmsg.u.cap.rchan; 486 cap->pchan[0] = hdl->rmsg.u.cap.pchan; 487 cap->rate[0] = hdl->rmsg.u.cap.rate; 488 cap->confs[0].enc = 1; 489 cap->confs[0].rchan = (hdl->rmsg.u.cap.rchan > 0) ? 1 : 0; 490 cap->confs[0].pchan = (hdl->rmsg.u.cap.pchan > 0) ? 1 : 0; 491 cap->confs[0].rate = 1; 492 cap->nconf = 1; 493 return 1; 494 } 495 496 static size_t 497 aucat_read(struct sio_hdl *sh, void *buf, size_t len) 498 { 499 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 500 ssize_t n; 501 502 while (hdl->rstate != STATE_DATA) { 503 switch (hdl->rstate) { 504 case STATE_MSG: 505 if (!aucat_runmsg(hdl)) 506 return 0; 507 break; 508 case STATE_IDLE: 509 DPRINTF("aucat_read: unexpected idle state\n"); 510 hdl->sio.eof = 1; 511 return 0; 512 } 513 } 514 if (len > hdl->rtodo) 515 len = hdl->rtodo; 516 while ((n = read(hdl->fd, buf, len)) < 0) { 517 if (errno == EINTR) 518 continue; 519 if (errno != EAGAIN) { 520 hdl->sio.eof = 1; 521 DPERROR("aucat_read: read"); 522 } 523 return 0; 524 } 525 if (n == 0) { 526 DPRINTF("aucat_read: eof\n"); 527 hdl->sio.eof = 1; 528 return 0; 529 } 530 hdl->rtodo -= n; 531 if (hdl->rtodo == 0) { 532 hdl->rstate = STATE_MSG; 533 hdl->rtodo = sizeof(struct amsg); 534 } 535 return n; 536 } 537 538 static int 539 aucat_buildmsg(struct aucat_hdl *hdl, size_t len) 540 { 541 unsigned sz; 542 543 if (hdl->curvol != hdl->reqvol) { 544 hdl->wstate = STATE_MSG; 545 hdl->wtodo = sizeof(struct amsg); 546 hdl->wmsg.cmd = AMSG_SETVOL; 547 hdl->wmsg.u.vol.ctl = hdl->reqvol; 548 hdl->curvol = hdl->reqvol; 549 return 1; 550 } else if (len > 0 && hdl->maxwrite > 0) { 551 sz = len; 552 if (sz > AMSG_DATAMAX) 553 sz = AMSG_DATAMAX; 554 if (sz > hdl->maxwrite) 555 sz = hdl->maxwrite; 556 sz -= sz % hdl->wbpf; 557 if (sz == 0) 558 sz = hdl->wbpf; 559 hdl->wstate = STATE_MSG; 560 hdl->wtodo = sizeof(struct amsg); 561 hdl->wmsg.cmd = AMSG_DATA; 562 hdl->wmsg.u.data.size = sz; 563 return 1; 564 } 565 return 0; 566 } 567 568 static size_t 569 aucat_write(struct sio_hdl *sh, const void *buf, size_t len) 570 { 571 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 572 ssize_t n; 573 574 while (hdl->wstate != STATE_DATA) { 575 switch (hdl->wstate) { 576 case STATE_IDLE: 577 if (!aucat_buildmsg(hdl, len)) 578 return 0; 579 /* PASSTHROUGH */ 580 case STATE_MSG: 581 if (!aucat_wmsg(hdl)) 582 return 0; 583 if (hdl->wmsg.cmd == AMSG_DATA) { 584 hdl->wstate = STATE_DATA; 585 hdl->wtodo = hdl->wmsg.u.data.size; 586 } else 587 hdl->wstate = STATE_IDLE; 588 break; 589 default: 590 DPRINTF("aucat_write: bad state\n"); 591 abort(); 592 } 593 } 594 if (hdl->maxwrite <= 0) 595 return 0; 596 if (len > hdl->maxwrite) 597 len = hdl->maxwrite; 598 if (len > hdl->wtodo) 599 len = hdl->wtodo; 600 if (len == 0) { 601 DPRINTF("aucat_write: len == 0\n"); 602 abort(); 603 } 604 while ((n = write(hdl->fd, buf, len)) < 0) { 605 if (errno == EINTR) 606 continue; 607 if (errno != EAGAIN) { 608 hdl->sio.eof = 1; 609 DPERROR("aucat_write: write"); 610 } 611 return 0; 612 } 613 hdl->maxwrite -= n; 614 hdl->wtodo -= n; 615 if (hdl->wtodo == 0) { 616 hdl->wstate = STATE_IDLE; 617 hdl->wtodo = 0xdeadbeef; 618 } 619 return n; 620 } 621 622 static int 623 aucat_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) 624 { 625 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 626 627 hdl->events = events; 628 if (hdl->maxwrite <= 0) 629 events &= ~POLLOUT; 630 if (hdl->rstate == STATE_MSG) 631 events |= POLLIN; 632 pfd->fd = hdl->fd; 633 pfd->events = events; 634 return 1; 635 } 636 637 static int 638 aucat_revents(struct sio_hdl *sh, struct pollfd *pfd) 639 { 640 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 641 int revents = pfd->revents; 642 643 if (revents & POLLIN) { 644 while (hdl->rstate == STATE_MSG) { 645 if (!aucat_runmsg(hdl)) { 646 revents &= ~POLLIN; 647 break; 648 } 649 } 650 } 651 if (revents & POLLOUT) { 652 if (hdl->maxwrite <= 0) 653 revents &= ~POLLOUT; 654 } 655 if (hdl->sio.eof) 656 return POLLHUP; 657 return revents & (hdl->events | POLLHUP); 658 } 659 660 static int 661 aucat_setvol(struct sio_hdl *sh, unsigned vol) 662 { 663 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 664 665 hdl->reqvol = vol; 666 return 1; 667 } 668 669 static void 670 aucat_getvol(struct sio_hdl *sh) 671 { 672 struct aucat_hdl *hdl = (struct aucat_hdl *)sh; 673 674 sio_onvol_cb(&hdl->sio, hdl->reqvol); 675 return; 676 } 677