📄 vwsnd.c
字号:
wport->sample_size); ad1843_setup_dac(&devc->lith, wport->sw_framerate, wport->sw_samplefmt, wport->sw_channels); li_enable_interrupts(&devc->lith, WRITE_INTR_MASK); } DBGRV(); return 0;}/* * pcm_shutdown_port - shut down one port (direction) for PCM I/O. * Only called from pcm_shutdown. */static void pcm_shutdown_port(vwsnd_dev_t *devc, vwsnd_port_t *aport, unsigned int mask){ unsigned long flags; vwsnd_port_hwstate_t hwstate; DECLARE_WAITQUEUE(wait, current); aport->swstate = SW_INITIAL; add_wait_queue(&aport->queue, &wait); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); spin_lock_irqsave(&aport->lock, flags); { hwstate = aport->hwstate; } spin_unlock_irqrestore(&aport->lock, flags); if (hwstate == HW_STOPPED) break; schedule(); } current->state = TASK_RUNNING; remove_wait_queue(&aport->queue, &wait); li_disable_interrupts(&devc->lith, mask); if (aport == &devc->rport) ad1843_shutdown_adc(&devc->lith); else /* aport == &devc->wport) */ ad1843_shutdown_dac(&devc->lith); li_shutdown_dma(&aport->chan); vfree(aport->swbuf); aport->swbuf = NULL; aport->byte_count = 0;}/* * pcm_shutdown undoes what pcm_setup did. * Also sets the ports' swstate to newstate. */static void pcm_shutdown(vwsnd_dev_t *devc, vwsnd_port_t *rport, vwsnd_port_t *wport){ DBGEV("(devc=0x%p, rport=0x%p, wport=0x%p)\n", devc, rport, wport); if (rport && rport->swbuf) { DBGPV("shutting down rport\n"); pcm_shutdown_port(devc, rport, READ_INTR_MASK); } if (wport && wport->swbuf) { DBGPV("shutting down wport\n"); pcm_shutdown_port(devc, wport, WRITE_INTR_MASK); } DBGRV();}static void pcm_copy_in(vwsnd_port_t *rport, int swidx, int hwidx, int nb){ char *src = rport->hwbuf + hwidx; char *dst = rport->swbuf + swidx; int fmt = rport->sw_samplefmt; DBGPV("swidx = %d, hwidx = %d\n", swidx, hwidx); ASSERT(rport->hwbuf != NULL); ASSERT(rport->swbuf != NULL); ASSERT(nb > 0 && (nb % 32) == 0); ASSERT(swidx % 32 == 0 && hwidx % 32 == 0); ASSERT(swidx >= 0 && swidx + nb <= rport->swbuf_size); ASSERT(hwidx >= 0 && hwidx + nb <= rport->hwbuf_size); if (fmt == AFMT_MU_LAW || fmt == AFMT_A_LAW || fmt == AFMT_S8) { /* See Sample Format Notes above. */ char *end = src + nb; while (src < end) *dst++ = *src++ ^ 0x80; } else memcpy(dst, src, nb);}static void pcm_copy_out(vwsnd_port_t *wport, int swidx, int hwidx, int nb){ char *src = wport->swbuf + swidx; char *dst = wport->hwbuf + hwidx; int fmt = wport->sw_samplefmt; ASSERT(nb > 0 && (nb % 32) == 0); ASSERT(wport->hwbuf != NULL); ASSERT(wport->swbuf != NULL); ASSERT(swidx % 32 == 0 && hwidx % 32 == 0); ASSERT(swidx >= 0 && swidx + nb <= wport->swbuf_size); ASSERT(hwidx >= 0 && hwidx + nb <= wport->hwbuf_size); if (fmt == AFMT_MU_LAW || fmt == AFMT_A_LAW || fmt == AFMT_S8) { /* See Sample Format Notes above. */ char *end = src + nb; while (src < end) *dst++ = *src++ ^ 0x80; } else memcpy(dst, src, nb);}/* * pcm_output() is called both from baselevel and from interrupt level. * This is where audio frames are copied into the hardware-accessible * ring buffer. * * Locking note: The part of this routine that figures out what to do * holds wport->lock. The longer part releases wport->lock, but sets * wport->flags & HW_BUSY. Afterward, it reacquires wport->lock, and * checks for more work to do. * * If another thread calls pcm_output() while HW_BUSY is set, it * returns immediately, knowing that the thread that set HW_BUSY will * look for more work to do before returning. * * This has the advantage that port->lock is held for several short * periods instead of one long period. Also, when pcm_output is * called from base level, it reenables interrupts. */static void pcm_output(vwsnd_dev_t *devc, int erflown, int nb){ vwsnd_port_t *wport = &devc->wport; const int hwmax = wport->hwbuf_max; const int hwsize = wport->hwbuf_size; const int swsize = wport->swbuf_size; const int fragsize = wport->hw_fragsize; unsigned long iflags; DBGEV("(devc=0x%p, erflown=%d, nb=%d)\n", devc, erflown, nb); spin_lock_irqsave(&wport->lock, iflags); if (erflown) wport->flags |= ERFLOWN; (void) __swb_inc_u(wport, nb); if (wport->flags & HW_BUSY) { spin_unlock_irqrestore(&wport->lock, iflags); DBGPV("returning: HW BUSY\n"); return; } if (wport->flags & DISABLED) { spin_unlock_irqrestore(&wport->lock, iflags); DBGPV("returning: DISABLED\n"); return; } wport->flags |= HW_BUSY; while (1) { int swptr, hwptr, hw_avail, sw_avail, swidx; vwsnd_port_hwstate_t hwstate = wport->hwstate; vwsnd_port_swstate_t swstate = wport->swstate; int hw_unavail; ustmsc_t ustmsc; hwptr = li_read_hwptr(&wport->chan); swptr = li_read_swptr(&wport->chan); hw_unavail = (swptr - hwptr + hwsize) % hwsize; hw_avail = (hwmax - hw_unavail) & -fragsize; sw_avail = wport->swb_i_avail & -fragsize; if (sw_avail && swstate == SW_RUN) { if (wport->flags & ERFLOWN) { wport->flags &= ~ERFLOWN; } } else if (swstate == SW_INITIAL || swstate == SW_OFF || (swstate == SW_DRAIN && !sw_avail && (wport->flags & ERFLOWN))) { DBGP("stopping. hwstate = %d\n", hwstate); if (hwstate != HW_STOPPED) { li_deactivate_dma(&wport->chan); wport->hwstate = HW_STOPPED; } wake_up(&wport->queue); break; } if (!sw_avail || !hw_avail) break; spin_unlock_irqrestore(&wport->lock, iflags); /* * We gave up the port lock, but we have the HW_BUSY flag. * Proceed without accessing any nonlocal state. * Do not exit the loop -- must check for more work. */ swidx = wport->swb_i_idx; nb = hw_avail; if (nb > sw_avail) nb = sw_avail; if (nb > hwsize - swptr) nb = hwsize - swptr; /* don't overflow hwbuf */ if (nb > swsize - swidx) nb = swsize - swidx; /* don't overflow swbuf */ ASSERT(nb > 0); if (nb % fragsize) { DBGP("nb = %d, fragsize = %d\n", nb, fragsize); DBGP("hw_avail = %d\n", hw_avail); DBGP("sw_avail = %d\n", sw_avail); DBGP("hwsize = %d, swptr = %d\n", hwsize, swptr); DBGP("swsize = %d, swidx = %d\n", swsize, swidx); } ASSERT(!(nb % fragsize)); DBGPV("copying swb[%d..%d] to hwb[%d..%d]\n", swidx, swidx + nb, swptr, swptr + nb); pcm_copy_out(wport, swidx, swptr, nb); li_write_swptr(&wport->chan, (swptr + nb) % hwsize); spin_lock_irqsave(&wport->lock, iflags); if (hwstate == HW_STOPPED) { DBGPV("starting\n"); li_activate_dma(&wport->chan); wport->hwstate = HW_RUNNING; li_read_USTMSC(&wport->chan, &ustmsc); ASSERT(wport->byte_count % wport->frame_size == 0); wport->MSC_offset = ustmsc.msc - wport->byte_count / wport->frame_size; } __swb_inc_i(wport, nb); wport->byte_count += nb; wport->frag_count += nb / fragsize; ASSERT(nb % fragsize == 0); wake_up(&wport->queue); } wport->flags &= ~HW_BUSY; spin_unlock_irqrestore(&wport->lock, iflags); DBGRV();}/* * pcm_input() is called both from baselevel and from interrupt level. * This is where audio frames are copied out of the hardware-accessible * ring buffer. * * Locking note: The part of this routine that figures out what to do * holds rport->lock. The longer part releases rport->lock, but sets * rport->flags & HW_BUSY. Afterward, it reacquires rport->lock, and * checks for more work to do. * * If another thread calls pcm_input() while HW_BUSY is set, it * returns immediately, knowing that the thread that set HW_BUSY will * look for more work to do before returning. * * This has the advantage that port->lock is held for several short * periods instead of one long period. Also, when pcm_input is * called from base level, it reenables interrupts. */static void pcm_input(vwsnd_dev_t *devc, int erflown, int nb){ vwsnd_port_t *rport = &devc->rport; const int hwmax = rport->hwbuf_max; const int hwsize = rport->hwbuf_size; const int swsize = rport->swbuf_size; const int fragsize = rport->hw_fragsize; unsigned long iflags; DBGEV("(devc=0x%p, erflown=%d, nb=%d)\n", devc, erflown, nb); spin_lock_irqsave(&rport->lock, iflags); if (erflown) rport->flags |= ERFLOWN; (void) __swb_inc_u(rport, nb); if (rport->flags & HW_BUSY || !rport->swbuf) { spin_unlock_irqrestore(&rport->lock, iflags); DBGPV("returning: HW BUSY or !swbuf\n"); return; } if (rport->flags & DISABLED) { spin_unlock_irqrestore(&rport->lock, iflags); DBGPV("returning: DISABLED\n"); return; } rport->flags |= HW_BUSY; while (1) { int swptr, hwptr, hw_avail, sw_avail, swidx; vwsnd_port_hwstate_t hwstate = rport->hwstate; vwsnd_port_swstate_t swstate = rport->swstate; hwptr = li_read_hwptr(&rport->chan); swptr = li_read_swptr(&rport->chan); hw_avail = (hwptr - swptr + hwsize) % hwsize & -fragsize; if (hw_avail > hwmax) hw_avail = hwmax; sw_avail = rport->swb_i_avail & -fragsize; if (swstate != SW_RUN) { DBGP("stopping. hwstate = %d\n", hwstate); if (hwstate != HW_STOPPED) { li_deactivate_dma(&rport->chan); rport->hwstate = HW_STOPPED; } wake_up(&rport->queue); break; } if (!sw_avail || !hw_avail) break; spin_unlock_irqrestore(&rport->lock, iflags); /* * We gave up the port lock, but we have the HW_BUSY flag. * Proceed without accessing any nonlocal state. * Do not exit the loop -- must check for more work. */ swidx = rport->swb_i_idx; nb = hw_avail; if (nb > sw_avail) nb = sw_avail; if (nb > hwsize - swptr) nb = hwsize - swptr; /* don't overflow hwbuf */ if (nb > swsize - swidx) nb = swsize - swidx; /* don't overflow swbuf */ ASSERT(nb > 0); if (nb % fragsize) { DBGP("nb = %d, fragsize = %d\n", nb, fragsize); DBGP("hw_avail = %d\n", hw_avail); DBGP("sw_avail = %d\n", sw_avail); DBGP("hwsize = %d, swptr = %d\n", hwsize, swptr); DBGP("swsize = %d, swidx = %d\n", swsize, swidx); } ASSERT(!(nb % fragsize)); DBGPV("copying hwb[%d..%d] to swb[%d..%d]\n", swptr, swptr + nb, swidx, swidx + nb); pcm_copy_in(rport, swidx, swptr, nb); li_write_swptr(&rport->chan, (swptr + nb) % hwsize); spin_lock_irqsave(&rport->lock, iflags); __swb_inc_i(rport, nb); rport->byte_count += nb; rport->frag_count += nb / fragsize; ASSERT(nb % fragsize == 0); wake_up(&rport->queue); } rport->flags &= ~HW_BUSY; spin_unlock_irqrestore(&rport->lock, iflags); DBGRV();}/* * pcm_flush_frag() writes zero samples to fill the current fragment, * then flushes it to the hardware. * * It is only meaningful to flush output, not input. */static void pcm_flush_frag(vwsnd_dev_t *devc){ vwsnd_port_t *wport = &devc->wport; DBGPV("swstate = %d\n", wport->swstate); if (wport->swstate == SW_RUN) { int idx = wport->swb_u_idx; int end = (idx + wport->hw_fragsize - 1) >> wport->hw_fragshift << wport->hw_fragshift; int nb = end - idx; DBGPV("clearing %d bytes\n", nb); if (nb) memset(wport->swbuf + idx, (char) wport->zero_word, nb); wport->swstate = SW_DRAIN; pcm_output(devc, 0, nb); } DBGRV();}/* * Wait for output to drain. This sleeps uninterruptibly because * there is nothing intelligent we can do if interrupted. This * means the process will be delayed in responding to the signal. */static void pcm_write_sync(vwsnd_dev_t *devc){ vwsnd_port_t *wport = &devc->wport; DECLARE_WAITQUEUE(wait, current); unsigned long flags; vwsnd_port_hwstate_t hwstate; DBGEV("(devc=0x%p)\n", devc); add_wait_queue(&wport->queue, &wait); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); spin_lock_irqsave(&wport->lock, flags); { hwstate = wport->hwstate; } spin_unlock_irqrestore(&wport->lock, flags); if (hwstate == HW_STOPPED) break; schedule(); } current->state = TASK_RUNNING; remove_wait_queue(&wport->queue, &wait); DBGPV("swstate = %d, hwstate = %d\n", wport->swstate, wport->hwstate); DBGRV();}/*****************************************************************************//* audio driver *//* * seek on an audio device always fails. */static void vwsnd_audio_read_intr(vwsnd_dev_t *devc, unsigned int status){ int overflown = status & LI_INTR_COMM1_OVERFLOW; if (status & READ_INTR_MASK) pcm_input(devc, overflown, 0);}static void vwsnd_audio_write_intr(vwsnd_dev_t *devc, unsigned int status){ int underflown = status & LI_INTR_COMM2_UNDERFLOW; if (status & WRITE_INTR_MASK) pcm_output(devc, underflown, 0);}static void vwsnd_audio_intr(int irq, void *dev_id, struct pt_regs *regs){ vwsnd_dev_t *devc = (vwsnd_dev_t *) dev_id; unsigned int status; DBGEV("(irq=%d, dev_id=0x%p, regs=0x%p)\n", irq, dev_id, regs); status = li_get_clear_intr_status(&devc->lith); vwsnd_audio_read_intr(devc, status); vwsnd_audio_write_intr(devc, status);}static ssize_t vwsnd_audio_do_read(struct file *file, char *buffe
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -