1、PTP简介
PTP(precision time protocol)精确时间协议,是一种时间同步的协议,对应 IEEE 1588 标准,是基于网络数据包的一种时间同步协议,1588v2的同步精度可以达到ns级,但1588协议对硬件有依赖。
2、PTP原理
时间同步的核心就是不同时钟源之间的“对齐”,那么多个时钟源实体之间,要解决两个问题:
a、主从关系:两个设备或端口,谁作为基准方,谁作为跟随方。
b、对齐跟踪:如何实现从端RTC对齐主端RTC。
1588协议针对两个问题分别给出了解决方案;实际上1588中存在两套协议,一套是控制面协议,用于确定主从关系,一套是业务面协议,用于主从之间时间对齐。
控制面协议采用基于STP协议(生成树算法)的自动选源协议,被称为BMCA。
具体实现是依赖Announce报文携带信息,选出根节点和设备上的根端口,从而生成树状的结构,在我们的应用场景中这不是重点。
数据面协议采用双向打戳定时协议, 1588的理论依据需要来回路径严格对称,协议默认收发物理链路上的时延相等。下面重点介绍双向打戳的技术原理。
图1:delay one step
t1:master向slave发送sync报文的时间,包含在sync报文内,slave通过解析sync报文得到。
t2:slave接收到sync报文的时间,slave的硬件解析到sync报文时记录时间戳,并传递给slave。
t3:slave向master发送delay_req报文,slave的硬件解析到报文的发送,记录发送时间戳并传递给slave
t4:master获取到slave发送过来的delay_req报文,master硬件解析到delay_req报文,记录到时间戳,并传递给master,master随后将t4放在Delay_resp中传递给slave,slave通过解析delay_resp报文获取到t4
slave获取到t1,t2,t3,t4后通过下面的公式,即可计算出与master时间之间的偏移,从而调整时间。
从上面的公式可以看出,master与slave之间的传输时延越稳定,则算出的offset越准确,时间同步也越精确。理想的时间戳如下方左图,精准地在报文在物理链路上收发的时刻打上时间戳,但是在实际中,会因为各种因素带来误差。
3、软件PTP和硬件PTP
软件PTP是在报文发送和接收时,在协议栈使用系统时间打时间戳,由于软件处理与物理链路实际发送存在gap,且软件处理的时延不可预测,必然带来时间同步抖动的问题,精度难以保障。
硬件PTP是在报文发送和接收时,由MAC/PHY内部的PHC时钟对报文打时间戳,精度可以达到1us以下。
图2:软件时间戳和硬件时间戳(图片来自网络)
4、以phy驱动说明如何支持硬件PTP
4.1 phy ptp驱动需求分析
由上面对协议的介绍可知,Phy PTP驱动的核心是实现对1588报文识别并记录时间戳。因此驱动首先要实现的功能是时间戳相关的功能。
- 时间戳功能:根据Linux社区文档(linux/kernel_doc/networking/timestamping)指导,phy支持时间戳功能的核心数据结构是mii_timestamper,如下:
struct mii_timestamper {bool (*rxtstamp)(struct mii_timestamper *mii_ts,struct sk_buff *skb, int type);void (*txtstamp)(struct mii_timestamper *mii_ts,struct sk_buff *skb, int type);int (*hwtstamp)(struct mii_timestamper *mii_ts,struct ifreq *ifreq);void (*link_state)(struct mii_timestamper *mii_ts,struct phy_device *phydev);int (*ts_info)(struct mii_timestamper *mii_ts,struct ethtool_ts_info *ts_info);struct device *device;
};
rxtstamp:接收方向的报文获取时间戳
txtstamp:发送方向的报文获取时间戳
hwtstamp:phy硬件PTP配置
ts_info:上报phy硬件PTP的能力,对接ethtool。
4.2 PHC时钟功能
在进行时钟同步时,必然少不了要获取并比对时间,phy硬件要支持打时间戳,内部有一个PHC时钟,需要使能PHC时钟,并提供时钟相关接口,根据社区文档(Documentation\driver-api\ptp.rst),驱动要实现的核心数据结构是ptp_clock_info
struct ptp_clock_info {struct module *owner;char name[16];s32 max_adj;int n_alarm;int n_ext_ts;int n_per_out;int n_pins;int pps;struct ptp_pin_desc *pin_config;int (*adjfine)(struct ptp_clock_info *ptp, long scaled_ppm);int (*adjfreq)(struct ptp_clock_info *ptp, s32 delta);int (*adjphase)(struct ptp_clock_info *ptp, s32 phase);int (*adjtime)(struct ptp_clock_info *ptp, s64 delta);int (*gettime64)(struct ptp_clock_info *ptp, struct timespec64 *ts);int (*gettimex64)(struct ptp_clock_info *ptp, struct timespec64 *ts,struct ptp_system_timestamp *sts);int (*getcrosststamp)(struct ptp_clock_info *ptp,struct system_device_crosststamp *cts);int (*settime64)(struct ptp_clock_info *p, const struct timespec64 *ts);int (*enable)(struct ptp_clock_info *ptp,struct ptp_clock_request *request, int on);int (*verify)(struct ptp_clock_info *ptp, unsigned int pin,enum ptp_pin_function func, unsigned int chan);long (*do_aux_work)(struct ptp_clock_info *ptp);
};
在这里我们看到了很多和系统时钟类似的接口,例如adjtime,adjfreq,gettime等,在内核态注册PHC时钟后,用户态调用clock_gettime类似的接口就可以获取到相关的时间或是调整时间。
至于这些接口的具体实现可参考Linux社区中的代码,如果大家细心去看就会发现很多厂家的驱动里面通过timecounter来维护时间,是不是和前面的内容关联上了呢。
4.3 PHC时间与系统时间的同步
仅仅在PHC时钟之间完成同步是不够的,毕竟对内核或者用户态应用来说可便宜地获取时间的方法是获取系统时间,那么PHC时钟需要实现与系统时钟的同步。Phy PTP与gmac驱动及Linux ptp应用的对接:master设备需要同步系统时间到phy的phc时钟,slave设备需要同步phc时钟到系统时间,高精度的方法为使用pps定时校准,可参考社区文档(\Documentation\driver-api\pps.rst)。
4.4 PPS时间同步
//todo
五、发送和接收流程中的时间戳处理
发送流程中时间戳获取流程:
接收流程中时间戳获取流程:
由RX和TX流程可知,TX方向是软件先发送1588报文,需要等待获取时间戳,RX方向是软件已收到时间戳,需等待相匹配的1588报文,时间戳与报文需一一对应,严格匹配。
具体实现略过。事实上不同厂家的gamc/phy对协议和时间戳支持的程度不同,具体的获取时间戳的方式也不一样,所以需要具体情况具体处理。