Cleanup remaining tsleep priority issues.
[dragonfly.git] / sys / dev / sound / pcm / channel.c
CommitLineData
984263bc
MD
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.
1de703da
MD
26 *
27 * $FreeBSD: src/sys/dev/sound/pcm/channel.c,v 1.19.2.19 2003/03/11 15:15:41 orion Exp $
2f65cd66 28 * $DragonFly: src/sys/dev/sound/pcm/channel.c,v 1.6 2003/08/01 17:46:18 dillon Exp $
984263bc
MD
29 */
30
31#include <dev/sound/pcm/sound.h>
32
33#include "feeder_if.h"
34
2f65cd66 35SND_DECLARE_FILE("$DragonFly: src/sys/dev/sound/pcm/channel.c,v 1.6 2003/08/01 17:46:18 dillon Exp $");
984263bc
MD
36
37#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */
38#define DMA_ALIGN_THRESHOLD 4
39#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1))
40
984263bc
MD
41#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED))
42
43/*
44#define DEB(x) x
45*/
46
47static int chn_targetirqrate = 32;
48TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate);
49
50static int
51sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS)
52{
53 int err, val;
54
55 val = chn_targetirqrate;
56 err = sysctl_handle_int(oidp, &val, sizeof(val), req);
57 if (val < 16 || val > 512)
58 err = EINVAL;
59 else
60 chn_targetirqrate = val;
61
62 return err;
63}
64SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW,
65 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", "");
66static int report_soft_formats = 1;
67SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW,
68 &report_soft_formats, 1, "report software-emulated formats");
69
70static int chn_buildfeeder(struct pcm_channel *c);
71
72static void
73chn_lockinit(struct pcm_channel *c)
74{
75 c->lock = snd_mtxcreate(c->name, "pcm channel");
76}
77
78static void
79chn_lockdestroy(struct pcm_channel *c)
80{
81 snd_mtxfree(c->lock);
82}
83
84static int
85chn_polltrigger(struct pcm_channel *c)
86{
87 struct snd_dbuf *bs = c->bufsoft;
88 unsigned amt, lim;
89
90 CHN_LOCKASSERT(c);
91 if (c->flags & CHN_F_MAPPED) {
92 if (sndbuf_getprevblocks(bs) == 0)
93 return 1;
94 else
95 return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0;
96 } else {
97 amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs);
98 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1;
99 lim = 1;
100 return (amt >= lim)? 1 : 0;
101 }
102 return 0;
103}
104
105static int
106chn_pollreset(struct pcm_channel *c)
107{
108 struct snd_dbuf *bs = c->bufsoft;
109
110 CHN_LOCKASSERT(c);
111 sndbuf_updateprevtotal(bs);
112 return 1;
113}
114
115static void
116chn_wakeup(struct pcm_channel *c)
117{
118 struct snd_dbuf *bs = c->bufsoft;
119
120 CHN_LOCKASSERT(c);
121 if (sndbuf_getsel(bs)->si_pid && chn_polltrigger(c))
122 selwakeup(sndbuf_getsel(bs));
123 wakeup(bs);
124}
125
126static int
127chn_sleep(struct pcm_channel *c, char *str, int timeout)
128{
129 struct snd_dbuf *bs = c->bufsoft;
130 int ret;
131
132 CHN_LOCKASSERT(c);
133#ifdef USING_MUTEX
2f65cd66 134 ret = msleep(bs, c->lock, PCATCH, str, timeout);
984263bc 135#else
377d4740 136 ret = tsleep(bs, PCATCH, str, timeout);
984263bc
MD
137#endif
138
139 return ret;
140}
141
142/*
143 * chn_dmaupdate() tracks the status of a dma transfer,
144 * updating pointers. It must be called at spltty().
145 */
146
147static unsigned int
148chn_dmaupdate(struct pcm_channel *c)
149{
150 struct snd_dbuf *b = c->bufhard;
151 unsigned int delta, old, hwptr, amt;
152
153 KASSERT(sndbuf_getsize(b) > 0, ("bufsize == 0"));
154 CHN_LOCKASSERT(c);
155
156 old = sndbuf_gethwptr(b);
157 hwptr = chn_getptr(c);
158 delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b);
159 sndbuf_sethwptr(b, hwptr);
160
161 DEB(
162 if (delta >= ((sndbuf_getsize(b) * 15) / 16)) {
163 if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING)))
164 device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr);
165 }
166 );
167
168 if (c->direction == PCMDIR_PLAY) {
169 amt = MIN(delta, sndbuf_getready(b));
170 if (amt > 0)
171 sndbuf_dispose(b, NULL, amt);
172 } else {
173 amt = MIN(delta, sndbuf_getfree(b));
174 if (amt > 0)
175 sndbuf_acquire(b, NULL, amt);
176 }
177
178 return delta;
179}
180
181void
182chn_wrupdate(struct pcm_channel *c)
183{
184 int ret;
185
186 CHN_LOCKASSERT(c);
187 KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel"));
188
189 if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED))
190 return;
191 chn_dmaupdate(c);
192 ret = chn_wrfeed(c);
193 /* tell the driver we've updated the primary buffer */
194 chn_trigger(c, PCMTRIG_EMLDMAWR);
195 DEB(if (ret)
196 printf("chn_wrupdate: chn_wrfeed returned %d\n", ret);)
197
198}
199
200int
201chn_wrfeed(struct pcm_channel *c)
202{
203 struct snd_dbuf *b = c->bufhard;
204 struct snd_dbuf *bs = c->bufsoft;
205 unsigned int ret, amt;
206
207 CHN_LOCKASSERT(c);
208 DEB(
209 if (c->flags & CHN_F_CLOSING) {
210 sndbuf_dump(b, "b", 0x02);
211 sndbuf_dump(bs, "bs", 0x02);
212 })
213
214 if (c->flags & CHN_F_MAPPED)
215 sndbuf_acquire(bs, NULL, sndbuf_getfree(bs));
216
217 amt = sndbuf_getfree(b);
218 if (sndbuf_getready(bs) < amt)
219 c->xruns++;
220
221 ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC;
222 if (ret == 0 && sndbuf_getfree(b) < amt)
223 chn_wakeup(c);
224
225 return ret;
226}
227
228static void
229chn_wrintr(struct pcm_channel *c)
230{
231 int ret;
232
233 CHN_LOCKASSERT(c);
234 /* update pointers in primary buffer */
235 chn_dmaupdate(c);
236 /* ...and feed from secondary to primary */
237 ret = chn_wrfeed(c);
238 /* tell the driver we've updated the primary buffer */
239 chn_trigger(c, PCMTRIG_EMLDMAWR);
240 DEB(if (ret)
241 printf("chn_wrintr: chn_wrfeed returned %d\n", ret);)
242}
243
244/*
245 * user write routine - uiomove data into secondary buffer, trigger if necessary
246 * if blocking, sleep, rinse and repeat.
247 *
248 * called externally, so must handle locking
249 */
250
251int
252chn_write(struct pcm_channel *c, struct uio *buf)
253{
254 int ret, timeout, newsize, count, sz;
255 struct snd_dbuf *bs = c->bufsoft;
256
257 CHN_LOCKASSERT(c);
258 /*
259 * XXX Certain applications attempt to write larger size
260 * of pcm data than c->blocksize2nd without blocking,
261 * resulting partial write. Expand the block size so that
262 * the write operation avoids blocking.
263 */
264 if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) {
265 DEB(device_printf(c->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n",
266 buf->uio_resid, sndbuf_getblksz(bs)));
267 newsize = 16;
268 while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2))
269 newsize <<= 1;
270 chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize);
271 DEB(device_printf(c->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs)));
272 }
273
274 ret = 0;
275 count = hz;
276 while (!ret && (buf->uio_resid > 0) && (count > 0)) {
277 sz = sndbuf_getfree(bs);
278 if (sz == 0) {
279 if (c->flags & CHN_F_NBIO)
280 ret = EWOULDBLOCK;
281 else {
282 timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs));
283 if (timeout < 1)
284 timeout = 1;
285 timeout = 1;
286 ret = chn_sleep(c, "pcmwr", timeout);
287 if (ret == EWOULDBLOCK) {
288 count -= timeout;
289 ret = 0;
290 } else if (ret == 0)
291 count = hz;
292 }
293 } else {
294 sz = MIN(sz, buf->uio_resid);
295 KASSERT(sz > 0, ("confusion in chn_write"));
296 /* printf("sz: %d\n", sz); */
297 ret = sndbuf_uiomove(bs, buf, sz);
298 if (ret == 0 && !(c->flags & CHN_F_TRIGGERED))
299 chn_start(c, 0);
300 }
301 }
302 /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */
303
304 if (count <= 0) {
305 c->flags |= CHN_F_DEAD;
306 printf("%s: play interrupt timeout, channel dead\n", c->name);
307 }
308
309 return ret;
310}
311
312static int
313chn_rddump(struct pcm_channel *c, unsigned int cnt)
314{
315 struct snd_dbuf *b = c->bufhard;
316
317 CHN_LOCKASSERT(c);
318 sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt);
319 return sndbuf_dispose(b, NULL, cnt);
320}
321
322/*
323 * Feed new data from the read buffer. Can be called in the bottom half.
324 * Hence must be called at spltty.
325 */
326int
327chn_rdfeed(struct pcm_channel *c)
328{
329 struct snd_dbuf *b = c->bufhard;
330 struct snd_dbuf *bs = c->bufsoft;
331 unsigned int ret, amt;
332
333 CHN_LOCKASSERT(c);
334 DEB(
335 if (c->flags & CHN_F_CLOSING) {
336 sndbuf_dump(b, "b", 0x02);
337 sndbuf_dump(bs, "bs", 0x02);
338 })
339
340 amt = sndbuf_getready(b);
341 if (sndbuf_getfree(bs) < amt) {
342 c->xruns++;
343 amt = sndbuf_getfree(bs);
344 }
345 ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0;
346
347 amt = sndbuf_getready(b);
348 if (amt > 0)
349 chn_rddump(c, amt);
350
351 chn_wakeup(c);
352
353 return ret;
354}
355
356void
357chn_rdupdate(struct pcm_channel *c)
358{
359 int ret;
360
361 CHN_LOCKASSERT(c);
362 KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel"));
363
364 if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED))
365 return;
366 chn_trigger(c, PCMTRIG_EMLDMARD);
367 chn_dmaupdate(c);
368 ret = chn_rdfeed(c);
369 if (ret)
370 printf("chn_rdfeed: %d\n", ret);
371
372}
373
374/* read interrupt routine. Must be called with interrupts blocked. */
375static void
376chn_rdintr(struct pcm_channel *c)
377{
378 int ret;
379
380 CHN_LOCKASSERT(c);
381 /* tell the driver to update the primary buffer if non-dma */
382 chn_trigger(c, PCMTRIG_EMLDMARD);
383 /* update pointers in primary buffer */
384 chn_dmaupdate(c);
385 /* ...and feed from primary to secondary */
386 ret = chn_rdfeed(c);
387}
388
389/*
390 * user read routine - trigger if necessary, uiomove data from secondary buffer
391 * if blocking, sleep, rinse and repeat.
392 *
393 * called externally, so must handle locking
394 */
395
396int
397chn_read(struct pcm_channel *c, struct uio *buf)
398{
399 int ret, timeout, sz, count;
400 struct snd_dbuf *bs = c->bufsoft;
401
402 CHN_LOCKASSERT(c);
403 if (!(c->flags & CHN_F_TRIGGERED))
404 chn_start(c, 0);
405
406 ret = 0;
407 count = hz;
408 while (!ret && (buf->uio_resid > 0) && (count > 0)) {
409 sz = MIN(buf->uio_resid, sndbuf_getready(bs));
410
411 if (sz > 0) {
412 ret = sndbuf_uiomove(bs, buf, sz);
413 } else {
414 if (c->flags & CHN_F_NBIO) {
415 ret = EWOULDBLOCK;
416 } else {
417 timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs));
418 if (timeout < 1)
419 timeout = 1;
420 ret = chn_sleep(c, "pcmrd", timeout);
421 if (ret == EWOULDBLOCK) {
422 count -= timeout;
423 ret = 0;
424 } else {
425 count = hz;
426 }
427
428 }
429 }
430 }
431
432 if (count <= 0) {
433 c->flags |= CHN_F_DEAD;
434 printf("%s: record interrupt timeout, channel dead\n", c->name);
435 }
436
437 return ret;
438}
439
440void
441chn_intr(struct pcm_channel *c)
442{
443 CHN_LOCK(c);
444 c->interrupts++;
445 if (c->direction == PCMDIR_PLAY)
446 chn_wrintr(c);
447 else
448 chn_rdintr(c);
449 CHN_UNLOCK(c);
450}
451
452u_int32_t
453chn_start(struct pcm_channel *c, int force)
454{
455 u_int32_t i, j;
456 struct snd_dbuf *b = c->bufhard;
457 struct snd_dbuf *bs = c->bufsoft;
458
459 CHN_LOCKASSERT(c);
460 /* if we're running, or if we're prevented from triggering, bail */
461 if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force))
462 return EINVAL;
463
464 i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs);
465 j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b);
466 if (force || (i >= j)) {
467 c->flags |= CHN_F_TRIGGERED;
468 /*
469 * if we're starting because a vchan started, don't feed any data
470 * or it becomes impossible to start vchans synchronised with the
471 * first one. the hardbuf should be empty so we top it up with
472 * silence to give it something to chew. the real data will be
473 * fed at the first irq.
474 */
475 if (c->direction == PCMDIR_PLAY) {
476 if (SLIST_EMPTY(&c->children))
477 chn_wrfeed(c);
478 else
479 sndbuf_fillsilence(b);
480 }
481 sndbuf_setrun(b, 1);
482 c->xruns = 0;
483 chn_trigger(c, PCMTRIG_START);
484 return 0;
485 }
486
487 return 0;
488}
489
490void
491chn_resetbuf(struct pcm_channel *c)
492{
493 struct snd_dbuf *b = c->bufhard;
494 struct snd_dbuf *bs = c->bufsoft;
495
496 c->blocks = 0;
497 sndbuf_reset(b);
498 sndbuf_reset(bs);
499}
500
501/*
502 * chn_sync waits until the space in the given channel goes above
503 * a threshold. The threshold is checked against fl or rl respectively.
504 * Assume that the condition can become true, do not check here...
505 */
506int
507chn_sync(struct pcm_channel *c, int threshold)
508{
509 u_long rdy;
510 int ret;
511 struct snd_dbuf *bs = c->bufsoft;
512
513 CHN_LOCKASSERT(c);
514 for (;;) {
515 rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs);
516 if (rdy <= threshold) {
517 ret = chn_sleep(c, "pcmsyn", 1);
518 if (ret == ERESTART || ret == EINTR) {
519 DEB(printf("chn_sync: tsleep returns %d\n", ret));
520 return -1;
521 }
522 } else
523 break;
524 }
525 return 0;
526}
527
528/* called externally, handle locking */
529int
530chn_poll(struct pcm_channel *c, int ev, struct proc *p)
531{
532 struct snd_dbuf *bs = c->bufsoft;
533 int ret;
534
535 CHN_LOCKASSERT(c);
536 if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED))
537 chn_start(c, 1);
538 ret = 0;
539 if (chn_polltrigger(c) && chn_pollreset(c))
540 ret = ev;
541 else
41c20dac 542 selrecord(p->p_thread, sndbuf_getsel(bs));
984263bc
MD
543 return ret;
544}
545
546/*
547 * chn_abort terminates a running dma transfer. it may sleep up to 200ms.
548 * it returns the number of bytes that have not been transferred.
549 *
550 * called from: dsp_close, dsp_ioctl, with channel locked
551 */
552int
553chn_abort(struct pcm_channel *c)
554{
555 int missing = 0;
556 struct snd_dbuf *b = c->bufhard;
557 struct snd_dbuf *bs = c->bufsoft;
558
559 CHN_LOCKASSERT(c);
560 if (!(c->flags & CHN_F_TRIGGERED))
561 return 0;
562 c->flags |= CHN_F_ABORTING;
563
564 c->flags &= ~CHN_F_TRIGGERED;
565 /* kill the channel */
566 chn_trigger(c, PCMTRIG_ABORT);
567 sndbuf_setrun(b, 0);
568 if (!(c->flags & CHN_F_VIRTUAL))
569 chn_dmaupdate(c);
570 missing = sndbuf_getready(bs) + sndbuf_getready(b);
571
572 c->flags &= ~CHN_F_ABORTING;
573 return missing;
574}
575
576/*
577 * this routine tries to flush the dma transfer. It is called
578 * on a close. We immediately abort any read DMA
579 * operation, and then wait for the play buffer to drain.
580 *
581 * called from: dsp_close
582 */
583
584int
585chn_flush(struct pcm_channel *c)
586{
587 int ret, count, resid, resid_p;
588 struct snd_dbuf *b = c->bufhard;
589 struct snd_dbuf *bs = c->bufsoft;
590
591 CHN_LOCKASSERT(c);
592 KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel"));
593 DEB(printf("chn_flush c->flags 0x%08x\n", c->flags));
594 if (!(c->flags & CHN_F_TRIGGERED))
595 return 0;
596
597 c->flags |= CHN_F_CLOSING;
598 resid = sndbuf_getready(bs) + sndbuf_getready(b);
599 resid_p = resid;
600 count = 10;
601 ret = 0;
602 while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) {
603 /* still pending output data. */
604 ret = chn_sleep(c, "pcmflu", hz / 10);
605 if (ret == EWOULDBLOCK)
606 ret = 0;
607 if (ret == 0) {
608 resid = sndbuf_getready(bs) + sndbuf_getready(b);
609 if (resid >= resid_p)
610 count--;
611 resid_p = resid;
612 }
613 }
614 if (count == 0)
615 DEB(printf("chn_flush: timeout\n"));
616
617 c->flags &= ~CHN_F_TRIGGERED;
618 /* kill the channel */
619 chn_trigger(c, PCMTRIG_ABORT);
620 sndbuf_setrun(b, 0);
621
622 c->flags &= ~CHN_F_CLOSING;
623 return 0;
624}
625
626int
627fmtvalid(u_int32_t fmt, u_int32_t *fmtlist)
628{
629 int i;
630
631 for (i = 0; fmtlist[i]; i++)
632 if (fmt == fmtlist[i])
633 return 1;
634 return 0;
635}
636
637int
638chn_reset(struct pcm_channel *c, u_int32_t fmt)
639{
640 int hwspd, r;
641
642 CHN_LOCKASSERT(c);
643 c->flags &= CHN_F_RESET;
644 c->interrupts = 0;
645 c->xruns = 0;
646
647 r = CHANNEL_RESET(c->methods, c->devinfo);
648 if (fmt != 0) {
649 hwspd = DSP_DEFAULT_SPEED;
650 /* only do this on a record channel until feederbuilder works */
651 if (c->direction == PCMDIR_REC)
652 RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed);
653 c->speed = hwspd;
654
655 if (r == 0)
656 r = chn_setformat(c, fmt);
657 if (r == 0)
658 r = chn_setspeed(c, hwspd);
659 if (r == 0)
660 r = chn_setvolume(c, 100, 100);
661 }
662 if (r == 0)
663 r = chn_setblocksize(c, 0, 0);
664 if (r == 0) {
665 chn_resetbuf(c);
666 r = CHANNEL_RESETDONE(c->methods, c->devinfo);
667 }
668 return r;
669}
670
671int
672chn_init(struct pcm_channel *c, void *devinfo, int dir)
673{
674 struct feeder_class *fc;
675 struct snd_dbuf *b, *bs;
676 int ret;
677
678 chn_lockinit(c);
679 CHN_LOCK(c);
680
681 b = NULL;
682 bs = NULL;
683 c->devinfo = NULL;
684 c->feeder = NULL;
685
686 ret = EINVAL;
687 fc = feeder_getclass(NULL);
688 if (fc == NULL)
689 goto out;
690 if (chn_addfeeder(c, fc, NULL))
691 goto out;
692
693 ret = ENOMEM;
694 b = sndbuf_create(c->dev, c->name, "primary");
695 if (b == NULL)
696 goto out;
697 bs = sndbuf_create(c->dev, c->name, "secondary");
698 if (bs == NULL)
699 goto out;
700 sndbuf_setup(bs, NULL, 0);
701 c->bufhard = b;
702 c->bufsoft = bs;
703 c->flags = 0;
704 c->feederflags = 0;
705
706 ret = ENODEV;
707 c->devinfo = CHANNEL_INIT(c->methods, devinfo, b, c, dir);
708 if (c->devinfo == NULL)
709 goto out;
710
711 ret = ENOMEM;
712 if ((sndbuf_getsize(b) == 0) && ((c->flags & CHN_F_VIRTUAL) == 0))
713 goto out;
714
715 ret = chn_setdir(c, dir);
716 if (ret)
717 goto out;
718
719 ret = sndbuf_setfmt(b, AFMT_U8);
720 if (ret)
721 goto out;
722
723 ret = sndbuf_setfmt(bs, AFMT_U8);
724 if (ret)
725 goto out;
726
727
728out:
729 if (ret) {
730 if (c->devinfo) {
731 if (CHANNEL_FREE(c->methods, c->devinfo))
732 sndbuf_free(b);
733 }
734 if (bs)
735 sndbuf_destroy(bs);
736 if (b)
737 sndbuf_destroy(b);
738 c->flags |= CHN_F_DEAD;
739 chn_lockdestroy(c);
740
741 return ret;
742 }
743
744 CHN_UNLOCK(c);
745 return 0;
746}
747
748int
749chn_kill(struct pcm_channel *c)
750{
751 struct snd_dbuf *b = c->bufhard;
752 struct snd_dbuf *bs = c->bufsoft;
753
754 CHN_LOCK(c);
755 if (c->flags & CHN_F_TRIGGERED)
756 chn_trigger(c, PCMTRIG_ABORT);
757 while (chn_removefeeder(c) == 0);
758 if (CHANNEL_FREE(c->methods, c->devinfo))
759 sndbuf_free(b);
760 c->flags |= CHN_F_DEAD;
761 sndbuf_destroy(bs);
762 sndbuf_destroy(b);
763 chn_lockdestroy(c);
764 return 0;
765}
766
767int
768chn_setdir(struct pcm_channel *c, int dir)
769{
770 struct snd_dbuf *b = c->bufhard;
771 int r;
772
773 CHN_LOCKASSERT(c);
774 c->direction = dir;
775 r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction);
776 if (!r && ISA_DMA(b))
777 sndbuf_isadmasetdir(b, c->direction);
778 return r;
779}
780
781int
782chn_setvolume(struct pcm_channel *c, int left, int right)
783{
784 CHN_LOCKASSERT(c);
785 /* could add a feeder for volume changing if channel returns -1 */
786 c->volume = (left << 8) | right;
787 return 0;
788}
789
790static int
791chn_tryspeed(struct pcm_channel *c, int speed)
792{
793 struct pcm_feeder *f;
794 struct snd_dbuf *b = c->bufhard;
795 struct snd_dbuf *bs = c->bufsoft;
796 struct snd_dbuf *x;
797 int r, delta;
798
799 CHN_LOCKASSERT(c);
800 DEB(printf("setspeed, channel %s\n", c->name));
801 DEB(printf("want speed %d, ", speed));
802 if (speed <= 0)
803 return EINVAL;
804 if (CANCHANGE(c)) {
805 r = 0;
806 c->speed = speed;
807 sndbuf_setspd(bs, speed);
808 RANGE(speed, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed);
809 DEB(printf("try speed %d, ", speed));
810 sndbuf_setspd(b, CHANNEL_SETSPEED(c->methods, c->devinfo, speed));
811 DEB(printf("got speed %d\n", sndbuf_getspd(b)));
812
813 delta = sndbuf_getspd(b) - sndbuf_getspd(bs);
814 if (delta < 0)
815 delta = -delta;
816
817 c->feederflags &= ~(1 << FEEDER_RATE);
818 if (delta > 500)
819 c->feederflags |= 1 << FEEDER_RATE;
820 else
821 sndbuf_setspd(bs, sndbuf_getspd(b));
822
823 r = chn_buildfeeder(c);
824 DEB(printf("r = %d\n", r));
825 if (r)
826 goto out;
827
828 r = chn_setblocksize(c, 0, 0);
829 if (r)
830 goto out;
831
832 if (!(c->feederflags & (1 << FEEDER_RATE)))
833 goto out;
834
835 r = EINVAL;
836 f = chn_findfeeder(c, FEEDER_RATE);
837 DEB(printf("feedrate = %p\n", f));
838 if (f == NULL)
839 goto out;
840
841 x = (c->direction == PCMDIR_REC)? b : bs;
842 r = FEEDER_SET(f, FEEDRATE_SRC, sndbuf_getspd(x));
843 DEB(printf("feeder_set(FEEDRATE_SRC, %d) = %d\n", sndbuf_getspd(x), r));
844 if (r)
845 goto out;
846
847 x = (c->direction == PCMDIR_REC)? bs : b;
848 r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x));
849 DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r));
850out:
851 DEB(printf("setspeed done, r = %d\n", r));
852 return r;
853 } else
854 return EINVAL;
855}
856
857int
858chn_setspeed(struct pcm_channel *c, int speed)
859{
860 int r, oldspeed = c->speed;
861
862 r = chn_tryspeed(c, speed);
863 if (r) {
864 DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed));
865 r = chn_tryspeed(c, oldspeed);
866 }
867 return r;
868}
869
870static int
871chn_tryformat(struct pcm_channel *c, u_int32_t fmt)
872{
873 struct snd_dbuf *b = c->bufhard;
874 struct snd_dbuf *bs = c->bufsoft;
875 int r;
876
877 CHN_LOCKASSERT(c);
878 if (CANCHANGE(c)) {
879 DEB(printf("want format %d\n", fmt));
880 c->format = fmt;
881 r = chn_buildfeeder(c);
882 if (r == 0) {
883 sndbuf_setfmt(bs, c->format);
884 chn_resetbuf(c);
885 r = CHANNEL_SETFORMAT(c->methods, c->devinfo, sndbuf_getfmt(b));
886 if (r == 0)
887 r = chn_tryspeed(c, c->speed);
888 }
889 return r;
890 } else
891 return EINVAL;
892}
893
894int
895chn_setformat(struct pcm_channel *c, u_int32_t fmt)
896{
897 u_int32_t oldfmt = c->format;
898 int r;
899
900 r = chn_tryformat(c, fmt);
901 if (r) {
902 DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt));
903 chn_tryformat(c, oldfmt);
904 }
905 return r;
906}
907
908int
909chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz)
910{
911 struct snd_dbuf *b = c->bufhard;
912 struct snd_dbuf *bs = c->bufsoft;
913 int bufsz, irqhz, tmp, ret;
914
915 CHN_LOCKASSERT(c);
916 if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED))
917 return EINVAL;
918
919 ret = 0;
920 DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz));
921 if (blksz == 0 || blksz == -1) {
922 if (blksz == -1)
923 c->flags &= ~CHN_F_HAS_SIZE;
924 if (!(c->flags & CHN_F_HAS_SIZE)) {
925 blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / chn_targetirqrate;
926 tmp = 32;
927 while (tmp <= blksz)
928 tmp <<= 1;
929 tmp >>= 1;
930 blksz = tmp;
931 blkcnt = CHN_2NDBUFMAXSIZE / blksz;
932
933 RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2);
934 RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz);
935 DEB(printf("%s: defaulting to (%d, %d)\n", __func__, blkcnt, blksz));
936 } else {
937 blkcnt = sndbuf_getblkcnt(bs);
938 blksz = sndbuf_getblksz(bs);
939 DEB(printf("%s: updating (%d, %d)\n", __func__, blkcnt, blksz));
940 }
941 } else {
942 ret = EINVAL;
943 if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE))
944 goto out;
945 ret = 0;
946 c->flags |= CHN_F_HAS_SIZE;
947 }
948
949 bufsz = blkcnt * blksz;
950
951 ret = ENOMEM;
952 if (sndbuf_remalloc(bs, blkcnt, blksz))
953 goto out;
954 ret = 0;
955
956 /* adjust for different hw format/speed */
957 irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / sndbuf_getblksz(bs);
958 DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __func__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz));
959 RANGE(irqhz, 16, 512);
960
961 sndbuf_setblksz(b, (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz);
962
963 /* round down to 2^x */
964 blksz = 32;
965 while (blksz <= sndbuf_getblksz(b))
966 blksz <<= 1;
967 blksz >>= 1;
968
969 /* round down to fit hw buffer size */
970 RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2);
971 DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __func__, blksz, sndbuf_getmaxsize(b)));
972
973 sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz));
974
975 irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b);
976 DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz));
977
978 chn_resetbuf(c);
979out:
980 return ret;
981}
982
983int
984chn_trigger(struct pcm_channel *c, int go)
985{
986 struct snd_dbuf *b = c->bufhard;
987 int ret;
988
989 CHN_LOCKASSERT(c);
990 if (ISA_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD))
991 sndbuf_isadmabounce(b);
992 ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go);
993
994 return ret;
995}
996
997int
998chn_getptr(struct pcm_channel *c)
999{
1000 int hwptr;
1001 int a = (1 << c->align) - 1;
1002
1003 CHN_LOCKASSERT(c);
1004 hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0;
1005 /* don't allow unaligned values in the hwa ptr */
1006#if 1
1007 hwptr &= ~a ; /* Apply channel align mask */
1008#endif
1009 hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */
1010 return hwptr;
1011}
1012
1013struct pcmchan_caps *
1014chn_getcaps(struct pcm_channel *c)
1015{
1016 CHN_LOCKASSERT(c);
1017 return CHANNEL_GETCAPS(c->methods, c->devinfo);
1018}
1019
1020u_int32_t
1021chn_getformats(struct pcm_channel *c)
1022{
1023 u_int32_t *fmtlist, fmts;
1024 int i;
1025
1026 fmtlist = chn_getcaps(c)->fmtlist;
1027 fmts = 0;
1028 for (i = 0; fmtlist[i]; i++)
1029 fmts |= fmtlist[i];
1030
1031 /* report software-supported formats */
1032 if (report_soft_formats)
1033 fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U16_LE|AFMT_U16_BE|
1034 AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8;
1035
1036 return fmts;
1037}
1038
1039static int
1040chn_buildfeeder(struct pcm_channel *c)
1041{
1042 struct feeder_class *fc;
1043 struct pcm_feederdesc desc;
1044 u_int32_t tmp[2], type, flags, hwfmt;
1045 int err;
1046
1047 CHN_LOCKASSERT(c);
1048 while (chn_removefeeder(c) == 0);
1049 KASSERT((c->feeder == NULL), ("feeder chain not empty"));
1050
1051 c->align = sndbuf_getalign(c->bufsoft);
1052
1053 if (SLIST_EMPTY(&c->children)) {
1054 fc = feeder_getclass(NULL);
1055 KASSERT(fc != NULL, ("can't find root feeder"));
1056
1057 err = chn_addfeeder(c, fc, NULL);
1058 if (err) {
1059 DEB(printf("can't add root feeder, err %d\n", err));
1060
1061 return err;
1062 }
1063 c->feeder->desc->out = c->format;
1064 } else {
1065 desc.type = FEEDER_MIXER;
1066 desc.in = 0;
1067 desc.out = c->format;
1068 desc.flags = 0;
1069 fc = feeder_getclass(&desc);
1070 if (fc == NULL) {
1071 DEB(printf("can't find vchan feeder\n"));
1072
1073 return EOPNOTSUPP;
1074 }
1075
1076 err = chn_addfeeder(c, fc, &desc);
1077 if (err) {
1078 DEB(printf("can't add vchan feeder, err %d\n", err));
1079
1080 return err;
1081 }
1082 }
1083 flags = c->feederflags;
1084
1085 DEB(printf("not mapped, feederflags %x\n", flags));
1086
1087 for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) {
1088 if (flags & (1 << type)) {
1089 desc.type = type;
1090 desc.in = 0;
1091 desc.out = 0;
1092 desc.flags = 0;
1093 DEB(printf("find feeder type %d, ", type));
1094 fc = feeder_getclass(&desc);
1095 DEB(printf("got %p\n", fc));
1096 if (fc == NULL) {
1097 DEB(printf("can't find required feeder type %d\n", type));
1098
1099 return EOPNOTSUPP;
1100 }
1101
1102 if (c->feeder->desc->out != fc->desc->in) {
1103 DEB(printf("build fmtchain from %x to %x: ", c->feeder->desc->out, fc->desc->in));
1104 tmp[0] = fc->desc->in;
1105 tmp[1] = 0;
1106 if (chn_fmtchain(c, tmp) == 0) {
1107 DEB(printf("failed\n"));
1108
1109 return ENODEV;
1110 }
1111 DEB(printf("ok\n"));
1112 }
1113
1114 err = chn_addfeeder(c, fc, fc->desc);
1115 if (err) {
1116 DEB(printf("can't add feeder %p, output %x, err %d\n", fc, fc->desc->out, err));
1117
1118 return err;
1119 }
1120 DEB(printf("added feeder %p, output %x\n", fc, c->feeder->desc->out));
1121 }
1122 }
1123
1124 if (fmtvalid(c->feeder->desc->out, chn_getcaps(c)->fmtlist)) {
1125 hwfmt = c->feeder->desc->out;
1126 } else {
1127 if (c->direction == PCMDIR_REC) {
1128 tmp[0] = c->format;
1129 tmp[1] = NULL;
1130 hwfmt = chn_fmtchain(c, tmp);
1131 } else {
1132 hwfmt = chn_fmtchain(c, chn_getcaps(c)->fmtlist);
1133 }
1134 }
1135
1136 if (hwfmt == 0)
1137 return ENODEV;
1138
1139 sndbuf_setfmt(c->bufhard, hwfmt);
1140
1141 return 0;
1142}
1143
1144int
1145chn_notify(struct pcm_channel *c, u_int32_t flags)
1146{
1147 struct pcmchan_children *pce;
1148 struct pcm_channel *child;
1149 int run;
1150
1151 if (SLIST_EMPTY(&c->children))
1152 return ENODEV;
1153
1154 run = (c->flags & CHN_F_TRIGGERED)? 1 : 0;
1155 /*
1156 * if the hwchan is running, we can't change its rate, format or
1157 * blocksize
1158 */
1159 if (run)
1160 flags &= CHN_N_VOLUME | CHN_N_TRIGGER;
1161
1162 if (flags & CHN_N_RATE) {
1163 /*
1164 * we could do something here, like scan children and decide on
1165 * the most appropriate rate to mix at, but we don't for now
1166 */
1167 }
1168 if (flags & CHN_N_FORMAT) {
1169 /*
1170 * we could do something here, like scan children and decide on
1171 * the most appropriate mixer feeder to use, but we don't for now
1172 */
1173 }
1174 if (flags & CHN_N_VOLUME) {
1175 /*
1176 * we could do something here but we don't for now
1177 */
1178 }
1179 if (flags & CHN_N_BLOCKSIZE) {
1180 int blksz;
1181 /*
1182 * scan the children, find the lowest blocksize and use that
1183 * for the hard blocksize
1184 */
1185 blksz = sndbuf_getmaxsize(c->bufhard) / 2;
1186 SLIST_FOREACH(pce, &c->children, link) {
1187 child = pce->channel;
1188 if (sndbuf_getblksz(child->bufhard) < blksz)
1189 blksz = sndbuf_getblksz(child->bufhard);
1190 }
1191 chn_setblocksize(c, 2, blksz);
1192 }
1193 if (flags & CHN_N_TRIGGER) {
1194 int nrun;
1195 /*
1196 * scan the children, and figure out if any are running
1197 * if so, we need to be running, otherwise we need to be stopped
1198 * if we aren't in our target sstate, move to it
1199 */
1200 nrun = 0;
1201 SLIST_FOREACH(pce, &c->children, link) {
1202 child = pce->channel;
1203 if (child->flags & CHN_F_TRIGGERED)
1204 nrun = 1;
1205 }
1206 if (nrun && !run)
1207 chn_start(c, 1);
1208 if (!nrun && run)
1209 chn_abort(c);
1210 }
1211 return 0;
1212}