PTPを用いた時刻同期について

概要

PTP(Precision Time Protocol) はNTPに似た方法でより精度の高い時刻同期を提供するしくみで、IEEE 1588で標準化されています。仕様上100ns未満の精度で、Linuxシステムでmsecオーダーの精度を提供します。
通信プロトコルはEtherNet layer 2で定義されており、ネットワークインターフェースによるハードウェアタイムスタンプのサポートが定義されています。ハードウェアタイムスタンプは精度の向上とマイコンのCPUリソースの低減に寄与します。

PTPはNTPほど有名ではありませんが、LANでより精度の高い時刻同期が必要とされる場合は有用です。

Linux環境における設定や動作確認の方法について以下に説明します。

NICのハードウェアサポートの確認

ethtoolの-Tオプションによって確認できます。

# ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
        hardware-transmit     (SOF_TIMESTAMPING_TX_HARDWARE)
        software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)
        hardware-receive      (SOF_TIMESTAMPING_RX_HARDWARE)
        software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
        software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
        hardware-raw-clock    (SOF_TIMESTAMPING_RAW_HARDWARE)
PTP Hardware Clock: 0
Hardware Transmit Timestamp Modes:
        off                   (HWTSTAMP_TX_OFF)
        on                    (HWTSTAMP_TX_ON)
Hardware Receive Filter Modes:
        none                  (HWTSTAMP_FILTER_NONE)
        ptpv1-l4-event        (HWTSTAMP_FILTER_PTP_V1_L4_EVENT)
        ptpv2-event           (HWTSTAMP_FILTER_PTP_V2_EVENT)

ソフトウエアタイムスタンプを使用するには、下記のパラメータが表示されている必要があります。
SOF_TIMESTAMPING_SOFTWARE
SOF_TIMESTAMPING_TX_SOFTWARE
SOF_TIMESTAMPING_RX_SOFTWARE

ハードウエアタイムスタンプを使用するには、下記のパラメータが表示されている必要があります。
SOF_TIMESTAMPING_RAW_HARDWARE
SOF_TIMESTAMPING_TX_HARDWARE
SOF_TIMESTAMPING_RX_HARDWARE

アプリケーションでハードウェアタイムスタンプを取得するにはSO_TIMESTAMPINGソケットオプションを使用します。

PTPの通信

ptp-event(UDP 319), ptp-general(UDP 320)でマスターからマルチキャストパケットが一秒おきに送出される。 スレーブからはptp-eventが1秒おきに送出される。
Firewall等の設定ではこれらのポートへの通信は許可しておく必要があります。
必要に応じて、unicastによる通信を選択することも可能となっています。

Linuxにおける実装

ptpdとlinuxptpの2つの代表的な実装があるので、それぞれについて説明します。

ptpd

ubuntuやdebianなどのディストリビューションで採用されている実装です。
現時点(2020/8/31現在)のバージョン2.3.1はハードウェアタイムスタンプに対応していません。

設定

debianでは/etc/default/ptpdに設定ファイルが置かれています。ここで、コマンドラインオプションの設定を行います。systemdには対応していないようです。
設定の詳細については割愛しますが、この例ではバインドするNICの指定と、master/slaveの指定を行っています。

# /etc/default/ptpd

# Set to "yes" to actually start ptpd automatically
START_DAEMON=yes

# Add command line options for ptpd
PTPD_OPTS="-i eth0 -s"

起動

