Skip to content

Adapter doctor — dying-dongle triage (EFUSE stability / fw-boot / RX smoke)#211

Merged
josephnef merged 2 commits into
masterfrom
adapter-doctor
Jul 7, 2026
Merged

Adapter doctor — dying-dongle triage (EFUSE stability / fw-boot / RX smoke)#211
josephnef merged 2 commits into
masterfrom
adapter-doctor

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Follow-up to #205. That investigation ended with a dongle that enumerates cleanly, inits green, and is stone-deaf — its EFUSE reads return stochastic garbage (wrong RFE/PA/LNA → wrong PHY tables) and its 8051 never boots firmware (deliberately non-fatal on Jaguar1). Neither is visible to any "did init succeed" check, on the bench or in the field. This PR makes both measurable and ships a triage tool.

Library — health probes on IRtlDevice (all generations)

  • ProbeEfuseStability(N) — N fresh physical EFUSE logical-map reads (not the cached shadow), cross-compared byte-for-byte + 0x8129 EEPROM-ID validation. Healthy silicon is byte-identical on every read, cold or warm — any mismatch is conclusive dying-silicon evidence. Wired per generation: J1 (EepromManager::ReadPhysicalEfuseMap), J2 (public logical-map reader), J3 8822C only — the 8822E's OTP is not reliably readable post-bring-up by design (see cache_efuse_8822e), so probing it would false-flag healthy units; it reports supported=false.
  • GetFwBootStatus() — the firmware-download outcome recorded at the real hardware boundaries inside each generation's DLFW machine: checksum_ok = IMEM/DMEM checksum-ready (MCUFW_CTRL & 0x50 on J2/J3, the chksum report on J1), ready_ok = the boot handshake (0xC078 / CPU_DL_READY). Distinguishes a transport/checksum failure from a downloaded-but-never-booted MCU — and survives the fatal bring-up throw on J2/J3, so an init abort still reports which stage died.
  • src/AdapterHealth.h — structs, the shared probe loop, and a pure classifier (HEALTHY / SUSPECT / FAILING + reason bits). Grading rules documented next to the code; ctest'd (adapter_health_classify) with cases encoding the verdicts measured on the real dying unit vs its healthy twin — including the edges (blank dev-board EFUSE, RF-quiet silence, corrupt-frame ratios deliberately ungraded).

Tool + harness

  • examples/doctor — pure-CLI end-user triage: bring-up → EFUSE probe → FW status → RX smoke. Deaf grades FAILING only when --expect-traffic vouches for a source (an RF-quiet room is otherwise indistinguishable); verdict in the exit code (0/1/2), <devourer-doctor> machine line, --vid/--bus/--port topology select for rigs with same-PID/same-serial adapters.
  • tests/adapter_doctor_cold.sh — definitive-verdict wrapper: per-rep VBUS cold via uhubctl, in-tree rtw88 module temp-blacklisted (udev modalias reloads it at every re-enumeration — a bare modprobe -r does not survive), radiation-verified canonical-SA flood. DOCTOR_SKIP_VBUS=1 for DUTs on root ports.
  • docs/adapter-doctor.md + README/CLAUDE.md pointers.

Hardware validation

