Skip to content

Add USB SuperSpeed (USB 3.2) device support with WCH CH569 and CH32H417 ports - #3779

Open
hathach wants to merge 72 commits into
masterfrom
claude/wch-ch569-ch32h417-usb3
Open

Add USB SuperSpeed (USB 3.2) device support with WCH CH569 and CH32H417 ports#3779
hathach wants to merge 72 commits into
masterfrom
claude/wch-ch569-ch32h417-usb3

Conversation

@hathach

@hathach hathach commented Jul 21, 2026

Copy link
Copy Markdown
Owner

First SuperSpeed device support in TinyUSB. The stack work is layered so a new SuperSpeed MCU only needs one DCD file + a BSP — to usbd, SuperSpeed is just another speed.

Stack

  • TUD_OPT_SUPER_SPEED / OPT_MODE_SUPER_SPEED capability, EP0 fixed at 512, ep0_xact_limit() chunking
  • SuperSpeed endpoint-companion descriptors, BOS, SET_SEL / SET_ISOCH_DELAY / U1-U2
  • TUD_*_SS_* descriptor templates; class drivers skip and account for companions via usbd_skip_ss_ep_companion()
  • Device examples are speed-switched via tud_speed_get()0-byte code delta on non-SuperSpeed builds

Ports

  • WCH CH569 (hydrausb3_v1) and CH32H417 (nanoch32h417), each with a USB3 DCD, a USB2 high-speed DCD, and runtime HS fallback on a shared root port
  • test/hil flashing support (OpenOCD/wlink + a UART firmware loader) and rig config

Testing

  • HIL device suites on both boards at SuperSpeed (12/12) plus the usbtest battery
  • New test_usbd.c SuperSpeed cases (endpoint validate, companion skipping, SET_SEL/SET_ISOCH_DELAY)
  • Throughput (SuperSpeed, both boards): usbtest bulk ~72 MB/s, MSC ~200 MB/s read, iperf/NCM ~74–79 Mbps
  • Adds an adding-superspeed-port skill documenting the port contract + hardware-validated gotchas

Notes

  • SuperSpeed-only validated on hardware; ISO-class examples (audio/video/uac2) are out of scope until a DCD supports SuperSpeed isochronous.

Copilot AI review requested due to automatic review settings July 21, 2026 10:49
Comment thread src/device/usbd.c

