Skip to content

Jaguar1 hardware beacon + TBTT steering — StartBeacon on every generation#243

Merged
josephnef merged 1 commit into
masterfrom
j1-beacon-tbtt-steering
Jul 11, 2026
Merged

Jaguar1 hardware beacon + TBTT steering — StartBeacon on every generation#243
josephnef merged 1 commit into
masterfrom
j1-beacon-tbtt-steering

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Extends #242's beacon-TBTT steering to Jaguar1StartBeacon / AdjustBeaconTiming / AdjustBeaconTimingFine now work on all three generations (8812AU / 8811AU / 8821AU / 8814AU included), so the hardware-beacon downlink and the uplink-TA / PTP-discipline actuator are no longer generation-gated. AdapterCaps.hw_beacon_txtsf is now true on J1 (body-timestamp verified live on air).

How the old "impossible" became possible

The previous refusal ("no reserved-page path on Jaguar1, bench-confirmed negative") only ruled out a plain QSEL-beacon send. Golden usbmon dumps of the in-tree rtw88_8821au / rtw88_8814au — captured on the same adapters — recovered the real store bracket:

clear BCN_VALID (W1C) → CR+1 |= BIT0 (SW beacon DMA) → beacon function off
→ bulk [MINIMAL descriptor][MPDU] → poll BCN_VALID → function on → CR+1 &= ~BIT0

The descriptor is the crack: LAST_SEG + OFFSET + PKT_SIZE + QSEL_BEACON only. OWN/FIRST_SEG/BMC/HWSEQ/USE_RATE mark a live TX and make the store silently no-op — while BCN_VALID still latches, deceptively. Enable = the vendor port-0 AP recipe (MSR=AP, DUAL_TSF_RST BIT0, BCN_CTRL 0x18, ResumeTxBeacon) + the 8821A-only "BCN on port 0" 0x454[5] clear.

