embassy-rs/embassy · critical

UART DMA reported invalid `write_addr`

Error message

UART DMA reported invalid `write_addr`

What it means

`read_to_break_with_count` uses UART DMA and periodically inspects the DMA write_addr to see how much data has landed in the buffer. The address is expected to stay within [buffer start, buffer end + 1]. If the reported address falls outside that range, the DMA transfer state is inconsistent with the buffer, so the driver panics rather than read out-of-bounds memory.

Solutions

  1. Ensure each read future fully completes (or the DMA channel is aborted/reset) before starting another read on the same UART.
  2. Pass a non-empty, validly-aligned buffer with enough capacity for the expected break-delimited data.
  3. Update embassy-rp; DMA lifecycle bugs around dropped futures have been fixed in later releases.
  4. If it persists, dump the DMA `write_addr`, `read_addr`, and transfer count registers and file an upstream issue.

Example fix

// before
let mut buf = [0u8; 0];
uart.read_to_break(&mut buf).await?; // invalid DMA range
// after
let mut buf = [0u8; 256];
uart.read_to_break(&mut buf).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Before calling, ensure a valid buffer and a quiesced DMA channel:
assert!(buffer.len() > 0, "read_to_break needs a non-empty buffer");
// abort/complete any previous read future before starting a new one

Try / catch

// A panic here aborts the firmware; recover by construction:
// await each read_to_break future to completion, never drop and restart mid-transfer.
let n = uart.read_to_break(&mut buf).await?;

Prevention

When it happens

Trigger: During `read_to_break`/`read_to_break_with_count`, the DMA channel's `write_addr` evaluates to an address below the slice start (`sval`) or beyond one-past-the-end (`eval`), e.g. due to a corrupted/invalid DMA descriptor or a buffer moved/freed while the transfer is active.

Common situations: Passing a zero-length or improperly aligned buffer; a future-safety bug where the future is dropped and a new one created while the old DMA transfer continues; hardware/DMA channel reuse without proper abort between reads.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at embassy-rp/src/uart/mod.rs:728

            } else if errors.beris() {
                // We got a Line Break! By this point, we've finished/aborted the DMA
                // transaction, which means that we need to figure out where it left off
                // by looking at the write_addr.
                //
                // First, we do a sanity check to make sure the write value is within the
                // range of DMA we just did.
                let sval = buffer.as_ptr() as usize;
                let eval = sval + buffer.len();

                // This is the address where the DMA would write to next
                let next_addr = self.rx_dma.as_mut().unwrap().write_addr() as usize;

                // If we DON'T end up inside the range, something has gone really wrong.
                // Note that it's okay that `eval` is one past the end of the slice, as
                // this is where the write pointer will end up at the end of a full
                // transfer.
                if (next_addr < sval) || (next_addr > eval) {
                    unreachable!("UART DMA reported invalid `write_addr`");
                }

                if (next_addr - sval) < min_count {
                    sbuffer = &mut buffer[(next_addr - sval)..];
                    continue;
                }

                let regs = self.info.regs;
                let all_full = next_addr == eval;

                // NOTE: This is off label usage of RSR! See the issue below for
                // why I am not checking if there is an "extra" FIFO byte, and why
                // I am checking RSR directly (it seems to report the status of the LAST
                // POPPED value, rather than the NEXT TO POP value like the datasheet
                // suggests!)
                //
                // issue: https://github.com/raspberrypi/pico-feedback/issues/367
                let last_was_break = regs.uartrsr().read().be();

View on GitHub (pinned to 463a07b963)