Skip to content

Make capture work + add full-duplex (record+playback) on modern kernels#8

Open
ljadach wants to merge 1 commit into
mmm444:masterfrom
ljadach:full-duplex-capture-fix
Open

Make capture work + add full-duplex (record+playback) on modern kernels#8
ljadach wants to merge 1 commit into
mmm444:masterfrom
ljadach:full-duplex-capture-fix

Conversation

@ljadach
Copy link
Copy Markdown

@ljadach ljadach commented May 28, 2026

Summary

This makes capture/recording work on the Akai EIE Pro - long reported broken (#1, #3, #7) - and enables simultaneous playback + capture (full duplex) on current kernels. Tested on Linux Mint 22.3 / kernel 6.17.0-29-generic: recorded a 4-channel overdub while playing back, no crashes.

The playback path is left unchanged; all changes are on the capture side plus one shared-reset serialization.

Background

Capture never produced data: arecord blocked forever and wrote a 44-byte (header-only) file. Investigating revealed a chain of issues, each hidden behind the previous one.

Root causes & fixes

  1. Capture URBs were never submitted. init_cap_urbs() allocates them, but nothing ever called usb_submit_urb() on them - there was submit_init_sync_urbs() / submit_init_play_urbs() but no capture equivalent, so no completion ever fired. Added submit_init_cap_urbs() and call it from the capture trigger START (by then reset_eie() from prepare has the sync + playback streams flowing, so the device is clocked and the bulk-in endpoint is live). Resubmission in cap_urb_complete() is now gated on CAPTURE_RUNNING so the URBs drain cleanly on stop.

  2. Missing URB_NO_TRANSFER_DMA_MAP on capture URBs. They use usb_alloc_coherent buffers but didn't set the flag (the playback/sync URBs do). On kernels with the vmalloc-DMA-map hardening the core then tries to DMA-map the buffer and the submit fails with -EAGAIN (xhci_hcd: rejecting DMA map of vmalloc memory). Set the flag.

  3. Capture buffer overflow -> kernel panic. cap_urb_complete() writes BYTES_PER_FRAME_CAP (16, i.e. 4x __le32) per frame, but capture shared the playback hw advertising S24_3LE = 12 bytes/frame, so ALSA sized the runtime buffer at 12. Once the URBs actually flowed, the 16-byte writes overran the buffer -> memory corruption -> panic. Capture now advertises its own SNDRV_PCM_FMTBIT_S32_LE (16 bytes/frame) in eie_cpcm_open().

  4. Full-duplex reset race. Opening playback and capture near-simultaneously ran reset_eie() from both *_prepare callbacks concurrently, re-submitting already-active URBs (-EBUSY, "URB submitted while active"). Serialized the if (rate != eie->rate) reset_eie() decision under devices_mutex in both prepares, so the second stream sees the device already streaming and skips the redundant reset.

  5. Capture decoded to noise. The 24-bit sample was written low-aligned and unsigned, so S32 -> S16 downconversion kept only ~8 noisy bits. Sign-align the value into S32_LE (ch << 8).

Testing

  • Linux Mint 22.3 (Ubuntu 24.04 base), kernel 6.17.0-29-generic, Akai EIE Pro 09e8:0010.
  • Builds clean (needs the existing >6.12 vmalloc handling already in master).
  • Capture-only: records 4-channel audio.
  • Full duplex: played a clip out through the EIE while recording a live guitar on input 1 - clean overdub on playback, dmesg clean, no crash.

Known remaining work (honest)

  • cap_urb_complete() still updates cap_buf_pos / cap_frames without the spinlock (the existing TODO); fine in light testing, should be hardened for heavy DAW load.
  • Only tested at 48 kHz so far.
  • Capture can reach full scale; trim the EIE input gain for headroom (or reduce the shift).

This was debugged with a lot of dmesg-driven iteration; the commit message lists the same chain. Happy to split into smaller commits if preferred.

Capture never produced data (issues mmm444#1, mmm444#3, mmm444#7): arecord blocked and wrote a
44-byte header-only file. Fixing it uncovered a chain of issues:

- Capture URBs were allocated but never submitted (no submit_init_cap_urbs).
- Capture URBs lacked URB_NO_TRANSFER_DMA_MAP, so on kernels with the
  vmalloc-DMA-map check usb_submit_urb fails with -EAGAIN.
- cap_urb_complete writes 16 bytes/frame but the stream advertised S24_3LE
  (12 bytes/frame) via the shared hw, overrunning the buffer -> panic.
- In full duplex reset_eie ran from both prepares concurrently and
  re-submitted active URBs (-EBUSY).
- The 24-bit sample was written low-aligned/unsigned, so S32->S16 left noise.

Changes:
- Add submit_init_cap_urbs(); submit cap URBs from the capture trigger and
  gate resubmission on CAPTURE_RUNNING so they drain on stop.
- Set URB_NO_TRANSFER_DMA_MAP on the capture URBs.
- Capture advertises its own S32_LE format (16 bytes/frame) in eie_cpcm_open.
- Serialize the reset decision in both prepares under devices_mutex.
- Sign-align the decoded 24-bit sample into S32_LE (<< 8).

Tested on Linux Mint 22.3, kernel 6.17.0-29-generic: simultaneous playback
and 4-channel capture (recorded an overdub), dmesg clean, no crash.

cap_urb_complete still lacks the spinlock noted in the TODO; only tested at
48 kHz.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant