From e47eabd49d1838dc2a4eb42b9bd167a1d24d7cf5 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 13 Jun 2026 00:22:28 +0700 Subject: dcd/musb: replay deferred SETUP instead of stalling EP0 dcd_edpt_stall(EP0 OUT) discarded the deferred SETUP and armed SendStall. A deferred SETUP can only exist once the old transfer's status stage was seen on the wire, so the request usbd is rejecting (class callback failing at CONTROL_STAGE_DATA) already succeeded host-side and the hardware already ACKed the next SETUP - the STALL would land on that innocent request, which then fails host-side without any tud callback ever seeing it. Skip the stall and replay the deferred SETUP; the rejected transfer needs no wire-level stall since it is already over. Review follow-up for #3643 (dcd_musb.c l.860 finding). Co-Authored-By: Claude Fable 5 --- src/portable/mentor/musb/dcd_musb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 0485c4374..15928ec24 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -534,7 +534,7 @@ static void process_ep0(uint8_t rhport) { // - Status IN/OUT finished, its IRQ and the new SETUP IRQ arrive at the same time. // - Data IN finished and status OUT is received, both IRQs and the new SETUP IRQ arrive at the same time. // Save the SETUP; it is replayed only once the old transfer is fully retired — i.e. when usbd has - // made (or already made) its final edpt0_xfer() call for it. Until the replayed + // made (or already made) its final edpt0_xfer()/dcd_edpt_stall() call for it. Until the replayed // packet is acked, its RXRDY stays parked so a stale latched EP0 IRQ cannot re-process it. case PIPE0_STATE_DATA_IN: case PIPE0_STATE_STATUS_OUT: @@ -935,11 +935,17 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn); if (0 == epn) { - if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 OUT */ + if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 IN */ _dcd.pipe0.state = PIPE0_STATE_IDLE; _dcd.pipe0.buf = NULL; - _dcd.pipe0.deferred_setup_valid = false; - ep_csr->csr0l = MUSB_CSRL0_STALL; + if (_dcd.pipe0.deferred_setup_valid) { + // The transfer being stalled already completed on the wire (a deferred SETUP can only exist + // once its status stage was seen) and the host's next request was already ACKed — SendStall + // would land on that innocent request. Skip the stall and replay the deferred SETUP instead. + pipe0_process_deferred_setup(rhport, ep_csr, false); + } else { + ep_csr->csr0l = MUSB_CSRL0_STALL; + } } } else { const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr); -- cgit v1.3.1