Skip to content

rp2040 fix for at least a dozen crash scenarios#3582

Open
rumbledethumps wants to merge 14 commits into
hathach:masterfrom
rumbledethumps:rp2040-hcd-epx
Open

rp2040 fix for at least a dozen crash scenarios#3582
rumbledethumps wants to merge 14 commits into
hathach:masterfrom
rumbledethumps:rp2040-hcd-epx

Conversation

@rumbledethumps

Copy link
Copy Markdown

This is the diff you asked for. I've been leaving my overrides an AI mess because of the moving target. Feel free to close this after you've reviewed and picked out the good bits.

Copilot AI review requested due to automatic review settings April 2, 2026 04:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the RP2040 native USB host/device implementation to improve robustness when sharing the single host “EPX” endpoint across multiple devices (notably EP0/control traffic), adds additional error/timeout handling in the host ISR, and tweaks RP2040/RP2350 SDK compatibility.

Changes:

  • Replace short cycle-based “concurrent access” delays with busy_wait_us(1) in several USB register write paths.
  • Rework host EP0 handling to always use the shared EPX, track per-device EP0 max packet size, and reduce EPX monopolization between control phases.
  • Implement missing host endpoint close/abort/clear-stall logic and add additional ISR handling/clearing for several host error conditions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
src/portable/raspberrypi/rp2040/rp2040_usb.c Changes buffer-control “concurrent access” delay behavior when setting AVAILABLE.
src/portable/raspberrypi/rp2040/hcd_rp2040.c Substantial host-side changes: EP0 MPS tracking, ISR robustness improvements, endpoint lifecycle implementation, and interrupt endpoint slot allocation changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 121 to 128
// 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 (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) {
if (!rp2usb_is_host_mode()) {
busy_wait_at_least_cycles(12);
busy_wait_us(1);
}
*buf_reg = value; // then set AVAILABLE bit last

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

busy_wait_us(1) replaces the previous 12-cycle wait on the device-side AVAILABLE write path. This adds ~1µs of busy-waiting in an IRQ hot path per buffer arm and may significantly reduce device throughput/latency. Consider reverting to a minimal cycle-based wait (e.g. busy_wait_at_least_cycles(12)) or otherwise justify/guard the longer delay.

Copilot uses AI. Check for mistakes.
Comment on lines 139 to 145
// 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_us(1);
}
*buf_reg16 = value; // then set AVAILABLE bit last
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: switching from a 12-cycle delay to busy_wait_us(1) in the 16-bit bufctrl write adds a relatively large busy-wait in an IRQ path. Prefer the datasheet-minimum cycle wait (or an equivalent minimal delay) to avoid unnecessary throttling.

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +109
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;
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a new non-static exported function hcd_free_ep_count(), but it is not declared in any header and is not referenced anywhere else in the repo. Keeping an unadvertised global symbol in a portable driver makes the surface area harder to reason about; consider making it static (if only for local diagnostics), gating it behind a debug macro, or removing it if unused.

Copilot uses AI. Check for mistakes.
// 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);
busy_wait_us(1);

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

busy_wait_us(1) is used here where the RP2040 datasheet guidance is “wait some cycles” (previously 12 cycles). In an IRQ path, a fixed 1µs wait is a much larger stall than required and could impact scheduling/throughput under load. Consider returning to a minimal cycle-based wait (e.g. busy_wait_at_least_cycles(12)) or otherwise making the delay proportional to the datasheet requirement.

Suggested change
busy_wait_us(1);
busy_wait_at_least_cycles(12);

Copilot uses AI. Check for mistakes.
Comment on lines 650 to 668
ep->max_packet_size = max_packet_size;
ep->ep_addr = ep_addr;
ep->dev_addr = dev_addr;
ep->transfer_type = ep_desc->bmAttributes.xfer;
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->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;

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential stale interrupt_num: when opening a non-interrupt endpoint, interrupt_num is never explicitly cleared. Since endpoints are recycled by edpt_alloc() using only max_packet_size==0, a slot previously used for an interrupt endpoint can be reused for bulk/iso while retaining a nonzero interrupt_num, causing later xfers to go down the interrupt-endpoint path incorrectly. Ensure interrupt_num is set to 0 for non-interrupt endpoints (and/or fully cleared when freeing an endpoint slot).