# ptpd -i eth0 -s -C -D
Runtime debug not enabled. Please compile with RUNTIME_DEBUG
2020-08-31 01:00:07.316787 ptpd2[18637].startup (info)      (___) Configuration OK
2020-08-31 01:00:07.327169 ptpd2[18637].startup (info)      (___) Successfully acquired lock on /var/run/ptpd2.lock
2020-08-31 01:00:07.330414 ptpd2[18637].startup (notice)    (___) PTPDv2 started successfully on eth0 using "slaveonly" preset (PID 18637)
2020-08-31 01:00:07.332890 ptpd2[18637].startup (info)      (___) TimingService.PTP0: PTP service init
2020-08-31 01:00:07.339212 ptpd2[18637].eth0 (info)      (init) Observed_drift loaded from kernel: 40182 ppb
2020-08-31 01:00:07.442412 ptpd2[18637].eth0 (notice)    (lstn_init) Now in state: PTP_LISTENING
2020-08-31 01:00:08.809053 ptpd2[18637].eth0 (info)      (lstn_init) New best master selected: dca632fffe729385(unknown)/1
2020-08-31 01:00:08.813397 ptpd2[18637].eth0 (notice)    (slv) Now in state: PTP_SLAVE, Best master: dca632fffe729385(unknown)/1 (IPv4:192.168.10.193)
2020-08-31 01:00:09.804580 ptpd2[18637].eth0 (notice)    (slv) Received first Sync from Master
2020-08-31 01:00:09.806791 ptpd2[18637].eth0 (critical)  (slv) Offset above 1 second. Clock will step.
2020-08-31 14:36:41.914507 ptpd2[18637].eth0 (warning)   (slv) Stepped the system clock to: 08/31/20 14:36:41.905679118
2020-08-31 14:36:42.021871 ptpd2[18637].eth0 (notice)    (lstn_reset) Now in state: PTP_LISTENING
2020-08-31 14:36:42.905186 ptpd2[18637].eth0 (info)      (lstn_reset) New best master selected: dca632fffe729385(unknown)/1
2020-08-31 14:36:42.909169 ptpd2[18637].eth0 (notice)    (slv) Now in state: PTP_SLAVE, Best master: dca632fffe729385(unknown)/1 (IPv4:192.168.10.193)
2020-08-31 14:36:43.900990 ptpd2[18637].eth0 (notice)    (slv) Received first Sync from Master
2020-08-31 14:36:44.905415 ptpd2[18637].eth0 (notice)    (slv) Received first Delay Response from Master
2020-08-31 14:36:49.432288 ptpd2[18637].eth0 (notice)    (slv) TimingService.PTP0: elected best TimingService
2020-08-31 14:36:49.432591 ptpd2[18637].eth0 (info)      (slv) TimingService.PTP0: acquired clock control

(slv) Now in state: PTP_SLAVE, Best master: dca632fffe729385(unknown)/1 (IPv4:192.168.10.193)
masterが見つかり、自身がslaveとして構成された旨のログ。
この時出力されるmasterのIDの4-5オクテットを除く部分(例だとdca632729385)はmasterのMACアドレスになっている。

時刻が大きくずれているとき、step modeで調整が入る。この時次のメッセージが出る。
(slv) Offset above 1 second. Clock will step.

動作確認

ptpdは特に動作確認を行うツールは用意されていないので、必要に応じ、デバッグレベルを上げるなどしてトラブルシュートする必要があります。

linuxptp

linuxptpパッケージは、ptp4l, phc2sys という2つのプログラムで構成されています。
ptp4l は boundary clock の機能と ordinary clock の両方の機能を提供しています。ハードウエア側にタイムスタンプ機能が用意されていれば、 ptp4lはデフォルトでハードウェアタイムスタンプを使用します。
phc2sys はシステムクロックをPTP時刻に同期させるために使用します。

ptp4lの設定

デフォルトではコンフィグファイルはありません。systemdでは、/etc/systemd/system/multi-user.target.wants/ptp4l.service で次のように設定されていると思うので、ここでコマンドラインオプションを指定するか設定ファイルを変更します。
ExecStart=/usr/sbin/ptp4l -f /etc/linuxptp/ptp4l.conf -i eth0

基本的に変更の必要はありませんが、必要ならmaster/slaveを明示的に指定します。
また、ログをかなり頻繁に出力するので、summary_interval=10などを指定する事を推奨します。(2のべき乗[sec]で指定)

ハードウェアがサポートしていれば、ハードウェアタイムスタンプが利用されます。

ptp4lの起動

