Jaguar2 fine beacon-TBTT steering via reserved-page re-download — unblock the 8821CE PCIe AP↔PTP loop#242
Merged
Merged
Conversation
The J2 beacon engine loses its bcn-valid latch on any TBTT re-latch and the hardware does not retain the reserved-page bytes — but the driver does: StartBeacon now keeps the beacon MPDU + interval, and both AdjustBeaconTiming / AdjustBeaconTimingFine steer then re-run the reserved-page download to re-arm the latch (bus-neutral: QSEL_BEACON routes to the PCIe BCN ring). At most one skipped beacon per correction. Also fixes a coarse-steer race on both generations: REG_BCN_INTERVAL latches at a TBTT, so the fixed-hold one-shot tweak could silently no-op (write+restore inside one beacon period) or double-shift (two shortened TBTTs before the restore latches) — bench-caught on the 8821CE. The coarse path now phase-aligns off the TSF (restore lands mid-way into the first tweaked interval → exactly one tweaked TBTT) and tracks the TBTT-grid offset that consecutive coarse steers accumulate. Bench: 8812BU USB (the chip the fatal drop was proven on) — survival PASS, 0-1 beacon lost per steer, ~9 ms per fine steer. 8821CE PCIe (radxa-x4) — 0-1 lost, 0-1 ms per steer, fine accuracy -4842..-5047 us for a -5000 us request (~30 us systematic offset), coarse -20 TU steers within ~±150 us incl. back-to-back; hardware seq consecutive across every steer. Unblocks the AP-beacon <-> network-PTP discipline loop on the 8821CE. New: tests/beacon_steer_check.cpp (USB/PCIe master, N repeated steers) + tests/beacon_steer_survival.sh (paired observer, survival + phase-step analysis; MASTER_CMD runs the master on a remote PCIe rig). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jul 11, 2026
…tion (#243) Extends #242's beacon-TBTT steering to **Jaguar1** — `StartBeacon` / `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](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
…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>
josephnef
added a commit
that referenced
this pull request
Jul 11, 2026
…er, roaming robot UEs (+ animation) (#251) Conceptual map of what the time-distribution primitives (#242–#250) build at facility scale — three applications, LTE analogies explicit, every per-link claim linked to its bench numbers. ## `docs/multi-ap-cellular.md` 1. **ICIC over the existing Ethernet backhaul.** The classic Wi-Fi obstacle — cells that can't agree on *when* — is gone: the shared clock makes a slot boundary a real network-wide instant with µs-grade guards. Coordination rides the same wire as PTP; centralized and peer (X2-style) scheduler shapes; orthogonality in time, frequency (~ms retune), or a reuse pattern — a policy, not a driver feature. 2. **Make-before-break handover.** The ~100 ms roam decomposed and dissolved: no time re-acquisition (the network clock is valid across cells), no discovery scan (passive neighbor beacons + ms retune in idle slots), **network-side decision** (every AP hardware-timestamps and RSSI-tags every uplink it hears — LTE measurement reports without client cooperation), handover = a schedule-ownership update between two slots. Honest residue named: security-context motion + the ms retune, both inside a slot guard. 3. **Robots as roaming UEs.** Camp / scheduled DL+UL slots / per-AP TA loop tracking motion / handover as above — ending on the point that matters for robotics: **determinism becomes a schedule property, not a contention statistic.** A boundary paragraph keeps the doc honest as it ages: per-link primitives are bench-proven with numbers in the linked docs; the multi-cell layer is ordinary distributed software on the wired side — nothing blocked on the radio. ## The animation `tools/warehouse_cellular_gif.py` → `docs/img/warehouse_cellular.gif` (house monitor style): a floor plan with two overlapping cells and a driving robot, stacked over the network-wide slot ruler. Three acts: uncoordinated collisions in the overlap → the scheduler splitting the grid orthogonally over the backhaul → the robot crossing cells with RSSI crossover, `>> HO: robot → B` on the bus, and its DL/UL slots recoloring between one slot and the next — against the ghost bar *"ordinary Wi-Fi roam: ~100 ms dead"* vs **"handover gap here: 0 slots"**. Cross-linked from `time-distribution.md` and the rf-primer's where-to-next. Doc + tools only; `ctest` 17/17. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #241.
What
AdjustBeaconTiming/AdjustBeaconTimingFinenow work on Jaguar2. The J2 beacon engine loses its bcn-valid latch on any TBTT re-latch and the hardware does not retain the reserved-page bytes — but the driver does:StartBeaconretains the beacon MPDU + interval, and both actuators steer then re-run the reserved-page beacon download to re-arm the latch. The re-download is bus-neutral (QSEL_BEACONroutes to the PCIe BCN ring), so the same path serves the 8821CE over vfio-pci MMIO.Cost: at most one skipped beacon per correction — at a realistic ~10 s discipline cadence, ~1 lost beacon per 100.
Bonus fix: coarse-steer latch race (J2 and J3)
REG_BCN_INTERVALlatches at a TBTT, so the previous fixed-hold one-shot tweak raced the beacon phase — bench-caught on the 8821CE:The coarse path now phase-aligns off the TSF: land the tweak write clear of the next TBTT, restore mid-way into the first tweaked interval so its closing TBTT latches the restore → exactly one tweaked TBTT, deterministically. Consecutive coarse steers move the TBTT grid off
TSF % period == 0; the actuator tracks that offset (_tbtt_off_us; a fine steer /StartBeaconre-zeroes it — theEN_BCN_FUNCTIONre-latch re-derives the grid from the TSF).Hardware validation
Two-adapter observer benches (
tests/beacon_steer_survival.sh): hardware seq stayed consecutive across every steer — the beacon never stopped airing.Steer duration: ~9 ms over USB, 0–1 ms over PCIe.
ctest: 17/17.With the read/hold half already at ~240 ns RMS vs the I226 PTP reference, this actuator closes the AP-beacon ↔ network-PTP discipline loop on hardware that exists today.
New tests
tests/beacon_steer_check.cpp— steer master:InitWrite+StartBeacon, then N repeated steers at a cadence; opens USB or PCIe (DEVOURER_PCIE_BDF)tests/beacon_steer_survival.sh— pairs it with a phase/seq-logging observer + survival/accuracy analysis;MASTER_CMD='ssh …'runs the master on a remote PCIe rig🤖 Generated with Claude Code