Skip to content

5. Configure LinuxPTP

Josh Blake edited this page Jan 6, 2026 · 32 revisions

LinuxPTP is a group of applications designed to implement the Precision Time Protocol (PTP) IEEE 1588 over a local network. It delivers sub-microsecond timing accuracy over Layer 2 or Layer 3 infrastructure. With adequate switch hardware, you can have syncronisation in the 10s of nanosecond range. The RPi 5 (and RPi 4) both feature the Broadcom BCM54213PE PTP enabled NICs, although only the CM5/4 breaks out the external timepulse pin.

There are a few ways that LinuxPTP can integrate into the timeserver. See the below diagrams for setup permutations. Options 2-4 are preferred.

The below configuration steps will create a topology that mirrors Option 2. If you progress to the Alternative options, you will end up with Option 3 or Option 4.

I am currently running Option 4.

1. Use Software Based Timestamping

This option is useful if you are running a RPi 5/4 or are not using the SYNC_OUT pin on your CM5/4. I will not cover this setup here as there are other examples online that use this.

flowchart TD
    A[Uputronics HAT]-->|"/dev/ttyAMA0"|C["GPSD"]
    C-->|"/run/chrony.ttyAMA0.sock"|E["Chrony Synchronisation"]
    F["PPS"]-->|"/dev/pps0"|E
    A-->F
    E-->H["NTP Service"]
    I["ptp4l"]-->J["eth0 Timestamp"]
    K["phc2sys"]-->J
    E-->K
    J-->L["PTP Service"]
Loading

2. Use Hardware Based Timestamping with Chrony + GPSD

This option uses GPSD + Chrony to derive the current time, and uses the SYNC_OUT pin exposed on the CM5 board to timestamp the NIC in hardware rather than software.

flowchart TD
    A[Uputronics HAT]-->|"/dev/ttyAMA0"|B["GPSD"]
    B-->|"/run/chrony.ttyAMA0.sock"|C["Chrony Synchronisation"]
    A-->D["PPS"]
    D-->|"/dev/pps0"|C
    D-->|"SYNC_OUT"|E["ts2phc"]
    E-->F["eth0 Timestamp"]
    G["ptp4l"]-->F
    C-->H["NTP Service"]
    C-->G
    F-->I["PTP Service"]
Loading

3. Use Hardware Based Timestamping with Chrony + GPSD and ts2phc

This option uses GPSD + Chrony to derive the current time which is served over NTP. It uses ts2phc to read a copied version of the NMEA sentence and the SYNC_OUT pin exposed on the CM5 board to parallel the PPS sync. NIC time will be slightly different than system time although largely more stable as it relies on the NIC clock (also hardware based) rather than the system clock (kernel interrupt based).

flowchart TD
    A[Uputronics HAT]-->|"/dev/ttyAMA0"|B["GPSD"]
    B-->|"/run/chrony.ttyAMA0.sock"|C["Chrony Synchronisation"]
    A-->D["PPS"]
    D-->|"SYNC_OUT"|F["ts2phc"]
    D-->|"/dev/pps0"|C
    B-->E["gpspipe"]
    E-->|"/run/gpsd.ttyAMA0.sock"|F
    F-->G["eth0 Timestamp"]
    H["ptp4l"]-->G
    G-->I["phc2sys"]
    I-.->|"/run/chrony.PTP0.sock"|C
    C-->J["NTP Service"]
    G-->K["PTP Service"]
Loading

4. Use Hardware Based Timestamping with Chrony and ts2phc (Current)

This option uses ts2phc to supply both time and PPS data to Chrony. It only uses the PPS and GPSD SOCK as a sanity check. In my experience there is a 40us delay on the kernel time when using GPSD SOCK and PPS time compared to the NIC time. This option is simpler as Chrony serves the time found on the NIC. This has the advantage of avoiding kernel-level precision (18ns) errors, and keeps both NTP time and PTP time uniform.

flowchart TD
    A[Uputronics HAT]-->B["GPSD"]
    B-->|"gpspipe Socket"|C["ts2phc"]
    B-->|"GPSD SOCK"|E["Chrony Synchronisation"]
    C-->|"PHC, prefer"|E
    A-->D["PPS"]
    D-->|"SYNC_OUT"|C
    D-->|"/dev/pps0"|E
    E-->F["NTP Service"]
    C-->G["PTP Service"]
    H["PTP4l"]-->G
Loading

Configure ptp4l.conf