# ptp4l -i eth0 -m -s
ptp4l[52620.241]: selected /dev/ptp0 as PTP clock
ptp4l[52620.249]: port 1: INITIALIZING to LISTENING on INIT_COMPLETE
ptp4l[52620.254]: port 0: INITIALIZING to LISTENING on INIT_COMPLETE
ptp4l[52620.296]: port 1: new foreign master dca632.fffe.729385-1
ptp4l[52624.293]: selected best master clock dca632.fffe.729385
ptp4l[52624.293]: foreign master not using PTP timescale
ptp4l[52624.293]: running in a temporal vortex
ptp4l[52624.294]: port 1: LISTENING to UNCALIBRATED on RS_SLAVE
ptp4l[52626.293]: master offset      73201 s0 freq  +49258 path delay     42084
ptp4l[52627.293]: master offset      87920 s1 freq  +63978 path delay     42084
ptp4l[52628.293]: master offset     -19782 s2 freq  +44196 path delay     42084
ptp4l[52628.293]: port 1: UNCALIBRATED to SLAVE on MASTER_CLOCK_SELECTED
ptp4l[52629.293]: master offset        212 s2 freq  +58255 path delay     38436
ptp4l[52630.293]: master offset     -13903 s2 freq  +44204 path delay     42084

master offset 以下には、マスターとの時刻差[nsec]が表示されます。

s0 , s1 , s2 の各表示は、クロックサーボの状態を表わしています。それぞれ s0 はロック解除状態を、 s1 はクロックステップを、 s2 はロック済み状態を表わしています。サーボがロック済みの状態 ( s2 ) にある場合、 pi_offset_const オプションが負の値に設定されていると、クロックをステップさせる (大きく変更する) ことは行なわず、徐々に調整するslew modeになります。

freq で示されている値は、クロックの周波数調整値 (単位はppbで、10億あたりの値)を表わしています。

path delay には、マスターから送信された同期メッセージの見積もり遅延時間[nsec]を表わしています。

PTP のマスタークロックと同期が成功すると、クロックサーボのステータスがs2となり、次のメッセージが出力されます。
ptp4l[52628.293]: port 1: UNCALIBRATED to SLAVE on MASTER_CLOCK_SELECTED

phc2sysの設定

ptp4lを動作させただけでは、システムクロックが同期されることはありません。この為にはphc2sysの設定が必要となります。
システムクロックのソースとしては、ptp4lのソフトウェアタイムスタンプ、NICのハードウェアタイムスタンプを選択できます。
自ホストに精度の高いクロック源が接続されている場合、クロックソースを指定することもできます。

phc2sysはログを頻繁に出力するので-u 1000などを設定することを推奨します。

典型的な設定パターンを以下に示します。

phc2sysの起動

# phc2sys -a -r -m
phc2sys[97961.628]: reconfiguring after port state change
phc2sys[97961.635]: selecting CLOCK_REALTIME for synchronization
phc2sys[97961.637]: selecting eth0 as the master clock
phc2sys[97961.638]: CLOCK_REALTIME phc offset -51684648605282 s0 freq -100000000 delay   2109
phc2sys[97962.650]: CLOCK_REALTIME phc offset -51684556618893 s1 freq  +51714 delay   2154
phc2sys[97963.650]: CLOCK_REALTIME phc offset     15175 s2 freq  +66889 delay   1916

クロックサーボのステータスはptp4lと同じです。
例のように、時刻が大きくずれている場合、起動時にstep modeでの調整が行われます。

動作確認

ptp4lのマネージメントを行うプログラムとしてpmcコマンドが提供されています。これを用いて状態を確認することができます。

# pmc -u -b0 'GET CURRENT_DATA_SET'
sending: GET CURRENT_DATA_SET
        0479b7.fffe.b16674-0 seq 0 RESPONSE MANAGEMENT CURRENT_DATA_SET
                stepsRemoved     1
                offsetFromMaster 3538.0
                meanPathDelay    42642.0

# pmc -u -b0 'GET TIME_STATUS_NP'
sending: GET TIME_STATUS_NP
        0479b7.fffe.b16674-0 seq 0 RESPONSE MANAGEMENT TIME_STATUS_NP
                master_offset              -4931
                ingress_time               1598813396899250986
                cumulativeScaledRateOffset +0.000000000
                scaledLastGmPhaseChange    0
                gmTimeBaseIndicator        0
                lastGmPhaseChange          0x0000'0000000000000000.0000
                gmPresent                  true
                gmIdentity                 dca632.fffe.729385