summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
8 hoursdocs: link STM32F7 hub limitation from READMEdocs/stm32f7-usb-host-limitationHiFiPHile
8 hoursdocs: document STM32F7 low-speed hub limitationHiFiPHile
27 hoursMerge pull request #3815 from HiFiPhile/agent/fix-dwc2-host-fifo-allocationZixun LI
hcd/dwc2: fix FIFO allocation and periodic transfer
34 hoursMerge pull request #3877 from kilograham/rp2040-pio-usb-fixZixun LI
rp2040: do not require PICO_DEFAULT_PIO_USB_DP_PIN to be set for pio_usb
43 hoursrp2040: do not require PICO_DEFAULT_PIO_USB_DP_PIN to be set for pio_usbgraham sanderson
47 hoursfix(dwc2): bound periodic intervals to HFNUM rangeHiFiPHile
HFNUM retains only 16384 host-frame positions, while valid periodic endpoint intervals can be longer. Resubmission after the counter wraps can therefore alias the elapsed time and skip the next established service phase. Cap the host-selected interval to one HFNUM cycle using the root-port frame unit. USB permits a shorter host-provided period, and the bounded interval keeps phase calculation unambiguous for native and split endpoints.
47 hoursfix(dwc2): defer periodic DMA abort cleanup until haltHiFiPHile
Periodic DMA channels use their natural service-boundary halt instead of a software CHDIS request. Keep the endpoint busy after an abort so a replacement transfer cannot reuse its state or buffer while the channel remains active. When HCINT.HALTED arrives, release the channel without reporting completion for the aborted transfer. If endpoint closure is also pending, release the endpoint from the same halt path.
48 hoursMerge remote-tracking branch 'origin/master' into ↵HiFiPHile
agent/fix-dwc2-host-fifo-allocation
48 hoursUSBH: fail enumeration cleanly after disconnectHiFiPHile
A disconnect can close endpoint zero after an enumeration control stage completes but before USBH submits the next stage or request. HCD submission then legitimately returns false; treating that result as an invariant violation asserts during rapid replug and can leave enumeration unfinished. Complete an in-progress control request as failed when its DATA or status stage cannot be submitted. Propagate submission failures from every asynchronous enumeration continuation and finish enumeration through the normal failure cleanup path. This keeps controller teardown races out of assertions without fabricating a successful transfer. Validated by interrupting enumeration during rapid STM32U5A5 replug tests with DWC2 DMA and slave modes.
48 hoursDWC2 host: clean up channels on disconnectHiFiPHile
A root-port disconnect invalidates every active transfer. Retire channel and FIFO interrupt sources plus host-channel state in the disconnect ISR using the Linux DWC2 cleanup model instead of reinitializing the core and PHY, which can sleep on STM32 HS PHYs. Flush posted slave requests, request halts for enabled channels, clear channel interrupt and software ownership, and keep endpoint records closing until USBH processes the remove event. Reject transfer submissions to closing endpoints, preserve fast-replug notification, and re-enable the global host-channel interrupt when a new channel is initialized.
48 hoursMerge remote-tracking branch 'origin/master' into ↵HiFiPHile
agent/fix-dwc2-host-fifo-allocation
48 hoursfix(dwc2): queue initial slave OUT packet immediatelyHiFiPHile
In slave mode, channel_xfer_start() enabled an OUT channel but left every FIFO write to a later PTXFEMP interrupt. DWC2 creates the request-queue entry only when the packet's final FIFO word is written, so unrelated interrupt work could consume the selected service frame before the transfer was actually queued. Factor FIFO writes into a capacity-checked helper and write the initial packet while the channel-enable operation is still protected from DWC2 interrupts. Keep FIFO-empty interrupts only for data that does not fit immediately. The protected section never waits for FIFO or request-queue space. When initial periodic OUT submission is too close to the frame boundary, release the unused channel and defer the still-pending endpoint to the next SOF. Internal retries bypass this initial boundary guard. A hardware trace showed HCCHAR enabled for frame 0x0378 while the packet's final FIFO word was delayed until frame 0x03ae. The complete five-commit fix set passed 600 seconds in every O0/O2 and slave/DMA mode. Signed-off-by: HiFiPHile <[email protected]>
2 daysfix(dwc2): enable periodic channels in the selected frameHiFiPHile
Periodic IN and DMA-backed transfers selected ODDFRM before waiting for request-queue space. A DWC2 interrupt could also run between reading HFNUM and writing HCCHAR.CHENA, allowing the selected frame to pass while the transfer still appeared active. Wait for request-queue capacity with controller interrupts enabled, then mask only GAHBCFG.GINT while sampling HFNUM and enabling a new periodic channel. Record the periodic phase from that same HFNUM sample so a boundary after channel enable cannot shift later interval calculations. The bounded critical section contains no queue wait, callback, disable, or allocation loop. Retries that already selected their frame bypass the new selection step. Also clear a retained HCCHAR.CHDIS before every channel enable. A halted channel can otherwise be re-enabled as CHENA|CHDIS and wait for a terminal interrupt that never arrives. Hardware traces captured periodic IN selections at frames 0x3303 and 0x3266 but activation only after 0x330c and 0x3273, respectively.
2 daysfix(dwc2): fail missed isochronous framesHiFiPHile
A frame-overrun interrupt means the selected periodic service interval has already been missed. Retrying an isochronous transfer after that point cannot deliver the original packet and can leave the class waiting indefinitely for a terminal result. Enable frame-overrun interrupts for slave periodic channels. Complete isochronous IN and OUT overruns as XFER_RESULT_FAILED in both slave and DMA modes, accounting for bytes already written on OUT. Preserve the existing retry behavior for non-isochronous DMA transfers. This reports the missed packet honestly through the normal HCD completion path: no fabricated success and no class-level abort workaround.
2 daysfix(dwc2): let DMA periodic channels halt naturallyHiFiPHile
DWC2 buffer/external DMA mode automatically halts a periodic channel at its next service boundary. Programming HCCHAR.CHDIS|CHENA for a non-split periodic channel is explicitly disallowed by the controller programming guide, yet channel_disable() skipped that write only for split periodic transfers. Return without programming channel disable for every periodic DMA channel. Non-periodic DMA and slave-mode channels retain the existing explicit-disable path. The previous path reproduced after 420 seconds in O2/DMA with a closing capture transfer left INVALID while HCCHAR retained CHENA|CHDIS and HCINT was clear.
2 daysfix(dwc2): preserve simultaneous slave channel haltHiFiPHile
Slave-mode channel handlers process one interrupt cause per pass, but the dispatcher acknowledged every HCINT bit before invoking them. When ChHltd arrived together with another cause, the handler consumed the other cause and the halt was lost. A subsequent disable could then leave CHENA|CHDIS asserted with HCINT and HAINT clear, so the submitted periodic transfer never completed. When a slave channel reports ChHltd with another cause, acknowledge only the non-halt causes and leave ChHltd pending for the next channel-IRQ pass. DMA handlers retain their existing combined-cause behavior. The uninstrumented negative capture reproduced the lost terminal state with HCCHAR=0xe044881c, HCTSIZ=0x0008001c, HCINT=0, and XFER_RESULT_INVALID.
2 daysfix(dwc2): serialize deferred transfer abortHiFiPHile
Protect periodic deferral cancellation from the SOF interrupt. Re-enable the host interrupt before disabling an active channel because slave-mode channel disable may wait for request-queue space.
2 daysMerge remote-tracking branch 'origin/master' into ↵HiFiPHile
agent/fix-dwc2-host-fifo-allocation
2 daysfix(dwc2): drain host RX status before channel IRQHiFiPHile
Popping an IN transfer-completion entry from GRXSTSP asserts HCINT.XferCompl. Drain the receive FIFO first, then read the live masked global status so the newly asserted channel completion is handled without waiting for another interrupt.
2 daysMerge branch 'master' into agent/fix-dwc2-host-fifo-allocationHiFiPHile
2 daysfix(dwc2): preserve periodic transfer phaseHiFiPHile
Anchor resubmitted periodic transfers to the endpoint service interval and defer early submissions through SOF. This prevents callback latency from shifting the cadence or causing intervals to be skipped, while keeping pending transfers abortable. Signed-off-by: HiFiPHile <[email protected]>
3 daysMerge pull request #3872 from runelauridsen/dwc2-pid-desyncZixun LI
Fix DWC2 DMA data toggle mismatch in IN-transfers
3 daysMerge pull request #3828 from dxbjavid/ep2drv-endpoint-boundZixun LI
bound endpoint number in tu_bind_driver_to_ep_itf
3 dayspropagate the endpoint-bound failure on the host pathHiFiPHile
tu_bind_driver_to_ep_itf() now returns false when ep_num >= CFG_TUH_ENDPOINT_MAX, but the host caller ignores that result and continues enumeration. Configurations such as host/bare_api set the limit to 8, while valid USB devices may use endpoints 8–15. A recognized class can therefore continue and later index ep_status[epnum] or ep2drv[epnum] out of bounds. Wrap this call in TU_ASSERT(...), as the device path already does, so parsing fails immediately. Signed-off-by: HiFiPHile <[email protected]>
3 daysFix DWC2 DMA data toggle mismatch in IN-transfersrunelauridsen
3 daysMerge pull request #3868 from michaelajax/add-ucpd-attached-callbackZixun LI
ucpd: Add "attached" state callback for CC state changes
5 daysremove unnecessary commentMike Ajax
5 daysSuppress USB-C attached callback unless cable state changed from ↵Mike Ajax
disconnected->connected or connected->disconnected
5 daysAdd "attached" state callback for UCPDMike Ajax
7 daysMerge pull request #3851 from hathach/etmtrace-rp2350Ha Thach
rp2350: ETM trace board pico2_etm_trace
7 daysdocs: msc-host TUR retry handoff, split out of this PRhathach
7 daysrp2350: PIO-USB runs the pico-sdk stock 150 MHzhathach
Closes the sys-clock question. rp2350: the dynamic 156 MHz switch is removed - 150 MHz soak-tested clean (and a runtime switch truncates ETM capture on the trace carrier). rp2040 keeps its existing 120 MHz: soak sweeps show 120 = 8/8, stock 125 = 0/3 (bulk-OUT collapses, device NAKs ~600:1 with zero CRC errors on the wire), and 132 = 2/10 flaky despite an exact 12n/375k divider - no divider criterion predicts rp2040 PIO-USB health (the RX state machine samples at raw sysclk), so only soak-validated clocks ship.
7 dayspico2_etm_trace: RP2350 board on the MIPI-20 ETM trace carrierhathach
Board files for the trace carrier (console GP12/13, LED GP10, I2C GP8/9, PIO-USB host on GP20, all retargeted in board.cmake so the SDK defaults cannot mux a trace pin), compile-time trace pin-conflict checks, the measured DBGPAUSE rationale, Ozone project, and skill/docs updates. Trace validated at the stock 150 MHz (75 MHz TRACECLK, +1 ns sampling): zero overflow through a 15 s throughput soak; V2 probe ceiling 120 MHz.
7 daysMerge pull request #3860 from hathach/claude/hil-blindnessHa Thach
hil: drop the sysfs blindness subsystem and derive the recovery reserve
7 daystest/hil, docs: move the containment history into the design dochathach
The modules were 21% comment, much of it review-cycle argument rather than guidance -- _kill_kids stated 'descendant by construction, no argv check needed' twice, eight lines apart. Deleting such comments outright makes maintenance worse: the next reader simplifies the thing the comment was defending. So the history moves to the 2026-07-30 fleet-wedge design doc, which gains a trim addendum recording what was removed, what was deliberately kept, and the rule that decided each -- the CI ceiling bounds how long a run burns, and does nothing about state that outlives it. One comment was not merely long but WRONG: the report wipe carried 'The unlink is DEFERRED to inside the pool try/except below', which is the opposite of what the code does -- it sits before Manager() with its own comment explaining why. That is the failure mode this pass is about, so it is deleted rather than reworded. Kept everywhere: citations that refute a plausible wrong reading. That usb_lock_device_interruptible is why the readers are killable, that usblp_mutex is driver-global, that rawmidi honours O_NONBLOCK where usblp does not. Two follow-ups are retired with them: pr3803-hil-blindness-reporting.md (there is no blindness to report any more) and pr3803-usbtest-recovery-reserve.md (the reserve is derived now). Kept: pr3803-flasher-recover.md, which PR #3832 implements, plus pr3803-pci-rebind-stranding.md and pr3803-hil-iar-rerun-spec.md, both independent of this work.
7 daystest/hil: drop the sysfs blindness subsystem and derive the recovery reservehathach
Two layers whose cost was a contract to reason about rather than an outcome. SYSFS_UNKNOWN was a three-valued return five files had to keep apart, and misreading unknown as absence was silent: a healthy board reported as a firmware regression. What it guarded is real -- `serial` is served by usb_string_attr, which takes usb_lock_device_interruptible (v6.12.96 sysfs.c:141-143), the same lock a wedged usbfs ioctl holds -- so the BOUND stays, on every caller by default. usb_scan reads `serial` on every device matching the VID, and hil_lock's controller_of does that on essentially every board, so one wedged DUT would otherwise stall every worker, not one. What goes is the third value. read_sysfs now returns str or None, and the question the third value existed to answer is asked directly instead, by two predicates that say which question they answer: sysfs_stranded() is process-wide and sticky, for hil_pool_check's footer ("could anything here be the tool losing sight of healthy hardware?"), and path_stranded(path) is per-device, which is what usbtest needs to tell a DUT whose `serial` is held under device_lock from one that genuinely left the bus -- that difference decides whether it performs driver-registry writes that take the uninterruptible device_lock. Gone: _SysfsUnknown, SYSFS_UNKNOWN, sysfs_blind, sysfs_blind_note, note_sysfs_strand, the cross-process blindness publishing and its report banner, usb_scan's (list, bool) return, usbtest's inconclusive abort, _blind_note's slot in the result tuple, and bounded_open, whose last caller went in the previous commit. The strand memo is rewritten around the one invariant that makes it safe to reuse: it is keyed by the path's kernfs inode, captured BEFORE the read. A busport does not change when a board returns to the same physical port, so a path-only blacklist outlives the wedge and hil_pool_check's own recovery flow -- reset, reflash, wait_device polling that busport -- would never look at the board again. A re-enumeration destroys the kernfs node and makes a new one, so a changed inode is the all-clear. Two ceilings bound different things: per path (_PATH_STRAND_MAX) for a board that flaps while still wedged, and per process (_STRAND_MAX) as a backstop against RLIMIT_NOFILE, counted per PATH rather than per reader because hil_pool_check runs four poll threads over one bus. A board the pool guard never reached is now reported as run-aborted rather than pool-timed-out, and outranks a stale board-locked cell for the same reason the pool-timeout cell does. Both predicates answer conservatively where they are consulted before something irreversible. path_stranded() covers the paths read_sysfs answered None for WITHOUT reading -- past _STRAND_MAX it declines to start another reader, and vouching for a path nobody looked at hands usbtest's fail-CLOSED guard a fabricated all-clear, running remove_id/unbind against a wedged device. usbtest's startup lookup carries the same caveat hil_test's absent arm already did, because its stderr is relayed verbatim into the report cell. strand_note() survives the removal for the same reason master had it: every caller that can say "not found" needs the same sentence, and the one site left to re-invent it got missed -- a wedged-but-enumerated printer was reported as an enumeration failure, sending a maintainer after firmware. The two predicates are not interchangeable, and usbtest needs both. Its per-case verdict is per-DUT -- a peer that stranded at case 2 must not make our board report wedged at case 29 -- but the finally block's cleanup is process-wide: remove_id plus an unbind of EVERY interface under the driver, including that peer's, each taking the uninterruptible device_lock. So the verdict uses path_stranded() and the global cleanup stays gated on sysfs_stranded(). USBTEST_RECOVERY_BUDGET was an independent 250s that could not actually contain the ladder it reserved for, which is why usbtest.py carried a _time_left() gate re-deciding before every step -- with a bare '- 35' for costs paid downstream that nobody could re-derive. Between them the two produced a recovery that skipped its own steps for most real hangs. The reserve is now derived from the bounds usbtest itself declares, per flasher and per target: a probe reset, a reflash, and the Rescue-DP POR plus retry a wedged RP DAP needs, plus the settles and hil_util.REAP_GRACE for each bounded step. The Rescue-DP legs are openocd-only and gated on the RP target cfg, and a stub reset is screened out, so the reserve tracks each board's real ladder instead of one fleet number: 390s for the two RP boards -- whose ladder the old 250 could not contain, which is exactly why the gates skipped their steps -- 190s for the other seventeen probe-reset boards, and 150s for esptool and lm4flash, whose reset is a no-op. Changing a bound in usbtest moves the reserve with it, and a unit test asserts it covers the ladder. With the room actually reserved, the child runs the ladder straight through: recovery_steps, _time_left, the three per-step gates and the parsed-but-unused --outer-timeout are gone. What stays is what decides outcomes -- the convoy_safe gate, reset-before-reflash, the no_op screen so a stub that resets nothing is not claimed, and wedged_pids() as the arbiter, because a clean flash only proves the probe wrote the MCU. hil_util.py 616 -> 514 lines.
7 daystest/hil: run the printer write in a child, like the readhathach
test_device_printer_to_cdc opened /dev/usb/lp* on the worker itself and let hil_util.bounded_open abandon a thread when the open blocked. usblp allows one opener -- usblp_open() returns -EBUSY while usblp->used (v6.12.96 usblp.c) -- so the abandoned thread's fd poisoned the node for every later test that worker ran. The read half already avoided this by forking; the write half now does too, via the same run_alongside, and a killed child takes its fd with it. This removes the only production caller of bounded_open.
7 daysAdd RTT console/capture tooling (tools/rtt.py), rtt skill, and HIL harness ↵Ha Thach
support (#3853) Promote SEGGER RTT from an inline debugging technique to a standalone skill backed by one stdlib-only implementation in tools/rtt.py: a CLI and importable module for console/capture over J-Link (RTTTelnetPort) and OpenOCD (rtt server) probes, with probe selection by serial or VID:PID, control-block address via --elf or --addr, bidirectional console, post-mortem ring dump, and --reset-before-attach for boot-time capture. The HIL harness reads a board's console over RTT when its probe has no VCOM ("logger": "rtt" plus a LOGGER=rtt variant define), covering device_info, pool-check aliveness, and CI wiring. Validated on 22 boards across both backends; 26 unit tests run in pre-commit.
7 daysMerge pull request #3863 from hathach/claude/validate-loopHa Thach
validate workflow: loop validate -> fix cycles until green Turn the single-pass validate gate into a loop: run unit + builds + size + PVS + claude/codex reviews in parallel; on a red verdict one fix agent repairs the gate-failing evidence (CONFIRMED findings, codex P0/P1, failed stages - PLAUSIBLE/quality stay report-only), commits, and the affected stages re-run, up to maxCycles (default 5). Hardened per review: fix-commit paths verified from git rather than self-report, restartRequired when a fix edits the workflow itself, per-stage evidence budgeting so the fixer prompt JSON never truncates mid-document, dirty-tree and moving-base-ref guards, dead stage agents retried instead of ending the loop, and only pure-docs fixes skip a full stage re-run.
7 daysMerge pull request #3858 from hathach/claude/validator-done-signalsHa Thach
pr-review-validator: done waits for every auto-reviewer to settle
7 dayspr-review-validator: done waits for every auto-reviewer to settle on the ↵hathach
head SHA A cycle running before the bots posted saw zero findings and reported done; with a fast-green CI the babysit loop could exit unreviewed. done now needs every reviewer settled for the current head: Copilot's verdict review (commit_id), Codex's verdict comment (Reviewed-commit line), its thumbs-up reaction on the PR body, or the named claude-review check run — with quota/ error notices and the reaction freshness-gated on push time (check-suite creation, not committer date) and every lookup paginated. pr-babysit re-arms with backoff on a pending reviewer instead of exiting unactionable, skipping the pointless final-cycle wait.
7 daysMerge pull request #3861 from hathach/claude/pr-babysit-hil-guardHa Thach
pr-babysit: never edit HIL rig configs without user approval
8 dayspr-babysit: never edit HIL rig configs without user approvalhathach
The workflow's fix lane once skipped two host tests in test/hil/tinyusb.json to green a check whose root cause was a failing fixture drive (reverted in 4b11d59a4). Rig rosters describe physical hardware: papering over a fixture fault hides it from the user who has to swap the part. Now fixAndVerify strips test/hil/*.json from every fix scope (a group left with no other files is withheld and logged), the code-writer prompt carries the constraint, and ok=false keeps such cycles from pushing. HIL stays red when the fix is a hardware swap - that red is the signal.
8 daysMerge pull request #3856 from hathach/claude/hil-drop-ntHa Thach
test/hil: drop the Windows accommodations, which accommodate nothing
8 daysMerge pull request #3855 from hathach/claude/pr-babysit-split-triageHa Thach
workflows/agents: overlap pr-babysit's review and CI lanes; split pr-monitor; pin agent efforts
8 daysvalidate: add claude + codex diff-review stages (opus/high, sol/high)hathach
The claude stage reviews the diff directly (the code-review skill is a CLI built-in, unavailable to subagents); the gate is enforced in-script from structured findings, failing only on confirmed correctness/safety bugs.
8 dayspr-babysit: overlap a fast review lane with the CI watchhathach
Review findings are validated, fixed, and pushed without waiting on CI; checkoutDir decouples the PR checkout from the session cwd. File-less CI failures are scoped by a dedicated agent, paths canonicalized and existence-checked via git ls-files, overlapping groups merged. Per-id reply/resolve accounting retries failures and holds the green exit until all outward work is drained.
8 daysagents: split pr-monitor into pr-ci-watcher + pr-review-validator; rename ↵hathach
port-dev/driver-reviewer to code-writer/code-verifier; pin model+effort on every agent
8 daystest/hil: drop the Windows accommodations, which accommodate nothinghathach
hil_test.py cannot run on Windows and never could: it imports helper.hil_lock, whose module-level `import fcntl` is POSIX-only, so the harness fails at import before a line of it executes. Past that it reads /sys/bus/usb, /dev/bus/usb, /dev/serial/by-id and /proc, kills by process group, and takes flock board locks -- none of which Windows has. So the guards were protecting a platform the code cannot reach: - run_cmd branched three ways on os.name to decide whether to set start_new_session and whether to killpg. The non-POSIX arm called p.kill() instead, which kills only the direct child -- exactly the semantics the whole containment design rejects, since a flasher run through a shell reparents out of reach. Dead code that documented the wrong answer. - hil_test picked multiprocessing's default context on Windows "so it still IMPORTS there". It does not import there. - test_device_audio_test_freertos returned 'skipped' on nt before touching ALSA, in a function only ever reached from a worker that cannot start there. - Seven @unittest.skipIf(os.name == 'nt') decorators across the two suites. These were the only ones with a real effect -- the unit tests DO import and run on Windows, because they stub pyserial and mostly exercise pure logic -- but what they buy is a partially-green suite for a harness that cannot run, and nothing verifies the set is correct: the hil-test hook only ever runs on ubuntu-latest, so a missing guard fails silently until someone tries. Removing them makes the POSIX assumption single and explicit rather than scattered and half-honoured. Nothing changes on Linux: every removed branch was the one already taken there. Removing the run_cmd guards also removes their `else: p.kill()` arms. Those were the Windows branches, and p.kill() reaches only the direct child -- a flasher run through a shell keeps grandchildren it cannot touch, which is the semantics this containment design rejects. RunCmdCleanupShape pins what is left: both cleanup paths killpg, no try carries an else whose body would run when the kill SUCCEEDED, and the BaseException path still re-raises. Structural rather than behavioural because driving a real SIGINT into a blocked communicate() is timing-dependent, and what actually breaks this block is an edit that rebinds a branch -- which is a shape.
8 daystest/hil: run the HID echo in a child, which is the only bound that works ↵Ha Thach
(#3852) hid_generic_inout was the last unbounded blocking IO in the file. hidapi's hidraw backend reads manufacturer/product via udev for each device reaching create_device_info_for_device, both usb_string_attr served under the device lock a wedged usbfs ioctl holds — and every DUT here is VID cafe, so a wedged sibling stalls the walk. A thread cannot bound it: cython-hidapi calls hid_open and hid_close bare (0.15.0 hid.pyx), so they hold the GIL and the waiter can never resume. Measured — a 1.0s bound never returned. run_cmd's killpg reaches a child regardless; it gains an argv form for the -c body. Filters on both ids: hidapi only runs the free uevent pre-check when ids are passed (linux/hid.c:962), so an unfiltered walk sends every device straight to the locked reads. Tests stall via ctypes.PyDLL, which unlike CDLL holds the GIL — the shape a thread bound cannot cover.