You may want to back up the default configuration here for future reference.

Adjust your configuration file, save and exit:

sudo nano /etc/linuxptp/ptp4l.conf

[global]
summary_interval	10
use_syslog		0
verbose			1

# Increase priority to allow this server to be chosen as the PTP grandmaster.
priority1		0
priority2		0
clock_servo		linreg
serverOnly		1
time_stamping		hardware
ptp_minor_version	0
tx_timestamp_timeout	100
dscp_event		46
dscp_general		46

[eth0]
#network_transport	UDPv4
delay_mechanism		Auto
network_transport	L2

Don't forget to enable ptp4l for eth0:

sudo systemctl enable ptp4l@eth0

And restart ptp4l

sudo systemctl restart ptp4l@eth0

The above configuration is important. It uses L2 (MAC based) PTP rather than UDP based PTP. You can change this if you prefer using L3 based PTP. It also compels ptp4l to use PTPv1 to communicate with the NIC. Due to quirks with the Broadcom NIC, if you do not include the ptp_minor_version directive, you will run into timeout issues and all sorts of headache. Again, this prioritises packets with a DSCP value of 46 (high priority, low latency).

Create/Edit ptp4l systemd unit

You may also want to increase the priority at which ptp4l runs on your system. I have not modified mine to restrict the process to a particular CPU core, but I have changed the scheduler and priority of the process, and changed the startup sequence. This compels ptp4l to operate with sub-hardware level interrupt priority.

Create a systemd unit file if it does not already exist

sudo nano /etc/systemd/system/ptp4l@.service

[Unit]
Description=Precision Time Protocol service
Documentation=man:ptp4l
After=chronyd.service coalesce@.service

[Service]
Type=simple
ExecStart=/usr/local/sbin/ptp4l -A -i %i -f /etc/linuxptp/ptp4l.conf
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=40
Restart=on-failure

[Install]
WantedBy=multi-user.target

Don't forget to reload your unit file, enable ts2phc@, and start the service.

sudo systemctl daemon-reload

sudo systemctl enable ptp4l@eth0

sudo systemctl restart ptp4l@eth0

Configure ts2phc

Here ts2phc is used to timestamp the clock information on the NIC. It uses the SYNC_OUT pin that is exposed via the CM5 connected directly to the PPS out signal from the Uputronics GPS HAT. The internal CM5 Pulldown resistor is enabled here (specified through the overlay in config.txt), however the pulldown strength is notoriously weak. I will consider adding a 10k pulldown resistor directly to this line off the HAT however I have not yet attempted this. So far, I have not had any issues with the internal pulldown resistor.

You may want to back up the default configuration here for future reference.

Create a configuration file, save and exit:

sudo nano /etc/linuxptp/ts2phc.conf

[global]
ts2phc.tod_source	generic
leapfile		/usr/share/zoneinfo/leap-seconds.list
logging_level		7
ts2phc.pulsewidth	100000000

[eth0]
ts2phc.pin_index	0

Create/Edit ts2phc systemd unit

sudo nano /etc/systemd/system/ts2phc@.service

[Unit]
Description=Synchronize PTP Hardware Clock from System Time
Documentation=man:ts2phc
Requires=ptp4l@.service
After=ptp4l@.service

[Service]
Type=simple
ExecStart=/usr/sbin/ts2phc -c %i -f /etc/linuxptp/ts2phc.conf
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=40

[Install]
WantedBy=multi-user.target

Don't forget to reload your unit file, enable ts2phc@, and start the service. You must have ptp4l@ running in order for this service to start.

sudo systemctl daemon-reload

sudo systemctl enable ts2phc@eth0

sudo systemctl restart ts2phc@eth0

If the NIC is receiving the PPS pulse, you should see the timestamp converge to within nanoseconds:

sudo systemctl status ts2phc@eth0

Screenshot 2025-11-10 at 22 54 54

(Alternative) Use ts2phc with Direct Timestamping

This next section is an alternate configuration that allows ts2phc to read the NMEA data directly from GPSD or directly from the GPS serial device. There are several configuration permutations as outlined above.

In the previous configuration, Chrony supplies timing information to the PHY and the timestamp is applied via a PPS pulse. If there is a lapse in NMEA information, such as a loss of 3D fix, the NMEA sentence that ts2phc reads is incomplete and will fail to sync. This is a limitation in ts2phc; that it relies on complete RMC sentences only. The most robust correction for this is using GPSD as a serial source directly to ts2phc as it can fill in the blanks and supply pseudo-complete RMC sentences. For example, if you are using a u-blox device and enable only the NAV-PVT message, GPSD will convert this into a valid NMEA RMC sentence for gpspipe.