unit path protocol verdict
8812AU (the #205 dying unit) J1 VBUS cold ×2 + vouched flood FAILING 2/2 (fw-dead + deaf-to-flood)
8812AU (healthy twin) J1 VBUS cold ×2 + vouched flood HEALTHY 2/2 (EFUSE 4/4 stable, ~3.7k frames)
8821CU J2 / 8821C VBUS cold ×2 + vouched flood HEALTHY 2/2
8822BU (Archer T3U) J2 / 8822B warm ×2 + vouched flood (root-port DUT) HEALTHY 2/2
8812CU J3 / 8822C warm, ambient HEALTHY
8812EU J3 / 8822E warm, ambient HEALTHY, probe correctly refused

The dying unit's EFUSE happened to read stable during the validation session — its sickness drifts between faces (garbage EFUSE / valid-ID-with-dead-MCU / deaf RX), which is exactly why the doctor probes independent subsystems: the verdict landed anyway, via the FW and vouched-deaf checks. A cheap external cross-check is also documented: the in-tree rtw88 probe fails failed to validate firmware on such a unit and binds cleanly on a healthy one.

11/11 ctest; jaguar1-only and jaguar2-only subset builds verified green (all probe wiring lives inside each generation's own conditionally-compiled directory — no new #if guards needed).

🤖 Generated with Claude Code

josephnef and others added 2 commits July 7, 2026 11:26
…smoke)

A dying adapter can enumerate cleanly, init green, and still be
stone-deaf: the #205 unit's EFUSE reads return stochastic garbage
(wrong RFE/PA/LNA -> wrong PHY tables) and its 8051 never boots
firmware — non-fatal on Jaguar1, so nothing aborts. No "did init
succeed" check reaches either. This adds the probes and a triage tool
so both the bench and end users can grade a suspect dongle.

Library (all generations):

- src/AdapterHealth.h — EfuseStability / FwBootStatus structs, the
  shared physical-map cross-compare loop, and a pure classifier
  (HEALTHY / SUSPECT / FAILING + reason bits), ctest'd in
  tests/adapter_health_selftest.cpp with cases encoding the verdicts
  measured on the real dying unit vs its healthy twin.
- IRtlDevice::ProbeEfuseStability(N) — N fresh PHYSICAL EFUSE map
  reads cross-compared byte-for-byte + 0x8129 ID validation. Healthy
  silicon is byte-identical every read; any mismatch is conclusive.
  Wired per generation: J1 via EepromManager::ReadPhysicalEfuseMap,
  J2 via the public logical-map reader, J3 8822C only — the 8822E's
  OTP is not reliably readable post-bring-up by design (see
  cache_efuse_8822e), so probing it would flag healthy units.
- IRtlDevice::GetFwBootStatus — the fw-download outcome recorded at
  the real hardware boundaries in each generation's DLFW machine:
  checksum_ok = IMEM/DMEM checksum-ready (MCUFW_CTRL & 0x50 on
  J2/J3, chksum report on J1), ready_ok = the boot handshake (0xC078
  / CPU_DL_READY). Distinguishes a transport failure from a
  downloaded-but-never-booted MCU, and survives the fatal bring-up
  throw on J2/J3 so an init abort still says which stage died.

Tool + harness:

- examples/doctor — pure-CLI triage: bring-up, EFUSE probe, fw
  status, RX smoke (deaf = FAILING only with --expect-traffic, else
  SUSPECT — RF-quiet rooms lie). Verdict in the exit code (0/1/2),
  <devourer-doctor> machine line, --vid/--bus/--port topology select
  for same-PID/same-serial rigs.
- tests/adapter_doctor_cold.sh — definitive-verdict wrapper: per-rep
  VBUS cold on uhubctl-switchable hub ports, in-tree rtw88 module
  temp-blacklisted (udev reloads it on every re-enumeration),
  radiation-verified beacon flood. DOCTOR_SKIP_VBUS=1 for DUTs on
  root ports (never uhubctl root ports on this rig).
- docs/adapter-doctor.md + README/CLAUDE.md pointers.

Hardware-validated on the bench: dying 8812AU FAILING 2/2 from true
cold (fw-dead + deaf-to-flood — its EFUSE happened to read stable
that session; the sickness drifts, which is why independent probes),
healthy 8812AU / 8821CU HEALTHY 2/2 from true cold, 8822BU (T3U)
HEALTHY 2/2, 8812CU HEALTHY, 8812EU HEALTHY with the probe correctly
refused. 11/11 ctest; jaguar1-only and jaguar2-only subset builds
green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mingw job builds an explicit target list and its Test step fails
any registered ctest binary that isn't in it as Not Run — which is
exactly what adapter_health_classify did on the first push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit a84a6f0 into master Jul 7, 2026
15 checks passed
@josephnef josephnef deleted the adapter-doctor branch July 7, 2026 08:37
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