Skip to content

Commit cfdab25

Browse files
committed
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.
1 parent 4c7fd70 commit cfdab25

1 file changed

Lines changed: 19 additions & 2 deletions

File tree

src/portable/synopsys/dwc2/dcd_dwc2.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,17 @@ static void handle_rxflvl_irq(uint8_t rhport) {
946946
}
947947

948948
static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) {
949+
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
950+
const bool ep0_status_complete_before_setup = (epnum == 0) && doepint_bm.setup_phase_done &&
951+
doepint_bm.xfer_complete &&
952+
(_dcd_data.ep0_pending[TUSB_DIR_OUT] == 0) &&
953+
(xfer->total_len == 0);
954+
949955
if (doepint_bm.setup_phase_done) {
956+
if (ep0_status_complete_before_setup) {
957+
dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true);
958+
}
959+
950960
// Cleanup previous pending EP0 IN transfer if any
951961
dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0];
952962
if (edpt_is_enabled(epin0)) {
@@ -962,7 +972,6 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe
962972
// Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they
963973
// can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done
964974
if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) {
965-
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
966975
if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) {
967976
// EP0 can only handle one packet, Schedule another packet to be received.
968977
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
10051014
#if CFG_TUD_DWC2_DMA_ENABLE
10061015
static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) {
10071016
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
1017+
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
1018+
const bool ep0_status_complete_before_setup = (epnum == 0) && doepint_bm.setup_phase_done &&
1019+
doepint_bm.xfer_complete &&
1020+
(_dcd_data.ep0_pending[TUSB_DIR_OUT] == 0) &&
1021+
(xfer->total_len == 0);
10081022

10091023
if (doepint_bm.setup_phase_done) {
1024+
if (ep0_status_complete_before_setup) {
1025+
dcd_event_xfer_complete(rhport, epnum, 0, XFER_RESULT_SUCCESS, true);
1026+
}
1027+
10101028
// Cleanup previous pending EP0 IN transfer if any
10111029
dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0];
10121030
if (edpt_is_enabled(epin0)) {
@@ -1028,7 +1046,6 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi
10281046
edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT);
10291047
} else {
10301048
dwc2_dep_t* epout = &dwc2->epout[epnum];
1031-
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
10321049

10331050
// determine actual received bytes
10341051
const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz};

0 commit comments

Comments
 (0)