Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions embassy-nrf/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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.
- bugfix: usb: apply the nRF52840 Erratum 199 workaround around USBD EasyDMA transfers.

## 0.11.0 - 2026-06-16

Expand Down
37 changes: 36 additions & 1 deletion embassy-nrf/src/usb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,14 @@ unsafe fn read_dma(regs: pac::usbd::Usbd, i: usize, buf: &mut [u8]) -> Result<us
regs.events_endepout(i).write_value(0);
dma_end();

regs.size().epout(i).write(|_| ());
// Do NOT write SIZE.EPOUT here. A bulk/interrupt OUT endpoint resumes
// accepting packets when the EasyDMA transfer completes *or* when
// SIZE.EPOUT[n] is written — either one on its own re-arms it. ENDEPOUT
// above has already done so, and writing SIZE here arms it a second time,
// letting the endpoint accept a further packet on top of one it has not yet
// handed to software. That packet is overwritten in the endpoint's internal
// buffer and lost. Initial arming, before any DMA has run, is done in
// `endpoint_set_enabled`.

Ok(size)
}
Expand Down Expand Up @@ -728,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);
}

Expand Down Expand Up @@ -843,6 +852,32 @@ 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`.
/// 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 {
poke(0x40027C1C, 0x00000082);
}
}

/// Counterpart to [`dma_start`]. Works around Erratum 199.
pub fn dma_end() {
#[cfg(feature = "nrf52840")]
unsafe {
poke(0x40027C1C, 0x00000000);
}
}
Comment thread
gabelerner-kernel marked this conversation as resolved.

pub fn pre_enable() {
// Works around Erratum 187 on chip revisions 1 and 2.
#[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))]
Expand Down