Skip to content

cca gates: make the Jaguar1 arm of the regcheck actually runnable - #429

Merged
josephnef merged 3 commits into
masterfrom
fix/cca-gates-jaguar1-validatable
Sep 13, 2026
Merged

josephnef merged 3 commits into
masterfrom
fix/cca-gates-jaguar1-validatable

Conversation

@josephnef

@josephnef josephnef commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

A Jaguar1 reached this bench after #427 merged — an RTL8821AU (TP-Link Archer T2U PLUS, 2357:0120), which dispatches to RtlJaguarDevice, chip-id 0x05, CHIP_8821_Normal_Chip_TSMC_D_CUT_1T1R. A second Jaguar1 die alongside the 8812AU the split was measured on.

Using it found that the harness #427 shipped cannot check Jaguar1 at all. Two independent reasons, and both produce a clean-looking non-result rather than a failure, which is the worst shape for this kind of gap.

1. The tracker cell measures a tracker that was never built

tests/cca_gates_probe.cpp constructs a default DeviceConfig, so tuning.phydm_watchdog is false. On Jaguar1 that watchdog is the EDCCA tracker — HalModule builds _phydmWatchdog only under that flag, so phydm_watchdog() returns nullptr and SetEdccaTrack is never reached. A default probe run on the 8821AU:

PhydmWatchdog lines: 0
re-tracked lines:    0

The cell then reports no EDCCA tracker running in the default arm about a backend whose tracking path was simply never instantiated, and skips. That is a second reason the Jaguar1 arm could not produce a verdict, independent of the per-family register fix in #427 — so my note on that PR, that the cell would now give a real verdict on an 8812AU, was wrong. It would still have skipped.

--phydm-watchdog builds it. Jaguar3 ignores tuning.phydm_watchdog entirely (only src/jaguar1/ reads it — its own phydm runtime rides the RX/coex thread), so the tracker cell can pass the flag unconditionally.

2. Every register cell fails on a vendor VID

peek32/poke32 addressed chipstate by --pid alone while start_hold passed --vid "$VID". Any adapter enumerating under a vendor VID rather than 0x0bda — TP-Link 0x2357, and most retail parts — had every peek look for it under the default Realtek VID and fail:

FAIL: 0x0120 regs: 0x520 peek failed (primary=0 edcca=0)      x4
SKIP: 0x0120 cntdown: only 0/4 arms reported
FAIL: 0x0120 track: 0x8a4 peek failed                         x2
== 6 passed, 6 failed, 1 skipped ==

Six failed cells, none of them about the gates.

Result

With both fixed, the RTL8821AU is 11 passed / 0 failed / 0 skipped, and the tracker cell gives a real verdict:

note: 0x0120 generation: jaguar1
note: 0x0120 track: tracker IS running at 0x8a4 in the default arm (as expected)
PASS: 0x0120 track: EDCCA tracking stops when EDCCA is the gate turned off
note: 0x0120 cntdown: 0x524[11] constant at 1 — not an EDCCA gate on this backend

That last line is the documented known-limit path working: Jaguar1 never writes 0x524, so the cell reports instead of failing.

This retires the unvalidated caveat from #427

The tracker-ordering fix went in without silicon behind it. It reproduces here. Instrumenting the park instant, widening the window, and landing the disable while DIG is still walking IGI:

pre-fix   NEGCTRL: parked 0x8a4=0x7f7f, tracker still ON, window open
          PhydmWatchdog: EDCCA L2H/H2L re-tracked to 3/-4 (igi=0x1e)   <- park destroyed
          NEGCTRL: window closed, about to clear the tracker
          (3 overwrites across the disable arms)

fixed     0 overwrites inside the same window, across three disable arms,
          with the tracker demonstrably still alive (re-tracks again at igi=0x2a later)

One nuance worth recording, because it cost me a wrong conclusion first: the tracker is write-on-change (l2h != _edcca_last_l2h), so the race only fires while DIG is actively moving IGI. My first negative control ran the disable arm ~30 s after bring-up, by which point DIG had converged, IGI had stopped moving, and the pre-fix ordering looked perfectly correct — 0x8a4 sat parked at 7f7f across five samples. A negative control that fails to reproduce can mean the trigger condition was absent, not that the bug is. The window had to be forced early.

