diff options
| author | Alex-Schaefer <[email protected]> | 2026-05-09 09:10:47 +0200 |
|---|---|---|
| committer | Alex-Schaefer <[email protected]> | 2026-05-09 09:10:47 +0200 |
| commit | cfdab2564fa5735f0a7d5653aa6eb5388456028e (patch) | |
| tree | 8fb5be25b290b96f29e42ae242eb251dbd3aab8d | |
| parent | 4c7fd70e53b34bf63ef9334f2b5624cac48299f1 (diff) | |
dwc2: preserve EP0 status completion before SETUP
On STM32 DWC2, SETUP phase done and EP0 OUT transfer complete can be
reported together. Processing SETUP first can overwrite control state
before the previous zero-length OUT status stage is acknowledged, which
causes DFU DNLOAD/GETSTATUS traffic to lose the status ACK and stall.
Queue the EP0 OUT zero-length transfer completion before queuing the
SETUP event when the endpoint has no pending OUT data and total_len is
zero. This keeps TinyUSB control-transfer ordering intact for the
combined interrupt case.
| -rw-r--r-- | src/portable/synopsys/dwc2/dcd_dwc2.c | 21 |
1 files changed, 19 insertions, 2 deletions
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 30e24a9ad..9b8f44df8 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -946,7 +946,17 @@ static void handle_rxflvl_irq(uint8_t rhport) { } static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + const bool ep0_status_complete_before_setup = (epnum == 0) && doepint_bm.setup_phase_done && + doepint_bm.xfer_complete && + (_dcd_data.ep0_pending[TUSB_DIR_OUT] == 0) && + (xfer->total_len == 0); + if (doepint_bm.setup_phase_done) { + if (ep0_status_complete_before_setup) { + dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); + } + // Cleanup previous pending EP0 IN transfer if any dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; if (edpt_is_enabled(epin0)) { @@ -962,7 +972,6 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe // Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they // can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) { - xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) { // EP0 can only handle one packet, Schedule another packet to be received. edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); @@ -1005,8 +1014,17 @@ static void handle_epin_slave(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diep #if CFG_TUD_DWC2_DMA_ENABLE static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + const bool ep0_status_complete_before_setup = (epnum == 0) && doepint_bm.setup_phase_done && + doepint_bm.xfer_complete && + (_dcd_data.ep0_pending[TUSB_DIR_OUT] == 0) && + (xfer->total_len == 0); if (doepint_bm.setup_phase_done) { + if (ep0_status_complete_before_setup) { + dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); + } + // Cleanup previous pending EP0 IN transfer if any dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; if (edpt_is_enabled(epin0)) { @@ -1028,7 +1046,6 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); } else { dwc2_dep_t* epout = &dwc2->epout[epnum]; - xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); // determine actual received bytes const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz}; |
