From cfdab2564fa5735f0a7d5653aa6eb5388456028e Mon Sep 17 00:00:00 2001 From: Alex-Schaefer <81265029+Alex-Schaefer@users.noreply.github.com> Date: Sat, 9 May 2026 09:10:47 +0200 Subject: 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. --- src/portable/synopsys/dwc2/dcd_dwc2.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'src') 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}; -- cgit v1.3.1 From 16bc0548dc0793404a5885aa90d78100b604c227 Mon Sep 17 00:00:00 2001 From: Alex-Schaefer <81265029+Alex-Schaefer@users.noreply.github.com> Date: Sat, 9 May 2026 11:14:00 +0200 Subject: dwc2: guard EP0 status completion with armed ZLP state Track when an EP0 OUT zero-length transfer is actually armed and require that state before synthesizing a status-stage completion ahead of a co-reported SETUP event. This preserves the validated status-before-SETUP ordering fix while avoiding stale zero-length state from producing spurious EP0 OUT completions. --- src/portable/synopsys/dwc2/dcd_dwc2.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 9b8f44df8..72beb1b80 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -62,6 +62,7 @@ static xfer_ctl_t xfer_status[DWC2_EP_MAX][2]; typedef struct { // EP0 transfers are limited to 1 packet - larger sizes has to be split uint16_t ep0_pending[2]; // Index determines direction as tusb_dir_t type + bool ep0_out_zlp_armed; // EP0 OUT ZLP transfer is armed and waiting for completion uint16_t dfifo_top; // top free location in DFIFO in words // Number of IN endpoints active @@ -665,6 +666,9 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to // EP0 can only handle one packet if (epnum == 0) { _dcd_data.ep0_pending[dir] = total_bytes; + if (dir == TUSB_DIR_OUT) { + _dcd_data.ep0_out_zlp_armed = (total_bytes == 0); + } } // Schedule packets to be sent within interrupt @@ -744,6 +748,9 @@ static void handle_bus_reset(uint8_t rhport) { tu_memclr(xfer_status, sizeof(xfer_status)); + _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; + _dcd_data.ep0_pending[TUSB_DIR_IN] = 0; + _dcd_data.ep0_out_zlp_armed = false; _dcd_data.sof_en = false; _dcd_data.allocated_epin_count = 0; @@ -949,12 +956,16 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe 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_out_zlp_armed && (_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_data.ep0_out_zlp_armed = false; dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); + } else if (epnum == 0) { + _dcd_data.ep0_out_zlp_armed = false; } // Cleanup previous pending EP0 IN transfer if any @@ -976,6 +987,9 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe // EP0 can only handle one packet, Schedule another packet to be received. edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); } else { + if (epnum == 0) { + _dcd_data.ep0_out_zlp_armed = false; + } dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } } @@ -1017,12 +1031,16 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi 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_out_zlp_armed && (_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_data.ep0_out_zlp_armed = false; dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); + } else if (epnum == 0) { + _dcd_data.ep0_out_zlp_armed = false; } // Cleanup previous pending EP0 IN transfer if any @@ -1058,6 +1076,9 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi dma_setup_prepare(rhport); } + if (epnum == 0) { + _dcd_data.ep0_out_zlp_armed = false; + } dcd_dcache_invalidate(xfer->buffer, xfer->total_len); dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } -- cgit v1.3.1 From 650e8a194fcfaa4b91203e9aedfecd614ea70105 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Wed, 20 May 2026 23:22:02 +0200 Subject: dwc2: handle EP0 status OUT in RXFLVL interrupt Signed-off-by: HiFiPhile --- src/portable/synopsys/dwc2/dcd_dwc2.c | 65 +++++++++++++---------------------- 1 file changed, 24 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 72beb1b80..bdab49f1b 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -62,7 +62,6 @@ static xfer_ctl_t xfer_status[DWC2_EP_MAX][2]; typedef struct { // EP0 transfers are limited to 1 packet - larger sizes has to be split uint16_t ep0_pending[2]; // Index determines direction as tusb_dir_t type - bool ep0_out_zlp_armed; // EP0 OUT ZLP transfer is armed and waiting for completion uint16_t dfifo_top; // top free location in DFIFO in words // Number of IN endpoints active @@ -70,6 +69,9 @@ typedef struct { // SOF enabling flag - required for SOF to not get disabled in ISR when SOF was enabled by bool sof_en; + + // EP0 status OUT flag + bool ep0_status_out; } dcd_data_t; static dcd_data_t _dcd_data; @@ -667,7 +669,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to if (epnum == 0) { _dcd_data.ep0_pending[dir] = total_bytes; if (dir == TUSB_DIR_OUT) { - _dcd_data.ep0_out_zlp_armed = (total_bytes == 0); + _dcd_data.ep0_status_out = (total_bytes == 0); } } @@ -748,11 +750,9 @@ static void handle_bus_reset(uint8_t rhport) { tu_memclr(xfer_status, sizeof(xfer_status)); - _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; - _dcd_data.ep0_pending[TUSB_DIR_IN] = 0; - _dcd_data.ep0_out_zlp_armed = false; _dcd_data.sof_en = false; _dcd_data.allocated_epin_count = 0; + _dcd_data.ep0_status_out = false; // 1. NAK for all OUT endpoints for (uint8_t n = 0; n < ep_count; n++) { @@ -907,6 +907,9 @@ static void handle_rxflvl_irq(uint8_t rhport) { // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); + + // Clear previous pending EP0 OUT if any + _dcd_data.ep0_status_out = false; break; } @@ -946,6 +949,12 @@ static void handle_rxflvl_irq(uint8_t rhport) { // Out packet done // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on // the specified OUT endpoint which will be handled by handle_epout_irq() + + // EP0 status OUT is complete + if (epnum == 0 && _dcd_data.ep0_status_out) { + _dcd_data.ep0_status_out = false; + dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); + } break; default: break; // nothing to do @@ -953,21 +962,7 @@ 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_out_zlp_armed && - (_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_data.ep0_out_zlp_armed = false; - dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); - } else if (epnum == 0) { - _dcd_data.ep0_out_zlp_armed = false; - } - // Cleanup previous pending EP0 IN transfer if any dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; if (edpt_is_enabled(epin0)) { @@ -983,13 +978,16 @@ 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) { - 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); - } else { - if (epnum == 0) { - _dcd_data.ep0_out_zlp_armed = false; + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + if (epnum == 0) { + if (_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); + } else if (xfer->total_len > 0) { + // EP0 status out is handled in handle_rxflvl_irq + dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } + } else { dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } } @@ -1028,21 +1026,8 @@ 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_out_zlp_armed && - (_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_data.ep0_out_zlp_armed = false; - dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); - } else if (epnum == 0) { - _dcd_data.ep0_out_zlp_armed = false; - } - // Cleanup previous pending EP0 IN transfer if any dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; if (edpt_is_enabled(epin0)) { @@ -1064,6 +1049,7 @@ 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}; @@ -1076,9 +1062,6 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi dma_setup_prepare(rhport); } - if (epnum == 0) { - _dcd_data.ep0_out_zlp_armed = false; - } dcd_dcache_invalidate(xfer->buffer, xfer->total_len); dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } -- cgit v1.3.1 From e520cff090e5da57e38ef3c7af250aab6d57a186 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Wed, 27 May 2026 00:19:42 +0200 Subject: dwc2: simplify EP0 ZLP handling Signed-off-by: HiFiPhile --- src/portable/synopsys/dwc2/dcd_dwc2.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 8997394dd..23fe671d4 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -69,9 +69,6 @@ typedef struct { // SOF enabling flag - required for SOF to not get disabled in ISR when SOF was enabled by bool sof_en; - - // EP0 status OUT flag - bool ep0_status_out; } dcd_data_t; static dcd_data_t _dcd_data; @@ -668,9 +665,6 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to // EP0 can only handle one packet if (epnum == 0) { _dcd_data.ep0_pending[dir] = total_bytes; - if (dir == TUSB_DIR_OUT) { - _dcd_data.ep0_status_out = (total_bytes == 0); - } } // Schedule packets to be sent within interrupt @@ -752,7 +746,6 @@ static void handle_bus_reset(uint8_t rhport) { _dcd_data.sof_en = false; _dcd_data.allocated_epin_count = 0; - _dcd_data.ep0_status_out = false; // 1. NAK for all OUT endpoints for (uint8_t n = 0; n < ep_count; n++) { @@ -907,9 +900,6 @@ static void handle_rxflvl_irq(uint8_t rhport) { // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); - - // Clear previous pending EP0 OUT if any - _dcd_data.ep0_status_out = false; break; } @@ -940,6 +930,10 @@ static void handle_rxflvl_irq(uint8_t rhport) { xfer->total_len -= tsiz.xfer_size; if (epnum == 0) { _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; + // Handle EP0 STATUS OUT (or ZLP) here to avoid mix with next SETUP packet received IRQ + if (xfer->total_len == 0) { + dcd_event_xfer_complete(rhport, 0, 0, XFER_RESULT_SUCCESS, true); + } } } break; @@ -949,12 +943,6 @@ static void handle_rxflvl_irq(uint8_t rhport) { // Out packet done // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on // the specified OUT endpoint which will be handled by handle_epout_irq() - - // EP0 status OUT is complete - if (epnum == 0 && _dcd_data.ep0_status_out) { - _dcd_data.ep0_status_out = false; - dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true); - } break; default: break; // nothing to do @@ -984,7 +972,7 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe // EP0 can only handle one packet, Schedule another packet to be received. edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); } else if (xfer->total_len > 0) { - // EP0 status out is handled in handle_rxflvl_irq + // EP0 STATUS OUT (or ZLP) is handled in handle_rxflvl_irq() dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } } else { -- cgit v1.3.1 From a560281051022473c844bdb258be452da52ce5c3 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Fri, 22 May 2026 13:42:08 +0200 Subject: dwc2: fix EP0 DMA setup race condition Signed-off-by: HiFiPhile --- src/portable/synopsys/dwc2/dcd_dwc2.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 23fe671d4..1c119eef4 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -73,8 +73,9 @@ typedef struct { static dcd_data_t _dcd_data; -CFG_TUD_MEM_SECTION static struct { - TUD_EPBUF_DEF(setup_packet, 8); +CFG_TUD_MEM_SECTION static union { + TUD_EPBUF_DEF(setup_buffer, 8); + tusb_control_request_t setup_packet; } _dcd_usbbuf; static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT; @@ -138,7 +139,7 @@ static void dma_setup_prepare(uint8_t rhport) { // Receive only 1 packet dwc2->epout[0].doeptsiz = (1 << DOEPTSIZ_STUPCNT_Pos) | (1 << DOEPTSIZ_PKTCNT_Pos) | (8 << DOEPTSIZ_XFRSIZ_Pos); - dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_packet; + dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_buffer; dwc2->epout[0].doepctl |= DOEPCTL_EPENA | DOEPCTL_USBAEP; } @@ -896,7 +897,7 @@ static void handle_rxflvl_irq(uint8_t rhport) { case GRXSTS_PKTSTS_SETUP_RX: { // Setup packet received - uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_packet; + uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer; // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); @@ -956,7 +957,7 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe if (edpt_is_enabled(epin0)) { edpt_disable(rhport, 0x80, false); } - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); return; } @@ -1021,9 +1022,13 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi if (edpt_is_enabled(epin0)) { edpt_disable(rhport, 0x80, false); } - dma_setup_prepare(rhport); - dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8); - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, 8); + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + + // Prepare EP0 for next setup if this setup has no data stage + if (_dcd_usbbuf.setup_packet.wLength == 0) { + dma_setup_prepare(rhport); + } return; } @@ -1044,9 +1049,8 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi const uint16_t remain = tsiz.xfer_size; xfer->total_len -= remain; - // this is ZLP, so prepare EP0 for next setup - // TODO use status phase rx - if(epnum == 0 && xfer->total_len == 0) { + // prepare EP0 for next setup + if(epnum == 0) { dma_setup_prepare(rhport); } @@ -1065,9 +1069,6 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin // EP0 can only handle one packet. Schedule another packet to be transmitted. edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); } else { - if(epnum == 0) { - dma_setup_prepare(rhport); - } dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); } } -- cgit v1.3.1 From dafdc5c54f34f88c963f7efc0062d0e992074b8f Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Fri, 29 May 2026 00:05:44 +0200 Subject: dwc2: move OUT transfer management into RXFLVL IRQ - GRXSTSP register has internal FIFO, receiving events won't mix up (STATUS OUT & next SETUP) - Improve efficiency, remove 2nd IRQ overhead Signed-off-by: HiFiPhile --- src/portable/synopsys/dwc2/dcd_dwc2.c | 71 ++++++++++++++--------------------- 1 file changed, 28 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 1c119eef4..233840e8b 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -794,13 +794,15 @@ static void handle_bus_reset(uint8_t rhport) { xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + uint32_t oepmsk = 0; if(dma_device_enabled(dwc2)) { + oepmsk = GINTMSK_OEPINT; dma_setup_prepare(rhport); } else { dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); } - dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; + dwc2->gintmsk |= GINTMSK_OTGINT | oepmsk | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; } static void handle_enum_done(uint8_t rhport) { @@ -901,13 +903,27 @@ static void handle_rxflvl_irq(uint8_t rhport) { // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); + + dwc2_dep_t* epin0 = &dwc2->epin[0]; + if (edpt_is_enabled(epin0)) { + edpt_disable(rhport, 0x80, false); + } + + // (GenID < 3.00a) Must wait SETUP_DONE before next OUT transfer, otherwise OUT data may be corrupted. + // (GenID >= 3.00a) On the other hand STUPCNT is auto reloaded and SETUP_DONE is only triggered once after bus reset. + if (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a) { + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + } break; } case GRXSTS_PKTSTS_SETUP_DONE: // Setup packet done: - // After popping this out, dwc2 asserts a DOEPINT_SETUP interrupt which is handled by handle_epout_irq() epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); + + if (dwc2->gsnpsid < DWC2_CORE_REV_3_00a) { + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + } break; case GRXSTS_PKTSTS_RX_DATA: { @@ -931,55 +947,24 @@ static void handle_rxflvl_irq(uint8_t rhport) { xfer->total_len -= tsiz.xfer_size; if (epnum == 0) { _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; - // Handle EP0 STATUS OUT (or ZLP) here to avoid mix with next SETUP packet received IRQ - if (xfer->total_len == 0) { - dcd_event_xfer_complete(rhport, 0, 0, XFER_RESULT_SUCCESS, true); - } } } break; } - case GRXSTS_PKTSTS_RX_COMPLETE: + case GRXSTS_PKTSTS_RX_COMPLETE: { // Out packet done - // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on - // the specified OUT endpoint which will be handled by handle_epout_irq() - break; - - default: break; // nothing to do - } -} - -static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { - if (doepint_bm.setup_phase_done) { - // Cleanup previous pending EP0 IN transfer if any - dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; - if (edpt_is_enabled(epin0)) { - edpt_disable(rhport, 0x80, false); - } - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); - return; - } - - // Normal OUT transfer complete - if (doepint_bm.xfer_complete) { - // only handle data skip if it is setup or status related - // 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) { - if (_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); - } else if (xfer->total_len > 0) { - // EP0 STATUS OUT (or ZLP) is handled in handle_rxflvl_irq() - dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); - } + if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) { + // EP0 can only handle one packet, schedule another packet to be received. + edpt_schedule_packets(rhport, 0, TUSB_DIR_OUT); } else { dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } + break; } + + default: break; // nothing to do } } @@ -1108,8 +1093,6 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) { #if CFG_TUD_DWC2_SLAVE_ENABLE if (dir == TUSB_DIR_IN) { handle_epin_slave(rhport, epnum, intr.diepint_bm); - } else { - handle_epout_slave(rhport, epnum, intr.doepint_bm); } #endif } @@ -1217,7 +1200,7 @@ void dcd_int_handler(uint8_t rhport) { dwc2->gotgint = otg_int; } - if(gintsts & GINTSTS_SOF) { + if(gintsts & GINTSTS_SOF && dwc2->gintmsk & GINTMSK_SOFM) { dwc2->gintsts = GINTSTS_SOF; dwc2->gintmsk |= GINTMSK_USBSUSPM; const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos; @@ -1244,11 +1227,13 @@ void dcd_int_handler(uint8_t rhport) { } #endif +#if CFG_TUD_DWC2_DMA_ENABLE // OUT endpoint interrupt handling. if (gintsts & GINTSTS_OEPINT) { // OEPINT is read-only, clear using DOEPINTn handle_ep_irq(rhport, TUSB_DIR_OUT); } +#endif // IN endpoint interrupt handling. if (gintsts & GINTSTS_IEPINT) { -- cgit v1.3.1 From b4e7c25c1b57a69e4c04e41d0f2203abf0d8d358 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Fri, 29 May 2026 11:42:47 +0200 Subject: dwc2: process IN EP before OUT To avoid STATUS IN completion of previous control transfer treated as next DATA IN when IRQ latency is high. Signed-off-by: HiFiPhile --- src/portable/synopsys/dwc2/dcd_dwc2.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 233840e8b..c90429a15 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -1213,6 +1213,12 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_sof(rhport, frame, true); } + // IN endpoint interrupt handling. + if (gintsts & GINTSTS_IEPINT) { + // IEPINT bit read-only, clear using DIEPINTn + handle_ep_irq(rhport, TUSB_DIR_IN); + } + #if CFG_TUD_DWC2_SLAVE_ENABLE // RxFIFO non-empty interrupt handling. if (gintsts & GINTSTS_RXFLVL) { @@ -1235,12 +1241,6 @@ void dcd_int_handler(uint8_t rhport) { } #endif - // IN endpoint interrupt handling. - if (gintsts & GINTSTS_IEPINT) { - // IEPINT bit read-only, clear using DIEPINTn - handle_ep_irq(rhport, TUSB_DIR_IN); - } - // Incomplete isochronous IN transfer interrupt handling. if (gintsts & GINTSTS_IISOIXFR) { dwc2->gintsts = GINTSTS_IISOIXFR; -- cgit v1.3.1