So the bug is real but narrower than #427's description implied: it needs a tick inside a window that is microseconds wide in production and IGI in motion. The fix removes it rather than narrowing it.

Verification

Build clean, ctest 63/63, and the regcheck over everything plugged here:

adapter backend result
2357:0120 RTL8821AU Jaguar1 11 passed / 0 failed / 0 skipped
0bda:a81a RTL8822E Jaguar3 12 cells, all pass
0bda:c812 RTL8822C Jaguar3 12 cells, all pass
0bda:b812 RTL8822BU Jaguar2 not ported, refuses cleanly
0bda:b733 RTL8733BU RTL8733B not ported, refuses cleanly

26 pass / 0 fail / 0 skip on the non-Jaguar1 set, with no regression from the GATES-GEN line gaining a phydm_watchdog=N field.

Not covered: no 8812AU or 8814AU here, so the Jaguar1 evidence is one die (8821A, 1T1R). The on-air delivery tables in #427 remain @snokvist's and are not re-measured — this is register-level and tracker-level only.

🤖 Generated with Claude Code

https://claude.ai/code/session_0192ViRjaiFT9vTiiJpohZGT

The harness shipped in #427 could not check Jaguar1 at all. Two independent
reasons, both of which made the tracker cell report a clean-looking
non-result rather than fail.

The probe builds a default DeviceConfig, so tuning.phydm_watchdog is false.
On Jaguar1 that watchdog IS the EDCCA tracker — HalModule constructs it only
under that flag — so SetEdccaTrack was never reached and the cell reported
"no EDCCA tracker running in the default arm" on a backend whose tracking
path had simply never been instantiated. A --phydm-watchdog flag builds it;
Jaguar3 ignores the field, its phydm runtime rides the RX/coex thread.

Separately, peek32/poke32 addressed chipstate by --pid alone while the probe
got --vid. Any adapter enumerating under a vendor VID rather than 0x0bda —
TP-Link 0x2357, and most retail parts — failed every register cell, because
the peek was looking for it under the default Realtek VID. Six failed cells
on the first Jaguar1 run here, none of them about the gates.

With both fixed, an RTL8821AU (Jaguar1, chip-id 0x05, 1T1R) reports
11 passed / 0 failed / 0 skipped, and the tracker cell gives a real verdict
instead of a skip:

  note: 0x0120 track: tracker IS running at 0x8a4 in the default arm
  PASS: 0x0120 track: EDCCA tracking stops when EDCCA is the gate turned off
  note: 0x0120 cntdown: 0x524[11] constant at 1 — not an EDCCA gate here

That last one is the documented known-limit path behaving correctly: Jaguar1
never writes 0x524, so the cell reports rather than fails.

This also retires the "not hardware-validated" caveat on the tracker-ordering
fix from #427. The race reproduces on this part once the window is widened
and the disable lands while DIG is still walking IGI:

  pre-fix   parked 0x8a4=0x7f7f, tracker still ON, window open
            PhydmWatchdog: EDCCA L2H/H2L re-tracked to 3/-4 (igi=0x1e)
            window closed, about to clear the tracker
  fixed     0 overwrites inside the same window, across three disable arms

Worth recording that the tracker is write-on-change, so the race only fires
while DIG is actively moving IGI — once it converges the pre-fix ordering
looks correct. A first negative control missed the bug for exactly that
reason.

Verified: build clean, ctest 63/63, regcheck 11/0/0 on the RTL8821AU and no
regression on an 8822E, 8822C, 8822BU or RTL8733BU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192ViRjaiFT9vTiiJpohZGT
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Make Jaguar1 CCA gate regression checks runnable

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Enable Jaguar1’s optional PHYDM watchdog during EDCCA tracker validation.
• Propagate vendor IDs to register reads and writes for retail adapters.
• Preserve generation parsing while exposing watchdog state in probe output.
Diagram

sequenceDiagram
  actor R as Reviewer
  participant H as Regcheck
  participant P as CCA Probe
  participant C as chipstate
  participant J as Jaguar1 Radio
  R->>H: Run VID and PID
  H->>P: Start tracker arm
  P->>J: Enable PHYDM watchdog
  J-->>P: Report generation state
  H->>C: Peek with vendor VID
  C->>J: Access EDCCA register
  J-->>C: Return tracker value
  C-->>H: Provide tracker evidence
  H-->>R: Report validation verdict
