diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 02a4e055ee..db08e5e7db 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -32,6 +32,14 @@ #include "pico.h" + // These two SIE_STATUS bits are present in the RP2350 SDK but absent from the RP2040 SDK. + #ifndef USB_SIE_STATUS_RX_SHORT_PACKET_BITS + #define USB_SIE_STATUS_RX_SHORT_PACKET_BITS 0x00001000u + #endif + #ifndef USB_SIE_STATUS_ENDPOINT_ERROR_BITS + #define USB_SIE_STATUS_ENDPOINT_ERROR_BITS 0x00800000u + #endif + #if defined(PICO_RP2350) && PICO_RP2350 == 1 #define HAS_STOP_EPX_ON_NAK #endif @@ -52,10 +60,17 @@ // //--------------------------------------------------------------------+ -// Host mode uses one shared endpoint register for non-interrupt endpoint +// Host mode uses one shared endpoint register (EPX) for all non-interrupt endpoints static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint +// EP0 max packet size per device address. EPX is shared across all devices so +// its max_packet_size can become stale when switching between devices with +// different EP0 sizes (e.g. low-speed MPS=8 vs full-speed MPS=64). +static uint8_t ep0_mps[CFG_TUH_DEVICE_MAX + CFG_TUH_HUB + 1]; // +1 for addr0 + +static bool epx_post_error = false; + #ifndef HAS_STOP_EPX_ON_NAK static volatile bool epx_switch_request = false; #endif @@ -84,11 +99,25 @@ static hw_endpoint_t *edpt_alloc(void) { return NULL; } +int hcd_free_ep_count(void) { + int count = 0; + for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { + if (ep_pool[i].max_packet_size == 0) { + count++; + } + } + return count; +} + 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++) { + // EP0 (control) always uses the shared EPX at ep_pool[0] + if (tu_edpt_number(ep_addr) == 0) { + return &ep_pool[0]; + } + + for (uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { 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))) { + if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) && (ep->ep_addr == ep_addr)) { return ep; } } @@ -122,27 +151,39 @@ 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) {} -} + usb_hw->sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; + while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) tight_loop_contents(); +} + +static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool use_preamble) { + // Clear transient handshake/status latches from the prior EPX phase so + // stale bits cannot trigger immediate spurious interrupts on the new transfer. + usb_hw_clear->sie_status = USB_SIE_STATUS_ACK_REC_BITS | + USB_SIE_STATUS_NAK_REC_BITS | + USB_SIE_STATUS_STALL_REC_BITS | + USB_SIE_STATUS_DATA_SEQ_ERROR_BITS | + USB_SIE_STATUS_RX_TIMEOUT_BITS | + USB_SIE_STATUS_RX_SHORT_PACKET_BITS | + USB_SIE_STATUS_TRANS_COMPLETE_BITS | + USB_SIE_STATUS_ENDPOINT_ERROR_BITS; + usb_hw_clear->buf_status = 0x3u; // clear stale EPX buf_status -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; } else { sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); } - if (need_pre) { + if (use_preamble) { 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. + // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access". + // Write everything except START_TRANS first, wait for the controller to see it, + // then set START_TRANS. usb_hw->sie_ctrl = sie_ctrl; - busy_wait_at_least_cycles(12); + busy_wait_at_least_cycles(32); usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS; } @@ -151,40 +192,36 @@ 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). // 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; +// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0). +// Caller passes the captured buf_ctrl (read BEFORE clearing epx_buf_ctrl). +static void __tusb_irq_path_func(epx_rollback_pid)(hw_endpoint_t *ep, uint32_t 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; - } + const bool is_double = (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS); + + for (uint b = 0; b <= (is_double ? 1u : 0u); b++) { + const uint16_t bc16 = (uint16_t)(buf_ctrl >> (b * 16u)); + if (!bc16) continue; + 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); - +// Save buffer context for EPX preemption (called after STOP_TRANS). +static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) { + epx_rollback_pid(ep, usbh_dpram->epx_buf_ctrl); usbh_dpram->epx_buf_ctrl = 0; - ep->state = EPSTATE_PENDING; } @@ -196,7 +233,7 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { ep->state = EPSTATE_ACTIVE; if (is_setup) { - // panic("new setup \n"); + usbh_dpram->epx_buf_ctrl = 0; // clear stale buf_ctrl from previous endpoint usb_hw->dev_addr_ctrl = ep->dev_addr; sie_start_xfer(true, false, ep->need_pre); } else { @@ -228,6 +265,27 @@ static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ return NULL; } +// After STOP_TRANS in an error handler, the SIE needs time to settle before the +// next START_TRANS. Arm the SOF interrupt so dispatch happens on the next frame +// (~1ms) instead of immediately, which can silently lose the new transfer. +TU_ATTR_ALWAYS_INLINE static inline void arm_post_error_sof(void) { + if (epx->state != EPSTATE_ACTIVE && epx_next_pending(epx) != NULL) { + epx_post_error = true; + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + } +} + +// Queue a pending EPX transfer for round-robin dispatch. +// RP2350: STOP_EPX_ON_NAK switches when the active EPX NAKs. +// RP2040: 2-SOF fallback driven by the SOF interrupt handler. +TU_ATTR_ALWAYS_INLINE static inline void enable_pending_dispatch(void) { + #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 +} //--------------------------------------------------------------------+ // Interrupt handlers @@ -238,17 +296,26 @@ static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_resu rp2usb_reset_transfer(ep); hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true); - // Carry more transfer on epx - if (is_more) { + // Carry more transfer on epx. + // Don't dispatch pending after EP0 (control) completions — leave EPX idle + // so the stack's next phase call (hcd_setup_send/hcd_edpt_xfer) starts + // directly. This prevents bulk traffic (e.g. VCP) from monopolising EPX + // between control phases, which starves recovery sequences. + if (is_more && tu_edpt_number(ep->ep_addr) != 0) { hw_endpoint_t *next_ep = epx_next_pending(epx); if (next_ep != NULL) { epx_switch_ep(next_ep); } } +#ifdef HAS_STOP_EPX_ON_NAK + // RP2350: after EP0 completes EPX is idle and STOP_EPX_ON_NAK cannot fire. + else if (is_more && epx_next_pending(epx) != NULL) { + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + } +#endif } 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 }; @@ -305,56 +372,124 @@ 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) { - uint8_t speed = dev_speed(); + // Clear speed latch first; after settle the re-read reflects the true current state. + usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; + busy_wait_at_least_cycles(32); + const uint8_t speed = dev_speed(); if (speed == SIE_CTRL_SPEED_DISCONNECT) { + // Stop SIE and silence hardware before emitting remove. Concurrent + // BUFF_STATUS / TRANS_COMPLETE bits in this snapshot must not deliver + // completions for the device the stack is about to tear down. + usb_hw->sie_ctrl = SIE_CTRL_BASE; + usb_hw->int_ep_ctrl = 0; + usb_hw_clear->buf_status = 0xffffffffu; + usbh_dpram->epx_buf_ctrl = 0; + epx_post_error = false; + #ifndef HAS_STOP_EPX_ON_NAK + epx_switch_request = false; + #endif + for (uint i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + if (ep_pool[i].state != EPSTATE_IDLE) rp2usb_reset_transfer(&ep_pool[i]); + } 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; + // KEEP_ALIVE_EN is needed even for full-speed root devices (hubs) when + // low-speed devices are attached downstream. It sends a low-speed EOP + // before each SOF so low-speed devices behind the hub can see SOFs. + usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS; } hcd_event_device_attach(RHPORT_NATIVE, true); } - usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; + } + + if (status & USB_INTS_HOST_RESUME_BITS) { + usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS; } if (status & USB_INTS_STALL_BITS) { usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; - xfer_complete_isr(epx, XFER_RESULT_STALLED, true); + usb_hw_clear->buf_status = 0x3u; // prevent spurious EPX BUFF_STATUS + const uint32_t stall_bc = usbh_dpram->epx_buf_ctrl; + usbh_dpram->epx_buf_ctrl = 0; + if (epx->state == EPSTATE_ACTIVE) { + // Device sent STALL: no data ACKed, PID did not toggle on the device. + // Roll back so the post-CLEAR_FEATURE retry can't drift. + epx_rollback_pid(epx, stall_bc); + xfer_complete_isr(epx, XFER_RESULT_STALLED, true); + } } if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { - usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; + usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS | + USB_SIE_STATUS_RX_SHORT_PACKET_BITS | + USB_SIE_STATUS_TRANS_COMPLETE_BITS | + USB_SIE_STATUS_ENDPOINT_ERROR_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) {} + sie_stop_xfer(); + + usb_hw_clear->buf_status = 0x3u; // prevent spurious EPX BUFF_STATUS + const uint32_t rx_bc = usbh_dpram->epx_buf_ctrl; + usbh_dpram->epx_buf_ctrl = 0; - // 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->state == EPSTATE_ACTIVE) { + // Device didn't respond: no ACK on the wire, so device's PID did not toggle. + // Undo the pre-arm toggle from bufctrl_prepare16 so the retry matches. + epx_rollback_pid(epx, rx_bc); xfer_complete_isr(epx, XFER_RESULT_FAILED, false); } + + arm_post_error_sof(); } if (status & USB_INTS_TRANS_COMPLETE_BITS) { - // only applies for epx, interrupt endpoint does not seem to raise this + // Only raised by EPX. Interrupt endpoints signal completion via BUFF_STATUS only. usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS; if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { 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); + } else if (epx->state == EPSTATE_ACTIVE && ((usb_hw->buf_status & 0x3u) == 0)) { + // STATUS-phase ZLP (0-byte transfer): TRANS_COMPLETE fires but BUFF_STATUS does not + // because there is no DPRAM movement for zero bytes. Complete EPX now. + xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true); + } + } + + if (status & USB_INTS_ERROR_DATA_SEQ_BITS) { + // DATA_SEQ must be handled before BUFF_STATUS: if both fire simultaneously, + // processing BUFF_STATUS first would sync the wrong-PID data as SUCCESS. + // Clearing EPX buf_status bits here prevents handle_buf_status_isr() from + // delivering a false success completion after we report FAILED. + usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS | + USB_SIE_STATUS_RX_SHORT_PACKET_BITS | + USB_SIE_STATUS_RX_TIMEOUT_BITS | + USB_SIE_STATUS_TRANS_COMPLETE_BITS | + USB_SIE_STATUS_ENDPOINT_ERROR_BITS; + + sie_stop_xfer(); + + usb_hw_clear->buf_status = 0x3u; + const uint32_t ds_bc = usbh_dpram->epx_buf_ctrl; + TU_LOG(1, " Data Seq Error: [0] = 0x%04x [1] = 0x%04x\r\n", + tu_u32_low16(ds_bc), tu_u32_high16(ds_bc)); + usbh_dpram->epx_buf_ctrl = 0; + if (epx->state == EPSTATE_ACTIVE) { + // PID mismatch was not ACKed on the wire, so device's PID did not toggle. + // Undo the pre-arm toggle from bufctrl_prepare16 so the retry matches. + epx_rollback_pid(epx, ds_bc); + xfer_complete_isr(epx, XFER_RESULT_FAILED, false); } + arm_post_error_sof(); } 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; @@ -367,6 +502,27 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre); } } + + // Post-error SOF deferral: after STOP_TRANS in error handlers, wait one SOF + // for the SIE to settle before dispatching the next pending transfer. + // STOP_EPX_ON_NAK handles the active-EPX case; here we only drive idle-EPX + // dispatch after errors. + if (status & USB_INTS_HOST_SOF_BITS) { + (void)usb_hw->sof_rd; + if (epx_post_error) { + // This SOF may be in the same IRQ snapshot as the error. Skip it; + // the next SOF (~1ms) will dispatch. + epx_post_error = false; + } else { + if (epx->state != EPSTATE_ACTIVE) { + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep != NULL) { + epx_switch_ep(next_ep); + } + } + usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; + } + } #else // 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 @@ -375,29 +531,35 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { // 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) { - usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; - usb_hw->nak_poll = USB_NAK_POLL_RESET; - epx_switch_request = false; - } else if (epx->state == EPSTATE_ACTIVE) { - if (epx_switch_request) { - // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch. + if (epx_post_error) { + // Skip this SOF — may be in the same IRQ snapshot as the error. + // Next SOF (~1ms) will dispatch via the idle-EPX path below. + epx_post_error = false; + } else { + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep == NULL) { + usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; + usb_hw->nak_poll = USB_NAK_POLL_RESET; + epx_switch_request = false; + } else if (epx->state != EPSTATE_ACTIVE) { + // EPX is idle with pending transfers (e.g. after RX_TIMEOUT). + // Start the next pending transfer directly. epx_switch_request = false; - sie_stop_xfer(); - epx_save_context(epx); epx_switch_ep(next_ep); - } else { - epx_switch_request = true; + } 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; + sie_stop_xfer(); + epx_save_context(epx); + epx_switch_ep(next_ep); + } else { + epx_switch_request = true; + } } } } #endif - - 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"); - } } void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { @@ -427,6 +589,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { // clear epx and interrupt eps memset(&ep_pool, 0, sizeof(ep_pool)); + epx_post_error = false; // 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; @@ -452,7 +615,9 @@ bool hcd_deinit(uint8_t rhport) { void hcd_port_reset(uint8_t rhport) { (void)rhport; - // TODO: Nothing to do here yet. Perhaps need to reset some state? + // No software action: the SIE drives the electrical reset timing for the + // root port automatically during connect/enumerate. Downstream port resets + // are issued by the hub class driver via SET_FEATURE(PORT_RESET). } void hcd_port_reset_end(uint8_t rhport) { @@ -484,8 +649,21 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { return; // address 0 is for device enumeration } + if (dev_addr < TU_ARRAY_SIZE(ep0_mps)) { + ep0_mps[dev_addr] = 0; + } + rp2usb_critical_enter(); + // If the current EPX endpoint belongs to the closing device, the SIE may + // still be polling a device that is about to vanish. Stop it and clear + // buf_ctrl so the next EPX user starts from a clean slate. + // (epx never points at an interrupt endpoint — those use int_ep slots.) + if (epx->dev_addr == dev_addr) { + sie_stop_xfer(); + usbh_dpram->epx_buf_ctrl = 0; + } + 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) { @@ -502,6 +680,7 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { *ep_reg = 0; } + ep->interrupt_num = 0; ep->max_packet_size = 0; // mark as unused } } @@ -516,12 +695,14 @@ uint32_t hcd_frame_number(uint8_t rhport) { void hcd_int_enable(uint8_t rhport) { (void)rhport; + // Per-core; see hcd_int_disable for the calling-core contract. irq_set_enabled(USBCTRL_IRQ, true); } void hcd_int_disable(uint8_t rhport) { (void)rhport; - // todo we should check this is disabling from the correct core; note currently this is never called + // irq_set_enabled is per-core: this only masks the USB IRQ on the caller's + // core. Contract: must be called from the same core that called hcd_init. irq_set_enabled(USBCTRL_IRQ, false); } @@ -532,8 +713,8 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t (void)rhport; pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); hw_endpoint_t *ep; - if (dev_addr == 0) { - ep = &ep_pool[0]; + if (dev_addr == 0 || ep_desc->bmAttributes.xfer == TUSB_XFER_CONTROL) { + ep = &ep_pool[0]; // EP0 shares EPX, no pool slot needed } else { ep = edpt_alloc(); } @@ -549,14 +730,31 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t ep->need_pre = need_pre(dev_addr); ep->next_pid = 0u; + // Remember EP0 max packet size so hcd_setup_send/hcd_edpt_xfer can restore it + if (tu_edpt_number(ep_addr) == 0 && dev_addr < TU_ARRAY_SIZE(ep0_mps)) { + ep0_mps[dev_addr] = (uint8_t)max_packet_size; + } + if (ep->transfer_type != TUSB_XFER_INTERRUPT) { + ep->interrupt_num = 0; ep->dpram_buf = usbh_dpram->epx_data; } else { - // from 15 interrupt endpoints pool + // Scan ep_pool (not usb_hw->int_ep_ctrl) to find a free interrupt slot. + // int_ep_ctrl is zeroed during EPX suppression and by abort, so using it + // as a free-slot bitmap causes interrupt_num collisions between devices. 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; + const uint8_t candidate = int_idx + 1; + bool in_use = false; + for (uint j = 0; j < TU_ARRAY_SIZE(ep_pool); j++) { + if (&ep_pool[j] != ep && ep_pool[j].interrupt_num == candidate && + ep_pool[j].max_packet_size > 0) { + in_use = true; + break; + } + } + if (!in_use) { + ep->interrupt_num = candidate; break; } } @@ -591,17 +789,66 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { (void)rhport; - (void)daddr; - (void)ep_addr; - return false; // TODO not implemented yet + if (tu_edpt_number(ep_addr) == 0) return true; // EP0 (EPX) is shared + hw_endpoint_t *ep = edpt_find(daddr, ep_addr); + if (!ep) return true; + + rp2usb_critical_enter(); + if (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; + *dpram_int_ep_ctrl(ep->interrupt_num) = 0; + *dpram_int_ep_buffer_ctrl(ep->interrupt_num) = 0; + } else { + // Non-interrupt (bulk/iso) endpoint using shared EPX + if (epx == ep) { + if (ep->state == EPSTATE_ACTIVE) { + sie_stop_xfer(); + } + usbh_dpram->epx_buf_ctrl = 0; + } + } + rp2usb_reset_transfer(ep); + ep->interrupt_num = 0; + ep->max_packet_size = 0; + rp2usb_critical_exit(); + 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; + hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); + if (!ep) return false; + + rp2usb_critical_enter(); + + if (ep->interrupt_num) { + *dpram_int_ep_buffer_ctrl(ep->interrupt_num) = 0; + rp2usb_reset_transfer(ep); + } else { + if (ep->state == EPSTATE_ACTIVE && ep == epx) { + sie_stop_xfer(); + usbh_dpram->epx_buf_ctrl = 0; + rp2usb_reset_transfer(ep); + + // Clear any pending NAK/SOF round-robin state so a stale interrupt + // doesn't immediately dispatch another endpoint when IRQs re-enable. + #ifdef HAS_STOP_EPX_ON_NAK + usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS | + USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + #else + epx_switch_request = false; + #endif + } else { + ep->state = EPSTATE_IDLE; + ep->remaining_len = 0; + ep->user_buf = 0; + } + } + ep->next_pid = 0; + + rp2usb_critical_exit(); + return true; } bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { @@ -616,11 +863,18 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b 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); + // Re-enable polling in case it was cleared by hcd_edpt_abort_xfer. + // Always set unconditionally; suppress only applies to EPX transactions. + usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num); } 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 + ep->ep_addr = ep_addr; + if (tu_edpt_number(ep_addr) == 0) { + // Control data and status stages always start with DATA1. + ep->dev_addr = dev_addr; + ep->need_pre = need_pre(dev_addr); + ep->max_packet_size = (dev_addr < TU_ARRAY_SIZE(ep0_mps) && ep0_mps[dev_addr]) + ? ep0_mps[dev_addr] : 8; + ep->next_pid = 1; } // If EPX is busy with another transfer, mark as pending @@ -629,14 +883,7 @@ 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->state = EPSTATE_PENDING; - - #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 - 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 + enable_pending_dispatch(); } else { io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; @@ -667,25 +914,25 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet usbh_dpram->setup_packet[i] = setup_packet[i]; } - ep->ep_addr = 0; // setup is OUT - ep->remaining_len = 8; - ep->xferred_len = 0; + ep->dev_addr = dev_addr; + ep->need_pre = need_pre(dev_addr); + ep->max_packet_size = (dev_addr < TU_ARRAY_SIZE(ep0_mps) && ep0_mps[dev_addr]) + ? ep0_mps[dev_addr] : 8; + ep->ep_addr = 0; // setup is OUT + ep->remaining_len = 8; + ep->xferred_len = 0; // If EPX is busy, mark as pending setup (DPRAM already has the packet) 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 - 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 + enable_pending_dispatch(); } else { epx = ep; ep->state = EPSTATE_ACTIVE; + usbh_dpram->epx_buf_ctrl = 0; // clear stale buf_ctrl from previous phase usb_hw->dev_addr_ctrl = ep->dev_addr; - sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); + sie_start_xfer(true, false, ep->need_pre); } rp2usb_critical_exit(); @@ -694,11 +941,19 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { (void)rhport; - (void)dev_addr; - (void)ep_addr; + if (tu_edpt_number(ep_addr) == 0) return true; // CLEAR_FEATURE(HALT) is not valid on EP0 + hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); + if (!ep) return true; - panic("hcd_clear_stall"); - // return true; + rp2usb_critical_enter(); + ep->next_pid = 0; + if (ep->interrupt_num) { + *dpram_int_ep_buffer_ctrl(ep->interrupt_num) = 0; + } else if (epx == ep) { + usbh_dpram->epx_buf_ctrl = 0; + } + rp2usb_critical_exit(); + return true; } #endif diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 1b13934d31..d1d4d255a8 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -30,7 +30,7 @@ #if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED) #include - #include "rp2040_usb.h" + #include "portable/raspberrypi/rp2040/rp2040_usb.h" #include "device/dcd.h" #include "host/hcd.h" @@ -51,10 +51,14 @@ critical_section_t rp2usb_lock; //--------------------------------------------------------------------+ // Implementation //--------------------------------------------------------------------+ -// Provide own byte by byte memcpy as not all copies are aligned +// Provide own byte by byte memcpy as not all copies are aligned. +// Use volatile to prevent compiler from widening to 16/32-bit accesses +// which cause hard fault on RP2350 when dst/src point to USB DPRAM. static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) { + volatile uint8_t *vdst = dst; + const volatile uint8_t *vsrc = src; while (n--) { - *dst++ = *src++; + *vdst++ = *vsrc++; } } @@ -123,7 +127,7 @@ void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) { // Don't need delay in host mode as host is in charge of when to start the transaction. if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) { if (!rp2usb_is_host_mode()) { - busy_wait_at_least_cycles(12); + busy_wait_at_least_cycles(32); } *buf_reg = value; // then set AVAILABLE bit last } @@ -139,7 +143,7 @@ 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 if (value & USB_BUF_CTRL_AVAIL) { if (!rp2usb_is_host_mode()) { - busy_wait_at_least_cycles(12); + busy_wait_at_least_cycles(32); } *buf_reg16 = value; // then set AVAILABLE bit last }