Skip to content

kernel::dma_broker

DMA Broker (Phase 3).

Manages a pool of DMA-safe SRAM and provides safe submission/await for physical driver tasks. Drivers never touch DMA engine registers — the broker validates and programs them.

fn alloc(size: u32) -> Result<DmaHandle, DmaError>

Allocate a DMA-safe buffer (bump allocator over the linker dma_pool).

Item 8: both the alignment step (size + 3) and the bump step (start + aligned) used unchecked u32 arithmetic. A size near u32::MAX wraps (size + 3) & !3 to a small aligned value, and even with a merely large-but-in-range aligned, start + aligned can wrap past u32::MAX back to a small end — in both cases the end > pool_size() guard is bypassed by the wrapped value looking small, so the caller gets Ok with a handle describing memory outside (or the bump pointer moving backward, aliasing already-live allocations rather than past) the pool. Checked arithmetic turns both into the pool-exhausted error the size genuinely earned instead of a bogus success.

References: DmaError, DmaHandle

fn available() -> u32

Bytes still free in the pool.

The bump allocator never frees, so this only falls. A caller that wants to size a buffer to what the pool can actually spare — the display’s DMA chunk, which follows the pool rather than a hardcoded constant — reads this once before allocating.

fn await_transfer(id: DmaTransferId, timeout_ms: u32) -> Result<(), DmaError>

Block until id completes, or timeout_ms passes.

A real block, not a poll. It was a 1 ms sampling loop for exactly as long as queue::deadline_for was taking the scheduler lock recursively and panicking every blocking receive; with that fixed the task can be descheduled properly and woken by the interrupt.

Completions for other transfers are discarded rather than put back. A stale id belongs to a transfer whose waiter has already given up, and returning it to the queue would spin this loop against something that never drains.

References: DmaError, DmaTransferId

fn begin(handle: &DmaHandle) -> Result<DmaTransferId, DmaError>

Begin a transfer and get the id its completion will carry.

Checks the caller owns the buffer. Starting the engine is the driver’s job; this only mints the id the two sides agree on.

References: DmaError, DmaHandle, DmaTransferId

fn begin_pair(source: &DmaHandle, destination: &DmaHandle) -> Result<DmaTransferId, DmaError>

Begin one transfer spanning two owned buffers.

Full-duplex engines read one broker allocation while writing another. Both ownership checks happen before the id is minted, so a driver cannot start half of a transfer over another task’s memory.

References: DmaError, DmaHandle, DmaTransferId

fn signal_complete(id: DmaTransferId)

Signal that id has finished. Call from a driver’s top-half.

The queue’s ISR path, which wakes a blocked receiver. A full queue drops the message rather than retrying: an ISR that spins waiting for a task to drain something is an ISR that never returns.

References: DmaTransferId