欢迎淘宝搜索 飞灵科技,我司相关新产品陆续上线

在一些应用中我们需要获取网路报文进出MAC的精准的时间戳。相比较于软件时间戳,硬件时间戳排除了系统软件引起的延时和抖动。如下图所示意:

 下面我们使用北京飞灵科技有限公司开发的TSync时钟同步开发板来测试 .

查看网卡捕获时间戳的能力

进入Linux命令行后,我们可以使用ethtool -T eth0 来查看对应的MAC捕获时间戳的能力。

root@TSync:~# 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)
        all                   (HWTSTAMP_FILTER_ALL)


在Capabilities 字段里, 我们可以知道这个MAC 支持捕获那些时间戳的类型。

  • SOF_TIMESTAMPING_TX_HARDWARE 支持捕获硬件发送时间戳。
  • SOF_TIMESTAMPING_RX_HARDWARE 支持捕获硬件接收时间戳。
  • SOF_TIMESTAMPING_TX_SOFTWARE 支持捕获软件发送时间戳。
  • SOF_TIMESTAMPING_RX_SOFTWARE 支持捕获软件接收时间戳。

捕获发送时间戳时,可配置为两种模式:

  • HWTSTAMP_TX_OFF 表示网卡发送的报文都不捕获硬件时间戳。
  • HWTSTAMP_TX_ON 表示网卡对所有接收到的报文都捕获硬件时间戳。

捕获接收报文的时间戳时,有几种过滤器,可以选择捕获特定数据流的时间戳:

  • HWTSTAMP_FILTER_NONE: 所有收到的数据流都不捕获时间戳。
  • HWTSTAMP_FILTER_ALL:所有收到的数据流都捕获时间戳。

除了以上两个过滤器外,一些网卡可能还有以下几种过滤器用于捕获PTP协议的报文时间戳。

  • ptpv1-l4-sync            (HWTSTAMP_FILTER_PTP_V1_L4_SYNC)
  • ptpv1-l4-delay-req    (HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ)
  • ptpv2-l4-sync            (HWTSTAMP_FILTER_PTP_V2_L4_SYNC)
  • ptpv2-l4-delay-req    (HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ)
  • ptpv2-l2-sync            (HWTSTAMP_FILTER_PTP_V2_L2_SYNC)
  • ptpv2-l2-delay-req    (HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ)
  • ptpv2-event              (HWTSTAMP_FILTER_PTP_V2_EVENT)
  • ptpv2-sync               (HWTSTAMP_FILTER_PTP_V2_SYNC)
  • ptpv2-delay-req       (HWTSTAMP_FILTER_PTP_V2_DELAY_REQ)

配置捕获硬件时间戳功能

要捕获硬件时间戳,需要配置MAC上的PHC硬件和配置socket 生成时间戳的类型。

使能PHC时间戳功能

通过SIOCSHWTSTAMP ioctl命令配置硬件发送时间戳捕获模式和 硬件接收时间戳的过滤器。代码如下:

struct ifreq hwtstamp;
struct hwtstamp_config hwconfig;
memset(&hwtstamp, 0, sizeof(hwtstamp)); 
memset(&hwconfig, 0, sizeof(hwconfig)); 
hwtstamp.ifr_name = "eth1"; 
hwtstamp.ifr_data = (void*)&hwconfig;
// 设置网卡捕获所有发送报文的硬件时间戳。
hwconfig.tx_type = HWTSTAMP_TX_ON;
// 设置网卡捕获所有接收到的报文的时间戳。 
hwconfig.rx_filter = HWTSTAMP_FILTER_ALL;
ioctl(sock, SIOCSHWTSTAMP, &hwtstamp)

配置socket 捕获时间戳的类型

int so_timestamping_flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE;
setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING,&so_timestamping_flags, sizeof(so_timestamping_flags)) < 0)

获取发送或接收报文的时间戳

在发送完或接收完报文后,时间戳被记录到一个 cmsg_level为SOL_SOCKET, cmsg_type 为SCM_TIMESTAMPING, data为 struct scm_timestamping 的一个control message中。这个cmsg可以通过recvmsg() 接口读取。

对于发送报文的时间戳,是放在socket的error 队列中,使用下面函数取到msg中。

ssize_t recv_len = recvmsg(sock, &msg, MSG_ERRQUEUE);