Allow ts2phc to Copy GPS Serial Data (preferred)

Allow ts2phc to read GPSD serialised data. This allows GPSD to receive and parse the GPS data. It is then relayed along a virtual serial device to ts2phc. While this level of indirection introduces delays, it is compensated for by the PPS pulse that ultimately syncronises the timestamp. The gpsd-tools package contains gpspipe which, aptly named, pipes the serial data received by GPSD somewhere. Here it is piped to a low latency FIFO socket that mimics a serial line device that ts2phc can interact with.

Create a gpspipe@ systemd service file, save and exit:

sudo nano /etc/systemd/system/gpspipe@.service

[Unit]
Description=gpspipe Service for %i
Requires=gpspipe@.socket
Requires=gpsd.service

[Service]
ExecStart=/usr/bin/gpspipe -B -r -o /run/gpsd.%i.sock
CPUAffinity=3
CPUSchedulingPolicy=rr
CPUSchedulingPriority=20
Restart=on-failure

[Install]
WantedBy=multi-user.target

Create a gpspipe@ systemd socket file, save and exit:

sudo nano /etc/systemd/system/gpspipe@.socket

[Unit]
Description=GPSD Serial Socket for %i

[Socket]
ListenFIFO=/run/gpsd.%i.sock
NoDelay=true
AmbientCapabilities=CAP_NET_ADMIN
Priority=7

[Install]
WantedBy=sockets.target

Reload your systemd unit files, enable and start your gpspipe socket and service:

sudo systemctl daemon-reload

sudo systemctl enable gpspipe@ttyAMA0.socket gpspipe@ttyAMA0.service

sudo systemctl start gpspipe@ttyAMA0.socket gpspipe@ttyAMA0.service

Edit ts2phc.conf, save and exit:

sudo nano /etc/linuxptp/ts2phc.conf

[global]
ts2phc.tod_source       nmea
ts2phc.nmea_serialport  /run/gpsd.ttyAMA0.sock
ts2phc.nmea_baudrate    460800
leapfile                /usr/share/zoneinfo/leap-seconds.list
logging_level           7
ts2phc.pulsewidth      100000000

[eth0]
ts2phc.pin_index        0
ts2phc.channel          0
ts2phc.extts_polarity   rising

Restart ts2phc:

sudo systemctl restart ts2phc@eth0

To take advantage of a topology like Example 4 (currently used) make sure you have the following lines in your chrony.conf file:

#SOCK GPS with PPS
refclock SOCK /run/chrony.ttyAMA0.sock refid SOCK precision 1e-9 offset 0 poll 0 maxsamples 10 noselect
refclock PPS /dev/pps0 refid PPS precision 1e-9 offset 0 poll 0 maxsamples 10 lock SOCK noselect

#PHC eth0 Refclock
refclock PHC /dev/ptp0:extpps:pin=0 tai refid PHC precision 1e-9 poll 0 rate 1 width 0.1 maxsamples 10 prefer

Restart relevant services (note that the order of service restarts is important here; GPSD requires chrony to create a socket for time data, and gpspipe requires gpsd to be running. ts2phc requires gpspipe to receive NMEA timing information):

sudo systemctl restart chrony gpsd setpps gpspipe@ttyAMA0 ts2phc@eth0

(Not Recommended) Allow ts2phc to Read GPS Serial Data Directly

Allow ts2phc to read GPS serial data directly from the device; this is at the expense of GPSD. You will not be able to run both concurrently. This removes the indirection between GPSD and the serial device. Note that your GNSS device must serve a valid RMC message for this to work!

Disable GPSD:

sudo systemctl stop gpsd

sudo systemctl disable gpsd

Edit ts2phc.conf, save and exit:

sudo nano /etc/linuxptp/ts2phc.conf

[global]
ts2phc.tod_source       nmea
ts2phc.nmea_serialport  /dev/ttyAMA0
ts2phc.nmea_baudrate    460800
leapfile                /usr/share/zoneinfo/leap-seconds.list
logging_level           7
ts2phc.pulsewidth      100000000

[eth0]
ts2phc.pin_index        0
ts2phc.channel          0
ts2phc.extts_polarity   rising

Restart ts2phc:

sudo systemctl restart ts2phc@eth0