channel.c revision 75319
1/* 2 * Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk> 3 * Portions Copyright by Luigi Rizzo - 1997-99 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 * $FreeBSD: head/sys/dev/sound/pcm/channel.c 75319 2001-04-08 20:20:52Z cg $ 28 */ 29 30#include <dev/sound/pcm/sound.h> 31 32#include "feeder_if.h" 33 34#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ 35#define DMA_ALIGN_THRESHOLD 4 36#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) 37 38#define MIN(x, y) (((x) < (y))? (x) : (y)) 39#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) 40 41/* 42#define DEB(x) x 43*/ 44 45static int chn_buildfeeder(struct pcm_channel *c); 46 47static void 48chn_lockinit(struct pcm_channel *c) 49{ 50 c->lock = snd_mtxcreate(c->name); 51} 52 53static void 54chn_lockdestroy(struct pcm_channel *c) 55{ 56 snd_mtxfree(c->lock); 57} 58 59static int 60chn_polltrigger(struct pcm_channel *c) 61{ 62 struct snd_dbuf *bs = c->bufsoft; 63 unsigned amt, lim; 64 65 CHN_LOCKASSERT(c); 66 if (c->flags & CHN_F_MAPPED) { 67 if (sndbuf_getprevblocks(bs) == 0) 68 return 1; 69 else 70 return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0; 71 } else { 72 amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); 73 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; 74 lim = 1; 75 return (amt >= lim)? 1 : 0; 76 } 77 return 0; 78} 79 80static int 81chn_pollreset(struct pcm_channel *c) 82{ 83 struct snd_dbuf *bs = c->bufsoft; 84 85 CHN_LOCKASSERT(c); 86 sndbuf_updateprevtotal(bs); 87 return 1; 88} 89 90static void 91chn_wakeup(struct pcm_channel *c) 92{ 93 struct snd_dbuf *bs = c->bufsoft; 94 95 CHN_LOCKASSERT(c); 96 if (sndbuf_getsel(bs)->si_pid && chn_polltrigger(c)) 97 selwakeup(sndbuf_getsel(bs)); 98 wakeup(bs); 99} 100 101static int 102chn_sleep(struct pcm_channel *c, char *str, int timeout) 103{ 104 struct snd_dbuf *bs = c->bufsoft; 105 int ret; 106 107 CHN_LOCKASSERT(c); 108 ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); 109 110 return ret; 111} 112 113/* 114 * chn_dmaupdate() tracks the status of a dma transfer, 115 * updating pointers. It must be called at spltty(). 116 */ 117 118static unsigned int 119chn_dmaupdate(struct pcm_channel *c) 120{ 121 struct snd_dbuf *b = c->bufhard; 122 unsigned int delta, old, hwptr, amt; 123 124 CHN_LOCKASSERT(c); 125 old = sndbuf_gethwptr(b); 126 hwptr = chn_getptr(c); 127 delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); 128 sndbuf_sethwptr(b, hwptr); 129 130 DEB( 131 if (delta >= ((sndbuf_getsize(b) * 15) / 16)) { 132 if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) 133 device_printf(c->parent->dev, "hwptr went backwards %d -> %d\n", old, hwptr); 134 } 135 ); 136 137 if (c->direction == PCMDIR_PLAY) { 138 amt = MIN(delta, sndbuf_getready(b)); 139 if (amt > 0) 140 sndbuf_dispose(b, NULL, amt); 141 } else { 142 amt = MIN(delta, sndbuf_getfree(b)); 143 if (amt > 0) 144 sndbuf_acquire(b, NULL, amt); 145 } 146 147 return delta; 148} 149 150void 151chn_wrupdate(struct pcm_channel *c) 152{ 153 int ret; 154 155 CHN_LOCKASSERT(c); 156 KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); 157 158 if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED)) 159 return; 160 chn_dmaupdate(c); 161 ret = chn_wrfeed(c); 162 /* tell the driver we've updated the primary buffer */ 163 chn_trigger(c, PCMTRIG_EMLDMAWR); 164 DEB(if (ret) 165 printf("chn_wrupdate: chn_wrfeed returned %d\n", ret);) 166 167} 168 169static int irqc = 0; 170 171int 172chn_wrfeed(struct pcm_channel *c) 173{ 174 struct snd_dbuf *b = c->bufhard; 175 struct snd_dbuf *bs = c->bufsoft; 176 unsigned int ret, amt; 177 178 CHN_LOCKASSERT(c); 179 DEB( 180 if (c->flags & CHN_F_CLOSING) { 181 sndbuf_dump(b, "b", 0x02); 182 sndbuf_dump(bs, "bs", 0x02); 183 }) 184 185 amt = sndbuf_getfree(b); 186 ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; 187 if (ret == 0 && sndbuf_getfree(b) < amt) 188 chn_wakeup(c); 189/* 190 if (!(irqc & 63) || (ret != 0)) 191 sndbuf_dump(b, "b:wrfeed", 0x03); 192*/ 193 return ret; 194} 195 196static void 197chn_wrintr(struct pcm_channel *c) 198{ 199 int ret; 200 201 CHN_LOCKASSERT(c); 202 irqc++; 203 /* update pointers in primary buffer */ 204 chn_dmaupdate(c); 205 /* ...and feed from secondary to primary */ 206 ret = chn_wrfeed(c); 207 /* tell the driver we've updated the primary buffer */ 208 chn_trigger(c, PCMTRIG_EMLDMAWR); 209 DEB(if (ret) 210 printf("chn_wrintr: chn_wrfeed returned %d\n", ret);) 211} 212 213/* 214 * user write routine - uiomove data into secondary bufhard, trigger if necessary 215 * if blocking, sleep, rinse and repeat. 216 * 217 * called externally, so must handle locking 218 */ 219 220int 221chn_write(struct pcm_channel *c, struct uio *buf) 222{ 223 int ret, timeout, newsize, count, sz; 224 struct snd_dbuf *bs = c->bufsoft; 225 226 CHN_LOCKASSERT(c); 227 /* 228 * XXX Certain applications attempt to write larger size 229 * of pcm data than c->blocksize2nd without blocking, 230 * resulting partial write. Expand the block size so that 231 * the write operation avoids blocking. 232 */ 233 if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) { 234 DEB(device_printf(c->parent->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n", 235 buf->uio_resid, sndbuf_getblksz(bs))); 236 newsize = 16; 237 while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) 238 newsize <<= 1; 239 chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize); 240 DEB(device_printf(c->parent->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs))); 241 } 242 243 ret = 0; 244 count = hz; 245 while (!ret && (buf->uio_resid > 0) && (count > 0)) { 246 sz = sndbuf_getfree(bs); 247 if (sz == 0) { 248 if (c->flags & CHN_F_NBIO) 249 ret = EWOULDBLOCK; 250 else { 251 timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); 252 if (timeout < 1) 253 timeout = 1; 254 ret = chn_sleep(c, "pcmwr", timeout); 255 if (ret == EWOULDBLOCK) { 256 count -= timeout; 257 ret = 0; 258 } else if (ret == 0) 259 count = hz; 260 } 261 } else { 262 sz = MIN(sz, buf->uio_resid); 263 KASSERT(sz > 0, ("confusion in chn_write")); 264 /* printf("sz: %d\n", sz); */ 265 ret = sndbuf_uiomove(bs, buf, sz); 266 if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) 267 chn_start(c, 0); 268 } 269 } 270 /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */ 271 272 if (count <= 0) { 273 c->flags |= CHN_F_DEAD; 274 device_printf(c->parent->dev, "play interrupt timeout, channel dead\n"); 275 } 276 277 return ret; 278} 279 280static int 281chn_rddump(struct pcm_channel *c, unsigned int cnt) 282{ 283 struct snd_dbuf *b = c->bufhard; 284 285 CHN_LOCKASSERT(c); 286 sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); 287 return sndbuf_dispose(b, NULL, cnt); 288} 289 290/* 291 * Feed new data from the read bufhard. Can be called in the bottom half. 292 * Hence must be called at spltty. 293 */ 294int 295chn_rdfeed(struct pcm_channel *c) 296{ 297 struct snd_dbuf *b = c->bufhard; 298 struct snd_dbuf *bs = c->bufsoft; 299 int ret; 300 301 CHN_LOCKASSERT(c); 302 DEB( 303 if (c->flags & CHN_F_CLOSING) { 304 sndbuf_dump(b, "b", 0x02); 305 sndbuf_dump(bs, "bs", 0x02); 306 }) 307 308 ret = sndbuf_feed(b, bs, c, c->feeder, sndbuf_getblksz(b)); 309 310 if (ret == 0) 311 chn_wakeup(c); 312 313 return ret; 314} 315 316void 317chn_rdupdate(struct pcm_channel *c) 318{ 319 int ret; 320 321 CHN_LOCKASSERT(c); 322 KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); 323 324 if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED)) 325 return; 326 chn_trigger(c, PCMTRIG_EMLDMARD); 327 chn_dmaupdate(c); 328 ret = chn_rdfeed(c); 329 if (ret) 330 printf("chn_rdfeed: %d\n", ret); 331 332} 333 334/* read interrupt routine. Must be called with interrupts blocked. */ 335static void 336chn_rdintr(struct pcm_channel *c) 337{ 338 struct snd_dbuf *b = c->bufhard; 339 int ret; 340 341 CHN_LOCKASSERT(c); 342 /* tell the driver to update the primary bufhard if non-dma */ 343 chn_trigger(c, PCMTRIG_EMLDMARD); 344 /* update pointers in primary bufhard */ 345 chn_dmaupdate(c); 346 /* ...and feed from primary to secondary */ 347 ret = chn_rdfeed(c); 348 if (ret) 349 chn_rddump(c, sndbuf_getblksz(b)); 350} 351 352/* 353 * user read routine - trigger if necessary, uiomove data from secondary bufhard 354 * if blocking, sleep, rinse and repeat. 355 * 356 * called externally, so must handle locking 357 */ 358 359int 360chn_read(struct pcm_channel *c, struct uio *buf) 361{ 362 int ret, timeout, sz, count; 363 struct snd_dbuf *bs = c->bufsoft; 364 365 CHN_LOCKASSERT(c); 366 if (!(c->flags & CHN_F_TRIGGERED)) 367 chn_start(c, 0); 368 369 ret = 0; 370 count = hz; 371 while (!ret && (buf->uio_resid > 0) && (count > 0)) { 372 sz = MIN(buf->uio_resid, sndbuf_getblksz(bs)); 373 374 if (sz <= sndbuf_getready(bs)) { 375 ret = sndbuf_uiomove(bs, buf, sz); 376 } else { 377 if (c->flags & CHN_F_NBIO) 378 ret = EWOULDBLOCK; 379 else { 380 timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); 381 if (timeout < 1) 382 timeout = 1; 383 CHN_UNLOCK(c); 384 ret = chn_sleep(c, "pcmrd", timeout); 385 CHN_LOCK(c); 386 if (ret == EWOULDBLOCK) { 387 count -= timeout; 388 ret = 0; 389 } 390 } 391 } 392 } 393 394 if (count <= 0) { 395 c->flags |= CHN_F_DEAD; 396 device_printf(c->parent->dev, "record interrupt timeout, channel dead\n"); 397 } 398 399 return ret; 400} 401 402void 403chn_intr(struct pcm_channel *c) 404{ 405 CHN_LOCK(c); 406 if (c->direction == PCMDIR_PLAY) 407 chn_wrintr(c); 408 else 409 chn_rdintr(c); 410 CHN_UNLOCK(c); 411} 412 413u_int32_t 414chn_start(struct pcm_channel *c, int force) 415{ 416 u_int32_t i; 417 struct snd_dbuf *b = c->bufhard; 418 struct snd_dbuf *bs = c->bufsoft; 419 420 CHN_LOCKASSERT(c); 421 /* if we're running, or if we're prevented from triggering, bail */ 422 if ((c->flags & CHN_F_TRIGGERED) || (c->flags & CHN_F_NOTRIGGER)) 423 return EINVAL; 424 425 i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); 426 if (force || (i >= sndbuf_getblksz(b))) { 427 c->flags |= CHN_F_TRIGGERED; 428 if (c->direction == PCMDIR_PLAY) 429 chn_wrfeed(c); 430 sndbuf_setrun(b, 1); 431 chn_trigger(c, PCMTRIG_START); 432 return 0; 433 } 434 435 return 0; 436} 437 438void 439chn_resetbuf(struct pcm_channel *c) 440{ 441 struct snd_dbuf *b = c->bufhard; 442 struct snd_dbuf *bs = c->bufsoft; 443 444 c->blocks = 0; 445 sndbuf_reset(b); 446 sndbuf_reset(bs); 447} 448 449/* 450 * chn_sync waits until the space in the given channel goes above 451 * a threshold. The threshold is checked against fl or rl respectively. 452 * Assume that the condition can become true, do not check here... 453 */ 454int 455chn_sync(struct pcm_channel *c, int threshold) 456{ 457 u_long rdy; 458 int ret; 459 struct snd_dbuf *bs = c->bufsoft; 460 461 CHN_LOCKASSERT(c); 462 for (;;) { 463 chn_wrupdate(c); 464 rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); 465 if (rdy <= threshold) { 466 ret = chn_sleep(c, "pcmsyn", 1); 467 if (ret == ERESTART || ret == EINTR) { 468 DEB(printf("chn_sync: tsleep returns %d\n", ret)); 469 return -1; 470 } 471 } else 472 break; 473 } 474 return 0; 475} 476 477/* called externally, handle locking */ 478int 479chn_poll(struct pcm_channel *c, int ev, struct proc *p) 480{ 481 struct snd_dbuf *bs = c->bufsoft; 482 int ret; 483 484 CHN_LOCK(c); 485 if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED)) 486 chn_start(c, 1); 487 ret = 0; 488 if (chn_polltrigger(c) && chn_pollreset(c)) 489 ret = ev; 490 else 491 selrecord(p, sndbuf_getsel(bs)); 492 CHN_UNLOCK(c); 493 return ret; 494} 495 496/* 497 * chn_abort terminates a running dma transfer. it may sleep up to 200ms. 498 * it returns the number of bytes that have not been transferred. 499 * 500 * called from: dsp_close, dsp_ioctl, with both bufhards locked 501 */ 502int 503chn_abort(struct pcm_channel *c) 504{ 505 int missing = 0, cnt = 0; 506 struct snd_dbuf *b = c->bufhard; 507 struct snd_dbuf *bs = c->bufsoft; 508 509 CHN_LOCKASSERT(c); 510 if (!(c->flags & CHN_F_TRIGGERED)) 511 return 0; 512 c->flags |= CHN_F_ABORTING; 513 514 /* wait up to 200ms for the secondary bufhard to empty */ 515 cnt = 10; 516 while ((sndbuf_getready(bs) > 0) && (cnt-- > 0)) { 517 chn_sleep(c, "pcmabr", hz / 50); 518 } 519 520 c->flags &= ~CHN_F_TRIGGERED; 521 /* kill the channel */ 522 chn_trigger(c, PCMTRIG_ABORT); 523 sndbuf_setrun(b, 0); 524 chn_dmaupdate(c); 525 missing = sndbuf_getready(bs) + sndbuf_getready(b); 526 527 c->flags &= ~CHN_F_ABORTING; 528 return missing; 529} 530 531/* 532 * this routine tries to flush the dma transfer. It is called 533 * on a close. We immediately abort any read DMA 534 * operation, and then wait for the play bufhard to drain. 535 * 536 * called from: dsp_close 537 */ 538 539int 540chn_flush(struct pcm_channel *c) 541{ 542 int ret, count, resid, resid_p; 543 struct snd_dbuf *b = c->bufhard; 544 struct snd_dbuf *bs = c->bufsoft; 545 546 CHN_LOCKASSERT(c); 547 KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); 548 DEB(printf("chn_flush c->flags 0x%08x\n", c->flags)); 549 if (!(c->flags & CHN_F_TRIGGERED)) 550 return 0; 551 552 c->flags |= CHN_F_CLOSING; 553 resid = sndbuf_getready(bs) + sndbuf_getready(b); 554 resid_p = resid; 555 count = 10; 556 ret = 0; 557 while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { 558 /* still pending output data. */ 559 ret = chn_sleep(c, "pcmflu", hz / 10); 560 if (ret == EWOULDBLOCK) 561 ret = 0; 562 if (ret == 0) { 563 resid = sndbuf_getready(bs) + sndbuf_getready(b); 564 if (resid >= resid_p) 565 count--; 566 resid_p = resid; 567 } 568 } 569 if (count == 0) 570 DEB(printf("chn_flush: timeout\n")); 571 572 c->flags &= ~CHN_F_TRIGGERED; 573 /* kill the channel */ 574 chn_trigger(c, PCMTRIG_ABORT); 575 sndbuf_setrun(b, 0); 576 577 c->flags &= ~CHN_F_CLOSING; 578 return 0; 579} 580 581int 582fmtvalid(u_int32_t fmt, u_int32_t *fmtlist) 583{ 584 int i; 585 586 for (i = 0; fmtlist[i]; i++) 587 if (fmt == fmtlist[i]) 588 return 1; 589 return 0; 590} 591 592int 593chn_reset(struct pcm_channel *c, u_int32_t fmt) 594{ 595 int hwspd, r = 0; 596 597 CHN_LOCKASSERT(c); 598 c->flags &= CHN_F_RESET; 599 CHANNEL_RESET(c->methods, c->devinfo); 600 if (fmt) { 601 hwspd = DSP_DEFAULT_SPEED; 602 RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); 603 c->speed = hwspd; 604 605 r = chn_setformat(c, fmt); 606 if (r == 0) 607 r = chn_setspeed(c, hwspd); 608 if (r == 0) 609 r = chn_setvolume(c, 100, 100); 610 } 611 r = chn_setblocksize(c, 0, 0); 612 if (r == 0) { 613 chn_resetbuf(c); 614 CHANNEL_RESETDONE(c->methods, c->devinfo); 615 } 616 return r; 617} 618 619int 620chn_init(struct pcm_channel *c, void *devinfo, int dir) 621{ 622 struct feeder_class *fc; 623 struct snd_dbuf *b, *bs; 624 625 chn_lockinit(c); 626 CHN_LOCK(c); 627 /* Initialize the hardware and DMA bufhard first. */ 628 c->feeder = NULL; 629 fc = feeder_getclass(NULL); 630 if (fc == NULL) 631 return EINVAL; 632 if (chn_addfeeder(c, fc, NULL)) 633 return EINVAL; 634 635 b = sndbuf_create(c->name, "primary"); 636 if (b == NULL) 637 return ENOMEM; 638 bs = sndbuf_create(c->name, "secondary"); 639 if (bs == NULL) { 640 sndbuf_destroy(b); 641 return ENOMEM; 642 } 643 sndbuf_setup(bs, NULL, 0); 644 c->bufhard = b; 645 c->bufsoft = bs; 646 c->flags = 0; 647 c->feederflags = 0; 648 c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, dir); 649 if (c->devinfo == NULL) { 650 sndbuf_destroy(bs); 651 sndbuf_destroy(b); 652 return ENODEV; 653 } 654 if (sndbuf_getsize(b) == 0) { 655 sndbuf_destroy(bs); 656 sndbuf_destroy(b); 657 return ENOMEM; 658 } 659 chn_setdir(c, dir); 660 661 /* And the secondary bufhard. */ 662 sndbuf_setfmt(b, AFMT_U8); 663 sndbuf_setfmt(bs, AFMT_U8); 664 CHN_UNLOCK(c); 665 return 0; 666} 667 668int 669chn_kill(struct pcm_channel *c) 670{ 671 struct snd_dbuf *b = c->bufhard; 672 struct snd_dbuf *bs = c->bufsoft; 673 674 CHN_LOCK(c); 675 if (c->flags & CHN_F_TRIGGERED) 676 chn_trigger(c, PCMTRIG_ABORT); 677 while (chn_removefeeder(c) == 0); 678 if (CHANNEL_FREE(c->methods, c->devinfo)) 679 sndbuf_free(c->bufhard); 680 c->flags |= CHN_F_DEAD; 681 sndbuf_destroy(bs); 682 sndbuf_destroy(b); 683 chn_lockdestroy(c); 684 return 0; 685} 686 687int 688chn_setdir(struct pcm_channel *c, int dir) 689{ 690 struct snd_dbuf *b = c->bufhard; 691 int r; 692 693 CHN_LOCKASSERT(c); 694 c->direction = dir; 695 r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction); 696 if (!r && ISA_DMA(b)) 697 sndbuf_isadmasetdir(b, c->direction); 698 return r; 699} 700 701int 702chn_setvolume(struct pcm_channel *c, int left, int right) 703{ 704 CHN_LOCKASSERT(c); 705 /* could add a feeder for volume changing if channel returns -1 */ 706 c->volume = (left << 8) | right; 707 return 0; 708} 709 710static int 711chn_tryspeed(struct pcm_channel *c, int speed) 712{ 713 struct pcm_feeder *f; 714 struct snd_dbuf *b = c->bufhard; 715 struct snd_dbuf *bs = c->bufsoft; 716 int r, delta; 717 718 CHN_LOCKASSERT(c); 719 DEB(printf("want speed %d, ", speed)); 720 if (speed <= 0) 721 return EINVAL; 722 if (CANCHANGE(c)) { 723 r = 0; 724 c->speed = speed; 725 sndbuf_setspd(bs, speed); 726 RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); 727 DEB(printf("try speed %d, ", speed)); 728 sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed)); 729 DEB(printf("got speed %d, ", sndbuf_getspd(b))); 730 731 delta = sndbuf_getspd(b) - sndbuf_getspd(bs); 732 if (delta < 0) 733 delta = -delta; 734 735 c->feederflags &= ~(1 << FEEDER_RATE); 736 if (delta > 500) 737 c->feederflags |= 1 << FEEDER_RATE; 738 else 739 sndbuf_setspd(bs, sndbuf_getspd(b)); 740 741 r = chn_buildfeeder(c); 742 DEB(printf("r = %d\n", r)); 743 if (r) 744 goto out; 745 746 r = chn_setblocksize(c, 0, 0); 747 if (r) 748 goto out; 749 750 if (!(c->feederflags & (1 << FEEDER_RATE))) 751 goto out; 752 753 r = EINVAL; 754 f = chn_findfeeder(c, FEEDER_RATE); 755 DEB(printf("feedrate = %p\n", f)); 756 if (f == NULL) 757 goto out; 758 759 r = FEEDER_SET(f, FEEDRATE_SRC, sndbuf_getspd(bs)); 760 DEB(printf("feeder_set(FEEDRATE_SRC, %d) = %d\n", sndbuf_getspd(bs), r)); 761 if (r) 762 goto out; 763 764 r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(b)); 765 DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(b), r)); 766out: 767 return r; 768 } else 769 return EINVAL; 770} 771 772int 773chn_setspeed(struct pcm_channel *c, int speed) 774{ 775 int r, oldspeed = c->speed; 776 777 r = chn_tryspeed(c, speed); 778 if (r) { 779 DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); 780 chn_tryspeed(c, oldspeed); 781 } 782 return r; 783} 784 785static int 786chn_tryformat(struct pcm_channel *c, u_int32_t fmt) 787{ 788 struct snd_dbuf *b = c->bufhard; 789 struct snd_dbuf *bs = c->bufsoft; 790 int r; 791 792 CHN_LOCKASSERT(c); 793 if (CANCHANGE(c)) { 794 DEB(printf("want format %d\n", fmt)); 795 c->format = fmt; 796 r = chn_buildfeeder(c); 797 if (r == 0) { 798 sndbuf_setfmt(b, c->feeder->desc->out); 799 sndbuf_setfmt(bs, fmt); 800 chn_resetbuf(c); 801 CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b)); 802 r = chn_tryspeed(c, c->speed); 803 } 804 return r; 805 } else 806 return EINVAL; 807} 808 809int 810chn_setformat(struct pcm_channel *c, u_int32_t fmt) 811{ 812 u_int32_t oldfmt = c->format; 813 int r; 814 815 r = chn_tryformat(c, fmt); 816 if (r) { 817 DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); 818 chn_tryformat(c, oldfmt); 819 } 820 return r; 821} 822 823int 824chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) 825{ 826 struct snd_dbuf *b = c->bufhard; 827 struct snd_dbuf *bs = c->bufsoft; 828 int bufsz, irqhz, tmp, ret; 829 830 CHN_LOCKASSERT(c); 831 if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) 832 return EINVAL; 833 834 ret = 0; 835 DEB(printf("%s(%d, %d)\n", __FUNCTION__, blkcnt, blksz)); 836 if (blksz == 0 || blksz == -1) { 837 if (blksz == -1) 838 c->flags &= ~CHN_F_HAS_SIZE; 839 if (!(c->flags & CHN_F_HAS_SIZE)) { 840 blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / CHN_DEFAULT_HZ; 841 tmp = 32; 842 while (tmp <= blksz) 843 tmp <<= 1; 844 tmp >>= 1; 845 blksz = tmp; 846 blkcnt = CHN_2NDBUFMAXSIZE / blksz; 847 848 RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); 849 RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz); 850 DEB(printf("%s: defaulting to (%d, %d)\n", __FUNCTION__, blkcnt, blksz)); 851 } else { 852 blkcnt = sndbuf_getblkcnt(bs); 853 blksz = sndbuf_getblksz(bs); 854 DEB(printf("%s: updating (%d, %d)\n", __FUNCTION__, blkcnt, blksz)); 855 } 856 } else { 857 ret = EINVAL; 858 if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) 859 goto out; 860 ret = 0; 861 c->flags |= CHN_F_HAS_SIZE; 862 } 863 864 bufsz = blkcnt * blksz; 865 866 ret = ENOMEM; 867 if (sndbuf_remalloc(bs, blkcnt, blksz)) 868 goto out; 869 ret = 0; 870 871 /* adjust for different hw format/speed */ 872 irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / sndbuf_getblksz(bs); 873 DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __FUNCTION__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz)); 874 RANGE(irqhz, 16, 512); 875 876 sndbuf_setblksz(b, (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz); 877 878 /* round down to 2^x */ 879 blksz = 32; 880 while (blksz <= sndbuf_getblksz(b)) 881 blksz <<= 1; 882 blksz >>= 1; 883 884 /* round down to fit hw bufhard size */ 885 RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2); 886 DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __FUNCTION__, blksz, sndbuf_getmaxsize(b))); 887 888 sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); 889 890 irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b); 891 DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz)); 892 893 chn_resetbuf(c); 894out: 895 return ret; 896} 897 898int 899chn_trigger(struct pcm_channel *c, int go) 900{ 901 struct snd_dbuf *b = c->bufhard; 902 int ret; 903 904 CHN_LOCKASSERT(c); 905 if (ISA_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) 906 sndbuf_isadmabounce(b); 907 ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); 908 909 return ret; 910} 911 912int 913chn_getptr(struct pcm_channel *c) 914{ 915 int hwptr; 916 int a = (1 << c->align) - 1; 917 918 CHN_LOCKASSERT(c); 919 hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; 920 /* don't allow unaligned values in the hwa ptr */ 921#if 1 922 hwptr &= ~a ; /* Apply channel align mask */ 923#endif 924 hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ 925 return hwptr; 926} 927 928struct pcmchan_caps * 929chn_getcaps(struct pcm_channel *c) 930{ 931 CHN_LOCKASSERT(c); 932 return CHANNEL_GETCAPS(c->methods, c->devinfo); 933} 934 935u_int32_t 936chn_getformats(struct pcm_channel *c) 937{ 938 u_int32_t *fmtlist, fmts; 939 int i; 940 941 fmtlist = chn_getcaps(c)->fmtlist; 942 fmts = 0; 943 for (i = 0; fmtlist[i]; i++) 944 fmts |= fmtlist[i]; 945 946 return fmts; 947} 948 949static int 950chn_buildfeeder(struct pcm_channel *c) 951{ 952 struct feeder_class *fc; 953 struct pcm_feederdesc desc; 954 u_int32_t tmp[2], type, flags; 955 956 CHN_LOCKASSERT(c); 957 while (chn_removefeeder(c) == 0); 958 KASSERT((c->feeder == NULL), ("feeder chain not empty")); 959 960 c->align = sndbuf_getalign(c->bufsoft); 961 962 fc = feeder_getclass(NULL); 963 if (fc == NULL) { 964 DEB(printf("can't find root feeder\n")); 965 return EINVAL; 966 } 967 if (chn_addfeeder(c, fc, NULL)) { 968 DEB(printf("can't add root feeder\n")); 969 return EINVAL; 970 } 971 c->feeder->desc->out = c->format; 972 973 flags = c->feederflags; 974 975 if ((c->flags & CHN_F_MAPPED) && (flags != 0)) { 976 DEB(printf("can't build feeder chain on mapped channel\n")); 977 return EINVAL; 978 } 979 DEB(printf("not mapped, flags %x\n", flags)); 980 981 for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) { 982 if (flags & (1 << type)) { 983 desc.type = type; 984 desc.in = 0; 985 desc.out = 0; 986 desc.flags = 0; 987 DEB(printf("find feeder type %d, ", type)); 988 fc = feeder_getclass(&desc); 989 DEB(printf("got %p\n", fc)); 990 if (fc == NULL) { 991 DEB(printf("can't find required feeder type %d\n", type)); 992 return EINVAL; 993 } 994 995 if (c->feeder->desc->out != fc->desc->in) { 996 DEB(printf("build fmtchain from %x to %x: ", c->feeder->desc->out, fc->desc->in)); 997 tmp[0] = fc->desc->in; 998 tmp[1] = 0; 999 if (chn_fmtchain(c, tmp) == 0) { 1000 DEB(printf("failed\n")); 1001 return EINVAL; 1002 } 1003 DEB(printf("ok\n")); 1004 } 1005 1006 if (chn_addfeeder(c, fc, fc->desc)) { 1007 DEB(printf("can't add feeder %p, output %x\n", fc, fc->desc->out)); 1008 return EINVAL; 1009 } 1010 DEB(printf("added feeder %p, output %x\n", fc, c->feeder->desc->out)); 1011 } 1012 } 1013 1014 if (!fmtvalid(c->feeder->desc->out, chn_getcaps(c)->fmtlist)) { 1015 if (chn_fmtchain(c, chn_getcaps(c)->fmtlist) == 0) { 1016 DEB(printf("can't build fmtchain from %x\n", c->feeder->desc->out)); 1017 return EINVAL; 1018 } 1019 DEB(printf("built fmtchain from %x\n", c->feeder->desc->out)); 1020 } 1021 1022 return 0; 1023} 1024 1025