diff options
| author | hathach <[email protected]> | 2026-03-26 19:33:08 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-03-26 19:43:44 +0700 |
| commit | 94f48272c79310c5ab47c79c7b57a1be7bdee901 (patch) | |
| tree | 0baf2d516401a4feb0904c21295e66285cb2903a | |
| parent | e81faa22af6369745e4aa0420289a335d2eaa047 (diff) | |
implement ping-pong double buffered for both tx and rx
| -rw-r--r-- | src/portable/raspberrypi/rp2040/dcd_rp2040.c | 77 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/hcd_rp2040.c | 13 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.c | 223 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.h | 65 | ||||
| -rwxr-xr-x | test/hil/hil_test.py | 2 |
5 files changed, 267 insertions, 113 deletions
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 65871df05..d9c30efba 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -92,12 +92,13 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa // Clear existing buffer control state const uint8_t epnum = tu_edpt_number(ep_addr); const tusb_dir_t dir = tu_edpt_dir(ep_addr); - io_rw_32 *buf_ctrl_reg = get_buf_ctrl(epnum, dir); - *buf_ctrl_reg = 0; + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + + *buf_reg = 0; // allocated hw buffer if (epnum == 0) { - // Buffer offset is fixed (also double buffered) + // Buffer offset is fixed (also double buffered) TODO EP0 double buffer ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0]; } else { // round up size to multiple of 64 @@ -107,7 +108,7 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa if (transfer_type == TUSB_XFER_BULK) { size *= 2u; - #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + #if CFG_TUSB_RP2_ERRATA_E15 if (dir == TUSB_DIR_IN) { ep->e15_bulk_in = true; } @@ -127,8 +128,8 @@ static void hw_endpoint_enable(uint8_t epnum, tusb_dir_t dir, uint8_t transfer_t io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); // Set endpoint control register to enable (EP0 has no endpoint control register) if (ep_reg != NULL) { - const uint32_t ctrl_value = - EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(dpram_buf); + const uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(dpram_buf); *ep_reg = ctrl_value; } } @@ -179,32 +180,25 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { const uint8_t i = (uint8_t) __builtin_ctz(buf_status); const uint bit = TU_BIT(i); - // Read which buffer to handle BEFORE clearing buf_status - uint8_t buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; - usb_hw_clear->buf_status = bit; - buf_status &= ~bit; - // IN transfer for even i, OUT transfer for odd i - const uint8_t epnum = i >> 1u; - const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN; - hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); - - io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); - io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); - bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, buf_id); + const uint8_t epnum = i >> 1u; + const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN; + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); // Double-buffered: if both buffers completed at once, buf_status re-sets // immediately after clearing (datasheet Table 406). Process the second buffer too. - if (!done && (usb_hw->buf_status & bit)) { - buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; + while (usb_hw->buf_status & bit) { + const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; // before clear buf_status usb_hw_clear->buf_status = bit; - done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, buf_id); - } + buf_status &= ~bit; - if (done) { - const uint16_t xferred_len = ep->xferred_len; - hw_endpoint_reset_transfer(ep); - dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); + if (hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, buf_id)) { + const uint16_t xferred_len = ep->xferred_len; + hw_endpoint_reset_transfer(ep); + dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); + } } } } @@ -244,7 +238,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { handled |= USB_INTF_DEV_SOF_BITS; -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +#if CFG_TUSB_RP2_ERRATA_E15 // Errata 15 workaround for Device Bulk-In endpoint e15_last_sof = time_us_32(); @@ -260,7 +254,32 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { ep->pending = 0; io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN); io_rw_32 *buf_reg = get_buf_ctrl(i, TUSB_DIR_IN); - hw_endpoint_buffer_xact(ep, ep_reg, buf_reg); + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; + + // Check each buffer half: idle when both FULL and AVAIL are clear. + // Use 16-bit writes to avoid clobbering the other half (DPSRAM concurrent access). + const uint16_t busy_mask = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL; + const bool do_buf0 = !(buf_reg16[0] & busy_mask); + const bool do_buf1 = ep->remaining_len > 0 && !(buf_reg16[1] & busy_mask); + + // Set ep_ctrl BEFORE buf_ctrl (controller reads ep_ctrl to determine double-buffered mode) + if (ep_reg != NULL) { + if (do_buf1) { + *ep_reg |= EP_CTRL_DOUBLE_BUFFERED_BITS; + } else { + *ep_reg &= ~EP_CTRL_DOUBLE_BUFFERED_BITS; + } + } + + if (do_buf0) { + uint16_t buf0 = bufctrl_prepare(ep, ep->dpram_buf, false); + buf0 |= USB_BUF_CTRL_SEL; // reset buffer selector to buf0 + bufctrl_write16(buf_reg16, buf0); + } + if (do_buf1) { + uint16_t buf1 = bufctrl_prepare(ep, ep->dpram_buf + 64, false); + bufctrl_write16(buf_reg16 + 1, buf1); + } } hw_endpoint_lock_update(ep, -1); } @@ -464,7 +483,7 @@ void dcd_sof_enable(uint8_t rhport, bool en) { if (en) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; } -#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + #if !CFG_TUSB_RP2_ERRATA_E15 else { // Don't clear immediately if the SOF workaround is in use. // The SOF handler will conditionally disable the interrupt. diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 4ebdf8284..3f4c95422 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -351,11 +351,11 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_des // const uint8_t bmInterval = ep_desc->bInterval; ep->max_packet_size = wMaxPacketSize; - ep->ep_addr = ep_addr; - ep->dev_addr = dev_addr; - ep->transfer_type = transfer_type; - ep->need_pre = need_pre(dev_addr); - ep->next_pid = 0u; + ep->ep_addr = ep_addr; + ep->dev_addr = dev_addr; + ep->transfer_type = transfer_type; + ep->need_pre = need_pre(dev_addr); + ep->next_pid = 0u; if (transfer_type != TUSB_XFER_INTERRUPT) { ep->dpram_buf = usbh_dpram->epx_data; @@ -572,8 +572,7 @@ static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_ // ep control const uint32_t dpram_offset = hw_data_offset(ep->dpram_buf); const uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset /*| - (1u << 16)*/; // INTERRUPT_ON_NAK + ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; usbh_dpram->epx_ctrl = ep_ctrl; io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 1d21952a8..d0a229785 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -27,18 +27,23 @@ #include "tusb_option.h" -#if CFG_TUSB_MCU == OPT_MCU_RP2040 +#if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED) -#include <stdlib.h> -#include "rp2040_usb.h" + #include <stdlib.h> + #include "rp2040_usb.h" + + #include "device/dcd.h" + #include "host/hcd.h" //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ - #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + #if CFG_TUSB_RP2_ERRATA_E15 static bool e15_is_critical_frame_period(struct hw_endpoint *ep); - #else - #define e15_is_critical_frame_period(x) (false) + #endif + + #if CFG_TUSB_RP2_ERRATA_E2 +static uint8_t rp2040_chipversion = 2; #endif //--------------------------------------------------------------------+ @@ -84,6 +89,10 @@ void rp2usb_init(void) { // Mux the controller to the onboard usb phy usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; + #if CFG_TUSB_RP2_ERRATA_E2 + rp2040_chipversion = rp2040_chip_version(); + #endif + TU_LOG2_INT(sizeof(hw_endpoint_t)); } @@ -134,12 +143,46 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an *buf_ctrl_reg = value; } +void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) { + const uint32_t current = *buf_reg; + const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16); + if (current & value & avail_mask) { + panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg); + } + *buf_reg = value & ~USB_BUF_CTRL_AVAIL; // write other bits first + + // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, + // wait for USB controller to see the update before setting AVAILABLE. + // Don't need delay in host mode as host is in charge of when to start the transaction. + if (!rp2usb_is_host_mode() && (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)))) { + busy_wait_at_least_cycles(12); + } + + *buf_reg = value; // then set AVAILABLE bit (if set) last +} + +void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) { + const uint16_t current = *buf_reg16; + if (current & value & USB_BUF_CTRL_AVAIL) { + panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16); + } + *buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first + + // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, + // wait for USB controller to see the update before setting AVAILABLE. + // Don't need delay in host mode as host is in charge of when to start the transaction. + if (!rp2usb_is_host_mode() && (value & USB_BUF_CTRL_AVAIL)) { + busy_wait_at_least_cycles(12); + } + *buf_reg16 = value; // then set AVAILABLE bit (if set) last +} + // prepare buffer, move data if tx, return buffer control -static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t *dpram_buf, bool is_rx) { +uint16_t __tusb_irq_path_func(bufctrl_prepare)(struct hw_endpoint *ep, uint8_t *dpram_buf, bool is_rx) { const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size); ep->remaining_len -= buflen; - uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; + uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; if (ep->next_pid) { buf_ctrl |= USB_BUF_CTRL_DATA1_PID; } @@ -174,8 +217,8 @@ static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint } // Start transaction on hw buffer -void __tusb_irq_path_func(hw_endpoint_buffer_xact)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { - const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); +void __tusb_irq_path_func(hw_endpoint_buffer_start)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { + const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); const bool is_host = rp2usb_is_host_mode(); bool is_rx; @@ -185,29 +228,26 @@ void __tusb_irq_path_func(hw_endpoint_buffer_xact)(struct hw_endpoint *ep, io_rw is_rx = (dir == TUSB_DIR_OUT); } - // In case short packet on buf0 in double-buffered RX, buf1 may already contain data from the - // NEXT transfer (host sent it before CPU processed this IRQ). Cannot safely recover. Avoid by not using double - // buffering for rx transfer - // RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to // BUF1 half instead of BUF0. The side effect that controller can execute an extra packet after writing to BUF1 // since it leave BUF0 intact, which can be polled before buf_status interrupt is trigger. // Workaround for the side effect, we will enable double-buffered for rx but only prepare 1 buf at a time. - #if CFG_TUSB_RP2040_ERRATA_E4_FIX + #if CFG_TUSB_RP2_ERRATA_E4 #endif // always compute and start with buffer 0 - uint32_t buf_ctrl = hwbuf_prepare(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; + uint32_t buf_ctrl = bufctrl_prepare(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; // Device mode EP0 has no endpoint control register if (ep_reg != NULL) { // Each buffer completion triggers its own IRQ. // If both complete simultaneously, buf_status re-sets on next clock (datasheet Table 406). - uint32_t ep_ctrl = *ep_reg | EP_CTRL_INTERRUPT_PER_BUFFER; -#if 1 - const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0); -#else + uint32_t ep_ctrl = *ep_reg; + #if 1 + const bool force_single = // (!is_host && is_rx) || + (is_host && tu_edpt_number(ep->ep_addr) != 0); + #else bool force_single = false; // is_rx; #if CFG_TUH_ENABLED if (is_host && ep->interrupt_num != 0) { @@ -218,7 +258,7 @@ void __tusb_irq_path_func(hw_endpoint_buffer_xact)(struct hw_endpoint *ep, io_rw if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data - buf_ctrl |= hwbuf_prepare(ep, ep->dpram_buf+64, is_rx) << 16; + buf_ctrl |= (uint32_t)bufctrl_prepare(ep, ep->dpram_buf + 64, is_rx) << 16; ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; } else { // Single buffered since 1 is enough @@ -228,10 +268,8 @@ void __tusb_irq_path_func(hw_endpoint_buffer_xact)(struct hw_endpoint *ep, io_rw *ep_reg = ep_ctrl; } - // TU_LOG(1, "xact: buf_ctrl = 0x%08lx\r\n", buf_ctrl); - // Finally, write to buffer_control which will trigger the transfer the next time the controller polls this endpoint - hwbuf_ctrl_set(buf_reg, buf_ctrl); + bufctrl_write32(buf_reg, buf_ctrl); } void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, @@ -261,7 +299,41 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * ep->is_xfer_fifo = false; } - #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + if (ep->future_len > 0) { + // only on rx endpoint + const uint8_t future_len = ep->future_len; + memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len); + ep->xferred_len += future_len; + ep->remaining_len -= future_len; + ep->user_buf += future_len; + + ep->future_len = 0; + ep->future_bufid = 0; + + if (ep->remaining_len == 0) { + // all data has been received, no need to start hw transfer + ep->active = false; + const uint16_t xferred_len = ep->xferred_len; + hw_endpoint_reset_transfer(ep); + + const bool is_host = rp2usb_is_host_mode(); + #if CFG_TUH_ENABLED + if (is_host) { + hcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); + } + #endif + #if CFG_TUD_ENABLED + if (!is_host) { + dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); + } + #endif + + hw_endpoint_lock_update(ep, -1); + return; + } + } + + #if CFG_TUSB_RP2_ERRATA_E15 if (ep->e15_bulk_in) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; } @@ -271,14 +343,14 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * } else #endif { - hw_endpoint_buffer_xact(ep, ep_reg, buf_reg); + hw_endpoint_buffer_start(ep, ep_reg, buf_reg); } hw_endpoint_lock_update(ep, -1); } // sync endpoint buffer and return transferred bytes -static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, bool is_rx, uint32_t buf_ctrl, uint8_t *dpram_buf) { +static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl, uint8_t *dpram_buf) { const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; if (!is_rx) { @@ -316,16 +388,23 @@ static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, bool is_rx, bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id) { hw_endpoint_lock_update(ep, 1); + const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); + const bool is_host = rp2usb_is_host_mode(); + const bool is_rx = is_host ? (dir == TUSB_DIR_IN) : (dir == TUSB_DIR_OUT); + + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; + uint16_t buf_ctrl16 = *(buf_reg16 + buf_id); + if (!ep->active) { - panic("Can't continue xfer on inactive ep %02X", ep->ep_addr); + // probably land here due to short packet on rx with double buffered + hw_endpoint_lock_update(ep, -1); + return false; } - const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); - const bool is_host = rp2usb_is_host_mode(); - const bool is_rx = is_host ? (dir == TUSB_DIR_IN) : (dir == TUSB_DIR_OUT); - const bool is_double = ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS); + const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); + (void)is_double; - #if CFG_TUSB_RP2040_ERRATA_E4_FIX + #if CFG_TUSB_RP2_ERRATA_E4 const bool need_e4_fix = (is_host && !is_double); #endif @@ -335,40 +414,73 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ // BUF1 half instead of BUF0. The side effect that controller can execute an extra packet after writing to BUF1 // since it leave BUF0 intact, which can be poll before buf_status interrupt is trigger. // Workaround for the side effect, we will enable double-buffered for rx but only prepare 1 buf at a time. - uint32_t buf_ctrl = *buf_reg; - // TU_LOG(1, "sync: buf_ctrl = 0x%08lx, buf id = %u\r\n", buf_ctrl, buf_id); - uint8_t* dpram_buf = ep->dpram_buf; if (buf_id) { - buf_ctrl = buf_ctrl >> 16; - #if CFG_TUSB_RP2040_ERRATA_E4_FIX - if (!need_e4_fix) // incorrect buf_id, buffer pointer is still buf0 + #if CFG_TUSB_RP2_ERRATA_E4 + if (!need_e4_fix) // incorrect buf_id, buffer pointer is still buf0 #endif { dpram_buf += 64; // buf1 offset } } - hwbuf_sync(ep, is_rx, buf_ctrl, dpram_buf); - const bool is_done = (ep->remaining_len == 0); + const uint16_t xact_bytes = hwbuf_sync(ep, is_rx, buf_ctrl16, dpram_buf); + const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST; + const bool is_short = xact_bytes < ep->max_packet_size; + const bool is_done = is_short || (buf_ctrl16 & USB_BUF_CTRL_LAST); - if (is_double) { - if (buf_id == 0) { - // buf0 done: wait for buf1, don't start new buffers - hw_endpoint_lock_update(ep, -1); - return false; + // short packet on rx with double buffer: abort the other half (if not last) and reset double-buffer state. + // The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed. + // We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer. + // After abort, zero buf_ctrl + if (is_short && is_double && is_rx && !is_last) { + io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1); + const uint32_t abort_bit = TU_BIT((tu_edpt_number(ep->ep_addr) << 1) | (dir ? 0 : 1)); + + #if CFG_TUSB_RP2_ERRATA_E2 + if (rp2040_chipversion >= 2) + #endif + { + usb_hw_set->abort = abort_bit; + while ((usb_hw->abort_done & abort_bit) != abort_bit) {} } - // buf1 done: is_done determined by remaining_len above + + // After abort, check if the other buffer received valid data + const uint16_t buf_ctrl16_other = *buf_reg16_other; + if (buf_ctrl16_other & USB_BUF_CTRL_FULL) { + // Host already sent data into this buffer (e.g. write payload right after short CBW). + // Save it for the next transfer. + ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK); + ep->future_bufid = buf_id ^ 1; + // buff_status will be clear by the next run + } else { + ep->next_pid ^= 1u; + } + + *buf_reg = 0; // reset buffer control + + #if CFG_TUSB_RP2_ERRATA_E2 + if (rp2040_chipversion >= 2) + #endif + { + usb_hw_clear->abort_done = abort_bit; + usb_hw_clear->abort = abort_bit; + } + + hw_endpoint_lock_update(ep, -1); + return true; } - if (!is_done) { - #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + if (!is_done && ep->remaining_len > 0) { + #if CFG_TUSB_RP2_ERRATA_E15 if (e15_is_critical_frame_period(ep)) { ep->pending = 1; } else #endif { - hw_endpoint_buffer_xact(ep, ep_reg, buf_reg); + // ping-pong: do 16-bit write since controller is accessing the other half + const uint16_t buf_ctrl16_new = bufctrl_prepare(ep, dpram_buf, is_rx); + bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new); } } @@ -380,7 +492,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ // Errata 15 //--------------------------------------------------------------------+ -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +#if CFG_TUSB_RP2_ERRATA_E15 // E15 is fixed with RP2350 /* Don't mark IN buffers as available during the last 200us of a full-speed @@ -410,16 +522,15 @@ static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoin /* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point. * The device state machine cannot recover from receiving an incorrect PID - * when it is expecting an ACK. - */ + * when it is expecting an ACK. */ uint32_t delta = time_us_32() - e15_last_sof; if (delta < 800 || delta > 998) { return false; } - TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), - e15_last_sof); + // TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), + // e15_last_sof); return true; } -#endif // TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + #endif #endif diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index c4dd0cb98..8b0fc83b7 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -15,46 +15,57 @@ #error TinyUSB device and host mode not supported at the same time #endif -// E5 and E15 only apply to RP2040 #if defined(PICO_RP2040) && PICO_RP2040 == 1 - // RP2040 E5: USB device fails to exit RESET state on busy USB bus. + // RP2040-E2 USB device endpoint abort is not cleared. + #define CFG_TUSB_RP2_ERRATA_E2 1 + + // RP2040-E4: USB host writes to upper half of buffer status in single buffered mode. + #define CFG_TUSB_RP2_ERRATA_E4 1 + + // RP2040-E5: USB device fails to exit RESET state on busy USB bus. #if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX) #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX #endif - // RP2040 E15: USB Device controller will hang if certain bus errors occur during an IN transfer. - #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) - #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX PICO_RP2040_USB_DEVICE_UFRAME_FIX + // RP2040-E15: USB Device controller will hang if certain bus errors occur during an IN transfer. + #ifndef CFG_TUSB_RP2_ERRATA_E15 + #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) + #define CFG_TUSB_RP2_ERRATA_E15 PICO_RP2040_USB_DEVICE_UFRAME_FIX + #elif defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) + #define CFG_TUSB_RP2_ERRATA_E15 TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + #endif #endif +#endif - #define CFG_TUSB_RP2040_ERRATA_E4_FIX 1 +#ifndef CFG_TUSB_RP2_ERRATA_E2 + #define CFG_TUSB_RP2_ERRATA_E2 0 +#endif + +#ifndef CFG_TUSB_RP2_ERRATA_E4 + #define CFG_TUSB_RP2_ERRATA_E4 0 #endif #ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0 #endif -#ifndef TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX - #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX 0 +#ifndef CFG_TUSB_RP2_ERRATA_E15 + #define CFG_TUSB_RP2_ERRATA_E15 0 #endif -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +#if CFG_TUSB_RP2_ERRATA_E15 #undef PICO_RP2040_USB_FAST_IRQ #define PICO_RP2040_USB_FAST_IRQ 1 #endif #ifndef PICO_RP2040_USB_FAST_IRQ -#define PICO_RP2040_USB_FAST_IRQ 0 -#endif - -#ifndef CFG_TUSB_RP2040_ERRATA_E4_FIX -#define CFG_TUSB_RP2040_ERRATA_E4_FIX 0 + #define PICO_RP2040_USB_FAST_IRQ 0 #endif #if PICO_RP2040_USB_FAST_IRQ -#define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x) + #define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x) #else -#define __tusb_irq_path_func(x) x + #define __tusb_irq_path_func(x) x #endif //--------------------------------------------------------------------+ @@ -66,6 +77,12 @@ #define pico_info(...) TU_LOG(2, __VA_ARGS__) #define pico_trace(...) TU_LOG(3, __VA_ARGS__) +enum { + EPSTATE_IDLE = 0, + EPSTATE_ACTIVE, + EPSTATE_PENDING, +}; + // Hardware information per endpoint typedef struct hw_endpoint { uint8_t ep_addr; @@ -74,8 +91,11 @@ typedef struct hw_endpoint { uint8_t pending; // Transfer scheduled but not active bool is_xfer_fifo; // transfer using fifo -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX - bool e15_bulk_in; // Errata15 device bulk in + uint8_t future_bufid; + uint8_t future_len; + +#if CFG_TUSB_RP2_ERRATA_E15 + bool e15_bulk_in; // Errata15 device bulk in #endif #if CFG_TUH_ENABLED @@ -98,7 +118,7 @@ typedef struct hw_endpoint { } hw_endpoint_t; -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +#if CFG_TUSB_RP2_ERRATA_E15 extern volatile uint32_t e15_last_sof; #endif @@ -115,7 +135,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); bool hw_endpoint_xfer_continue(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id); -void hw_endpoint_buffer_xact(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); +void hw_endpoint_buffer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); void hw_endpoint_reset_transfer(struct hw_endpoint *ep); TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) { @@ -129,6 +149,11 @@ TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct //--------------------------------------------------------------------+ void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask); +void bufctrl_write32(io_rw_32 *buf_reg, uint32_t value); +void bufctrl_write16(io_rw_16 *buf_reg16, uint16_t value); + +uint16_t bufctrl_prepare(struct hw_endpoint *ep, uint8_t *dpram_buf, bool is_rx); + TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) { hwbuf_ctrl_update(buf_ctrl_reg, 0, value); } diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 8b0070d40..154a251c1 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -50,7 +50,7 @@ import ctypes from pymtp import MTP import string -ENUM_TIMEOUT = 30 +ENUM_TIMEOUT = 10 STATUS_OK = "\033[32mOK\033[0m" STATUS_FAILED = "\033[31mFailed\033[0m" |
