| Age | Commit message (Collapse) | Author |
|
Replace PR #3515's separate dcd_ch58x_usbfs.c / hcd_ch58x_usbfs.c with the
shared WCH USBFS device driver (combined per-endpoint control, like CH32V103),
adding two CH58x-specific behaviors guarded so CH32V103/V20x/V307 are unchanged:
- CH32_USBFS_EP_MANUAL_TOG: CH58x's hardware AUTO_TOG does not stay in sync, so
the ISR toggles DATA0/DATA1 manually and discards toggle-mismatched OUT
packets. Fixes multi-packet bulk-IN (e.g. MSC READ10) that otherwise hung.
- CH32_USBFS_EP4_SHARES_EP0: EP4 has no DMA register and overlays EP0's region
(EP0[0:63] + EP4 OUT[64:127] + EP4 IN[128:191]); add a 192-byte shared buffer
and buffer-pointer helpers (transparent for the other parts). Fixes
cdc_dual_ports (Port1 is on EP4).
Add the ch582m_evt board. Device only on USB0 (rhport 0): the shared
hcd_ch32_usbfs.c is CH32V20x-specific and cannot drive CH58x, so host / USB2
(rhport 1) is left commented out in the BSP for easy re-add.
Verified on ch582m_evt via local HIL: all device examples pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
|
|
|
CH32V103 uses the older USBFS IP: a single combined UEPn_CTRL register per
endpoint (IN response in bits [1:0], OUT response in [3:2], shared auto-toggle,
separate IN/OUT toggles) instead of the separate UEPn_TX_CTRL/UEPn_RX_CTRL
bytes of the newer IP (CH32V20x/V307). The shared driver was written for the
newer IP, so EP0 control transfers never worked on V103: the OUT response was
written to a reserved byte and the IN write clobbered the OUT bits.
- ch32_usbfs_reg.h: annotate the V103 register struct with byte offsets and add
a union exposing the combined UEPn_CTRL at the UEPn_TX_CTRL offset; define
CH32_USBFS_EP_CTRL_COMBINED and the combined-register bit positions.
- dcd_ch32_usbfs.c: abstract EP control access behind ep_tx/rx_ctrl_set() (full
write) and ep_tx/rx_set_response() (response-only RMW). The newer-IP path is
unchanged; the combined path read-modify-writes the single register and arms
the post-SETUP data stage at DATA1.
- bsp/ch32v10x: implement board_get_unique_id() (real chip UID) and drop the
CSR 0x800 writes that corrupted the QingKe V3 interrupt config and left all
interrupts disabled (the USB ISR never ran).
Verified on ch32v103r_r1_1v0: enumerates and passes HIL for cdc_msc, hid,
msc, midi, mtp, dfu, etc.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
|
Cleanup from a code-review pass, no behavior change:
- Replace the open-coded "last DATA packet" test (remain_wlength == 0 ||
len < CFG_TUD_ENDPOINT0_SIZE), duplicated in the edpt0_xfer DATA IN
arm, pipe0_process_xfer_state_isr, and the DATA OUT drain, with one
inline pipe0_data_stage_done() so IN and OUT can't drift.
- Correct the xact_len comment (only the IN path reports it; OUT reports
count0) and the dcd_edpt_stall comment (a deferred SETUP means the old
transfer ended on the wire, not that its status stage was "seen").
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The helper advances the whole EP0 control state machine on a
completion/confirmation IRQ — it dispatches on pipe0->state and also
fires the DATA_IN completion, not just the status stage — so
"process_status" undersold it. Matches the process_*_isr family.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
Mirror the IN-side short-packet fix on the OUT drain: end the data
stage (-> STATUS_IN) when wLength is received OR a short OUT packet
(count0 < CFG_TUD_ENDPOINT0_SIZE) signals the host's end-of-data, not
only when remain_wlength hits exactly 0. Also clamp the
remain_wlength subtraction so a host that overruns wLength can't
underflow it and strand the transfer.
Without this, a control-OUT whose host sends fewer bytes than wLength
left pipe0 in DATA_OUT; usbd then armed STATUS IN and tripped the
split's TU_ASSERT(!dir_in). Found by /code-review; conformant hosts
send exactly wLength so HIL was already green.
Verified: HIL pass on ek_tm4c123gxl and max32666fthr (13/13 each).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
pipe0_read_setup() copied the FIFO into a local union, then copied that
into the caller's struct. Read the two FIFO words straight into the
caller's uint32_t[2] (one copy) and cast to tusb_control_request_t* in
pipe0_start_setup(). pipe0.deferred_setup becomes uint32_t[2] so the
deferral path reads directly into it as well.
Verified: HIL pass on ek_tm4c123gxl and max32666fthr (13/13 each).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The deferral (RXRDY-combined) and csrl==0 tail paths in process_ep0_isr
ran the same per-state status-stage logic. Move all of it into one
pipe0_process_status_isr() helper covering every state including
DATA_IN, which picks STATUS_OUT vs STATUS_OUT_PENDING_IRQ from
deferred_setup_valid (a deferred SETUP means the status confirm was
coalesced with it). Both callers now just invoke the helper; the
deferral path saves the SETUP and sets deferred_setup_valid first.
Also drops the deferral path's TU_ASSERT(remain_wlength == 0), which
was wrong for a short last DATA-IN packet, and renames
pipe0_process_deferred_setup -> pipe0_try_deferred_setup (it no-ops
when nothing is deferred).
Verified: HIL pass on ek_tm4c123gxl and max32666fthr (13/13 each),
including the #3643 high-CPU-load IRQ-toggle coalescing stress.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
Rename process_ep0/process_epin/process_epout/process_bus_reset (all
invoked only from dcd_int_handler) to *_isr, making their ISR context
explicit at every call site. pipe0_process_deferred_setup is left
as-is since it also runs from task context (dcd_edpt_stall).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
A short IN control response (device sends fewer bytes than wLength —
e.g. the 18-byte device descriptor answering a 64-byte GET_DESCRIPTOR)
left remain_wlength != 0, so the DATA_IN -> STATUS_OUT transition never
fired and pipe0 stayed in DATA_IN through the status stage. usbd then
armed the status-OUT while state was still DATA_IN. Set DATAEND and
transition on the last packet: remain_wlength == 0, or a short packet
(incl. a terminating ZLP) which ends the data stage.
With state now tracking the stage, split edpt0_xfer's DATA handling
into separate DATA_IN / DATA_OUT cases dispatching on state (asserting
state == call direction) instead of the combined dir_in branch.
Verified: HIL pass on ek_tm4c123gxl and max32666fthr (13/13 each),
including the #3643 high-CPU-load IRQ-toggle coalescing stress.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
Pure cleanup, no behavior change:
- Extract the EP0 control-transfer state into a named pipe0_state_t
typedef instead of an anonymous nested struct, and access it through
a local pipe0_state_t* in the functions that touch it repeatedly.
- Group the pipe0 fields so the two bools sit together and the larger
tusb_control_request_t deferred_setup is last.
- Reword the deferral comments: "coalesced" -> "combined".
Note: separating the edpt0_xfer DATA_IN/DATA_OUT case (dispatch on
state instead of dir_in) was attempted and reverted — it breaks ADI
MUSB enumeration. usbd can arm the opposite-direction status while
pipe0 is still in a DATA state, and only dir-dispatch routes that
correctly; a comment on the combined case records this.
Verified: HIL pass on ek_tm4c123gxl and max32666fthr (13/13 each).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The actual-STALL path (no deferred SETUP) forced EP0 to IDLE but left
rxrdy_consumed set if the aborted transfer had parked RXRDY via NAK flow
control (e.g. a rejected OUT-data request in DATA_OUT). A subsequent
SETUP IRQ would then hit the parked-gate early return and be ignored,
relying on SentStall/SetupEnd to clear the flag first. Clear it here
so recovery never depends on that ordering.
Addresses Copilot review on #3699.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
dcd_edpt_stall(EP0 OUT) discarded the deferred SETUP and armed
SendStall. A deferred SETUP can only exist once the old transfer's
status stage was seen on the wire, so the request usbd is rejecting
(class callback failing at CONTROL_STAGE_DATA) already succeeded
host-side and the hardware already ACKed the next SETUP - the STALL
would land on that innocent request, which then fails host-side
without any tud callback ever seeing it. Skip the stall and replay
the deferred SETUP; the rejected transfer needs no wire-level stall
since it is already over.
Review follow-up for #3643 (dcd_musb.c l.860 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The deferral path drains the SETUP but leaves RxPktRdy set, and the
SETUP's IRQ latches after the ISR's clear-on-read intr_tx read - so a
second process_ep0 pass (same ISR, via the intr_tx re-read merge) is
guaranteed and misreads the leftovers: count0==0 fires a spurious
DATA OUT completion, the replay's RXRDYC write turns the second pass
into a phantom csrl==0 DATA IN completion, and a zero-length replay
re-enters the deferral case on a drained FIFO (count0 assert or
garbage saved as a SETUP). The registers cannot expose the staleness:
RxPktRdy and count0 read unchanged until ServicedRxPktRdy is written.
Track it in software: rxrdy_consumed means "RxPktRdy is set in hw but
its packet was already consumed". Set wherever a drained packet's
RXRDY is intentionally left set (OUT/zero-length flow-control parks,
every DATA OUT drain awaiting the next arm, the deferral path);
cleared at every RXRDYC write site (edpt0_xfer arms, dcd_set_address,
STALLED/SETEND recovery, bus reset). The RXRDY block returns early
while parked. Replayed IN requests skip the RXRDYC in
pipe0_start_setup and keep the packet parked until the
edpt0_xfer(DATA IN) arm acks it (before loading the shared FIFO), so
the stale pass sees RXRDY+parked instead of csrl==0. The normal IDLE
path is unchanged - master never re-entered these windows because the
single SETUP edge was always consumed by the pass that parked it; the
deferral is what introduced a pending second pass.
Review follow-up for #3643 (dcd_musb.c l.516 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
STATUS_OUT_PENDING conflated "edpt0_xfer(STATUS OUT) called, awaiting
confirm IRQ" with "confirm IRQ seen, awaiting edpt0_xfer". The
deferral path completed the status and replayed the saved SETUP from
the ISR in both flavors; in the IRQ-first one, usbd's still-
outstanding edpt0_xfer(STATUS OUT) for the old transfer (queued via
status_stage_xact) then landed in the replayed transfer's state and
corrupted it: NULL pipe0.buf armed plus RXRDYC, so the host's next
DATA OUT drained through a NULL pointer. usbd processes EP0
XFER_COMPLETE events unconditionally, so nothing downstream defuses
it.
Split the state into STATUS_OUT_PENDING_XFER / _IRQ. The deferral
completes and replays only in PENDING_XFER (old transfer already
retired); in PENDING_IRQ it only holds the SETUP and the usbd-driven
edpt0_xfer fires the completion and replays. The DATA_IN deferral now
synthesizes PENDING_IRQ (its remain==0 invariant asserted: a SETUP
before DataEnd raises SetupEnd instead), which also makes the old
deferred-promotion in the csrl==0 DATA_IN case unreachable - dropped.
Assert the drain buffer before the DATA OUT FIFO read as a cheap
backstop for this corruption class.
Review follow-up for #3643 (dcd_musb.c l.503 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
MUSBMHDRC 21.1.5 requires the EP0 service routine to check SentStall
and SetupEnd first; the early DATAEND return ran before both, and
SentStall is most likely to fire exactly while DataEnd may still read
back set (auto-STALL after DataEnd, 21.1.7), which would skip the
recovery. The guard also moves below the RXRDY block so a coalesced
DATAEND|RXRDY read cannot swallow a SETUP on cores where the
CPU-set-only DataEnd bit reads back 1; the comment documents the
vendor-dependent read-back.
Review follow-up for #3643 (dcd_musb.c l.445 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The goto jumped into the csrl==0 completion switch with RXRDY still
set, making its "When CSRL0 is zero" guard comment untrue on that
path. Handle each deferral state in a self-contained switch instead;
the csrl==0 switch is now only reached with csrl==0 and its comment
is truthful again. Behavior unchanged.
Review follow-up for #3643 (dcd_musb.c l.523 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The pre-existing comment explaining why the OUT branch does not ack
RxPktRdy was dropped when the SETUP handling moved into
pipe0_start_setup(). It is load-bearing: acking before edpt0_xfer()
arms the drain buffer would let the host send data with nowhere to
put it. Restore it with the databook-deviation rationale so the
branches don't get "unified" later.
Review follow-up for #3643 (dcd_musb.c l.116 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
The 8-byte EP0 SETUP drain (count0 assert + two FIFO word reads via a
union) was duplicated verbatim between the IDLE case and the deferral
case; a future fix applied to one copy but not the other would only
show up on the rare deferred-race path. Share one helper. count0 is
now read inside the only remaining user (DATA OUT drain).
Review follow-up for #3643 (dcd_musb.c l.507 finding).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
# Conflicts:
# test/hil/hil_test.py
|
|
# Conflicts:
# README.rst
|
|
split-IN NAK storm (#3677)
Fix stm32f723disco host HIL: UART RX starvation + DWC2 split bulk NAK/XactErr handling (#3677)
stm32f7 BSP — UART RX starvation
- The host console USART shared interrupt priority with the USB OTG ISR, so a long
OTG interrupt could starve RXNE and drop received bytes. Raise the USART RX IRQ
above OTG_FS/OTG_HS in both the bare-metal and FreeRTOS init paths, guarded by
#ifdef UART_ID so boards without a UART console keep the default OTG priority.
dwc2 host — split NAK/XactErr handling
- Slave mode: a persistently-NAKing split bulk/control IN poll re-armed the
start-split immediately, storming the ISR and starving task context. Throttle by
disabling the channel and re-arming on the resulting halt (no frame deferral).
- Buffer-DMA mode: a pure split bulk-OUT NAK was unhandled, leaving the channel
halted and stalling the transfer — the dominant cause of CDC echo truncation.
Handle it by rewinding the buffer pointers and retrying the start-split
(Programming Guide v4.20a 5.1.4.2).
- Buffer-DMA mode: a split bulk-OUT XactErr was retried immediately, exhausting
HCD_XFER_ERROR_MAX before the transient cleared. Throttle via channel_disable +
re-arm to give the hub TT a recovery gap, mirroring slave mode.
- All three are scoped to split transfers (hcsplt.split_en); non-split NAK/XactErr
keep the core-handled / immediate-retry behavior. The OUT XactErr throttle also
excludes periodic split, where channel_disable() is a no-op and would wedge the
channel. The nak_disabled flag is generalized to retry_disabled and honors
xfer->closing so an endpoint close during a throttled retry tears down cleanly.
Verified on stm32f723disco HIL (slave + CFG_TUH_DWC2_DMA_ENABLE): host/cdc_msc_hid,
msc_file_explorer, and device_info all pass on both variants; DMA CDC echo went
from ~15-25% raw failure to 10/10 clean.
|
|
Cast DOEPDMA0 through uintptr_t and use sizeof(tusb_control_request_t)
instead of the magic constant 8, matching project convention. Add a
reference to Programming Guide v4.20a 9.1.2.1 for the DOEPDMAn-8 rule.
Addresses Copilot review comment; no functional change.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
|
|
|
|
|
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
|
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
|
core v3.10a (STM32L476)
DWC2 core rev 3.10a pushes an extra EP0 RX_COMPLETE (RXFLVL PKTSTS 0x3)
that is not a real OUT data completion, in two cases flagged on DOEPINT:
- STPKTRX (Setup Packet Received): between SETUP_RX and SETUP_DONE
- STSPHSRX (Status Phase Received, control write): after the OUT data
stage when the host starts the IN status phase
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
To avoid STATUS IN completion of previous control transfer treated as next DATA IN when IRQ latency is high.
Signed-off-by: HiFiPhile <[email protected]>
|
|
|
|
- GRXSTSP register has internal FIFO, receiving events won't mix up (STATUS OUT & next SETUP)
- Improve efficiency, remove 2nd IRQ overhead
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Fix issue in the stall handling:
- dcd_edpt_stall() for an IN endpoint cleared EP_TX_LEN(0) instead of
EP_TX_LEN(ep_num), clobbering endpoint 0's transmit length register
when stalling any other IN endpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Refactor the driver to follow USBFS style for easier maintenance.
Replace the old packet/response helpers with explicit queue and update paths for IN and OUT transfers.
Introduce transfer validity tracking and per-endpoint data toggle state.
Reset toggle state on init, close-all, endpoint close, clear-stall, and bus reset.
Initialize endpoint controls consistently in NAK + TOG_0 mode.
Tighten EP0 setup/status handling and route transfer IRQ processing through the transfer-flag path.
Stop enabling ISO_ACT in INT_EN and clear unhandled interrupt flags explicitly.
Signed-off-by: HiFiPhile <[email protected]>
|
|
It would casue race condition, SETUP packet is always acked.
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
OUT is still buggy
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
|
|
|