Final integration for the CH32H417 port.
- .github/workflows/ci_set_matrix.py: add ch32h417 (riscv-gcc) so CI builds it.
- examples/device/net_lwip_webserver: enable LWIP_HIGH_THROUGHPUT for the H417
(SuperSpeed iperf).
- docs/reference/{boards,dependencies}.rst + hw/bsp/BoardPresets.json: regenerated
for the new board and dependency.
usbtest quirks intentionally left at 0 for the H417: its endpoint engine fixes the
two CH569 5 Gbps silicon limitations (RB_EP_TX/RX_HALT gives a repeatable STALL for
case 13; the reworked EP0/chain path is expected free of the wLength%4==1 ctrl_out
drop), so the H417 targets a full 30/30 at SuperSpeed rather than 27/27.
The HIL entry (test/hil/tinyusb.json) is deliberately deferred: it needs the chip's
ESIG unique id read from the board on first flash, which is pending hardware access.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWhrtHjbSVMKcY68vxBwBm
Second stage: the USBSS SuperSpeed device driver (SPEED=super now links). The
LINK layer is the same IP as the CH569 but the endpoint engine is the H417's
reworked chain-DMA design (hardware SEQ_AUTO/ERDY_AUTO, persistent RB_EP_*_HALT,
per-chain completion). The RCC/PHY/LINK bring-up and EP0 control sequences are
transcribed from the WCH EVT USBSS device demo; data endpoints arm one chain per
packet (burst 1) for correctness-first bring-up, matching the CH569 port's
initial path - raise CFG_TUD_WCH_USB30_MAX_BURST once the hardware validates.
Buffers may live anywhere in the shared SRAM (all DMA-reachable), so no bounce
machinery is needed.
- src/portable/wch/dcd_ch32h417_usb30.c (+ ch32h417_usb30_reg.h): full dcd with
a STATE_CHG-driven LTSSM handler, LMP PORT_CAP/PORT_CFG exchange, EP0 DPH/ERDY
flow with software sequence, chain-armed data endpoints, per-EP HALT stall, and
a TIM12-backstopped runtime USB2 fallback that hands rhport 0 to ch32h417_usb2_*
when SuperSpeed training fails (host has no SS port).
- examples/device/*/skip.txt: add mcu:CH32H417 to the FreeRTOS + audio/video
examples (no FreeRTOSConfig for this family yet), mirroring mcu:CH569.
Build-verified: all 20 device examples build for SPEED=super, and cdc_msc for
SPEED=high, clean under -Werror (riscv-none-elf-gcc 13.2). Hardware bring-up at
5 Gbps (usbtest battery, throughput) is pending rig availability.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QWhrtHjbSVMKcY68vxBwBm
The CH569 USBSS ctrl_out flakiness (cases 14/21, host -EPROTO) is a
silicon erratum, not a driver bug: EP0 OUT data stages whose wLength
% 4 == 1 are intermittently dropped at 5 Gbps. Proven by A/B against
WCH's own binary USB3 stack (SimulateCDC EVT demo patched to accept
the gadget-zero 0x5b/0x5c requests), which fails identically on the
same board — independent of host LPM (persists with U1/U2 disabled)
and sysclk (80 vs 120 MHz). Bulk OUT at the same lengths is clean.
Document the erratum in the dcd and drop the ineffective zeroed-BOS
workaround.
Rather than letting known-impossible cases flake the battery, the
firmware now advertises quirk flags in bcdDevice bits 4-7 (tier stays
in bits 0-3): 0x10 skips ctrl_out 14/21, 0x20 skips ep-halt 13 (the
previously proven single-STALL limitation). usbtest.py skips flagged
cases visibly at SuperSpeed only (explicit --tests still runs them)
and reports them in the summary/JSON.
Also fix the CH56x USB3 dcd leaving an endpoint halted across
reconfiguration: clear the stalled flag in dcd_edpt_open/close/
close_all (USB 2.0 9.4.5) so a SET_CONFIGURATION recovery of a
halted-then-reopened endpoint does not STALL forever.
Validated on hydrausb3_v1 at 5000M: tier-4 battery x3 = 27/27 passed
+ 3 skipped, full HIL device suite 12/12 green (MSC 160/134 MB/s).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
The register model comes from disassembling WCH's official USB30 device
blob (libCH56x_USB30_device_lib.a): USB30_ISO_Setendp reveals per-
endpoint isochronous mode bits in UEP_CFG (bit 16+ep for OUT, bit 24+ep
for IN); arming and completion then use the normal endpoint flow. The
usbtest example moves back to tier 4 at SuperSpeed with an iso pair in
the SS descriptor set - all four kernel usbtest iso cases (15/16/22/23)
pass on hardware at 5000M.
The endpoint-halt latch from the previous commit is kept (it fixes
usbtest case 29); case 13 is now documented as a silicon limitation
after exhaustive on-hardware experiments: a halted endpoint answers
exactly one probe with a STALL TP and no re-arm strategy (any NUMP,
ERDY, 30 us timer re-assertion, endpoint enable bounce) revives it -
no event fires per stall TP. The CH32H417's reworked endpoint engine
adds a persistent RB_EP_TX_HALT mode, evidently addressing exactly
this. Battery results: 30/30 at high speed, 28/30 at SuperSpeed
(case 13 = silicon halt limitation; case 14 ctrl_out is intermittent
- pre-existing EP0 OUT flakiness under investigation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
Running the Linux kernel usbtest/testusb battery against the CH569 for
the first time. Results: 30/30 at high speed (4 consecutive reflash
cycles), 25/26 at SuperSpeed.
DCD fixes found by the battery:
- dcd_ch56x_usbhs: clear-halt on an armed bulk-OUT pipe reset the toggle
but dropped the queued receive, NAKing forever (usbtest case 29 - the
same defect previously found on rp2040/fsdev/ch32_usbhs/rusb2)
- dcd_ch56x_usb30: endpoint halt is now latched; the endpoint event
handler used to clear the response field on every completion, wiping a
just-set STALL while the peer streamed, and clear-halt now re-arms a
still-armed transfer with the fresh packet sequence (cases 13/29)
usbtest example gains SuperSpeed support: tier 3 on SS builds, a
4-endpoint SS descriptor set with companions, EP0 512 config and
1024-byte receive buffers.
test/hil/usbtest.py gains SuperSpeed-aware case parameters: bulk sizes
and vary steps must be 1024-multiples at SS or the device's whole-packet
source overruns the host request (case 4 EOVERFLOW).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
Brings the usbtest device example (gadget-zero source/sink peer for the
kernel's usbtest.ko/testusb), the test/hil/usbtest.py battery runner and
the extended vendor class driver (interrupt/isochronous endpoints, alt
settings) into the CH569 SuperSpeed branch, enabling the battery to run
against both CH56x dcds.
Conflict resolution: test/hil/tinyusb.json keeps this branch's
hydrausb3_v1 fixture entry and adds the usbtest branch's
ch32v307v_r1_1v0 entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
Measurements with the 9*MSS window showed iperf pinned at 51 Mbit/s with
the in-flight data exactly tracking the advertised window: the 120 MHz
core's TCP receive path is the bottleneck, not the window or the USB
link. Recover the software-checksum cost: inbound IP/UDP/TCP checksum
VERIFICATION is skipped on the SuperSpeed build (USB already CRC32
protects every payload; outbound generation stays on). iperf over
SuperSpeed improves 51 -> 74.5 Mbit/s (device RX, lwiperf).
Also from bringing this up on a marginal SS lane that takes seconds to
train (normal lanes train in <100 ms):
- CFG_TUD_WCH_USB30_FALLBACK is now overridable from the build line;
=0 builds SuperSpeed-only, which retries training indefinitely
instead of settling for high speed
- the fallback keeps retrying twice as long (4.4 s) when far-end RX
terminations were seen: terminations mean an SS-capable partner
exists, while a USB2-only host (no terminations) still falls back
after ~2 s
Validated on HydraUSB3 behind a Renesas uPD720201 hub; 65/65 unit
tests, full example sweep, pre-commit clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
iperf on the SuperSpeed build is TCP-window-bound (51 Mbit/s with the
6*MSS window). Reclaim RAMX to widen it:
- dcd_ch56x_usbhs: new CFG_TUD_WCH_USBHS_EP_MAX limits usable endpoint
numbers and scales the static RAMX bounce buffers accordingly
(default unchanged at 8 endpoints)
- net_lwip_webserver: limit the (fallback) USB2 endpoints to 1-3,
freeing 4 KB of RAMX; grow the SuperSpeed build to PBUF_POOL_SIZE 9 /
TCP_WND 9*MSS (the high-speed build keeps 6/6). RAMX 94% of 32 KB.
High-speed fallback verified unchanged on HydraUSB3 (33 Mbit/s, full
enumeration); the SuperSpeed window measurement is pending the rig's SS
lane, which trains only intermittently (analog-marginal path - same
binaries trained and ran 51 Mbit/s earlier the same day).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
Two fixes found bringing up SuperSpeed on a Renesas uPD720201-based hub:
- dcd_ch56x_usb30: retry link training with a fresh detect cycle every
second fallback-timer tick (4 ticks total) before switching to the
USB2 fallback. Some hubs only complete Polling on a re-attempt; with
a single continuous attempt the link reached U0 rarely and the port
fell back to high speed almost every boot.
- net_lwip_webserver: CFG_TUD_ENDPOINT0_SIZE was left at plain 64, so
usbd chunked EP0 data at 64 bytes; on a 512-mps SuperSpeed control
endpoint the first chunk is a short packet, the host ends the data
stage early and the status stage deadlocks until timeout ("can't
read configurations, error -110"). Use the SuperSpeed-aware size and
clamp the device-qualifier bMaxPacketSize0 like the other examples.
Validated on HydraUSB3: NCM enumerates at 5000M reliably across warm
reboots; lwiperf over SuperSpeed measures 51 Mbit/s device RX (TCP
window bound; 33 Mbit/s on the high-speed fallback), MSC 169/136 MB/s.
Full example sweep, 65/65 unit tests, pre-commit clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
Class drivers: skip and account the SS endpoint companion descriptor in
every descriptor walk (hid, midi, midi2, ecm_rndis, printer, mtp, usbtmc),
via a shared usbd_skip_ss_ep_companion() helper in usbd_pvt.h (cdc/ncm
already handled; vendor/dfu naturally tolerant). usbtmc buffer grows to
1024 at SS to satisfy its packet-multiple assert. bth/audio/video are left
FS/HS-only (ISO-centric; ISO is unsupported on the CH569 dcd).
usbd.h: new SS template macros TUD_HID_SS_DESCRIPTOR(+INOUT),
TUD_MIDI_SS_DESCRIPTOR (+ DESC_EP_SS part), TUD_MIDI2_DESC_ALT1_EP_SS,
TUD_PRINTER_SS_DESCRIPTOR, TUD_MTP_SS_DESCRIPTOR,
TUD_USBTMC_BULK_SS_DESCRIPTORS/INT_SS_DESCRIPTOR, with _SS_DESC_LEN
counterparts (bulk endpoints hardcode 1024, companion after every EP).
Examples: 17 bulk/interrupt examples gain the full SS descriptor set
(EP0 512/64 clamp, SS device descriptor bcdUSB 3.20 mps0=2^9, SS config
with companions, BOS caps, speed-switched callbacks): cdc_dual_ports,
cdc_msc_freertos, dfu, dfu_runtime, dynamic_configuration,
hid_boot_interface, hid_composite(+freertos), hid_generic_inout,
hid_multiple_interface, midi_test(+freertos), midi2_device, msc_dual_lun,
mtp, printer_to_cdc, usbtmc, webusb_serial. msc_dual_lun is un-skipped for
CH569 using read-only flash disks (16KB RAM can't hold two 8KB RAM disks,
same as ch32v10x/v20x boards).
Validation: full example sweep green for hydrausb3_v1 (SPEED=super, 19
device ELFs) and raspberry_pi_pico; wTotalLength == sizeof() verified for
every SS configuration array in the built ELFs; ceedling 65/65;
pre-commit clean; code-size delta +0.0% on rp2040 (SS code folds out on
non-SS builds).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019MRGjBT2NBkCoWwyDT4LaE
- TUD_CDC_NCM_SS_DESCRIPTOR template (bulk 1024 + endpoint companions)
and companion handling in netd_open (notification endpoint skip,
bulk pair drv_len)
- net_lwip_webserver: SS device/config/BOS descriptors for the NCM
build, CH569 added to the LWIP_HIGH_THROUGHPUT tier (builds lwiperf).
The 16 KB RAMS cannot hold lwIP: heap and pools go to the 32 KB RAMX
via LWIP_DECLARE_MEMORY_ALIGNED (arch/cc.h), with a 6-pbuf pool /
6*MSS window and a 3 KB dcd bounce pool to fit (RAMX 92%)
- CH569 dcd fixes found while bringing this up: TMR0 counts only 26
bits, so the previous 1 s fallback CNT_END (120e6 > 2^26) silently
never expired - use 0.55 s per phase; make link busy-waits in
usb30_hw_init best-effort so a stuck-BUSY teardown state cannot
abort re-initialization half way
Builds clean (full ch56x example sweep); NOT yet hardware-validated:
the rig's WCH-LinkE stopped completing flash writes mid-session (reads
and erases fine), leaving the board without firmware - iperf numbers
pending physical probe/board recovery.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Default CFG_TUD_WCH_USB30_MAX_BURST to 4 (tusb_mcu.h): matches the
link layer's header-packet buffer count (NUM_HP_BUF). Burst 8 fails
to configure; the TeenyUSB-reported burst corruption quirk did not
reproduce with per-burst DMA re-arm (50x MSC write/read-back and
256KB CDC echo integrity all clean at burst 4)
- Bounce slots stay one max packet regardless of burst (bounced
transfers cap their burst to the slot; zero-copy uses the full burst)
so the RAMX pool no longer scales with burst size
- cdc_msc/cdc_msc_throughput SS descriptors advertise
bMaxBurst = CFG_TUD_WCH_USB30_MAX_BURST-1 via the
CFG_EXAMPLE_SS_BULK_MAXBURST knob (0 for non-WCH SuperSpeed ports)
Measured on HydraUSB3 v1 at 5 Gbps (HIL cdc_msc_throughput): MSC read
147 MBps write 127 MBps - about 2x the single-packet figures (raw dd:
up to 163 MB/s read at 1M blocks).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SS device/config/BOS descriptors with endpoint companions, speed-aware
EP0/CDC/MSC buffer sizes (same pattern as cdc_msc)
- Lengthen the CH569 SuperSpeed-training fallback timeout to 1 s per
phase: training from a host port settled in another connection state
can exceed the previous 0.5 s window
Measured on HydraUSB3 v1 at 5 Gbps (NUMP=1): MSC sequential read
70-79 MB/s, write 55 MB/s via dd direct IO - about 3.5x the USB2
high-speed fallback figures on the same setup. Burst support
(CFG_TUD_WCH_USB30_MAX_BURST > 1) remains the main headroom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New dcd_ch56x_usb30.c + ch56x_usb30_reg.h (both MIT, written fresh):
- Register model from the hydrausb3 project's reverse engineering
(wch-ch56x-lib, Apache-2.0) and TeenyUSB's CH56x port (MIT), with
LINK-layer bit names per the officially documented CH32H417 (same IP,
CH32H417RM chapter 27); TU_VERIFY_STATIC offset asserts guard the
vendor header layout
- Software-assisted LTSSM in the LINK interrupt: polling/TXEQ/ready
sequencing, LMP PORT_CAPABILITY + PORT_CONFIGURATION exchange, warm
and hot reset handling. Link-up (ready after TXEQ) is reported as
dcd_event_bus_reset with TUSB_SPEED_SUPER
- EP0 with fixed 512-byte mps: SETUP via RX flag bit30, unified status
stage via bit29 (SET_ADDRESS applied there), manual IN packet sequence
- Data endpoints with per-burst DMA re-arm, partial burst-drain
accounting, ERDY flow control and NUMP=1 default (multi-packet burst
silicon quirk); RAMX bounce slots from a small pool for transfer
buffers outside RAMX; isochronous not supported
- Board vector forwarders unified into one handler + symbol aliases:
gcc identical-code-folding otherwise rewrites one interrupt handler
as a call into another whose mret skips the caller's epilogue,
leaking a stack frame per interrupt (found the hard way)
- hydrausb3_v1 default SPEED flipped to super; skip net_lwip_webserver
(16 KB RAMS, same as other small-RAM parts)
Verified on HydraUSB3 v1 hardware (ci rig, USB3 host port):
- cdc_msc enumerates at 5000M: bcdUSB 3.20, EP0 mps 2^9, endpoint
companion descriptors, BOS with USB2-ext + SuperSpeed capability
- CDC echo and MSC FAT12 reads functional at SuperSpeed; 30x sustained
read stress and 5x replug/reset cycles all re-enumerate at 5000M
- SPEED=high regression: still enumerates at 480M
- Full example sweep builds for both controllers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- OPT_MCU_CH569 (2250) + tusb_mcu.h capability block: USB3 SuperSpeed
(USBSS) or USB2 HighSpeed (USBHS) selected at compile time via
CFG_TUD_WCH_USBIP_USB30/USBHS, both on rhport 0
- New dcd_ch56x_usbhs.c: CH56x USB2 HS device driver ported from
dcd_ch32_usbhs.c (same bit encoding, CH56x-specific register layout:
R8_UEPn_MOD enables, different DMA/MAX_LEN/control offsets, 8 EPs).
Internals exposed as ch56x_usb2_* (dcd_ch56x.h) for the upcoming USB3
dcd's runtime fallback. USB DMA only reaches RAMX: buffers in the
.dmadata section, non-RAMX transfer buffers bounce per-endpoint
- hw/bsp/ch56x family (SDK dep hydrausb3/wch-ch56x-bsp, Apache-2.0, no
binary blobs) + hydrausb3_v1 board: 120 MHz clock (required for USB3),
UART1 PA8/PA7 logging, LED PB22, button PB23, linker script with
16-byte aligned NOLOAD .dmadata section in RAMX
- SPEED=high|super build option (default high until the USB3 dcd lands)
- CI: add ch56x to riscv-gcc matrix; skip FreeRTOS/audio/video examples
(as CH583) and msc_dual_lun (16 KB RAMS, as SAMD11/MKL25)
Verified: cdc_msc builds via CMake and Make; full example sweep green;
.dmadata lands at 0x20020000 (RAMX) with 16-byte alignment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Groundwork for TinyUSB's first SuperSpeed port (WCH CH569):
- TUSB_SPEED_SUPER, OPT_MODE_SUPER_SPEED, TUD_OPT_SUPER_SPEED and
TUP_RHPORT_SUPERSPEED capability plumbing
- Allow CFG_TUD_ENDPOINT0_SIZE 512 on SS builds; EP0 control transfers
chunk at the runtime link speed (512 at SS, 64 on USB2 fallback)
- Handle SET_SEL, SET_ISOCH_DELAY and U1/U2_ENABLE feature requests
- Tolerate SS endpoint companion descriptors in usbd_open_edpt_pair,
cdcd_open and mscd_open interface parsing
- SS descriptor types/structs and template macros: endpoint companion,
BOS USB2-extension + SuperSpeed capability, SS config and
CDC/MSC/vendor SS variants (bulk fixed at 1024)
- cdc_msc example: SS device/config/BOS descriptors, speed-aware sizes
- Unit tests: SS endpoint validation, companion-tolerant endpoint pair
open, SET_SEL, SET_ISOCH_DELAY
All SS paths compile out when disabled: verified 0-byte code-size delta
on stm32f407disco (MinSizeRel) vs master; 65/65 unit tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot review:
- tud_vendor_int_tx_cb/iso_tx_cb submitted sizeof(chunk) (the HS-capacity buffer)
instead of the negotiated-speed mps, diverging from usbtest_pump() on an
HS-capable build enumerated at full speed. Factor the length into shared
usbtest_int_len()/usbtest_iso_len() helpers used by the pump and both callbacks.
- Reword the usb_descriptors.h comment that still called the plain mps macro
'live operating speed' — it is the compile-time capability maximum.
Claude review (test/hil/tinyusb-sudoer): drop the two grants that bypassed the
file's root-owned-helper boundary and gave uid 1000 passwordless root:
- /sbin/debugfs (unused; permits '!/bin/sh' shell escape + raw root-disk write).
- /home/*/actions-runner/svc.sh (user-writable script). Replace with a root-owned,
argument-restricted 'systemctl stop/start actions.runner.*' grant. Also tighten
the dmesg/uhubctl runas from (ALL) to (root).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Per-case table: direction convention, what the host verifies, and which
device/DCD path each case stresses (EP0 storms, sg queue pressure, unlink,
halt/toggle semantics, iso frame deadlines, throughput).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
- dcd(nrf5x): dcd_edpt_iso_activate drops a stale armed transfer (started/
data_received/iso_in_transfer_ready) — there is no dcd close on this port, so
a transfer armed before SET_INTERFACE would trip TU_ASSERT(!xfer->started) on
the class's next arm after usbd reset the endpoint's claim state.
- class(vendor): tud_vendor_n_mounted() counts the interrupt endpoints, so an
interrupt-only vendor interface (bulk absent) reports mounted.
- example(usbtest): int/iso write lengths follow the negotiated speed
(tud_speed_get), not the compile-time capability — a high-speed build
enumerated at full speed serves the FS descriptor (mps 64/128) and must not
submit HS-sized packets.
- test/hil: test_example() return annotation matches the (err, status, metric)
3-tuple it returns.
Verified: feather_nrf52840_express and raspberry_pi_pico 30/30 through the HIL
battery; pico/feather/ch32v307-usbhs builds clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
- dcd(rp2040): rewind IN staging before the clear-halt re-issue. Staging advances
user_buf/remaining_len when packets are copied into DPRAM, before the host has
consumed them; aborting and re-issuing from the advanced pointer silently
skipped the staged-but-unsent bytes.
- dcd(ip3511): dcd_edpt_iso_activate aborts a transfer still armed from the
previous altsetting (clear Active on both buffer slots) so the hardware can't
keep servicing the old descriptor against the class's fresh queueing.
- dcd(rusb2): reuse an endpoint's existing pipe in dcd_edpt_open. usbd_edpt_close
is a no-op on ISO_ALLOC ports, so a class's close/open across SET_INTERFACE
would allocate a second pipe with the same EPNUM and leak pipes.
- class(vendor): the buffered-mode free-slot check also tests the interrupt
endpoints, so an interrupt-only vendor interface isn't clobbered as "free".
- bsp(ch32v30x): board_get_unique_id respects max_len and copies bytes
(alignment-safe) instead of writing 12 bytes through a uint32_t cast.
- test/hil/usbtest.py: treat same-serial multi-matches as ambiguous and retry
until the dual-port stale node drops (nanoch32v203/ch32v307 variants); reprobe
a binding that predates the dynamic-id re-registration so a stale capability
profile can't survive; skip the remove_id/unbind cleanup after an unrecovered
kernel-side hang (it would join the D-state convoy and deadlock the bus).
- usbtest skip.txt: add the families whose DCD refuses dcd_edpt_iso_alloc
(CXD56, FT90X, LPC175X_6X, LPC40XX, NUC100/120/505, PIC32MZ, SAMG, SAMX7X,
VALENTYUSB_EPTRI) — tier 4 cannot enumerate there.
- test/hil/tinyusb-sudoer: note that '#1000' is a sudoers UID specifier, not a
comment (the review misread the grants as commented out).
Refuted in review threads: nrf5x ISOSPLIT keeps reserve-max per-configuration
allocation semantics (iso_alloc is per-config, not per-alt); the LPC EP-number
claim (default descriptor uses EP1-3; lpcxpresso11u37 passes 30/30 on HW); the
vendor alt-0 "abort" concern (host cannot address endpoints outside the active
altsetting; usbd_edpt_iso_activate resets state on re-entry).
Verified on hardware: raspberry_pi_pico, lpcxpresso11u37, ra4m1_ek all 30/30
through the HIL battery after the changes; builds clean incl. buffered-mode
vendor (webusb_serial).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Extract the endpoint pump into usbtest_pump(); the FreeRTOS task loops it,
the bare-metal path calls it once per main-loop tick instead of a
while(1){...return;} whose body can only execute once.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Four PR-CI failures, each with a distinct root cause:
- class/vendor: mark the shared non-buffered transfer helpers TU_ATTR_UNUSED.
In buffered mode with the int/iso endpoint gates off none is referenced, and
clang (-Wunused-function -Werror) and IAR (Error[Pe177]) reject an unused
static — gcc does not, which is why only the clang/IAR matrix failed.
- bsp/ch32v20x: keep dcd_int_handler alive under LTO. The `call` sits inside
naked asm where LTO cannot see it, so -flto make builds internalized the
symbol and every ch32v20x device example failed to link (cmake has no LTO,
which masked it). A TU_ATTR_USED function-pointer reference pins it.
- usbtest: LPC11/13 (ip3511 FS) keep endpoint buffers in a dedicated 2 KB USB
RAM; the 2048 B bulk epbuf overflowed it (RamUsb2 150%) in make builds. Use
512 B (= 8 FS packets) on those parts; lpcxpresso11u37 still passes 30/30.
- test/hil: skip device/usbtest on lpcxpresso43s67 (hfp rig) — its ip3511 HS
port wedges from the first control case (1/30) and needs on-rig debugging.
Verified: make builds link for nanoch32v203 (dfu_runtime + usbtest),
lpcxpresso11u37 and lpcxpresso1347 (RamUsb2 75%); buffered-mode webusb_serial
builds; 61/61 unit tests; lpcxpresso11u37 usbtest 30/30 on hardware with the
smaller epbuf.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
The CH32V307 exposes two device controllers: the full-speed USBFS (dcd_ch32_usbfs,
selected with -DSPEED=full) and the high-speed USBHS (dcd_ch32_usbhs, the default).
Both now pass the Linux testusb tier-4 battery 30/30 (3 reflash cycles each) on
ch32v307v_r1_1v0.
- dcd(ch32_usbhs): fix clear-stall on an armed OUT endpoint. It set the pipe to
NAK unconditionally, so a receive the class still considers submitted NAKed
forever after CLEAR_FEATURE(HALT) and usbtest toggle test 29 timed out. Re-queue
the pending receive (ACK/NYET) instead. This is the first usbtest coverage of the
ch32_usbhs driver.
- bsp(ch32v30x): implement board_get_unique_id from the ESIG UID (0x1FFFF7E8);
the port previously fell back to the placeholder serial.
- usbtest: the CH32V307 usbfs port caps iso at 64 B like the other WCH USBFS parts,
so add it to the FS iso-64 descriptor branch (its usbhs port uses the HS sizes).
- test/hil: register ch32v307v_r1_1v0 (usbhs + usbfs variants, WCH-Link BC5DA47360D0)
and move nrf54lm20dk to boards-skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Cross-checking the branch against the MCU datasheets surfaced a regression and
several latent defects. Fixes (finding # from the review):
#1 dcd_ch32_usbfs: restore EP3's enlarged iso buffer. CH32V20x/V30x/F20x USBFS
give endpoint 3 a 1023-byte iso packet (CH32FV2x_V3xRM ch23; 16-bit
R16_UEP3_T_LEN), unlike every other 64 B endpoint. Commit c05e9313e removed
that buffer and capped iso at 64 B, breaking >64 B FS iso (UVC/UAC) on EP3.
Re-add it via CFG_TUD_WCH_USBFS_EP3_BUFSIZE, defaulted to 1023 on those parts
(CH32_USBFS_EP3_1023_BUFSIZE in ch32_usbfs_reg.h) and 64 elsewhere.
#3 dcd_ch32_usbfs: make data.isochronous[] per-direction and clear it on
endpoint (re)open, so a reused ep number can't inherit a stale iso response.
#7 dcd_ch32_usbfs: exempt iso from the PID_OUT data-toggle handling too (was
only done for PID_IN); iso is DATA0-only in both directions.
#13 dcd_ch32_usbfs: bounds-assert ep < EP_MAX in dcd_edpt_iso_alloc.
#2/#6 usbtest descriptors: TUD_OPT_HIGH_SPEED is a compile-time capability, not
the live speed, so the full-speed config (and OTHER_SPEED) advertised int/iso
mps 512 on HS boards — illegal at FS / over the 1023 B/frame iso budget. Split
into FS-legal / HS descriptor sizes; the plain macro tracks operating speed.
#10 vendor: non-alt free-slot detection also checks the int endpoints (a vendor
interface may now be interrupt-only, no bulk ep).
#14 vendor: replace the order-coupled ITF_MEM_RESET_SIZE ladder (2 unreachable
branches) with offsetof(vendord_interface_t, tx_stream).
#5 rp2040: clear_stall re-issue preserves the already-transferred count so a
mid-transfer clear-halt reports the full length, not just the remainder.
#8 rp2040: force single-buffering for iso (only BULK gets a double DPRAM buffer),
so a multi-packet iso xfer can't spill buffer 1 into the next ep's DPRAM.
Verified: CH32V203 usbfs and CH583 still pass usbtest tier-4 30/30; all board
families build; pre-commit (format/codespell/unit) clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
The CH582/CH583 USBFS DCD refused isochronous outright, but the datasheet
(CH582-CH583 DS1, USB controller chapter) lists synchronous/real-time
(isochronous) alongside control/bulk/interrupt, with 64 B packets. Accept iso
like the other WCH USBFS parts (iso mps 64, capped to the 64 B endpoint buffer;
drop the CH583-only refusals in dcd_edpt_iso_alloc/activate).
That alone left iso IN dropping a deterministic ~1/8 of packets. Root cause:
CH58x drives the data toggle manually in the ISR (its hardware AUTO_TOG is
unreliable) and flipped the TX toggle after every IN packet — including
isochronous, which is DATA0-only and must never toggle. Skip the manual toggle
flip for iso endpoints. This path is CH58x-only (CH32_USBFS_EP_MANUAL_TOG),
so CH32V103/V20x/V307 (hardware AUTO_TOG) are unaffected.
Verified on ch582m_evt: Linux testusb tier-4 (30 cases) = 30/30 across 4 reflash
cycles; CH32V103 and the CH32V203 usbfs port still pass. Drop CH583 from the
usbtest skip list — all WCH USBFS parts now run the full battery.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
CH32V103 (ch32v103r) uses the same usbfs DCD as the CH32V203 usbfs port and
passes tier-4 30/30 with the fixes already in place (iso mps 64 + the clear-halt
re-arm) — drop it from the skip list. Verified 30/30 across 6 reflash cycles.
dcd_ch32_usbfs.c gave EP3 an enlarged 1023-byte IN buffer for full-speed iso,
but the CH32V20x/V103 USB FS controller caps every endpoint (bulk/interrupt/iso)
at a 64-byte packet (per the RM "USB Full-speed Host/Device Controller"), so no
endpoint can ever send more than 64 B and the oversized EP3 buffer was pure
waste. Treat EP3 like any other endpoint (64 B OUT + 64 B IN) and drop the
special buffer, saving ~1 KB of RAM. dcd_edpt_iso_alloc now rejects an iso mps
> 64 rather than running off the end of the buffer. usbtest (iso mps 64) still
passes 30/30 on CH32V103 and the CH32V203 usbfs port.
CH583 has no isochronous support in its USBFS IP, so it cannot run tier-4 (the
iso endpoints fail to open and SET_CONFIG times out); it stays skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Neither of the CH32V203's two USB device IPs could run the usbtest tier-4 battery.
usbfs (port 1):
- 128 B iso doesn't fit its fixed 64 B/ep buffers -> drop iso mps to 64 on CH32
(usb_descriptors.h).
- dcd_ch32_usbfs.c: clear-halt on an armed OUT endpoint forced it to NAK, so a
submitted read NAK'd forever and the host timed out. Re-assert ACK when a
receive is still armed (usbtest toggle test 29).
fsdev (port 0, the shared stm32_fsdev driver):
- The descriptor hard-coded the interrupt mps, so USBTEST_INT_EP_MPS never took
effect; all six tier-4 endpoints then overflowed the 512 B PMA and SET_CONFIG
timed out. Wire the interrupt mps to the macro and use 16 on the fsdev port.
- The USBD raises three IRQ lines (LP/HP/WakeUp) into one non-reentrant handler
and they nest (HP preempts LP) with QingKe HWSTK enabled. The mainline
toolchain's plain __attribute__((interrupt)) emits a software prologue that
fights the hardware context stack and corrupts the return on nesting -> wild
jump / illegal-instruction crash. Emit naked handlers that rely on HWSTK for
context save/restore -- the equivalent of WCH's "WCH-Interrupt-fast", which
mainline riscv-none-elf-gcc ignores -- so nesting is safe.
- Single-buffered iso IN can't stay fed under load; double-buffer it
(CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP in the example tusb_config.h). iso mps
32 x2 = 64 B/ep keeps the same PMA budget. Left single for other fsdev parts
(audio's larger iso would overflow their PMA too).
Verified on nanoch32v203 (CH32V203D6): Linux testusb tier-4 (30 cases across
bulk/control/interrupt/isochronous) = 30/30 on both the fsdev and usbfs variant,
each across 5 reflash cycles. Full CH32V20X example set builds; cdc_msc still
enumerates CDC+MSC on the fsdev port (naked-handler regression check). Drop
CH32V20X from the usbtest skip list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Mirror the video_capture #if pattern: dual-mode tasks that vTaskDelay under
FreeRTOS and return under OS_NONE, a usb_device_task + freertos_init
(xTaskCreate blinky/usbd/app), and an ESP_PLATFORM app_main. Add the ESP-IDF
src/CMakeLists.txt and CFG_TUSB_OS_INC_PATH; replace the family:espressif skip
with per-MCU skips for parts that can't fit the tier-4 descriptor.
Verified: esp32s3 (FS) and esp32p4 (HS) both 30/30 on the usbtest battery.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Two pre-existing bugs (present on master, unrelated to the usbtest work on this
branch) flagged by the review gate; fixed in isolation so this commit can be
dropped or cherry-picked separately:
- examples/device/video_capture{,_2ch}/src/main.c: xTaskCreate() used the
undefined macro VIDEO_STACK_SZIE (typo for VIDEO_STACK_SIZE), breaking the
non-static-allocation FreeRTOS build.
- lib/rt-thread/port/msc_device_port.c: tud_msc_inquiry_cb() did
memcpy(dst, src, strlen(src)) into the fixed 8/16/4-byte SCSI inquiry fields,
overflowing them if PKG_TINYUSB_DEVICE_MSC_VID/PID/REV exceed those sizes.
Bound each copy to the destination field size.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
nRF5x fixes the isochronous endpoint number to 8 (EP_ISO_NUM) in hardware for
both ISOOUT and ISOIN. The default tier-4 descriptor used EP3 for iso, so
dcd_edpt_open() rejected it and SET_CONFIGURATION failed (can't set config,
-EPIPE) - the device enumerated but never configured, so usbtest couldn't bind.
Add an OPT_MCU_NRF5X case to the per-MCU endpoint-number block placing iso on
EP8. usbtest on feather_nrf52840_express then configures and passes 29/30
(all bulk/int/iso/ctrl_out/halt/toggle); test 10 (queued control) hangs - a
separate nRF EP0 issue tracked for follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
- example: interrupt/iso endpoint buffers track the per-speed max packet
size instead of a fixed 512, saving ~1.6 KB of USB DMA RAM on FS builds.
- test/hil/usbtest.py: re-resolve the device node after a mid-battery
re-enumeration (stale node was misreported as NOTRUN); reject an
out-of-range tier (a tier-0 device would run an empty battery and read
green); validate --tests case numbers; run cleanup in a try/finally so a
crash can't leave the dynamic id registered to hijack the next device;
guard set_pattern and the pci-reset placeholder; report a sudo-password
failure on the testusb fallback instead of a misleading NOTRUN.
- tinyusb-sudoer: allow the foreign-driver unbind path and the testusb
fallback used by the runner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Gadget-Zero parity: alt 0 carries no endpoints, alt 1 the full set (bulk
+ interrupt + iso IN/OUT, iso FS 128B / HS 512B). The host usbtest driver
skips pipe-less altsettings and selects alt 1 itself, so no usbtest
module parameters are needed.
The complete 30-case battery (all four tiers) passes on stm32f407disco,
stm32f723disco, stm32h743nucleo and stm32l476disco, including the ch9
case which now exercises 256 consecutive SET_INTERFACE cycles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
The host usbtest driver collects interrupt pipes from the same altsetting
as the bulk pair, so the interface grows to 4 endpoints (hand-rolled
descriptor: bulk IN/OUT + interrupt IN/OUT, FS 64B interval 1 / HS 512B
interval 4) using the vendor class with CFG_TUD_VENDOR_EP_INT_OUT/IN. The
interrupt pumps mirror the bulk ones: poll + re-arm from callbacks,
self-healing across the halt tests.
Full tier-3 battery (26 cases, now incl. 25/26 interrupt write/read)
passes on stm32f407disco, stm32f723disco, stm32h743nucleo and
stm32l476disco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Gadget-Zero ctrl_out protocol: 0x5b stores the host's wLength bytes in a
1 KiB buffer (multi-packet EP0 data stage), 0x5c returns them for
comparison. bcdDevice now advertises tier 2 so the runner adds usbtest
cases 14/21. No descriptor change needed: ctrl_out is unlocked by the
Gadget Zero capability profile of the dynamic-id binding, not by
endpoints (interrupt/iso endpoints come with tiers 3/4).
Full tier-2 battery (24 cases) passes on stm32f407disco, stm32f723disco,
stm32h743nucleo and stm32l476disco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
Vendor interface with bulk IN source / bulk OUT sink implementing the
Gadget-Zero style protocol expected by the kernel usbtest driver.
Non-buffered vendor mode keeps every transfer an exact packet multiple so
the data-integrity cases never see an unexpected short packet, and polling
pumps self-heal after the endpoint-halt cases. Tier (bulk-only for now) is
advertised in bcdDevice for the host-side runner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
- Order CFG_TUH_DEVICE_MAX before CFG_TUH_HID in the host config snippets
(cdc_msc_hid, hid_controller) so the documented snippet has no forward
macro reference when copied into tusb_config.h.
- Fix stale `examples.rst` references: the generator now writes per-group
`docs/examples/<group>/index.rst` pages, so update the conf.py comment and
the build-doc SKILL.md accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documentation tooling:
- Add the `build-doc` skill and `tools/build_doc.py` wrapper for local
Sphinx builds (clean / -W / open).
- Enable Markdown (MyST) in conf.py and auto-collect
examples/{device,host,dual}/*/README.md into a 3-level Examples nav
(Examples > Device/Host/Dual > example), noting each page's source
location and normalizing headings to a single H1.
- Remove the stale `.claude/commands/build-doc.md`; point the AGENTS.md
Documentation section at the skill.
Example docs:
- Add a README.md for every device/host/dual example: what it does, USB
interface table, notable tusb_config.h settings, generic CMake + Make
build steps, and how to try it.
- Fold each *_freertos variant into its base README, noting the FreeRTOS
source path and any RTOS-specific behavior.
Generated docs/examples/ output is git-ignored. Builds clean with
`sphinx-build -W`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The BSP family and MCU option were named "ch58x"/"CH58X", but the supported part is
the CH583/CH582 (and the SDK repo is openwch/ch583); CH585 is a separate MCU family,
so the CH58x umbrella was misleading. Rename to the specific family:
- hw/bsp/ch58x -> hw/bsp/ch583 (dir), and the BSP-local files ch58x_it.* ->
ch583_it.*, system_ch58x.* -> system_ch583.* (include guards/refs updated). The
vendor SDK files (CH58x_common.h, CH58x_*.c in hw/mcu/wch/ch583) keep their names.
- OPT_MCU_CH58X -> OPT_MCU_CH583 in tusb_option.h, tusb_mcu.h, and the shared WCH
USBFS driver (ch32_usbfs_reg.h, dcd_ch32_usbfs.c). OPT_MCU_CH582 is kept as an
alias (same value), so either name selects the same code.
- FAMILY_MCUS CH58X -> CH583, CFG_TUSB_MCU=OPT_MCU_CH583, mcu:CH58X -> mcu:CH583 in
the example skip lists, the CI build matrix (ci_set_matrix.py), the get_deps family
tag, and docs/reference/boards.rst.
Board names (ch582m_evt, yd-ch582m) are unchanged. Verified: make + cmake build for
ch582m_evt, and ci.lan HIL (all device examples pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CH58x has no isochronous support (dcd_edpt_iso_alloc() returns false), but the
audio class ignores that result and the endpoints fall back to capped 64-byte
non-iso transfers, producing non-functional audio firmware. video_capture and the
FreeRTOS audio examples already carry mcu:CH58X; add it to the remaining iso
examples (audio_test, audio_4_channel_mic, audio_test_multi_rate, cdc_uac2,
uac2_headset, uac2_speaker_fb) so they are not built for CH58x.
Found by Codex review. Verified via build_utils.skip_example() that all six now
skip on ch582m_evt while control examples (e.g. cdc_msc) still build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>