{"record":{"id":"762836d74ae6d555","repo":"embassy-rs/embassy","slug":"uart-dma-reported-invalid-write-addr","errorCode":null,"errorMessage":"UART DMA reported invalid `write_addr`","messagePattern":"UART DMA reported invalid `write_addr`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"embassy-rp/src/uart/mod.rs","lineNumber":728,"sourceCode":"            } else if errors.beris() {\n                // We got a Line Break! By this point, we've finished/aborted the DMA\n                // transaction, which means that we need to figure out where it left off\n                // by looking at the write_addr.\n                //\n                // First, we do a sanity check to make sure the write value is within the\n                // range of DMA we just did.\n                let sval = buffer.as_ptr() as usize;\n                let eval = sval + buffer.len();\n\n                // This is the address where the DMA would write to next\n                let next_addr = self.rx_dma.as_mut().unwrap().write_addr() as usize;\n\n                // If we DON'T end up inside the range, something has gone really wrong.\n                // Note that it's okay that `eval` is one past the end of the slice, as\n                // this is where the write pointer will end up at the end of a full\n                // transfer.\n                if (next_addr < sval) || (next_addr > eval) {\n                    unreachable!(\"UART DMA reported invalid `write_addr`\");\n                }\n\n                if (next_addr - sval) < min_count {\n                    sbuffer = &mut buffer[(next_addr - sval)..];\n                    continue;\n                }\n\n                let regs = self.info.regs;\n                let all_full = next_addr == eval;\n\n                // NOTE: This is off label usage of RSR! See the issue below for\n                // why I am not checking if there is an \"extra\" FIFO byte, and why\n                // I am checking RSR directly (it seems to report the status of the LAST\n                // POPPED value, rather than the NEXT TO POP value like the datasheet\n                // suggests!)\n                //\n                // issue: https://github.com/raspberrypi/pico-feedback/issues/367\n                let last_was_break = regs.uartrsr().read().be();","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/embassy-rs/embassy/blob/463a07b963419a1bfe61d5d597c44acb810afb8b/embassy-rp/src/uart/mod.rs#L710-L746","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet mut buf = [0u8; 0];\nuart.read_to_break(&mut buf).await?; // invalid DMA range\n// after\nlet mut buf = [0u8; 256];\nuart.read_to_break(&mut buf).await?;","handlingStrategy":"try-catch","validationCode":"// Before calling, ensure a valid buffer and a quiesced DMA channel:\nassert!(buffer.len() > 0, \"read_to_break needs a non-empty buffer\");\n// abort/complete any previous read future before starting a new one","typeGuard":null,"tryCatchPattern":"// A panic here aborts the firmware; recover by construction:\n// await each read_to_break future to completion, never drop and restart mid-transfer.\nlet n = uart.read_to_break(&mut buf).await?;","preventionTips":["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"],"tags":["uart","dma","embedded","panic","rp2040"],"backgroundTag":"internal-invariant-violation","analyzedSha":"463a07b963419a1bfe61d5d597c44acb810afb8b","analyzedAt":"2026-09-10T13:38:26.660Z","contentChangedAt":"2026-09-10T13:38:26.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}