Copilot uses AI. Check for mistakes.
usbh_dpram->epx_buf_ctrl = 0;
}
}
rp2usb_reset_transfer(ep);

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When closing an endpoint, the slot is marked unused by ep->max_packet_size = 0, but ep->interrupt_num is left unchanged. Because edpt_alloc() reuses slots based on max_packet_size==0, a later non-interrupt endpoint can inherit a stale nonzero interrupt_num and be treated as an interrupt endpoint. Clear interrupt_num (and ideally other per-endpoint identity fields) when freeing the slot.

Suggested change
rp2usb_reset_transfer(ep);
rp2usb_reset_transfer(ep);
ep->interrupt_num = 0;

Copilot uses AI. Check for mistakes.
Comment on lines 737 to +741
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 true;

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hcd_edpt_abort_xfer() returns true even when edpt_find() fails. This conflicts with the HCD API contract in src/host/hcd.h (return false if there is no transfer to abort) and makes it harder for callers to detect incorrect state. Consider returning false when the endpoint isn't found and only returning true when an actual queued/active transfer was cleared.

Copilot uses AI. Check for mistakes.

@hathach hathach left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for the PR, can you give a bit of explanation e.g what this pr improve and/or which issue it resolves.

@rumbledethumps

Copy link
Copy Markdown
Author

These are the problems my changes address. Keep in mind I have a complete MSC implementation with BOT, CBI, LUN, removable media, LBA64, and even TRIM support so I hit code parts that the examples do not, especially concerning removable media error recovery.

  • busy_wait_at_least_cycles uses the CPU clock instead of the SIE clock
  • Devices refuse to mount, e.g. a hub with MSC-BOT, CDC, and HID won't mount the HID.
  • Removing devices from hub causes panic hcd_clear_stall.
  • Too many endpoints allocated, one extra EP0 per device is allocated.
  • Entire close and abort path is missing!!!
  • hcd_free_ep_count() added because pool is so tiny and user needs to know.

@rumbledethumps

Copy link
Copy Markdown
Author

I did some math and busy_wait_at_least_cycles(32) should be safe up to 512 MHz. The original 12 cycles can fail intermittently as you pass 192 MHz, which a lot of projects do.

@rumbledethumps rumbledethumps changed the title rp2040 changes for RP6502 rp2040 fix for at least a dozen crash scenarios Apr 29, 2026
@rumbledethumps

Copy link
Copy Markdown
Author

Just a heads up that the next release will be a significant regression for anyone using the rp2040 in host mode. This PR has already been deployed to my users who all use hubs and have multiple devices connected simultaneously. Typically, keyboard, mouse, gamepad, MSC CBI/BOT, and CDC, all at once. We're also regularly hot plugging devices, which is where most of the crashes happen.

@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

Top 8 targets by memory change (%) (out of 2384 targets) View Project Dashboard →

target .text .rodata .data .bss total % diff
raspberry_pi_pico/hid_controller 24,032 → 24,952 (+920) 908 → 892 (-16) 3,148 → 3,152 (+4) 31,748 → 32,656 (+908) +2.9%
raspberry_pi_pico/device_info 24,752 → 25,664 (+912) 1,004 → 980 (-24) 3,580 → 3,588 (+8) 33,028 → 33,924 (+896) +2.7%
raspberry_pi_pico/bare_api 25,408 → 26,320 (+912) 1,092 → 1,076 (-16) 4,204 → 4,212 (+8) 34,396 → 35,300 (+904) +2.6%
raspberry_pi_pico/midi_rx 26,024 → 26,936 (+912) 664 → 648 (-16) 4,612 → 4,620 (+8) 34,992 → 35,896 (+904) +2.6%
raspberry_pi_pico/midi2_host 26,416 → 27,328 (+912) 944 → 928 (-16) 8,112 → 8,116 (+4) 39,164 → 40,064 (+900) +2.3%
raspberry_pi_pico/cdc_msc_hid 36,640 → 37,552 (+912) 2,032 → 2,016 (-16) 6,864 → 6,872 (+8) 50,608 → 51,512 (+904) +1.8%
raspberry_pi_pico/msc_file_explorer 44,296 → 45,216 (+920) 3,612 → 3,596 (-16) 12,532 → 12,536 (+4) 65,580 → 66,488 (+908) +1.4%
raspberry_pi_pico/msc_file_explorer_freertos 51,356 → 52,276 (+920) 3,836 → 3,820 (-16) 17,656 → 17,660 (+4) 78,060 → 78,968 (+908) +1.2%

@hathach

