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
- Ensure each read future fully completes (or the DMA channel is aborted/reset) before starting another read on the same UART.
- Pass a non-empty, validly-aligned buffer with enough capacity for the expected break-delimited data.
- Update embassy-rp; DMA lifecycle bugs around dropped futures have been fixed in later releases.
- 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
- Never abandon a DMA-backed read future and start another on the same UART
- Always pass a non-empty, appropriately sized buffer
- Let each read_to_break future run to completion or properly abort the DMA channel
- Update embassy-rp; DMA lifecycle fixes may resolve rare invalid write_addr reports
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
- unrecognized rx error
- Boot prepare error
- DMA data error
- RTS and CTS pins must be either both set or none set.
- Failed to apply workaround for UART
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)