对于接收到的报文的时间戳,使用下面函数取到msg中。

ssize_t recv_len = recvmsg(sock, &msg, 0);

时间戳信息以struct scm_timestamping结构保存在cmsg消息的data段中。结构里包含三个时间戳:

struct scm_timestamping { 
    struct timespec ts[3]; 
};
  • ts[0]里存放的时software 时间戳,如果使能的话有效,否则为0。
  • ts[1]里存放的是一个被转化为系统时间的硬件时间戳,这个硬件时间类似一个影子时钟,用来系统时间和MAC上phc 时钟同步。
  • ts[2]里存放的便是我们想要获取的硬件时间戳。

使用下面代码,即可获提取到我们想要的时间戳。

struct cmsghdr *cmsg = NULL;
struct scm_timestamping hw_ts;
struct timespec ts;
for(cmsg=CMSG_FIRSTHDR(&msg);cmsg!=NULL;cmsg=CMSG_NXTHDR(&msg, cmsg)) {
    if(cmsg->cmsg_level==SOL_SOCKET && cmsg->cmsg_type==SO_TIMESTAMPING) {
        hw_ts=*((struct scm_timestamping *)CMSG_DATA(cmsg));
        fprintf(stdout,"HW: %lu s, %lu ns\n",hw_ts.ts[2].tv_sec,hw_ts.ts[2].tv_nsec);
        fprintf(stdout,"ts: %lu s, %lu ns\n",hw_ts.ts[1].tv_sec,hw_ts.ts[1].tv_nsec);
        fprintf(stdout,"SW: %lu s, %lu ns\n",hw_ts.ts[0].tv_sec,hw_ts.ts[0].tv_nsec);
        memcpy(&ts, &scm_ts.ts[2], sizeof(struct timespec)); // 拷贝捕获的硬件时钟到timespec 结构中。 
    }
}

代码测试

为了验证获取的时间戳的正确性,我们使用两块飞灵科技的TSync时钟同步开发板作为报文的发送端和接收端,并分别在两个板子上捕获发送和接收的硬件时间戳。

 为了使发送端和接收端的时间一致,我们首先让他们分别和GNSS 卫星时间同步。板子上电后,点击下图按钮,打开GNSS同步。

 将两个板子分别通过PTP端口连接到路由器。 在串口控制台运行dhcp获取IP地址。分别拷贝stamp_send.c 和 stamp_recv.c 文件到两块板子上,并分别如下编译:

gcc stamp_recv.c -o stamp_recv
gcc stamp_send.c -o stamp_send

在接收端运行 ./stamp_recv eth0

root@TSync:~/stamp_test# ./stamp_recv eth0
source IP: 192.168.1.78
Test started.
Recv pakage: hello world 0
HW: 1639236711 s, 631040873 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236711 s, 631040873 ns

Recv pakage: hello world 1
HW: 1639236712 s, 632989572 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236712 s, 632989572 ns

Recv pakage: hello world 2
HW: 1639236713 s, 635084458 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236713 s, 635084458 ns

Recv pakage: hello world 3
HW: 1639236714 s, 637115333 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave recv timestamp: 1639236714 s, 637115333 ns

在发送端运行 “./stamp_send eth0 192.168.1.78”,192.168.1.78 为接收端的IP 地址。

root@TSync:~/stamp_test# ./stamp_send eth0 192.168.1.78
source IP: 192.168.1.79
Test started.
Sent packet number (0/10) : hello world 0
HW: 1639236711 s, 631013470 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236711 s, 631013470 ns

Sent packet number (1/10) : hello world 1
HW: 1639236712 s, 632962036 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236712 s, 632962036 ns

Sent packet number (2/10) : hello world 2
HW: 1639236713 s, 635057007 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236713 s, 635057007 ns

Sent packet number (3/10) : hello world 3
HW: 1639236714 s, 637087896 ns
ts[1]: 0 s, 0 ns
SW: 0 s, 0 ns
Hardwave send timestamp: 1639236714 s, 637087896 ns

从发送端和接收端的log可以看到, 报文的接收时间戳和发送时间戳之差,就是路由器的链路延时。

测试源码文件“硬件时间戳使用示例代码.zip”可以从飞灵科技的Wiki上下载。资料下载 - 飞灵科技-文档 :

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