Hardware time distribution: beacon TX-egress TSF + TsfSync, and the measured PTP reality#230
Merged
Merged
Conversation
… and the measured PTP reality
The 802.11 MAC inserts its live TSF into every beacon at the instant of
transmission — a genuine sub-µs hardware TX-egress timestamp (bench-measured
0.289 µs RMS against an independent receiver, vs 117-473 µs for any host-side
"read the clock after send" shortcut). Paired with the per-frame hardware RX
timestamp (RxAtrib.tsfl) it is the primitive for one-way hardware time
distribution — the only sub-µs option on Wi-Fi silicon that exposes no
host-reported TX-egress timestamp.
Library:
- Packet::TxEgressTsf(): parse the sender's hardware egress TSF from a received
beacon / probe response (works against any AP), returned alongside tsfl.
- TsfSync (src/TsfSync.h): running least-squares over {remote egress, local
arrival} pairs -> skew (ppm), offset, and clock translation; feed it a Packet
and beacons are picked automatically. Header-only, chip-neutral.
- AdapterCaps hw_rx_timestamp / hw_beacon_txtsf flags (surfaced in adapter.caps),
hardware-verified per generation (J2/J3 stamp beacons; J1 has no StartBeacon).
- rxdemo emits tx_tsf on beacons; docs/logging.md documents it.
- Headless selftest tsf_sync_math (ctest): skew/offset recovery, clock
translation, the 32-bit TSF wrap, and the beacon pick.
Docs:
- docs/timing-accuracy.md: the measured NTP-vs-TSF comparison, why hardware PTP
can't run on Realtek Wi-Fi (no exposed HW timestamps; TX egress firmware-
blocked), the ath9k exception, the real-PTP-hardware (Intel I226) cross-check,
and the TX-egress-shortcut benchmark, with source links.
- tests/txegress_{witness.cpp,analyze.py,bench.sh}: the single-witness bench
behind the 0.289 us / 117 us / 473 us figures.
ctest 16/16 green; OFF-config builds unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mingw job builds an explicit target list because ctest runs every registered test and a missing binary is a "Not Run" failure. Add the new TsfSyncSelftest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jul 10, 2026
…aintaining the mingw list) (#231) ## Problem The `build-mingw` job can't build the whole tree — `rxdemo` / `txdemo` / `precoder` use POSIX-only APIs (e.g. `fork()`), so it built a **hand-maintained list** of targets. But `ctest` runs *every* registered test, and any self-test binary missing from that list fails as **"Not Run"**. So adding a self-test meant also remembering to edit the workflow — and forgetting broke CI the same way, repeatedly (most recently `TsfSyncSelftest` in #230). ## Fix Add a `selftests` aggregate target that depends on **every `*Selftest` executable**, collected automatically by naming convention via `BUILDSYSTEM_TARGETS` in `CMakeLists.txt`: ```cmake add_custom_target(selftests) get_directory_property(_dvr_targets BUILDSYSTEM_TARGETS) foreach(_dvr_t IN LISTS _dvr_targets) if(_dvr_t MATCHES "Selftest$") add_dependencies(selftests ${_dvr_t}) endif() endforeach() ``` The mingw job now builds `devourer streamtx duplex selftests` instead of the explicit list. A newly-added self-test is picked up with **no workflow edit** and can never be silently skipped into a "Not Run". ## Verified `cmake --build build --target devourer streamtx duplex selftests` builds all 15 `*Selftest` binaries (including the Jaguar2-conditional `TxPktPwrSelftest` and `StreamStdinSelftest`); `ctest` 16/16 green. Naming convention matches every existing self-test target. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
The 802.11 MAC inserts its live TSF into every beacon at the instant of transmission — a genuine sub-µs hardware TX-egress timestamp. Bench-measured at 0.289 µs RMS against an independent receiver, versus 117–473 µs for any host-side "read the clock after
send" shortcut. Paired with the per-frame hardware RX timestamp (RxAtrib.tsfl), it is the primitive for one-way hardware time distribution — the only sub-µs option on Wi-Fi silicon that exposes no host-reported TX-egress timestamp.This lands the library support for that primitive, plus the measured writeup of how devourer's TSF sync compares to NTP/PTP over Wi-Fi.
Library
Packet::TxEgressTsf()— parse the sender's hardware egress TSF from a received beacon / probe response (any AP, not just a devourer transmitter), returned alongsidetsfl.TsfSync(src/TsfSync.h) — a running least-squares fit over{remote egress, local arrival}pairs →SkewPpm(),OffsetUs(), andLocalForRemote()/RemoteForLocal()clock translation. Feed it aPacketand beacons are picked automatically. Header-only, chip-neutral.AdapterCapshw_rx_timestamp/hw_beacon_txtsfflags (surfaced in theadapter.capsevent), hardware-verified per generation (J2/J3 stamp beacons; J1 has noStartBeacon→hw_beacon_txtsf=0).rxdemoemitstx_tsfon beacons;docs/logging.mddocuments it.tsf_sync_math(ctest): skew/offset recovery within 1 ppm, clock translation round-trip, the 32-bit TSF wrap, and the beacon pick.Docs
docs/timing-accuracy.md— measured NTP-vs-TSF (~0.76 ms vs ~0.25 µs, ~3000×), why hardware PTP can't run on Realtek Wi-Fi (stock driver exposes no HW timestamps; TX egress is firmware-blocked), the lone ath9k exception, a real-PTP-hardware cross-check against the on-board Intel I226 (TSF tracks it to ~290 ns — its quantization floor — at +42 ppm), and the TX-egress-shortcut benchmark. Includes source links so the state can be re-checked if it changes.tests/txegress_{witness.cpp,analyze.py,bench.sh}— the single-witness benchmark behind the 0.289 µs / 117 µs / 473 µs figures.Test
ctest16/16 green (addstsf_sync_math). No chip HAL behavior change beyond the two additive caps flags; OFF-config builds unaffected.🤖 Generated with Claude Code