diff options
| author | hathach <[email protected]> | 2026-07-09 23:38:31 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-07-09 23:38:31 +0700 |
| commit | ad7acc849ab36c1bc2e560fcfac89bd136ec96b3 (patch) | |
| tree | 1749913c499d2e7f764a3e0fb9859bc259d686ab | |
| parent | 4bbb23545a91926b9372554f5a31294d0a279829 (diff) | |
dcd(rp2040): re-issue in-flight transfer on clear-halt toggle reset
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
| -rw-r--r-- | src/portable/raspberrypi/rp2040/dcd_rp2040.c | 43 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.c | 9 |
2 files changed, 47 insertions, 5 deletions
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 63097cd0a..a0d312b8f 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -550,9 +550,46 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { if (epnum != 0) { struct hw_endpoint* ep = hw_endpoint_get(epnum, dir); - ep->next_pid = 0; // reset data toggle - io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); - *buf_reg = 0; + + if (ep->state == EPSTATE_ACTIVE) { + // Clear-halt on an endpoint with an in-flight transfer is used as a data-toggle reset + // (e.g. usbtest case 29) rather than to recover from a real stall (a stall aborts the + // transfer, leaving the endpoint IDLE). Abort and re-issue the transfer with the toggle + // reset to DATA0 so it still completes and releases the usbd claim, instead of silently + // dropping it and starving the endpoint. Save the buffer/length before the abort clears them. + uint8_t* user_buf = ep->user_buf; + uint16_t remaining = ep->remaining_len; + const uint16_t xferred = ep->xferred_len; // bytes already moved on this submission + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + // bufctrl_prepare16() subtracts each armed buffer's length from remaining_len when arming, + // for BOTH directions, before the host has drained (IN) or filled (OUT) it. The abort below + // discards those still-armed buffers, so rewind remaining_len by their lengths or the re-issue + // is short by 1-2 packets. IN additionally advances user_buf as packets are copied into DPRAM, + // so its pointer must rewind too; OUT copies out only on completion, so its pointer is intact. + const uint32_t bc = *buf_reg; + uint16_t staged = 0; + if (bc & USB_BUF_CTRL_AVAIL) { + staged = (uint16_t)(bc & USB_BUF_CTRL_LEN_MASK); + } + if ((bc >> 16) & USB_BUF_CTRL_AVAIL) { + staged = (uint16_t)(staged + ((bc >> 16) & USB_BUF_CTRL_LEN_MASK)); + } + remaining = (uint16_t)(remaining + staged); + if (dir == TUSB_DIR_IN) { + user_buf -= staged; + } + hw_endpoint_abort_xfer(ep); // safe abort (handles RP2040-E2), resets ep transfer state + ep->next_pid = 0; // DATA0 + rp2usb_xfer_start(ep, ep_reg, buf_reg, user_buf, NULL, remaining); + // rp2usb_xfer_start() zeroes xferred_len; add back what the aborted transfer already moved so + // the eventual completion reports the full length, not just the post-clear-halt remainder. + ep->xferred_len += xferred; + } else { + ep->next_pid = 0; // reset data toggle + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; // clear the stall response + } } } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index e4eb0184e..5421b9b2b 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -176,10 +176,15 @@ void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_r // Note: device EP0 does not have an endpoint control register if (ep_reg != NULL) { uint32_t ep_ctrl = *ep_reg; + // Isochronous endpoints get a single DPRAM buffer (hw_endpoint_open only double-sizes BULK), so + // they must never be double-buffered here even when a transfer spans multiple packets, or buffer + // 1 (at dpram_buf+64) would spill into the next endpoint's DPRAM. (Never true for BULK, so the + // double-buffered bulk path is unaffected.) + const bool is_iso = (((ep_ctrl >> EP_CTRL_BUFFER_TYPE_LSB) & 0x3u) == TUSB_XFER_ISOCHRONOUS); #if CFG_TUH_ENABLED - const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0); + const bool force_single = is_iso || (rp2usb_is_host_mode() && ep->interrupt_num > 0); #else - const bool force_single = false; + const bool force_single = is_iso; #endif if (ep->remaining_len && !force_single) { |