hathach commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Just a heads up that the next release will be a significant regression for anyone using the rp2040 in host mode. This PR has already been deployed to my users who all use hubs and have multiple devices connected simultaneously. Typically, keyboard, mouse, gamepad, MSC CBI/BOT, and CDC, all at once. We're also regularly hot plugging devices, which is where most of the crashes happen.

Thanks, I saw issues with pico2 with hub + ch34x + a msc device on hil test as well. I am not sure if it is due to my cheap hub, is ordering a ch334 hub to add to hil pool. Will test this out when they arrive

hathach and others added 4 commits June 20, 2026 23:17
Non-static function with no prototype or caller triggers -Werror=missing-prototypes, breaking the RP2040/RP2350 host build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Hardware-in-the-loop (HIL) Test Report

hfp.json

Board cdc_dual_ports dfu cdc_msc cdc_msc_throughput audio_test_freertos dfu_runtime cdc_msc_freertos hid_boot_interface msc_dual_lun hid_generic_inout printer_to_cdc midi_test mtp
stm32l412nucleo ✅ CDC 625k/401k MSC 812k/764k
stm32f746disco ✅ CDC 13.5M/10.3M MSC 15.5M/30.3M
stm32f746disco-DMA ✅ CDC 14.4M/11.2M MSC 16.4M/30.6M
lpcxpresso43s67 ✅ CDC 12.9M/12.2M MSC 29.9M/34.5M

Legend: ✅ pass · ❌ fail · ⚪ skipped · blank not run

tinyusb.json

Board cdc_dual_ports dfu cdc_msc cdc_msc_throughput audio_test_freertos dfu_runtime cdc_msc_freertos hid_boot_interface msc_dual_lun hid_generic_inout printer_to_cdc midi_test mtp host_info_to_device_cdc cdc_msc_hid msc_file_explorer msc_file_explorer_freertos device_info hid_composite_freertos
ek_tm4c123gxl ✅ CDC 627k/737k MSC 826k/1.1M
espressif_p4_function_ev rd 409KB/s
espressif_p4_function_ev-DMA rd 409KB/s
espressif_s3_devkitm rd 409KB/s
espressif_s3_devkitm-DMA rd 409KB/s
feather_nrf52840_express ✅ CDC 445k/311k MSC 556k/516k
max32666fthr ✅ CDC 7M/14.3M MSC 17.7M/21.7M
metro_m4_express ✅ CDC 360k/336k MSC 539k/550k
mimxrt1015_evk ✅ CDC 13.1M/4.5M MSC 24.1M/18.6M
mimxrt1064_evk ✅ CDC 16.7M/12M MSC 23.9M/16.4M rd 1361KB/s rd 1365KB/s
lpcxpresso11u37 ✅ CDC 377k/295k MSC 580k/504k
ra4m1_ek ✅ CDC 499k/358k MSC 595k/509k
raspberry_pi_pico ✅ CDC 648k/512k MSC 742k/989k
raspberry_pi_pico_w rd 1106KB/s rd 1022KB/s
raspberry_pi_pico2 rd 1110KB/s rd 1022KB/s
adafruit_fruit_jam ✅ CDC 1M/566k MSC 1.1M/1M rd 62KB/s rd 62KB/s
stm32f072disco ✅ CDC 418k/280k MSC 550k/499k
stm32f407disco ✅ CDC 737k/562k MSC 869k/988k
stm32f723disco ✅ CDC 838k/719k MSC 1M/987k rd 18078KB/s rd 4096KB/s
stm32f723disco-DMA ✅ CDC 992k/716k MSC 1.1M/1M rd 19418KB/s rd 4096KB/s
stm32h743nucleo ✅ CDC 508k/511k MSC 511k/511k
stm32h743nucleo-DMA ✅ CDC 594k/614k MSC 569k/547k
stm32g0b1nucleo ✅ CDC 534k/492k MSC 572k/561k
stm32l476disco ✅ CDC 557k/547k MSC 548k/522k
stm32u083nucleo ✅ CDC 561k/517k MSC 726k/552k
nanoch32v203-fsdev ✅ CDC 1M/955k MSC 1M/957k
nanoch32v203-usbfs ✅ CDC 767k/602k MSC 1M/957k
ch32v103r_r1_1v0
ch582m_evt ✅ CDC 232k/223k MSC 475k/506k

Legend: ✅ pass · ❌ fail · ⚪ skipped · blank not run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants