| Age | Commit message (Collapse) | Author |
|
The CH32 workaround only restored EP0 to CONTROL in edpt_xfer(). When
the stack rejects a control write with data (handle_ctr_setup having
already switched EP0 to BULK) it stalls EP0 via dcd_edpt_stall() without
ever calling dcd_edpt_xfer(), leaving EP0 typed BULK+STALL. As SETUP
recognition is tied to CONTROL-typed endpoints, the host's recovery
SETUP (which should auto-clear the stall) would be ignored, wedging EP0
until a bus reset.
Restore CONTROL for EP0 in the same exclusive write that sets the STALL
status. DFU download-in-wrong-state is a concrete trigger.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
edpt_xfer() restored EP0 to CONTROL at the top of the function, before
the OUT stage was armed (rx bufsize + STAT_RX=VALID written further
down, with interrupts enabled in between). That re-enabled the CH32
blind OUT ACK while STAT_RX was still NAK and the buffer size stale, so
a host-retried DATA OUT / status ZLP could be ACKed into the wrong
buffer in the gap.
Fold the CONTROL restore into the single exclusive write that programs
STAT_RX=VALID for the OUT direction, after the buffer size is set, so
type and arming go live together. The IN direction keeps the (now
atomic) early restore, where no pending OUT exists to be blind-ACKed.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
Parse the setup packet via tusb_control_request_t instead of hand-rolled byte
indexing; drop a stray blank line and fix indentation. No functional change.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
ep0_set_type() read the EP0 register outside any critical section and
only ep_write() masked the USB IRQ around the store. A USB interrupt
landing between the read and the write (e.g. a new SETUP whose handler
installs the BULK gate) was silently undone when the task resumed and
wrote back its pre-interrupt snapshot with the type forced to CONTROL,
re-exposing the unsolicited EP0 OUT ACK the workaround blocks.
Bracket the whole read-modify-write with fsdev_int_disable/enable when
called with need_exclusive (task context). ISR-context callers pass
false and are unaffected (the ISR cannot preempt itself).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
|
|
dcd/musb: defer EP0 SETUP during DATA_IN/STATUS race
|
|
* device: clamp EP0 OUT data copy to the control transfer buffer
usbd_control_xfer_cb() copied xferred_bytes from the EP0 bounce buffer
into the requester's buffer with no bound. A non-compliant host that
sends an OUT data packet larger than the control transfer's data_len
(= min(len, wLength), the buffer capacity) would overflow that buffer
and over-count total_xferred. Clamp xferred_bytes to the remaining
buffer space before the memcpy and accounting.
|
|
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]>
|
|
Add control transfer fifo for host stack
|
|
|
|
- src/tusb.c: extend tu_str_xfer_result[] with "ABORTED" and "INVALID"
to match the new enum size. Not reachable today (no HCD posts those
values through hcd_event_xfer_complete), but keeps the enum/table
invariant intact so future HCDs that surface ABORTED don't index OOB.
- examples/dual/dynamic_switch/src/main.c: apply the same while(1)
hoist already done for cdc_task / print_devinfo_task to
led_blinking_task. On OS_NONE the loop returned mid-iteration, which
on first call could fire multiple back-to-back toggles while
start_ms (initially 0) caught up to uptime.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
|
|
Fix premature MTP phase change after short MTP OUT transfer
|
|
|
|
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]>
|
|
Handle OUT transfer differently from IN to not prematurely change MTP phase when host sends short packet that is not end of MTP data phase. Only reaching container length or ZLP should change phase.
|
|
- Added a pending FIFO queue for asynchronous control transfers when the active slot is busy.
- Introduced `control_xfer_dispatch_pending` to handle queued transfers on slot availability.
- Improved synchronization for blocking and non-blocking transfer modes, preventing deadlocks in RTOS.
- Refactored and renamed related functions for clarity and consistency.
- Enhanced error handling and callback invocation for failed or stale transfers.
|
|
|
|
- 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]>
|
|
checks
|
|
|
|
Signed-off-by: HiFiPhile <[email protected]>
|
|
Signed-off-by: HiFiPhile <[email protected]>
|