Hardware clock (PHC) and phc2sys#
Why#
Even with a well-configured real-time kernel, a timestamp taken in software (when the kernel processes the packet) accumulates 10 to 100 µs of non-deterministic latency between the signal’s physical arrival on the network cable and the moment the timestamp is actually recorded: PHY reception, DMA to RAM, interrupt handling, network stack traversal. To serve clients with better than ten-microsecond precision, this latency needs to be taken out of the equation, by having the network card itself timestamp the packet, before any software processing.
How#
The PHC (PTP Hardware Clock)#
The Raspberry Pi 5’s network controller exposes a dedicated hardware clock
accessible via /dev/ptp0, the PHC, standardized by IEEE 1588.
Chrony can use it directly to timestamp incoming and outgoing NTP packets
at the silicon level, independent of kernel latency.
# In chrony.conf
hwtimestamp end0 rxfilter all txcomp 0 rxcomp 0 minpoll 0hwtimestamp end0 alone is incomplete: without these explicit
options, chrony has to guess the best settings, which doesn’t always work
reliably. rxfilter all requests hardware timestamping for every
received packet, not just those the controller’s filter recognizes as
PTP (otherwise ordinary NTP traffic wouldn’t get the hardware timestamp
at all). txcomp 0 enables hardware timestamping at transmit completion,
with no additional software compensation (0). rxcomp 0 does the same
on reception: no correction is applied afterward, the raw hardware
timestamp is taken as-is. minpoll 0 imposes no artificial floor on
measurement frequency beyond the standard 2⁰ = 1 second granularity.
phc2sys: keeping the PHC aligned#
The PHC needs to be kept synchronized against the system clock (itself
disciplined by chrony from the PPS). That’s phc2sys’s job, from the
linuxptp package:
cat > /etc/systemd/system/phc2sys-chrony.service <<'EOF'
[Unit]
Description=Synchronize PHC to system clock
After=chrony.service
Wants=chrony.service
[Service]
Type=simple
ExecStart=/usr/sbin/phc2sys -s CLOCK_REALTIME -c /dev/ptp0 -O 0 -R 1 -q
Restart=always
RestartSec=5
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=90
CPUAffinity=1
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now phc2sys-chrony.service-s CLOCK_REALTIME names the system clock (disciplined by chrony from
the PPS) as the reference source for the PHC, -c /dev/ptp0 the target
to align. -O 0 sets the TAI-UTC offset to zero: that’s not an oversight
of leap seconds, chrony (via leap-seconds.list, dedicated chapter)
already handles that aspect, phc2sys doesn’t need to duplicate it.
-q (quiet) suppresses the default logging, otherwise the /var/log
tmpfs (Filesystem chapter) would quickly fill up with one line per
measurement.
Pitfalls to avoid#
Requires=chrony.servicesilently breaksphc2sysin production. A systemdRequires=dependency automatically stops the dependent service on every state transition of its dependency, including a plain restart or a temporary chrony failure. A real incident on this project:phc2syswould silently stop on every chrony cycle, leaving the PHC to drift at 94 µs per second with no visible alert. The fix:Wants=chrony.service(a soft dependency that doesn’t cascade a stop), combined withRestart=alwaysfor resilience against real crashes.
phc2sys’s measurement rate-Rshould be reduced, not maximized. The PHC is a single hardware counter, read and written concurrently by two processes:phc2sys, which writes it to keep it aligned, andchrony, which reads it to timestamp incoming packets. The network driver doesn’t explicitly serialize these accesses. Real-world measurements: at-R 16(linuxptp’s default), only 63% of packets get a hardware timestamp (the rest silently fall back to a less precise software timestamp); at-R 1, that rate climbs to 86%. Reducing the write frequency reduces contention, at the cost of a marginally higher PHC offset, but one that stays well under a microsecond.
- Expecting near-100% hardware TX timestamping. On the Pi 5, the
network driver only sets the hardware flag required for TX timestamping
on packets recognized as PTP by the hardware filter, “normal” NTP
packets don’t get it. This is a documented driver bug
(
raspberrypi/linux, issue #5904), not a configuration mistake: the hardware TX timestamping rate stays near 0% regardless of configuration. The real client-side impact is modest (tens of microseconds at worst, largely filtered out by the client’s own PLL).
Verify#
# The PHC should be detected on the interface
ethtool -T end0
# Should show "PTP Hardware Clock: 0" (or another number, not "none")
# Instantaneous offset between the PHC and the system clock
phc_ctl /dev/ptp0 cmp
# e.g.: "offset from CLOCK_REALTIME is 14ns"A single
phc_ctl cmpreading is an unfiltered snapshot, so it’s occasionally subject to a one-off scheduling spike (a few hundred ns to a few µs). For a reliable diagnostic, repeat the reading several times at real-time priority (chrt -f 99) and take the median, the exact same statistical filtering logic chrony uses for the PPS (Chrony chapter).
# Effective hardware timestamping rate, from chrony's side
chronyc -h /run/chrony/chronyd.sock serverstats | grep -i hardware📚 Going further
Hardware timestamping is standardized by IEEE 1588 (Precision Time Protocol, latest revision 2019). Linux exposes it via the
SO_TIMESTAMPINGsocket option (since kernel 2.6.30): when chrony enableshwtimestamp end0, it asks the kernel, viasetsockopt(), for receive and transmit timestamps to be taken directly in the network card’s silicon rather than in the software stack, typically achieving sub-100ns precision once PHC/chrony contention is under control (measurements published by M. Lichvar, chrony’s maintainer, at Linux.conf.au 2020).