summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-09-01 07:06:47 +0200
committerHiFiPHile <[email protected]>2026-09-02 15:21:41 +0200
commit3a1dc0dc1f57d45ed75aa9e176425901270e982e (patch)
tree00436812c6477719c18f629d298d70995336528c
parent86893fb2ddcf2a7d263a7fc0ad07783ddb98a21f (diff)
DWC2 host: clean up channels on disconnect
A root-port disconnect invalidates every active transfer. Retire channel and FIFO interrupt sources plus host-channel state in the disconnect ISR using the Linux DWC2 cleanup model instead of reinitializing the core and PHY, which can sleep on STM32 HS PHYs. Flush posted slave requests, request halts for enabled channels, clear channel interrupt and software ownership, and keep endpoint records closing until USBH processes the remove event. Reject transfer submissions to closing endpoints, preserve fast-replug notification, and re-enable the global host-channel interrupt when a new channel is initialized.
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c98
1 files changed, 81 insertions, 17 deletions
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 5bb5a979b..b6e8ecb03 100644
--- a/src/portable/synopsys/dwc2/hcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/hcd_dwc2.c
@@ -210,6 +210,55 @@ TU_ATTR_ALWAYS_INLINE static inline bool channel_disable(const dwc2_regs_t* dwc2
return true;
}
+// Retire all active host channels on root-port disconnect without waiting for
+// Channel Halted interrupts.
+// stop new channel/FIFO interrupts, flush queued slave requests, request a
+// halt for enabled channels, then clear their interrupt and software state.
+static void channel_cleanup_on_disconnect(dwc2_regs_t *dwc2) {
+ const uint32_t xfer_ints = GINTSTS_NPTX_FIFO_EMPTY | GINTSTS_PTX_FIFO_EMPTY | GINTSTS_HCINT;
+ dwc2->gintmsk &= ~xfer_ints;
+ dwc2->gintsts = xfer_ints;
+ dwc2->haintmsk = 0;
+
+ const uint8_t max_channel = dwc2_channel_count(dwc2);
+ #if CFG_TUH_DWC2_SLAVE_ENABLE
+ if (!dma_host_enabled(dwc2)) {
+ // With CHENA clear, CHDIS flushes a posted request without consuming
+ // request-queue space. Clear EPDIR as required for this flush operation.
+ for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
+ if (_hcd_data.xfer[ch_id].allocated) {
+ dwc2_channel_t *channel = &dwc2->channel[ch_id];
+ const uint32_t hcchar = channel->hcchar;
+ if (hcchar & HCCHAR_CHENA) {
+ channel->hcchar = (hcchar & ~(HCCHAR_CHENA | HCCHAR_EPDIR)) | HCCHAR_CHDIS;
+ }
+ }
+ }
+ }
+ #endif
+
+ for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
+ if (_hcd_data.xfer[ch_id].allocated) {
+ dwc2_channel_t *channel = &dwc2->channel[ch_id];
+ const uint32_t hcchar = channel->hcchar;
+ if (hcchar & HCCHAR_CHENA) {
+ channel->hcchar = hcchar | HCCHAR_CHDIS;
+ }
+ channel->hcintmsk = 0;
+ channel->hcint = 0xFFFFFFFFU;
+ }
+ }
+
+ tu_memclr(_hcd_data.xfer, sizeof(_hcd_data.xfer));
+ for (uint8_t ep_id = 0; ep_id < CFG_TUH_DWC2_ENDPOINT_MAX; ep_id++) {
+ hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
+ if (edpt->hcchar_bm.enable) {
+ edpt->closing = 1;
+ edpt->xfer_pending = 0;
+ }
+ }
+}
+
// Enable a channel, selecting the following frame for a new periodic transfer.
// Return that frame from the same HFNUM sample used for ODDFRM selection.
// Clear CHDIS explicitly: a halted channel may retain it in HCCHAR.
@@ -296,11 +345,13 @@ static void edpt_close(dwc2_regs_t *dwc2, uint8_t ep_id) {
// Find an endpoint that is opened previously with hcd_edpt_open()
// Note: EP0 is bidirectional
-TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_find_opened(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) {
+TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_find_opened(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir,
+ bool include_closing) {
for (uint8_t i = 0; i < (uint8_t)CFG_TUH_DWC2_ENDPOINT_MAX; i++) {
const hcd_endpoint_t *edpt = &_hcd_data.edpt[i];
const dwc2_channel_char_t hcchar_bm = edpt->hcchar_bm;
- if (hcchar_bm.enable && hcchar_bm.dev_addr == dev_addr && hcchar_bm.ep_num == ep_num &&
+ if (hcchar_bm.enable && (include_closing || !edpt->closing) && hcchar_bm.dev_addr == dev_addr &&
+ hcchar_bm.ep_num == ep_num &&
(ep_num == 0 || hcchar_bm.ep_dir == ep_dir)) {
return i;
}
@@ -606,7 +657,7 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
dwc2_regs_t *dwc2 = DWC2_REG(rhport);
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- const uint8_t ep_id = edpt_find_opened(daddr, ep_num, ep_dir);
+ const uint8_t ep_id = edpt_find_opened(daddr, ep_num, ep_dir, true);
TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
edpt_close(dwc2, ep_id);
@@ -687,6 +738,7 @@ static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id, bool defer_peri
channel->hcsplt = edpt->hcsplt;
channel->hcint = 0xFFFFFFFFU; // clear all channel interrupts
+ dwc2->gintmsk |= GINTSTS_HCINT;
if (dma_host_enabled(dwc2)) {
channel->hcintmsk = HCINT_HALTED;
@@ -815,8 +867,8 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir);
- TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
+ uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false);
+ TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
TU_VERIFY(edpt->closing == 0); // skip if endpoint is closing
@@ -857,7 +909,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir);
+ const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false);
TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
@@ -887,8 +939,8 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked
bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
- uint8_t ep_id = edpt_find_opened(dev_addr, 0, TUSB_DIR_OUT);
- TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); // no opened endpoint
+ uint8_t ep_id = edpt_find_opened(dev_addr, 0, TUSB_DIR_OUT, false);
+ TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); // endpoint can close asynchronously on disconnect
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
edpt->next_pid = HCTSIZ_PID_SETUP;
@@ -900,7 +952,7 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
(void) rhport;
const uint8_t ep_num = tu_edpt_number(ep_addr);
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
- const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir);
+ const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false);
TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX);
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
@@ -990,6 +1042,13 @@ static void handle_rxflvl_irq(uint8_t rhport) {
// In packet received, pop this entry --> ACK interrupt
const uint16_t byte_count = grxstsp.byte_count;
hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id];
+ if (!xfer->allocated) {
+ // Discard data for a channel retired by disconnect.
+ for (uint16_t count = 0; count < byte_count; count += sizeof(uint32_t)) {
+ (void) dwc2->fifo[0][0];
+ }
+ break;
+ }
TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX,);
hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id];
@@ -1684,6 +1743,19 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
}
}
+ if (gintsts & GINTSTS_DISCINT) {
+ dwc2->gintsts = GINTSTS_DISCINT;
+ channel_cleanup_on_disconnect(dwc2);
+ hcd_event_device_remove(rhport, in_isr);
+
+ // A fast replug can be visible without a pending connect-detect interrupt.
+ const uint32_t hprt = dwc2->hprt;
+ if (!(hprt & HPRT_CONN_DETECT) && (hprt & HPRT_CONN_STATUS)) {
+ hcd_event_device_attach(rhport, in_isr);
+ }
+ return;
+ }
+
if (gintsts & GINTSTS_HPRTINT) {
// Host port interrupt: source is cleared in HPRT register
// TU_LOG1_HEX(dwc2->hprt);
@@ -1728,14 +1800,6 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) {
handle_channel_irq(rhport, in_isr);
}
- if (gintsts & GINTSTS_DISCINT) {
- // Device disconnected
- dwc2->gintsts = GINTSTS_DISCINT;
-
- if (0 == (dwc2->hprt & HPRT_CONN_STATUS)) {
- hcd_event_device_remove(rhport, in_isr);
- }
- }
}
#endif