8814A specifics (own golden dump + live register diff)

  • Valid latch at 0x204[15], with the head held at the BCNQ boundary during the store — pointing it at page 0 (the fw-download bracket's shape) stores the beacon where the TBTT engine never reads
  • Enable bits 0x420[12] (gen1 EN_BCNQ_DL analog) + 0x454[2:0]=0x05
  • Its stored beacon airs with the 802.11 seq pinned at 0 — kernel rtw88 parity (verified against the kernel driver's own beacons on the same chip)
  • Its TBTT counter free-runs across the EN_BCN toggle (a bare TSF shift moves the phase by only the bracket's ~0.8 ms), so the fine steer pulses DUAL_TSF_RST: the grid re-derives absolutely from the shifted TSF — the TBTT ends up TSF-locked and each steer cancels accumulated drift

Steering

Rides #242's steer-then-re-download pattern (the J1 engines also lose the bcn-valid latch on a re-latch). The coarse one-shot interval tweak is inert on the J1 engine (bench: beacon survives, phase never moves), so AdjustBeaconTiming delegates TU-quantized to the fine mechanism (documented: it then also shifts the reported TSF).

Bench (observer = 8811CU, tests/beacon_steer_survival.sh)

Chip Fine Coarse Loss/steer Accuracy
8821AU 11/11 PASS 3/3 PASS 0–1 beacon −3392…−3472 µs for −5000 µs (±40 µs consistency; ~1.6 ms USB systematic a closed loop absorbs)
8814AU 5/5 PASS 3/3 PASS 0–3 beacons (RF-weak unit) −3139…−3424 µs for −5000 µs; absolute TSF-locked re-derive

Beacon body timestamps verified live on both. ctest 17/17.

Docs/comments sweep

All stale "Jaguar2/3-only" / "both HalMAC generations" / "no reserved-page path on Jaguar1" claims for this feature removed across IRtlDevice.h, AdapterCaps.h, docs/time-distribution.md, docs/ap-mode.md, and the beacon test headers. tests/beacon_steer_survival.sh gates its frozen-seq check on seq != 0 (8814A kernel parity) and gains a MASTER_CMD='ssh …' hook for remote masters.

🤖 Generated with Claude Code

…tion

Unlock StartBeacon / AdjustBeaconTiming / AdjustBeaconTimingFine on
Jaguar1 (8812AU/8811AU/8821AU/8814AU), closing the last generation gap
in the hardware-beacon time-distribution feature. The old refusal ("no
reserved-page path, bench-confirmed negative") only ruled out a plain
QSEL-beacon send; golden usbmon dumps of the in-tree rtw88_8821au and
rtw88_8814au (captured on the same adapters) recovered the real store
bracket:

  clear BCN_VALID (W1C) -> CR+1 |= BIT0 (SW beacon DMA) -> beacon
  function off -> bulk [MINIMAL descriptor][MPDU] -> poll BCN_VALID ->
  function on -> CR+1 &= ~BIT0     (0x422[6] stays set throughout)

The store descriptor is the crack: LAST_SEG + OFFSET + PKT_SIZE +
QSEL_BEACON only — OWN/FIRST_SEG/BMC/HWSEQ/USE_RATE mark a live TX and
the store silently no-ops (BCN_VALID still latches, deceptively). The
enable is the vendor port-0 AP recipe (MSR=AP, DUAL_TSF_RST BIT0,
BCN_CTRL 0x18, ResumeTxBeacon — init leaves the StopTxBeacon state) plus
the 8821A "BCN on port 0" 0x454[5] clear.

8814A specifics (own golden dump + register diff against the live
beacons-airing kernel state): valid latch at 0x204[15] with the head
HELD AT THE BCNQ BOUNDARY during the store (page 0 — the fw-download
bracket's shape — stores where the TBTT engine never reads), enable
bits 0x420[12] + 0x454[2:0]=0x05, and its stored beacon airs with the
802.11 seq pinned at 0 (kernel rtw88 parity — verified). Steering: the
8814 TBTT counter free-runs across the EN_BCN toggle, so the fine steer
pulses DUAL_TSF_RST — the grid re-derives absolutely from the shifted
TSF (TSF-locked TBTT; each steer also cancels accumulated drift).

Steering rides the J2 steer-then-re-download pattern (the J1 engines
also drop the bcn-valid latch on a re-latch). The coarse interval tweak
is inert on the J1 engine (bench: beacon survives, phase never moves),
so AdjustBeaconTiming delegates TU-quantized to the fine mechanism.

Bench (observer = 8811CU): 8821AU fine 11/11 + coarse 3/3 PASS,
-3392..-3472 us for a -5000 us request (±40 us consistency, ~1.6 ms USB
systematic a closed loop absorbs), 0-1 beacon lost per steer; 8814AU
fine 5/5 + coarse 3/3 PASS, 0-3 lost (RF-weak unit). Beacon body
timestamp verified live on both (hw_beacon_txtsf now true on J1).

Docs/comments swept for stale "Jaguar2/3-only" claims on this feature;
tests/beacon_steer_survival.sh gates its frozen-seq check on seq!=0 for
the 8814A and grows a MASTER_CMD hook for remote masters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit 3a61c25 into master Jul 11, 2026
15 checks passed
@josephnef josephnef deleted the j1-beacon-tbtt-steering branch July 11, 2026 04:26
josephnef added a commit that referenced this pull request Jul 11, 2026
…k-PTP bridge (#244)

Doc-only pass on `docs/time-distribution.md`, folding in two bench
results that were living only in session notes:

- **Transport floors for the software downlink**: the ~94 µs per-slave
absolute lock is the transport's submit-to-air floor, not a protocol
property — USB ~93 µs RMS vs **PCIe ~12 µs RMS** (robust
outlier-rejecting fit; `tests/pcie_txegress_tx.cpp` +
`tests/txegress_witness.cpp`). A PCIe master lifts the absolute
software-path lock ~8×; the inter-slave difference and the
hardware-beacon path are unaffected (already transport-free).

- **New "Bridging to network PTP (IEEE 1588)" section**: the 8821CE TSF
exposed as a Linux PHC (`tests/pcie_phc/`) and disciplined by `phc2sys`
to the Radxa X4's Intel I226 holds **~290 ns RMS** (the TSF's own 1
µs-quantization floor: uniform ±0.5 µs → 289 ns) with the ~+42 ppm raw
crystal offset servoed out. Combined with the TBTT steering actuator
(#242/#243), that's the complete wired-PTP → Wi-Fi-beacon time bridge:
network PTP disciplines the master's TSF, the TSF-locked hardware beacon
distributes it, slaves inherit the wired timebase at the sub-µs beacon
floor.

Numbers cross-checked against `docs/timing-accuracy.md`; all referenced
tools exist in-tree. (The "Beacon-TBTT steering is Jaguar3-only" wording
was already removed by #242 — repo-wide grep is clean.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
josephnef added a commit that referenced this pull request Jul 11, 2026
…'t corrupt the discipline loop's clock (#245)

Follow-up to #242/#243, driven by bench feedback from the 8821CE AP↔PTP
discipline loop.

## The problem (bench-observed)

`AdjustBeaconTimingFine` necessarily jumps the reported TSF — that's the
steering mechanism (the TBTT re-derives from a shifted TSF). But the
discipline loop's phase estimate is a fit against that same TSF (`ref =
a·tsf + b`), so **every steer corrupts the controller's own sensor**:

- high-authority controllers (PI, ±200 µs clamped steps) chase the
corrupted estimate into a limit cycle
- the working ~60 µs proportional loop is a **sweet spot, not a floor**
— its ~0.5·e steers are just small enough not to wreck the fit; more
authority = more fit corruption = worse
- `SetXtalCap` can't buy a lower steer cadence: the AFE trim moves only
~10 of the ~42 ppm crystal offset

## The fix

`IRtlDevice::PinBeaconTbtt(offset_us)` inverts the oldest finding in
this feature — *a bare TSF write does not move the TBTT* (why `WriteTsf`
failed as an actuator). Do the shift + re-latch, then **immediately
write the TSF back onto its original timeline**: the steered TBTT phase
survives, the clock the loop reads stays continuous.

Semantics are **absolute**: the TBTT fires at `TSF % interval ==
offset`. The controller commands the target offset directly (no step
integration), and a PTP-disciplined TSF drags the pinned TBTT with it
between corrections.

## Bench (8821CE PCIe, `TSF_WATCH=1` probe)

| actuator | TSF discontinuity / steer | on-air phase | beacons lost |
|---|---|---|---|
| `AdjustBeaconTimingFine` | **+4996 µs** (the full steer) | steers |
0–1 |
| `PinBeaconTbtt` | **−9…−11 µs** | pins within ±150 µs of the commanded
offset, both directions | **0** |

~500× less fit disturbance — full control authority is safe again.

## Scope & caveats

- Jaguar2 implementation (the loop hardware); base virtual is a no-op.
The same pattern ports to J3/J1 when hardware is on the bench to
validate.
- USB caveat documented: the restore write costs ~0.5–1 ms there (worse
than a small steer) — `docs/time-distribution.md` documents the
controller-side **steering ledger** as the everywhere-fallback (add
cumulative commanded shifts back onto the raw TSF before fitting).
- `tests/beacon_steer_check.cpp` grows `STEER_MODE=pin` and
`TSF_WATCH=1` (µs-resolution steady_clock-vs-ReadTsf discontinuity
probe).
- Docs record the sweet-spot and XtalCap findings so they aren't
re-discovered.

`ctest` 17/17.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
josephnef added a commit that referenced this pull request Jul 11, 2026
…bution doc consistency (#246)

Follow-up to #245: port the TSF-preserving TBTT pin to J3 and J1, with
per-die bench characterization that reshaped the contract — plus a
latent #243 bug the port flushed out, and a consistency pass on
`docs/time-distribution.md`.

## Pin per generation (all bench-proven this session)

| generation | pin contract | bench |
|---|---|---|
| **Jaguar2** (#245) | full | ~10 µs TSF disturbance (PCIe); loop holds
~±1 µs |
| **Jaguar3** (new, 8822CU) | full | offsets toggle on air as commanded,
SURVIVAL PASS; TSF disturbance −0.5…−1.5 ms (USB restore write) vs the
fine steer's full +4.5 ms jump |
| **Jaguar1** (new, all three dies) | **offset 0 only** | a nonzero pin
*never holds* — the J1 TBTT is **hardware-locked to the TSF grid**
(steps identical for pin(0) and pin(−5000): pure bracket downtime).
Refused honestly; flip side: a J1 master needs no actuator — discipline
the TSF, the TBTT tracks in hardware |

8814A extra: `DUAL_TSF_RST` (its TBTT re-arm) **zeroes the reported
TSF** — the fine steer was silently rewinding its clock by seconds per
call; the 8814 pin path reconstructs the timeline from the host clock
(~1 ms class).

## Latent #243 bug: J1 StartBeacon never ignited standalone

Every prior J1 bench had steers — the first steer was the accidental
igniter. Bench-swept the trigger: not a bare TSF write, not an EN
toggle, not a write-inside-EN-bracket, not ordering — only the complete
steer sequence with a real TSF **value edge** (same-value rewrites
ignite nothing; the same-value-rewrite trap's third appearance).
`StartBeacon` now runs enable → store → internal `PinBeaconTbtt(0)`
igniter (full-extra-period shift: grid unchanged, edge guaranteed).
First beacon ~200 ms after `StartBeacon` on all three dies, standalone.

## Doc consistency pass (`docs/time-distribution.md`)

- stale claims removed: "which J2 does not survive" (false since #242),
"both HalMAC generations" (wire-check is 3-generation now, 8814A seq-0
exception noted)
- generation-scoped: "TBTT deaf to `REG_TSFTR`" and "bare TSF write does
not move the TBTT" → latched J2/J3 engines only (J1 is the opposite
architecture)
- internal contradiction fixed: per-frame `tsfl` is universal (verified
`FrameParser.cpp` fills it on J1); the *slave role* is what's
J2/3-bench-validated
- `SetCcaMode` noted as J2/J3-only (a J1 HW-beacon master still defers
to CSMA)
- per-generation pin table + the held **~±1 µs** AP↔PTP loop result
folded in; all referenced files/symbols verified in-tree

`ctest` 17/17; J1 fine-steer regressions PASS post-change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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