From 0f3c90b445e8d28b7925b629157ffe22b5d385ff Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 15 Jan 2026 10:53:06 +0700 Subject: [PATCH 01/29] draft: hcd control work --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 266 +++++++++---------- src/portable/raspberrypi/rp2040/rp2040_usb.h | 5 +- 2 files changed, 126 insertions(+), 145 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 06c0ce3403..8e33796222 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -56,21 +56,33 @@ static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, ""); static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS]; #define epx (ep_pool[0]) +static hw_endpoint_t *ep_active = NULL; + // Flags we set by default in sie_ctrl (we add other bits on top) enum { SIE_CTRL_BASE = USB_SIE_CTRL_SOF_EN_BITS | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS | USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS }; -static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) { - uint8_t num = tu_edpt_number(ep_addr); - if (num == 0) { - return &epx; +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +static hw_endpoint_t *edpt_alloc(void) { + for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->wMaxPacketSize == 0) { + return ep; + } } + return NULL; +} - for (uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { +static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { + for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { struct hw_endpoint *ep = &ep_pool[i]; - if (ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr)) { + if ((ep->dev_addr == daddr) && (ep->wMaxPacketSize > 0) && + (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) { return ep; } } @@ -78,6 +90,10 @@ static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) { return NULL; } +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) { return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB; } @@ -113,7 +129,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { uint32_t bit = 1u; if (buf_status & bit) { buf_status &= ~bit; - struct hw_endpoint * ep = &epx; + hw_endpoint_t *ep = ep_active; handle_hwbuf_status_bit(bit, ep); } @@ -141,91 +157,58 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } } -static void __tusb_irq_path_func(hw_trans_complete)(void) -{ - if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) - { - pico_trace("Sent setup packet\n"); - struct hw_endpoint *ep = &epx; - assert(ep->active); - // Set transferred length to 8 for a setup packet +static void __tusb_irq_path_func(hw_trans_complete)(void) { + if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { + hw_endpoint_t *ep = ep_active; ep->xferred_len = 8; hw_xfer_complete(ep, XFER_RESULT_SUCCESS); - } - else - { + } else { // Don't care. Will handle this in buff status return; } } -static void __tusb_irq_path_func(hcd_rp2040_irq)(void) -{ - uint32_t status = usb_hw->ints; - uint32_t handled = 0; +static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { + const uint32_t status = usb_hw->ints; - if ( status & USB_INTS_HOST_CONN_DIS_BITS ) - { - handled |= USB_INTS_HOST_CONN_DIS_BITS; - - if ( dev_speed() ) - { + if (status & USB_INTS_HOST_CONN_DIS_BITS) { + if (dev_speed()) { hcd_event_device_attach(RHPORT_NATIVE, true); - } - else - { + } else { hcd_event_device_remove(RHPORT_NATIVE, true); } - // Clear speed change interrupt usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; } - if ( status & USB_INTS_STALL_BITS ) - { + if (status & USB_INTS_STALL_BITS) { // We have rx'd a stall from the device // NOTE THIS SHOULD HAVE PRIORITY OVER BUFF_STATUS // AND TRANS_COMPLETE as the stall is an alternative response // to one of those events - pico_trace("Stall REC\n"); - handled |= USB_INTS_STALL_BITS; usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; hw_xfer_complete(&epx, XFER_RESULT_STALLED); } - if ( status & USB_INTS_BUFF_STATUS_BITS ) - { - handled |= USB_INTS_BUFF_STATUS_BITS; - TU_LOG(2, "Buffer complete\r\n"); + if (status & USB_INTS_BUFF_STATUS_BITS) { handle_hwbuf_status(); } - if ( status & USB_INTS_TRANS_COMPLETE_BITS ) - { - handled |= USB_INTS_TRANS_COMPLETE_BITS; + if (status & USB_INTS_TRANS_COMPLETE_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS; - TU_LOG(2, "Transfer complete\r\n"); hw_trans_complete(); } - if ( status & USB_INTS_ERROR_RX_TIMEOUT_BITS ) - { - handled |= USB_INTS_ERROR_RX_TIMEOUT_BITS; + if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; } - if ( status & USB_INTS_ERROR_DATA_SEQ_BITS ) - { + if (status & USB_INTS_ERROR_DATA_SEQ_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS; TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", tu_u32_low16(*hwbuf_ctrl_reg_host(&epx)), tu_u32_high16(*hwbuf_ctrl_reg_host(&epx))); panic("Data Seq Error \n"); } - - if ( status ^ handled ) - { - panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); - } } void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { @@ -234,54 +217,16 @@ void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { hcd_rp2040_irq(); } -static struct hw_endpoint *_next_free_interrupt_ep(void) -{ - struct hw_endpoint * ep = NULL; - for ( uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++ ) - { - ep = &ep_pool[i]; - if ( !ep->configured ) - { - // Will be configured by hw_endpoint_init / hw_endpoint_allocate - ep->interrupt_num = (uint8_t) (i - 1); - return ep; - } - } - return ep; -} - -static hw_endpoint_t *hw_endpoint_allocate(uint8_t transfer_type) { - hw_endpoint_t *ep = NULL; - - if (transfer_type == TUSB_XFER_CONTROL) { - ep = &epx; - ep->hw_data_buf = &usbh_dpram->epx_data[0]; - } else { - // Note: even though datasheet name these "Interrupt" endpoints. These are actually - // "Asynchronous" endpoints and can be used for other type such as: Bulk (ISO need confirmation) - ep = _next_free_interrupt_ep(); - pico_info("Allocate %s ep %d\n", tu_edpt_type_str(transfer_type), ep->interrupt_num); - assert(ep); - // 0 for epx (double buffered): TODO increase to 1024 for ISO - // 2x64 for intep0 - // 3x64 for intep1 - // etc - ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)]; - } - - return ep; -} - -static void hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize, - uint8_t transfer_type, uint8_t bmInterval) { - // Already has data buffer, endpoint control, and buffer control allocated at this point - assert(ep->hw_data_buf); - - uint8_t const num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); - - ep->ep_addr = ep_addr; - ep->dev_addr = dev_addr; +static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { + const uint8_t ep_addr = ep_desc->bEndpointAddress; + const uint16_t wMaxPacketSize = tu_edpt_packet_size(ep_desc); + const uint8_t transfer_type = ep_desc->bmAttributes.xfer; + const uint8_t bmInterval = ep_desc->bInterval; + const uint8_t num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + ep->ep_addr = ep_addr; + ep->dev_addr = dev_addr; + ep->transfer_type = transfer_type; // Response to a setup packet on EP0 starts with pid of 1 ep->next_pid = (num == 0 ? 1u : 0u); @@ -303,7 +248,6 @@ static void hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t e io_rw_32 *ctrl_reg = hwep_ctrl_reg_host(ep); *ctrl_reg = ctrl_value; pico_trace("endpoint control (0x%p) <- 0x%lx\n", ctrl_reg, ctrl_value); - ep->configured = true; if (ep != &epx) { // Endpoint has its own addr_endp and interrupt bits to be setup! @@ -376,24 +320,17 @@ bool hcd_deinit(uint8_t rhport) { return true; } -void hcd_port_reset(uint8_t rhport) -{ +void hcd_port_reset(uint8_t rhport) { (void) rhport; - pico_trace("hcd_port_reset\n"); - assert(rhport == 0); // TODO: Nothing to do here yet. Perhaps need to reset some state? } -void hcd_port_reset_end(uint8_t rhport) -{ - (void) rhport; +void hcd_port_reset_end(uint8_t rhport) { + (void)rhport; } -bool hcd_port_connect_status(uint8_t rhport) -{ +bool hcd_port_connect_status(uint8_t rhport) { (void) rhport; - pico_trace("hcd_port_connect_status\n"); - assert(rhport == 0); return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS; } @@ -421,8 +358,8 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { (void) rhport; // reset epx if it is currently active with unplugged device - if (epx.configured && epx.active && epx.dev_addr == dev_addr) { - epx.configured = false; + if (epx.wMaxPacketSize > 0 && epx.active && epx.dev_addr == dev_addr) { + epx.wMaxPacketSize = 0; *hwep_ctrl_reg_host(&epx) = 0; *hwbuf_ctrl_reg_host(&epx) = 0; hw_endpoint_reset_transfer(&epx); @@ -432,13 +369,13 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { if (dev_addr != 0) { for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { hw_endpoint_t *ep = &ep_pool[i]; - if (ep->dev_addr == dev_addr && ep->configured) { + if (ep->dev_addr == dev_addr && ep->wMaxPacketSize > 0) { // in case it is an interrupt endpoint, disable it usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1)); usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0; // unconfigure the endpoint - ep->configured = false; + ep->wMaxPacketSize = 0; *hwep_ctrl_reg_host(ep) = 0; *hwbuf_ctrl_reg_host(ep) = 0; hw_endpoint_reset_transfer(ep); @@ -468,36 +405,82 @@ void hcd_int_disable(uint8_t rhport) { //--------------------------------------------------------------------+ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { (void)rhport; - pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); - hw_endpoint_t *ep = hw_endpoint_allocate(ep_desc->bmAttributes.xfer); + hw_endpoint_t *ep = edpt_alloc(); TU_ASSERT(ep); - hw_endpoint_init(ep, dev_addr, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer, - ep_desc->bInterval); + hw_endpoint_init(ep, dev_addr, ep_desc); return true; } bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { - (void) rhport; (void) daddr; (void) ep_addr; + (void)rhport; + (void)daddr; + (void)ep_addr; return false; // TODO not implemented yet } -bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { - (void) rhport; +// xfer using epx +static bool edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { + const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); - pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen); + ep->remaining_len = total_len; + ep->xferred_len = 0; + ep->active = true; - const uint8_t ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); + if (ff != NULL) { + ep->user_fifo = ff; + ep->is_xfer_fifo = true; + } else { + ep->user_buf = buffer; + ep->is_xfer_fifo = false; + } + + ep_active = ep; - // Get appropriate ep. Either EPX or interrupt endpoint - struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); + ep->hw_data_buf = &usbh_dpram->epx_data[0]; + uint dpram_offset = hw_data_offset(ep->hw_data_buf); + // Fill in endpoint control register with buffer offset + uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; + usbh_dpram->epx_ctrl = ctrl_value; + + hw_endpoint_start_next_buffer(ep); + + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); + uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE | + (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | + (need_pre(ep->dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + + // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit + // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". + // We write everything except the START_TRANS bit first, then wait some cycles. + usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS; + busy_wait_at_least_cycles(12); + usb_hw->sie_ctrl = flags; + + return true; +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + (void)rhport; + + hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); TU_ASSERT(ep); + // Control endpoint can change direction 0x00 <-> 0x80 + if (tu_edpt_number(ep_addr) == 0) { + ep->ep_addr = ep_addr; + ep->next_pid = 1; // data and status stage start with DATA1 + } + + edpt_xfer(ep, buffer, NULL, buflen); + + #if 0 // EP should be inactive - assert(!ep->active); + // assert(!ep->active); // Control endpoint can change direction 0x00 <-> 0x80 if (ep_addr != ep->ep_addr) { @@ -529,6 +512,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b } else { hw_endpoint_xfer_start(ep, buffer, NULL, buflen); } + #endif return true; } @@ -541,9 +525,8 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { return false; } -bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) -{ - (void) rhport; +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) { + (void)rhport; // Copy data into setup packet buffer for (uint8_t i = 0; i < 8; i++) { @@ -551,18 +534,15 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet } // Configure EP0 struct with setup info for the trans complete - hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL); + // hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL); + hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); TU_ASSERT(ep); - // EPX should be inactive - assert(!ep->active); - - // EP0 out - hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0); - assert(ep->configured); - + ep->ep_addr = 0; // setup is OUT ep->remaining_len = 8; - ep->active = true; + ep->active = true; + + ep_active = ep; // Set device address usb_hw->dev_addr_ctrl = dev_addr; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index c03dc34b23..682e9dab46 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -73,12 +73,13 @@ typedef struct hw_endpoint { #endif #if CFG_TUH_ENABLED - bool configured; // Is this a valid struct uint8_t dev_addr; uint8_t interrupt_num; // for host interrupt endpoints + uint8_t transfer_type; + bool need_pre; // need preamble for low speed device behind full speed hub #endif - uint16_t wMaxPacketSize; + uint16_t wMaxPacketSize; // max packet size also indicates configured uint8_t *hw_data_buf; // Buffer pointer in usb dpram // transfer info From 084ef43be65484c3fd928d1f224aed70b3d6af8a Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 16 Jan 2026 11:48:09 +0700 Subject: [PATCH 02/29] refactor hcd, get both control and interrupt endpoint working --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 33 +- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 364 +++++++++++-------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 84 ++--- src/portable/raspberrypi/rp2040/rp2040_usb.h | 65 +--- 4 files changed, 290 insertions(+), 256 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 240e6c7273..8c7dc83b8b 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -69,6 +69,22 @@ TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8 return hw_endpoint_get(num, dir); } +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) { + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + if (epnum == 0) { + // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled + return NULL; + } + return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out; +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) { + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out; +} + // main processing for dcd_edpt_iso_activate static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { ep->ep_addr = ep_addr; @@ -170,7 +186,10 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN; hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); - const bool done = hw_endpoint_xfer_continue(ep); + io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); + io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); + const bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg); + if (done) { // Notify usbd const uint16_t xferred_len = ep->xferred_len; @@ -232,7 +251,9 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { hw_endpoint_lock_update(ep, 1); if (ep->pending) { ep->pending = 0; - hw_endpoint_start_next_buffer(ep); + io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); + io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); + hw_endpoint_start_next_buffer(ep, ep_reg, buf_reg); } hw_endpoint_lock_update(ep, -1); } @@ -501,7 +522,9 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to (void)rhport; (void)is_isr; hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_xfer_start(ep, buffer, NULL, total_bytes); + io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); + io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); + hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes); return true; } @@ -509,7 +532,9 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t (void)rhport; (void)is_isr; hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_xfer_start(ep, NULL, ff, total_bytes); + io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); + io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); + hw_endpoint_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes); return true; } diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 8e33796222..ff2db2499a 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -47,42 +47,42 @@ // Low level rp2040 controller functions //--------------------------------------------------------------------+ -#ifndef PICO_USB_HOST_INTERRUPT_ENDPOINTS -#define PICO_USB_HOST_INTERRUPT_ENDPOINTS (USB_MAX_ENDPOINTS - 1) -#endif -static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, ""); - // Host mode uses one shared endpoint register for non-interrupt endpoint -static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS]; -#define epx (ep_pool[0]) - -static hw_endpoint_t *ep_active = NULL; +static hcd_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; +static hcd_endpoint_t *epx = &ep_pool[0]; // current active endpoint // Flags we set by default in sie_ctrl (we add other bits on top) enum { - SIE_CTRL_BASE = USB_SIE_CTRL_SOF_EN_BITS | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS | - USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS + SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS, + SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS | + USB_SIE_CTRL_KEEP_ALIVE_EN_BITS +}; + +enum { + SIE_CTRL_SPEED_DISCONNECT = 0, + SIE_CTRL_SPEED_LOW = 1, + SIE_CTRL_SPEED_FULL = 2, }; //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ -static hw_endpoint_t *edpt_alloc(void) { +static hcd_endpoint_t *edpt_alloc(void) { for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hw_endpoint_t *ep = &ep_pool[i]; - if (ep->wMaxPacketSize == 0) { + hcd_endpoint_t *ep = &ep_pool[i]; + if (ep->hwep.wMaxPacketSize == 0) { return ep; } } return NULL; } -static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { +static hcd_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - struct hw_endpoint *ep = &ep_pool[i]; - if ((ep->dev_addr == daddr) && (ep->wMaxPacketSize > 0) && - (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) { + hcd_endpoint_t *ep = &ep_pool[i]; + if ((ep->dev_addr == daddr) && (ep->hwep.wMaxPacketSize > 0) && + (ep->hwep.ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->hwep.ep_addr) == 0))) { return ep; } } @@ -90,6 +90,24 @@ static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { return NULL; } +// static hcd_endpoint_t* epdt_find_interrupt(uint8_t ) + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(hw_endpoint_t *ep) { + if (tu_edpt_number(ep->ep_addr) == 0) { + return &usbh_dpram->epx_ctrl; + } + // return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl; + return NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(hw_endpoint_t *ep) { + if (tu_edpt_number(ep->ep_addr) == 0) { + return &usbh_dpram->epx_buf_ctrl; + } + // return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl; + return NULL; +} + //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ @@ -104,18 +122,17 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { return hcd_port_speed_get(0) != tuh_speed_get(dev_addr); } -static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_result_t xfer_result) { +static void __tusb_irq_path_func(hw_xfer_complete)(hcd_endpoint_t *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack - uint8_t dev_addr = ep->dev_addr; - uint8_t ep_addr = ep->ep_addr; - uint xferred_len = ep->xferred_len; - hw_endpoint_reset_transfer(ep); + uint8_t dev_addr = ep->dev_addr; + uint8_t ep_addr = ep->hwep.ep_addr; + uint xferred_len = ep->hwep.xferred_len; + hw_endpoint_reset_transfer(&ep->hwep); hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); } -static void __tusb_irq_path_func(handle_hwbuf_status_bit)(uint bit, struct hw_endpoint *ep) { - usb_hw_clear->buf_status = bit; - const bool done = hw_endpoint_xfer_continue(ep); +static void __tusb_irq_path_func(handle_hwbuf_status_bit)(hcd_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { + const bool done = hw_endpoint_xfer_continue(&ep->hwep, ep_reg, buf_reg); if (done) { hw_xfer_complete(ep, XFER_RESULT_SUCCESS); } @@ -129,25 +146,35 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { uint32_t bit = 1u; if (buf_status & bit) { buf_status &= ~bit; - hw_endpoint_t *ep = ep_active; - handle_hwbuf_status_bit(bit, ep); + usb_hw_clear->buf_status = bit; + + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + handle_hwbuf_status_bit(epx, ep_reg, buf_reg); } // Check "interrupt" (asynchronous) endpoints for both IN and OUT + // TODO use clz for better efficiency for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && buf_status; i++) { - // EPX is bit 0 & 1 - // IEP1 IN is bit 2 - // IEP1 OUT is bit 3 - // IEP2 IN is bit 4 - // IEP2 OUT is bit 5 - // IEP3 IN is bit 6 - // IEP3 OUT is bit 7 + // EPX IN/OUT is bit 0, 1 + // IEP1 IN/OUT is bit 2, 3 + // IEP2 IN/OUT is bit 4, 5 // etc for (uint j = 0; j < 2; j++) { bit = 1 << (i * 2 + j); if (buf_status & bit) { buf_status &= ~bit; - handle_hwbuf_status_bit(bit, &ep_pool[i]); + usb_hw_clear->buf_status = bit; + + for (uint8_t e = 0; e < USB_MAX_ENDPOINTS; e++) { + hcd_endpoint_t *ep = &ep_pool[e]; + if (ep->interrupt_num == i) { + io_rw_32 *ep_reg = &usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl; + io_rw_32 *buf_reg = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl; + handle_hwbuf_status_bit(ep, ep_reg, buf_reg); + break; + } + } } } } @@ -157,27 +184,24 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } } -static void __tusb_irq_path_func(hw_trans_complete)(void) { - if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { - hw_endpoint_t *ep = ep_active; - ep->xferred_len = 8; - hw_xfer_complete(ep, XFER_RESULT_SUCCESS); - } else { - // Don't care. Will handle this in buff status - return; - } -} +// static void edpt_scheduler(void) { +// } static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { const uint32_t status = usb_hw->ints; if (status & USB_INTS_HOST_CONN_DIS_BITS) { - if (dev_speed()) { - hcd_event_device_attach(RHPORT_NATIVE, true); - } else { + uint8_t speed = dev_speed(); + if (speed == SIE_CTRL_SPEED_DISCONNECT) { hcd_event_device_remove(RHPORT_NATIVE, true); + } else { + if (speed == SIE_CTRL_SPEED_LOW) { + usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS; + } else { + usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS; + } + hcd_event_device_attach(RHPORT_NATIVE, true); } - usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; } @@ -187,7 +211,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // AND TRANS_COMPLETE as the stall is an alternative response // to one of those events usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; - hw_xfer_complete(&epx, XFER_RESULT_STALLED); + hw_xfer_complete(epx, XFER_RESULT_STALLED); } if (status & USB_INTS_BUFF_STATUS_BITS) { @@ -196,7 +220,14 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { if (status & USB_INTS_TRANS_COMPLETE_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS; - hw_trans_complete(); + + // only handle setup packet + if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { + epx->hwep.xferred_len = 8; + hw_xfer_complete(epx, XFER_RESULT_SUCCESS); + } else { + // Don't care. Will handle this in buff status + } } if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { @@ -205,36 +236,69 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { if (status & USB_INTS_ERROR_DATA_SEQ_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS; - TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", tu_u32_low16(*hwbuf_ctrl_reg_host(&epx)), - tu_u32_high16(*hwbuf_ctrl_reg_host(&epx))); panic("Data Seq Error \n"); } } void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { - (void) rhport; - (void) in_isr; + (void)rhport; + (void)in_isr; hcd_rp2040_irq(); } -static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { +static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { const uint8_t ep_addr = ep_desc->bEndpointAddress; const uint16_t wMaxPacketSize = tu_edpt_packet_size(ep_desc); const uint8_t transfer_type = ep_desc->bmAttributes.xfer; - const uint8_t bmInterval = ep_desc->bInterval; - const uint8_t num = tu_edpt_number(ep_addr); - const tusb_dir_t dir = tu_edpt_dir(ep_addr); - ep->ep_addr = ep_addr; - ep->dev_addr = dev_addr; - ep->transfer_type = transfer_type; - - // Response to a setup packet on EP0 starts with pid of 1 - ep->next_pid = (num == 0 ? 1u : 0u); - ep->wMaxPacketSize = wMaxPacketSize; - - pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, transfer_type); - pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->ep_addr, ep->hw_data_buf); - uint dpram_offset = hw_data_offset(ep->hw_data_buf); + // const uint8_t bmInterval = ep_desc->bInterval; + + ep->hwep.ep_addr = ep_addr; + ep->dev_addr = dev_addr; + ep->transfer_type = transfer_type; + ep->need_pre = need_pre(dev_addr); + ep->hwep.next_pid = 0u; + ep->hwep.wMaxPacketSize = wMaxPacketSize; + + if (transfer_type != TUSB_XFER_INTERRUPT) { + ep->hwep.hw_data_buf = usbh_dpram->epx_data; + } else { + // from 15 interrupt endpoints pool + uint8_t int_idx; + for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) { + if (!tu_bit_test(usb_hw_set->int_ep_ctrl, 1 + int_idx)) { + ep->interrupt_num = int_idx + 1; + break; + } + } + assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS); + assert(ep_desc->bInterval > 0); + + //------------- dpram buf -------------// + // 15x64 last bytes of DPRAM for interrupt endpoint buffers + ep->hwep.hw_data_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); + uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hwep.hw_data_buf) | + (uint32_t)((ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); + usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; + + //------------- address control -------------// + const uint8_t epnum = tu_edpt_number(ep_addr); + uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB)); + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { + addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS; + } + if (ep->need_pre) { + addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; + } + usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl; + + // Finally, activate interrupt endpoint + usb_hw_set->int_ep_ctrl |= 1u << ep->interrupt_num; + } + #if 0 + pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->hwep.ep_addr, transfer_type); + pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->hwep.ep_addr, ep->hwep.hw_data_buf); + uint dpram_offset = hw_data_offset(ep->hwep.hw_data_buf); // Bits 0-5 should be 0 assert(!(dpram_offset & 0b111111)); @@ -271,6 +335,7 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_des // If it's an interrupt endpoint we need to set up the buffer control register } + #endif } //--------------------------------------------------------------------+ @@ -312,11 +377,9 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { bool hcd_deinit(uint8_t rhport) { (void) rhport; - irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); - return true; } @@ -330,58 +393,54 @@ void hcd_port_reset_end(uint8_t rhport) { } bool hcd_port_connect_status(uint8_t rhport) { - (void) rhport; + (void)rhport; return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS; } -tusb_speed_t hcd_port_speed_get(uint8_t rhport) -{ - (void) rhport; - assert(rhport == 0); - - // TODO: Should enumval this register - switch ( dev_speed() ) - { - case 1: +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void)rhport; + switch (dev_speed()) { + case SIE_CTRL_SPEED_LOW: return TUSB_SPEED_LOW; - case 2: + case SIE_CTRL_SPEED_FULL: return TUSB_SPEED_FULL; default: - panic("Invalid speed\n"); - // return TUSB_SPEED_INVALID; + return TUSB_SPEED_INVALID; } } // Close all opened endpoint belong to this device void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { - pico_trace("hcd_device_close %d\n", dev_addr); - (void) rhport; + (void)rhport; + (void)dev_addr; + #if 0 // reset epx if it is currently active with unplugged device - if (epx.wMaxPacketSize > 0 && epx.active && epx.dev_addr == dev_addr) { - epx.wMaxPacketSize = 0; + if (epx.hw_ep.wMaxPacketSize > 0 && epx.hw_ep.active && epx.dev_addr == dev_addr) { + epx.hw_ep.wMaxPacketSize = 0; *hwep_ctrl_reg_host(&epx) = 0; *hwbuf_ctrl_reg_host(&epx) = 0; - hw_endpoint_reset_transfer(&epx); + hw_endpoint_reset_transfer(&epx.hw_ep); } // dev0 only has ep0 if (dev_addr != 0) { for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { - hw_endpoint_t *ep = &ep_pool[i]; - if (ep->dev_addr == dev_addr && ep->wMaxPacketSize > 0) { + hcd_endpoint_t *ep = &ep_pool[i]; + if (ep->dev_addr == dev_addr && ep->hwep.wMaxPacketSize > 0) { // in case it is an interrupt endpoint, disable it usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1)); usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0; // unconfigure the endpoint - ep->wMaxPacketSize = 0; + ep->hwep.wMaxPacketSize = 0; *hwep_ctrl_reg_host(ep) = 0; *hwbuf_ctrl_reg_host(ep) = 0; - hw_endpoint_reset_transfer(ep); + hw_endpoint_reset_transfer(&ep->hwep); } } } + #endif } uint32_t hcd_frame_number(uint8_t rhport) { @@ -405,9 +464,9 @@ void hcd_int_disable(uint8_t rhport) { //--------------------------------------------------------------------+ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { (void)rhport; - hw_endpoint_t *ep = edpt_alloc(); + pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); + hcd_endpoint_t *ep = edpt_alloc(); TU_ASSERT(ep); - hw_endpoint_init(ep, dev_addr, ep_desc); return true; @@ -420,46 +479,49 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { return false; // TODO not implemented yet } -// xfer using epx -static bool edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { - const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); +TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value) { + value |= (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK); // preserve base bits - ep->remaining_len = total_len; - ep->xferred_len = 0; - ep->active = true; + // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit + // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". + // We write everything except the START_TRANS bit first, then wait some cycles. + usb_hw->sie_ctrl = value; + busy_wait_at_least_cycles(12); + usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; +} - if (ff != NULL) { - ep->user_fifo = ff; - ep->is_xfer_fifo = true; +// xfer using epx +static bool edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { + if (ep->transfer_type == TUSB_XFER_INTERRUPT) { + // For interrupt endpoint control and buffer is already configured + // Note: Interrupt is single buffered only + io_rw_32 *ep_reg = &usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl; + io_rw_32 *buf_reg = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl; + hw_endpoint_xfer_start(&ep->hwep, ep_reg, buf_reg, buffer, ff, total_len); } else { - ep->user_buf = buffer; - ep->is_xfer_fifo = false; - } - - ep_active = ep; + const uint8_t ep_num = tu_edpt_number(ep->hwep.ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep->hwep.ep_addr); - ep->hw_data_buf = &usbh_dpram->epx_data[0]; - uint dpram_offset = hw_data_offset(ep->hw_data_buf); + // ep control + const uint32_t dpram_offset = hw_data_offset(ep->hwep.hw_data_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; + usbh_dpram->epx_ctrl = ep_ctrl; - // Fill in endpoint control register with buffer offset - uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; - usbh_dpram->epx_ctrl = ctrl_value; + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + hw_endpoint_xfer_start(&ep->hwep, ep_reg, buf_reg, buffer, ff, total_len); - hw_endpoint_start_next_buffer(ep); + // addr control + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE | - (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | - (need_pre(ep->dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + epx = ep; - // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit - // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". - // We write everything except the START_TRANS bit first, then wait some cycles. - usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS; - busy_wait_at_least_cycles(12); - usb_hw->sie_ctrl = flags; + // start transfer + const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | + (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sie_ctrl); + } return true; } @@ -467,13 +529,13 @@ static bool edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void)rhport; - hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); + hcd_endpoint_t *ep = edpt_find(dev_addr, ep_addr); TU_ASSERT(ep); // Control endpoint can change direction 0x00 <-> 0x80 - if (tu_edpt_number(ep_addr) == 0) { - ep->ep_addr = ep_addr; - ep->next_pid = 1; // data and status stage start with DATA1 + if (ep_addr != ep->hwep.ep_addr) { + ep->hwep.ep_addr = ep_addr; + ep->hwep.next_pid = 1; // data and status stage start with DATA1 } edpt_xfer(ep, buffer, NULL, buflen); @@ -518,9 +580,9 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b } bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - (void) dev_addr; - (void) ep_addr; + (void)rhport; + (void)dev_addr; + (void)ep_addr; // TODO not implemented yet return false; } @@ -533,38 +595,30 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet usbh_dpram->setup_packet[i] = setup_packet[i]; } - // Configure EP0 struct with setup info for the trans complete - // hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL); - hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); + hcd_endpoint_t *ep = edpt_find(dev_addr, 0x00); TU_ASSERT(ep); - ep->ep_addr = 0; // setup is OUT - ep->remaining_len = 8; - ep->active = true; + ep->hwep.ep_addr = 0; // setup is OUT + ep->hwep.remaining_len = 8; + ep->hwep.xferred_len = 0; + ep->hwep.active = true; - ep_active = ep; + epx = ep; // Set device address usb_hw->dev_addr_ctrl = dev_addr; // Set pre if we are a low speed device on full speed hub - uint32_t const flags = SIE_CTRL_BASE | USB_SIE_CTRL_SEND_SETUP_BITS | USB_SIE_CTRL_START_TRANS_BITS | - (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - - // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit - // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". - // We write everything except the START_TRANS bit first, then wait some cycles. - usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS; - busy_wait_at_least_cycles(12); - usb_hw->sie_ctrl = flags; + const uint32_t sie_ctrl = USB_SIE_CTRL_SEND_SETUP_BITS | (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sie_ctrl); return true; } bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - (void) dev_addr; - (void) ep_addr; + (void)rhport; + (void)dev_addr; + (void)ep_addr; panic("hcd_clear_stall"); // return true; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 3b65f57a42..74c6bf655a 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -35,7 +35,7 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ -static void sync_xfer(hw_endpoint_t *ep); +static void sync_xfer(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX static bool e15_is_critical_frame_period(struct hw_endpoint *ep); @@ -92,6 +92,7 @@ void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) { ep->remaining_len = 0; ep->xferred_len = 0; ep->user_buf = 0; + ep->is_xfer_fifo = false; } void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask) { @@ -124,7 +125,7 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an } // prepare buffer, move data if tx, return buffer control -static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { +uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { const uint16_t buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize); ep->remaining_len = (uint16_t) (ep->remaining_len - buflen); @@ -166,33 +167,23 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, } // Prepare buffer control register value -void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) { +void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(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); - bool is_rx; - bool is_host = false; - io_rw_32 *ep_ctrl_reg; - io_rw_32 *buf_ctrl_reg; + const bool is_host = rp2usb_is_host_mode(); - #if CFG_TUH_ENABLED - is_host = rp2usb_is_host_mode(); + bool is_rx; if (is_host) { - buf_ctrl_reg = hwbuf_ctrl_reg_host(ep); - ep_ctrl_reg = hwep_ctrl_reg_host(ep); - is_rx = (dir == TUSB_DIR_IN); - } else - #endif - { - buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); - ep_ctrl_reg = hwep_ctrl_reg_device(ep); - is_rx = (dir == TUSB_DIR_OUT); + is_rx = (dir == TUSB_DIR_IN); + } else { + is_rx = (dir == TUSB_DIR_OUT); } // always compute and start with buffer 0 - uint32_t buf_ctrl = prepare_ep_buffer(ep, 0, is_rx) | USB_BUF_CTRL_SEL; + uint32_t buf_ctrl = hwbuf_prepare(ep, 0, is_rx) | USB_BUF_CTRL_SEL; // EP0 has no endpoint control register, also usbd only schedule 1 packet at a time (single buffer) - if (ep_ctrl_reg != NULL) { - uint32_t ep_ctrl = *ep_ctrl_reg; + if (ep_reg != NULL) { + uint32_t ep_ctrl = *ep_reg; // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause // short packet on buffer0 @@ -205,7 +196,7 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) // Use buffer 1 (double buffered) if there is still data // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt) - buf_ctrl |= prepare_ep_buffer(ep, 1, is_rx); + buf_ctrl |= hwbuf_prepare(ep, 1, is_rx); // Set endpoint control double buffered bit if needed ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER; @@ -216,17 +207,18 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; } - *ep_ctrl_reg = ep_ctrl; + *ep_reg = ep_ctrl; } TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl)); // Finally, write to buffer_control which will trigger the transfer // the next time the controller polls this dpram address - hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl); + hwbuf_ctrl_set(buf_reg, buf_ctrl); } -void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { +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) { hw_endpoint_lock_update(ep, 1); if (ep->active) { @@ -258,15 +250,14 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t * } else #endif { - hw_endpoint_start_next_buffer(ep); + hw_endpoint_start_next_buffer(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(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, - bool is_rx) { +uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, bool is_rx) { uint32_t buf_ctrl = *buf_ctrl_reg; if (buf_id) { buf_ctrl = buf_ctrl >> 16; @@ -304,37 +295,26 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32 } // Update hw endpoint struct with info from hardware after a buff status interrupt -static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep) { +static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { // const uint8_t ep_num = tu_edpt_number(ep->ep_addr); const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); + const bool is_host = rp2usb_is_host_mode(); + bool is_rx; - io_rw_32 *buf_ctrl_reg; - io_rw_32 *ep_ctrl_reg; - bool is_rx; - - #if CFG_TUH_ENABLED - const bool is_host = rp2usb_is_host_mode(); if (is_host) { - buf_ctrl_reg = hwbuf_ctrl_reg_host(ep); - ep_ctrl_reg = hwep_ctrl_reg_host(ep); - is_rx = (dir == TUSB_DIR_IN); - } else - #endif - { - buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); - ep_ctrl_reg = hwep_ctrl_reg_device(ep); - is_rx = (dir == TUSB_DIR_OUT); + is_rx = (dir == TUSB_DIR_IN); + } else { + is_rx = (dir == TUSB_DIR_OUT); } - TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_ctrl_reg), - tu_u32_high16(*buf_ctrl_reg)); - uint16_t buf0_bytes = sync_ep_buffer(ep, buf_ctrl_reg, 0, is_rx); // always sync buffer 0 + TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_reg), tu_u32_high16(*buf_reg)); + uint16_t buf0_bytes = hwbuf_sync(ep, buf_reg, 0, is_rx); // always sync buffer 0 // sync buffer 1 if double buffered - if (ep_ctrl_reg != NULL && (*ep_ctrl_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) { + if (ep_reg != NULL && (*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) { if (buf0_bytes == ep->wMaxPacketSize) { // sync buffer 1 if not short packet - sync_ep_buffer(ep, buf_ctrl_reg, 1, is_rx); + hwbuf_sync(ep, buf_reg, 1, is_rx); } else { // short packet on buffer 0 // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example @@ -368,7 +348,7 @@ static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep) { } // Returns true if transfer is complete -bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { +bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { hw_endpoint_lock_update(ep, 1); // Part way through a transfer @@ -376,7 +356,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { panic("Can't continue xfer on inactive ep %02X", ep->ep_addr); } - sync_xfer(ep); // Update EP struct from hardware state + sync_xfer(ep, ep_reg, buf_reg); // Update EP struct from hardware state // Now we have synced our state with the hardware. Is there more data to transfer? // If we are done then notify tinyusb @@ -392,7 +372,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { } else #endif { - hw_endpoint_start_next_buffer(ep); + hw_endpoint_start_next_buffer(ep, ep_reg, buf_reg); } } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 682e9dab46..ac1d8610a3 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -72,13 +72,6 @@ typedef struct hw_endpoint { uint8_t pending; // Transfer scheduled but not active #endif -#if CFG_TUH_ENABLED - uint8_t dev_addr; - uint8_t interrupt_num; // for host interrupt endpoints - uint8_t transfer_type; - bool need_pre; // need preamble for low speed device behind full speed hub -#endif - uint16_t wMaxPacketSize; // max packet size also indicates configured uint8_t *hw_data_buf; // Buffer pointer in usb dpram @@ -92,6 +85,15 @@ typedef struct hw_endpoint { } hw_endpoint_t; +// Host controller endpoint +typedef struct { + hw_endpoint_t hwep; + uint8_t dev_addr; + uint8_t interrupt_num; // 1-15 for interrupt endpoints + uint8_t transfer_type; + bool need_pre; // need preamble for low speed device behind full speed hub +} hcd_endpoint_t; + #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX extern volatile uint32_t e15_last_sof; #endif @@ -103,10 +105,14 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; } -void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); -bool hw_endpoint_xfer_continue(struct hw_endpoint *ep); +//--------------------------------------------------------------------+ +// Hardware Endpoint +//--------------------------------------------------------------------+ +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); void hw_endpoint_reset_transfer(struct hw_endpoint *ep); -void hw_endpoint_start_next_buffer(struct hw_endpoint *ep); +void hw_endpoint_start_next_buffer(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) { // todo add critsec as necessary to prevent issues between worker and IRQ... @@ -114,43 +120,12 @@ TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct // sense to have worker and IRQ on same core, however I think using critsec is about equivalent. } -// #if CFG_TUD_ENABLED -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) { - uint8_t const epnum = tu_edpt_number(ep->ep_addr); - const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); - if (epnum == 0) { - // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled - return NULL; - } - return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out; -} - -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) { - const uint8_t epnum = tu_edpt_number(ep->ep_addr); - const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); - return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out; -} -// #endif - -#if CFG_TUH_ENABLED -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(struct hw_endpoint *ep) { - if (tu_edpt_number(ep->ep_addr) == 0) { - return &usbh_dpram->epx_ctrl; - } - return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl; -} - -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(struct hw_endpoint *ep) { - if (tu_edpt_number(ep->ep_addr) == 0) { - return &usbh_dpram->epx_buf_ctrl; - } - return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl; -} -#endif - //--------------------------------------------------------------------+ -// +// Hardware Buffer //--------------------------------------------------------------------+ +uint32_t hwbuf_prepare(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx); +uint16_t hwbuf_sync(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, bool is_rx); + void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask); TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) { From 0c9a170dca9b0a0982b4b0462508ee4bda7a06da Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 16 Jan 2026 13:39:06 +0700 Subject: [PATCH 03/29] implement hcd_device_close() --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 123 +++---------------- 1 file changed, 20 insertions(+), 103 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index ff2db2499a..8a891c49a1 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -252,12 +252,12 @@ static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_de const uint8_t transfer_type = ep_desc->bmAttributes.xfer; // const uint8_t bmInterval = ep_desc->bInterval; + ep->hwep.wMaxPacketSize = wMaxPacketSize; ep->hwep.ep_addr = ep_addr; ep->dev_addr = dev_addr; ep->transfer_type = transfer_type; ep->need_pre = need_pre(dev_addr); ep->hwep.next_pid = 0u; - ep->hwep.wMaxPacketSize = wMaxPacketSize; if (transfer_type != TUSB_XFER_INTERRUPT) { ep->hwep.hw_data_buf = usbh_dpram->epx_data; @@ -293,49 +293,8 @@ static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_de usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl; // Finally, activate interrupt endpoint - usb_hw_set->int_ep_ctrl |= 1u << ep->interrupt_num; - } - #if 0 - pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->hwep.ep_addr, transfer_type); - pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->hwep.ep_addr, ep->hwep.hw_data_buf); - uint dpram_offset = hw_data_offset(ep->hwep.hw_data_buf); - // Bits 0-5 should be 0 - assert(!(dpram_offset & 0b111111)); - - // Fill in endpoint control register with buffer offset - uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; - if (bmInterval) { - ctrl_value |= (uint32_t)((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); - } - - io_rw_32 *ctrl_reg = hwep_ctrl_reg_host(ep); - *ctrl_reg = ctrl_value; - pico_trace("endpoint control (0x%p) <- 0x%lx\n", ctrl_reg, ctrl_value); - - if (ep != &epx) { - // Endpoint has its own addr_endp and interrupt bits to be setup! - // This is an interrupt/async endpoint. so need to set up ADDR_ENDP register with: - // - device address - // - endpoint number / direction - // - preamble - uint32_t reg = (uint32_t)(dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB)); - - if (dir == TUSB_DIR_OUT) { - reg |= USB_ADDR_ENDP1_INTEP_DIR_BITS; - } - - if (need_pre(dev_addr)) { - reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; - } - usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = reg; - - // Finally, enable interrupt that endpoint - usb_hw_set->int_ep_ctrl = 1 << (ep->interrupt_num + 1); - - // If it's an interrupt endpoint we need to set up the buffer control register + usb_hw_set->int_ep_ctrl = 1u << ep->interrupt_num; } - #endif } //--------------------------------------------------------------------+ @@ -414,33 +373,29 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { (void)rhport; (void)dev_addr; - #if 0 // reset epx if it is currently active with unplugged device - if (epx.hw_ep.wMaxPacketSize > 0 && epx.hw_ep.active && epx.dev_addr == dev_addr) { - epx.hw_ep.wMaxPacketSize = 0; - *hwep_ctrl_reg_host(&epx) = 0; - *hwbuf_ctrl_reg_host(&epx) = 0; - hw_endpoint_reset_transfer(&epx.hw_ep); + if (epx->hwep.wMaxPacketSize > 0 && epx->dev_addr == dev_addr) { + // if (epx->hwep.active) { + // // need to abort transfer + // } + epx->hwep.wMaxPacketSize = 0; } - // dev0 only has ep0 - if (dev_addr != 0) { - for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { - hcd_endpoint_t *ep = &ep_pool[i]; - if (ep->dev_addr == dev_addr && ep->hwep.wMaxPacketSize > 0) { - // in case it is an interrupt endpoint, disable it - usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1)); - usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0; - - // unconfigure the endpoint - ep->hwep.wMaxPacketSize = 0; - *hwep_ctrl_reg_host(ep) = 0; - *hwbuf_ctrl_reg_host(ep) = 0; - hw_endpoint_reset_transfer(&ep->hwep); + for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hcd_endpoint_t *ep = &ep_pool[i]; + if (ep->dev_addr == dev_addr && ep->hwep.wMaxPacketSize > 0) { + if (ep->interrupt_num) { + // disable interrupt endpoint + usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num; + usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0; + + usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl = 0; + usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl = 0; } + + ep->hwep.wMaxPacketSize = 0; // mark as unused } } - #endif } uint32_t hcd_frame_number(uint8_t rhport) { @@ -491,7 +446,7 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value) { } // xfer using epx -static bool edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { +static void edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { if (ep->transfer_type == TUSB_XFER_INTERRUPT) { // For interrupt endpoint control and buffer is already configured // Note: Interrupt is single buffered only @@ -522,8 +477,6 @@ static bool edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16 (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); sie_start_xfer(sie_ctrl); } - - return true; } bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { @@ -540,42 +493,6 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b edpt_xfer(ep, buffer, NULL, buflen); - #if 0 - // EP should be inactive - // assert(!ep->active); - - // Control endpoint can change direction 0x00 <-> 0x80 - if (ep_addr != ep->ep_addr) { - assert(ep_num == 0); - - // Direction has flipped on endpoint control so re init it but with same properties - hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, TUSB_XFER_CONTROL, 0); - } - - // If a normal transfer (non-interrupt) then initiate using - // sie ctrl registers. Otherwise, interrupt ep registers should - // already be configured - if (ep == &epx) { - hw_endpoint_xfer_start(ep, buffer, NULL, buflen); - - // That has set up buffer control, endpoint control etc - // for host we have to initiate the transfer - usb_hw->dev_addr_ctrl = (uint32_t) (dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - - uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE | - (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | - (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit - // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". - // We write everything except the START_TRANS bit first, then wait some cycles. - usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS; - busy_wait_at_least_cycles(12); - usb_hw->sie_ctrl = flags; - } else { - hw_endpoint_xfer_start(ep, buffer, NULL, buflen); - } - #endif - return true; } From 292f334bd9b9d76524bbd6b76aea3cf9e6fd24c4 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 16 Jan 2026 15:03:09 +0700 Subject: [PATCH 04/29] rename --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 14 ++++++------ src/portable/raspberrypi/rp2040/hcd_rp2040.c | 23 ++++++++++---------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 15 +++++++------ src/portable/raspberrypi/rp2040/rp2040_usb.h | 9 +++----- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 8c7dc83b8b..7af6cdb430 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -89,7 +89,7 @@ TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_en static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { ep->ep_addr = ep_addr; ep->next_pid = 0u; - ep->wMaxPacketSize = wMaxPacketSize; + ep->max_packet_size = wMaxPacketSize; // Clear existing buffer control state io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); @@ -99,7 +99,7 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa const uint8_t epnum = tu_edpt_number(ep_addr); if (epnum == 0) { // Buffer offset is fixed (also double buffered) - ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0]; + ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0]; } else { // round up size to multiple of 64 uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64); @@ -116,11 +116,11 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa } // assign buffer - ep->hw_data_buf = hw_buffer_ptr; + ep->dpram_buf = hw_buffer_ptr; hw_buffer_ptr += size; hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data)); - pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->hw_data_buf); + pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->dpram_buf); } } @@ -129,7 +129,7 @@ static void hw_endpoint_enable(hw_endpoint_t *ep, uint8_t transfer_type) { // Set endpoint control register to enable (EP0 has no endpoint control register) if (ctrl_reg != NULL) { const uint32_t ctrl_value = - EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf); + EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf); *ctrl_reg = ctrl_value; } } @@ -501,12 +501,12 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress); const tusb_dir_t dir = tu_edpt_dir(ep_desc->bEndpointAddress); struct hw_endpoint *ep = hw_endpoint_get(epnum, dir); - TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously + TU_ASSERT(ep->dpram_buf != NULL); // must be inited and allocated previously if (ep->active) { hw_endpoint_abort_xfer(ep); // abort any pending transfer } - ep->wMaxPacketSize = ep_desc->wMaxPacketSize; + ep->max_packet_size = ep_desc->wMaxPacketSize; hw_endpoint_enable(ep, TUSB_XFER_ISOCHRONOUS); return true; diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 8a891c49a1..6377a5d752 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -71,7 +71,7 @@ enum { static hcd_endpoint_t *edpt_alloc(void) { for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { hcd_endpoint_t *ep = &ep_pool[i]; - if (ep->hwep.wMaxPacketSize == 0) { + if (ep->hwep.max_packet_size == 0) { return ep; } } @@ -81,7 +81,7 @@ static hcd_endpoint_t *edpt_alloc(void) { static hcd_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { hcd_endpoint_t *ep = &ep_pool[i]; - if ((ep->dev_addr == daddr) && (ep->hwep.wMaxPacketSize > 0) && + if ((ep->dev_addr == daddr) && (ep->hwep.max_packet_size > 0) && (ep->hwep.ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->hwep.ep_addr) == 0))) { return ep; } @@ -184,6 +184,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } } +// // static void edpt_scheduler(void) { // } @@ -252,7 +253,7 @@ static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_de const uint8_t transfer_type = ep_desc->bmAttributes.xfer; // const uint8_t bmInterval = ep_desc->bInterval; - ep->hwep.wMaxPacketSize = wMaxPacketSize; + ep->hwep.max_packet_size = wMaxPacketSize; ep->hwep.ep_addr = ep_addr; ep->dev_addr = dev_addr; ep->transfer_type = transfer_type; @@ -260,7 +261,7 @@ static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_de ep->hwep.next_pid = 0u; if (transfer_type != TUSB_XFER_INTERRUPT) { - ep->hwep.hw_data_buf = usbh_dpram->epx_data; + ep->hwep.dpram_buf = usbh_dpram->epx_data; } else { // from 15 interrupt endpoints pool uint8_t int_idx; @@ -275,9 +276,9 @@ static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_de //------------- dpram buf -------------// // 15x64 last bytes of DPRAM for interrupt endpoint buffers - ep->hwep.hw_data_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); + ep->hwep.dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hwep.hw_data_buf) | + (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hwep.dpram_buf) | (uint32_t)((ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; @@ -374,16 +375,16 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { (void)dev_addr; // reset epx if it is currently active with unplugged device - if (epx->hwep.wMaxPacketSize > 0 && epx->dev_addr == dev_addr) { + if (epx->hwep.max_packet_size > 0 && epx->dev_addr == dev_addr) { // if (epx->hwep.active) { // // need to abort transfer // } - epx->hwep.wMaxPacketSize = 0; + epx->hwep.max_packet_size = 0; } for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { hcd_endpoint_t *ep = &ep_pool[i]; - if (ep->dev_addr == dev_addr && ep->hwep.wMaxPacketSize > 0) { + if (ep->dev_addr == dev_addr && ep->hwep.max_packet_size > 0) { if (ep->interrupt_num) { // disable interrupt endpoint usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num; @@ -393,7 +394,7 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl = 0; } - ep->hwep.wMaxPacketSize = 0; // mark as unused + ep->hwep.max_packet_size = 0; // mark as unused } } } @@ -458,7 +459,7 @@ static void edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16 const tusb_dir_t ep_dir = tu_edpt_dir(ep->hwep.ep_addr); // ep control - const uint32_t dpram_offset = hw_data_offset(ep->hwep.hw_data_buf); + const uint32_t dpram_offset = hw_data_offset(ep->hwep.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; usbh_dpram->epx_ctrl = ep_ctrl; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 74c6bf655a..477e50a6a1 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -125,8 +125,8 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an } // prepare buffer, move data if tx, return buffer control -uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { - const uint16_t buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize); +static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { + const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size); ep->remaining_len = (uint16_t) (ep->remaining_len - buflen); uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; @@ -138,7 +138,7 @@ uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t buf if (!is_rx) { if (buflen) { // Copy data from user buffer/fifo to hw buffer - uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64; + uint8_t *hw_buf = ep->dpram_buf + buf_id * 64; if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround tu_hwfifo_write_from_fifo(hw_buf, ep->user_fifo, buflen, NULL); @@ -257,7 +257,8 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * } // sync endpoint buffer and return transferred bytes -uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, bool is_rx) { +static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, + bool is_rx) { uint32_t buf_ctrl = *buf_ctrl_reg; if (buf_id) { buf_ctrl = buf_ctrl >> 16; @@ -274,7 +275,7 @@ uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_ // we have received AFTER we have copied it to the user buffer at the appropriate offset assert(buf_ctrl & USB_BUF_CTRL_FULL); - uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64; + uint8_t *hw_buf = ep->dpram_buf + buf_id * 64; if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround tu_hwfifo_read_to_fifo(hw_buf, ep->user_fifo, xferred_bytes, NULL); @@ -286,7 +287,7 @@ uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_ ep->xferred_len += xferred_bytes; // Short packet - if (xferred_bytes < ep->wMaxPacketSize) { + if (xferred_bytes < ep->max_packet_size) { // Reduce total length as this is last packet ep->remaining_len = 0; } @@ -312,7 +313,7 @@ static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep, io_rw_32 *ep_reg, // sync buffer 1 if double buffered if (ep_reg != NULL && (*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) { - if (buf0_bytes == ep->wMaxPacketSize) { + if (buf0_bytes == ep->max_packet_size) { // sync buffer 1 if not short packet hwbuf_sync(ep, buf_reg, 1, is_rx); } else { diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index ac1d8610a3..cbb2290c18 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -72,8 +72,8 @@ typedef struct hw_endpoint { uint8_t pending; // Transfer scheduled but not active #endif - uint16_t wMaxPacketSize; // max packet size also indicates configured - uint8_t *hw_data_buf; // Buffer pointer in usb dpram + uint16_t max_packet_size; // max packet size also indicates configured + uint8_t *dpram_buf; // Buffer pointer in usb dpram // transfer info union { @@ -111,8 +111,8 @@ 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); -void hw_endpoint_reset_transfer(struct hw_endpoint *ep); void hw_endpoint_start_next_buffer(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) { // todo add critsec as necessary to prevent issues between worker and IRQ... @@ -123,9 +123,6 @@ TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct //--------------------------------------------------------------------+ // Hardware Buffer //--------------------------------------------------------------------+ -uint32_t hwbuf_prepare(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx); -uint16_t hwbuf_sync(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, bool is_rx); - void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask); TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) { From edcc95de2e0605603d2400239587ea078631076f Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 16 Jan 2026 15:03:41 +0700 Subject: [PATCH 05/29] rename --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 6377a5d752..8c2e938050 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -92,22 +92,6 @@ static hcd_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { // static hcd_endpoint_t* epdt_find_interrupt(uint8_t ) -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(hw_endpoint_t *ep) { - if (tu_edpt_number(ep->ep_addr) == 0) { - return &usbh_dpram->epx_ctrl; - } - // return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl; - return NULL; -} - -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(hw_endpoint_t *ep) { - if (tu_edpt_number(ep->ep_addr) == 0) { - return &usbh_dpram->epx_buf_ctrl; - } - // return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl; - return NULL; -} - //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ From 0dd509ad3639a49f7f6a885da02670c09c05a058 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 16 Jan 2026 16:10:00 +0700 Subject: [PATCH 06/29] revert back to shared hw_endpoint_t --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 96 ++++++++++---------- src/portable/raspberrypi/rp2040/rp2040_usb.h | 18 ++-- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 8c2e938050..0a8dbe11a0 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -48,8 +48,8 @@ //--------------------------------------------------------------------+ // Host mode uses one shared endpoint register for non-interrupt endpoint -static hcd_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; -static hcd_endpoint_t *epx = &ep_pool[0]; // current active endpoint +static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; +static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint // Flags we set by default in sie_ctrl (we add other bits on top) enum { @@ -68,21 +68,21 @@ enum { // //--------------------------------------------------------------------+ -static hcd_endpoint_t *edpt_alloc(void) { +static hw_endpoint_t *edpt_alloc(void) { for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hcd_endpoint_t *ep = &ep_pool[i]; - if (ep->hwep.max_packet_size == 0) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->max_packet_size == 0) { return ep; } } return NULL; } -static hcd_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { +static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hcd_endpoint_t *ep = &ep_pool[i]; - if ((ep->dev_addr == daddr) && (ep->hwep.max_packet_size > 0) && - (ep->hwep.ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->hwep.ep_addr) == 0))) { + hw_endpoint_t *ep = &ep_pool[i]; + if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) && + (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) { return ep; } } @@ -90,7 +90,7 @@ static hcd_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { return NULL; } -// static hcd_endpoint_t* epdt_find_interrupt(uint8_t ) +// static hw_endpoint_t* epdt_find_interrupt(uint8_t ) //--------------------------------------------------------------------+ // @@ -106,17 +106,17 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { return hcd_port_speed_get(0) != tuh_speed_get(dev_addr); } -static void __tusb_irq_path_func(hw_xfer_complete)(hcd_endpoint_t *ep, xfer_result_t xfer_result) { +static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack uint8_t dev_addr = ep->dev_addr; - uint8_t ep_addr = ep->hwep.ep_addr; - uint xferred_len = ep->hwep.xferred_len; - hw_endpoint_reset_transfer(&ep->hwep); + uint8_t ep_addr = ep->ep_addr; + uint xferred_len = ep->xferred_len; + hw_endpoint_reset_transfer(ep); hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); } -static void __tusb_irq_path_func(handle_hwbuf_status_bit)(hcd_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { - const bool done = hw_endpoint_xfer_continue(&ep->hwep, ep_reg, buf_reg); +static void __tusb_irq_path_func(handle_hwbuf_status_bit)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { + const bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg); if (done) { hw_xfer_complete(ep, XFER_RESULT_SUCCESS); } @@ -151,7 +151,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { usb_hw_clear->buf_status = bit; for (uint8_t e = 0; e < USB_MAX_ENDPOINTS; e++) { - hcd_endpoint_t *ep = &ep_pool[e]; + hw_endpoint_t *ep = &ep_pool[e]; if (ep->interrupt_num == i) { io_rw_32 *ep_reg = &usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl; io_rw_32 *buf_reg = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl; @@ -168,7 +168,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } } -// +// All non-interrupt endpoints use shared EPX. // static void edpt_scheduler(void) { // } @@ -208,7 +208,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // only handle setup packet if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { - epx->hwep.xferred_len = 8; + epx->xferred_len = 8; hw_xfer_complete(epx, XFER_RESULT_SUCCESS); } else { // Don't care. Will handle this in buff status @@ -231,21 +231,21 @@ void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { hcd_rp2040_irq(); } -static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { +static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { const uint8_t ep_addr = ep_desc->bEndpointAddress; const uint16_t wMaxPacketSize = tu_edpt_packet_size(ep_desc); const uint8_t transfer_type = ep_desc->bmAttributes.xfer; // const uint8_t bmInterval = ep_desc->bInterval; - ep->hwep.max_packet_size = wMaxPacketSize; - ep->hwep.ep_addr = ep_addr; + 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->hwep.next_pid = 0u; + ep->next_pid = 0u; if (transfer_type != TUSB_XFER_INTERRUPT) { - ep->hwep.dpram_buf = usbh_dpram->epx_data; + ep->dpram_buf = usbh_dpram->epx_data; } else { // from 15 interrupt endpoints pool uint8_t int_idx; @@ -260,9 +260,9 @@ static void hw_endpoint_init(hcd_endpoint_t *ep, uint8_t dev_addr, const tusb_de //------------- dpram buf -------------// // 15x64 last bytes of DPRAM for interrupt endpoint buffers - ep->hwep.dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); + ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hwep.dpram_buf) | + (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) | (uint32_t)((ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; @@ -359,16 +359,16 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { (void)dev_addr; // reset epx if it is currently active with unplugged device - if (epx->hwep.max_packet_size > 0 && epx->dev_addr == dev_addr) { - // if (epx->hwep.active) { + if (epx->max_packet_size > 0 && epx->dev_addr == dev_addr) { + // if (epx->active) { // // need to abort transfer // } - epx->hwep.max_packet_size = 0; + epx->max_packet_size = 0; } for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hcd_endpoint_t *ep = &ep_pool[i]; - if (ep->dev_addr == dev_addr && ep->hwep.max_packet_size > 0) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) { if (ep->interrupt_num) { // disable interrupt endpoint usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num; @@ -378,7 +378,7 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl = 0; } - ep->hwep.max_packet_size = 0; // mark as unused + ep->max_packet_size = 0; // mark as unused } } } @@ -405,7 +405,7 @@ void hcd_int_disable(uint8_t rhport) { bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { (void)rhport; pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); - hcd_endpoint_t *ep = edpt_alloc(); + hw_endpoint_t *ep = edpt_alloc(); TU_ASSERT(ep); hw_endpoint_init(ep, dev_addr, ep_desc); @@ -431,26 +431,26 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value) { } // xfer using epx -static void edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { +static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { if (ep->transfer_type == TUSB_XFER_INTERRUPT) { // For interrupt endpoint control and buffer is already configured // Note: Interrupt is single buffered only io_rw_32 *ep_reg = &usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl; io_rw_32 *buf_reg = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl; - hw_endpoint_xfer_start(&ep->hwep, ep_reg, buf_reg, buffer, ff, total_len); + hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); } else { - const uint8_t ep_num = tu_edpt_number(ep->hwep.ep_addr); - const tusb_dir_t ep_dir = tu_edpt_dir(ep->hwep.ep_addr); + const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); // ep control - const uint32_t dpram_offset = hw_data_offset(ep->hwep.dpram_buf); + 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; usbh_dpram->epx_ctrl = ep_ctrl; io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - hw_endpoint_xfer_start(&ep->hwep, ep_reg, buf_reg, buffer, ff, total_len); + hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); // addr control usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); @@ -467,13 +467,13 @@ static void edpt_xfer(hcd_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16 bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void)rhport; - hcd_endpoint_t *ep = edpt_find(dev_addr, ep_addr); + hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); TU_ASSERT(ep); // Control endpoint can change direction 0x00 <-> 0x80 - if (ep_addr != ep->hwep.ep_addr) { - ep->hwep.ep_addr = ep_addr; - ep->hwep.next_pid = 1; // data and status stage start with DATA1 + if (ep_addr != ep->ep_addr) { + ep->ep_addr = ep_addr; + ep->next_pid = 1; // data and status stage start with DATA1 } edpt_xfer(ep, buffer, NULL, buflen); @@ -497,13 +497,13 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet usbh_dpram->setup_packet[i] = setup_packet[i]; } - hcd_endpoint_t *ep = edpt_find(dev_addr, 0x00); + hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); TU_ASSERT(ep); - ep->hwep.ep_addr = 0; // setup is OUT - ep->hwep.remaining_len = 8; - ep->hwep.xferred_len = 0; - ep->hwep.active = true; + ep->ep_addr = 0; // setup is OUT + ep->remaining_len = 8; + ep->xferred_len = 0; + ep->active = true; epx = ep; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index cbb2290c18..46bd727c8b 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -65,11 +65,18 @@ typedef struct hw_endpoint { uint8_t ep_addr; uint8_t next_pid; bool active; // transferring data + 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 pending; // Transfer scheduled but not active +#endif + +#if CFG_TUH_ENABLED + uint8_t dev_addr; + uint8_t interrupt_num; // 1-15 for interrupt endpoints + uint8_t transfer_type; + bool need_pre; // need preamble for low speed device behind full speed hub #endif uint16_t max_packet_size; // max packet size also indicates configured @@ -85,15 +92,6 @@ typedef struct hw_endpoint { } hw_endpoint_t; -// Host controller endpoint -typedef struct { - hw_endpoint_t hwep; - uint8_t dev_addr; - uint8_t interrupt_num; // 1-15 for interrupt endpoints - uint8_t transfer_type; - bool need_pre; // need preamble for low speed device behind full speed hub -} hcd_endpoint_t; - #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX extern volatile uint32_t e15_last_sof; #endif From 0c6fa9bd7f4d2619a538fcfd866ef6150fa2adeb Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 19 Mar 2026 22:59:22 +0700 Subject: [PATCH 07/29] Add EPX transfer scheduling when hcd_edpt_xfer() is called while epx is busy --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 105 +++++++++++++++++-- src/portable/raspberrypi/rp2040/rp2040_usb.c | 12 ++- test/hil/hil_test.py | 11 +- 3 files changed, 116 insertions(+), 12 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 0a8dbe11a0..b5c4422e25 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -106,6 +106,9 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { return hcd_port_speed_get(0) != tuh_speed_get(dev_addr); } +// forward declaration +static void __tusb_irq_path_func(edpt_schedule_next)(void); + static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack uint8_t dev_addr = ep->dev_addr; @@ -113,6 +116,11 @@ static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_resul uint xferred_len = ep->xferred_len; hw_endpoint_reset_transfer(ep); hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); + + // Schedule next pending EPX transfer (only for non-interrupt endpoints) + if (ep == epx) { + edpt_schedule_next(); + } } static void __tusb_irq_path_func(handle_hwbuf_status_bit)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { @@ -169,8 +177,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } // All non-interrupt endpoints use shared EPX. -// static void edpt_scheduler(void) { -// } +// Forward declared above hw_xfer_complete, defined after edpt_xfer below. static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { const uint32_t status = usb_hw->ints; @@ -206,7 +213,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { if (status & USB_INTS_TRANS_COMPLETE_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS; - // only handle setup packet + // only handle a setup packet if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { epx->xferred_len = 8; hw_xfer_complete(epx, XFER_RESULT_SUCCESS); @@ -215,6 +222,30 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } } + // if (status & USB_INTS_EP_STALL_NAK_BITS) { + // const uint32_t ep_stall_nak = usb_hw->ep_status_stall_nak; + // usb_hw->ep_status_stall_nak = ep_stall_nak; // clear by writing back (WC) + // + // // Preempt non-control EPX bulk transfer when a different ep is pending + // if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { + // for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + // hw_endpoint_t *ep = &ep_pool[i]; + // if (ep->pending && ep != epx) { + // // Stop current transaction + // usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; + // + // // Mark current EPX as pending to resume later + // epx->pending = 1; + // epx->active = false; + // + // // Start the next pending transfer + // edpt_schedule_next(); + // break; + // } + // } + // } + // } + if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; } @@ -250,7 +281,7 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_des // from 15 interrupt endpoints pool uint8_t int_idx; for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) { - if (!tu_bit_test(usb_hw_set->int_ep_ctrl, 1 + int_idx)) { + if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) { ep->interrupt_num = int_idx + 1; break; } @@ -313,6 +344,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { USB_INTE_HOST_RESUME_BITS | USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | + // USB_INTE_EP_STALL_NAK_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS | USB_INTE_ERROR_DATA_SEQ_BITS ; @@ -369,6 +401,8 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { hw_endpoint_t *ep = &ep_pool[i]; if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) { + ep->pending = 0; // clear any pending transfer + if (ep->interrupt_num) { // disable interrupt endpoint usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num; @@ -442,10 +476,18 @@ static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_ const uint8_t ep_num = tu_edpt_number(ep->ep_addr); const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); + // RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode. + // The buffer selector toggles even in single-buffered mode, so the previous transfer's status + // may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here. +#if defined(PICO_RP2040) && PICO_RP2040 == 1 + usbh_dpram->epx_buf_ctrl = 0; +#endif + // 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; + ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset /*| + (1u << 16)*/; // INTERRUPT_ON_NAK usbh_dpram->epx_ctrl = ep_ctrl; io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; @@ -464,6 +506,40 @@ static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_ } } +// Schedule next pending EPX transfer from ISR context +static void __tusb_irq_path_func(edpt_schedule_next)(void) { + // EPX may already be active if the completion callback started a new transfer + if (epx->active) return; + + for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->pending == 0) continue; + + if (ep->pending == 2) { + // Pending setup: DPRAM already has the setup packet + ep->ep_addr = 0; + ep->remaining_len = 8; + ep->xferred_len = 0; + ep->active = true; + ep->pending = 0; + + epx = ep; + usb_hw->dev_addr_ctrl = ep->dev_addr; + + const uint32_t sie_ctrl = USB_SIE_CTRL_SEND_SETUP_BITS | + (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sie_ctrl); + } else { + // Pending data transfer: preserve partial progress from preemption + uint16_t prev_xferred = ep->xferred_len; + ep->pending = 0; + edpt_xfer(ep, ep->user_buf, NULL, ep->remaining_len); + epx->xferred_len += prev_xferred; // restore partial progress + } + return; // start only one transfer + } +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void)rhport; @@ -476,6 +552,14 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b ep->next_pid = 1; // data and status stage start with DATA1 } + // If EPX is busy with another transfer, mark as pending + if (ep->transfer_type != TUSB_XFER_INTERRUPT && epx->active) { + ep->user_buf = buffer; + ep->remaining_len = buflen; + ep->pending = 1; + return true; + } + edpt_xfer(ep, buffer, NULL, buflen); return true; @@ -492,7 +576,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) { (void)rhport; - // Copy data into setup packet buffer + // Copy data into setup packet buffer (usbh only schedules one setup at a time) for (uint8_t i = 0; i < 8; i++) { usbh_dpram->setup_packet[i] = setup_packet[i]; } @@ -500,7 +584,14 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); TU_ASSERT(ep); - ep->ep_addr = 0; // setup is OUT + ep->ep_addr = 0; // setup is OUT + + // If EPX is busy, mark as pending setup (DPRAM already has the packet) + if (epx->active) { + ep->pending = 2; + return true; + } + ep->remaining_len = 8; ep->xferred_len = 0; ep->active = true; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index f2d4800e13..0f1075eda3 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -110,7 +110,17 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an value |= or_mask; if (or_mask & USB_BUF_CTRL_AVAIL) { if (buf_ctrl & USB_BUF_CTRL_AVAIL) { - panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg); + if (is_host) { +#if defined(PICO_RP2040) && PICO_RP2040 == 1 + // RP2040-E4: host buffer selector toggles in single-buffered mode, causing status + // to be written to BUF1 half and leaving stale AVAILABLE in BUF0 half. Clear it. + *buf_ctrl_reg = 0; +#else + panic("buf_ctrl @ 0x%lX already available (host)", (uintptr_t)buf_ctrl_reg); +#endif + } else { + panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg); + } } *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL; diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 41e9fad88b..b3458e59dc 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -475,7 +475,7 @@ def test_host_cdc_msc_hid(board): cdc_devs = [d for d in dev_attached if d.get('is_cdc')] msc_devs = [d for d in dev_attached if d.get('is_msc')] if not cdc_devs and not msc_devs: - return + return 'skipped' port = get_serial_dev(flasher["uid"], None, None, 0) ser = open_serial_dev(port) @@ -568,7 +568,7 @@ def test_host_msc_file_explorer(board): flasher = board['flasher'] msc_devs = [d for d in board['tests'].get('dev_attached', []) if d.get('is_msc')] if not msc_devs: - return + return 'skipped' port = get_serial_dev(flasher["uid"], None, None, 0) ser = open_serial_dev(port) @@ -1113,8 +1113,11 @@ def test_example(board, f1, example): ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, fw_name) if ret.returncode == 0: try: - globals()[f'test_{example.replace("/", "_")}'](board) - print(' OK', end='') + tret = globals()[f'test_{example.replace("/", "_")}'](board) + if tret == 'skipped': + print(f' {STATUS_SKIPPED}', end='') + else: + print(' OK', end='') break except Exception as e: if i == max_rety - 1: From f7d1c10b73e2f549d9727b4a8a11c1ca58906881 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 20 Mar 2026 00:50:55 +0700 Subject: [PATCH 08/29] Add support for EPX preemption on RP2350 during NAK conditions --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 112 +++++++++++++++---- 1 file changed, 88 insertions(+), 24 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index b5c4422e25..f3153d0e92 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -108,6 +108,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { // forward declaration static void __tusb_irq_path_func(edpt_schedule_next)(void); +TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value); +static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack @@ -222,29 +224,79 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } } - // if (status & USB_INTS_EP_STALL_NAK_BITS) { - // const uint32_t ep_stall_nak = usb_hw->ep_status_stall_nak; - // usb_hw->ep_status_stall_nak = ep_stall_nak; // clear by writing back (WC) - // - // // Preempt non-control EPX bulk transfer when a different ep is pending - // if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { - // for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - // hw_endpoint_t *ep = &ep_pool[i]; - // if (ep->pending && ep != epx) { - // // Stop current transaction - // usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; - // - // // Mark current EPX as pending to resume later - // epx->pending = 1; - // epx->active = false; - // - // // Start the next pending transfer - // edpt_schedule_next(); - // break; - // } - // } - // } - // } +#if defined(PICO_RP2350) && PICO_RP2350 == 1 + if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { + // RP2350: EPX transfer stopped due to NAK from device. + // Clear EPX_STOPPED_ON_NAK status (WC) + usb_hw->nak_poll |= USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; + + bool preempted = false; + + // Only preempt non-control endpoints + if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { + // Find the next pending transfer (different from the current epx) + for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->pending && ep != epx) { + // NAK means no data transferred. Restore remaining_len from buffer control + // so edpt_schedule_next can properly resume this transfer later. + const uint16_t buf0_len = usbh_dpram->epx_buf_ctrl & USB_BUF_CTRL_LEN_MASK; + epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); + epx->next_pid ^= 1u; // undo PID toggle from hwbuf_prepare + if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { + epx->user_buf -= buf0_len; // undo buffer advance for OUT + } + + // Mark current EPX as pending to resume later + epx->pending = 1; + epx->active = false; + + // Clear EPX buffer control - AVAILABLE is still set from the NAK'd transfer + usbh_dpram->epx_buf_ctrl = 0; + + // Start the found pending transfer directly + if (ep->pending == 2) { + // Pending setup: DPRAM already has the setup packet + ep->ep_addr = 0; + ep->remaining_len = 8; + ep->xferred_len = 0; + ep->active = true; + ep->pending = 0; + + epx = ep; + usb_hw->dev_addr_ctrl = ep->dev_addr; + + const uint32_t sc = USB_SIE_CTRL_SEND_SETUP_BITS | + (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sc); + } else { + // Pending data transfer: preserve partial progress + uint16_t prev_xferred = ep->xferred_len; + ep->pending = 0; + edpt_xfer(ep, ep->user_buf, NULL, ep->remaining_len); + epx->xferred_len += prev_xferred; + } + + preempted = true; + break; + } + } + } + + if (!preempted && epx->active) { + // No preemption needed: disable stop-on-NAK and restart the transaction. + // Buffer control still has AVAILABLE set, just re-trigger START_TRANS. + uint32_t nak_poll = usb_hw->nak_poll; + nak_poll &= ~USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + usb_hw->nak_poll = nak_poll; + + const tusb_dir_t ep_dir = tu_edpt_dir(epx->ep_addr); + const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | + (epx->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sie_ctrl); + } + } +#endif if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; @@ -344,10 +396,14 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { USB_INTE_HOST_RESUME_BITS | USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | - // USB_INTE_EP_STALL_NAK_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS | USB_INTE_ERROR_DATA_SEQ_BITS ; +#if defined(PICO_RP2350) && PICO_RP2350 == 1 + // RP2350: Enable EPX stopped-on-NAK interrupt (feature is enabled dynamically when transfers are pending) + usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS; +#endif + return true; } @@ -557,6 +613,10 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b ep->user_buf = buffer; ep->remaining_len = buflen; ep->pending = 1; +#if defined(PICO_RP2350) && PICO_RP2350 == 1 + // RP2350: Enable stop-on-NAK so current EPX transfer can be preempted + usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; +#endif return true; } @@ -589,6 +649,10 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet // If EPX is busy, mark as pending setup (DPRAM already has the packet) if (epx->active) { ep->pending = 2; +#if defined(PICO_RP2350) && PICO_RP2350 == 1 + // RP2350: Enable stop-on-NAK so current EPX transfer can be preempted + usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; +#endif return true; } From 99d70e2990a04ca7874fe6dc3ff129cfe67c9cc7 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 20 Mar 2026 18:24:42 +0700 Subject: [PATCH 09/29] Add dd command to MSC file explorer for sector read and speed reporting, adjust CLI configuration and HIL tests --- examples/host/msc_file_explorer/src/msc_app.c | 75 ++++++++++++++++++- .../boards/ch32v203g_r0_1v0/board.cmake | 1 + test/hil/hil_test.py | 60 ++++++++++----- 3 files changed, 117 insertions(+), 19 deletions(-) diff --git a/examples/host/msc_file_explorer/src/msc_app.c b/examples/host/msc_file_explorer/src/msc_app.c index 238af5431a..21ff42726c 100644 --- a/examples/host/msc_file_explorer/src/msc_app.c +++ b/examples/host/msc_file_explorer/src/msc_app.c @@ -46,7 +46,7 @@ #define CLI_RX_BUFFER_SIZE 16 #define CLI_CMD_BUFFER_SIZE 64 #define CLI_HISTORY_SIZE 32 -#define CLI_BINDING_COUNT 8 +#define CLI_BINDING_COUNT 9 static EmbeddedCli *_cli; static CLI_UINT cli_buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)]; @@ -56,7 +56,11 @@ static CFG_TUH_MEM_SECTION FATFS fatfs[CFG_TUH_DEVICE_MAX]; // for simplicity on static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX]; static CFG_TUH_MEM_SECTION FIL file1, file2; -static CFG_TUH_MEM_SECTION uint8_t rw_buf[512]; + +#ifndef CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE +#define CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE 4096 +#endif +static CFG_TUH_MEM_SECTION uint8_t rw_buf[CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE]; // define the buffer to be place in USB/DMA memory with correct alignment/cache line size CFG_TUH_MEM_SECTION static struct { @@ -279,6 +283,7 @@ DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */ void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context); void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context); void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context); +void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context); void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context); void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context); void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context); @@ -316,6 +321,9 @@ bool cli_init(void) { embeddedCliAddBinding(_cli, (CliCommandBinding){"cp", "Usage: cp SOURCE DEST\r\n\tCopy SOURCE to DEST.", true, NULL, cli_cmd_cp}); + embeddedCliAddBinding(_cli, (CliCommandBinding){"dd", "Usage: dd [COUNT]\r\n\t" "Read COUNT sectors (default 1024) and report speed.", true, NULL, + cli_cmd_dd}); + embeddedCliAddBinding(_cli, (CliCommandBinding){"ls", "Usage: ls [DIR]...\r\n\tList information about the FILEs (the " "current directory by default).", @@ -339,6 +347,69 @@ bool cli_init(void) { return true; } +void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context) { + (void)cli; + (void)context; + + uint32_t count = 1024; // default sectors to read + if (embeddedCliGetTokenCount(args) >= 1) { + count = (uint32_t)atoi(embeddedCliGetToken(args, 1)); + if (count == 0) { + count = 1024; + } + } + + // find first mounted MSC device + uint8_t dev_addr = 0; + for (uint8_t i = 1; i <= CFG_TUH_DEVICE_MAX; i++) { + if (tuh_msc_mounted(i)) { + dev_addr = i; + break; + } + } + if (dev_addr == 0) { + printf("no MSC device mounted\r\n"); + return; + } + + const uint8_t lun = 0; + const uint32_t block_size = tuh_msc_get_block_size(dev_addr, lun); + const uint32_t block_count = tuh_msc_get_block_count(dev_addr, lun); + if (count > block_count) { + count = block_count; + } + + const uint16_t sectors_per_xfer = (uint16_t)(sizeof(rw_buf) / block_size); + const uint32_t xfer_count = (count + sectors_per_xfer - 1) / sectors_per_xfer; + + printf("dd: reading %" PRIu32 " sectors (%" PRIu32 " bytes), %u sectors/xfer ...\r\n", + count, count * block_size, sectors_per_xfer); + + const uint32_t start_ms = tusb_time_millis_api(); + const uint8_t pdrv = dev_addr - 1; + + for (uint32_t i = 0; i < count; i += sectors_per_xfer) { + const uint16_t n = (uint16_t)((count - i < sectors_per_xfer) ? (count - i) : sectors_per_xfer); + _disk_busy[pdrv] = true; + tuh_msc_read10(dev_addr, lun, rw_buf, i, n, disk_io_complete, 0); + wait_for_disk_io(pdrv); + } + + const uint32_t elapsed_ms = tusb_time_millis_api() - start_ms; + const uint32_t total_data = count * block_size; + // each SCSI transaction has 31-byte CBW + data + 13-byte CSW + const uint32_t total_bus = total_data + xfer_count * (31 + 13); + + if (elapsed_ms > 0) { + const uint32_t data_kbs = total_data / elapsed_ms; // KB/s (bytes/ms = KB/s) + const uint32_t bus_kbs = total_bus / elapsed_ms; + printf("dd: %" PRIu32 " bytes in %" PRIu32 " ms = %" PRIu32 " KB/s (bus %" PRIu32 " KB/s)\r\n", + total_data, elapsed_ms, data_kbs, bus_kbs); + } else { + printf("dd: %" PRIu32 " bytes in <1 ms\r\n", total_data); + } +} + void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context) { (void)cli; (void)context; diff --git a/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake b/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake index ecb8b378fb..819a1ba1e3 100644 --- a/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake +++ b/hw/bsp/ch32v20x/boards/ch32v203g_r0_1v0/board.cmake @@ -9,5 +9,6 @@ function(update_board TARGET) target_compile_definitions(${TARGET} PUBLIC SYSCLK_FREQ_144MHz_HSI=144000000 CFG_EXAMPLE_MSC_DUAL_READONLY + CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE=1024 ) endfunction() diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index b3458e59dc..f8b43430d1 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -538,28 +538,28 @@ def test_host_cdc_msc_hid(board): def rand_ascii(length): return "".join(random.choices(string.ascii_letters + string.digits, k=length)).encode("ascii") - sizes = [8, 32, 64, 128] - for size in sizes: - test_data = rand_ascii(size) - ser.reset_input_buffer() + packet_size = 64 - # Write byte-by-byte with delay to avoid UART overrun - for b in test_data: - ser.write(bytes([b])) - ser.flush() - time.sleep(0.001) - - # Read echo back with timeout + # Echo test: 1KB random data, write random 1-packet_size chunks, only write next once echo matched + echo_len = 1024 + echo_data = rand_ascii(echo_len) + ser.reset_input_buffer() + offset = 0 + while offset < echo_len: + chunk_size = min(random.randint(1, packet_size), echo_len - offset) + ser.write(echo_data[offset:offset + chunk_size]) + ser.flush() + # wait until this chunk is echoed back echo = b'' t = 5.0 - while t > 0 and len(echo) < size: - rd = ser.read(max(1, ser.in_waiting)) + while t > 0 and len(echo) < chunk_size: + rd = ser.read(chunk_size - len(echo)) if rd: echo += rd - time.sleep(0.05) - t -= 0.05 - assert echo == test_data, (f'CDC echo wrong data ({size} bytes):\n' - f' expected: {test_data}\n received: {echo}') + expected = echo_data[offset:offset + chunk_size] + assert echo == expected, (f'CDC echo mismatch at offset {offset} ({chunk_size} bytes):\n' + f' expected: {expected}\n received: {echo}') + offset += chunk_size ser.close() @@ -620,6 +620,32 @@ def test_host_msc_file_explorer(board): f' received: {resp_text}') print('README.TXT matched ', end='') + # MSC throughput test: send dd command to read sectors + time.sleep(0.5) + ser.reset_input_buffer() + for ch in 'dd 1024\r': + ser.write(ch.encode()) + ser.flush() + time.sleep(0.002) + + # Read dd output until prompt + resp = b'' + t = 30.0 + while t > 0: + rd = ser.read(max(1, ser.in_waiting)) + if rd: + resp += rd + if b'KB/s' in resp and b'>' in resp: + break + time.sleep(0.05) + t -= 0.05 + + resp_text = resp.decode('utf-8', errors='ignore') + for line in resp_text.splitlines(): + if 'KB/s' in line: + print(f'{line.strip()} ', end='') + break + ser.close() From f7cf6bc581c514984d3513bea8538e33e7d726d8 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 20 Mar 2026 19:36:57 +0700 Subject: [PATCH 10/29] clean up --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index f3153d0e92..3c250ae7fb 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -30,11 +30,15 @@ #if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421 #include "pico.h" -#include "rp2040_usb.h" + +#if defined(PICO_RP2350) && PICO_RP2350 == 1 +#define HAS_STOP_EPX_ON_NAK +#endif //--------------------------------------------------------------------+ // INCLUDE //--------------------------------------------------------------------+ +#include "rp2040_usb.h" #include "osal/osal.h" #include "host/hcd.h" @@ -224,11 +228,11 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } } -#if defined(PICO_RP2350) && PICO_RP2350 == 1 +#ifdef HAS_STOP_EPX_ON_NAK if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { - // RP2350: EPX transfer stopped due to NAK from device. + // EPX transfer stopped due to NAK from the device. // Clear EPX_STOPPED_ON_NAK status (WC) - usb_hw->nak_poll |= USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; + usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; bool preempted = false; @@ -286,9 +290,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { if (!preempted && epx->active) { // No preemption needed: disable stop-on-NAK and restart the transaction. // Buffer control still has AVAILABLE set, just re-trigger START_TRANS. - uint32_t nak_poll = usb_hw->nak_poll; - nak_poll &= ~USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; - usb_hw->nak_poll = nak_poll; + usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; const tusb_dir_t ep_dir = tu_edpt_dir(epx->ep_addr); const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | @@ -399,8 +401,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { USB_INTE_ERROR_RX_TIMEOUT_BITS | USB_INTE_ERROR_DATA_SEQ_BITS ; -#if defined(PICO_RP2350) && PICO_RP2350 == 1 - // RP2350: Enable EPX stopped-on-NAK interrupt (feature is enabled dynamically when transfers are pending) +#ifdef HAS_STOP_EPX_ON_NAK usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS; #endif @@ -613,8 +614,8 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b ep->user_buf = buffer; ep->remaining_len = buflen; ep->pending = 1; -#if defined(PICO_RP2350) && PICO_RP2350 == 1 - // RP2350: Enable stop-on-NAK so current EPX transfer can be preempted +#ifdef HAS_STOP_EPX_ON_NAK + // Enable stop-on-NAK to round-robin when NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; #endif return true; @@ -649,8 +650,8 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet // If EPX is busy, mark as pending setup (DPRAM already has the packet) if (epx->active) { ep->pending = 2; -#if defined(PICO_RP2350) && PICO_RP2350 == 1 - // RP2350: Enable stop-on-NAK so current EPX transfer can be preempted +#ifdef HAS_STOP_EPX_ON_NAK + // Enable stop-on-NAK to round-robin when NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; #endif return true; @@ -661,9 +662,7 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet ep->active = true; epx = ep; - - // Set device address - usb_hw->dev_addr_ctrl = dev_addr; + usb_hw->dev_addr_ctrl = dev_addr; // Set device address // Set pre if we are a low speed device on full speed hub const uint32_t sie_ctrl = USB_SIE_CTRL_SEND_SETUP_BITS | (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); From 3c2627e7390dc9145329732900f0c0de6e886cc0 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 23 Mar 2026 15:43:47 +0700 Subject: [PATCH 11/29] update host msc hil test --- examples/host/cdc_msc_hid/src/msc_app.c | 4 +-- .../host/cdc_msc_hid_freertos/src/msc_app.c | 6 ++-- examples/host/msc_file_explorer/src/msc_app.c | 6 ++-- test/hil/hil_test.py | 30 ++++++++++++++----- test/hil/tinyusb.json | 2 +- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/examples/host/cdc_msc_hid/src/msc_app.c b/examples/host/cdc_msc_hid/src/msc_app.c index dd4e22d7f5..4a85e46d74 100644 --- a/examples/host/cdc_msc_hid/src/msc_app.c +++ b/examples/host/cdc_msc_hid/src/msc_app.c @@ -47,8 +47,8 @@ static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun); uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun); - printf("Disk Size: %" PRIu32 " MB\r\n", block_count / ((1024*1024)/block_size)); - printf("Block Count = %" PRIu32 ", Block Size: %" PRIu32 "\r\n", block_count, block_size); + printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n", + block_count, block_size, block_count / ((1024 * 1024) / block_size)); return true; } diff --git a/examples/host/cdc_msc_hid_freertos/src/msc_app.c b/examples/host/cdc_msc_hid_freertos/src/msc_app.c index fa864c364a..17df11951f 100644 --- a/examples/host/cdc_msc_hid_freertos/src/msc_app.c +++ b/examples/host/cdc_msc_hid_freertos/src/msc_app.c @@ -47,14 +47,14 @@ static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const } // Print out Vendor ID, Product ID and Rev - printf("%.8s %.16s rev %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, scsi_resp.inquiry.product_rev); + printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, scsi_resp.inquiry.product_rev); // Get capacity of device uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun); uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun); - printf("Disk Size: %" PRIu32 " MB\r\n", block_count / ((1024 * 1024) / block_size)); - printf("Block Count = %" PRIu32 ", Block Size: %" PRIu32 "\r\n", block_count, block_size); + printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n", + block_count, block_size, block_count / ((1024 * 1024) / block_size)); return true; } diff --git a/examples/host/msc_file_explorer/src/msc_app.c b/examples/host/msc_file_explorer/src/msc_app.c index 21ff42726c..c7cc366b54 100644 --- a/examples/host/msc_file_explorer/src/msc_app.c +++ b/examples/host/msc_file_explorer/src/msc_app.c @@ -118,15 +118,15 @@ static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t } // Print out Vendor ID, Product ID and Rev - printf("%.8s %.16s rev %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, + printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, scsi_resp.inquiry.product_rev); // Get capacity of device const uint32_t block_count = tuh_msc_get_block_count(dev_addr, cbw->lun); const uint32_t block_size = tuh_msc_get_block_size(dev_addr, cbw->lun); - printf("Disk Size: %" PRIu32 " MB\r\n", block_count / ((1024 * 1024) / block_size)); - // printf("Block Count = %lu, Block Size: %lu\r\n", block_count, block_size); + printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n", + block_count, block_size, block_count / ((1024 * 1024) / block_size)); // For simplicity: we only mount 1 LUN per device const uint8_t drive_num = dev_addr - 1; diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index f8b43430d1..4b17bed547 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -456,17 +456,30 @@ def test_host_device_info(board): return 0 -def print_msc_info(lines): - """Print MSC inquiry and disk size on a single line""" +def check_msc_info(lines, msc_devs): + """Print MSC info and verify block_count/block_size against config""" inquiry = '' disk_size = '' for l in lines: - if re.match(r'^[A-Za-z].*\s+rev\s+', l): + if re.match(r'^[A-Za-z].*\s+(rev\s+|[0-9])', l) and 'Disk Size' not in l: inquiry = l.strip() if 'Disk Size' in l: disk_size = l.strip() if inquiry or disk_size: print(f'\r\n {inquiry} {disk_size} ', end='') + # Verify block_count and block_size from "Disk Size: COUNT SIZE-byte blocks: N MB" + if disk_size and msc_devs: + m = re.match(r'Disk Size:\s+(\d+)\s+(\d+)-byte blocks', disk_size) + if m: + actual_count = int(m.group(1)) + actual_size = int(m.group(2)) + for dev in msc_devs: + exp_count = dev.get('block_count') + exp_size = dev.get('block_size') + if exp_count and actual_count == exp_count: + assert actual_size == exp_size, ( + f'MSC block_size mismatch: expected {exp_size}, got {actual_size}') + break def test_host_cdc_msc_hid(board): @@ -525,7 +538,7 @@ def test_host_cdc_msc_hid(board): if msc_devs: assert b'MassStorage device is mounted' in data, 'MSC device not mounted on host' assert b'Disk Size' in data, 'MSC Disk Size not reported' - print_msc_info(lines) + check_msc_info(lines, msc_devs) # CDC echo test via flasher serial if not cdc_devs: @@ -533,6 +546,7 @@ def test_host_cdc_msc_hid(board): return time.sleep(2) + ser.read(ser.in_waiting) ser.reset_input_buffer() def rand_ascii(length): @@ -540,7 +554,7 @@ def rand_ascii(length): packet_size = 64 - # Echo test: 1KB random data, write random 1-packet_size chunks, only write next once echo matched + # Echo test: write random 1-packet_size chunks, wait for echo before sending next echo_len = 1024 echo_data = rand_ascii(echo_len) ser.reset_input_buffer() @@ -551,8 +565,8 @@ def rand_ascii(length): ser.flush() # wait until this chunk is echoed back echo = b'' - t = 5.0 - while t > 0 and len(echo) < chunk_size: + t_end = time.monotonic() + 5.0 + while time.monotonic() < t_end and len(echo) < chunk_size: rd = ser.read(chunk_size - len(echo)) if rd: echo += rd @@ -591,7 +605,7 @@ def test_host_msc_file_explorer(board): timeout -= 0.1 assert b'Disk Size' in data, 'MSC device not mounted' lines = data.decode('utf-8', errors='ignore').splitlines() - print_msc_info(lines) + check_msc_info(lines, msc_devs) # Send "cat README.TXT" and read response time.sleep(1) diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json index b4c6aaefef..37e98a52f7 100644 --- a/test/hil/tinyusb.json +++ b/test/hil/tinyusb.json @@ -182,7 +182,7 @@ "dev_attached": [ {"vid_pid": "0403_6001", "serial": "0", "is_cdc": true}, {"vid_pid": "058f_6387", "serial": "A8BEE062633D", "is_msc": true, - "msc_disk_size": 3730, "msc_inquiry": "Generic Flash Disk rev 8.07"} + "block_size": 512, "block_count": 7639040, "msc_inquiry": "Generic Flash Disk 8.07"} ] }, "flasher": { From 15eef94df0e759adf14697197a89dddeb8fc3aeb Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 23 Mar 2026 15:49:58 +0700 Subject: [PATCH 12/29] add rp2040 sof + stop_trans on nak. increase nak_poll fs/ls delay to 300 us to prevent xfer is ack while stopping. --- examples/host/cdc_msc_hid/src/msc_app.c | 2 +- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 163 ++++++++++++------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 10 +- 3 files changed, 105 insertions(+), 70 deletions(-) diff --git a/examples/host/cdc_msc_hid/src/msc_app.c b/examples/host/cdc_msc_hid/src/msc_app.c index 4a85e46d74..8181bdcb91 100644 --- a/examples/host/cdc_msc_hid/src/msc_app.c +++ b/examples/host/cdc_msc_hid/src/msc_app.c @@ -41,7 +41,7 @@ static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const } // Print out Vendor ID, Product ID and Rev - printf("%.8s %.16s rev %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev); + printf("%.8s %.16s %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev); // Get capacity of device uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun); diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 3c250ae7fb..9ee15d343f 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -185,6 +185,54 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { // All non-interrupt endpoints use shared EPX. // Forward declared above hw_xfer_complete, defined after edpt_xfer below. +// Save current EPX context, mark pending, switch to next_ep +static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *next_ep) { + const uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; + const uint16_t buf0_len = buf_ctrl & USB_BUF_CTRL_LEN_MASK; + epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); + epx->next_pid = (buf_ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; + if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { + epx->user_buf -= buf0_len; + } + epx->pending = 1; + epx->active = false; + usbh_dpram->epx_buf_ctrl = 0; + + if (next_ep->pending == 2) { + next_ep->ep_addr = 0; + next_ep->remaining_len = 8; + next_ep->xferred_len = 0; + next_ep->active = true; + next_ep->pending = 0; + epx = next_ep; + usb_hw->dev_addr_ctrl = next_ep->dev_addr; + const uint32_t sc = USB_SIE_CTRL_SEND_SETUP_BITS | + (next_ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sc); + } else { + uint16_t prev_xferred = next_ep->xferred_len; + next_ep->pending = 0; + edpt_xfer(next_ep, next_ep->user_buf, NULL, next_ep->remaining_len); + epx->xferred_len += prev_xferred; + } +} + +// Round-robin find next pending ep after current epx +static hw_endpoint_t *__tusb_irq_path_func(epx_find_pending)(void) { + const uint start = (uint)(epx - &ep_pool[0]) + 1; + for (uint i = start; i < TU_ARRAY_SIZE(ep_pool); i++) { + if (ep_pool[i].pending) { + return &ep_pool[i]; + } + } + for (uint i = 0; i < start - 1; i++) { + if (ep_pool[i].pending) { + return &ep_pool[i]; + } + } + return NULL; +} + static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { const uint32_t status = usb_hw->ints; @@ -229,73 +277,51 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } #ifdef HAS_STOP_EPX_ON_NAK + // RP2350: hardware stops EPX on NAK automatically if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { - // EPX transfer stopped due to NAK from the device. - // Clear EPX_STOPPED_ON_NAK status (WC) usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; - bool preempted = false; - - // Only preempt non-control endpoints + hw_endpoint_t *next_ep = NULL; if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { - // Find the next pending transfer (different from the current epx) - for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hw_endpoint_t *ep = &ep_pool[i]; - if (ep->pending && ep != epx) { - // NAK means no data transferred. Restore remaining_len from buffer control - // so edpt_schedule_next can properly resume this transfer later. - const uint16_t buf0_len = usbh_dpram->epx_buf_ctrl & USB_BUF_CTRL_LEN_MASK; - epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); - epx->next_pid ^= 1u; // undo PID toggle from hwbuf_prepare - if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { - epx->user_buf -= buf0_len; // undo buffer advance for OUT - } - - // Mark current EPX as pending to resume later - epx->pending = 1; - epx->active = false; - - // Clear EPX buffer control - AVAILABLE is still set from the NAK'd transfer - usbh_dpram->epx_buf_ctrl = 0; - - // Start the found pending transfer directly - if (ep->pending == 2) { - // Pending setup: DPRAM already has the setup packet - ep->ep_addr = 0; - ep->remaining_len = 8; - ep->xferred_len = 0; - ep->active = true; - ep->pending = 0; - - epx = ep; - usb_hw->dev_addr_ctrl = ep->dev_addr; - - const uint32_t sc = USB_SIE_CTRL_SEND_SETUP_BITS | - (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sc); - } else { - // Pending data transfer: preserve partial progress - uint16_t prev_xferred = ep->xferred_len; - ep->pending = 0; - edpt_xfer(ep, ep->user_buf, NULL, ep->remaining_len); - epx->xferred_len += prev_xferred; - } - - preempted = true; - break; - } - } + next_ep = epx_find_pending(); } - - if (!preempted && epx->active) { - // No preemption needed: disable stop-on-NAK and restart the transaction. - // Buffer control still has AVAILABLE set, just re-trigger START_TRANS. + if (next_ep) { + epx_switch_ep(next_ep); + } else { + // No preemption: disable stop-on-NAK, restart current transfer usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; - - const tusb_dir_t ep_dir = tu_edpt_dir(epx->ep_addr); - const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | - (epx->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sie_ctrl); + if (epx->active) { + const tusb_dir_t ep_dir = tu_edpt_dir(epx->ep_addr); + const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | + (epx->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sie_ctrl); + } + } + } +#else + // RP2040: on SOF, stop and switch if there's a pending ep + if (status & USB_INTS_HOST_SOF_BITS) { + (void) usb_hw->sof_rd; // clear SOF by reading SOF_RD + if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { + hw_endpoint_t *next_ep = epx_find_pending(); + if (next_ep) { + usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; + while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} + busy_wait_at_least_cycles(12); + if (usb_hw->buf_status & 1u) { + usb_hw->nak_poll = USB_NAK_POLL_RESET; + handle_hwbuf_status(); + } else { + epx_switch_ep(next_ep); + } + } else { + usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; + usb_hw->nak_poll = USB_NAK_POLL_RESET; + } + } else if (!epx_find_pending()) { + // EPX is on control endpoint or inactive — disable SOF if nothing pending + usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; + usb_hw->nak_poll = USB_NAK_POLL_RESET; } } #endif @@ -615,8 +641,14 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b ep->remaining_len = buflen; ep->pending = 1; #ifdef HAS_STOP_EPX_ON_NAK - // Enable stop-on-NAK to round-robin when NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; +#else + // Only enable SOF preemption for non-control endpoints + if (tu_edpt_number(epx->ep_addr) != 0) { + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | + (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + } #endif return true; } @@ -651,8 +683,13 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet if (epx->active) { ep->pending = 2; #ifdef HAS_STOP_EPX_ON_NAK - // Enable stop-on-NAK to round-robin when NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; +#else + if (tu_edpt_number(epx->ep_addr) != 0) { + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | + (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + } #endif return true; } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 0f1075eda3..66e579c391 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -124,12 +124,10 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an } *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL; - // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, we need to - // wait at least 1/48 mhz (usb clock), 12 cycles should be good for 48*12Mhz = 576Mhz. - // Don't need delay in host mode as host is in charge - if (!is_host) { - busy_wait_at_least_cycles(12); - } + // 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. + // Host also needs this for continuation buffers in multi-packet transfers. + busy_wait_at_least_cycles(12); } } From 09197ed27929d03a7f16e3d67dc76fa312948316 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 24 Mar 2026 16:05:31 +0700 Subject: [PATCH 13/29] handle buf_status in per buffer basic (INTERRUPT_PER_BUFFER), this allows us to sync/move half data payload instead of waiting for pair complete. Refactor endpoint control and buffer handling functions for clarity and efficiency. --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 130 ++++++++-------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 154 ++++++------------- src/portable/raspberrypi/rp2040/rp2040_usb.h | 10 +- 3 files changed, 128 insertions(+), 166 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index e4d56f69a2..ec73cafc1f 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -69,20 +69,18 @@ TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8 return hw_endpoint_get(num, dir); } -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) { - const uint8_t epnum = tu_edpt_number(ep->ep_addr); - const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(const uint8_t epnum, tusb_dir_t dir) { if (epnum == 0) { // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled return NULL; } - return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out; + struct usb_device_dpram_ep_ctrl *ep_ctrl = &usb_dpram->ep_ctrl[epnum - 1]; + return (dir == TUSB_DIR_IN) ? &ep_ctrl->in : &ep_ctrl->out; } -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) { - const uint8_t epnum = tu_edpt_number(ep->ep_addr); - const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); - return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out; +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(const uint8_t epnum, tusb_dir_t dir) { + struct usb_device_dpram_ep_buf_ctrl *buf_ctrl = &usb_dpram->ep_buf_ctrl[epnum]; + return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out; } // main processing for dcd_edpt_iso_activate @@ -92,11 +90,12 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa ep->max_packet_size = wMaxPacketSize; // Clear existing buffer control state - io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + 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; // allocated hw buffer - const uint8_t epnum = tu_edpt_number(ep_addr); if (epnum == 0) { // Buffer offset is fixed (also double buffered) ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0]; @@ -109,7 +108,7 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa size *= 2u; #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + if (dir == TUSB_DIR_IN) { ep->e15_bulk_in = true; } #endif @@ -124,13 +123,13 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa } } -static void hw_endpoint_enable(hw_endpoint_t *ep, uint8_t transfer_type) { - io_rw_32 *ctrl_reg = hwep_ctrl_reg_device(ep); +static void hw_endpoint_enable(uint8_t epnum, tusb_dir_t dir, uint8_t transfer_type, uint8_t *dpram_buf) { + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); // Set endpoint control register to enable (EP0 has no endpoint control register) - if (ctrl_reg != NULL) { + 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(ep->dpram_buf); - *ctrl_reg = ctrl_value; + EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(dpram_buf); + *ep_reg = ctrl_value; } } @@ -141,7 +140,7 @@ static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); hw_endpoint_init(ep, ep_addr, wMaxPacketSize, transfer_type); - hw_endpoint_enable(ep, transfer_type); + hw_endpoint_enable(epnum, dir, transfer_type, ep->dpram_buf); } static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { @@ -162,7 +161,7 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { buf_ctrl |= USB_BUF_CTRL_DATA1_PID; } - io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + io_rw_32 *buf_ctrl_reg = get_buf_ctrl(epnum, dir); hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl); hw_endpoint_reset_transfer(ep); @@ -173,32 +172,41 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { } static void __tusb_irq_path_func(handle_hw_buff_status)(void) { - uint32_t remaining_buffers = usb_hw->buf_status; - pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers); - uint bit = 1u; - for (uint8_t i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) { - if (remaining_buffers & bit) { - // clear this in advance + uint32_t buf_status = usb_hw->buf_status; + pico_trace("buf_status = 0x%08lx\r\n", buf_status); + while (buf_status) { + // ctz/clz is faster than loop which has only a few bit set in general + 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; + + // 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); + + // 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; usb_hw_clear->buf_status = bit; + done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, buf_id); + } - // 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 = hwep_ctrl_reg_device(ep); - io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); - const bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg); - - if (done) { - // Notify usbd - 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); - } - remaining_buffers &= ~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); } - bit <<= 1u; + + buf_status &= ~bit; } } @@ -251,9 +259,9 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { hw_endpoint_lock_update(ep, 1); if (ep->pending) { ep->pending = 0; - io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); - io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); - hw_endpoint_start_next_buffer(ep, ep_reg, buf_reg); + 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); } hw_endpoint_lock_update(ep, -1); } @@ -361,7 +369,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { (void) rh_init; assert(rhport == 0); - TU_LOG(2, "Chip Version B%u\r\n", rp2040_chip_version()); + TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version()); // Reset hardware to default state rp2usb_init(); @@ -508,7 +516,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) } ep->max_packet_size = ep_desc->wMaxPacketSize; - hw_endpoint_enable(ep, TUSB_XFER_ISOCHRONOUS); + hw_endpoint_enable(epnum, dir, TUSB_XFER_ISOCHRONOUS, ep->dpram_buf); return true; } @@ -521,9 +529,12 @@ void dcd_edpt_close_all(uint8_t rhport) { bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { (void)rhport; (void)is_isr; - hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr); - io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); - io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + 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); hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes); return true; } @@ -532,9 +543,9 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) { (void)rhport; (void)is_isr; - hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr); - io_rw_32 *ep_reg = hwep_ctrl_reg_device(ep); - io_rw_32 *buf_reg = hwbuf_ctrl_reg_device(ep); + 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); hw_endpoint_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes); return true; } @@ -544,7 +555,6 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void)rhport; const uint8_t epnum = tu_edpt_number(ep_addr); const tusb_dir_t dir = tu_edpt_dir(ep_addr); - hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); if (epnum == 0) { // A stall on EP0 has to be armed so it can be cleared on the next setup packet @@ -552,19 +562,19 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { } // stall and clear current pending buffer, may need to use EP_ABORT - io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + io_rw_32 *buf_ctrl_reg = get_buf_ctrl(epnum, dir); hwbuf_ctrl_set(buf_ctrl_reg, USB_BUF_CTRL_STALL); } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); - if (tu_edpt_number(ep_addr)) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - - // clear stall also reset toggle to DATA0, ready for next transfer - ep->next_pid = 0; - io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + if (epnum != 0) { + struct hw_endpoint* ep = hw_endpoint_get(epnum, dir); + ep->next_pid = 0; // reset data toggle + io_rw_32 *buf_ctrl_reg = get_buf_ctrl(epnum, dir); hwbuf_ctrl_clear_mask(buf_ctrl_reg, USB_BUF_CTRL_STALL); } } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 66e579c391..ac1536f5b4 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -35,8 +35,6 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ -static void sync_xfer(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); - #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX static bool e15_is_critical_frame_period(struct hw_endpoint *ep); #else @@ -137,18 +135,18 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an // prepare buffer, move data if tx, return buffer control static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size); - ep->remaining_len = (uint16_t) (ep->remaining_len - buflen); + ep->remaining_len -= buflen; uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; - - // PID - buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; + if (ep->next_pid) { + buf_ctrl |= USB_BUF_CTRL_DATA1_PID; + } ep->next_pid ^= 1u; if (!is_rx) { if (buflen) { // Copy data from user buffer/fifo to hw buffer - uint8_t *hw_buf = ep->dpram_buf + buf_id * 64; + uint8_t *hw_buf = ep->dpram_buf + (buf_id << 6); #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround @@ -161,7 +159,6 @@ static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint } } - // Mark as full buf_ctrl |= USB_BUF_CTRL_FULL; } @@ -172,15 +169,11 @@ static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint buf_ctrl |= USB_BUF_CTRL_LAST; } - if (buf_id) { - buf_ctrl = buf_ctrl << 16; - } - return buf_ctrl; } -// Prepare buffer control register value -void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { +// 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); const bool is_host = rp2usb_is_host_mode(); @@ -194,39 +187,34 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep, // always compute and start with buffer 0 uint32_t buf_ctrl = hwbuf_prepare(ep, 0, is_rx) | USB_BUF_CTRL_SEL; - // EP0 has no endpoint control register, also usbd only schedule 1 packet at a time (single buffer) + // Device mode EP0 has no endpoint control register if (ep_reg != NULL) { - uint32_t ep_ctrl = *ep_reg; - - // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause - // short packet on buffer0 - // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single - // buffered, - // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint - const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0); + // 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; + + // Since 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 + bool force_single = is_rx; + #if CFG_TUH_ENABLED + force_single |= (is_host && ep->interrupt_num != 0); // host interrupt is single only + #endif if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt) - - buf_ctrl |= hwbuf_prepare(ep, 1, is_rx); - - // Set endpoint control double buffered bit if needed - ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER; - ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER; + buf_ctrl |= (hwbuf_prepare(ep, 1, is_rx) << 16); + ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; } else { // Single buffered since 1 is enough - ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER); - ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; + ep_ctrl &= ~EP_CTRL_DOUBLE_BUFFERED_BITS; } *ep_reg = ep_ctrl; } - TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl)); - - // Finally, write to buffer_control which will trigger the transfer - // the next time the controller polls this dpram address + // 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); } @@ -267,7 +255,7 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * } else #endif { - hw_endpoint_start_next_buffer(ep, ep_reg, buf_reg); + hw_endpoint_buffer_xact(ep, ep_reg, buf_reg); } hw_endpoint_lock_update(ep, -1); @@ -315,91 +303,49 @@ static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *bu return xferred_bytes; } -// Update hw endpoint struct with info from hardware after a buff status interrupt -static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { - // const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); - const bool is_host = rp2usb_is_host_mode(); - bool is_rx; - - if (is_host) { - is_rx = (dir == TUSB_DIR_IN); - } else { - is_rx = (dir == TUSB_DIR_OUT); - } - - TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_reg), tu_u32_high16(*buf_reg)); - uint16_t buf0_bytes = hwbuf_sync(ep, buf_reg, 0, is_rx); // always sync buffer 0 - - // sync buffer 1 if double buffered - if (ep_reg != NULL && (*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) { - if (buf0_bytes == ep->max_packet_size) { - // sync buffer 1 if not short packet - hwbuf_sync(ep, buf_reg, 1, is_rx); - } else { - // short packet on buffer 0 - // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example - // At this time (currently trigger per 2 buffer), the buffer1 is probably filled with data from - // the next transfer (not current one). For now we disable double buffered for device OUT - // NOTE this could happen to Host IN -#if 0 - uint8_t const ep_num = tu_edpt_number(ep->ep_addr); - uint8_t const dir = (uint8_t) tu_edpt_dir(ep->ep_addr); - uint8_t const ep_id = 2*ep_num + (dir ? 0 : 1); - - // abort queued transfer on buffer 1 - usb_hw->abort |= TU_BIT(ep_id); - - while ( !(usb_hw->abort_done & TU_BIT(ep_id)) ) {} - - uint32_t ep_ctrl = *ep->endpoint_control; - ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER); - ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; - - io_rw_32 *buf_ctrl_reg = is_host ? hwbuf_ctrl_reg_host(ep) : hwbuf_ctrl_reg_device(ep); - hwbuf_ctrl_set(buf_ctrl_reg, 0); - - usb_hw->abort &= ~TU_BIT(ep_id); - - TU_LOG(3, "----SHORT PACKET buffer0 on EP %02X:\r\n", ep->ep_addr); - TU_LOG(3, " BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl)); -#endif - } - } -} - -// Returns true if transfer is complete -bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { +// Returns true if transfer is complete. +// buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered). +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); - // Part way through a transfer if (!ep->active) { panic("Can't continue xfer on inactive ep %02X", ep->ep_addr); } - sync_xfer(ep, ep_reg, buf_reg); // Update EP struct from hardware state + 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 uint16_t xferred = hwbuf_sync(ep, buf_reg, is_double ? buf_id : 0, is_rx); + bool is_done = (ep->remaining_len == 0); + + if (is_double) { + if (xferred < ep->max_packet_size) { + // Short packet + is_done = true; + } else if (buf_id == 0) { + // buf0 done: wait for buf1, don't start new buffers + hw_endpoint_lock_update(ep, -1); + return false; + } + // buf1 done: is_done determined by remaining_len above + } - // Now we have synced our state with the hardware. Is there more data to transfer? - // If we are done then notify tinyusb - if (ep->remaining_len == 0) { - pico_trace("Completed transfer of %d bytes on ep %02X\r\n", ep->xferred_len, ep->ep_addr); - // Notify caller we are done so it can notify the tinyusb stack - hw_endpoint_lock_update(ep, -1); - return true; - } else { + if (!is_done) { #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX if (e15_is_critical_frame_period(ep)) { ep->pending = 1; } else #endif { - hw_endpoint_start_next_buffer(ep, ep_reg, buf_reg); + hw_endpoint_buffer_xact(ep, ep_reg, buf_reg); } } hw_endpoint_lock_update(ep, -1); - // More work to do - return false; + return is_done; } //--------------------------------------------------------------------+ diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 46bd727c8b..c4dd0cb98d 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -26,6 +26,8 @@ #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 #endif + + #define CFG_TUSB_RP2040_ERRATA_E4_FIX 1 #endif #ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX @@ -45,6 +47,10 @@ #define PICO_RP2040_USB_FAST_IRQ 0 #endif +#ifndef CFG_TUSB_RP2040_ERRATA_E4_FIX +#define CFG_TUSB_RP2040_ERRATA_E4_FIX 0 +#endif + #if PICO_RP2040_USB_FAST_IRQ #define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x) #else @@ -108,8 +114,8 @@ 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); -void hw_endpoint_start_next_buffer(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); +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_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) { From aeac28e5171442c6aef5ec44f9043d5e74371e41 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 25 Mar 2026 11:23:50 +0700 Subject: [PATCH 14/29] update hcd to handle interrupt per buf --- hw/bsp/rp2040/family.c | 6 +- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 7 +- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 89 ++++++++++---------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 29 +++---- test/hil/hil_test.py | 57 +++++++++++-- 5 files changed, 116 insertions(+), 72 deletions(-) diff --git a/hw/bsp/rp2040/family.c b/hw/bsp/rp2040/family.c index adb58449d3..c640250368 100644 --- a/hw/bsp/rp2040/family.c +++ b/hw/bsp/rp2040/family.c @@ -165,7 +165,11 @@ void board_init(void) { #if (CFG_TUH_ENABLED && CFG_TUH_RPI_PIO_USB) || (CFG_TUD_ENABLED && CFG_TUD_RPI_PIO_USB) // Set the system clock to a multiple of 12mhz for bit-banging USB with pico-usb - set_sys_clock_khz(120000, true); + #if defined(PICO_RP2350) && PICO_RP2350 == 1 + set_sys_clock_khz(156000, true); // rp2350 default is 150Mhz + #else + set_sys_clock_khz(120000, true); // rp2040 default is 125Mhz + #endif // set_sys_clock_khz(180000, true); // set_sys_clock_khz(192000, true); // set_sys_clock_khz(240000, true); diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index ec73cafc1f..0665484a0b 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -69,7 +69,7 @@ TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8 return hw_endpoint_get(num, dir); } -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(const uint8_t epnum, tusb_dir_t dir) { +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(uint8_t epnum, tusb_dir_t dir) { if (epnum == 0) { // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled return NULL; @@ -78,7 +78,7 @@ TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(const uint8_t epnum, t return (dir == TUSB_DIR_IN) ? &ep_ctrl->in : &ep_ctrl->out; } -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(const uint8_t epnum, tusb_dir_t dir) { +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(uint8_t epnum, tusb_dir_t dir) { struct usb_device_dpram_ep_buf_ctrl *buf_ctrl = &usb_dpram->ep_buf_ctrl[epnum]; return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out; } @@ -182,6 +182,7 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { // 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; @@ -205,8 +206,6 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { hw_endpoint_reset_transfer(ep); dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); } - - buf_status &= ~bit; } } diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 9ee15d343f..4ebdf82843 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -94,7 +94,13 @@ static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { return NULL; } -// static hw_endpoint_t* epdt_find_interrupt(uint8_t ) +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) { + return &usbh_dpram->int_ep_ctrl[int_num-1].ctrl; +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 * dpram_int_ep_buffer_ctrl(uint8_t int_num) { + return &usbh_dpram->int_ep_buffer_ctrl[int_num-1].ctrl; +} //--------------------------------------------------------------------+ // @@ -129,57 +135,53 @@ static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_resul } } -static void __tusb_irq_path_func(handle_hwbuf_status_bit)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { - const bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg); - if (done) { - hw_xfer_complete(ep, XFER_RESULT_SUCCESS); - } -} - static void __tusb_irq_path_func(handle_hwbuf_status)(void) { - uint32_t buf_status = usb_hw->buf_status; pico_trace("buf_status 0x%08lx\n", buf_status); + enum { + BUF_STATUS_EPX = 1u + }; - // Check EPX first - uint32_t bit = 1u; - if (buf_status & bit) { - buf_status &= ~bit; - usb_hw_clear->buf_status = bit; + // Check EPX first (bit 0). EPX is currently single-buffered, always use buf_id=0. + // Double-buffered: if both buffers completed at once, buf_status re-sets + // immediately after clearing (datasheet Table 406). Process the second buffer too. + while (usb_hw->buf_status & BUF_STATUS_EPX) { + const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0; + usb_hw_clear->buf_status = 1u; // clear io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - handle_hwbuf_status_bit(epx, ep_reg, buf_reg); + if (hw_endpoint_xfer_continue(epx, ep_reg, buf_reg, buf_id)) { + hw_xfer_complete(epx, XFER_RESULT_SUCCESS); + } } // Check "interrupt" (asynchronous) endpoints for both IN and OUT - // TODO use clz for better efficiency - for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && buf_status; i++) { - // EPX IN/OUT is bit 0, 1 + uint32_t buf_status = usb_hw->buf_status & ~1u; + while (buf_status) { + // ctz/clz is faster than loop which has only a few bit set in general + const uint8_t idx = (uint8_t) __builtin_ctz(buf_status); + const uint bit = TU_BIT(idx); + usb_hw_clear->buf_status = bit; + buf_status &= ~bit; + + // IN transfer for even i, OUT transfer for odd i + // EPX is bit 0. Bit 1 is not used // IEP1 IN/OUT is bit 2, 3 - // IEP2 IN/OUT is bit 4, 5 - // etc - for (uint j = 0; j < 2; j++) { - bit = 1 << (i * 2 + j); - if (buf_status & bit) { - buf_status &= ~bit; - usb_hw_clear->buf_status = bit; - - for (uint8_t e = 0; e < USB_MAX_ENDPOINTS; e++) { - hw_endpoint_t *ep = &ep_pool[e]; - if (ep->interrupt_num == i) { - io_rw_32 *ep_reg = &usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl; - io_rw_32 *buf_reg = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl; - handle_hwbuf_status_bit(ep, ep_reg, buf_reg); - break; - } + // IEP2 IN/OUT is bit 4, 5 etc + const uint8_t epnum = idx >> 1u; + for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) { + hw_endpoint_t *ep = &ep_pool[e]; + if (ep->interrupt_num == epnum) { + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + const bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, 0); + if (done) { + hw_xfer_complete(ep, XFER_RESULT_SUCCESS); } + break; } } } - - if (buf_status) { - panic("Unhandled buffer %d\n", buf_status); - } } // All non-interrupt endpoints use shared EPX. @@ -491,8 +493,10 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num; usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0; - usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl = 0; - usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl = 0; + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + *buf_reg = 0; + *ep_reg = 0; } ep->max_packet_size = 0; // mark as unused @@ -547,13 +551,12 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value) { usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; } -// xfer using epx static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { if (ep->transfer_type == TUSB_XFER_INTERRUPT) { // For interrupt endpoint control and buffer is already configured // Note: Interrupt is single buffered only - io_rw_32 *ep_reg = &usbh_dpram->int_ep_ctrl[ep->interrupt_num - 1].ctrl; - io_rw_32 *buf_reg = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num - 1].ctrl; + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); } else { const uint8_t ep_num = tu_edpt_number(ep->ep_addr); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index ac1536f5b4..1e2c4f2119 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -124,8 +124,10 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an // 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. - // Host also needs this for continuation buffers in multi-packet transfers. - busy_wait_at_least_cycles(12); + // Don't need delay in host mode as host is in charge of when to start the transaction. + if (!is_host) { + busy_wait_at_least_cycles(12); + } } } @@ -193,13 +195,13 @@ void __tusb_irq_path_func(hw_endpoint_buffer_xact)(struct hw_endpoint *ep, io_rw // 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; - // Since 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 - bool force_single = is_rx; - #if CFG_TUH_ENABLED - force_single |= (is_host && ep->interrupt_num != 0); // host interrupt is single only - #endif + // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause + // short packet on buffer0 + // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single + // buffered, + // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint + const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0); + // bool force_single = is_rx || (is_host && ep->interrupt_num != 0); if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data @@ -318,14 +320,11 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ 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 uint16_t xferred = hwbuf_sync(ep, buf_reg, is_double ? buf_id : 0, is_rx); - bool is_done = (ep->remaining_len == 0); + hwbuf_sync(ep, buf_reg, buf_id, is_rx); + const bool is_done = (ep->remaining_len == 0); if (is_double) { - if (xferred < ep->max_packet_size) { - // Short packet - is_done = true; - } else if (buf_id == 0) { + if (buf_id == 0) { // buf0 done: wait for buf1, don't start new buffers hw_endpoint_lock_update(ep, -1); return false; diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 4b17bed547..8b0070d406 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -155,8 +155,7 @@ def read_disk_file(uid, lun, fname): def open_mtp_dev(uid): mtp = MTP() - # MTP seems to take a while to enumerate - timeout = 2 * ENUM_TIMEOUT + timeout = ENUM_TIMEOUT while timeout > 0: # unmount gio/gvfs MTP mount which blocks libmtp from accessing the device subprocess.run(f"gio mount -u mtp://TinyUsb_TinyUsb_Device_{uid}/", @@ -607,7 +606,7 @@ def test_host_msc_file_explorer(board): lines = data.decode('utf-8', errors='ignore').splitlines() check_msc_info(lines, msc_devs) - # Send "cat README.TXT" and read response + # Send "cat README.TXT" and check response (optional — file may not exist on all drives) time.sleep(1) ser.reset_input_buffer() for ch in 'cat README.TXT\r': @@ -615,24 +614,20 @@ def test_host_msc_file_explorer(board): ser.flush() time.sleep(0.002) - # Read response resp = b'' t = 10.0 while t > 0: rd = ser.read(max(1, ser.in_waiting)) if rd: resp += rd - # wait for prompt after command output if b'>' in resp and resp.rstrip().endswith(b'>'): break time.sleep(0.05) t -= 0.05 - # Verify response contains README content resp_text = resp.decode('utf-8', errors='ignore') - assert MSC_README_TXT.decode() in resp_text, (f'MSC README.TXT not found in response:\n' - f' received: {resp_text}') - print('README.TXT matched ', end='') + if MSC_README_TXT.decode() in resp_text: + print('README.TXT matched ', end='') # MSC throughput test: send dd command to read sectors time.sleep(0.5) @@ -740,6 +735,50 @@ def rand_ascii(length): data = read_disk_file(uid, 0, 'README.TXT') assert data == MSC_README_TXT, f'MSC wrong data in README.TXT\n expected: {MSC_README_TXT.decode()}\n received: {data.decode()}' + # MSC dd throughput test: read all sectors then write back same data + dev = get_disk_dev(uid, 'TinyUSB', 0) + timeout = ENUM_TIMEOUT + while timeout > 0: + if os.path.exists(dev): + break + time.sleep(1) + timeout -= 1 + assert timeout > 0, f'Disk {dev} not found for dd test' + + block_count = 16 + block_size = 512 + tmp_file = f'/tmp/msc_dd_{uid}.bin' + + # Read: dd from device to file + ret = run_cmd(f'dd if={dev} of={tmp_file} bs={block_size} count={block_count} iflag=direct 2>&1') + assert ret.returncode == 0, f'dd read failed: {ret.stdout.decode()}' + dd_out = ret.stdout.decode() + read_speed = '' + for line in dd_out.splitlines(): + m = re.search(r'(\d+[\.\d]*\s+[kMG]?B/s)', line) + if m: + read_speed = m.group(1) + break + + # Write back the same data to avoid corrupting the disk + ret = run_cmd(f'dd if={tmp_file} of={dev} bs={block_size} count={block_count} oflag=direct 2>&1') + assert ret.returncode == 0, f'dd write failed: {ret.stdout.decode()}' + dd_out = ret.stdout.decode() + write_speed = '' + for line in dd_out.splitlines(): + m = re.search(r'(\d+[\.\d]*\s+[kMG]?B/s)', line) + if m: + write_speed = m.group(1) + break + + try: + os.remove(tmp_file) + except OSError: + pass + + if read_speed and write_speed: + print(f' dd read: {read_speed}, write: {write_speed}', end='') + def test_device_cdc_msc_freertos(board): test_device_cdc_msc(board) From e81faa22af6369745e4aa0420289a335d2eaa047 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 25 Mar 2026 15:56:09 +0700 Subject: [PATCH 15/29] fix e4 incorrect buf with incorrect buf_id = 1 --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 2 +- src/portable/raspberrypi/rp2040/rp2040_usb.c | 85 +++++++++++++------- 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 0665484a0b..65871df058 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -368,7 +368,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { (void) rh_init; assert(rhport == 0); - TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version()); + // TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version()); // Reset hardware to default state rp2usb_init(); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 1e2c4f2119..1d21952a88 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -135,7 +135,7 @@ void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t an } // prepare buffer, move data if tx, return buffer control -static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { +static uint32_t __tusb_irq_path_func(hwbuf_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; @@ -148,15 +148,14 @@ static uint32_t __tusb_irq_path_func(hwbuf_prepare)(struct hw_endpoint *ep, uint if (!is_rx) { if (buflen) { // Copy data from user buffer/fifo to hw buffer - uint8_t *hw_buf = ep->dpram_buf + (buf_id << 6); #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround - tu_hwfifo_write_from_fifo(hw_buf, ep->user_fifo, buflen, NULL); + tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL); } else #endif { - unaligned_memcpy(hw_buf, ep->user_buf, buflen); + unaligned_memcpy(dpram_buf, ep->user_buf, buflen); ep->user_buf += buflen; } } @@ -186,27 +185,40 @@ 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 + + #endif + // always compute and start with buffer 0 - uint32_t buf_ctrl = hwbuf_prepare(ep, 0, is_rx) | USB_BUF_CTRL_SEL; + uint32_t buf_ctrl = hwbuf_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; - - // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause - // short packet on buffer0 - // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single - // buffered, - // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint +#if 1 const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0); - // bool force_single = is_rx || (is_host && ep->interrupt_num != 0); +#else + bool force_single = false; // is_rx; + #if CFG_TUH_ENABLED + if (is_host && ep->interrupt_num != 0) { + force_single = true; + } + #endif +#endif if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data - // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt) - buf_ctrl |= (hwbuf_prepare(ep, 1, is_rx) << 16); + buf_ctrl |= hwbuf_prepare(ep, ep->dpram_buf+64, is_rx) << 16; ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; } else { // Single buffered since 1 is enough @@ -216,6 +228,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); } @@ -264,13 +278,7 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * } // sync endpoint buffer and return transferred bytes -static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, - bool is_rx) { - uint32_t buf_ctrl = *buf_ctrl_reg; - if (buf_id) { - buf_ctrl = buf_ctrl >> 16; - } - +static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, bool is_rx, uint32_t buf_ctrl, uint8_t *dpram_buf) { const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; if (!is_rx) { @@ -281,16 +289,14 @@ static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *bu // If we have received some data, so can increase the length // we have received AFTER we have copied it to the user buffer at the appropriate offset assert(buf_ctrl & USB_BUF_CTRL_FULL); - - uint8_t *hw_buf = ep->dpram_buf + buf_id * 64; #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround - tu_hwfifo_read_to_fifo(hw_buf, ep->user_fifo, xferred_bytes, NULL); + tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL); } else #endif { - unaligned_memcpy(ep->user_buf, hw_buf, xferred_bytes); + unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes); ep->user_buf += xferred_bytes; } } @@ -307,8 +313,7 @@ static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, io_rw_32 *bu // Returns true if transfer is complete. // buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered). -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) { +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); if (!ep->active) { @@ -320,7 +325,31 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ 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); - hwbuf_sync(ep, buf_reg, buf_id, is_rx); + #if CFG_TUSB_RP2040_ERRATA_E4_FIX + const bool need_e4_fix = (is_host && !is_double); + #endif + + // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed. + + // 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 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 + #endif + { + dpram_buf += 64; // buf1 offset + } + } + + hwbuf_sync(ep, is_rx, buf_ctrl, dpram_buf); const bool is_done = (ep->remaining_len == 0); if (is_double) { From 94f48272c79310c5ab47c79c7b57a1be7bdee901 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 26 Mar 2026 19:33:08 +0700 Subject: [PATCH 16/29] implement ping-pong double buffered for both tx and rx --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 77 ++++--- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 13 +- src/portable/raspberrypi/rp2040/rp2040_usb.c | 223 ++++++++++++++----- src/portable/raspberrypi/rp2040/rp2040_usb.h | 65 ++++-- 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 65871df058..d9c30efbac 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 4ebdf82843..3f4c954226 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 1d21952a88..d0a2297857 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 -#include "rp2040_usb.h" + #include + #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); + + // 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) {} + } - 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; + // 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; } - // buf1 done: is_done determined by remaining_len above + + 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 c4dd0cb98d..8b0fc83b78 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 8b0070d406..154a251c15 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -50,7 +50,7 @@ from pymtp import MTP import string -ENUM_TIMEOUT = 30 +ENUM_TIMEOUT = 10 STATUS_OK = "\033[32mOK\033[0m" STATUS_FAILED = "\033[31mFailed\033[0m" From 9b3d51790f19676c6863f3362608794eff02bfa2 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 26 Mar 2026 22:41:28 +0700 Subject: [PATCH 17/29] clean up hw_endpoint_open(), still has issue with E15 and ping-pong (slow read). --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 182 +++++++++---------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 58 +++--- src/portable/raspberrypi/rp2040/rp2040_usb.h | 2 +- test/hil/hil_test.py | 28 +-- 4 files changed, 126 insertions(+), 144 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index d9c30efbac..15ac97ddee 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -83,24 +83,28 @@ TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(uint8_t epnum, tusb_d return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out; } -// main processing for dcd_edpt_iso_activate -static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { - ep->ep_addr = ep_addr; - ep->next_pid = 0u; - ep->max_packet_size = wMaxPacketSize; - - // Clear existing buffer control state +// Init and enable endpoint +static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, bool ep_enabled) { const uint8_t epnum = tu_edpt_number(ep_addr); const tusb_dir_t dir = tu_edpt_dir(ep_addr); - io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); - *buf_reg = 0; + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + ep->ep_addr = ep_addr; + ep->next_pid = 0u; + ep->max_packet_size = wMaxPacketSize; + + // Clear existing buffer control state + 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) TODO EP0 double buffer + // Buffer offset is fixed (2 buffer allocated). + // Note: Only single buffer for EP since Double buffered RX can be troublesome with future data. ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0]; } else { + uint32_t ep_ctrl = EP_CTRL_INTERRUPT_PER_BUFFER | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB); + // round up size to multiple of 64 uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64); @@ -119,31 +123,18 @@ static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPa ep->dpram_buf = hw_buffer_ptr; hw_buffer_ptr += size; + ep_ctrl |= hw_data_offset(ep->dpram_buf); + if (ep_enabled) { + ep_ctrl |= EP_CTRL_ENABLE_BITS; + } + + *get_ep_ctrl(epnum, dir) = ep_ctrl; + hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data)); pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->dpram_buf); } } -static void hw_endpoint_enable(uint8_t epnum, tusb_dir_t dir, uint8_t transfer_type, uint8_t *dpram_buf) { - 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 | EP_CTRL_INTERRUPT_PER_BUFFER | - ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(dpram_buf); - *ep_reg = ctrl_value; - } -} - -// Init and enable endpoint -static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { - const uint8_t epnum = tu_edpt_number(ep_addr); - const tusb_dir_t dir = tu_edpt_dir(ep_addr); - hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); - - hw_endpoint_init(ep, ep_addr, wMaxPacketSize, transfer_type); - hw_endpoint_enable(epnum, dir, transfer_type, ep->dpram_buf); -} - static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { // Abort any pending transfer const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); @@ -231,101 +222,101 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) { static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { const uint32_t status = usb_hw->ints; - uint32_t handled = 0; if (status & USB_INTF_DEV_SOF_BITS) { - bool keep_sof_alive = false; + uint32_t sof_count = usb_hw->sof_rd & USB_SOF_RD_BITS; // clear interrupt by reading SOF_RD + + #if CFG_TUSB_RP2_ERRATA_E15 + e15_last_sof = time_us_32(); // timing critical + #endif + + dcd_event_sof(0, sof_count, true); + } + + // xfer events are handled before setup req. So if a transfer completes immediately + // before closing the EP, the events will be delivered in same order. + if (status & USB_INTS_BUFF_STATUS_BITS) { + handle_hw_buff_status(); + } - handled |= USB_INTF_DEV_SOF_BITS; + if (status & USB_INTS_SETUP_REQ_BITS) { + const uint8_t *setup = remove_volatile_cast(const uint8_t *, &usb_dpram->setup_packet); -#if CFG_TUSB_RP2_ERRATA_E15 - // Errata 15 workaround for Device Bulk-In endpoint - e15_last_sof = time_us_32(); + // reset pid to both 1 (data and ack) + reset_ep0(); + // Pass setup packet to tiny usb + dcd_event_setup_received(0, setup, true); + usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS; + } + + // Errata 15 workaround for Device Bulk-In endpoint, must be after BUF_STATUS interrupt to sync buf control first + if (status & USB_INTF_DEV_SOF_BITS) { + bool keep_sof_alive = false; + + #if CFG_TUSB_RP2_ERRATA_E15 for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) { struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN); // Active Bulk IN endpoint requires SOF if (ep->e15_bulk_in && ep->active) { keep_sof_alive = true; - hw_endpoint_lock_update(ep, 1); + if (ep->pending) { - 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); - io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; + ep->pending = 0; + io_rw_32 *buf_reg32 = (io_rw_32 *)get_buf_ctrl(i, TUSB_DIR_IN); + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32; // 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; - } + enum { + BUSY_MASK = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL + }; + + uint16_t buf0, buf1; + const bool use_buf0 = !(buf_reg16[0] & BUSY_MASK); + + if (use_buf0) { + buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); } - 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); + const bool use_buf1 = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK); + if (use_buf1) { + buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false); } - if (do_buf1) { - uint16_t buf1 = bufctrl_prepare(ep, ep->dpram_buf + 64, false); + + if (use_buf0 && use_buf1) { + buf0 |= USB_BUF_CTRL_SEL; // reset to buf0 since order of complete is not guaranteed + bufctrl_write32(buf_reg32, buf0 | ((uint32_t)buf1 << 16)); + } else if (use_buf0) { + bufctrl_write16(buf_reg16, buf0); + } else if (use_buf1) { bufctrl_write16(buf_reg16 + 1, buf1); } } + hw_endpoint_lock_update(ep, -1); } } -#endif + #endif // disable SOF interrupt if it is used for RESUME in remote wakeup if (!keep_sof_alive && !_sof_enable) { usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; } - - dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true); - } - - // xfer events are handled before setup req. So if a transfer completes immediately - // before closing the EP, the events will be delivered in same order. - if (status & USB_INTS_BUFF_STATUS_BITS) { - handled |= USB_INTS_BUFF_STATUS_BITS; - handle_hw_buff_status(); - } - - if (status & USB_INTS_SETUP_REQ_BITS) { - handled |= USB_INTS_SETUP_REQ_BITS; - uint8_t const* setup = remove_volatile_cast(uint8_t const*, &usb_dpram->setup_packet); - - // reset pid to both 1 (data and ack) - reset_ep0(); - - // Pass setup packet to tiny usb - dcd_event_setup_received(0, setup, true); - usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS; } -#if FORCE_VBUS_DETECT == 0 + #if FORCE_VBUS_DETECT == 0 // Since we force VBUS detect On, device will always think it is connected and // couldn't distinguish between disconnect and suspend if (status & USB_INTS_DEV_CONN_DIS_BITS) { - handled |= USB_INTS_DEV_CONN_DIS_BITS; - if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) { // Connected: nothing to do } else { // Disconnected dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); } - usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS; } #endif @@ -333,9 +324,6 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { // SE0 for 2.5 us or more (will last at least 10ms) if (status & USB_INTS_BUS_RESET_BITS) { pico_trace("BUS RESET\r\n"); - - handled |= USB_INTS_BUS_RESET_BITS; - usb_hw->dev_addr_ctrl = 0; reset_non_control_endpoints(); dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); @@ -358,20 +346,15 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { * being disconnected and suspended. */ if (status & USB_INTS_DEV_SUSPEND_BITS) { - handled |= USB_INTS_DEV_SUSPEND_BITS; dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); usb_hw_clear->sie_status = USB_SIE_STATUS_SUSPENDED_BITS; } if (status & USB_INTS_DEV_RESUME_FROM_HOST_BITS) { - handled |= USB_INTS_DEV_RESUME_FROM_HOST_BITS; dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS; } - if (status ^ handled) { - panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); - } } /*------------------------------------------------------------------*/ @@ -401,8 +384,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // Init control endpoints tu_memclr(hw_endpoints[0], 2 * sizeof(hw_endpoint_t)); - hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL); - hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL); + hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL, false); + hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL, false); // Init non-control endpoints reset_non_control_endpoints(); @@ -508,7 +491,7 @@ void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* req bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { (void) rhport; const uint8_t xfer_type = desc_edpt->bmAttributes.xfer; - hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type); + hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type, true); return true; } @@ -516,8 +499,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { // Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { (void)rhport; - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_init(ep, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS); + hw_endpoint_open(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS, false); return true; } @@ -534,7 +516,11 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) } ep->max_packet_size = ep_desc->wMaxPacketSize; - hw_endpoint_enable(epnum, dir, TUSB_XFER_ISOCHRONOUS, ep->dpram_buf); + // enable endpoint + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + if (ep_reg != NULL) { + *ep_reg |= EP_CTRL_ENABLE_BITS; + } return true; } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index d0a2297857..433bd261d6 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -39,7 +39,7 @@ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ #if CFG_TUSB_RP2_ERRATA_E15 -static bool e15_is_critical_frame_period(struct hw_endpoint *ep); +static bool e15_is_critical_frame_period(void); #endif #if CFG_TUSB_RP2_ERRATA_E2 @@ -178,7 +178,7 @@ void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) } // prepare buffer, move data if tx, return buffer control -uint16_t __tusb_irq_path_func(bufctrl_prepare)(struct hw_endpoint *ep, uint8_t *dpram_buf, bool is_rx) { +uint16_t __tusb_irq_path_func(bufctrl_prepare16)(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; @@ -237,16 +237,12 @@ void __tusb_irq_path_func(hw_endpoint_buffer_start)(struct hw_endpoint *ep, io_r #endif // always compute and start with buffer 0 - uint32_t buf_ctrl = bufctrl_prepare(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; + uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; - // Device mode EP0 has no endpoint control register + // Note: device EP0 does not have an 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; #if 1 - const bool force_single = // (!is_host && is_rx) || - (is_host && tu_edpt_number(ep->ep_addr) != 0); + const bool force_single = (is_host && tu_edpt_number(ep->ep_addr) != 0); #else bool force_single = false; // is_rx; #if CFG_TUH_ENABLED @@ -256,15 +252,15 @@ void __tusb_irq_path_func(hw_endpoint_buffer_start)(struct hw_endpoint *ep, io_r #endif #endif + uint32_t ep_ctrl = *ep_reg; if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data - buf_ctrl |= (uint32_t)bufctrl_prepare(ep, ep->dpram_buf + 64, is_rx) << 16; + buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16; ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; } else { - // Single buffered since 1 is enough + // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector ep_ctrl &= ~EP_CTRL_DOUBLE_BUFFERED_BITS; } - *ep_reg = ep_ctrl; } @@ -336,16 +332,17 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * #if CFG_TUSB_RP2_ERRATA_E15 if (ep->e15_bulk_in) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; - } - if (e15_is_critical_frame_period(ep)) { - ep->pending = 1; // skip transfer if we are in critical frame period - } else - #endif - { - hw_endpoint_buffer_start(ep, ep_reg, buf_reg); + // skip transfer if we are in critical frame period + if (e15_is_critical_frame_period()) { + ep->pending = 1; + hw_endpoint_lock_update(ep, -1); + return; + } } + #endif + hw_endpoint_buffer_start(ep, ep_reg, buf_reg); hw_endpoint_lock_update(ep, -1); } @@ -454,7 +451,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ ep->future_bufid = buf_id ^ 1; // buff_status will be clear by the next run } else { - ep->next_pid ^= 1u; + ep->next_pid ^= 1u; // roll back pid if aborted } *buf_reg = 0; // reset buffer control @@ -473,13 +470,17 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ if (!is_done && ep->remaining_len > 0) { #if CFG_TUSB_RP2_ERRATA_E15 - if (e15_is_critical_frame_period(ep)) { + if (ep->e15_bulk_in && e15_is_critical_frame_period()) { + // mark as pending if matches E15 condition ep->pending = 1; + } else if (ep->e15_bulk_in && ep->pending) { + // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it + // do nothing } else #endif { - // 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); + // ping-pong: arm the completed buffer with new data + const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx); bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new); } } @@ -492,7 +493,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ // Errata 15 //--------------------------------------------------------------------+ -#if CFG_TUSB_RP2_ERRATA_E15 + #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 @@ -513,13 +514,8 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep, io_ volatile uint32_t e15_last_sof = 0; -// check if we need to apply Errata 15 workaround : i.e -// Endpoint is BULK IN and is currently in critical frame period i.e 20% of last usb frame -static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoint* ep) { - if (!ep->e15_bulk_in) { - return false; - } - +// check if it is currently in critical frame period i.e 20% of last usb frame +static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) { /* 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. */ diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 8b0fc83b78..7b79cd2b16 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -152,7 +152,7 @@ void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_ma 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); +uint16_t bufctrl_prepare16(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 154a251c15..04dd4e2bcd 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -749,27 +749,27 @@ def rand_ascii(length): block_size = 512 tmp_file = f'/tmp/msc_dd_{uid}.bin' + # dd reports speed based on payload only. Each block also transfers 31-byte CBW + 13-byte CSW on USB. + scsi_ratio = (block_size + 31 + 13) / block_size + + def parse_dd_speed(dd_output): + """Parse dd output, return USB-adjusted speed string""" + for line in dd_output.splitlines(): + m = re.search(r'([\d.]+)\s+([kMG]?B/s)', line) + if m: + speed_val = float(m.group(1)) * scsi_ratio + return f'{speed_val:.1f} {m.group(2)}' + return '' + # Read: dd from device to file ret = run_cmd(f'dd if={dev} of={tmp_file} bs={block_size} count={block_count} iflag=direct 2>&1') assert ret.returncode == 0, f'dd read failed: {ret.stdout.decode()}' - dd_out = ret.stdout.decode() - read_speed = '' - for line in dd_out.splitlines(): - m = re.search(r'(\d+[\.\d]*\s+[kMG]?B/s)', line) - if m: - read_speed = m.group(1) - break + read_speed = parse_dd_speed(ret.stdout.decode()) # Write back the same data to avoid corrupting the disk ret = run_cmd(f'dd if={tmp_file} of={dev} bs={block_size} count={block_count} oflag=direct 2>&1') assert ret.returncode == 0, f'dd write failed: {ret.stdout.decode()}' - dd_out = ret.stdout.decode() - write_speed = '' - for line in dd_out.splitlines(): - m = re.search(r'(\d+[\.\d]*\s+[kMG]?B/s)', line) - if m: - write_speed = m.group(1) - break + write_speed = parse_dd_speed(ret.stdout.decode()) try: os.remove(tmp_file) From dd08939f68cd5e41f45a942efee41dba85110619 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 27 Mar 2026 13:11:53 +0700 Subject: [PATCH 18/29] fix E15 workaround issue with out of order in double buffer. device bulk rx/tx ping-pong double buffered all working well --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 15ac97ddee..ea791f4f65 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -265,7 +265,8 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { if (ep->pending) { ep->pending = 0; - io_rw_32 *buf_reg32 = (io_rw_32 *)get_buf_ctrl(i, TUSB_DIR_IN); + + io_rw_32 *buf_reg32 = get_buf_ctrl(i, TUSB_DIR_IN); io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32; // Check each buffer half: idle when both FULL and AVAIL are clear. @@ -274,24 +275,18 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { BUSY_MASK = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL }; - uint16_t buf0, buf1; - const bool use_buf0 = !(buf_reg16[0] & BUSY_MASK); - - if (use_buf0) { - buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); - } - - const bool use_buf1 = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK); - if (use_buf1) { - buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false); - } + const bool buf0_idle = !(buf_reg16[0] & BUSY_MASK); + const bool buf1_idle = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK); - if (use_buf0 && use_buf1) { - buf0 |= USB_BUF_CTRL_SEL; // reset to buf0 since order of complete is not guaranteed - bufctrl_write32(buf_reg32, buf0 | ((uint32_t)buf1 << 16)); - } else if (use_buf0) { + if (buf0_idle && buf1_idle) { + // both are idle, start fresh + io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN); + hw_endpoint_buffer_start(ep, ep_reg, buf_reg32); + } else if (buf0_idle) { + uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); bufctrl_write16(buf_reg16, buf0); - } else if (use_buf1) { + } else if (buf1_idle) { + uint16_t buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false); bufctrl_write16(buf_reg16 + 1, buf1); } } From d9d28b7e94aac295e08db5fff8649e1467a37399 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 27 Mar 2026 17:14:37 +0700 Subject: [PATCH 19/29] rename and clean up --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 33 ++- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 235 ++++++++++--------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 83 ++----- src/portable/raspberrypi/rp2040/rp2040_usb.h | 27 +-- 4 files changed, 159 insertions(+), 219 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index ea791f4f65..b52c3114fa 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -148,14 +148,9 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { while ((usb_hw->abort_done & abort_mask) != abort_mask) {} } - uint32_t buf_ctrl = USB_BUF_CTRL_SEL; // reset to buffer 0 - if (ep->next_pid) { - buf_ctrl |= USB_BUF_CTRL_DATA1_PID; - } - - io_rw_32 *buf_ctrl_reg = get_buf_ctrl(epnum, dir); - hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl); - hw_endpoint_reset_transfer(ep); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; // clear buffer control + rp2usb_reset_transfer(ep); if (rp2040_chip_version() >= 2) { usb_hw_clear->abort_done = abort_mask; @@ -185,9 +180,9 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { usb_hw_clear->buf_status = bit; buf_status &= ~bit; - if (hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, buf_id)) { + if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id)) { const uint16_t xferred_len = ep->xferred_len; - hw_endpoint_reset_transfer(ep); + rp2usb_reset_transfer(ep); dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); } } @@ -281,7 +276,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { if (buf0_idle && buf1_idle) { // both are idle, start fresh io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN); - hw_endpoint_buffer_start(ep, ep_reg, buf_reg32); + rp2usb_buffer_start(ep, ep_reg, buf_reg32); } else if (buf0_idle) { uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); bufctrl_write16(buf_reg16, buf0); @@ -534,7 +529,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to 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); - hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes); + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes); return true; } @@ -545,7 +540,7 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t 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); - hw_endpoint_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes); + rp2usb_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes); return true; } #endif @@ -554,15 +549,17 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void)rhport; const uint8_t epnum = tu_edpt_number(ep_addr); const tusb_dir_t dir = tu_edpt_dir(ep_addr); + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); if (epnum == 0) { // A stall on EP0 has to be armed so it can be cleared on the next setup packet usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; } - // stall and clear current pending buffer, may need to use EP_ABORT - io_rw_32 *buf_ctrl_reg = get_buf_ctrl(epnum, dir); - hwbuf_ctrl_set(buf_ctrl_reg, USB_BUF_CTRL_STALL); + // abort first then stall and clear current pending buffer + hw_endpoint_abort_xfer(ep); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = USB_BUF_CTRL_STALL; } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { @@ -573,8 +570,8 @@ 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_ctrl_reg = get_buf_ctrl(epnum, dir); - hwbuf_ctrl_clear_mask(buf_ctrl_reg, USB_BUF_CTRL_STALL); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; } } diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 3f4c954226..379ba075d3 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -119,14 +119,14 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { // forward declaration static void __tusb_irq_path_func(edpt_schedule_next)(void); TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value); -static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); +static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack uint8_t dev_addr = ep->dev_addr; uint8_t ep_addr = ep->ep_addr; uint xferred_len = ep->xferred_len; - hw_endpoint_reset_transfer(ep); + rp2usb_reset_transfer(ep); hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); // Schedule next pending EPX transfer (only for non-interrupt endpoints) @@ -150,7 +150,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - if (hw_endpoint_xfer_continue(epx, ep_reg, buf_reg, buf_id)) { + if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id)) { hw_xfer_complete(epx, XFER_RESULT_SUCCESS); } } @@ -174,7 +174,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { if (ep->interrupt_num == epnum) { io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); - const bool done = hw_endpoint_xfer_continue(ep, ep_reg, buf_reg, 0); + const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0); if (done) { hw_xfer_complete(ep, XFER_RESULT_SUCCESS); } @@ -185,8 +185,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } // All non-interrupt endpoints use shared EPX. -// Forward declared above hw_xfer_complete, defined after edpt_xfer below. - +// Forward declared above hw_xfer_complete, defined after epx_xfer below. // Save current EPX context, mark pending, switch to next_ep static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *next_ep) { const uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; @@ -214,7 +213,7 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *next_ep) { } else { uint16_t prev_xferred = next_ep->xferred_len; next_ep->pending = 0; - edpt_xfer(next_ep, next_ep->user_buf, NULL, next_ep->remaining_len); + epx_xfer(next_ep, next_ep->user_buf, NULL, next_ep->remaining_len); epx->xferred_len += prev_xferred; } } @@ -344,57 +343,6 @@ void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { hcd_rp2040_irq(); } -static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { - const uint8_t ep_addr = ep_desc->bEndpointAddress; - const uint16_t wMaxPacketSize = tu_edpt_packet_size(ep_desc); - const uint8_t transfer_type = ep_desc->bmAttributes.xfer; - // 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; - - if (transfer_type != TUSB_XFER_INTERRUPT) { - ep->dpram_buf = usbh_dpram->epx_data; - } else { - // from 15 interrupt endpoints pool - uint8_t int_idx; - for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) { - if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) { - ep->interrupt_num = int_idx + 1; - break; - } - } - assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS); - assert(ep_desc->bInterval > 0); - - //------------- dpram buf -------------// - // 15x64 last bytes of DPRAM for interrupt endpoint buffers - ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); - uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) | - (uint32_t)((ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); - usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; - - //------------- address control -------------// - const uint8_t epnum = tu_edpt_number(ep_addr); - uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB)); - if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { - addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS; - } - if (ep->need_pre) { - addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; - } - usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl; - - // Finally, activate interrupt endpoint - usb_hw_set->int_ep_ctrl = 1u << ep->interrupt_num; - } -} - //--------------------------------------------------------------------+ // HCD API //--------------------------------------------------------------------+ @@ -528,7 +476,55 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); hw_endpoint_t *ep = edpt_alloc(); TU_ASSERT(ep); - hw_endpoint_init(ep, dev_addr, ep_desc); + + const uint8_t ep_addr = ep_desc->bEndpointAddress; + const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc); + const uint8_t transfer_type = ep_desc->bmAttributes.xfer; + // const uint8_t bmInterval = ep_desc->bInterval; + + ep->max_packet_size = max_packet_size; + 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; + } else { + // from 15 interrupt endpoints pool + uint8_t int_idx; + for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) { + if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) { + ep->interrupt_num = int_idx + 1; + break; + } + } + assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS); + assert(ep_desc->bInterval > 0); + + //------------- dpram buf -------------// + // 15x64 last bytes of DPRAM for interrupt endpoint buffers + ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); + uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) | + (uint32_t)((ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); + usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; + + //------------- address control -------------// + const uint8_t epnum = tu_edpt_number(ep_addr); + uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB)); + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { + addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS; + } + if (ep->need_pre) { + addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; + } + usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl; + + // Finally, activate interrupt endpoint + usb_hw_set->int_ep_ctrl = 1u << ep->interrupt_num; + } return true; } @@ -551,54 +547,51 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value) { usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; } -static void edpt_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { - if (ep->transfer_type == TUSB_XFER_INTERRUPT) { - // For interrupt endpoint control and buffer is already configured - // Note: Interrupt is single buffered only - io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); - io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); - hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); - } else { - const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); - - // RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode. - // The buffer selector toggles even in single-buffered mode, so the previous transfer's status - // may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here. -#if defined(PICO_RP2040) && PICO_RP2040 == 1 - usbh_dpram->epx_buf_ctrl = 0; -#endif +// start a transfer on epx endpoint +static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { + const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); - // 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; - usbh_dpram->epx_ctrl = ep_ctrl; + // RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode. + // The buffer selector toggles even in single-buffered mode, so the previous transfer's status + // may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here. + #if defined(PICO_RP2040) && PICO_RP2040 == 1 + usbh_dpram->epx_buf_ctrl = 0; + #endif - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; - io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - hw_endpoint_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); + // 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; + usbh_dpram->epx_ctrl = ep_ctrl; - // addr control - usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); - epx = ep; + // addr control + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - // start transfer - const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | - (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sie_ctrl); - } + epx = ep; + + // start transfer + const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | + (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); + sie_start_xfer(sie_ctrl); } // Schedule next pending EPX transfer from ISR context static void __tusb_irq_path_func(edpt_schedule_next)(void) { // EPX may already be active if the completion callback started a new transfer - if (epx->active) return; + if (epx->active) { + return; + } for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { hw_endpoint_t *ep = &ep_pool[i]; - if (ep->pending == 0) continue; + if (ep->pending == 0) { + continue; + } if (ep->pending == 2) { // Pending setup: DPRAM already has the setup packet @@ -618,7 +611,7 @@ static void __tusb_irq_path_func(edpt_schedule_next)(void) { // Pending data transfer: preserve partial progress from preemption uint16_t prev_xferred = ep->xferred_len; ep->pending = 0; - edpt_xfer(ep, ep->user_buf, NULL, ep->remaining_len); + epx_xfer(ep, ep->user_buf, NULL, ep->remaining_len); epx->xferred_len += prev_xferred; // restore partial progress } return; // start only one transfer @@ -631,31 +624,39 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); TU_ASSERT(ep); - // Control endpoint can change direction 0x00 <-> 0x80 - if (ep_addr != ep->ep_addr) { - ep->ep_addr = ep_addr; - ep->next_pid = 1; // data and status stage start with DATA1 - } + if (ep->transfer_type == TUSB_XFER_INTERRUPT) { + // For interrupt endpoint control and buffer is already configured + // Note: Interrupt is single buffered only + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); + } else { + // Control endpoint can change direction 0x00 <-> 0x80 when changing stages + if (ep_addr != ep->ep_addr) { + ep->ep_addr = ep_addr; + ep->next_pid = 1; // data and status stage start with DATA1 + } - // If EPX is busy with another transfer, mark as pending - if (ep->transfer_type != TUSB_XFER_INTERRUPT && epx->active) { - ep->user_buf = buffer; - ep->remaining_len = buflen; - ep->pending = 1; -#ifdef HAS_STOP_EPX_ON_NAK - usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; -#else - // Only enable SOF preemption for non-control endpoints - if (tu_edpt_number(epx->ep_addr) != 0) { - usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | - (300 << USB_NAK_POLL_DELAY_LS_LSB); - usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + // If EPX is busy with another transfer, mark as pending + if (epx->active) { + ep->user_buf = buffer; + ep->remaining_len = buflen; + ep->pending = 1; + + #ifdef HAS_STOP_EPX_ON_NAK + usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + #else + // Only enable SOF round-robin for non-control endpoints + if (tu_edpt_number(epx->ep_addr) != 0) { + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + } + #endif + return true; } -#endif - return true; - } - edpt_xfer(ep, buffer, NULL, buflen); + epx_xfer(ep, buffer, NULL, buflen); + } return true; } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 433bd261d6..44191092e1 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -96,7 +96,7 @@ void rp2usb_init(void) { TU_LOG2_INT(sizeof(hw_endpoint_t)); } -void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) { +void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { ep->active = false; ep->remaining_len = 0; ep->xferred_len = 0; @@ -104,61 +104,23 @@ void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) { ep->is_xfer_fifo = false; } -void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask) { - const bool is_host = rp2usb_is_host_mode(); - uint32_t value = 0; - uint32_t buf_ctrl = *buf_ctrl_reg; - - if (and_mask) { - value = buf_ctrl & and_mask; - } - - if (or_mask) { - value |= or_mask; - if (or_mask & USB_BUF_CTRL_AVAIL) { - if (buf_ctrl & USB_BUF_CTRL_AVAIL) { - if (is_host) { -#if defined(PICO_RP2040) && PICO_RP2040 == 1 - // RP2040-E4: host buffer selector toggles in single-buffered mode, causing status - // to be written to BUF1 half and leaving stale AVAILABLE in BUF0 half. Clear it. - *buf_ctrl_reg = 0; -#else - panic("buf_ctrl @ 0x%lX already available (host)", (uintptr_t)buf_ctrl_reg); -#endif - } else { - panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg); - } - } - *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL; - - // 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 (!is_host) { - busy_wait_at_least_cycles(12); - } - } - } - - *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 + *buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // 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); + if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) { + if (!rp2usb_is_host_mode()) { + busy_wait_at_least_cycles(12); + } + *buf_reg = value; // then set AVAILABLE bit last } - - *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) { @@ -171,10 +133,12 @@ void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) // 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); + if (value & USB_BUF_CTRL_AVAIL) { + if (!rp2usb_is_host_mode()) { + busy_wait_at_least_cycles(12); + } + *buf_reg16 = value; // then set AVAILABLE bit last } - *buf_reg16 = value; // then set AVAILABLE bit (if set) last } // prepare buffer, move data if tx, return buffer control @@ -217,7 +181,7 @@ uint16_t __tusb_irq_path_func(bufctrl_prepare16)(struct hw_endpoint *ep, uint8_t } // Start transaction on hw buffer -void __tusb_irq_path_func(hw_endpoint_buffer_start)(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg) { +void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *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(); @@ -228,14 +192,6 @@ void __tusb_irq_path_func(hw_endpoint_buffer_start)(struct hw_endpoint *ep, io_r is_rx = (dir == TUSB_DIR_OUT); } - // 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_RP2_ERRATA_E4 - - #endif - // always compute and start with buffer 0 uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; @@ -268,15 +224,15 @@ void __tusb_irq_path_func(hw_endpoint_buffer_start)(struct hw_endpoint *ep, io_r 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, - uint16_t total_len) { +void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, + uint16_t total_len) { (void) ff; hw_endpoint_lock_update(ep, 1); if (ep->active) { // TODO: Is this acceptable for interrupt packets? TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr); - hw_endpoint_reset_transfer(ep); + rp2usb_reset_transfer(ep); } // Fill in info now that we're kicking off the hw @@ -310,7 +266,7 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * // 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); + rp2usb_reset_transfer(ep); const bool is_host = rp2usb_is_host_mode(); #if CFG_TUH_ENABLED @@ -342,7 +298,7 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 * } #endif - hw_endpoint_buffer_start(ep, ep_reg, buf_reg); + rp2usb_buffer_start(ep, ep_reg, buf_reg); hw_endpoint_lock_update(ep, -1); } @@ -382,7 +338,8 @@ static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, bool is_rx, // Returns true if transfer is complete. // buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered). -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) { +bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *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); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 7b79cd2b16..34687020ff 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -132,11 +132,11 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { //--------------------------------------------------------------------+ // Hardware Endpoint //--------------------------------------------------------------------+ -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_start(struct hw_endpoint *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); -void hw_endpoint_reset_transfer(struct hw_endpoint *ep); +void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, + uint16_t total_len); +bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id); +void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); +void rp2usb_reset_transfer(hw_endpoint_t *ep); TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) { // todo add critsec as necessary to prevent issues between worker and IRQ... @@ -147,26 +147,11 @@ TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct //--------------------------------------------------------------------+ // Hardware Buffer //--------------------------------------------------------------------+ -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_prepare16(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); -} - -TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) { - hwbuf_ctrl_update(buf_ctrl_reg, ~value, value); -} - -TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_clear_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) { - hwbuf_ctrl_update(buf_ctrl_reg, ~value, 0); -} - -static inline uintptr_t hw_data_offset(uint8_t *buf) { +TU_ATTR_ALWAYS_INLINE static inline uintptr_t hw_data_offset(uint8_t *buf) { // Remove usb base from buffer pointer return (uintptr_t)buf ^ (uintptr_t)usb_dpram; } From a9eb36b0fb5ad5322cb711954c98bf172faa1e06 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 27 Mar 2026 19:01:57 +0700 Subject: [PATCH 20/29] refactor --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 266 +++++++++---------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 379ba075d3..8145cfdcc3 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -116,12 +116,119 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { return hcd_port_speed_get(0) != tuh_speed_get(dev_addr); } -// forward declaration -static void __tusb_irq_path_func(edpt_schedule_next)(void); -TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value); +//--------------------------------------------------------------------+ +// EPX +//--------------------------------------------------------------------+ + +static void __tusb_irq_path_func(epx_schedule_next)(void); static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); -static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_result_t xfer_result) { +TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, tusb_dir_t ep_dir, bool need_pre) { + uint32_t value = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits + if (send_setup) { + value |= USB_SIE_CTRL_SEND_SETUP_BITS; + } else { + value |= (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); + } + if (need_pre) { + value |= USB_SIE_CTRL_PREAMBLE_EN_BITS; + } + + // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit + // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". + // We write everything except the START_TRANS bit first, then wait some cycles. + usb_hw->sie_ctrl = value; + busy_wait_at_least_cycles(12); + usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; +} + +// All non-interrupt endpoints use shared EPX. +// Save current EPX context, mark pending, switch to next_ep +static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *next_ep) { + const uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; + const uint16_t buf0_len = buf_ctrl & USB_BUF_CTRL_LEN_MASK; + epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); + epx->next_pid = (buf_ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; + if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { + epx->user_buf -= buf0_len; + } + epx->pending = 1; + epx->active = false; + usbh_dpram->epx_buf_ctrl = 0; + + if (next_ep->pending == 2) { + next_ep->ep_addr = 0; + next_ep->remaining_len = 8; + next_ep->xferred_len = 0; + next_ep->active = true; + next_ep->pending = 0; + epx = next_ep; + usb_hw->dev_addr_ctrl = next_ep->dev_addr; + sie_start_xfer(true, TUSB_DIR_OUT, next_ep->need_pre); + } else { + uint16_t prev_xferred = next_ep->xferred_len; + next_ep->pending = 0; + epx_xfer(next_ep, next_ep->user_buf, NULL, next_ep->remaining_len); + epx->xferred_len += prev_xferred; + } +} + +// Round-robin find next pending ep after current epx +static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) { + const uint cur_idx = (uint)(cur_ep - &ep_pool[0]); + for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) { + if (ep_pool[i].pending) { + return &ep_pool[i]; + } + } + for (uint i = 0; i < cur_idx; i++) { + if (ep_pool[i].pending) { + return &ep_pool[i]; + } + } + return NULL; +} + +// Schedule next pending EPX transfer from ISR context +static void __tusb_irq_path_func(epx_schedule_next)(void) { + // EPX may already be active if the completion callback started a new transfer + // if (epx->active) { + // return; + // } + + for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->pending == 0) { + continue; + } + + if (ep->pending == 2) { + // Pending setup: DPRAM already has the setup packet + ep->ep_addr = 0; + ep->remaining_len = 8; + ep->xferred_len = 0; + ep->active = true; + ep->pending = 0; + + epx = ep; + usb_hw->dev_addr_ctrl = ep->dev_addr; + + sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); + } else { + // Pending data transfer: preserve partial progress from preemption + uint16_t prev_xferred = ep->xferred_len; + ep->pending = 0; + epx_xfer(ep, ep->user_buf, NULL, ep->remaining_len); + epx->xferred_len += prev_xferred; // restore partial progress + } + return; // start only one transfer + } +} + +//--------------------------------------------------------------------+ +// Interrupt handlers +//--------------------------------------------------------------------+ +static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack uint8_t dev_addr = ep->dev_addr; uint8_t ep_addr = ep->ep_addr; @@ -131,17 +238,21 @@ static void __tusb_irq_path_func(hw_xfer_complete)(hw_endpoint_t *ep, xfer_resul // Schedule next pending EPX transfer (only for non-interrupt endpoints) if (ep == epx) { - edpt_schedule_next(); + epx_schedule_next(); + // hw_endpoint_t *next_ep = epx_next_pending(epx); + // if (next_ep != NULL) { + // epx_switch_ep(next_ep); + // } } } -static void __tusb_irq_path_func(handle_hwbuf_status)(void) { +static void __tusb_irq_path_func(handle_buf_status_isr)(void) { pico_trace("buf_status 0x%08lx\n", buf_status); enum { BUF_STATUS_EPX = 1u }; - // Check EPX first (bit 0). EPX is currently single-buffered, always use buf_id=0. + // Check EPX first (bit 0). EPX is currently single-buffered, always use buf_id=0.3 // Double-buffered: if both buffers completed at once, buf_status re-sets // immediately after clearing (datasheet Table 406). Process the second buffer too. while (usb_hw->buf_status & BUF_STATUS_EPX) { @@ -151,7 +262,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id)) { - hw_xfer_complete(epx, XFER_RESULT_SUCCESS); + xfer_complete_isr(epx, XFER_RESULT_SUCCESS); } } @@ -176,7 +287,7 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0); if (done) { - hw_xfer_complete(ep, XFER_RESULT_SUCCESS); + xfer_complete_isr(ep, XFER_RESULT_SUCCESS); } break; } @@ -184,56 +295,6 @@ static void __tusb_irq_path_func(handle_hwbuf_status)(void) { } } -// All non-interrupt endpoints use shared EPX. -// Forward declared above hw_xfer_complete, defined after epx_xfer below. -// Save current EPX context, mark pending, switch to next_ep -static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *next_ep) { - const uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; - const uint16_t buf0_len = buf_ctrl & USB_BUF_CTRL_LEN_MASK; - epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); - epx->next_pid = (buf_ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; - if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { - epx->user_buf -= buf0_len; - } - epx->pending = 1; - epx->active = false; - usbh_dpram->epx_buf_ctrl = 0; - - if (next_ep->pending == 2) { - next_ep->ep_addr = 0; - next_ep->remaining_len = 8; - next_ep->xferred_len = 0; - next_ep->active = true; - next_ep->pending = 0; - epx = next_ep; - usb_hw->dev_addr_ctrl = next_ep->dev_addr; - const uint32_t sc = USB_SIE_CTRL_SEND_SETUP_BITS | - (next_ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sc); - } else { - uint16_t prev_xferred = next_ep->xferred_len; - next_ep->pending = 0; - epx_xfer(next_ep, next_ep->user_buf, NULL, next_ep->remaining_len); - epx->xferred_len += prev_xferred; - } -} - -// Round-robin find next pending ep after current epx -static hw_endpoint_t *__tusb_irq_path_func(epx_find_pending)(void) { - const uint start = (uint)(epx - &ep_pool[0]) + 1; - for (uint i = start; i < TU_ARRAY_SIZE(ep_pool); i++) { - if (ep_pool[i].pending) { - return &ep_pool[i]; - } - } - for (uint i = 0; i < start - 1; i++) { - if (ep_pool[i].pending) { - return &ep_pool[i]; - } - } - return NULL; -} - static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { const uint32_t status = usb_hw->ints; @@ -258,11 +319,11 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // AND TRANS_COMPLETE as the stall is an alternative response // to one of those events usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; - hw_xfer_complete(epx, XFER_RESULT_STALLED); + xfer_complete_isr(epx, XFER_RESULT_STALLED); } if (status & USB_INTS_BUFF_STATUS_BITS) { - handle_hwbuf_status(); + handle_buf_status_isr(); } if (status & USB_INTS_TRANS_COMPLETE_BITS) { @@ -271,32 +332,23 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // only handle a setup packet if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { epx->xferred_len = 8; - hw_xfer_complete(epx, XFER_RESULT_SUCCESS); + xfer_complete_isr(epx, XFER_RESULT_SUCCESS); } else { // Don't care. Will handle this in buff status } } #ifdef HAS_STOP_EPX_ON_NAK - // RP2350: hardware stops EPX on NAK automatically if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; - hw_endpoint_t *next_ep = NULL; - if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { - next_ep = epx_find_pending(); - } - if (next_ep) { + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep != NULL) { epx_switch_ep(next_ep); } else { - // No preemption: disable stop-on-NAK, restart current transfer + // No switch: disable stop-on-NAK, restart current transfer usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; - if (epx->active) { - const tusb_dir_t ep_dir = tu_edpt_dir(epx->ep_addr); - const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | - (epx->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sie_ctrl); - } + sie_start_xfer(false, tu_edpt_dir(epx->ep_addr), epx->need_pre); } } #else @@ -304,22 +356,22 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { if (status & USB_INTS_HOST_SOF_BITS) { (void) usb_hw->sof_rd; // clear SOF by reading SOF_RD if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { - hw_endpoint_t *next_ep = epx_find_pending(); + hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep) { usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} busy_wait_at_least_cycles(12); if (usb_hw->buf_status & 1u) { usb_hw->nak_poll = USB_NAK_POLL_RESET; - handle_hwbuf_status(); + handle_buf_status_isr(); } else { epx_switch_ep(next_ep); } } else { usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; - usb_hw->nak_poll = USB_NAK_POLL_RESET; + usb_hw->nak_poll = USB_NAK_POLL_RESET; } - } else if (!epx_find_pending()) { + } else if (!epx_next_pending(epx)) { // EPX is on control endpoint or inactive — disable SOF if nothing pending usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; usb_hw->nak_poll = USB_NAK_POLL_RESET; @@ -536,17 +588,6 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { return false; // TODO not implemented yet } -TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(uint32_t value) { - value |= (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK); // preserve base bits - - // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit - // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". - // We write everything except the START_TRANS bit first, then wait some cycles. - usb_hw->sie_ctrl = value; - busy_wait_at_least_cycles(12); - usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; -} - // start a transfer on epx endpoint static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { const uint8_t ep_num = tu_edpt_number(ep->ep_addr); @@ -575,47 +616,7 @@ static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t epx = ep; // start transfer - const uint32_t sie_ctrl = (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) | - (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sie_ctrl); -} - -// Schedule next pending EPX transfer from ISR context -static void __tusb_irq_path_func(edpt_schedule_next)(void) { - // EPX may already be active if the completion callback started a new transfer - if (epx->active) { - return; - } - - for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hw_endpoint_t *ep = &ep_pool[i]; - if (ep->pending == 0) { - continue; - } - - if (ep->pending == 2) { - // Pending setup: DPRAM already has the setup packet - ep->ep_addr = 0; - ep->remaining_len = 8; - ep->xferred_len = 0; - ep->active = true; - ep->pending = 0; - - epx = ep; - usb_hw->dev_addr_ctrl = ep->dev_addr; - - const uint32_t sie_ctrl = USB_SIE_CTRL_SEND_SETUP_BITS | - (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sie_ctrl); - } else { - // Pending data transfer: preserve partial progress from preemption - uint16_t prev_xferred = ep->xferred_len; - ep->pending = 0; - epx_xfer(ep, ep->user_buf, NULL, ep->remaining_len); - epx->xferred_len += prev_xferred; // restore partial progress - } - return; // start only one transfer - } + sie_start_xfer(false, ep_dir, ep->need_pre); } bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { @@ -705,8 +706,7 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet usb_hw->dev_addr_ctrl = dev_addr; // Set device address // Set pre if we are a low speed device on full speed hub - const uint32_t sie_ctrl = USB_SIE_CTRL_SEND_SETUP_BITS | (ep->need_pre ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0); - sie_start_xfer(sie_ctrl); + sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); return true; } From 7d004257d9cb849e90166a50c0f751b162fd6a68 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 28 Mar 2026 12:12:20 +0700 Subject: [PATCH 21/29] host epx clean up --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 211 ++++++++----------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 9 +- src/portable/raspberrypi/rp2040/rp2040_usb.h | 14 +- 3 files changed, 107 insertions(+), 127 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 8145cfdcc3..167a80a7a7 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -44,11 +44,11 @@ #include "host/hcd.h" #include "host/usbh.h" -// port 0 is native USB port, other is counted as software PIO -#define RHPORT_NATIVE 0 + // port 0 is native USB port, other is counted as software PIO + #define RHPORT_NATIVE 0 //--------------------------------------------------------------------+ -// Low level rp2040 controller functions +// //--------------------------------------------------------------------+ // Host mode uses one shared endpoint register for non-interrupt endpoint @@ -119,10 +119,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { //--------------------------------------------------------------------+ // EPX //--------------------------------------------------------------------+ - -static void __tusb_irq_path_func(epx_schedule_next)(void); -static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); - TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, tusb_dir_t ep_dir, bool need_pre) { uint32_t value = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits if (send_setup) { @@ -142,34 +138,59 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, tusb_di usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; } -// All non-interrupt endpoints use shared EPX. -// Save current EPX context, mark pending, switch to next_ep -static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *next_ep) { +// prepare epx_ctrl register for new endpoint +TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(hw_endpoint_t *ep) { + // RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode. + // The buffer selector toggles even in single-buffered mode, so the previous transfer's status + // may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here. + #if defined(PICO_RP2040) && PICO_RP2040 == 1 + usbh_dpram->epx_buf_ctrl = 0; + #endif + + // ep control + const uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf); + usbh_dpram->epx_ctrl = ep_ctrl; +} + +// save on-going context +static void __tusb_irq_path_func(epx_save_context)(void) { const uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; - const uint16_t buf0_len = buf_ctrl & USB_BUF_CTRL_LEN_MASK; + const uint16_t buf0_len = buf_ctrl & USB_BUF_CTRL_LEN_MASK; // TODO handle double buffered case epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); epx->next_pid = (buf_ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { epx->user_buf -= buf0_len; } - epx->pending = 1; - epx->active = false; + epx->pending = 1; + epx->active = false; + usbh_dpram->epx_buf_ctrl = 0; +} + +// All non-interrupt endpoints use shared EPX. +// Save current EPX context, mark pending, switch to ep +static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { + const bool is_setup = (ep->pending == 2); + + epx = ep; // switch pointer + ep->pending = 0; + ep->active = true; - if (next_ep->pending == 2) { - next_ep->ep_addr = 0; - next_ep->remaining_len = 8; - next_ep->xferred_len = 0; - next_ep->active = true; - next_ep->pending = 0; - epx = next_ep; - usb_hw->dev_addr_ctrl = next_ep->dev_addr; - sie_start_xfer(true, TUSB_DIR_OUT, next_ep->need_pre); + if (is_setup) { + usb_hw->dev_addr_ctrl = ep->dev_addr; + sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); } else { - uint16_t prev_xferred = next_ep->xferred_len; - next_ep->pending = 0; - epx_xfer(next_ep, next_ep->user_buf, NULL, next_ep->remaining_len); - epx->xferred_len += prev_xferred; + const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + + epx_ctrl_prepare(ep); + rp2usb_buffer_start(ep, ep_reg, buf_reg); + + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(false, ep_dir, ep->need_pre); // start transfer } } @@ -189,41 +210,6 @@ static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ return NULL; } -// Schedule next pending EPX transfer from ISR context -static void __tusb_irq_path_func(epx_schedule_next)(void) { - // EPX may already be active if the completion callback started a new transfer - // if (epx->active) { - // return; - // } - - for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { - hw_endpoint_t *ep = &ep_pool[i]; - if (ep->pending == 0) { - continue; - } - - if (ep->pending == 2) { - // Pending setup: DPRAM already has the setup packet - ep->ep_addr = 0; - ep->remaining_len = 8; - ep->xferred_len = 0; - ep->active = true; - ep->pending = 0; - - epx = ep; - usb_hw->dev_addr_ctrl = ep->dev_addr; - - sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); - } else { - // Pending data transfer: preserve partial progress from preemption - uint16_t prev_xferred = ep->xferred_len; - ep->pending = 0; - epx_xfer(ep, ep->user_buf, NULL, ep->remaining_len); - epx->xferred_len += prev_xferred; // restore partial progress - } - return; // start only one transfer - } -} //--------------------------------------------------------------------+ // Interrupt handlers @@ -236,13 +222,12 @@ static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_resu rp2usb_reset_transfer(ep); hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); - // Schedule next pending EPX transfer (only for non-interrupt endpoints) + // Carry more transfer on epx if (ep == epx) { - epx_schedule_next(); - // hw_endpoint_t *next_ep = epx_next_pending(epx); - // if (next_ep != NULL) { - // epx_switch_ep(next_ep); - // } + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep != NULL) { + epx_switch_ep(next_ep); + } } } @@ -338,12 +323,13 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } } -#ifdef HAS_STOP_EPX_ON_NAK + #ifdef HAS_STOP_EPX_ON_NAK if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep != NULL) { + epx_save_context(); epx_switch_ep(next_ep); } else { // No switch: disable stop-on-NAK, restart current transfer @@ -412,7 +398,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // Remove shared irq if it was previously added so as not to fill up shared irq slots irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); - irq_add_shared_handler(USBCTRL_IRQ, hcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); // clear epx and interrupt eps @@ -588,37 +573,6 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { return false; // TODO not implemented yet } -// start a transfer on epx endpoint -static void epx_xfer(hw_endpoint_t *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { - const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); - - // RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode. - // The buffer selector toggles even in single-buffered mode, so the previous transfer's status - // may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here. - #if defined(PICO_RP2040) && PICO_RP2040 == 1 - usbh_dpram->epx_buf_ctrl = 0; - #endif - - // 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; - usbh_dpram->epx_ctrl = ep_ctrl; - - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; - io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, ff, total_len); - - // addr control - usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - - epx = ep; - - // start transfer - sie_start_xfer(false, ep_dir, ep->need_pre); -} - bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void)rhport; @@ -639,6 +593,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b } // If EPX is busy with another transfer, mark as pending + rp2usb_critical_enter(); if (epx->active) { ep->user_buf = buffer; ep->remaining_len = buflen; @@ -653,10 +608,21 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; } #endif - return true; - } + } else { + const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + + epx = ep; - epx_xfer(ep, buffer, NULL, buflen); + epx_ctrl_prepare(ep); + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl + + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(false, ep_dir, ep->need_pre); // start transfer + } + rp2usb_critical_exit(); } return true; @@ -681,33 +647,30 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); TU_ASSERT(ep); - ep->ep_addr = 0; // setup is OUT - - // If EPX is busy, mark as pending setup (DPRAM already has the packet) - if (epx->active) { - ep->pending = 2; -#ifdef HAS_STOP_EPX_ON_NAK - usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; -#else - if (tu_edpt_number(epx->ep_addr) != 0) { - usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | - (300 << USB_NAK_POLL_DELAY_LS_LSB); - usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; - } -#endif - return true; - } + rp2usb_critical_enter(); + ep->ep_addr = 0; // setup is OUT ep->remaining_len = 8; ep->xferred_len = 0; - ep->active = true; - epx = ep; - usb_hw->dev_addr_ctrl = dev_addr; // Set device address + // If EPX is busy, mark as pending setup (DPRAM already has the packet) + if (epx->active) { + ep->pending = 2; // setup + #ifdef HAS_STOP_EPX_ON_NAK + usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + #else + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + #endif + } else { + epx = ep; + ep->active = true; - // Set pre if we are a low speed device on full speed hub - sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); + usb_hw->dev_addr_ctrl = dev_addr; // Set device address + sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); // start transfer + } + rp2usb_critical_exit(); return true; } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 44191092e1..15d4d723f2 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -46,6 +46,8 @@ static bool e15_is_critical_frame_period(void); static uint8_t rp2040_chipversion = 2; #endif +critical_section_t rp2usb_lock; + //--------------------------------------------------------------------+ // Implementation //--------------------------------------------------------------------+ @@ -94,6 +96,8 @@ void rp2usb_init(void) { #endif TU_LOG2_INT(sizeof(hw_endpoint_t)); + + critical_section_init(&rp2usb_lock); } void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { @@ -383,10 +387,11 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ const bool is_short = xact_bytes < ep->max_packet_size; const bool is_done = is_short || (buf_ctrl16 & USB_BUF_CTRL_LAST); - // short packet on rx with double buffer: abort the other half (if not last) and reset double-buffer state. + // 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 + // After abort, zero buf_ctrl. + // Note: Host mode we cannot save next transfer data due to shared epx -> force single 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)); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 34687020ff..3b45c7c94b 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -7,6 +7,8 @@ #include "hardware/resets.h" #include "hardware/timer.h" +#include "pico/critical_section.h" + #include "common/tusb_common.h" #include "osal/osal.h" #include "common/tusb_fifo.h" @@ -129,6 +131,15 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; } +extern critical_section_t rp2usb_lock; + +TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_enter(void) { + critical_section_enter_blocking(&rp2usb_lock); +} +TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) { + critical_section_exit(&rp2usb_lock); +} + //--------------------------------------------------------------------+ // Hardware Endpoint //--------------------------------------------------------------------+ @@ -138,7 +149,8 @@ bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); void rp2usb_reset_transfer(hw_endpoint_t *ep); -TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) { + +TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint *ep, __unused int delta) { // todo add critsec as necessary to prevent issues between worker and IRQ... // note that this is perhaps as simple as disabling IRQs because it would make // sense to have worker and IRQ on same core, however I think using critsec is about equivalent. From dfcd271400c31e15a7d38b319c65173c3e4227c7 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 28 Mar 2026 23:27:39 +0700 Subject: [PATCH 22/29] rp2 common refactor --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 4 +- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 54 +++--- src/portable/raspberrypi/rp2040/rp2040_usb.c | 179 +++++++++---------- src/portable/raspberrypi/rp2040/rp2040_usb.h | 6 +- 4 files changed, 117 insertions(+), 126 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index b52c3114fa..d4c1bf708b 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -180,7 +180,7 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { usb_hw_clear->buf_status = bit; buf_status &= ~bit; - if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id)) { + if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id, dir == TUSB_DIR_OUT)) { const uint16_t xferred_len = ep->xferred_len; rp2usb_reset_transfer(ep); dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); @@ -276,7 +276,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { if (buf0_idle && buf1_idle) { // both are idle, start fresh io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN); - rp2usb_buffer_start(ep, ep_reg, buf_reg32); + rp2usb_buffer_start(ep, ep_reg, buf_reg32, false, false); } else if (buf0_idle) { uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); bufctrl_write16(buf_reg16, buf0); diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 167a80a7a7..31bbfc0be0 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -35,8 +35,11 @@ #define HAS_STOP_EPX_ON_NAK #endif +// port 0 is native USB port, other is counted as software PIO + #define RHPORT_NATIVE 0 + //--------------------------------------------------------------------+ -// INCLUDE + // INCLUDE //--------------------------------------------------------------------+ #include "rp2040_usb.h" #include "osal/osal.h" @@ -44,9 +47,6 @@ #include "host/hcd.h" #include "host/usbh.h" - // port 0 is native USB port, other is counted as software PIO - #define RHPORT_NATIVE 0 - //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ @@ -119,12 +119,12 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { //--------------------------------------------------------------------+ // EPX //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, tusb_dir_t ep_dir, bool need_pre) { +TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, bool is_rx, bool need_pre) { uint32_t value = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits if (send_setup) { value |= USB_SIE_CTRL_SEND_SETUP_BITS; } else { - value |= (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); + value |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); } if (need_pre) { value |= USB_SIE_CTRL_PREAMBLE_EN_BITS; @@ -179,18 +179,18 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { if (is_setup) { usb_hw->dev_addr_ctrl = ep->dev_addr; - sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); + sie_start_xfer(true, false, ep->need_pre); } else { - const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; - io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const bool is_rx = tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN; + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; epx_ctrl_prepare(ep); - rp2usb_buffer_start(ep, ep_reg, buf_reg); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, is_rx || ep->transfer_type == TUSB_XFER_INTERRUPT); usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - sie_start_xfer(false, ep_dir, ep->need_pre); // start transfer + sie_start_xfer(false, is_rx, ep->need_pre); // start transfer } } @@ -246,13 +246,13 @@ static void __tusb_irq_path_func(handle_buf_status_isr)(void) { io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id)) { + if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) { xfer_complete_isr(epx, XFER_RESULT_SUCCESS); } } // Check "interrupt" (asynchronous) endpoints for both IN and OUT - uint32_t buf_status = usb_hw->buf_status & ~1u; + uint32_t buf_status = usb_hw->buf_status & (uint32_t)~BUF_STATUS_EPX; while (buf_status) { // ctz/clz is faster than loop which has only a few bit set in general const uint8_t idx = (uint8_t) __builtin_ctz(buf_status); @@ -268,9 +268,9 @@ static void __tusb_irq_path_func(handle_buf_status_isr)(void) { for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) { hw_endpoint_t *ep = &ep_pool[e]; if (ep->interrupt_num == epnum) { - io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); - io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); - const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0); + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN); if (done) { xfer_complete_isr(ep, XFER_RESULT_SUCCESS); } @@ -326,21 +326,20 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { #ifdef HAS_STOP_EPX_ON_NAK if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; - hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep != NULL) { epx_save_context(); epx_switch_ep(next_ep); } else { - // No switch: disable stop-on-NAK, restart current transfer + // No pending endpoint, this is the only active one: disable stop-on-NAK, continue current transfer usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; - sie_start_xfer(false, tu_edpt_dir(epx->ep_addr), epx->need_pre); + sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre); } } -#else + #else // RP2040: on SOF, stop and switch if there's a pending ep if (status & USB_INTS_HOST_SOF_BITS) { - (void) usb_hw->sof_rd; // clear SOF by reading SOF_RD + (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep) { @@ -360,10 +359,10 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } else if (!epx_next_pending(epx)) { // EPX is on control endpoint or inactive — disable SOF if nothing pending usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; - usb_hw->nak_poll = USB_NAK_POLL_RESET; + usb_hw->nak_poll = USB_NAK_POLL_RESET; } } -#endif + #endif if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; @@ -611,6 +610,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b } else { const uint8_t ep_num = tu_edpt_number(ep->ep_addr); const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); + const bool is_rx = (ep_dir == TUSB_DIR_IN); io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; @@ -620,7 +620,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - sie_start_xfer(false, ep_dir, ep->need_pre); // start transfer + sie_start_xfer(false, is_rx, ep->need_pre); // start transfer } rp2usb_critical_exit(); } @@ -667,7 +667,7 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet ep->active = true; usb_hw->dev_addr_ctrl = dev_addr; // Set device address - sie_start_xfer(true, TUSB_DIR_OUT, ep->need_pre); // start transfer + sie_start_xfer(true, false, ep->need_pre); // start transfer } rp2usb_critical_exit(); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 15d4d723f2..49c948af68 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -58,7 +58,7 @@ static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) { } } -#if CFG_TUD_EDPT_DEDICATED_HWFIFO + #if CFG_TUD_EDPT_DEDICATED_HWFIFO void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) { (void)access_mode; unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len); @@ -68,25 +68,25 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co (void)access_mode; unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len); } -#endif + #endif void rp2usb_init(void) { // Reset usb controller reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); -#ifdef __GNUC__ - // Clear any previous state just in case -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#if __GNUC__ > 6 -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif -#endif + #ifdef __GNUC__ + // Clear any previous state just in case + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Warray-bounds" + #if __GNUC__ > 6 + #pragma GCC diagnostic ignored "-Wstringop-overflow" + #endif + #endif memset(usb_dpram, 0, sizeof(*usb_dpram)); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif // Mux the controller to the onboard usb phy usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; @@ -101,10 +101,10 @@ void rp2usb_init(void) { } void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { - ep->active = false; + ep->active = false; ep->remaining_len = 0; - ep->xferred_len = 0; - ep->user_buf = 0; + ep->xferred_len = 0; + ep->user_buf = 0; ep->is_xfer_fifo = false; } @@ -134,9 +134,7 @@ void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) } *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. + // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access if (value & USB_BUF_CTRL_AVAIL) { if (!rp2usb_is_host_mode()) { busy_wait_at_least_cycles(12); @@ -146,7 +144,7 @@ void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) } // prepare buffer, move data if tx, return buffer control -uint16_t __tusb_irq_path_func(bufctrl_prepare16)(struct hw_endpoint *ep, uint8_t *dpram_buf, bool is_rx) { +uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *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; @@ -158,13 +156,13 @@ uint16_t __tusb_irq_path_func(bufctrl_prepare16)(struct hw_endpoint *ep, uint8_t if (!is_rx) { if (buflen) { - // Copy data from user buffer/fifo to hw buffer - #if CFG_TUD_EDPT_DEDICATED_HWFIFO + // Copy data from user buffer/fifo to hw buffer + #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ep->is_xfer_fifo) { // not in sram, may mess up timing with E15 workaround tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL); } else - #endif + #endif { unaligned_memcpy(dpram_buf, ep->user_buf, buflen); ep->user_buf += buflen; @@ -185,33 +183,13 @@ uint16_t __tusb_irq_path_func(bufctrl_prepare16)(struct hw_endpoint *ep, uint8_t } // Start transaction on hw buffer -void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *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; - if (is_host) { - is_rx = (dir == TUSB_DIR_IN); - } else { - is_rx = (dir == TUSB_DIR_OUT); - } - +void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx, + bool force_single) { // always compute and start with buffer 0 uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; // Note: device EP0 does not have an endpoint control register if (ep_reg != NULL) { - #if 1 - const bool force_single = (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) { - force_single = true; - } - #endif -#endif - uint32_t ep_ctrl = *ep_reg; if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data @@ -224,13 +202,13 @@ void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_r *ep_reg = ep_ctrl; } - // Finally, write to buffer_control which will trigger the transfer the next time the controller polls this endpoint + // Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint bufctrl_write32(buf_reg, buf_ctrl); } void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { - (void) ff; + (void)ff; hw_endpoint_lock_update(ep, 1); if (ep->active) { @@ -241,20 +219,22 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u // Fill in info now that we're kicking off the hw ep->remaining_len = total_len; - ep->xferred_len = 0; - ep->active = true; + ep->xferred_len = 0; + ep->active = true; -#if CFG_TUD_EDPT_DEDICATED_HWFIFO + #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ff != NULL) { ep->user_fifo = ff; ep->is_xfer_fifo = true; } else -#endif + #endif { ep->user_buf = buffer; ep->is_xfer_fifo = false; } + const bool is_host = rp2usb_is_host_mode(); + if (ep->future_len > 0) { // only on rx endpoint const uint8_t future_len = ep->future_len; @@ -272,7 +252,6 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u const uint16_t xferred_len = ep->xferred_len; rp2usb_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); @@ -302,12 +281,20 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u } #endif - rp2usb_buffer_start(ep, ep_reg, buf_reg); + const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); + #if CFG_TUH_ENABLED + const bool force_single = (is_host && (is_rx || ep->transfer_type == TUSB_XFER_INTERRUPT)); + #else + const bool force_single = false; + #endif + + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, force_single); 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, uint16_t buf_ctrl, uint8_t *dpram_buf) { +static uint16_t __tusb_irq_path_func(bufctrl_sync16)(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) { @@ -342,17 +329,10 @@ static uint16_t __tusb_irq_path_func(hwbuf_sync)(hw_endpoint_t *ep, bool is_rx, // Returns true if transfer is complete. // buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered). -bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, - uint8_t buf_id) { +bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, + bool is_rx) { 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) { // probably land here due to short packet on rx with double buffered hw_endpoint_lock_update(ep, -1); @@ -360,7 +340,7 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ } const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); - (void)is_double; + const bool is_host = rp2usb_is_host_mode(); #if CFG_TUSB_RP2_ERRATA_E4 const bool need_e4_fix = (is_host && !is_double); @@ -372,7 +352,7 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ // 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. - uint8_t* dpram_buf = ep->dpram_buf; + uint8_t *dpram_buf = ep->dpram_buf; if (buf_id) { #if CFG_TUSB_RP2_ERRATA_E4 if (!need_e4_fix) // incorrect buf_id, buffer pointer is still buf0 @@ -382,7 +362,10 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ } } - const uint16_t xact_bytes = hwbuf_sync(ep, is_rx, buf_ctrl16, dpram_buf); + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; + uint16_t buf_ctrl16 = *(buf_reg16 + buf_id); + + const uint16_t xact_bytes = bufctrl_sync16(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); @@ -391,40 +374,48 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ // 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. - // Note: Host mode we cannot save next transfer data due to shared epx -> force single + // Note: Host mode we cannot save next transfer data due to shared epx --> force single 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) + #if CFG_TUH_ENABLED + if (is_host) {} #endif - { - usb_hw_set->abort = abort_bit; - while ((usb_hw->abort_done & abort_bit) != abort_bit) {} - } - // 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; // roll back pid if aborted - } + #if CFG_TUD_ENABLED + if (!is_host) { + 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); // IN endpoint - *buf_reg = 0; // reset buffer control + #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) {} + } - #if CFG_TUSB_RP2_ERRATA_E2 - if (rp2040_chipversion >= 2) - #endif - { - usb_hw_clear->abort_done = abort_bit; - usb_hw_clear->abort = abort_bit; + // 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; // roll back pid if aborted + } + + *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; + } } + #endif hw_endpoint_lock_update(ep, -1); return true; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 3b45c7c94b..c4c7e625e1 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -145,8 +145,8 @@ TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) { //--------------------------------------------------------------------+ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); -bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id); -void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg); +bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx); +void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx, bool force_single); void rp2usb_reset_transfer(hw_endpoint_t *ep); @@ -161,7 +161,7 @@ TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct //--------------------------------------------------------------------+ 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_prepare16(struct hw_endpoint *ep, uint8_t *dpram_buf, bool is_rx); +uint16_t bufctrl_prepare16(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx); TU_ATTR_ALWAYS_INLINE static inline uintptr_t hw_data_offset(uint8_t *buf) { // Remove usb base from buffer pointer From 31ec5a7d3afdb79c95cfc6eeaf9659066d83fd69 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 30 Mar 2026 12:19:20 +0700 Subject: [PATCH 23/29] hcd rp2 add double buffered for control endpoint transfers. --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 42 +++++++------------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 34 ++++++++-------- src/portable/raspberrypi/rp2040/rp2040_usb.h | 7 ++++ 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 31bbfc0be0..3a99e7275d 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -55,12 +55,6 @@ static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint -// Flags we set by default in sie_ctrl (we add other bits on top) -enum { - SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS, - SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS | - USB_SIE_CTRL_KEEP_ALIVE_EN_BITS -}; enum { SIE_CTRL_SPEED_DISCONNECT = 0, @@ -187,7 +181,7 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; epx_ctrl_prepare(ep); - rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, is_rx || ep->transfer_type == TUSB_XFER_INTERRUPT); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, ep->transfer_type == TUSB_XFER_INTERRUPT); usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); sie_start_xfer(false, is_rx, ep->need_pre); // start transfer @@ -340,26 +334,20 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // RP2040: on SOF, stop and switch if there's a pending ep if (status & USB_INTS_HOST_SOF_BITS) { (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD - if (epx->active && tu_edpt_number(epx->ep_addr) != 0) { - hw_endpoint_t *next_ep = epx_next_pending(epx); - if (next_ep) { + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep == NULL) { + // no more pending --> disable SOF + usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; + usb_hw->nak_poll = USB_NAK_POLL_RESET; + } else { + // stop transfer if is active + if (epx->active) { usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} - busy_wait_at_least_cycles(12); - if (usb_hw->buf_status & 1u) { - usb_hw->nak_poll = USB_NAK_POLL_RESET; - handle_buf_status_isr(); - } else { - epx_switch_ep(next_ep); - } - } else { - usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; - usb_hw->nak_poll = USB_NAK_POLL_RESET; } - } else if (!epx_next_pending(epx)) { - // EPX is on control endpoint or inactive — disable SOF if nothing pending - usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; - usb_hw->nak_poll = USB_NAK_POLL_RESET; + + epx_save_context(); + epx_switch_ep(next_ep); } } #endif @@ -602,10 +590,8 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; #else // Only enable SOF round-robin for non-control endpoints - if (tu_edpt_number(epx->ep_addr) != 0) { - usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB); - usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; - } + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; #endif } else { const uint8_t ep_num = tu_edpt_number(ep->ep_addr); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 49c948af68..ffb5fcbc69 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -283,7 +283,7 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); #if CFG_TUH_ENABLED - const bool force_single = (is_host && (is_rx || ep->transfer_type == TUSB_XFER_INTERRUPT)); + const bool force_single = (is_host && ep->transfer_type == TUSB_XFER_INTERRUPT); #else const bool force_single = false; #endif @@ -339,23 +339,17 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ return false; } - const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); const bool is_host = rp2usb_is_host_mode(); - - #if CFG_TUSB_RP2_ERRATA_E4 - const bool need_e4_fix = (is_host && !is_double); - #endif + const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed. - // 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 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. + // BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1 + // since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered. uint8_t *dpram_buf = ep->dpram_buf; if (buf_id) { #if CFG_TUSB_RP2_ERRATA_E4 - if (!need_e4_fix) // incorrect buf_id, buffer pointer is still buf0 + if (!(is_host && !is_double)) // incorrect buf_id, buffer data is still buf0 #endif { dpram_buf += 64; // buf1 offset @@ -368,16 +362,24 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ const uint16_t xact_bytes = bufctrl_sync16(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); + const bool is_done = is_short || is_last; - // Short packet on rx with double buffer: abort the other half (if not last) and reset double-buffer state. + // Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control. // 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. - // Note: Host mode we cannot save next transfer data due to shared epx --> force single + // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4 + // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale) if (is_short && is_double && is_rx && !is_last) { #if CFG_TUH_ENABLED - if (is_host) {} + if (is_host) { + // stop current transfer + uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; + sie_ctrl |= USB_SIE_CTRL_STOP_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl; + // maybe wait until STOP_TRANS bit is clear + + *buf_reg = 0; // reset buffer control + } #endif #if CFG_TUD_ENABLED diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index c4c7e625e1..2ba9d018e3 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -70,6 +70,13 @@ #define __tusb_irq_path_func(x) x #endif +// Flags we set by default in sie_ctrl (we add other bits on top) +enum { + SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS, + SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS | + USB_SIE_CTRL_KEEP_ALIVE_EN_BITS +}; + //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ From 660c8ca087c8006433301d9208143438f5f456a1 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 31 Mar 2026 16:01:58 +0700 Subject: [PATCH 24/29] host abort transfer on short packet in double buffer. --- src/portable/raspberrypi/rp2040/rp2040_usb.c | 65 ++++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index ffb5fcbc69..21237b0592 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -102,6 +102,7 @@ void rp2usb_init(void) { void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { ep->active = false; + ep->pending = 0; ep->remaining_len = 0; ep->xferred_len = 0; ep->user_buf = 0; @@ -172,9 +173,8 @@ uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpr buf_ctrl |= USB_BUF_CTRL_FULL; } - // Is this the last buffer? Only really matters for host mode. Will trigger - // the trans complete irq but also stop it polling. We only really care about - // trans complete for setup packets being sent + // Is this the last buffer? Will trigger the trans complete irq but also stop it polling. + // This is used to detect setup packets being sent in host mode if (ep->remaining_len == 0) { buf_ctrl |= USB_BUF_CTRL_LAST; } @@ -349,7 +349,7 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ uint8_t *dpram_buf = ep->dpram_buf; if (buf_id) { #if CFG_TUSB_RP2_ERRATA_E4 - if (!(is_host && !is_double)) // incorrect buf_id, buffer data is still buf0 + if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0 #endif { dpram_buf += 64; // buf1 offset @@ -370,54 +370,51 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4 // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale) if (is_short && is_double && is_rx && !is_last) { - #if CFG_TUH_ENABLED + const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint + if (is_host) { - // stop current transfer - uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; - sie_ctrl |= USB_SIE_CTRL_STOP_TRANS_BITS; + // host stop current transfer, not safe, can be racing + const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; usb_hw->sie_ctrl = sie_ctrl; - // maybe wait until STOP_TRANS bit is clear - - *buf_reg = 0; // reset buffer control - } - #endif - - #if CFG_TUD_ENABLED - if (!is_host) { - 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); // IN endpoint - - #if CFG_TUSB_RP2_ERRATA_E2 + while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} + } else { + // device abort current transfer + #if CFG_TUSB_RP2_ERRATA_E2 if (rp2040_chipversion >= 2) - #endif + #endif { usb_hw_set->abort = abort_bit; while ((usb_hw->abort_done & abort_bit) != abort_bit) {} } + } - // 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 + // After abort, check if the other buffer received valid data + io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1); + const uint16_t buf_ctrl16_other = *buf_reg16_other; + if (buf_ctrl16_other & USB_BUF_CTRL_FULL) { + // Data already sent into this buffer. Save it for the next transfer. + // buff_status will be clear by the next run + if (is_host) { + // host put future_len pointer at end of epx_data } else { - ep->next_pid ^= 1u; // roll back pid if aborted + ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK); } + ep->future_bufid = buf_id ^ 1; + } else { + ep->next_pid ^= 1u; // roll back pid if aborted + } - *buf_reg = 0; // reset buffer control + *buf_reg = 0; // reset buffer control - #if CFG_TUSB_RP2_ERRATA_E2 + if (!is_host) { + #if CFG_TUSB_RP2_ERRATA_E2 if (rp2040_chipversion >= 2) - #endif + #endif { usb_hw_clear->abort_done = abort_bit; usb_hw_clear->abort = abort_bit; } } - #endif hw_endpoint_lock_update(ep, -1); return true; From 9ac343a0471b03b1a76166c72347e7fdbf2c4800 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 31 Mar 2026 17:41:50 +0700 Subject: [PATCH 25/29] finally get rp2040 host epx working with 2-sof solution for switching --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 2 +- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 314 +++++++++++-------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 2 +- test/hil/hil_test.py | 11 +- 4 files changed, 189 insertions(+), 140 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index d4c1bf708b..8814f95d14 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -164,7 +164,7 @@ static void __tusb_irq_path_func(handle_hw_buff_status)(void) { while (buf_status) { // ctz/clz is faster than loop which has only a few bit set in general const uint8_t i = (uint8_t) __builtin_ctz(buf_status); - const uint bit = TU_BIT(i); + const uint32_t bit = TU_BIT(i); // IN transfer for even i, OUT transfer for odd i const uint8_t epnum = i >> 1u; diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 3a99e7275d..fb1676f500 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -1,3 +1,4 @@ + /* * The MIT License (MIT) * @@ -29,23 +30,23 @@ #if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421 -#include "pico.h" + #include "pico.h" -#if defined(PICO_RP2350) && PICO_RP2350 == 1 -#define HAS_STOP_EPX_ON_NAK -#endif + #if defined(PICO_RP2350) && PICO_RP2350 == 1 + #define HAS_STOP_EPX_ON_NAK + #endif // port 0 is native USB port, other is counted as software PIO #define RHPORT_NATIVE 0 -//--------------------------------------------------------------------+ + //--------------------------------------------------------------------+ // INCLUDE -//--------------------------------------------------------------------+ -#include "rp2040_usb.h" -#include "osal/osal.h" + //--------------------------------------------------------------------+ + #include "rp2040_usb.h" + #include "osal/osal.h" -#include "host/hcd.h" -#include "host/usbh.h" + #include "host/hcd.h" + #include "host/usbh.h" //--------------------------------------------------------------------+ // @@ -55,6 +56,9 @@ static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint + #ifndef HAS_STOP_EPX_ON_NAK +static volatile bool epx_switch_request = false; + #endif enum { SIE_CTRL_SPEED_DISCONNECT = 0, @@ -67,7 +71,7 @@ enum { //--------------------------------------------------------------------+ static hw_endpoint_t *edpt_alloc(void) { - for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { hw_endpoint_t *ep = &ep_pool[i]; if (ep->max_packet_size == 0) { return ep; @@ -89,11 +93,11 @@ static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { } TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) { - return &usbh_dpram->int_ep_ctrl[int_num-1].ctrl; + return &usbh_dpram->int_ep_ctrl[int_num - 1].ctrl; } -TU_ATTR_ALWAYS_INLINE static inline io_rw_32 * dpram_int_ep_buffer_ctrl(uint8_t int_num) { - return &usbh_dpram->int_ep_buffer_ctrl[int_num-1].ctrl; +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_buffer_ctrl(uint8_t int_num) { + return &usbh_dpram->int_ep_buffer_ctrl[int_num - 1].ctrl; } //--------------------------------------------------------------------+ @@ -113,30 +117,41 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { //--------------------------------------------------------------------+ // EPX //--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) { + uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl; + while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} +} + TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, bool is_rx, bool need_pre) { - uint32_t value = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits + uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits if (send_setup) { - value |= USB_SIE_CTRL_SEND_SETUP_BITS; + sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS; } else { - value |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); + sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); } if (need_pre) { - value |= USB_SIE_CTRL_PREAMBLE_EN_BITS; + sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS; } // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". // We write everything except the START_TRANS bit first, then wait some cycles. - usb_hw->sie_ctrl = value; + usb_hw->sie_ctrl = sie_ctrl; busy_wait_at_least_cycles(12); - usb_hw->sie_ctrl = value | USB_SIE_CTRL_START_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS; +} + +TU_ATTR_ALWAYS_INLINE static inline void epx_start_xfer(hw_endpoint_t *ep, bool is_setup) { + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(is_setup, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); } // prepare epx_ctrl register for new endpoint TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(hw_endpoint_t *ep) { - // RP2040-E4: USB host writes status to upper half of buffer control in single buffered mode. + // RP2040-E4: USB host writes status to the upper half of buffer control in single buffered mode. // The buffer selector toggles even in single-buffered mode, so the previous transfer's status - // may have been written to BUF1 half, leaving BUF0 with stale AVAILABLE bit. Clear it here. + // may have been written to BUF1 half, leaving BUF0 with a stale AVAILABLE bit. Clear it here. #if defined(PICO_RP2040) && PICO_RP2040 == 1 usbh_dpram->epx_buf_ctrl = 0; #endif @@ -147,23 +162,46 @@ TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(hw_endpoint_t *ep) { usbh_dpram->epx_ctrl = ep_ctrl; } -// save on-going context -static void __tusb_irq_path_func(epx_save_context)(void) { - const uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; - const uint16_t buf0_len = buf_ctrl & USB_BUF_CTRL_LEN_MASK; // TODO handle double buffered case - epx->remaining_len = (uint16_t)(epx->remaining_len + buf0_len); - epx->next_pid = (buf_ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; - if (tu_edpt_dir(epx->ep_addr) == TUSB_DIR_OUT) { - epx->user_buf -= buf0_len; - } - epx->pending = 1; - epx->active = false; +// Save buffer context for EPX preemption (called after STOP_TRANS). +// Undo PID toggle and buffer accounting for buffers NOT completed on the wire. +// A buffer completed on wire means: controller reached STATUS phase (ACK received). +// OUT completed: FULL cleared to 0 in STATUS phase (was 1 when armed) +// IN completed: FULL set to 1 in STATUS phase (was 0 when armed) +// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0) +static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) { + uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; + const bool is_out = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT); + + do { + const uint16_t bc16 = (uint16_t)buf_ctrl; + if (bc16) { + const bool avail = (bc16 & USB_BUF_CTRL_AVAIL); + const bool full = (bc16 & USB_BUF_CTRL_FULL); + if (avail || (is_out ? full : !full)) { + const uint16_t buf_len = bc16 & USB_BUF_CTRL_LEN_MASK; + ep->remaining_len += buf_len; + ep->next_pid ^= 1u; + if (is_out) { + ep->user_buf -= buf_len; + } + } + } + + if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) { + buf_ctrl >>= 16; + } else { + buf_ctrl = 0; + } + } while (buf_ctrl > 0); usbh_dpram->epx_buf_ctrl = 0; + + ep->pending = 1; + ep->active = false; } // All non-interrupt endpoints use shared EPX. -// Save current EPX context, mark pending, switch to ep +// Save the current EPX context, mark pending, switch to ep static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { const bool is_setup = (ep->pending == 2); @@ -172,19 +210,17 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { ep->active = true; if (is_setup) { - usb_hw->dev_addr_ctrl = ep->dev_addr; - sie_start_xfer(true, false, ep->need_pre); + // panic("new setup \n"); + epx_start_xfer(ep, true); } else { - const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const bool is_rx = tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN; - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; - io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; epx_ctrl_prepare(ep); - rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, ep->transfer_type == TUSB_XFER_INTERRUPT); + rp2usb_buffer_start(ep, ep_reg, buf_reg, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, + ep->transfer_type == TUSB_XFER_INTERRUPT); - usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - sie_start_xfer(false, is_rx, ep->need_pre); // start transfer + epx_start_xfer(ep, false); } } @@ -208,16 +244,14 @@ static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ //--------------------------------------------------------------------+ // Interrupt handlers //--------------------------------------------------------------------+ -static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result) { +static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) { // Mark transfer as done before we tell the tinyusb stack - uint8_t dev_addr = ep->dev_addr; - uint8_t ep_addr = ep->ep_addr; - uint xferred_len = ep->xferred_len; + uint xferred_len = ep->xferred_len; rp2usb_reset_transfer(ep); - hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); + hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true); // Carry more transfer on epx - if (ep == epx) { + if (is_more) { hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep != NULL) { epx_switch_ep(next_ep); @@ -231,26 +265,31 @@ static void __tusb_irq_path_func(handle_buf_status_isr)(void) { BUF_STATUS_EPX = 1u }; - // Check EPX first (bit 0). EPX is currently single-buffered, always use buf_id=0.3 + // Check EPX first (bit 0). // Double-buffered: if both buffers completed at once, buf_status re-sets // immediately after clearing (datasheet Table 406). Process the second buffer too. while (usb_hw->buf_status & BUF_STATUS_EPX) { - const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0; + const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0; usb_hw_clear->buf_status = 1u; // clear io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + #ifndef HAS_STOP_EPX_ON_NAK + // Any packet completion (mid-transfer or final) means data is flowing. + // Clear switch request so the 2-SOF fallback only fires for NAK-retrying endpoints. + epx_switch_request = false; + #endif if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) { - xfer_complete_isr(epx, XFER_RESULT_SUCCESS); + xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true); } } // Check "interrupt" (asynchronous) endpoints for both IN and OUT - uint32_t buf_status = usb_hw->buf_status & (uint32_t)~BUF_STATUS_EPX; + uint32_t buf_status = usb_hw->buf_status & ~(uint32_t)BUF_STATUS_EPX; while (buf_status) { // ctz/clz is faster than loop which has only a few bit set in general - const uint8_t idx = (uint8_t) __builtin_ctz(buf_status); - const uint bit = TU_BIT(idx); + const uint8_t idx = (uint8_t)__builtin_ctz(buf_status); + const uint32_t bit = TU_BIT(idx); usb_hw_clear->buf_status = bit; buf_status &= ~bit; @@ -258,7 +297,7 @@ static void __tusb_irq_path_func(handle_buf_status_isr)(void) { // EPX is bit 0. Bit 1 is not used // IEP1 IN/OUT is bit 2, 3 // IEP2 IN/OUT is bit 4, 5 etc - const uint8_t epnum = idx >> 1u; + const uint8_t epnum = idx >> 1u; for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) { hw_endpoint_t *ep = &ep_pool[e]; if (ep->interrupt_num == epnum) { @@ -266,7 +305,7 @@ static void __tusb_irq_path_func(handle_buf_status_isr)(void) { io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN); if (done) { - xfer_complete_isr(ep, XFER_RESULT_SUCCESS); + xfer_complete_isr(ep, XFER_RESULT_SUCCESS, false); } break; } @@ -293,69 +332,80 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { } if (status & USB_INTS_STALL_BITS) { - // We have rx'd a stall from the device - // NOTE THIS SHOULD HAVE PRIORITY OVER BUFF_STATUS - // AND TRANS_COMPLETE as the stall is an alternative response - // to one of those events usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; - xfer_complete_isr(epx, XFER_RESULT_STALLED); + xfer_complete_isr(epx, XFER_RESULT_STALLED, true); } - if (status & USB_INTS_BUFF_STATUS_BITS) { - handle_buf_status_isr(); + if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { + usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; + + const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl; + // while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} + + // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time + // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe. + if (epx->active) { + xfer_complete_isr(epx, XFER_RESULT_FAILED, false); + } } if (status & USB_INTS_TRANS_COMPLETE_BITS) { + // only applies for epx, interrupt endpoint does not seem to raise this usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS; - - // only handle a setup packet if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { - epx->xferred_len = 8; - xfer_complete_isr(epx, XFER_RESULT_SUCCESS); - } else { - // Don't care. Will handle this in buff status + uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; + usb_hw->sie_ctrl = sie_ctrl; // clear setup bit + epx->xferred_len = 8; + xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true); } } + if (status & USB_INTS_BUFF_STATUS_BITS) { + handle_buf_status_isr(); + } + + // SOF-based round-robin MUST run BEFORE BUFF_STATUS to avoid processing + // buf_status on the wrong EPX after a completion+switch in handle_buf_status_isr. #ifdef HAS_STOP_EPX_ON_NAK if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep != NULL) { - epx_save_context(); + epx_save_context(epx); epx_switch_ep(next_ep); } else { - // No pending endpoint, this is the only active one: disable stop-on-NAK, continue current transfer usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre); } } #else - // RP2040: on SOF, stop and switch if there's a pending ep + // RP2040: on SOF, switch EPX if another endpoint is pending. + // First SOF sets epx_switch_request. If a transfer completes before next SOF, the flag is + // cleared (data is flowing, no need to force-switch). Second SOF with flag still set means + // no data exchanged (endpoint NAK-retrying): STOP_TRANS is safe and we switch. + // This avoids stopping mid-data-transfer which corrupts double-buffered PID tracking. if (status & USB_INTS_HOST_SOF_BITS) { (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep == NULL) { - // no more pending --> disable SOF usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; usb_hw->nak_poll = USB_NAK_POLL_RESET; - } else { - // stop transfer if is active - if (epx->active) { - usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS; - while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} + epx_switch_request = false; + } else if (epx->active) { + if (epx_switch_request) { + // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch. + epx_switch_request = false; + sie_stop_xfer(); + epx_save_context(epx); + epx_switch_ep(next_ep); + } else { + epx_switch_request = true; } - - epx_save_context(); - epx_switch_ep(next_ep); } } #endif - if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { - usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; - } - if (status & USB_INTS_ERROR_DATA_SEQ_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS; panic("Data Seq Error \n"); @@ -371,9 +421,9 @@ void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { //--------------------------------------------------------------------+ // HCD API //--------------------------------------------------------------------+ -bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rhport; + (void)rh_init; pico_trace("hcd_init %d\n", rhport); assert(rhport == 0); @@ -392,24 +442,20 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // Enable in host mode with SOF / Keep alive on usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS | USB_MAIN_CTRL_HOST_NDEVICE_BITS; - usb_hw->sie_ctrl = SIE_CTRL_BASE; - usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | - USB_INTE_HOST_CONN_DIS_BITS | - USB_INTE_HOST_RESUME_BITS | - USB_INTE_STALL_BITS | - USB_INTE_TRANS_COMPLETE_BITS | - USB_INTE_ERROR_RX_TIMEOUT_BITS | - USB_INTE_ERROR_DATA_SEQ_BITS ; - -#ifdef HAS_STOP_EPX_ON_NAK + usb_hw->sie_ctrl = SIE_CTRL_BASE; + usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | USB_INTE_HOST_CONN_DIS_BITS | USB_INTE_HOST_RESUME_BITS | + USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS | + USB_INTE_ERROR_DATA_SEQ_BITS; + + #ifdef HAS_STOP_EPX_ON_NAK usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS; -#endif + #endif return true; } bool hcd_deinit(uint8_t rhport) { - (void) rhport; + (void)rhport; irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); @@ -417,7 +463,7 @@ bool hcd_deinit(uint8_t rhport) { } void hcd_port_reset(uint8_t rhport) { - (void) rhport; + (void)rhport; // TODO: Nothing to do here yet. Perhaps need to reset some state? } @@ -445,7 +491,10 @@ tusb_speed_t hcd_port_speed_get(uint8_t rhport) { // Close all opened endpoint belong to this device void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { (void)rhport; - (void)dev_addr; + + if (dev_addr == 0) { + return; // address 0 is for device enumeration + } // reset epx if it is currently active with unplugged device if (epx->max_packet_size > 0 && epx->dev_addr == dev_addr) { @@ -462,13 +511,13 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { if (ep->interrupt_num) { // disable interrupt endpoint - usb_hw_clear->int_ep_ctrl = 1u << ep->interrupt_num; + usb_hw_clear->int_ep_ctrl = TU_BIT(ep->interrupt_num); usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0; io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); - *buf_reg = 0; - *ep_reg = 0; + *buf_reg = 0; + *ep_reg = 0; } ep->max_packet_size = 0; // mark as unused @@ -498,13 +547,17 @@ void hcd_int_disable(uint8_t rhport) { bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { (void)rhport; pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); - hw_endpoint_t *ep = edpt_alloc(); + hw_endpoint_t *ep; + if (dev_addr == 0) { + ep = &ep_pool[0]; + } else { + ep = edpt_alloc(); + } TU_ASSERT(ep); const uint8_t ep_addr = ep_desc->bEndpointAddress; const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc); const uint8_t transfer_type = ep_desc->bmAttributes.xfer; - // const uint8_t bmInterval = ep_desc->bInterval; ep->max_packet_size = max_packet_size; ep->ep_addr = ep_addr; @@ -532,7 +585,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) | - (uint32_t)((ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); + ((uint32_t)(ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; //------------- address control -------------// @@ -547,7 +600,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl; // Finally, activate interrupt endpoint - usb_hw_set->int_ep_ctrl = 1u << ep->interrupt_num; + usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num); } return true; @@ -560,6 +613,14 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { return false; // TODO not implemented yet } +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void)rhport; + (void)dev_addr; + (void)ep_addr; + // TODO not implemented yet + return false; +} + bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void)rhport; @@ -594,19 +655,14 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; #endif } else { - const uint8_t ep_num = tu_edpt_number(ep->ep_addr); - const tusb_dir_t ep_dir = tu_edpt_dir(ep->ep_addr); - const bool is_rx = (ep_dir == TUSB_DIR_IN); - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; - io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; epx = ep; epx_ctrl_prepare(ep); rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl - - usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB)); - sie_start_xfer(false, is_rx, ep->need_pre); // start transfer + epx_start_xfer(ep, false); } rp2usb_critical_exit(); } @@ -614,27 +670,19 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b return true; } -bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void)rhport; - (void)dev_addr; - (void)ep_addr; - // TODO not implemented yet - return false; -} - bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) { (void)rhport; - // Copy data into setup packet buffer (usbh only schedules one setup at a time) - for (uint8_t i = 0; i < 8; i++) { - usbh_dpram->setup_packet[i] = setup_packet[i]; - } - hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); TU_ASSERT(ep); rp2usb_critical_enter(); + // Copy data into setup packet buffer (usbh only schedules one setup at a time) + for (uint8_t i = 0; i < 8; i++) { + usbh_dpram->setup_packet[i] = setup_packet[i]; + } + ep->ep_addr = 0; // setup is OUT ep->remaining_len = 8; ep->xferred_len = 0; @@ -651,9 +699,7 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet } else { epx = ep; ep->active = true; - - usb_hw->dev_addr_ctrl = dev_addr; // Set device address - sie_start_xfer(true, false, ep->need_pre); // start transfer + epx_start_xfer(ep, true); } rp2usb_critical_exit(); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 21237b0592..156be62e44 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -197,7 +197,7 @@ void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_r ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; } else { // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector - ep_ctrl &= ~EP_CTRL_DOUBLE_BUFFERED_BITS; + ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS; } *ep_reg = ep_ctrl; } diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 04dd4e2bcd..cf3cff1a32 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -50,7 +50,7 @@ from pymtp import MTP import string -ENUM_TIMEOUT = 10 +ENUM_TIMEOUT = 15 STATUS_OK = "\033[32mOK\033[0m" STATUS_FAILED = "\033[31mFailed\033[0m" @@ -766,10 +766,13 @@ def parse_dd_speed(dd_output): assert ret.returncode == 0, f'dd read failed: {ret.stdout.decode()}' read_speed = parse_dd_speed(ret.stdout.decode()) - # Write back the same data to avoid corrupting the disk + # Write back the same data to avoid corrupting the disk (skip if read-only) ret = run_cmd(f'dd if={tmp_file} of={dev} bs={block_size} count={block_count} oflag=direct 2>&1') - assert ret.returncode == 0, f'dd write failed: {ret.stdout.decode()}' - write_speed = parse_dd_speed(ret.stdout.decode()) + if ret.returncode != 0 and 'Read-only' in ret.stdout.decode(): + write_speed = 'skip (read-only)' + else: + assert ret.returncode == 0, f'dd write failed: {ret.stdout.decode()}' + write_speed = parse_dd_speed(ret.stdout.decode()) try: os.remove(tmp_file) From 78d34d5b6488d7129530461ce1865a33a946e698 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Apr 2026 15:51:31 +0700 Subject: [PATCH 26/29] reduce code size, use state to replace active + pending --- .claude/commands/build-doc.md | 24 +++++ .claude/commands/hil.md | 30 ++++++ examples/host/msc_file_explorer/README.md | 105 ++++++++++++++++++ src/portable/raspberrypi/rp2040/dcd_rp2040.c | 13 ++- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 106 ++++++++----------- src/portable/raspberrypi/rp2040/rp2040_usb.c | 78 ++++++-------- src/portable/raspberrypi/rp2040/rp2040_usb.h | 27 +++-- tools/metrics.py | 5 +- 8 files changed, 266 insertions(+), 122 deletions(-) create mode 100644 .claude/commands/build-doc.md create mode 100644 .claude/commands/hil.md create mode 100644 examples/host/msc_file_explorer/README.md diff --git a/.claude/commands/build-doc.md b/.claude/commands/build-doc.md new file mode 100644 index 0000000000..c9ad9f5396 --- /dev/null +++ b/.claude/commands/build-doc.md @@ -0,0 +1,24 @@ +# build-doc + +Scan all example READMEs and build the Sphinx documentation. + +## Instructions + +1. Install docs dependencies: + ```bash + pip install -r docs/requirements.txt + ``` + +2. Build the docs from the repo root: + ```bash + sphinx-build -b html docs docs/_build + ``` + `conf.py` automatically scans all `examples/{device,host,dual}/*/README.md`, copies them into `docs/examples/`, and regenerates `examples.rst` with the toctree. + +3. Use a timeout of at least 60 seconds. + +4. After the build completes: + - Show the build output to the user. + - Report total warnings and errors. + - List which example READMEs were discovered and included. + - If there are errors, suggest fixes. diff --git a/.claude/commands/hil.md b/.claude/commands/hil.md new file mode 100644 index 0000000000..2ba35ec222 --- /dev/null +++ b/.claude/commands/hil.md @@ -0,0 +1,30 @@ +# hil + +Run Hardware-in-the-Loop (HIL) tests on physical boards. + +## Arguments +- $ARGUMENTS: Optional flags (e.g. board name, extra args). If empty, runs all boards with default config. + +## Instructions + +1. Determine the HIL config file: + ```bash + HIL_CONFIG=$( (systemctl list-units --type=service --state=running 2>/dev/null; systemctl --user list-units --type=service --state=running 2>/dev/null) | grep -q 'actions\.runner' && echo tinyusb.json || echo local.json ) + ``` + Default is `local.json` for local development. + +2. Parse $ARGUMENTS: + - If $ARGUMENTS contains `-b BOARD_NAME`, run for that specific board only. + - If $ARGUMENTS is empty or has no `-b`, run for all boards in the config. + - Pass through any other flags (e.g. `-v` for verbose) directly to the command. + +3. Run the HIL test from the repo root directory: + - Specific board: `python test/hil/hil_test.py -b BOARD_NAME -B examples $HIL_CONFIG $EXTRA_ARGS` + - All boards: `python test/hil/hil_test.py -B examples $HIL_CONFIG $EXTRA_ARGS` + +4. Use a timeout of at least 20 minutes (600000ms). HIL tests take 2-5 minutes. NEVER cancel early. + +5. After the test completes: + - Show the test output to the user. + - Summarize pass/fail results per board. + - If there are failures, suggest re-running with `-v` flag for verbose output to help debug. diff --git a/examples/host/msc_file_explorer/README.md b/examples/host/msc_file_explorer/README.md new file mode 100644 index 0000000000..e220bedea8 --- /dev/null +++ b/examples/host/msc_file_explorer/README.md @@ -0,0 +1,105 @@ +# MSC File Explorer + +This host example implements an interactive command-line file browser for USB Mass Storage devices. +When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like +CLI is presented over the board's serial console. + +## Features + +- Automatic mount/unmount of USB storage devices +- FAT12/16/32 filesystem support via FatFS +- Interactive CLI with command history +- Read speed benchmarking with `dd` +- Support for up to 4 simultaneous USB storage devices (via hub) + +## Supported Commands + +| Command | Usage | Description | +|---------|--------------------|------------------------------------------------------| +| help | `help` | Print list of available commands | +| cat | `cat ` | Print file contents to the console | +| cd | `cd ` | Change current working directory | +| cp | `cp ` | Copy a file | +| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) | +| ls | `ls [dir]` | List directory contents | +| pwd | `pwd` | Print current working directory | +| mkdir | `mkdir ` | Create a directory | +| mv | `mv ` | Rename/move a file or directory | +| rm | `rm ` | Remove a file | + +## Build + +Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)): + +```bash +# Example: build for Raspberry Pi Pico +cmake -B build -DBOARD=raspberry_pi_pico -DFAMILY=rp2040 examples/host/msc_file_explorer +cmake --build build +``` + +## Usage + +1. Flash the firmware to your board. +2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud. +3. Plug a USB flash drive into the board's USB host port. +4. The device is auto-mounted and the prompt appears: + +``` +TinyUSB MSC File Explorer Example + +Device connected + Vendor : Kingston + Product : DataTraveler 2.0 + Rev : 1.0 + Capacity: 1.9 GB + +0:/> _ +``` + +### Browsing Files + +``` +0:/> ls +----a 1234 readme.txt +d---- 0 photos +d---- 0 docs + +0:/> cd photos +0:/photos> ls +----a 520432 vacation.jpg +----a 312088 family.png + +0:/> cat readme.txt +Hello from USB drive! +``` + +### Copying and Moving Files + +``` +0:/> cp readme.txt backup.txt +0:/> mv backup.txt docs/backup.txt +``` + +### Measuring Read Speed + +``` +0:/> dd +Reading 1024 sectors... + Data speed: 823 KB/s +``` + +### Multiple Devices + +When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to +navigate between them: + +``` +0:/> cd 1: +1:/> ls +``` + +## Testing + +This example is part of the TinyUSB HIL (Hardware-in-the-Loop) test suite. The HIL test +automatically flashes, runs the example, and verifies MSC enumeration and file operations +against a known USB drive. diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 8814f95d14..ca03ebf8a9 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -111,7 +111,6 @@ static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t // double buffered Bulk endpoint if (transfer_type == TUSB_XFER_BULK) { size *= 2u; - #if CFG_TUSB_RP2_ERRATA_E15 if (dir == TUSB_DIR_IN) { ep->e15_bulk_in = true; @@ -195,7 +194,7 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) { for (uint8_t dir = 0; dir < 2; dir++) { struct hw_endpoint *ep = hw_endpoint_get(0, dir); ep->next_pid = 1u; - if (ep->active) { + if (ep->state == EPSTATE_ACTIVE) { hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs } } @@ -254,12 +253,12 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN); // Active Bulk IN endpoint requires SOF - if (ep->e15_bulk_in && ep->active) { + if (ep->e15_bulk_in && ep->state == EPSTATE_ACTIVE) { keep_sof_alive = true; hw_endpoint_lock_update(ep, 1); - if (ep->pending) { - ep->pending = 0; + if (ep->state == EPSTATE_PENDING) { + ep->state = EPSTATE_ACTIVE; io_rw_32 *buf_reg32 = get_buf_ctrl(i, TUSB_DIR_IN); io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32; @@ -276,7 +275,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { if (buf0_idle && buf1_idle) { // both are idle, start fresh io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN); - rp2usb_buffer_start(ep, ep_reg, buf_reg32, false, false); + rp2usb_buffer_start(ep, ep_reg, buf_reg32, false); } else if (buf0_idle) { uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); bufctrl_write16(buf_reg16, buf0); @@ -501,7 +500,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) struct hw_endpoint *ep = hw_endpoint_get(epnum, dir); TU_ASSERT(ep->dpram_buf != NULL); // must be inited and allocated previously - if (ep->active) { + if (ep->state == EPSTATE_ACTIVE) { hw_endpoint_abort_xfer(ep); // abort any pending transfer } ep->max_packet_size = ep_desc->wMaxPacketSize; diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index fb1676f500..02a4e055ee 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -66,6 +66,10 @@ enum { SIE_CTRL_SPEED_FULL = 2, }; +enum { + EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data) +}; + //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ @@ -123,7 +127,7 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) { while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} } -TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, bool is_rx, bool need_pre) { +static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool need_pre) { uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits if (send_setup) { sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS; @@ -135,31 +139,16 @@ TU_ATTR_ALWAYS_INLINE static inline void sie_start_xfer(bool send_setup, bool is } // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit - // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". + // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".! // We write everything except the START_TRANS bit first, then wait some cycles. usb_hw->sie_ctrl = sie_ctrl; busy_wait_at_least_cycles(12); usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS; } -TU_ATTR_ALWAYS_INLINE static inline void epx_start_xfer(hw_endpoint_t *ep, bool is_setup) { - usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB)); - sie_start_xfer(is_setup, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); -} - // prepare epx_ctrl register for new endpoint -TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(hw_endpoint_t *ep) { - // RP2040-E4: USB host writes status to the upper half of buffer control in single buffered mode. - // The buffer selector toggles even in single-buffered mode, so the previous transfer's status - // may have been written to BUF1 half, leaving BUF0 with a stale AVAILABLE bit. Clear it here. - #if defined(PICO_RP2040) && PICO_RP2040 == 1 - usbh_dpram->epx_buf_ctrl = 0; - #endif - - // ep control - const uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | - ((uint32_t)ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf); - usbh_dpram->epx_ctrl = ep_ctrl; +TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(uint8_t transfer_type) { + usbh_dpram->epx_ctrl = EPX_CTRL_DEFAULT | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB); } // Save buffer context for EPX preemption (called after STOP_TRANS). @@ -196,31 +185,30 @@ static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) { usbh_dpram->epx_buf_ctrl = 0; - ep->pending = 1; - ep->active = false; + ep->state = EPSTATE_PENDING; } -// All non-interrupt endpoints use shared EPX. -// Save the current EPX context, mark pending, switch to ep +// switch epx to new endpoint and start the transfer static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { - const bool is_setup = (ep->pending == 2); + const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP); - epx = ep; // switch pointer - ep->pending = 0; - ep->active = true; + epx = ep; // switch pointer + ep->state = EPSTATE_ACTIVE; if (is_setup) { // panic("new setup \n"); - epx_start_xfer(ep, true); + usb_hw->dev_addr_ctrl = ep->dev_addr; + sie_start_xfer(true, false, ep->need_pre); } else { - io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + const bool is_rx = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN); + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; - epx_ctrl_prepare(ep); - rp2usb_buffer_start(ep, ep_reg, buf_reg, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, - ep->transfer_type == TUSB_XFER_INTERRUPT); + epx_ctrl_prepare(ep->transfer_type); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); - epx_start_xfer(ep, false); + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(is_setup, is_rx, ep->need_pre); } } @@ -228,12 +216,12 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) { const uint cur_idx = (uint)(cur_ep - &ep_pool[0]); for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) { - if (ep_pool[i].pending) { + if (ep_pool[i].state >= EPSTATE_PENDING) { return &ep_pool[i]; } } for (uint i = 0; i < cur_idx; i++) { - if (ep_pool[i].pending) { + if (ep_pool[i].state >= EPSTATE_PENDING) { return &ep_pool[i]; } } @@ -246,7 +234,7 @@ static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ //--------------------------------------------------------------------+ static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) { // Mark transfer as done before we tell the tinyusb stack - uint xferred_len = ep->xferred_len; + uint32_t xferred_len = ep->xferred_len; rp2usb_reset_transfer(ep); hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true); @@ -345,7 +333,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe. - if (epx->active) { + if (epx->state == EPSTATE_ACTIVE) { xfer_complete_isr(epx, XFER_RESULT_FAILED, false); } } @@ -392,7 +380,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; usb_hw->nak_poll = USB_NAK_POLL_RESET; epx_switch_request = false; - } else if (epx->active) { + } else if (epx->state == EPSTATE_ACTIVE) { if (epx_switch_request) { // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch. epx_switch_request = false; @@ -496,20 +484,14 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { return; // address 0 is for device enumeration } - // reset epx if it is currently active with unplugged device - if (epx->max_packet_size > 0 && epx->dev_addr == dev_addr) { - // if (epx->active) { - // // need to abort transfer - // } - epx->max_packet_size = 0; - } + rp2usb_critical_enter(); for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { hw_endpoint_t *ep = &ep_pool[i]; if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) { - ep->pending = 0; // clear any pending transfer + ep->state = EPSTATE_IDLE; // clear any pending transfer - if (ep->interrupt_num) { + if (ep->interrupt_num > 0) { // disable interrupt endpoint usb_hw_clear->int_ep_ctrl = TU_BIT(ep->interrupt_num); usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0; @@ -523,6 +505,8 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { ep->max_packet_size = 0; // mark as unused } } + + rp2usb_critical_exit(); } uint32_t hcd_frame_number(uint8_t rhport) { @@ -557,16 +541,15 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t const uint8_t ep_addr = ep_desc->bEndpointAddress; const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc); - const uint8_t transfer_type = ep_desc->bmAttributes.xfer; ep->max_packet_size = max_packet_size; ep->ep_addr = ep_addr; ep->dev_addr = dev_addr; - ep->transfer_type = transfer_type; + ep->transfer_type = ep_desc->bmAttributes.xfer; ep->need_pre = need_pre(dev_addr); ep->next_pid = 0u; - if (transfer_type != TUSB_XFER_INTERRUPT) { + if (ep->transfer_type != TUSB_XFER_INTERRUPT) { ep->dpram_buf = usbh_dpram->epx_data; } else { // from 15 interrupt endpoints pool @@ -627,7 +610,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); TU_ASSERT(ep); - if (ep->transfer_type == TUSB_XFER_INTERRUPT) { + if (ep->interrupt_num > 0) { // For interrupt endpoint control and buffer is already configured // Note: Interrupt is single buffered only io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); @@ -642,10 +625,10 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b // If EPX is busy with another transfer, mark as pending rp2usb_critical_enter(); - if (epx->active) { + if (epx->state == EPSTATE_ACTIVE) { ep->user_buf = buffer; ep->remaining_len = buflen; - ep->pending = 1; + ep->state = EPSTATE_PENDING; #ifdef HAS_STOP_EPX_ON_NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; @@ -660,9 +643,10 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b epx = ep; - epx_ctrl_prepare(ep); + epx_ctrl_prepare(ep->transfer_type); rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl - epx_start_xfer(ep, false); + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(false, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); } rp2usb_critical_exit(); } @@ -688,8 +672,8 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet ep->xferred_len = 0; // If EPX is busy, mark as pending setup (DPRAM already has the packet) - if (epx->active) { - ep->pending = 2; // setup + if (epx->state == EPSTATE_ACTIVE) { + ep->state = EPSTATE_PENDING_SETUP; #ifdef HAS_STOP_EPX_ON_NAK usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; #else @@ -697,9 +681,11 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; #endif } else { - epx = ep; - ep->active = true; - epx_start_xfer(ep, true); + epx = ep; + ep->state = EPSTATE_ACTIVE; + + usb_hw->dev_addr_ctrl = ep->dev_addr; + sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); } rp2usb_critical_exit(); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 156be62e44..206da041b1 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -101,12 +101,13 @@ void rp2usb_init(void) { } void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { - ep->active = false; - ep->pending = 0; + ep->state = EPSTATE_IDLE; ep->remaining_len = 0; ep->xferred_len = 0; ep->user_buf = 0; +#if CFG_TUD_EDPT_DEDICATED_HWFIFO ep->is_xfer_fifo = false; +#endif } void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) { @@ -183,14 +184,19 @@ uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpr } // Start transaction on hw buffer -void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx, - bool force_single) { +void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) { // always compute and start with buffer 0 uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; // Note: device EP0 does not have an endpoint control register if (ep_reg != NULL) { uint32_t ep_ctrl = *ep_reg; + #if CFG_TUH_ENABLED + const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0); + #else + const bool force_single = false; + #endif + if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16; @@ -211,8 +217,7 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u (void)ff; hw_endpoint_lock_update(ep, 1); - if (ep->active) { - // TODO: Is this acceptable for interrupt packets? + if (ep->state == EPSTATE_ACTIVE) { TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr); rp2usb_reset_transfer(ep); } @@ -220,7 +225,7 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u // Fill in info now that we're kicking off the hw ep->remaining_len = total_len; ep->xferred_len = 0; - ep->active = true; + ep->state = EPSTATE_ACTIVE; #if CFG_TUD_EDPT_DEDICATED_HWFIFO if (ff != NULL) { @@ -229,66 +234,50 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u } else #endif { - ep->user_buf = buffer; + ep->user_buf = buffer; + #if CFG_TUD_EDPT_DEDICATED_HWFIFO ep->is_xfer_fifo = false; + #endif } const bool is_host = rp2usb_is_host_mode(); + const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); - if (ep->future_len > 0) { - // only on rx endpoint + #if CFG_TUD_ENABLED + if (!is_host && ep->future_len > 0) { + // Device only: previous short-packet abort saved data from the other buffer 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; rp2usb_reset_transfer(ep); - - #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 - + dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); hw_endpoint_lock_update(ep, -1); return; } } - #if CFG_TUSB_RP2_ERRATA_E15 + #if CFG_TUSB_RP2_ERRATA_E15 if (ep->e15_bulk_in) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; // skip transfer if we are in critical frame period if (e15_is_critical_frame_period()) { - ep->pending = 1; + ep->state = EPSTATE_PENDING; hw_endpoint_lock_update(ep, -1); return; } } - #endif + #endif // CFG_TUSB_RP2_ERRATA_E15 + #endif // CFG_TUD_ENABLED - const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); - #if CFG_TUH_ENABLED - const bool force_single = (is_host && ep->transfer_type == TUSB_XFER_INTERRUPT); - #else - const bool force_single = false; - #endif - - rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, force_single); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); hw_endpoint_lock_update(ep, -1); } @@ -333,7 +322,7 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ bool is_rx) { hw_endpoint_lock_update(ep, 1); - if (!ep->active) { + if (ep->state != EPSTATE_ACTIVE) { // probably land here due to short packet on rx with double buffered hw_endpoint_lock_update(ep, -1); return false; @@ -394,12 +383,12 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ if (buf_ctrl16_other & USB_BUF_CTRL_FULL) { // Data already sent into this buffer. Save it for the next transfer. // buff_status will be clear by the next run - if (is_host) { - // host put future_len pointer at end of epx_data - } else { + #if CFG_TUD_ENABLED + if (!is_host) { ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK); + ep->future_bufid = buf_id ^ 1; } - ep->future_bufid = buf_id ^ 1; + #endif } else { ep->next_pid ^= 1u; // roll back pid if aborted } @@ -422,10 +411,11 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ if (!is_done && ep->remaining_len > 0) { #if CFG_TUSB_RP2_ERRATA_E15 - if (ep->e15_bulk_in && e15_is_critical_frame_period()) { + const bool need_e15 = ep->e15_bulk_in; + if (need_e15 && e15_is_critical_frame_period()) { // mark as pending if matches E15 condition - ep->pending = 1; - } else if (ep->e15_bulk_in && ep->pending) { + ep->state = EPSTATE_PENDING; + } else if (need_e15 && ep->state == EPSTATE_PENDING) { // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it // do nothing } else diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 2ba9d018e3..8ebc3e3fc9 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -32,9 +32,9 @@ // 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 + #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && 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 + #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) #endif #endif #endif @@ -90,18 +90,23 @@ enum { EPSTATE_IDLE = 0, EPSTATE_ACTIVE, EPSTATE_PENDING, + EPSTATE_PENDING_SETUP }; // Hardware information per endpoint typedef struct hw_endpoint { uint8_t ep_addr; uint8_t next_pid; - bool active; // transferring data - uint8_t pending; // Transfer scheduled but not active - bool is_xfer_fifo; // transfer using fifo + uint8_t state; - uint8_t future_bufid; - uint8_t future_len; +#if CFG_TUD_EDPT_DEDICATED_HWFIFO + bool is_xfer_fifo; // transfer using fifo +#endif + +#if CFG_TUD_ENABLED + uint8_t future_bufid; // which buffer holds next data + uint8_t future_len; // next data len +#endif #if CFG_TUSB_RP2_ERRATA_E15 bool e15_bulk_in; // Errata15 device bulk in @@ -110,8 +115,10 @@ typedef struct hw_endpoint { #if CFG_TUH_ENABLED uint8_t dev_addr; uint8_t interrupt_num; // 1-15 for interrupt endpoints - uint8_t transfer_type; - bool need_pre; // need preamble for low speed device behind full speed hub + struct TU_ATTR_PACKED { + uint8_t transfer_type : 2; + uint8_t need_pre : 1; // preamble for low-speed device behind full speed hub + }; #endif uint16_t max_packet_size; // max packet size also indicates configured @@ -153,7 +160,7 @@ TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) { void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx); -void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx, bool force_single); +void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx); void rp2usb_reset_transfer(hw_endpoint_t *ep); diff --git a/tools/metrics.py b/tools/metrics.py index 6b992c8f52..f624f382f0 100644 --- a/tools/metrics.py +++ b/tools/metrics.py @@ -166,6 +166,9 @@ def compute_avg(all_json_data): file_accumulator[fname]["symbols"][name].append(sym.get("size", 0)) sections_map = f.get("sections") or {} for sname, ssize in sections_map.items(): + # linkermap -v produces nested dicts {subsection: size}, flatten to total + if isinstance(ssize, dict): + ssize = sum(ssize.values()) file_accumulator[fname]["sections"][sname].append(ssize) # Build json_average with averaged values @@ -209,7 +212,7 @@ def compute_avg(all_json_data): def compare_files(base_file, new_file, filters=None): - """Compare two CSV or JSON inputs and generate difference report.""" + """Compare two CSV or JSON inputs and generate a difference report.""" filters = filters or [] base_avg = compute_avg(combine_files([base_file], filters)) From d2050487b7a248636792743dac36c238d1e3f2af Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Apr 2026 22:25:27 +0700 Subject: [PATCH 27/29] fix hil uid typo --- test/hil/tinyusb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json index 37e98a52f7..1ac9539395 100644 --- a/test/hil/tinyusb.json +++ b/test/hil/tinyusb.json @@ -147,7 +147,7 @@ }, { "name": "raspberry_pi_pico_w", - "uid": "E6614C311B764A37", + "uid": "E6614864D35DAE36", "tests": { "device": false, "host": true, "dual": false, "dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023934", "is_cdc": true}] From 087ceb7417e6c1d72701ae491407490cfe5c6558 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Apr 2026 23:50:26 +0700 Subject: [PATCH 28/29] fix issue caused by merging active + pending state --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 2 +- src/portable/raspberrypi/rp2040/rp2040_usb.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index ca03ebf8a9..2b6bbc43bf 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -253,7 +253,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN); // Active Bulk IN endpoint requires SOF - if (ep->e15_bulk_in && ep->state == EPSTATE_ACTIVE) { + if (ep->e15_bulk_in && ep->state >= EPSTATE_ACTIVE) { keep_sof_alive = true; hw_endpoint_lock_update(ep, 1); diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 206da041b1..1b13934d31 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -322,7 +322,7 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_ bool is_rx) { hw_endpoint_lock_update(ep, 1); - if (ep->state != EPSTATE_ACTIVE) { + if (ep->state == EPSTATE_IDLE) { // probably land here due to short packet on rx with double buffered hw_endpoint_lock_update(ep, -1); return false; From b6f04f8643e5ca1efe2b8f9754427f5e5a0303e7 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 2 Apr 2026 10:42:00 +0700 Subject: [PATCH 29/29] add msc device to hil rp2040 host pool --- test/hil/tinyusb.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json index 1ac9539395..86ac902ce8 100644 --- a/test/hil/tinyusb.json +++ b/test/hil/tinyusb.json @@ -150,7 +150,21 @@ "uid": "E6614864D35DAE36", "tests": { "device": false, "host": true, "dual": false, - "dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023934", "is_cdc": true}] + "dev_attached": [ + { + "vid_pid": "1a86_55d4", + "serial": "52D2023934", + "is_cdc": true + }, + { + "vid_pid": "2008_2018", + "serial": "O20070925A002746", + "is_msc": true, + "block_size": 512, + "block_count": 4124152, + "msc_inquiry": "USB2.0 Flash Disk 2.10" + } + ] }, "flasher": { "name": "openocd",