Loading
High-Level Assessment

The targeted approach is appropriate: the tracker-specific arm explicitly enables Jaguar1’s optional watchdog without changing normal probe behavior, while consistently forwarding VID fixes all register operations for vendor-branded adapters. Automatically enabling the watchdog globally or inferring it after radio construction would broaden behavior unnecessarily or occur too late to instantiate the tracker.

Files changed (2) +36 / -8

Other (2) +36 / -8
cca_gates_probe.cppAdd watchdog-enabled Jaguar1 probe configuration +18/-3

Add watchdog-enabled Jaguar1 probe configuration

• Adds a '--phydm-watchdog' option that enables Jaguar1’s optional PHYDM thread before radio construction. The probe also reports the selected watchdog state alongside the hardware generation for diagnostic visibility.

tests/cca_gates_probe.cpp

cca_gates_regcheck.shValidate Jaguar1 trackers using the correct USB vendor ID +18/-5

Validate Jaguar1 trackers using the correct USB vendor ID

• Passes the configured VID to every 'chipstate' peek and poke, allowing vendor-branded adapters to be addressed correctly. Tracker arms now start the probe with the PHYDM watchdog enabled, and generation parsing accommodates the expanded probe output.

tests/cca_gates_regcheck.sh

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Probe metadata breaks JSON streams 📘 Rule violation ◔ Observability
Description
main emits the machine-readable GATES-GEN marker as space-delimited text without an ev field
instead of a JSON object. Every probe run writes this marker to stdout, so consumers applying the
repository's JSON Lines event contract encounter a non-JSON machine record before processing gate
results.
Code

tests/cca_gates_probe.cpp[R131-133]

+  std::printf("GATES-GEN %s phydm_watchdog=%d\n",
+              devourer::generation_name(dev->GetAdapterCaps().generation),
+              phydm_wd);
Evidence
Compliance rule 6 requires stdout machine events to be one JSON object per line with ev first. The
probe identifies its output as machine-readable, but the changed generation marker emits plain text,
while the logging contract documents stdout as the JSON Lines plane.

CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts: CLAUDE.md: Preserve Structured Logging Plane Contracts
tests/cca_gates_probe.cpp[1-7]
tests/cca_gates_probe.cpp[131-134]
docs/logging.md[3-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The probe's machine-readable generation metadata is emitted as non-JSON text on stdout, violating the structured logging plane contract.
## Fix Focus Areas
- tests/cca_gates_probe.cpp[131-134]
- tests/cca_gates_regcheck.sh[197-197]
## Recommended Fix
Emit the generation and watchdog state as one JSON object whose first field is `ev`, using the repository event emitter where practical, and update the regression script to extract the generation from that JSON event.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Probe shutdown races hardware access ✓ Resolved 🐞 Bug ☼ Reliability
Description
Setting cfg.tuning.phydm_watchdog starts Jaguar1's background watchdog, but device destruction
powers down the chip before the watchdog object's destructor stops and joins its thread. A directly
invoked probe that exits normally with --phydm-watchdog can therefore overlap watchdog register
operations with MAC shutdown and power-off operations.
Code

tests/cca_gates_probe.cpp[111]

+  cfg.tuning.phydm_watchdog = phydm_wd != 0;
Evidence
The new probe flag constructs and starts the watchdog during Jaguar1 initialization. Its thread
calls TickOnce(), which performs BB reads and writes, while normal device destruction calls
rtw_hal_deinit() before the owning HalModule and its watchdog member are destroyed; only the
watchdog destructor currently stops and joins that thread.

tests/cca_gates_probe.cpp[104-113]
src/jaguar1/HalModule.cpp[90-111]
src/jaguar1/PhydmWatchdog.cpp[46-79]
src/jaguar1/PhydmWatchdog.cpp[82-123]
src/jaguar1/RtlJaguarDevice.cpp[2344-2353]
src/jaguar1/HalModule.cpp[663-670]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Enabling `--phydm-watchdog` starts a Jaguar1 thread that remains active until `PhydmWatchdog` is destroyed, while `RtlJaguarDevice` powers down the hardware earlier during teardown. This permits register operations to race with hardware deinitialization.
## Fix Focus Areas
- src/jaguar1/HalModule.cpp[663-670]
- src/jaguar1/PhydmWatchdog.cpp[57-65]
- tests/cca_gates_probe.cpp[104-113]
## Recommended Fix
Stop and join `_phydmWatchdog` at the beginning of `HalModule::rtw_hal_deinit()` before any MAC-disable or power-off writes. Keep the operation idempotent so repeated teardown and the watchdog destructor remain safe.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread tests/cca_gates_probe.cpp
Comment thread tests/cca_gates_probe.cpp
josephnef and others added 2 commits September 13, 2026 09:14
…ag is not

Two things a reader of this harness has to know and could not have learned
from it.

The tracker is write-on-change (l2h != _edcca_last_l2h), so the cell only
sees it act while DIG is still walking IGI. Each arm restarts the probe, so
DIG restarts with it and is in motion during the sample window — but if it
has converged instead, the default arm reads as "no tracker" and the
EDCCA-off arm skips. That degrades to no verdict rather than a false one,
which is the right failure, but a SKIP there means the cell could not create
the condition, not that the tracker behaved. This is the same property that
made a first negative control miss the race entirely.

And --phydm-watchdog is a test lever, not a hint at a better default. That
thread is opt-in because its periodic BB traffic shares the libusb queue
with the TX bulk path and costs throughput — 4500 to 1000 TX submits in 10s
on an 8821 at ch100, per HalModule. This probe never transmits, so it pays
none of it, which is why the cell can afford the flag and a normal session
cannot.

Comments only. Build clean, ctest 63/63, and an untouched end-to-end run of
the committed script reports 13 passed / 0 failed / 0 skipped over an 8822C
and an RTL8733BU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192ViRjaiFT9vTiiJpohZGT
rtw_hal_deinit() clears REG_CR, zeroes REG_RCR and runs the card-disable
power sequence with the phydm watchdog thread still ticking. That thread
does periodic BB reads and writes — FA counters, DIG on 0xc50/0xe50, the
EDCCA thresholds — so teardown races register access against power-off,
which is the one ordering hazard this driver is careful about everywhere
else.

Stop() first. It compare-exchanges _running, so it is idempotent and the
destructor's own Stop() after this is a no-op; the pointer is null whenever
tuning.phydm_watchdog did not build one, which is the default.

Pre-existing, but only reachable once something sets that config, and until
now nothing in-tree did — the --phydm-watchdog probe flag in this branch is
what makes it reachable, so it is fixed here rather than left for whoever
next turns the watchdog on.

Measured on an RTL8821AU, marking every tick and widening the de-init window
to 6 s so the 2 s tick has to land in it:

  pre-fix   clean de-init (stop TRX + card-disable)
            TICKMARK: watchdog touching the chip
            TICKMARK: watchdog touching the chip
            PowerOff: card-disable applied
            => 2 ticks inside the window

  fixed     clean de-init (stop TRX + card-disable)
            PowerOff: card-disable applied
            => 0 ticks inside the window

Found by Qodo on #429. Build clean, ctest 63/63.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192ViRjaiFT9vTiiJpohZGT
@josephnef

Copy link
Copy Markdown
Collaborator Author

Final validation on the merged bytes (a21615c), every adapter on this bench:

0x0120  RTL8821AU  Jaguar1     == 11 passed, 0 failed, 0 skipped ==
0xa81a  RTL8822E   Jaguar3     \
0xc812  RTL8822C   Jaguar3      |  == 26 passed, 0 failed, 0 skipped ==
0xb812  RTL8822BU  Jaguar2      |  (not ported, refuses cleanly)
0xb733  RTL8733BU  RTL8733B    /   (not ported, refuses cleanly)

Zero skips anywhere, which is the point of the change — the Jaguar1 tracker cell now produces a verdict instead of a non-result. Build clean, ctest 63/63, CI 24/24.

Both Qodo findings handled: the teardown race was real and is fixed in a21615c with a measured before/after; the logging one is declined with the reasoning on its thread.

@josephnef
josephnef merged commit 45f4022 into master Sep 13, 2026
29 of 31 checks passed
@josephnef
josephnef deleted the fix/cca-gates-jaguar1-validatable branch September 13, 2026 06:32
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