Skip to content

fix(dwc2): bound RXFLVL/IEPINT-OEPINT storms to avoid Interrupt WDT crashes - #83

Closed
hirokada wants to merge 65 commits into
espressif:masterfrom
Backbone-Labs:hirokiokada/FW-1861-dwc2-interrupt-storm-crash-fix
Closed

fix(dwc2): bound RXFLVL/IEPINT-OEPINT storms to avoid Interrupt WDT crashes#83
hirokada wants to merge 65 commits into
espressif:masterfrom
Backbone-Labs:hirokiokada/FW-1861-dwc2-interrupt-storm-crash-fix

Conversation

@hirokada

@hirokada hirokada commented Jul 27, 2026

Copy link
Copy Markdown

Description

Under a rapid, sustained sequence of USB bus resets (e.g. frequent physical cable plug/unplug cycling, or a downstream host repeatedly re-enumerating), dcd_int_handler() could be starved of CPU for 300ms+ by two different unbounded/high-rate conditions, tripping the ESP32 Interrupt WDT and crashing the device. This PR bounds both, and also fixes a separate NULL-pointer crash left over from a stale TXFE interrupt-enable bit after a bus reset.

Specifically:

  • The RXFLVL drain loop had no iteration cap and ran until hardware reported the RX FIFO empty. Under a hostile refill rate (FIFO filling faster than 64 packets/iteration can drain), it could spin unbounded entirely inside the ISR. Added a per-entry iteration cap, and when hit, mask RXFLVLM for a short cooldown instead of re-enabling immediately — otherwise many individually-bounded ISR entries back to back can still starve the CPU in aggregate.
  • IEPINT/OEPINT re-entering dcd_int_handler at a pathological rate (e.g. during rapid, repeated re-enumeration attempts) produced the same CPU-starvation crash via a different path. handle_ep_irq() itself is already bounded (a fixed small loop over endpoint count), so this tracks entry rate in a rolling window instead of a per-call iteration count, and backs off with the same cooldown pattern as RXFLVL when the rate is judged pathological.
  • handle_bus_reset() left a stale TXFE interrupt-enable bit (diepempmsk) set for endpoints with no queued transfer after a reset. A re-opened endpoint could get a TXFE interrupt before the class driver queued anything, and handle_epin_slave() would dereference the resulting NULL xfer->buffer in dfifo_write_packet() — a LoadProhibited crash. Cleared diepempmsk on bus reset, plus added a direct NULL guard in handle_epin_slave() as defense-in-depth.

Both cooldown activation counts are exposed via lock-free counters (dcd_dwc2_rxflvl_cooldown_count() / dcd_dwc2_ep_irq_cooldown_count()), intended to be read from normal task context — never logged from inside the ISR itself, since ESP_LOG/vprintf's newlib lock is unsafe to call from this interrupt level.

Testing

Verified on hardware with serial log capture under sustained rapid USB cable plug-unplug cycling and repeated device-side reboots (the specific scenario that reliably reproduced the Interrupt WDT / LoadProhibited crashes before this fix). No crashes reproduced over extended (several-minutes-plus) sessions after this fix; the cooldown activation counters were confirmed via log to actually engage under real stress conditions without causing regressions in normal HID/audio traffic.


Closed - this was mistakenly opened against the wrong base repository. The corresponding fix has been merged into our internal fork instead.

Checklist

  • 🚨 This PR does not introduce breaking changes.
  • All CI checks (GH Actions) pass.
  • Documentation is updated as needed.
  • Tests are updated or added as necessary.
  • Code is well-commented, especially in complex areas.
  • Git history is clean — commits are squashed to the minimum necessary.

tore-espressif and others added 30 commits July 12, 2023 17:16
Device can notify the host about network connection status by
CDC NetworkConnection.
We used it to solve the problem that WiFi devices would connect to different routers
feat(network): add network connect and disconnect status
1. Set default network state is connected, in case not actively call tud_network_connect
2. The application layer can get the call result
fix(NCM): set default NCM mode is connect, add notify judgement
TinyUSB release/0.15: DCD DWC2 interrupt handling cleanup
…rride

TinyUSB release/0.15: DCD DWC2 fix plug/unplug detection with bvalid signal override configuration
1. fix next interface may have IAD_DESC
2. fix vs_itf can't be open on bulk mode.
…erface

fix: uvc multiple interface support
            - Check if endpoint is avaliable while opening it
            - Release endponint once is closed
…oint_count

dcd_dwc2: Added runtime checking the amount of opened endpoints
…_return_value

hotfix(dwc_dwc2): Fixed return value in function with TU_ASSERT()
…n_ep_counters

fix(dcd_dwc2): Reset opened EP counters on bus_reset event

Closes espressif/esp-usb#28
…mple_build

feature(ci): Build ESP-IDF examples in CI
shogo-sugihara and others added 11 commits December 19, 2024 17:47
…ate-bb-usb-implementation-to-utilize-tinyusb-uninstall

FIR-572 update bb usb implementation to utilize tinyusb uninstall
Merge hathach/tinyusb 0.18 + Backbone edits
Add back alignment to buffer which get used for usb transfers
…estigate-whether-sleep-or-apb-locks-can-be-released-for

FIR-799/1238 Suppress assertion after USB PHY is stopped
…rashes

Under sustained rapid AP53782-driven USB restarts (Backbone-Labs/tori
FW-1861/1881), dcd_int_handler() could be starved of CPU for 300ms+ by
either of two unbounded/high-rate conditions, tripping the ESP32
Interrupt WDT:

- The RXFLVL drain loop had no iteration cap and ran until hardware
  reported the RX FIFO empty; a hostile refill rate (FIFO filling faster
  than 64 packets/iteration can drain) let it spin unbounded entirely
  inside the ISR. Add a per-entry iteration cap, and when hit, mask
  RXFLVLM for a short cooldown instead of re-enabling immediately -
  otherwise many individually-bounded ISR entries back to back still
  starve the CPU in aggregate.

- IEPINT/OEPINT re-entering dcd_int_handler at a pathological rate (e.g.
  during rapid re-enumeration attempts) produced the same starvation via
  a different path - handle_ep_irq() itself is already bounded, so this
  targets entry *rate* via a rolling window instead of a per-call
  iteration count.

- handle_bus_reset() left a stale TXFE interrupt-enable bit
  (diepempmsk) set for endpoints with no queued transfer after a reset;
  a re-opened endpoint could get a TXFE interrupt before the class
  driver queued anything, and handle_epin_slave() would dereference the
  resulting NULL xfer->buffer in dfifo_write_packet() - a LoadProhibited
  crash. Clear diepempmsk on bus reset, plus a direct NULL guard in
  handle_epin_slave() as defense-in-depth.

Both cooldown activation counts are exposed via lock-free counters
(dcd_dwc2_rxflvl_cooldown_count()/dcd_dwc2_ep_irq_cooldown_count()) read
from normal task context - never logged from inside the ISR itself,
since ESP_LOG/vprintf's newlib lock is unsafe to call from this
interrupt level.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@hirokada

Copy link
Copy Markdown
Author

Closing - this was mistakenly opened against the wrong base repository (should target our internal fork Backbone-Labs/tinyusb, not the upstream project). Re-opening against the correct target.

@hirokada hirokada closed this Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants