BPI-R64 hardpps soak — tickful (nohz=off), 5s pulses
Cortex-A53 (BPI-R64), 12.5 MHz arch counter, u-blox GNSS timepulse
into pps-gpio, in-kernel hardpps() disciplining
CLOCK_REALTIME with STA_PPSTIME|STA_PPSFREQ|STA_PLL. Kernel: the timekeeping
series (timekeeping branch)
on 7.3-rc1 — including the idle-sleep bound while a slew is in flight and the
proportional ntp_error correction — plus silent bench instrumentation (per-pulse
ring captured in the PPS hardirq, drained to disk once a minute in the pulse's
shadow). No distro userspace: a single static PID 1 sets the date from
NMEA, binds hardpps, and sleeps. All periodic wakeup sources were disabled
(vmstat, runtime-PM autosuspend, fair dl_server, ethernet not probed).
Tickless cells boot NO_HZ_IDLE; tickful cells add nohz=off
on the same kernel binary.
2465 pulses over 3.4 h.
Steady state (|ntp_error| at pulse, after 60 min):
median 1 ns,
p95 16 ns,
p99 1314 ns,
max 105208 ns.
Corrected pulse phase: median 0.4 µs,
p95 3.9 µs.
Per-pulse snapshot ntp_error
Pulse arrival phase (corrected ts_real)
Discipline state
Tickless gap verification
ntp_error drain lifecycle
The proportional drain engages when accumulated ntp_error exceeds one
minute's worth of ±1-dither delivery (~45 ns here), repays over
~10 s re-sized each second, hands back to the dither below threshold,
and aborts mid-second if it overshoots zero.
PPS irq path latency
Counter value is stamped at exception vector entry; this measures entry to the timestamping snapshot in the pps-gpio hardirq — on tickless kernels the deferred timekeeping catch-up runs in between.
Methodology
This run is the test asked for on the list: hardpps on a tickless kernel
driven by a real PPS source, with its real jitter and IRQ latency, and the
CPU genuinely idle between pulses.
- Hardware: Banana Pi R64 (MT7622, 2×Cortex-A53), 12.5 MHz
arch counter, u-blox GNSS HAT. Timepulse period set per cell via UBX-CFG-TP5
(GNSS-locked, aligned to top of second). PPS on a GPIO via
pps-gpio; timestamp taken in the hardirq handler.
- Kernel: the timekeeping series on 7.3-rc1 (unmodified STA_PPSTIME
delivery). While a phase slew or ntp_error drain is active, the timekeeping
CPU's idle sleep is bounded to the current second so the rate is recomputed
on schedule — the fix that makes multi-second pulse intervals stable on
a tickless kernel, part of the series. Tickful cells run the same binary
with
nohz=off.
- Quiescence: no distro userspace — a single static PID 1
binary sleeps between once-a-minute dumps. All periodic wakeup sources found
by tracing were disabled: vmstat interval raised, runtime-PM autosuspend
clamped, per-CPU fair dl_server off, ethernet (mtk_eth_soc + mt7530 1 s
pollers) not probed. The per-minute log dump and its mmc/console I/O are
issued immediately after a pulse edge so they never perturb a
timestamp. The gap histogram (above) verifies the result per cell: on tickless
cells the timekeeper advances in multi-second bursts bounded only by the
pulse and the per-second slew wakeups; on tickful cells it ticks at HZ=250
throughout.
- Instrumentation: at each pulse the hardirq records
snapshot_ntp_error() — the divergence between the ideal
NTP-disciplined time and the sanitized clock_gettime() line that
the series corrects in pps_get_ts() — plus the corrected
ts_real phase against the second boundary. The kernel discipline
state (hardpps corrections, second_overflow decisions, every mult step) is
logged via printk to the kmsg ring and drained to disk per dump.
- Caveats: the ∼30 µs correction stripe in the slew plot is
bench self-noise (a recurring IRQ-latency event, one-sided: latency can only
make a pulse look late), and this kernel still carries the trace printks;
a quiet-kernel confirmation run and the tickful (
nohz=off) A/B
comparison on identical hardware are the follow-ups.
Data
- pulses.csv — per-pulse snapshot
ntp_error, tk ledger, cycle_delta, corrected ts_real phase, err_drain
- adjtimex.csv — PLL state, 1/min
- hist.csv — per-minute log2 histogram of
timekeeping-advance gap sizes + max
- sec_rows.csv — per-second ledger
(time_offset, skew, ntp_error, err_drain)
- drain_counters.csv — drain
engagements / disengagements / overshoot drops, 1/min
- bench.log.gz — the raw on-board log