From 609e9078fac05860980ab0bc06d56c76a1af6457 Mon Sep 17 00:00:00 2001 From: Gabe Lerner Date: Sat, 1 Aug 2026 16:24:24 -0700 Subject: [PATCH 1/3] nrf/usb: don't re-arm OUT endpoints twice per packet A bulk/interrupt OUT endpoint resumes accepting packets when the EasyDMA transfer completes *or* when SIZE.EPOUT[n] is written. Either event on its own re-arms it. read_dma() waits for ENDEPOUT, which has already re-armed the endpoint, and then writes SIZE.EPOUT as well. That second write arms it again, so the endpoint can accept a further packet on top of one it has not yet handed to software. The unread packet is overwritten in the endpoint's internal buffer and lost, and the following packet is read in its place. This only shows up when the host has another packet ready during the window between ENDEPOUT and the SIZE write, so it needs sustained OUT traffic and an executor that is slow to call read_packet again. It presents as silent data loss rather than an error: no event is missed, no register reads back an unexpected value, and the driver reports a full-size packet. Nordic's nrfx never writes SIZE.EPOUT on the completion path; its only write is nrf_usbd_epout_clear(), called from nrfx_usbd_transfer_out_drop(), which deliberately discards a packet without a DMA transfer. The write in endpoint_set_enabled() stays: before any DMA has run, that path is the only way to arm the endpoint initially. Co-Authored-By: Claude Opus 5 (1M context) --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/usb/mod.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 9c491bc85a..1909da232d 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate - added: System OFF support for the nRF54L series. +- bugfix: usb: don't re-arm OUT endpoints twice per packet, which could silently drop received packets under load. ## 0.11.0 - 2026-06-16 diff --git a/embassy-nrf/src/usb/mod.rs b/embassy-nrf/src/usb/mod.rs index 2568089da0..29100151cc 100644 --- a/embassy-nrf/src/usb/mod.rs +++ b/embassy-nrf/src/usb/mod.rs @@ -541,7 +541,14 @@ unsafe fn read_dma(regs: pac::usbd::Usbd, i: usize, buf: &mut [u8]) -> Result Date: Sat, 1 Aug 2026 16:27:00 -0700 Subject: [PATCH 2/3] nrf/usb: apply Erratum 199 workaround around USBD EasyDMA nRF52840 Erratum 199, "USBD cannot receive tasks during DMA": while an EasyDMA transfer is in progress, a USBD task is silently dropped rather than performed. The documented workaround is to write 0x00000082 to 0x40027C1C when starting a transfer and 0x00000000 once it completes. dma_start() and dma_end() already bracket every USBD EasyDMA transfer and are exactly the hooks this calls for, but they were bare compiler fences, so the workaround was absent. The sibling errata 187 and 171 are already implemented in the same module. This matches nrfx, which performs the same two writes in usbd_dma_pending_set() and usbd_dma_pending_clear() under nrfx_usbd_errata_199(). Applied unconditionally: Nordic's nrf52_errata_199() returns true for every nRF52840 revision, including its default arm, so there is no revision to check against. Gated on nrf52840, the part the erratum is documented for. Co-Authored-By: Claude Opus 5 (1M context) --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/usb/mod.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 1909da232d..c26405a0fa 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - added: System OFF support for the nRF54L series. - bugfix: usb: don't re-arm OUT endpoints twice per packet, which could silently drop received packets under load. +- bugfix: usb: apply the nRF52840 Erratum 199 workaround around USBD EasyDMA transfers. ## 0.11.0 - 2026-06-16 diff --git a/embassy-nrf/src/usb/mod.rs b/embassy-nrf/src/usb/mod.rs index 29100151cc..c1a8c72fe1 100644 --- a/embassy-nrf/src/usb/mod.rs +++ b/embassy-nrf/src/usb/mod.rs @@ -735,9 +735,11 @@ impl<'d> driver::ControlPipe for ControlPipe<'d> { fn dma_start() { compiler_fence(Ordering::Release); + errata::dma_start(); } fn dma_end() { + errata::dma_end(); compiler_fence(Ordering::Acquire); } @@ -850,6 +852,31 @@ mod errata { (addr as *mut u32).read_volatile() } + /// Works around Erratum 199: "USBD cannot receive tasks during DMA". A USBD + /// task triggered while an EasyDMA transfer is in progress is silently + /// dropped instead of being performed. + /// + /// Applies to every nRF52840 revision — Nordic's `nrf52_errata_199()` + /// returns true for all of them, including its default arm — so unlike + /// errata 187 and 171 above there is no revision to check against. + /// + /// Called around every USBD EasyDMA transfer, matching nrfx's + /// `usbd_dma_pending_set` / `usbd_dma_pending_clear`. + pub fn dma_start() { + #[cfg(feature = "nrf52840")] + unsafe { + poke(0x40027C1C, 0x00000082); + } + } + + /// Counterpart to [`dma_start`]. Works around Erratum 199. + pub fn dma_end() { + #[cfg(feature = "nrf52840")] + unsafe { + poke(0x40027C1C, 0x00000000); + } + } + pub fn pre_enable() { // Works around Erratum 187 on chip revisions 1 and 2. #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] From 821d5c6da8cc3fb19707dda8881b21120f8dd2ed Mon Sep 17 00:00:00 2001 From: gabelerner-kernel <52436903+gabelerner-kernel@users.noreply.github.com> Date: Sat, 1 Aug 2026 20:17:18 -0700 Subject: [PATCH 3/3] Update embassy-nrf/src/usb/mod.rs Co-authored-by: leftger --- embassy-nrf/src/usb/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-nrf/src/usb/mod.rs b/embassy-nrf/src/usb/mod.rs index c1a8c72fe1..0f97ad728b 100644 --- a/embassy-nrf/src/usb/mod.rs +++ b/embassy-nrf/src/usb/mod.rs @@ -862,6 +862,7 @@ mod errata { /// /// Called around every USBD EasyDMA transfer, matching nrfx's /// `usbd_dma_pending_set` / `usbd_dma_pending_clear`. + /// based on https://docs.nordicsemi.com/r/bundle/errata_nrf52840_rev3/page/err/nrf52840/rev3/latest/anomaly_840_199.html pub fn dma_start() { #[cfg(feature = "nrf52840")] unsafe {