Skip to content

Jaguar2 fine beacon-TBTT steering via reserved-page re-download — unblock the 8821CE PCIe AP↔PTP loop#242

Merged
josephnef merged 1 commit into
masterfrom
j2-tbtt-steer-redownload
Jul 10, 2026
Merged

Jaguar2 fine beacon-TBTT steering via reserved-page re-download — unblock the 8821CE PCIe AP↔PTP loop#242
josephnef merged 1 commit into
masterfrom
j2-tbtt-steer-redownload

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Closes #241.

What

AdjustBeaconTiming / AdjustBeaconTimingFine now 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: StartBeacon retains 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_BEACON routes 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_INTERVAL latches at a TBTT, so the previous fixed-hold one-shot tweak raced the beacon phase — bench-caught on the 8821CE:

  • write + restore inside one beacon period → silent no-op (returns applied≠0, phase unmoved; 1 of 3 advances)
  • holding past two shortened periods → double shift (1 of 6)

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 / StartBeacon re-zeroes it — the EN_BCN_FUNCTION re-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.

Rig Fine Coarse Loss/steer Accuracy
8812BU USB — the chip the fatal re-latch was proven on 9/9 PASS 7/7 PASS 0–1 beacon fine ~2–3 ms systematic undershoot (USB reg latency; closed loop absorbs), coarse ±150 µs
8821CE PCIe (radxa-x4, vfio) 9/9 PASS incl. 10 s cadence 9/9 PASS incl. back-to-back 0–1 beacon fine −4842…−5047 µs for −5000 µs (~30 µs offset — MMIO is µs-scale), coarse ±150 µs

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

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 josephnef merged commit bb6c27e into master Jul 10, 2026
15 checks passed
@josephnef josephnef deleted the j2-tbtt-steer-redownload branch July 10, 2026 19:50
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>
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.

Jaguar2 fine beacon-TBTT steering via reserved-page re-download — unblock the PCIe AP↔PTP discipline loop on the 8821CE

1 participant