// EP0 transaction size: a SuperSpeed-capable build (EP0 = 512) can operate on a USB2 fallback
// link where EP0 max packet size is 64
TU_ATTR_ALWAYS_INLINE static inline uint16_t ep0_xact_limit(void) {
Comment thread src/device/usbd.c
Comment on lines +1053 to +1070
switch (p_request->wValue) { //-V2520
case TUSB_REQ_FEATURE_REMOTE_WAKEUP:
TU_LOG_USBD(" Disable Remote Wakeup\r\n");
// Host may disable remote wake up after resuming
_usbd_dev.remote_wakeup_en = 0;
tud_control_status(rhport, p_request);
return true;

// Host may disable remote wake up after resuming
_usbd_dev.remote_wakeup_en = 0;
tud_control_status(rhport, p_request);
return true;
#if TUD_OPT_SUPER_SPEED
case TUSB_REQ_FEATURE_U1_ENABLE:
case TUSB_REQ_FEATURE_U2_ENABLE:
tud_control_status(rhport, p_request);
return true;
#endif

// Stall unsupported feature selector
default: return false;
}
Comment thread src/class/midi/midi2_device.c Fixed
Comment thread src/class/midi/midi2_device.c Fixed
Comment thread src/class/net/ncm_device.c Fixed
Comment thread src/class/net/ncm_device.c Fixed
Comment thread src/class/printer/printer_device.c Fixed
Comment thread src/class/usbtmc/usbtmc_device.c Fixed
Comment thread src/device/usbd.c Fixed
Comment thread src/device/usbd_pvt.h Fixed

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 introduces first-class USB SuperSpeed (USB 3.2 Gen1) device support to TinyUSB, adding core stack capability for SuperSpeed descriptors/control requests and integrating two new WCH SuperSpeed-capable MCU ports (CH569 and CH32H417) plus related examples and HIL automation updates.

Changes:

  • Add SuperSpeed capability flags/options and core stack handling (EP0 sizing/chunking, SS requests, BOS + endpoint companion descriptor handling).
  • Add WCH CH569 + CH32H417 MCU/board integrations (deps, BSP scaffolding, CI matrix/presets, example SS descriptors/configs).
  • Extend unit tests + HIL tooling (usbtest quirk skips, WCH UART flash helper, updated flashing flows).

Reviewed changes

Copilot reviewed 125 out of 125 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/get_deps.py Adds WCH CH32H417 + CH569 SDK dependencies.
test/unit-test/test/support/tusb_config.h Enables SS code paths in unit-test config.
test/unit-test/test/device/usbd/test_usbd.c Adds SS-focused USBD unit tests (validate, companion skipping, SET_SEL/SET_ISOCH_DELAY).
test/hil/wch_uart_flash.py New stdlib-only UART flasher helper for CH32H417 rig use.
test/hil/usbtest.py Adds bcdDevice quirk-driven per-case SKIP logic + reporting.
test/hil/tinyusb-sudoer Adds documented sudoers allowlist template for HIL rigs.
test/hil/hil_test.py Updates WCH flashing flows, adds UART-loader flashing path, adjusts usbtest timeouts.
test/hil/hfp.json Skips usbtest for a wedging board with an explanatory comment.
src/tusb.c Adds SuperSpeed selection + SS-aware endpoint validation + “Super” debug string.
src/tusb_option.h Adds OPT_MODE_SUPER_SPEED + TUD_OPT_SUPER_SPEED + SS EP0 size validation.
src/portable/wch/dcd_ch56x.h Adds CH56x shared USB2/USB3 DCD interface + DMA constraints helper.
src/portable/wch/dcd_ch32h417.h Adds CH32H417 shared USB2/USB3 DCD interface.
src/portable/wch/ch32h417_usbhs_reg.h Adds CH32H417 USBHS register access helpers + static layout checks.
src/portable/wch/ch32h417_usb30_reg.h Adds CH32H417 USBSS register helpers + PHY backdoor defs + layout checks.
src/device/usbd.c Adds SS control handling (SET_SEL/SET_ISOCH_DELAY, U1/U2 feature selectors), EP0 chunking for HS fallback, companion skipping in open pair.
src/device/usbd_pvt.h Adds helper to skip SS endpoint companion descriptors.
src/common/tusb_types.h Extends core types for SuperSpeed (speed enum, SS MPS constants, SS requests/features, SS descriptors, BOS caps).
src/common/tusb_mcu.h Adds CH569/CH32H417 MCU config blocks and superspeed capability defaults.
src/class/usbtmc/usbtmc_device.c Makes USBTMC buffers SS-sized and adjusts SS companion accounting.
src/class/printer/printer_device.c Skips SS endpoint companion descriptors during open parsing.
src/class/net/ncm_device.c Skips SS companion descriptors + accounts SS companion sizing.
src/class/net/ecm_rndis_device.c Skips SS companions + accounts SS companion sizing.
src/class/mtp/mtp_device.c Accounts SS companions in interface sizing + uses SS companion skip helper.
src/class/msc/msc_device.c Accounts SS companions in driver length.
src/class/midi/midi2_device.c Allows SS companion descriptors in descriptor iteration.
src/class/midi/midi_device.c Uses SS companion skip helper after CS endpoint descriptor.
src/class/hid/hid_device.c Accounts SS companions in driver length.
src/class/cdc/cdc_device.c Uses SS companion skip helper during CDC endpoint parsing.
hw/bsp/ch569/wch-riscv.cfg Adds OpenOCD wlinke SDI config for CH569.
hw/bsp/ch569/family.mk Adds CH569 make-family build (USBSS/USBHS selection, fallback, DMA section/alignment).
hw/bsp/ch569/family.cmake Adds CH569 CMake family support.
hw/bsp/ch569/debug_uart.h Adds CH569 debug UART API header.
hw/bsp/ch569/debug_uart.c Adds CH569 non-blocking UART TX implementation.
hw/bsp/ch569/CH56xSFR.H Case-sensitive FS include shim for vendor EVT header naming.
hw/bsp/ch569/boards/hydrausb3_v1/board.mk HydraUSB3 v1 make-board RAM/flash sizing flags.
hw/bsp/ch569/boards/hydrausb3_v1/board.h HydraUSB3 v1 board pin/metadata definitions.
hw/bsp/ch569/boards/hydrausb3_v1/board.cmake HydraUSB3 v1 CMake-board settings.
hw/bsp/ch32h417/wch-riscv.cfg Adds OpenOCD wlinke SDI config for CH32H417.
hw/bsp/ch32h417/system_ch32h417.h Adds BSP-owned system header expected by SDK.
hw/bsp/ch32h417/family.mk Adds CH32H417 make-family build (USBSS/USBHS selection, fallback notes).
hw/bsp/ch32h417/family.cmake Adds CH32H417 CMake family support.
hw/bsp/ch32h417/ch32h417_conf.h Adds BSP-owned SDK config header with only required peripherals.
hw/bsp/ch32h417/boards/nanoch32h417/board.mk nanoCH32H417 make-board stub.
hw/bsp/ch32h417/boards/nanoch32h417/board.h nanoCH32H417 board pin/metadata definitions.
hw/bsp/ch32h417/boards/nanoch32h417/board.cmake nanoCH32H417 CMake-board stub.
hw/bsp/BoardPresets.json Adds build presets for hydrausb3_v1 and nanoch32h417.
examples/device/webusb_serial/src/tusb_config.h SS-capable EP0 + SS-sized CDC/Vendor buffers/epsizes.
examples/device/video_capture/skip.txt Skips CH569/CH32H417 for video example.
examples/device/video_capture_2ch/skip.txt Skips CH569/CH32H417 for video 2ch example.
examples/device/usbtmc/src/tusb_config.h Makes EP0 SS-aware.
examples/device/usbtest/src/usb_descriptors.h Adds bcdDevice-advertised SS quirk bits for host script.
examples/device/usbtest/src/tusb_config.h Makes EP0 SS-aware + sizes vendor RX ep for SS.
examples/device/usbtest/src/main.c Treats SS like HS for interrupt/iso length selection.
examples/device/uac2_speaker_fb/skip.txt Skips CH569/CH32H417 for UAC2 speaker example.
examples/device/uac2_headset/skip.txt Skips CH569/CH32H417 for UAC2 headset example.
examples/device/printer_to_cdc/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/printer_to_cdc/src/tusb_config.h SS-aware EP0 + SS-sized CDC/printer buffers/epsizes.
examples/device/net_lwip_webserver/src/tusb_config.h SS-aware EP0 + WCH RAMX pool/EP max tuning for fit.
examples/device/net_lwip_webserver/src/lwipopts.h WCH-specific lwIP tuning for SS/HS memory + checksum verify skip.
examples/device/net_lwip_webserver/src/arch/cc.h Places lwIP heap/pools in WCH .dmadata section for RAM fit.
examples/device/mtp/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/mtp/src/tusb_config.h SS-aware EP0 + SS-sized MTP bulk buffer.
examples/device/msc_dual_lun/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/msc_dual_lun/src/tusb_config.h SS-aware EP0 + SS-sized MSC buffer.
examples/device/midi2_device/src/usb_descriptors.c Adds SS device/config/BOS descriptors + SS MIDI descriptors.
examples/device/midi2_device/src/tusb_config.h SS-aware EP0.
examples/device/midi_test/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/midi_test/src/tusb_config.h SS-aware EP0 + SS-sized MIDI FIFOs.
examples/device/midi_test_freertos/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/midi_test_freertos/src/tusb_config.h SS-aware EP0 + SS-sized MIDI FIFOs.
examples/device/midi_test_freertos/skip.txt Skips CH569/CH32H417 for this FreeRTOS example.
examples/device/hid_multiple_interface/src/usb_descriptors.c Adds SS device/config/BOS descriptors + SS HID configs.
examples/device/hid_multiple_interface/src/tusb_config.h SS-aware EP0.
examples/device/hid_generic_inout/src/usb_descriptors.c Adds SS device/config/BOS descriptors + SS HID INOUT config.
examples/device/hid_generic_inout/src/tusb_config.h SS-aware EP0.
examples/device/hid_composite/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/hid_composite/src/tusb_config.h SS-aware EP0.
examples/device/hid_composite_freertos/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/hid_composite_freertos/src/tusb_config.h SS-aware EP0.
examples/device/hid_composite_freertos/skip.txt Skips CH569/CH32H417 for this FreeRTOS example.
examples/device/hid_boot_interface/src/usb_descriptors.c Adds SS device/config/BOS descriptors + SS HID boot config.
examples/device/hid_boot_interface/src/tusb_config.h SS-aware EP0.
examples/device/dynamic_configuration/src/tusb_config.h SS-aware EP0 + SS-sized class buffers (CDC/MIDI/MSC).
examples/device/dfu/src/usb_descriptors.c Adds SS device/config support + SS BOS capability count.
examples/device/dfu/src/tusb_config.h SS-aware EP0 + SS-sized DFU transfer buffer.
examples/device/dfu_runtime/src/usb_descriptors.c Adds SS device/config support + SS BOS capability count.
examples/device/dfu_runtime/src/tusb_config.h SS-aware EP0.
examples/device/cdc_uac2/skip.txt Skips CH569/CH32H417 for CDC+UAC2 example.
examples/device/cdc_msc/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/cdc_msc/src/tusb_config.h SS-aware EP0 + SS-sized CDC/MSC buffers.
examples/device/cdc_msc_throughput/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/cdc_msc_throughput/src/tusb_config.h SS-aware EP0 + SS-sized throughput buffers.
examples/device/cdc_msc_throughput/src/main.c Adds LED heartbeat task (noted as load-bearing on CH32H417 USBSS).
examples/device/cdc_msc_freertos/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/cdc_msc_freertos/src/tusb_config.h SS-aware EP0 + SS-sized CDC/MSC buffers.
examples/device/cdc_msc_freertos/skip.txt Skips CH569/CH32H417 for this FreeRTOS example.
examples/device/cdc_dual_ports/src/usb_descriptors.c Adds SS device/config/BOS descriptors + EP0 FS/HS sizing fix.
examples/device/cdc_dual_ports/src/tusb_config.h SS-aware EP0 + SS-sized CDC buffers.
examples/device/audio_test/skip.txt Skips CH569/CH32H417 for audio test.
examples/device/audio_test_multi_rate/skip.txt Skips CH569/CH32H417 for multi-rate audio test.
examples/device/audio_test_freertos/skip.txt Skips CH569/CH32H417 for FreeRTOS audio test.
examples/device/audio_4_channel_mic/skip.txt Skips CH569/CH32H417 for 4ch mic example.
examples/device/audio_4_channel_mic_freertos/skip.txt Skips CH569/CH32H417 for FreeRTOS 4ch mic example.
docs/reference/dependencies.rst Documents new WCH SDK dependencies and revisions.
docs/reference/boards.rst Adds nanoch32h417 and hydrausb3_v1 to board reference table.
.github/workflows/ci_set_matrix.py Adds ch32h417 and ch569 families to CI toolchain matrix.
.claude/skills/usbtest/SKILL.md Documents SS quirk-skip reporting + dynamic-id safety details.
.claude/skills/usb-debug/SKILL.md Updates usb-debug deployment guidance for rig environments.

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

Comment thread src/device/usbd_pvt.h Outdated
Comment on lines +113 to +121
// SuperSpeed: advance past an endpoint companion descriptor if present
TU_ATTR_ALWAYS_INLINE static inline
const uint8_t* usbd_skip_ss_ep_companion(const uint8_t* p_desc, const uint8_t* desc_end) {
if (TUD_OPT_SUPER_SPEED && tu_desc_in_bounds(p_desc, desc_end) &&
TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION == tu_desc_type(p_desc)) {
p_desc = tu_desc_next(p_desc);
}
return p_desc;
}
Comment thread src/class/usbtmc/usbtmc_device.c Outdated
Comment thread test/hil/usbtest.py Outdated
Comment on lines +40 to +44
# Quirk flags advertised in bcdDevice bits 4-7 (tier is bits 0-3), see usb_descriptors.h.
# Each flag maps to the cases the silicon cannot pass at SuperSpeed (5 Gbps).
QUIRK_SKIPS = {
0x10: ((14, 21), 'EP0 OUT data lost at 5 Gbps (silicon erratum)'),
0x20: ((13,), 'halted EP answers a single STALL at 5 Gbps (silicon limitation)'),
Comment thread test/hil/wch_uart_flash.py Outdated
Comment on lines +49 to +53
if b in (bytes([c]) for c in want):
return True
if b not in (bytes([c]) for c in ok_noise):
# unexpected byte (e.g. 'E'/'X'): fail fast
return False
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

Size Difference Report

Because TinyUSB code size varies by port and configuration, the metrics below represent the averaged totals across all example builds.

Note: If there is no change, only one value is shown.

Changes >1% in size

file .text .rodata .data .bss size % diff
dcd_ci_hs.c 1771 ➙ 1932 (+161) 0 0 1344 ➙ 779 (-565) 2549 ➙ 2712 (+163) +6.4%
dcd_lpc_ip3511.c 1538 ➙ 1580 (+42) 0 0 264 1758 ➙ 1800 (+42) +2.4%
hcd_ci_hs.c 181 ➙ 186 (+5) 0 0 0 181 ➙ 186 (+5) +2.8%
midi2_device.c 3001 ➙ 3608 (+607) 34 1342 ➙ 1600 (+258) 555 ➙ 563 (+8) 3569 ➙ 4183 (+614) +17.2%
mtp_device.c 1715 ➙ 1780 (+65) 22 743 ➙ 767 (+24) 589 ➙ 593 (+4) 2312 ➙ 2381 (+69) +3.0%
usbd.c 3611 ➙ 3717 (+106) 57 90 ➙ 92 (+2) 354 4028 ➙ 4135 (+107) +2.7%
TOTAL 11817 ➙ 12803 (+986) 113 2175 ➙ 2459 (+284) 3106 ➙ 2553 (-553) 14397 ➙ 15397 (+1000) +6.9%
Changes <1% in size
file .text .rodata .data .bss size % diff
cdc_device.c 1235 ➙ 1237 (+2) 16 1092 722 1955 ➙ 1957 (+2) +0.1%
dcd_ch32_usbfs.c 1698 ➙ 1708 (+10) 0 0 1364 3062 ➙ 3072 (+10) +0.3%
dcd_ch32_usbhs.c 1922 0 0 481 2402 ➙ 2404 (+2) +0.1%
dcd_dwc2.c 4229 ➙ 4230 (+1) 19 0 265 4513 ➙ 4514 (+1) +0.0%
dcd_eptri.c 2273 ➙ 2281 (+8) 0 0 259 2532 ➙ 2540 (+8) +0.3%
dcd_lpc17_40.c 1843 ➙ 1842 (-1) 0 0 792 2239 ➙ 2238 (-1) -0.0%
dcd_mm32f327x_otg.c 1478 ➙ 1474 (-4) 0 0 1290 2768 ➙ 2764 (-4) -0.1%
dcd_musb.c 2666 ➙ 2667 (+1) 0 0 179 2845 ➙ 2846 (+1) +0.0%
dcd_samg.c 1322 ➙ 1326 (+4) 0 0 72 1394 ➙ 1398 (+4) +0.3%
ecm_rndis_device.c 1067 ➙ 1083 (+16) 0 1 2759 ➙ 2763 (+4) 3826 ➙ 3846 (+20) +0.5%
hid_device.c 1121 ➙ 1132 (+11) 44 997 ➙ 1009 (+12) 119 1240 ➙ 1250 (+10) +0.8%
midi_device.c 1148 0 1007 614 1759 ➙ 1760 (+1) +0.1%
msc_device.c 2522 ➙ 2527 (+5) 108 2293 ➙ 2297 (+4) 802 3325 ➙ 3330 (+5) +0.2%
ncm_device.c 1766 ➙ 1812 (+46) 28 821 ➙ 843 (+22) 4393 6172 ➙ 6218 (+46) +0.7%
printer_device.c 827 ➙ 829 (+2) 0 706 555 1380 ➙ 1382 (+2) +0.1%
usbtmc_device.c 2261 ➙ 2284 (+23) 24 68 ➙ 69 (+1) 311 2604 ➙ 2627 (+23) +0.9%
video_device.c 4446 ➙ 4477 (+31) 5 1235 ➙ 1245 (+10) 480 4919 ➙ 4949 (+30) +0.6%
TOTAL 33824 ➙ 33979 (+155) 244 8220 ➙ 8269 (+49) 15457 ➙ 15461 (+4) 48935 ➙ 49095 (+160) +0.3%
No changes
file .text .rodata .data .bss size % diff
audio_device.c 2885 0 1252 1621 4501 +0.0%
cdc_host.c 6479 487 15 946 7639 +0.0%
dcd_ci_fs.c 1955 0 0 1290 3245 +0.0%
dcd_da146xx.c 3067 0 0 144 3211 +0.0%
dcd_ft9xx.c 3284 0 0 172 3456 +0.0%
dcd_msp430x5xx.c 1801 0 0 176 1977 +0.0%
dcd_nrf5x.c 2974 0 0 292 3266 +0.0%
dcd_nuc120.c 1096 0 0 78 1174 +0.0%
dcd_nuc121.c 1170 0 0 101 1271 +0.0%
dcd_nuc505.c 0 0 1533 157 1690 +0.0%
dcd_rp2040.c 1004 0 764 653 2420 +0.0%
dcd_rusb2.c 3346 0 0 156 3502 +0.0%
dcd_samd.c 1071 0 0 266 1337 +0.0%
dcd_stm32_fsdev.c 2568 0 0 291 2859 +0.0%
dfu_device.c 776 28 712 134 910 +0.0%
dfu_rt_device.c 157 0 134 0 157 +0.0%
dwc2_common.c 603 22 0 0 615 +0.0%
ehci.c 2763 0 0 6274 7783 +0.0%
fsdev_common.c 182 0 0 0 182 +0.0%
hcd_ch32_usbfs.c 2491 0 0 502 2993 +0.0%
hcd_ci_fs.c 2466 0 0 469 2936 +0.0%
hcd_dwc2.c 5071 25 1 545 5642 +0.0%
hcd_musb.c 3071 0 0 157 3228 +0.0%
hcd_pio_usb.c 262 0 240 0 502 +0.0%
hcd_rp2040.c 1996 17 4 321 2338 +0.0%
hcd_rusb2.c 2951 0 0 245 3196 +0.0%
hcd_samd.c 2220 0 0 324 2544 +0.0%
hcd_stm32_fsdev.c 3248 0 1 420 3670 +0.0%
hid_host.c 1247 0 0 1288 2534 +0.0%
hub.c 1385 8 8 30 1419 +0.0%
midi2_host.c 1802 0 0 5921 7723 +0.0%
midi_host.c 1339 7 7 3538 4880 +0.0%
msc_host.c 1625 0 0 395 2020 +0.0%
ohci.c 1925 0 0 2503 4428 +0.0%
rp2040_usb.c 386 35 632 11 1065 +0.0%
rusb2_common.c 160 0 16 0 176 +0.0%
tusb.c 449 0 389 3 451 +0.0%
tusb_fifo.c 855 0 486 0 850 +0.0%
typec_stm32.c 1230 8 2 19 1255 +0.0%
usbc.c 500 2 20 166 688 +0.0%
usbh.c 4979 57 81 1173 6256 +0.0%
vendor_device.c 1132 0 538 1561 2685 +0.0%
TOTAL 79971 696 6835 32342 114674 +0.0%

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0deec1784

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/portable/wch/dcd_ch32h417_usbhs.c Outdated
Comment on lines +233 to +235
{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove blocking debug poll from USB2 init

When the CH32H417 USB2 path is used directly (SPEED=high) or via USB3 fallback, this temporary poll runs after USBHSD->CONTROL enables the device but before dcd_int_enable() can enable USBHS interrupts. That leaves the device attached for roughly six seconds without servicing reset/setup traffic, so hosts can time out or abandon enumeration before the driver starts handling the bus; this looks like leftover diagnostics and should be removed or moved behind an explicit debug build guard before enabling the controller.

Useful? React with 👍 / 👎.

uint32_t status = USBSSD->USB_STATUS;

if ((status & USBSS_UDIF_SETUP) && !(status & USBSS_UDIF_STATUS)) {
USBSSD->USB_STATUS = USBSS_UDIF_SETUP;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep SETUP held until EP0 is armed

For CH32H417 SuperSpeed enumeration, this clears USBSS_UDIF_SETUP before handle_setup() defers the response to the TinyUSB task, but the setup_flag_held mechanism above says the flag must stay asserted until the first EP0 arm/stall so the controller keeps NRDYing the host. With the flag released here, a host that asks for the data/status stage before task context arms EP0 can hit an unarmed control endpoint and time out; set the hold/mask path instead of clearing the flag immediately.

Useful? React with 👍 / 👎.

Comment on lines +686 to +687
NVIC_DisableIRQ(USBSS_IRQn);
NVIC_DisableIRQ(USBSS_LINK_IRQn);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop TIM12 when disabling the USB3 DCD

When the CH32H417 SuperSpeed DCD is initialized with USB2 fallback, dcd_init() starts TIM12 via fallback_timer_start(true), and the retrain backoff can also arm it; however this disable path only masks the USBSS vectors. After tud_deinit()/tusb_deinit(), the shared ISR no longer dispatches to dcd_int_handler() once the role is invalid, so a still-enabled TIM12 update flag can interrupt continuously without being cleared; stop/disable TIM12 here as well.

Useful? React with 👍 / 👎.


.bMaxPacketSize0 = 9,
.idVendor = 0xCafe,
.idProduct = USB_PID + 11, // should be different PID than desc0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the MSC-mode SuperSpeed PID unique

When the dynamic-configuration example is in mode 1 at SuperSpeed, this advertises 0x400d + 11 == 0x4018, which collides with net_lwip_webserver and differs from the FS/HS mode-1 descriptor that deliberately uses USB_PID + 0x0100 to stay outside the per-example PID range. That breaks the fresh host driver/configuration match the examples rely on when reflashing or switching modes; use the same out-of-band PID for the SuperSpeed mode-1 descriptor.

Useful? React with 👍 / 👎.

Comment on lines +189 to +190
if (tud_speed_get() == TUSB_SPEED_SUPER) {
return desc_ss_configuration;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add a high-speed descriptor for MIDI2 fallback

When a SuperSpeed build runs on CH569's default USB2 fallback or another USB2-only connection, tud_speed_get() is TUSB_SPEED_HIGH, so this SuperSpeed-only branch is skipped and the callback falls through to desc_fs_configuration; that descriptor's MIDI bulk endpoints are 64 bytes, and usbd_edpt_open() rejects high-speed bulk endpoints unless MPS is 512. The example will fail to configure after fallback; add a HS configuration before returning the FS descriptor.

Useful? React with 👍 / 👎.

Comment thread src/device/usbd.c Outdated
Comment on lines +1084 to +1085
TU_VERIFY(6u == p_request->wLength);
return tud_control_xfer(rhport, p_request, _ctrl_epbuf.buf, 6);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stall malformed SET_SEL directions

On SuperSpeed, SET_SEL is an OUT device request with a 6-byte data stage; if a host sends the same bRequest as an IN request with wLength == 6, this accepts it and tud_control_xfer() transmits six bytes from _ctrl_epbuf instead of stalling. That can leak stale control-buffer contents and leaves the control transfer out of spec; validate the request direction and zero fields before queuing the transfer.

Useful? React with 👍 / 👎.

Comment thread src/portable/wch/dcd_ch32h417_usbhs.c Outdated
Comment on lines +233 to +244
{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}
char s[12];
const uint8_t ms = USBHSD->MIS_ST, fg = USBHSD->INT_FG, sp = (uint8_t)USBHSD->BUS;
const char hx[] = "0123456789abcdef";
s[0]='s'; s[1]=hx[ms>>4]; s[2]=hx[ms&15]; s[3]=' ';
s[4]=hx[fg>>4]; s[5]=hx[fg&15]; s[6]=' ';
s[7]=hx[sp>>4]; s[8]=hx[sp&15]; s[9]='\r'; s[10]='\n';
board_uart_write(s, 11);
}
}

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.

Leftover // TEMP bring-up instrumentation — fails to build and stalls enumeration. This block reads as debug code left in from hardware bring-up, and has two concrete problems:

  1. Build failure. board_uart_write() (line 242) is declared only in hw/bsp/board_api.h, which this DCD translation unit does not include — it pulls in only tusb_option.h, device/dcd.h, dcd_ch32h417.h, ch32h417_usbhs_reg.h. The call is therefore an implicit function declaration, which trips -Werror-implicit-function-declaration (set in hw/bsp/family_support.cmake) for exactly the configs that compile this file: the standalone USBHS build (SPEED=high) and the SuperSpeed→USB2 fallback build (CFG_TUD_WCH_USB30_FALLBACK=1). (The default SS-only preset compiles this file to nothing via the top-of-file #if, which is why the default CI build doesn't surface it.) It also reaches from the device stack into the BSP, crossing the layering boundary.

  2. ~6 s busy-wait on the init path. Even setting the build aside, the loop spins ~6×25M volatile iterations synchronously inside ch32h417_usb2_init() — reached from both dcd_init() and the USB3→USB2 fallback_to_usb2() path — which stalls the usbd task through the enumeration window.

Recommend deleting this block (lines 233-244) before merge.

Comment thread src/portable/wch/ch56x_usb30_reg.h Outdated
Comment on lines +97 to +98
#define usbss_status_ep_num(st) (((st) >> 8) & 7u)
#define usbss_status_ep_is_in(st) (((st) >> 12) & 1u)

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.

Minor / CLAUDE.md consistency: these two function-like macros are lowercase, but the repo's CLAUDE.md requires UPPER_CASE macros. Every other macro in this file follows that — including the argument-taking ones a few lines below (USBSS_EP_R_EN(ep), USBSS_EP_T_EN(ep), …) — and the sibling ch32h417_usb30_reg.h:54-55 implements the same accessors as USBSS_STATUS_EP_NUM / USBSS_STATUS_EP_IN. Suggest renaming to match; the two call sites in dcd_ch56x_usb30.c:598-599 would need updating too.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

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

hfp-iar

✅ 55 passed · ❌ 0 failed · ⚪ 0 skipped · blank not run

Board usbtest cdc_msc_throughput audio_test_freertos cdc_dual_ports cdc_msc cdc_msc_freertos dfu dfu_runtime hid_boot_interface hid_generic_inout midi_test msc_dual_lun mtp printer_to_cdc duration
lpcxpresso43s67 ✅ C 10.8/9.7M M 31.3/29.2M 78s
stm32l412nucleo ✅ 30/30 ✅ C 507/511k M 511/511k 115s
stm32f746disco ✅ 30/30 ✅ C 13.4/14.5M M 24.9/30.8M 100s
stm32f746disco-DMA ✅ 30/30 ✅ C 13.4/12.3M M 25.8/31.6M 86s

hfp.json

✅ 55 passed · ❌ 0 failed · ⚪ 0 skipped · blank not run

Board usbtest cdc_msc_throughput audio_test_freertos cdc_dual_ports cdc_msc cdc_msc_freertos dfu dfu_runtime hid_boot_interface hid_generic_inout midi_test msc_dual_lun mtp printer_to_cdc duration
lpcxpresso43s67 ✅ C 12/9.6M M 31.2/30.9M 69s
stm32l412nucleo ✅ 30/30 ✅ C 510/506k M 527/511k 101s
stm32f746disco ✅ 30/30 ✅ C 13.6/12.5M M 21.6/30.8M 96s
stm32f746disco-DMA ✅ 30/30 ✅ C 13.9/12.2M M 25.4/32M 88s

tinyusb-esp.json

✅ 22 passed · ❌ 2 failed · ⚪ 0 skipped · blank not run

Board usbtest msc_file_explorer_freertos audio_test_freertos cdc_msc_freertos device_info hid_composite_freertos duration
espressif_p4_function_ev ✅ 30/30 409 KB/s 123s
espressif_p4_function_ev-DMA ✅ 30/30 409 KB/s 110s
espressif_s3_devkitm ✅ 30/30 143s
espressif_s3_devkitm-DMA ✅ 30/30 140s

tinyusb.json

✅ 353 passed · ❌ 28 failed · ⚪ 17 skipped · blank not run

Board usbtest cdc_msc_throughput msc_file_explorer msc_file_explorer_freertos audio_test_freertos cdc_dual_ports cdc_msc cdc_msc_freertos cdc_msc_hid device_info dfu dfu_runtime hid_boot_interface hid_generic_inout host_info_to_device_cdc midi_test msc_dual_lun mtp printer_to_cdc duration
raspberry_pi_pico_w 1106 KB/s 1022 KB/s 26s
frdm_k64f 27s
raspberry_pi_pico2 83s
stm32g0b1nucleo ✅ 30/30 ✅ C 529/481k M 581/518k 162s
stm32f407disco ✅ 30/30 ✅ C 550/541k M 517/526k 184s
ra4m1_ek ✅ 30/30 ✅ C 507/460k M 511/515k 202s
metro_m4_express ✅ 30/30 ✅ C 512/585k M 576/527k 214s
ek_tm4c123gxl ✅ 30/30 ✅ C 499/511k M 546/553k 231s
feather_nrf52840_express ✅ 30/30 ✅ C 507/511k M 516/503k 254s
lpcxpresso55s28 ✅ 30/30 ✅ C 8.5/8.2M M 30.9/26.9M 255s
stm32f072disco ✅ 30/30 ✅ C 507/280k M 510/495k 283s
max32666fthr ✅ 30/30 ✅ C 6.9/14.3M M 17.7/21.3M 305s
ch32v103r_r1_1v0 ✅ 30/30 ✅ C 499/501k M 511/512k 229s
raspberry_pi_pico ✅ 30/30 ✅ C 537/511k M 526/511k 62 KB/s 62 KB/s 290s
lpcxpresso11u37 ✅ 30/30 ✅ C 502/268k M 471/540k 324s
stm32l476disco ✅ 30/30 ✅ C 507/512k M 514/549k 316s
stm32u083nucleo ✅ 30/30 ✅ C 522/511k M 518/511k 192s
adafruit_fruit_jam ✅ 30/30 ✅ C 507/511k M 511/511k 62 KB/s 62 KB/s 255s
nanoch32v203-fsdev ✅ 30/30 ✅ C 507/511k M 514/527k 152s
nanoch32v203-usbfs ✅ 30/30 284s
mimxrt1064_evk ✅ 30/30 ✅ C 21.4/12.3M M 32.5/26.6M 1365 KB/s 1361 KB/s 233s
ch582m_evt ✅ 30/30 ✅ C 232/205k M 479/481k 233s
nrf54lm20dk ✅ 30/30 ✅ C 2.1/4.1M M 7.7/8.1M 233s
stm32h743nucleo ✅ 30/30 ✅ C 501/513k M 512/520k 223s
stm32h743nucleo-DMA ✅ 30/30 ✅ C 507/516k M 511/513k 279s
stm32f723disco ✅ 30/30 ✅ C 579/540k M 535/559k 13443 KB/s 3942 KB/s 316s
stm32f723disco-DMA ✅ 30/30 ✅ C 501/511k M 512/554k 18078 KB/s 3942 KB/s 198s
ch32v307v_r1_1v0-usbhs ✅ 30/30 ✅ C 6.9/8M M 18.7/12.2M 247s
ch32v307v_r1_1v0-usbfs ✅ 30/30 ✅ C 507/510k M 511/511k 123s

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

Top 10 targets by memory change (%) (out of 2533 targets) View Project Dashboard →

target .text .rodata .data .bss total % diff
ch582m_evt/dfu_runtime 6,564 → 6,820 (+256) 17,444 → 14,836 (-2,608) -15.0%
ch582m_evt/hid_generic_inout 7,140 → 7,420 (+280) 18,240 → 15,656 (-2,584) -14.2%
ch582m_evt/hid_multiple_interface 7,644 → 7,916 (+272) 18,604 → 16,012 (-2,592) -13.9%
ch582m_evt/hid_boot_interface 7,812 → 8,084 (+272) 18,804 → 16,212 (-2,592) -13.8%
ch582m_evt/hid_composite 7,948 → 8,228 (+280) 18,928 → 16,344 (-2,584) -13.7%
ch582m_evt/midi_test 8,304 → 8,560 (+256) 19,528 → 16,920 (-2,608) -13.4%
ch582m_evt/cdc_dual_ports 8,896 → 9,148 (+252) 20,448 → 17,836 (-2,612) -12.8%
ch582m_evt/printer_to_cdc 9,212 → 9,468 (+256) 20,696 → 18,088 (-2,608) -12.6%
ch582m_evt/webusb_serial 9,672 → 9,928 (+256) 21,260 → 18,652 (-2,608) -12.3%
ch582m_evt/usbtmc 10,404 → 10,664 (+260) 21,920 → 19,316 (-2,604) -11.9%

@rhgndf

rhgndf commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

I have my own implementation of USBSS driver that I was testing in #3782
feel free to merge the changes in if you need them

I took a look and the main differences are that this PR has USB2 fallback, and #3782 has maxburst implemented which probably explains why the speed is higher

@hathach

hathach commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

I have my own implementation of USBSS driver that I was testing in #3782 feel free to merge the changes in if you need them

I took a look and the main differences are that this PR has USB2 fallback, and #3782 has maxburst implemented which probably explains why the speed is higher

thank you, I am still clearing some other works, and still setting the hil rig up. Once I get both ch56x and h417 reliably in the hil pool, we can merge both SS PRs to get first ever SS support to tinyusb.

@hathach
hathach force-pushed the claude/wch-ch569-ch32h417-usb3 branch from a0deec1 to 023d235 Compare July 24, 2026 16:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 023d235032

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/common/tusb_types.h
enum {
TUSB_EPSIZE_BULK_FS = 64,
TUSB_EPSIZE_BULK_HS = 512,
TUSB_EPSIZE_BULK_SS = 1024,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make bulk-size defaults account for SuperSpeed

With SuperSpeed enabled, this adds a 1024-byte bulk MPS but leaves TUD_EPSIZE_BULK_MAX at the old HS/FS value, so any SuperSpeed class using the default CFG_TUD_*_EPSIZE/FIFO sizes allocates 512 bytes (or 64 when CFG_TUD_MAX_SPEED is explicitly OPT_MODE_SUPER_SPEED) while the new SS descriptor macros advertise 1024-byte bulk endpoints. For example, CDC/vendor RX paths queue tu_edpt_packet_size(desc_ep) bytes into endpoint buffers sized from those defaults, so a single max-size OUT packet can overrun the buffer; include TUD_OPT_SUPER_SPEED in the max-size default to preserve TinyUSB's memory-safe invariant.

AGENTS.md reference: AGENTS.md:L3-L3

Useful? React with 👍 / 👎.

// WCH CH56x: lwIP memory lives in the 32 KB RAMX (see arch/cc.h). The SuperSpeed build
// reclaims the USB2-fallback bounce RAM (CFG_TUD_WCH_USBHS_EP_MAX) for a larger receive
// window - iperf is TCP-window-bound; the plain high-speed build keeps the smaller fit
#if defined(CFG_TUD_WCH_USBIP_USB30)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check USBIP macro values, not just definitions

In SPEED=high WCH builds, tusb_mcu.h still defines CFG_TUD_WCH_USBIP_USB30 to 0 after this file includes tusb_option.h, so defined(CFG_TUD_WCH_USBIP_USB30) is true and the SuperSpeed branch is selected instead of the USBHS branch. That gives the plain high-speed build the 9-pbuf TCP window and disables inbound checksum verification despite the comment saying USBHS keeps the smaller fit; use value checks such as #if CFG_TUD_WCH_USBIP_USB30 / #elif CFG_TUD_WCH_USBIP_USBHS.

Useful? React with 👍 / 👎.

/* WCH CH56x: only 16 KB general SRAM (RAMS); place the lwIP heap and memory pools in the
32 KB RAMX region (the .dmadata section of the ch56x linker script) to make room. The
defines below come from the ch56x board build. */
#if defined(CFG_TUD_WCH_USBIP_USB30) || defined(CFG_TUD_WCH_USBIP_USBHS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate the lwIP .dmadata override to CH56x

This condition also matches other WCH targets once tusb_option.h has supplied default USBIP macros (for example CH32V307/CH32H417 USBHS/USB30 builds), but .dmadata is only defined and explicitly cleared in the CH569 linker/startup code. Building this example for those WCH boards will place lwIP heap/pool objects into an unintended orphan section instead of normal RAM; restrict the override to the CH56x target and use macro value checks rather than defined(...).

Useful? React with 👍 / 👎.

// dcd API
//--------------------------------------------------------------------+

bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Implement deinit for the new WCH DCDs

These new WCH DCDs expose dcd_init() but none of the four new WCH backends (dcd_ch32h417_usb30.c, dcd_ch32h417_usbhs.c, dcd_ch56x_usb30.c, dcd_ch56x_usbhs.c) implement dcd_deinit(), so tud_deinit() falls through to the weak dcd_deinit() in usbd.c which returns false and trips TU_ASSERT(dcd_deinit(rhport)) after interrupts are disabled. Any application that deinitializes/reinitializes the stack on these boards will fail before class/queue cleanup; add a real deinit that shuts down the selected controller state and returns true.

Useful? React with 👍 / 👎.

Comment thread src/device/usbd.c
Comment on lines +1041 to +1044
case TUSB_REQ_FEATURE_U1_ENABLE:
case TUSB_REQ_FEATURE_U2_ENABLE:
// Accept and ignore: U1/U2 entry is managed by the link layer/dcd
tud_control_status(rhport, p_request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Track U1/U2 feature state

When a SuperSpeed host sends SET_FEATURE(U1_ENABLE) or SET_FEATURE(U2_ENABLE, this ACKs the request but leaves _usbd_dev.dev_state_bm with only the self-powered/remote-wakeup bits, and the matching CLEAR_FEATURE cases below also do nothing. A subsequent standard GET_STATUS(Device) therefore reports U1/U2 disabled even though the request succeeded, which breaks the control-state contract hosts and USB compliance tests observe; track bits 2/3 through SET/CLEAR or stall these requests if the stack is not going to maintain that state.

Useful? React with 👍 / 👎.

Comment thread src/portable/wch/dcd_ch32h417_usb30.c Outdated
Comment on lines +711 to +713
void dcd_sof_enable(uint8_t rhport, bool en) {
(void)rhport;
(void)en;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward SOF enable when CH32H417 falls back to USB2

When a CH32H417 SuperSpeed build runs with CFG_TUD_WCH_USB30_FALLBACK=1 and the link has switched to the USB2 controller, tud_sof_cb_enable() still reaches this no-op instead of ch32h417_usb2_sof_enable(). Any class or application that relies on SOF callbacks after fallback will never enable the USBHS SOF interrupt even though the USB2 backend implements it; add the same fallback dispatch used by the endpoint and status APIs.

Useful? React with 👍 / 👎.

Comment thread src/portable/wch/dcd_ch32h417_usbhs.c Outdated
Comment on lines +233 to +244
{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}
char s[12];
const uint8_t ms = USBHSD->MIS_ST, fg = USBHSD->INT_FG, sp = (uint8_t)USBHSD->BUS;
const char hx[] = "0123456789abcdef";
s[0]='s'; s[1]=hx[ms>>4]; s[2]=hx[ms&15]; s[3]=' ';
s[4]=hx[fg>>4]; s[5]=hx[fg&15]; s[6]=' ';
s[7]=hx[sp>>4]; s[8]=hx[sp&15]; s[9]='\r'; s[10]='\n';
board_uart_write(s, 11);
}
}

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.

Leftover // TEMP: debug scaffolding in ch32h417_usb2_init() — should be removed before merge. It has two concrete defects:

1. Compile error under -Werror. board_uart_write is declared in hw/bsp/board_api.h, but that header is not in this translation unit's include chain (only tusb_option.h, device/dcd.h, dcd_ch32h417.h, ch32h417_usbhs_reg.h). The call at L242 is therefore an implicit function declaration, which fails the build (-Werror + -Werror-implicit-function-declaration, set in hw/bsp/family_support.cmake and examples/build_system/make/toolchain/gcc_common.mk). The default nanoch32h417 build (SPEED=super, FALLBACK=0) doesn't compile this function body — so default CI stays green — but the SPEED=high build and the SPEED=super + CFG_TUD_WCH_USB30_FALLBACK=1 build (this PR's headline HS-fallback feature) both compile it and won't build.

2. Multi-second blocking busy-wait. 6 × 25,000,000 volatile iterations is several seconds of blocking inside ch32h417_usb2_init(), which runs from dcd_init() and from the USB3→USB2 fallback path — long enough to blow the host's USB2 enumeration timing.

Suggest deleting the entire block (L233–L244).

Comment thread src/portable/wch/dcd_ch32h417_usbhs.c Outdated
// read-modify-write) so RB_UEP_R_SETUP_IS is cleared - otherwise the next OUT would be
// re-detected as a SETUP in the ISR. Applies to both the zero-length status and a
// (single-packet, EP0-sized) control-write data stage.
EP_RX_CTRL(0) = USBHS_UEP_R_TOG_DATA1 | USBHS_UEP_R_RES_ACK;

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.

EP0 OUT data toggle is hard-wired to DATA1 on every transfer — multi-packet control-write data stages get a toggle mismatch. This DCD manages the data toggle in software, but nothing alternates the EP0 OUT toggle across successive data-stage packets: update_out() skips the flip for EP0 (its ep_num != 0 guard), so each dcd_edpt_xfer(0x00, …) re-arms DATA1 here.

On the USB2/fallback link, usbd chunks EP0 data stages to ep0_xact_limit() = 64 bytes, issuing one dcd_edpt_xfer(0x00, …, 64) per packet. For a control-write with wLength > 64 (DFU_DNLOAD, a large HID SET_REPORT, a WebUSB/vendor control-OUT): packet 1 arms DATA1 (host sends DATA1 → OK), then packet 2 arms DATA1 again while the host sends DATA0 → data-toggle mismatch → the SIE drops/NAKs it → the transfer hangs (or data is corrupted from byte 64 on). Enumeration itself uses only ZLP/IN control transfers, so the device enumerates fine and this bites later.

The sibling CH569 driver does this correctly — it XORs the toggle for data-stage packets and hard-wires DATA1 only for the zero-length status stage: dcd_ch56x_usbhs.c L402–L408. Suggest applying the same pattern here.

@hathach
hathach force-pushed the claude/wch-ch569-ch32h417-usb3 branch from 023d235 to 6429b8b Compare July 30, 2026 10:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6429b8b1eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/hil/wch_uart_flash.py Outdated
return False

for off in range(0, len(padded), CHUNK):
os.write(fd, b"C" + struct.pack("<H", CHUNK) + padded[off:off + CHUNK])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry short writes to the UART loader

When a signal or TTY-driver condition causes os.write() to accept only part of this 1027-byte frame, the script ignores the returned byte count and waits for an acknowledgement while the device is still waiting for the remainder. Because the flash was already erased, this can leave an unbootable partial image and abort the recovery path; send each header and chunk with a write-all loop that verifies every byte was accepted.

AGENTS.md reference: AGENTS.md:L20-L20

Useful? React with 👍 / 👎.

Comment thread src/device/usbd.c
Comment on lines +1087 to +1089
case TUSB_REQ_SET_ISOCH_DELAY:
// wValue = isochronous delay in ns, no data stage: just ACK
tud_control_status(rhport, p_request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject malformed SET_ISOCH_DELAY transfers

When a host sends this request with the IN direction or a nonzero wLength, this path ACKs it and immediately queues what it treats as the status stage instead of stalling. The host is expecting a data transaction in the opposite direction, so EP0 becomes desynchronized and the transfer times out; validate the required host-to-device direction, zero wIndex, and zero wLength before acknowledging.

Useful? React with 👍 / 👎.

Comment thread src/portable/wch/dcd_ch56x_usbhs.c Outdated
Comment on lines +380 to +382
} else {
EP_TX_CTRL(ep_num) = UEP_T_RES_NAK | RB_UEP_T_TOG_0;
ep_data_tog[ep_num][TUSB_DIR_IN] = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-arm active IN transfers after clearing their toggle

When CLEAR_FEATURE(ENDPOINT_HALT) is sent to a non-stalled IN endpoint with a transfer already armed, usbd_edpt_clear_stall() deliberately retains the endpoint's CLAIMED state, but this branch changes the hardware response to NAK without re-arming the still-valid DCD transfer. No replacement transfer can be claimed, so the endpoint remains NAKing indefinitely; mirror the OUT path by re-queuing the active IN transfer after resetting DATA0. The same omission exists in ch32h417_usb2_edpt_clear_stall().

Useful? React with 👍 / 👎.

Comment on lines +671 to +673
bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
(void)rhport;
(void)rh_init;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Implement software connect and disconnect for the WCH DCDs

When an application calls tud_disconnect() to force a host re-enumeration and later calls tud_connect(), both APIs report success but the bus remains attached: a repository-wide search of the four new WCH DCD files finds no strong dcd_disconnect() or dcd_connect(), so the weak no-op implementations in usbd.c are used. Implement these operations for the selected USBSS/USBHS controller, including the active fallback controller.

Useful? React with 👍 / 👎.

Comment thread src/portable/wch/dcd_ch32h417_usbhs.c Outdated
Comment on lines +233 to +244
{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}
char s[12];
const uint8_t ms = USBHSD->MIS_ST, fg = USBHSD->INT_FG, sp = (uint8_t)USBHSD->BUS;
const char hx[] = "0123456789abcdef";
s[0]='s'; s[1]=hx[ms>>4]; s[2]=hx[ms&15]; s[3]=' ';
s[4]=hx[fg>>4]; s[5]=hx[fg&15]; s[6]=' ';
s[7]=hx[sp>>4]; s[8]=hx[sp&15]; s[9]='\r'; s[10]='\n';
board_uart_write(s, 11);
}
}

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.

Leftover TEMP debug scaffolding shipped in the driver — compile break + multi-second blocking stall

This TEMP-labeled block busy-waits ~6 s (6 × a 25M-iteration volatile loop) and then calls board_uart_write():

{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}
char s[12];
const uint8_t ms = USBHSD->MIS_ST, fg = USBHSD->INT_FG, sp = (uint8_t)USBHSD->BUS;
const char hx[] = "0123456789abcdef";
s[0]='s'; s[1]=hx[ms>>4]; s[2]=hx[ms&15]; s[3]=' ';
s[4]=hx[fg>>4]; s[5]=hx[fg&15]; s[6]=' ';
s[7]=hx[sp>>4]; s[8]=hx[sp&15]; s[9]='\r'; s[10]='\n';
board_uart_write(s, 11);
}
}

Two independent problems:

  1. Compile error. board_uart_write() is declared only in hw/bsp/board_api.h, which this translation unit does not include (its includes are tusb_option.h, device/dcd.h, dcd_ch32h417.h, ch32h417_usbhs_reg.h). The call is therefore an implicit function declaration → a hard error under the tree's -Werror=implicit-function-declaration. (A portable DCD calling a BSP function is also a layering violation — it's the only such call in src/portable/wch/.)

  2. Blocks enumeration / runs in ISR context. The spin sits right after the controller is enabled (USBHSD->CONTROL = USBHS_UD_DEV_EN | …, L231) and before dcd_init() returns, so on the standalone SPEED=high path the host sees the attach and then gets no response through reset/first SETUP for ~6 s (enumeration timeout). In the CFG_TUD_WCH_USB30_FALLBACK=1 config, ch32h417_usb2_init() is reached from fallback_to_usb2() inside the USB ISR (dcd_int_handler → handle_timer_irq / handle_link_irq in dcd_ch32h417_usb30.c), so the entire blocking loop + blocking UART writes execute in the interrupt handler — contrary to CLAUDE.md's "defer ISR work to task context" (CLAUDE.md L20). The same file's handle_link_irq even notes "a blocking UART print here breaks SuperSpeed training."

ch32h417_usb2_init() compiles in the documented SPEED=high (CFG_TUD_WCH_USBIP_USBHS=1) and USB2-fallback builds — just not the default SuperSpeed-only build, which is likely why default CI didn't surface it.

Suggested fix: delete the entire TEMP block (lines 233–244).

@hathach
hathach force-pushed the claude/wch-ch569-ch32h417-usb3 branch from 6429b8b to c6764ba Compare July 31, 2026 01:08
Comment thread src/class/net/ncm_device.c Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6764bab02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +744 to +746
if (_fb_state == FB_USB2_ACTIVE) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Signal remote wakeup through the USB2 fallback

When the default CH569 SuperSpeed build has fallen back to USB2 and the host has enabled remote wakeup, tud_remote_wakeup() reports success but this branch returns without driving resume signaling, so a suspended host never wakes. Route this operation to the active USBHS controller; the CH32H417 fallback path similarly writes the disabled USBSS block instead of handling USB2.

Useful? React with 👍 / 👎.

Comment thread hw/bsp/ch32h417/family.c
Comment on lines +329 to +332
IWDG_SetPrescaler(IWDG_Prescaler_256);
IWDG_SetReload(1000); // ~8 s at the ~32 kHz LSI
IWDG_ReloadCounter();
IWDG_Enable();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid enabling an unconditionally resetting watchdog

On nanoCH32H417 this unconditionally starts an approximately eight-second watchdog whose only reload outside initialization is board_led_write(). Applications are not required to blink the board LED, and the newly supported printer_to_cdc example's main loop never calls it, so that example—and any similar user application—reboots continuously even while tud_task() is healthy; make this recovery watchdog opt-in or reload it from an actual liveness path.

Useful? React with 👍 / 👎.

Comment thread src/class/mtp/mtp_device.c Outdated
Comment on lines 292 to 294
const uint8_t* p_bulk = usbd_skip_ss_ep_companion(tu_desc_next(ep_desc_int), (const uint8_t*)itf_desc + max_len);
const tusb_desc_endpoint_t* ep_desc_bulk = (const tusb_desc_endpoint_t*) p_bulk;
TU_ASSERT(usbd_open_edpt_pair(rhport, (const uint8_t*)ep_desc_bulk, 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Track the SuperSpeed MTP packet size

When MTP opens this new SuperSpeed bulk endpoint, ep_sz_fs remains zero and the IN completion path therefore keeps using its hard-coded 512-byte threshold instead of the endpoint's 1024-byte MPS. If an object's final queued chunk is exactly 512 bytes, it is already a short SuperSpeed packet, but the driver treats it as full-sized and queues an extra ZLP; that ZLP can terminate the host's next bulk read prematurely. Store the negotiated bulk MPS and use it for short-packet and ZLP decisions at every speed.

Useful? React with 👍 / 👎.

Comment on lines +258 to +264
// superspeed configuration: bulk endpoints are 1024 with companion descriptors
static uint8_t const ncm_ss_configuration[] = {
// Config number (index+1), interface count, string index, total length, attribute, power in mA
TUD_CONFIG_SS_DESCRIPTOR(CONFIG_ID_NCM + 1, ITF_NUM_TOTAL, 0, NCM_SS_CONFIG_TOTAL_LEN, 0, 96),

// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), max segment size, EP notification bInterval, NCM capabilities, bulk max burst.
TUD_CDC_NCM_SS_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report a SuperSpeed NCM link rate

When this new SuperSpeed NCM configuration is selected, notification_xmit() in src/class/net/ncm_device.c recognizes only TUSB_SPEED_HIGH and sends 12,000,000 bit/s for every other speed. Consequently a 5-Gbit/s link is announced to the host as full-speed, producing incorrect link metrics and potentially affecting host network policy; add an explicit SuperSpeed rate, capped to the notification's 32-bit fields.

Useful? React with 👍 / 👎.

Comment thread test/hil/usbtest.py
Comment on lines +42 to +44
QUIRK_SKIPS = {
0x20: ((13,), 'halted EP answers a single STALL at 5 Gbps (silicon limitation)'),
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Register the CH569 control-OUT erratum quirk

At SuperSpeed the CH569 DCD documents that EP0 OUT lengths congruent to 1 modulo 4 are intermittently dropped, and usbtest cases 14 and 21 exercise those lengths, but the new skip table contains only the halt quirk. The firmware likewise advertises only 0x20, despite the accompanying usbtest skill defining this control erratum as 0x10, so the supposedly quirk-aware HIL battery still runs the silicon-impossible cases and fails intermittently; advertise 0x10 for CH569 and map it here to cases 14 and 21.

Useful? React with 👍 / 👎.

Comment thread src/portable/wch/dcd_ch32h417_usbhs.c Outdated
Comment on lines +233 to +244
{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}
char s[12];
const uint8_t ms = USBHSD->MIS_ST, fg = USBHSD->INT_FG, sp = (uint8_t)USBHSD->BUS;
const char hx[] = "0123456789abcdef";
s[0]='s'; s[1]=hx[ms>>4]; s[2]=hx[ms&15]; s[3]=' ';
s[4]=hx[fg>>4]; s[5]=hx[fg&15]; s[6]=' ';
s[7]=hx[sp>>4]; s[8]=hx[sp&15]; s[9]='\r'; s[10]='\n';
board_uart_write(s, 11);
}
}

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.

Leftover // TEMP debug scaffolding blocks USB2 init — and breaks the Make build

This self-labeled // TEMP block busy-waits ~6 s (for (volatile uint32_t i = 0; i < 25000000u; i++) {} repeated 6×) printing raw register dumps, immediately after the device pull-up is enabled in ch32h417_usb2_init(). It runs unconditionally in two shipping, supported configurations:

  • Standalone high-speed (SPEED=highCFG_TUD_WCH_USBIP_USBHS=1): dcd_init()ch32h417_usb2_init() spins ~6 s before interrupts are enabled, so the host (which begins enumeration within ~100 ms of attach) times out / reset-loops.
  • USB3→USB2 runtime fallback (CFG_TUD_WCH_USB30_FALLBACK=1): fallback_to_usb2() invokes this from the TIM12/LINK ISR, turning it into a ~6 s interrupt handler that wedges the system.

It also fails to compile under the Make toolchain: board_uart_write() (line 242) is not declared in this translation unit — none of the includes (tusb_option.h, device/dcd.h, dcd_ch32h417.h, ch32h417_usbhs_reg.h) reach hw/bsp/board_api.h, and examples/build_system/make/toolchain/gcc_common.mk sets -Werror-implicit-function-declaration.

This is bring-up scaffolding — please remove the entire block (lines 233-244). It's also a CLAUDE.md "Surgical changes" violation ("Remove only orphans your changes created").

{ // TEMP: poll link status ~6 s post-attach (MIS_ST / INT_FG / SPEED_TYPE)
for (int t = 0; t < 6; t++) {
for (volatile uint32_t i = 0; i < 25000000u; i++) {}
char s[12];
const uint8_t ms = USBHSD->MIS_ST, fg = USBHSD->INT_FG, sp = (uint8_t)USBHSD->BUS;
const char hx[] = "0123456789abcdef";
s[0]='s'; s[1]=hx[ms>>4]; s[2]=hx[ms&15]; s[3]=' ';
s[4]=hx[fg>>4]; s[5]=hx[fg&15]; s[6]=' ';
s[7]=hx[sp>>4]; s[8]=hx[sp&15]; s[9]='\r'; s[10]='\n';
board_uart_write(s, 11);
}
}

Comment thread src/portable/wch/ch56x_usb30_reg.h Outdated
Comment on lines +97 to +98
#define usbss_status_ep_num(st) (((st) >> 8) & 7u)
#define usbss_status_ep_is_in(st) (((st) >> 12) & 1u)

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.

Macros should be UPPER_CASE

These two function-like macros are lowercase, whereas the other macros in this same file (e.g. USBSS_EP_R_EN(ep) on the lines just below) and the equivalent accessors in the sibling port header — USBSS_STATUS_EP_NUM / USBSS_STATUS_EP_IN in ch32h417_usb30_reg.h — are all UPPER_CASE.

Per CLAUDE.md: "... snake_case helpers, UPPER_CASE macros."

Note that renaming (e.g. to USBSS_STATUS_EP_NUM / USBSS_STATUS_EP_IS_IN) also requires updating the two call sites in dcd_ch56x_usb30.c (lines 598-599).

#define USBSS_USB_STATUS_CLEAR_INIT 0x13u // W1C all pending at init
#define usbss_status_ep_num(st) (((st) >> 8) & 7u)
#define usbss_status_ep_is_in(st) (((st) >> 12) & 1u)

@hathach
hathach force-pushed the claude/wch-ch569-ch32h417-usb3 branch from c6764ba to c2d984c Compare July 31, 2026 05:48
Comment thread src/class/net/ncm_device.c Fixed
Comment thread src/device/usbd_pvt.h Fixed
hathach added 28 commits August 19, 2026 10:48
The re-arm fixed F1 (FB_USB3_UP is terminal, so a replug into a USB2-only host
stayed dead) but introduced a worse regression, because the ladder advances on a
timer regardless of whether anything is attached.

Unplugged, nothing sets _fb_saw_terms, so the budget is 4 ticks of 0.55 s: at
~2.2 s training is declared exhausted and USB3 is torn down, and the 5th tick
at ~2.75 s runs ch56x_usb2_init() and latches FB_USB2_ACTIVE, which is itself
terminal. Any unplug longer than about three seconds therefore demoted the board
to 480 Mbps permanently - including the ordinary case of replugging into the
same SuperSpeed host, and this is the shipping default for hydrausb3_v1.

Re-arming safely requires gating the ladder on an attached partner rather than
on a timer, so F1 stays open. The rest of the state-owner work is unaffected:
fallback_enter() remains the only writer of _fb_state, fallback_timer_start()
still re-enables TMR0_IRQn (F10), and the deferred re-init still re-checks the
state after its settle (F7).
…e real request

Two defects in the reset added earlier in this branch.

It was gated only on the compile-time TUD_OPT_SUPER_SPEED, while every other
USB3-only handler in the same function also tests link_is_superspeed().
remote_wakeup_en is the SHARED USB 2.0 bit that SET_FEATURE(DEVICE_REMOTE_WAKEUP)
sets, and USB 2.0 9.4.5 clears it only on a device reset - so on a runtime USB2
fallback link (CFG_TUD_WCH_USB30_FALLBACK, the CH569 default) a same-value
SET_CONFIGURATION, which Linux's usb_reset_configuration() sends, silently
revoked a wakeup authorization the host never revoked. The gate now lives in
func_wakeup_clear_all() itself, covering all three call sites.

The SET_INTERFACE reset also sat in the branch that runs only when the class
driver does NOT handle the request. Every class implementing alternate settings
- audio, video, ncm, ecm_rndis, vendor, bth, midi2, dfu - handles SET_INTERFACE
itself and returns true, so the reset never fired for precisely the devices
hosts send SET_INTERFACE to. It now runs before the class dispatch.
Both fixes shipped in the first remediation pass and both were wrong. F1's
re-arm demotes the board permanently on any unplug because the ladder advances
on a timer with nothing attached; F4's reordering performs the very write-0 on
RB_UEP_R_DONE that set_rx_res() forces a 1 to avoid. Each entry records what a
correct fix would need.
The sudoers grants and the wildcarded tee removal are out of this PR: the rig
account is expected to have passwordless sudo, so nothing in that file gates
anything there. Records what would need doing if the template is ever pointed
at a host without blanket sudo.
ch32h417_usb2_deinit() reset the SIE but left INT_EN and INT_FG alone, and the
USB3 dcd_init() path calls it without first disabling USBHS_IRQn. That vector
aliases to the USB3 handler, which takes the USB3 branch and never writes
INT_FG, so a flag latched by a prior fallback keeps the line asserted and the
ISR re-enters forever - the failure mode this driver already documents
elsewhere. ch56x_usb2_deinit() has always cleared INT_EN first.
fb_state selects which controller every dcd_* entry point delegates to. It is
written from the LINK/TIM12 ISR and read from task context by dcd_edpt_xfer,
dcd_edpt_open, dcd_edpt_stall, dcd_edpt_clear_stall, dcd_edpt_close_all,
dcd_set_address and dcd_connect - and both WCH families build with -flto, so
the compiler may inline those bodies into tud_task()'s loop and hoist the load,
leaving the task path writing clock-gated USBSS registers after the ISR handed
the port to USB2. Same exposure for fb_fail_count, pending_addr/valid (written
in task context, read by the ISR) and tim12_clocked. The CH569 twin already
marks its equivalents volatile.
usb30_hw_deinit()'s own GO_DISABLED write latches LINK_IF_DISABLE, and
usb30_hw_init() re-enables that bit in LINK_INT_CTRL. usb30_hw_reinit_task()
clears the flag register in between; usb30_bus_reset() and the TMR0 re-detect
retry did not, so the next LINK interrupt could enter handle_link_irq() with a
stale DISABLE and hand the port to USB2 - which would make the 4/8-tick retry
budget unreachable against a host that is merely slow to train.
…region

The section is NOLOAD and startup_CH56x.S zeroes it, so an object placed there
WITH an initialiser silently loses it: the bytes never reach the .bin and the
zero-fill runs over them, with no diagnostic from the compiler, linker or
objcopy. That is reachable by ordinary code because CFG_TUSB_MEM_SECTION is
defined to .dmadata for this family. Nothing currently relies on an initialiser
there, so this documents the contract rather than changing behaviour, and drops
the AT>FLASH load region, which is meaningless for a section that is never
loaded (the ELF now reports LMA == VMA instead of a flash address).
Two halves of the same computation disagreed once TUSB_SPEED_SUPER existed.
frame_div and k already resolve SUPER to the microframe form by testing
"== TUSB_SPEED_FULL", but the fifo_count rate constants and the feedback
interval tested "== TUSB_SPEED_HIGH", so at SuperSpeed the constants stayed 8x
too large and tud_audio_feedback_interval_isr() fired 8x too rarely. Latent
today - no DCD serves SuperSpeed isochronous yet - but it is shared class code.
The CH32H417 twin's terminal FB_USB3_UP and the CH569's unconditional demotion
on the first LINK_IF_DISABLE are both real, but the obvious fix for each is the
one that already regressed as F1, so they wait on a partner-aware ladder. Also
prunes the runners-up list of the items now fixed.
usb30_bus_reset_from_isr() deinits the controller and then latches
_hw_reinit_deferred only if usbd_defer_func() actually queued the work. When the
usbd event queue is full the deferral is dropped - and nothing else can ever run
it, because usb30_hw_deinit() has already zeroed LINK_INT_CTRL, so the dispatch
test in dcd_int_handler is permanently false and no LINK interrupt can arrive to
retry. Without the fallback ladder (the default) there is no timer either, and
with it FB_USB3_UP has already stopped TMR0. The port stayed off the bus until
the next dcd_init.

The partner-gone caller queues DCD_EVENT_UNPLUGGED one line earlier, so a single
free slot is enough to lose the re-init. Fall back to settling inline when the
deferral fails: ~30 ms in the ISR only on a full queue, versus losing the device.
…AKEUP at SS

func_wakeup_clear_all() on SET_INTERFACE wiped every function's bit plus the
device-level aggregate, so on a composite SuperSpeed device a routine
SET_INTERFACE(audio, alt=1) revoked the wake authorization the host had armed on
an unrelated HID function: tud_remote_wakeup() silently no-ops while
GET_STATUS(that interface) still reports D1=1. USB 3.2 Table 9-10 scopes the
reset to the function being reconfigured, so it now clears only the addressed
interface and re-derives the aggregate.

SET/CLEAR_FEATURE(DEVICE_REMOTE_WAKEUP) were also the only device selectors left
ungated by link_is_superspeed(). USB 3.2 9.2.5.4 does not use that selector on
Enhanced SuperSpeed devices - Linux picks the recipient by speed for exactly
this reason - and honouring it desynchronised remote_wakeup_en from the
func_wakeup_bm it is now derived from, in both directions. They are refused on a
SuperSpeed link, guarded so non-SuperSpeed builds still compile.

The FUNCTION_SUSPEND handler's open-coded aggregate loop becomes
func_wakeup_recompute(), the single definition of that rule.
test_usbd_set_sel, set_isoch_delay and their malformed variants never called
switch_to_superspeed() and passed only because an unrelated preceding test left
_usbd_dev.speed at SUPER - setUp() re-inits only when the stack is uninitialised
and tearDown() is empty. Run alone they failed, and worse, the two malformed
variants asserted exactly the double EP0 stall that a NON-SuperSpeed link
produces, so they reported PASS without ever exercising the wIndex/wLength/
direction validation they exist for. Verified: each of the four now passes in
isolation via --test-case.
hs_drop_rate.py ran its lsusb hammer with both streams discarded and no return
code, so a refused or missing sudo made every iteration a no-op while main()
still printed a rate - an enumeration-only figure presented as a hammered one.
It now fails loudly with sudo's own message.

flash_rom_read_words() spun on the SPI-ROM status bit in five unbounded loops,
reached from GET_DESCRIPTOR(STRING) on every serial-string request rather than
once at boot, on a BSP that arms no watchdog: a stuck bit hung the core
mid-control-transfer until a power cycle. Every wait is now bounded (including
the first command byte, which was previously unguarded) and
board_get_unique_id() reports no serial rather than hanging.
Seven validated findings that are not patched here, each with why: the unit-test
suite's SuperSpeed default (a project.yml restructure), the CH56x EP0 RX_CTRL
race (wants the H417's software-toggle shape and hardware sign-off), the BTH
iso walk, the lwIP checksum gate (a maintainer judgement call), the
usbd_open_edpt_pair signature break (wants a changelog line), the
lpcxpresso43s67 usbtest skip (wants an external baseline), and the triplicated
USBHS transfer engine.
Stalling it was a regression, caught on hardware. USB 3.2 9.2.5.4 says the
DEVICE_REMOTE_WAKEUP selector is "ignored and not used by Enhanced SuperSpeed
devices" - ignored, not rejected - and I read that as license to STALL. On a
CH32H417 at 5 Gbps a STALL there made usbtest ctrl_out (14, 21) and iso read
(16, 23) fail; bisected to this change and confirmed by reverting only these two
guards, with func_wakeup_clear_itf left in place and innocent.

Now genuinely ignored: the status stage is acknowledged and only the state
change is skipped, which still keeps remote_wakeup_en from being set while
func_wakeup_bm is empty - the desync the gate existed to prevent.
Reverts 5322732 and its follow-up d33e6da.

Hardware refutes them. On the CH32H417 at 5 Gbps the full usbtest battery drops
from 29/30 to 25/30, failing ctrl_out (14, 21) and iso read (16, 23), and
msc_dual_lun stops presenting its storage node. Bisected with full batteries:
775849bd9, f9a6e1f and this tip minus both commits all pass 29/30; the tip
with them fails 25/30.

Note the failures appear only in the FULL battery - all four cases pass when run
with --tests in isolation on every build, including the failing one - so an
isolated bisect proves nothing here and an earlier one of mine wrongly cleared
the change.

The review finding behind it is real: func_wakeup_clear_all() on SET_INTERFACE
wipes every function's FUNCTION REMOTE WAKEUP bit, not just the addressed one.
But clearing per-interface before the class dispatch regresses this device, and
with the mechanism not understood the honest position is to keep the working
behaviour and record the defect rather than ship a fix that costs four cases.
The earlier link_is_superspeed() gate on func_wakeup_clear_all (3507142)
stays: it was in f9a6e1f, which passes.
…KEUP gate"

This restores 5322732 and d33e6da. My bisect was invalid and the revert was
not justified.

The error: I compared usbtest run through hil_test on this tip against usbtest
run DIRECTLY on the baseline, and read the difference as a regression. Running
the current harness against the pre-remediation baseline firmware reproduces the
same 25/30 and the same four cases (14, 21, 16, 23), so the failure predates
every change in this branch and is a property of how hil_test invokes the
battery, not of the firmware.

Direct invocation gives 29/30 on both firmwares - baseline and tip, with and
without these commits - which is why the isolated --tests runs looked clean.

The DEVICE_REMOTE_WAKEUP handling stays as the corrected "ignore" rather than
the original stall: USB 3.2 9.2.5.4 says the selector is ignored by Enhanced
SuperSpeed devices, and acknowledging without changing state is what that means.
25/30 through hil_test, 29/30 running usbtest.py directly on the same binary -
and identical on the pre-remediation baseline, so it predates this branch.
Records what has been ruled out, the one dmesg clue, and the method note that
cost two bad bisects: never compare a harness run against a direct one.
find_firmware searches build roots in a fixed order, so a stale tree in the
first root silently wins over a fresh build in a later one and the whole run
reports results for firmware nobody meant to test. That is expensive to spot:
the symptom is failing test cases rather than an error, and it survives
reflashes and reboots.

It cost a long debugging session here. hil_test defaults --build-dir to
cmake-build (the tools/build.py layout), and a month-old cmake-build/ tree
shadowed every run; its pre-SuperSpeed-iso-size CH32H417 binary failed usbtest
cases 14, 21, 16 and 23 plus msc_dual_lun. The hil skill already documents
passing -B examples for the CLAUDE.md build layout - the flag was simply
omitted - so this adds no documentation, only a check that makes the mistake
audible instead of silent. With -B examples both WCH boards are clean: nano
29/30 (1 skipped), hydra 27/30 (3 skipped), 0 failed.

The check fires once per example, naming both paths and the age gap.
Replaces the open entry with the resolution: hil_test defaults --build-dir to
cmake-build, the runs omitted -B examples, and a 17 July tree shadowed every
lookup. The CH32H417 binary in it predates the SuperSpeed iso max-packet fix,
which is the whole 512-errors-of-1024 signature. Both boards are clean with the
flag, and it records that earlier HIL results in this session came from that
stale tree.
_wch_uart_park() calls glob.glob() to find the WCH-LinkE VCP, but glob was
never imported, so reset_wch_uart_loader() raised NameError instead of parking
the board. It went unnoticed because call_flasher() catches Exception and turns
it into a plain "reset failed", so the board simply never got parked and the
run carried on.

Reproduced before the fix (NameError: name 'glob' is not defined) and after it
(_wch_uart_park returns True against the real probe).
dcd_init() cleared a hand-picked subset - xfer_status, _pool_brk,
_pending_addr, _ep0_seq, _lmp_pending - and left _ep0_rx_seq,
_ep0_status_pending, _ep0_status_dir, _ep0_early_len, _ep0_data_in and
_link_suspended holding the previous session's values. After a
dcd_deinit()/dcd_init() cycle the first control transfer of the new session
could therefore complete a phantom status stage or replay stale early-OUT
data. It now calls ep_state_reset(), which already exists for exactly this,
plus the two fields outside it.
edpt_stall()/edpt_clear_stall() lacked the ep_num < EP_MAX guard that
edpt_open()/edpt_close() carry on the CH32H417 (and that neither carried on the
CH56x), so the two entry points reached by a host CLEAR/SET_FEATURE(HALT)
were the unguarded ones. Hardening rather than a fix: usbd asserts
ep_num < CFG_TUD_ENDPPOINT_MAX, which is 8 for both parts and equals EP_MAX, so
an out-of-range id cannot reach the driver today - the comment says so rather
than implying a live hole.

hs_drop_rate.py's teardown could raise TimeoutExpired out of sniffer.wait()
inside the finally, leaving the capture running and still writing - the exact
leak that block exists to prevent. It now escalates to kill().
The header still said 'compile-verified; full hardware bring-up (usbtest at
5 Gbps) is pending' while the same file carried ten hardware-derived findings,
and the board now runs 12/12 HIL examples and usbtest 29/30 at 5 Gbps. A reader
deciding whether to trust this driver was being told the opposite of the truth.
The LINK_IF_DISABLE branch handed the port to the USB2 controller
unconditionally, with no _fb_state or tick-budget test, and FB_USB2_ACTIVE has
no exit short of dcd_init. So unplugging a board that had already trained
SuperSpeed demoted it permanently: replugged into the SAME SuperSpeed port it
came back at 480 Mbps.

Reproduced on the hydra with the board powered from its WCH-Link so the cable
move could not reset it - 5-3 at 480 after the replug, and 6-3 at 5000 again
only after a reflash, which is dcd_init doing the recovery. With this change the
same move returns 5000 directly.

Reaching Disabled from FB_USB3_UP is a cable being pulled, not a partner
refusing SuperSpeed, so it now reports the disconnect and rebuilds the link
through the deferred path the TERM_PRESENT-lost branch already uses. The
demotion stays for FB_USB3_TRAINING, where it belongs; the CH32H417 twin has
always gated it that way (fb_state == FB_USB3_TRAINING && ++fb_fail_count >=
FB_FAIL_LIMIT), so this also closes a disagreement between the two families.

Does NOT fix F1: a replug into a genuinely USB2-only host still leaves the board
dead, because the ladder cannot distinguish that from an empty port without a
VBUS signal. Deliberately does not re-arm training on disconnect either - that
was tried and reverted, since the ladder then advances on its timer while
unplugged and lands in FB_USB2_ACTIVE within ~2.75 s, demoting the board on any
unplug.

HIL after the change: hydra 12/12 usbtest 27/30 (3 skipped), nano 12/12 usbtest
29/30 (1 skipped), 0 failed.
Splits the old 'two findings left alone' entry: the unconditional demotion on
LINK_IF_DISABLE is fixed and hardware-verified, while F1 (replug into a
USB2-only host) and the CH32H417 twin stay open on the same blocker - the
ladder needs an attach signal rather than a timer.
@hathach
hathach force-pushed the claude/wch-ch569-ch32h417-usb3 branch from 8078ca4 to 1d15cc9 Compare August 19, 2026 03:54
Hydra powered from the WCH-Link, so the cable move cannot reset it: SuperSpeed
-> USB2-only port enumerates nothing at all (0 enumerations on any 480 root hub
over ~30 s), and moving back to SuperSpeed returns 5000 with CDC and MSC bound.
Confirms FB_USB3_UP is terminal for the USB2-only case while showing a failed
excursion leaves the state recoverable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants