embassy-rs/embassy · error

MDMA: max block count hit

Error message

MDMA: max block count hit

What it means

This panic comes from MDMA channel configuration in embassy-stm32 when computing the block layout for a transfer. MDMA moves data in blocks, and the driver factorizes the total transfer length into block_count x block_size. If no valid factorization is found before block_count exceeds MDMA_MAX_BLOCK_COUNT (typically 128), the driver panics because the hardware cannot express the transfer.

Solutions

  1. Reduce the transfer length so the byte count (mem_len * word size) is comfortably below MDMA_MAX_BLOCK * MDMA_MAX_BLOCK_COUNT and has small factors (e.g. multiple of 128 or 1024).
  2. Pad or align the buffer length to a highly composite size so block_count * block_size == mem_len_bytes is found immediately (initial block_size = MDMA_MAX_BLOCK divides evenly).
  3. Split the transfer into several smaller DMA operations.
  4. Use a different DMA type (DMA/BDMA or GPDMA channel) that handles arbitrary lengths in 65535-byte blocks without factorization.
  5. Note: sizes with small factors never hit this loop break condition — check mem_len bytes for prime factors before choosing buffer size.

Example fix

// before
let mut buf = [0u8; 65407]; // prime-ish size, poor factorization
channel.read(&mut buf).await;
// after
let mut buf = [0u8; 65536]; // power of two: factors cleanly, one block
channel.read(&mut buf).await;
Defensive patterns

Strategy: validation

Validate before calling

const MDMA_MAX_BLOCK: usize = 16384; // per chip family; check HAL constants
const MDMA_MAX_BLOCK_COUNT: usize = 128;
fn mdma_len_ok(byte_len: usize) -> bool {
    byte_len > 0 && byte_len <= MDMA_MAX_BLOCK * MDMA_MAX_BLOCK_COUNT
        && (byte_len % 128 == 0 || byte_len <= MDMA_MAX_BLOCK)
}

Type guard

fn is_factorable_len(byte_len: usize) -> bool {
    // sizes <= MDMA_MAX_BLOCK or with a small divisor never exceed max block count
    byte_len <= 16384 || (byte_len % 128 == 0 && byte_len <= 16384 * 128)
}

Prevention

When it happens

Trigger: Starting an MDMA transfer (e.g. read/write on an MDMA-backed channel) whose total byte length (mem_len * word size) cannot be decomposed into at most MDMA_MAX_BLOCK_COUNT blocks whose sizes exactly multiply back to the length — typically because the byte length is a large number with poor factors near the upper limit (max block size * max block count), such as a large prime or awkwardly sized buffer.

Common situations: Using large buffers with sizes that are large primes or poorly factorable values (e.g. 65407-byte buffers), transferring buffers whose byte length barely exceeds MDMA_MAX_BLOCK * MDMA_MAX_BLOCK_COUNT, or word sizes (u16/u32) pushing an element count over the factorizable range.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/4a161aa9ee71c8cb. Report an issue: GitHub.

Appendix: source

Thrown at embassy-stm32/src/dma/dma_bdma.rs:713

                assert!(mem_len_bytes > 0 && mem_len_bytes <= MDMA_MAX_BLOCK * MDMA_MAX_BLOCK_COUNT);

                // Find the best block size/count. This is essentially a factorisation problem
                // So it's best to avoid large prime number transfer sizes.
                let mut block_count = mem_len_bytes.div_ceil(MDMA_MAX_BLOCK);
                let mut block_size = mem_len_bytes.div_ceil(block_count);

                loop {
                    // Everything matches up so we're good to go
                    if block_count * block_size == mem_len_bytes {
                        break;
                    }

                    // Try a higher block count, lower block size
                    block_count += 1;
                    block_size = mem_len_bytes.div_ceil(block_count);

                    if block_count > MDMA_MAX_BLOCK_COUNT {
                        panic!("MDMA: max block count hit");
                    }
                }

                // MDMA requires BNDT (block_size) to be a multiple of TLEN+1 (buffer_size).
                // Auto-decrease buffer_size until it divides cleanly into block_size.
                let mut buffer_size = options.buffer_size as usize;
                while block_size % buffer_size != 0 && buffer_size > 1 {
                    buffer_size -= 1;
                }
                // Update the options so the TCR write uses the correct value

                let (sinc, dinc) = match (incr_mem, dir) {
                    (Increment::None, _) => (Incmode::Fixed, Incmode::Fixed),
                    (Increment::Both, _) => (Incmode::Increment, Incmode::Increment),
                    (Increment::Memory, Dir::MemoryToMemory) => (Incmode::Increment, Incmode::Fixed),
                    (_, Dir::MemoryToMemory) => (Incmode::Increment, Incmode::Increment),
                    (Increment::Peripheral, Dir::PeripheralToMemory) => (Incmode::Increment, Incmode::Fixed),
                    (Increment::Peripheral, Dir::MemoryToPeripheral) => (Incmode::Fixed, Incmode::Increment),

View on GitHub (pinned to 463a07b963)