Skip to content

USB open: Kestrel skips the reset; reopen recovery + bounded topology discovery (#294)#296

Merged
josephnef merged 2 commits into
masterfrom
kestrel-usb-open-hardening
Jul 16, 2026
Merged

USB open: Kestrel skips the reset; reopen recovery + bounded topology discovery (#294)#296
josephnef merged 2 commits into
masterfrom
kestrel-usb-open-hardening

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Fixes the mechanical half of #294 (the deafness half was the RX-descriptor walk fix already merged in #295 — full root-cause analysis in the issue comment).

Problem

libusb_reset_device on a Kestrel with running (or SIGKILL-orphaned) firmware crashes it to bootrom: the device re-enumerates (device firmware changed), the open dies with a stale handle, and the ROM path can land in the dead ZeroCD DISK id (0bda:1a2bbInterfaceNumber 255, unclaimable by libusb and kernel usbfs, so no userspace eject exists; only a VBUS power-cycle recovers). Any harness cycling the demos hit retry roulette; every zero read looked like a deaf receiver.

Fix

  • claim_interface_then_reset skips the USB reset for any kestrel::variant_for_usb_id match. Kestrel power_on forces the MAC off from any retained state — the reset added nothing but the ROM-drop risk. Covers all demos at once. Bench: SIGKILL-churn 6/6 clean without the reset vs ZeroCD roulette with it.
  • New devourer::claim_interface_reset_reopen (used by rxdemo/txdemo/streamtx): when a reset does re-enumerate (other chips / forced), close the stale handle, wait bounded for the same bus+port to return under the original VID:PID (transient ZeroCD ids are not a match), re-open and re-claim without a second reset — the re-enumeration itself reloaded the firmware.
  • Bounded topology discovery: with DEVOURER_USB_BUS/PORT set, the demos poll the named socket for up to 15 s instead of failing on the first empty scan (a closed/killed predecessor leaves the chip re-enumerating for seconds). Strict no-fall-through behavior unchanged.
  • Selftest: locks the issue-Kestrel (8852BU/8832CU): bimodal deaf RX bring-up — loop starts green, 0 frames until VBUS cold (sometimes) #294 aggregate-walk regression — [PPDU_STS(drv_info, unit 16)][WIFI]: the trailing WIFI frame must be reached (with the old offset formula the walk broke after the phy-status sub-packet → green bring-up, zero frames).

Validation

  • SIGKILL-churn ×6 on the 8832CU, default env: 6/6 bring-ups deliver (700–1000 frames/15 s), 0 ZeroCD landings, 0 stale handles — vs 5/6 failing before.
  • From VBUS cold: clean.
  • tests/he_er_su_cross_rx.sh same-die (8852C→8852C) and cross-die (8852B→8852C), ch6: 8/8 cells pass on the first attempt, zero retries, zero VBUS cycles — vs all-day retry roulette.
  • 24/24 ctest.

🤖 Generated with Claude Code

josephnef and others added 2 commits July 16, 2026 19:12
Root-causes the issue #294 "bimodal deaf RX bring-up". Two mechanisms:

The deafness itself was the RX-descriptor aggregate-walk break fixed in
608e2bf (#295): whether the fw attaches phy-status (PPDU_STS sub-packets
with drv_info) or mac_info to the RX stream varies per bring-up session,
and with the wrong payload-offset formula a PPDU_STS-led aggregate broke
the walk after its first sub-packet — every aggregate delivered only phy
status, never a WIFI frame: green bring-up, zero frames, bimodal by
session. A selftest now locks the [PPDU_STS(drv_info, unit 16)][WIFI]
aggregate walk.

The stale-handle/ZeroCD churn around it is fixed here:
libusb_reset_device on a Kestrel with running (or SIGKILL-orphaned)
firmware crashes it to bootrom — the device re-enumerates ("device
firmware changed") and the ROM path can land in the dead ZeroCD DISK id
(0bda:1a2b, bInterfaceNumber 255 — unclaimable by libusb AND kernel usbfs,
both cap interfaces at 32, so no userspace eject exists; only a VBUS
power-cycle recovers it).

- claim_interface_then_reset skips the USB reset for any
  kestrel::variant_for_usb_id match: Kestrel power_on forces the MAC off
  from any retained state (the real cleaner), so the reset added nothing
  but the ROM-drop risk. Bench: SIGKILL-churn 6/6 clean without the reset
  vs ZeroCD roulette with it.
- New devourer::claim_interface_reset_reopen: when a reset does
  re-enumerate (other chips / forced), close the stale handle, wait
  (bounded) for the same bus+port to return under the original VID:PID
  (transient ZeroCD ids are not a match), re-open and re-claim WITHOUT a
  second reset. rxdemo/txdemo/streamtx use it.
- The demos' DEVOURER_USB_BUS/PORT discovery polls the named socket for up
  to 15 s instead of failing on the first empty scan — a closed/killed
  predecessor leaves the chip re-enumerating for seconds.

Validation: SIGKILL-churn x6 on the 8832CU with default env — 6/6
bring-ups deliver, 0 ZeroCD landings, 0 stale handles; from VBUS cold
clean; tests/he_er_su_cross_rx.sh same-die (8852C->8852C) and cross-die
(8852B->8852C) both pass 8/8 cells on the first attempt with zero
retries/VBUS cycles; 24/24 ctest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…_FOUND

With do_reset=false (streamtx Termux path, DEVOURER_SKIP_RESET) a
NOT_FOUND from the inner claim is an interface-absent claim failure, not
a reset re-enumeration — closing the handle and polling for a comeback
would just burn the 30 s timeout (and on Termux orphan a wrapped fd that
device-list scans can't re-find). Pass it straight through instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef
josephnef merged commit 2bdb71d into master Jul 16, 2026
18 checks passed
@josephnef
josephnef deleted the kestrel-usb-open-hardening branch July 16, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant