summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-03-26 19:33:08 +0700
committerhathach <[email protected]>2026-03-26 19:43:44 +0700
commit94f48272c79310c5ab47c79c7b57a1be7bdee901 (patch)
tree0baf2d516401a4feb0904c21295e66285cb2903a /src
parente81faa22af6369745e4aa0420289a335d2eaa047 (diff)
implement ping-pong double buffered for both tx and rx
Diffstat (limited to 'src')
-rw-r--r--src/portable/raspberrypi/rp2040/dcd_rp2040.c77
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c13
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c223
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h65
4 files changed, 266 insertions, 112 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);
}