BPI-R64 hardpps soak — patches 1-4 only (old pps_get_ts), tickless, 1Hz 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: ONLY patches 1-4 of the posted series (rodolfo-14-test branch) on 7.3-rc1 — the old pps_get_ts() path, no snapshot ntp_error correction — plus the NTP_PPS Kconfig change to allow tickless — 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.
31481 pulses over 8.7 h.
Steady state (|ntp_error| at pulse, after 30 min):
median 0 ns,
p95 0 ns,
p99 0 ns,
max 0 ns.
Corrected pulse phase: median 0.3 µs,
p95 2.2 µs.
Per-pulse snapshot ntp_error
Pulse arrival phase (corrected ts_real)
Discipline state
Disciplined frequency vs room temperature
Room air temperature (sensor near the board, not on it) against the PLL-disciplined frequency. Context only: the crystal's own temperature is not measured.
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