summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex-Schaefer <[email protected]>2026-05-09 09:10:47 +0200
committerAlex-Schaefer <[email protected]>2026-05-09 09:10:47 +0200
commitcfdab2564fa5735f0a7d5653aa6eb5388456028e (patch)
tree8fb5be25b290b96f29e42ae242eb251dbd3aab8d
parent4c7fd70e53b34bf63ef9334f2b5624cac48299f1 (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.c21
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};