From 5a3cc447443b259d61745ce900492b7909859828 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 17 Aug 2026 02:07:58 +0700 Subject: [PATCH] wch: recover the CH569 link when the deferred re-init is dropped usb30_bus_reset_from_isr() deinits the controller and then latches _hw_reinit_deferred only if usbd_defer_func() actually queued the work. When the usbd event queue is full the deferral is dropped - and nothing else can ever run it, because usb30_hw_deinit() has already zeroed LINK_INT_CTRL, so the dispatch test in dcd_int_handler is permanently false and no LINK interrupt can arrive to retry. Without the fallback ladder (the default) there is no timer either, and with it FB_USB3_UP has already stopped TMR0. The port stayed off the bus until the next dcd_init. The partner-gone caller queues DCD_EVENT_UNPLUGGED one line earlier, so a single free slot is enough to lose the re-init. Fall back to settling inline when the deferral fails: ~30 ms in the ISR only on a full queue, versus losing the device. --- src/portable/wch/dcd_ch56x_usb30.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/portable/wch/dcd_ch56x_usb30.c b/src/portable/wch/dcd_ch56x_usb30.c index fe7df10b1..a2d3be981 100644 --- a/src/portable/wch/dcd_ch56x_usb30.c +++ b/src/portable/wch/dcd_ch56x_usb30.c @@ -250,6 +250,17 @@ static void usb30_bus_reset_from_isr(void) { // queue is full, and a latch set for a call that was dropped would block every later re-init // (and make the fallback tick skip its own recovery) for good. _hw_reinit_deferred = usbd_defer_func(usb30_hw_reinit_task, NULL, true); + if (!_hw_reinit_deferred) { + // The deferral was dropped, and nothing else will ever run it: usb30_hw_deinit() above + // zeroed LINK_INT_CTRL, so the dispatch test in dcd_int_handler can never be true again and + // no LINK interrupt can arrive to retry. Without the fallback ladder (the default) there is + // no timer either, and with it FB_USB3_UP has already stopped TMR0 - so the port would stay + // off the bus until the next dcd_init. Pay the settle inline instead: ~30 ms in the ISR is + // far cheaper than losing the device, and this only happens when the event queue is full. + link_delay_us(30000); + USBSS->LINK_INT_FLAG = 0xFFFFFFFFu; + usb30_hw_init(); + } } // Reset endpoint/transfer bookkeeping when the link (re)trains