PTP协议精讲(3.8):硬件时间戳详解——纳秒级精度的魔法

3.8 硬件时间戳详解:纳秒级精度的魔法

时间戳精度的重要性

在PTP同步中,精度取决于时间戳精度:

复制代码
时间戳精度与同步精度:

软件时间戳:
- 在内核协议栈打时间戳
- 经过网络协议栈处理
- 延迟不确定(调度、中断等)
- 精度:微秒级(100-1000 ns误差)
- 适用:普通场景

硬件时间戳:
- 在网卡PHY打时间戳
- 最接近物理传输
- 延迟固定且极小
- 精度:纳秒级(<10 ns误差)
- 适用:工业、电信等高精度场景

差距有多大?
- 软件:±1微秒 → 同步精度±10微秒
- 硬件:±10纳秒 → 同步精度±100纳秒
- 差100倍!

时间戳类型

timestamp_type枚举

c 复制代码
/* util.h中定义 */

enum timestamp_type {
    TS_SOFTWARE,     /* 软件时间戳 */
    TS_HARDWARE,     /* 硬件时间戳 */
    TS_LEGACY_HW,    /* 旧版硬件时间戳 */
    TS_ONESTEP,      /* 单步时钟(Sync) */
    TS_P2P1STEP,     /* 单步时钟(P2P) */
};

五种时间戳类型

复制代码
TS_SOFTWARE:
- 内核在协议栈处理时打时间戳
- 使用系统时钟(CLOCK_REALTIME)
- 配置:SOF_TIMESTAMPING_TX_SOFTWARE
- 精度:微秒级

TS_HARDWARE:
- 网卡PHY硬件打时间戳
- 使用PHC时钟
- 配置:SOF_TIMESTAMPING_TX_HARDWARE
- 精度:纳秒级
- 需要硬件支持

TS_LEGACY_HW:
- 旧版硬件时间戳实现
- 某些老网卡使用
- 配置:SOF_TIMESTAMPING_SYS_HARDWARE
- 精度:亚微秒级

TS_ONESTEP:
- 单步时钟(One-Step Clock)
- 发送Sync时自动修改报文时间戳
- 不需要Follow_Up消息
- 需要硬件支持(HWTSTAMP_TX_ONESTEP_SYNC)

TS_P2P1STEP:
- 单步时钟用于P2P延迟测量
- 发送Pdelay_Resp时自动修改时间戳
- 需要硬件支持(HWTSTAMP_TX_ONESTEP_P2P)

ts_str函数

c 复制代码
/* util.c, 第74行 */

const char *ts_str(enum timestamp_type ts)
{
    switch (ts) {
    case TS_SOFTWARE:
        return "software";
    case TS_HARDWARE:
        return "hardware";
    case TS_LEGACY_HW:
        return "legacy_hwtstamp";
    case TS_ONESTEP:
        return "onestep";
    case TS_P2P1STEP:
        return "p2p1step";
    default:
        return "unknown";
    }
}

Linux时间戳子系统

SO_TIMESTAMPING标志

c 复制代码
/* <linux/net_tstamp.h> */

/* 软件时间戳 */
#define SOF_TIMESTAMPING_TX_SOFTWARE    (1<<0)
#define SOF_TIMESTAMPING_RX_SOFTWARE    (1<<1)
#define SOF_TIMESTAMPING_SOFTWARE       (1<<2)

/* 硬件时间戳 */
#define SOF_TIMESTAMPING_TX_HARDWARE    (1<<3)
#define SOF_TIMESTAMPING_RX_HARDWARE    (1<<4)
#define SOF_TIMESTAMPING_RAW_HARDWARE   (1<<6)    /* 真正的硬件时间戳 */
#define SOF_TIMESTAMPING_SYS_HARDWARE   (1<<5)    /* legacy硬件时间戳 */

/* 其他标志 */
#define SOF_TIMESTAMPING_OPT_TSONESTEP  (1<<11)   /* 单步时钟 */
#define SOF_TIMESTAMPING_BIND_PHC       (1<<15)   /* 绑定虚拟PHC */

标志组合

c 复制代码
/* 软件时间戳配置 */
flags = SOF_TIMESTAMPING_TX_SOFTWARE |    /* 发送软件时间戳 */
        SOF_TIMESTAMPING_RX_SOFTWARE |    /* 接收软件时间戳 */
        SOF_TIMESTAMPING_SOFTWARE;        /* 报告软件时间戳 */

/* 硬件时间戳配置 */
flags = SOF_TIMESTAMPING_TX_HARDWARE |    /* 发送硬件时间戳 */
        SOF_TIMESTAMPING_RX_HARDWARE |    /* 接收硬件时间戳 */
        SOF_TIMESTAMPING_RAW_HARDWARE;    /* 报告原始硬件时间戳 */

sk_timestamping_init详解

c 复制代码
/* sk.c, 第560-650行 - 简化版 */

int sk_timestamping_init(int fd, const char *device, enum timestamp_type type,
                         enum transport_type transport, int vclock)
{
    int flags;

    /* 根据时间戳类型设置标志 */
    switch (type) {
    case TS_SOFTWARE:
        flags = SOF_TIMESTAMPING_TX_SOFTWARE |
                SOF_TIMESTAMPING_RX_SOFTWARE |
                SOF_TIMESTAMPING_SOFTWARE;
        break;
    
    case TS_HARDWARE:
    case TS_ONESTEP:
    case TS_P2P1STEP:
        flags = SOF_TIMESTAMPING_TX_HARDWARE |
                SOF_TIMESTAMPING_RX_HARDWARE |
                SOF_TIMESTAMPING_RAW_HARDWARE;
        break;
    
    case TS_LEGACY_HW:
        flags = SOF_TIMESTAMPING_TX_HARDWARE |
                SOF_TIMESTAMPING_RX_HARDWARE |
                SOF_TIMESTAMPING_SYS_HARDWARE;
        break;
    }

    /* 硬件时间戳需要额外配置驱动 */
    if (type != TS_SOFTWARE) {
        /* 配置网卡驱动 */
        hwts_init(fd, device, filter, tx_type);
    }

    /* 绑定虚拟时钟 */
    if (vclock >= 0)
        flags |= SOF_TIMESTAMPING_BIND_PHC;

    /* 设置socket选项 */
    struct so_timestamping timestamping;
    timestamping.flags = flags;
    timestamping.bind_phc = vclock;

    setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, 
               &timestamping, sizeof(timestamping));

    /* 配置错误队列选择 */
    setsockopt(fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE, &flags, sizeof(flags));

    return 0;
}

硬件时间戳驱动配置

hwtstamp_config结构

c 复制代码
/* <linux/net_tstamp.h> */

struct hwtstamp_config {
    int flags;       /* 配置标志 */
    int tx_type;     /* 发送时间戳类型 */
    int rx_filter;   /* 接收时间戳过滤 */
};

发送时间戳类型

c 复制代码
/* 发送时间戳类型 */

enum hwtstamp_tx_types {
    HWTSTAMP_TX_OFF,            /* 不打发送时间戳 */
    HWTSTAMP_TX_ON,             /* 普通硬件时间戳 */
    HWTSTAMP_TX_ONESTEP_SYNC,   /* 单步时钟(Sync消息) */
    HWTSTAMP_TX_ONESTEP_P2P,    /* 单步时钟(P2P消息) */
};

发送类型详解

复制代码
HWTSTAMP_TX_OFF:
- 网卡不打发送时间戳
- 用于只接收时间戳的场景
- 或者软件时间戳

HWTSTAMP_TX_ON:
- 网卡打发送时间戳
- 时间戳存入错误队列
- 用户态用recvmsg(MSG_ERRQUEUE)读取
- 需要Follow_Up消息携带时间戳

HWTSTAMP_TX_ONESTEP_SYNC:
- 单步时钟
- 网卡在发送Sync时直接修改报文
- 将时间戳写入PTP消息的originTimestamp字段
- 不需要Follow_Up消息
- 硬件要求极高

HWTSTAMP_TX_ONESTEP_P2P:
- P2P单步时钟
- 网卡修改Pdelay_Resp的时间戳
- 将receiveTimestamp写入报文
- 不需要Pdelay_Resp_Follow_Up

接收时间戳过滤

c 复制代码
/* 接收时间戳过滤 */

enum hwtstamp_rx_filters {
    HWTSTAMP_FILTER_NONE,              /* 不接收时间戳 */
    
    /* 普通时间戳过滤 */
    HWTSTAMP_FILTER_ALL,               /* 所有报文 */
    HWTSTAMP_FILTER_SOME,              /* 部分报文 */
    
    /* PTP专用过滤 */
    HWTSTAMP_FILTER_PTP_V1_L4_EVENT,   /* PTPv1 UDP */
    HWTSTAMP_FILTER_PTP_V2_L4_EVENT,   /* PTPv2 UDP */
    HWTSTAMP_FILTER_PTP_V2_L2_EVENT,   /* PTPv2 以太网 */
    HWTSTAMP_FILTER_PTP_V2_EVENT,      /* PTPv2 所有 */
    
    /* 其他协议 */
    HWTSTAMP_FILTER_NTP_ALL,           /* NTP报文 */
};

过滤类型详解

复制代码
为什么需要过滤?

网卡时间戳资源有限:
- PHY硬件时间戳缓冲区有限
- 打所有报文时间戳会消耗资源
- 只给PTP报文打时间戳更高效

过滤级别:
HWTSTAMP_FILTER_NONE:
- 不给任何报文打时间戳
- 用于只发送时间戳的场景

HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
- 只给PTPv2 UDP事件消息打时间戳
- EtherType=IPv4/IPv6, UDP port=319
- 最常用的配置

HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
- 只给PTPv2以太网事件消息打时间戳
- EtherType=0x88F7
- 原始以太网传输

HWTSTAMP_FILTER_PTP_V2_EVENT:
- 给所有PTPv2事件消息打时间戳
- UDP和以太网都包含
- 最通用

HWTSTAMP_FILTER_ALL:
- 给所有报文打时间戳
- 最消耗资源
- 用于调试或特殊场景

hwts_init函数

c 复制代码
/* sk.c, 第59-136行 */

static int hwts_init(int fd, const char *device, int rx_filter,
                     int rx_filter2, int tx_type)
{
    struct ifreq ifreq;
    struct hwtstamp_config cfg;
    int err;

    init_ifreq(&ifreq, &cfg, device);

    /* 检查VLAN over bond支持 */
    cfg.flags = HWTSTAMP_FLAG_BONDED_PHC_INDEX;
    err = ioctl(fd, SIOCGHWTSTAMP, &ifreq);
    if (err < 0) {
        if (errno == EINVAL || errno == EOPNOTSUPP) {
            init_ifreq(&ifreq, &cfg, device);
        } else {
            pr_err("ioctl SIOCGHWTSTAMP failed: %m");
            return err;
        }
    }

    switch (sk_hwts_filter_mode) {
    case HWTS_FILTER_CHECK:
        /* 只检查,不修改 */
        err = ioctl(fd, SIOCGHWTSTAMP, &ifreq);
        if (err < 0) {
            pr_err("ioctl SIOCGHWTSTAMP failed: %m");
            return err;
        }
        break;

    case HWTS_FILTER_FULL:
        /* 全量过滤 */
        cfg.tx_type = tx_type;
        cfg.rx_filter = HWTSTAMP_FILTER_ALL;
        err = ioctl(fd, SIOCSHWTSTAMP, &ifreq);
        if (err < 0) {
            pr_err("ioctl SIOCSHWTSTAMP failed: %m");
            return err;
        }
        break;

    case HWTS_FILTER_NORMAL:
        /* 正常过滤,fallback机制 */
        cfg.tx_type = tx_type;
        cfg.rx_filter = rx_filter;
        err = ioctl(fd, SIOCSHWTSTAMP, &ifreq);
        if (err < 0) {
            pr_info("driver rejected most general HWTSTAMP filter");
            
            /* 尝试备用过滤 */
            init_ifreq(&ifreq, &cfg, device);
            cfg.tx_type = tx_type;
            cfg.rx_filter = rx_filter2;
            err = ioctl(fd, SIOCSHWTSTAMP, &ifreq);
            if (err < 0) {
                pr_err("ioctl SIOCSHWTSTAMP failed: %m");
                return err;
            }
        }
        break;
    }

    /* 验证配置 */
    if (cfg.tx_type != tx_type ||
        (cfg.rx_filter != rx_filter && cfg.rx_filter != rx_filter2)) {
        pr_err("The current filter does not match the required");
        return -1;
    }

    return 0;
}

SIOCSHWTSTAMP ioctl

复制代码
SIOCSHWTSTAMP:设置硬件时间戳配置

struct ifreq {
    char ifr_name[IFNAMSIZ];    /* 网卡名称 */
    void *ifr_data;             /* 指向hwtstamp_config */
};

调用流程:
1. 构造hwtstamp_config
2. 设置tx_type和rx_filter
3. ioctl(fd, SIOCSHWTSTAMP, &ifreq)
4. 网卡驱动接收配置
5. 驱动配置PHY芯片
6. PHY开始给指定报文打时间戳

注意:
- 每个网卡只能有一个配置
- 多个进程不能同时配置
- 配置后需要重新配置才能修改

Fallback机制

c 复制代码
/* 过滤配置的fallback */

filter1 = HWTSTAMP_FILTER_PTP_V2_EVENT;      /* 最通用 */
filter2 = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;   /* UDP专用 */

尝试filter1:
- 如果成功,使用最通用过滤
- 如果失败(驱动不支持),尝试filter2

为什么需要fallback?
- 不同网卡支持的过滤不同
- 有些网卡只支持L4过滤
- 有些网卡只支持L2过滤
- 需要根据实际情况选择

时间戳获取

时间戳数组结构

c 复制代码
/* SO_TIMESTAMPING返回三个时间戳 */

struct timespec ts[3];

ts[0]:软件时间戳(SOF_TIMESTAMPING_SOFTWARE)
- 内核协议栈处理时的时间
- 使用系统时钟

ts[1]:系统硬件时间戳(SOF_TIMESTAMPING_SYS_HARDWARE)
- legacy硬件时间戳
- 经过内核转换的时间戳

ts[2]:原始硬件时间戳(SOF_TIMESTAMPING_RAW_HARDWARE)
- PHY直接提供的时间戳
- 使用PHC时钟
- 最高精度

sk_receive解析时间戳

c 复制代码
/* sk.c中的时间戳解析 */

for (cm = CMSG_FIRSTHDR(&msg); cm != NULL; cm = CMSG_NXTHDR(&msg, cm)) {
    level = cm->cmsg_level;
    type  = cm->cmsg_type;
    
    if (SOL_SOCKET == level && SO_TIMESTAMPING == type) {
        ts = (struct timespec *) CMSG_DATA(cm);
    }
    
    if (SOL_SOCKET == level && SO_TIMESTAMPNS == type) {
        sw = (struct timespec *) CMSG_DATA(cm);
        hwts->sw = timespec_to_tmv(*sw);
    }
}

/* 根据类型选择时间戳 */
switch (hwts->type) {
case TS_SOFTWARE:
    hwts->ts = timespec_to_tmv(ts[0]);
    break;
case TS_HARDWARE:
case TS_ONESTEP:
case TS_P2P1STEP:
    hwts->ts = timespec_to_tmv(ts[2]);
    break;
case TS_LEGACY_HW:
    hwts->ts = timespec_to_tmv(ts[1]);
    break;
}

控制消息详解

复制代码
recvmsg的控制消息(ancillary data):

msg.msg_control包含控制消息
控制消息格式:
struct cmsghdr {
    size_t cmsg_len;    /* 数据长度 */
    int cmsg_level;     /* 协议层(SOL_SOCKET) */
    int cmsg_type;      /* 类型(SO_TIMESTAMPING) */
    /* 后面是数据 */
};

遍历控制消息:
CMSG_FIRSTHDR(&msg):第一个控制消息
CMSG_NXTHDR(&msg, cm):下一个控制消息
CMSG_DATA(cm):数据指针

SO_TIMESTAMPING数据:
- 包含3个struct timespec
- 总长度:3 * sizeof(struct timespec)

发送时间戳获取

MSG_ERRQUEUE机制

c 复制代码
/* 发送时间戳从错误队列获取 */

if (flags == MSG_ERRQUEUE) {
    struct pollfd pfd = { fd, sk_events, 0 };
    res = poll(&pfd, 1, sk_tx_timeout);
    if (res < 0 && errno == EINTR)
        res = poll(&pfd, 1, sk_tx_timeout);
    
    if (res < 0) {
        pr_err("poll for tx timestamp failed: %m");
        return -errno;
    } else if (!res) {
        pr_err("timed out while polling for tx timestamp");
        errno = ETIME;
        return -1;
    }
}

cnt = recvmsg(fd, &msg, MSG_ERRQUEUE);

错误队列原理

复制代码
发送时间戳的工作流程:

1. 应用层发送报文
   sendto(fd, buf, len, 0, &addr, sizeof(addr))

2. 报文经过内核协议栈
   → 到达网卡驱动

3. 网卡发送报文
   → PHY硬件打时间戳

4. 时间戳存入socket错误队列
   → 不是真正的"错误"
   → 只是借用错误队列机制

5. 应用层从错误队列读取
   recvmsg(fd, &msg, MSG_ERRQUEUE)

注意:
- MSG_ERRQUEUE不是接收新报文
- 是读取之前发送报文的时间戳
- 报文内容作为"错误信息"返回

poll等待时间戳

c 复制代码
/* 发送后需要等待时间戳可用 */

res = poll(&pfd, 1, sk_tx_timeout);
复制代码
为什么需要poll?

时间戳生成延迟:
- PHY打时间戳需要时间
- 时间戳需要传回内核
- 内核需要放入错误队列

典型延迟:
- 网卡发送:几微秒
- PHY打时间戳:<100纳秒
- 传回内核:几微秒
- 总延迟:10-100微秒

sk_tx_timeout:
- 默认值:1毫秒
- 可配置(tx_timestamp_timeout)
- 如果超时,可能是驱动bug

单步时钟(One-Step Clock)

单步时钟原理

复制代码
普通时钟(Two-Step):
1. 主时钟发送Sync
2. 主时钟记录发送时间t1
3. 主时钟发送Follow_Up携带t1
4. 从时钟接收Sync(时间t2)
5. 从时钟接收Follow_Up获得t1

单步时钟(One-Step):
1. 主时钟构造Sync(originTimestamp=0)
2. 网卡发送时硬件修改originTimestamp
3. 直接填入实际发送时间t1
4. 不需要Follow_Up消息
5. 从时钟接收Sync直接获得t1和t2

优势:
- 减少一个消息
- 降低网络负载
- 减少延迟(t1更精确)
- 简化协议栈

挑战:
- 网卡必须在发送时修改报文
- 需要硬件支持
- 时间戳必须在PHY发送前计算
- 技术难度极高

One-Step实现

c 复制代码
/* port.c,第1831-1866行(简化示例) */

/* 检查是否需要设置TWO_STEP标志 */
if (p->timestamping != TS_ONESTEP && p->timestamping != TS_P2P1STEP) {
    msg->header.flagField[0] |= TWO_STEP;
}

/* 发送Sync */
err = port_prepare_and_send(p, msg, event);
if (err) {
    pr_err("%s: send sync failed", p->log_name);
    goto out;
}

/* 单步时钟:硬件自动完成,无需Follow_Up */
if (p->timestamping == TS_ONESTEP || p->timestamping == TS_P2P1STEP) {
    goto out;
} else if (msg_sots_missing(msg)) {
    pr_err("missing timestamp on transmitted sync");
    err = -1;
    goto out;
}

/* 两步时钟:发送Follow_Up */
fup->follow_up.preciseOriginTimestamp = tmv_to_Timestamp(msg->hwts.ts);
err = port_prepare_and_send(p, fup, TRANS_GENERAL);

硬件修改过程

复制代码
单步时钟的硬件操作:

1. 应用层构造Sync报文
   originTimestamp = 0(或预估值)

2. 报文传给网卡驱动

3. 驱动配置PHY
   HWTSTAMP_TX_ONESTEP_SYNC

4. PHY发送前:
   - 读取PHC时间
   - 修改报文的originTimestamp字段
   - 计算UDP校验和(如果需要)
   - 发送报文

5. 报文发出
   originTimestamp = 实际发送时间

硬件修改位置:
- PTP消息头中
- originTimestamp字段(10字节)
- seconds字段(6字节)+ nanoseconds字段(4字节)

UDP校验和修正

c 复制代码
/* udp.c中的处理 */

if (event == TRANS_ONESTEP)
    len += 2;    /* 为UDP校验修正扩展 */
复制代码
单步时钟的UDP挑战:

UDP校验和覆盖:
- UDP头部
- UDP数据(PTP消息)
- IPv6 pseudo-header

修改PTP消息后:
- UDP校验和失效
- 需要重新计算

硬件处理方法:
1. 发送前预留2字节
2. PHY计算校验和修正值
3. 填入预留字节
4. 实际UDP校验和 = 原校验和 + 修正值

DP83640等PHY芯片:
- 支持自动UDP校验修正
- 需要预留空间
- len += 2

虚拟时钟(Virtual Clock)

vclock概念

c 复制代码
/* missing.h, 第70-79行 */

enum {
    SOF_TIMESTAMPING_BIND_PHC = (1 << 15),
};

struct so_timestamping {
    int flags;
    int bind_phc;    /* 虚拟时钟索引 */
};

虚拟PHC

复制代码
Linux 5.18引入虚拟时钟:

背景:
- 一个物理网卡可以有多个虚拟PHC
- 每个虚拟PHC独立运行
- 用于多域PTP

虚拟时钟索引:
- bind_phc = 0:物理PHC(默认)
- bind_phc = 1:第一个虚拟PHC
- bind_phc = 2:第二个虚拟PHC

作用:
- 时间戳绑定到指定虚拟时钟
- 发送报文使用指定虚拟时钟的时间
- 接收报文使用指定虚拟时钟的时间

配置:
vclock = interface_get_vclock(iface);
if (vclock >= 0)
    flags |= SOF_TIMESTAMPING_BIND_PHC;
timestamping.bind_phc = vclock;

多域PTP

复制代码
虚拟时钟的应用场景:

场景1:电信网络
- 同一物理网络承载多个域
- 域1:频率同步(1588v2)
- 域2:相位同步(1588-2019)
- 使用不同虚拟时钟

场景2:数据中心
- 多个租户共享网络
- 每个租户独立时钟域
- 虚拟时钟隔离

配置示例:
# 创建虚拟时钟
$ echo 2 > /sys/class/net/eth0/device/vclocks

# 查看
$ ls /sys/class/net/eth0/device/ptp/
ptp0/  ptp1/  ptp2/

# ptp0:物理时钟
# ptp1, ptp2:虚拟时钟

# ptp4l使用虚拟时钟
ptp4l -i eth0 --phc_index 1

时间戳延迟补偿

ingressLatency和egressLatency

c 复制代码
/* port.c中的延迟补偿 */

p->rx_timestamp_offset = config_get_int(cfg, p->name, "ingressLatency");
p->rx_timestamp_offset <<= 16;    /* 转换为nanoseconds_scaled */

p->tx_timestamp_offset = config_get_int(cfg, p->name, "egressLatency");
p->tx_timestamp_offset <<= 16;

/* 接收时减去延迟 */
ts_add(&msg->hwts.ts, -p->rx_timestamp_offset);

/* 发送时加上延迟 */
ts_add(&msg->hwts.ts, p->tx_timestamp_offset);

延迟补偿原理

复制代码
时间戳延迟:

接收延迟(ingressLatency):
PHY打时间戳 → MAC → DMA → 内核 → 应用层
延迟包括:
- PHY到MAC的延迟
- MAC处理延迟
- DMA传输延迟
- 内核协议栈延迟
- 总延迟:几微秒到几十微秒

发送延迟(egressLatency):
应用层 → 内核 → DMA → MAC → PHY
延迟类似接收

补偿方法:
接收时间戳 = 硬件时间戳 - ingressLatency
发送时间戳 = 硅件时间戳 + egressLatency

配置:
[eth0]
ingressLatency 0    # 接收延迟(纳秒)
egressLatency 0     # 发送延迟(纳秒)

通常:
- PHY硬件时间戳不需要补偿
- ingressLatency/egressLatency = 0
- 只在特殊场景需要

实战:检查时间戳能力

ethtool -T命令

bash 复制代码
$ ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
    hardware-transmit     (SOF_TIMESTAMPING_TX_HARDWARE)
    hardware-receive      (SOF_TIMESTAMPING_RX_HARDWARE)
    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 Timestamp Modes:
    none                  (HWTSTAMP_FILTER_NONE)
    all                   (HWTSTAMP_FILTER_ALL)
    ptpv2-l4-event        (HWTSTAMP_FILTER_PTP_V2_L4_EVENT)
    ptpv2-l4-sync         (HWTSTAMP_FILTER_PTP_V2_L4_SYNC)
    ...

解读输出

复制代码
Capabilities:时间戳能力
- hardware-transmit:支持发送硬件时间戳
- hardware-receive:支持接收硬件时间戳
- hardware-raw-clock:支持原始硬件时间戳

PTP Hardware Clock:关联的PHC索引
- 0:/dev/ptp0
- 如果为-1:不支持硬件时间戳

Transmit Timestamp Modes:发送时间戳模式
- HWTSTAMP_TX_ON:普通硬件时间戳
- HWTSTAMP_TX_ONESTEP_SYNC:单步时钟(如果有)

Receive Timestamp Modes:接收时间戳过滤
- 各种PTP过滤模式
- 根据网卡能力列出

检查单步时钟支持

bash 复制代码
# 查看是否支持单步时钟
$ ethtool -T eth0 | grep onestep

# 如果有onestep,说明支持单步时钟

# 查看网卡驱动信息
$ ethtool -i eth0
driver: igb
version: 5.15.0-1014-intel
firmware-version: 1.0.4
...

# Intel igb驱动支持单步时钟

常见网卡时间戳能力

Intel网卡

复制代码
Intel I210/I350/igb:
- 支持硬件时间戳
- 支持单步时钟
- PHC精度:<100纳秒
- 广泛使用

Intel X710/i40e:
- 支持硬件时间戳
- 精度:<50纳秒
- 不支持单步时钟

Intel E810/ice:
- 最新一代
- 支持硬件时间戳
- 支持虚拟时钟
- 精度:<10纳秒

其他网卡

复制代码
Broadcom bnxt_en:
- 支持硬件时间戳
- 精度:<100纳秒

Marvell mv88e6xxx:
- 交换机芯片
- 支持硬件时间戳
- 用于透明时钟

NXP sja1105:
- 交换机芯片
- 支持硬件时间戳
- 支持透明时钟

Realtek rtl8366:
- 低成本方案
- 部分型号支持硬件时间戳

时间戳调试

ts_proc调试接口

bash 复制代码
# 查看时间戳状态
$ cat /proc/net/ts

# 某些驱动提供调试信息
$ cat /sys/kernel/debug/ptp/ptp0/

# 查看时间戳统计
$ dmesg | grep timestamp

测试时间戳

bash 复制代码
# 使用testptp工具测试
$ testptp -d /dev/ptp0 --getcap

# 测试发送时间戳
$ testptp -d /dev/ptp0 --extts 0

# 测试接收时间戳
# 发送PTP报文并检查时间戳

小结:硬件时间戳的关键要点

时间戳类型

  • 软件、硬件、单步时钟
  • 根据需求选择

配置流程

  • SIOCSHWTSTAMP配置驱动
  • SO_TIMESTAMPING配置socket
  • recvmsg获取时间戳

单步时钟

  • 网卡硬件修改报文
  • 减少Follow_Up消息
  • 需要特殊硬件支持

虚拟时钟

  • 多域PTP支持
  • Linux新特性

延迟补偿

  • ingressLatency/egressLatency
  • 补偿系统延迟

下集预告

硬件时间戳解决了"何时打时间戳",但PTP消息中的扩展信息如何处理?

下一节,我们将分析TLV处理实现------看看LinuxPTP如何编解码各种TLV扩展。

【悬念留给3.9】

TLV(Type-Length-Value)是PTP的扩展机制。

管理消息、信号消息都使用TLV。

LinuxPTP如何解析复杂的TLV结构?

如何构造TLV发送给其他节点?

下一节,深入TLV的世界。

📚 本文内容摘自本人的开源书《PTP技术书 - 从思想实验到协议实现》

全书从时间本质的思想实验出发,深度解析 IEEE 1588 协议、逐章分析 LinuxPTP 源码,并带你动手实现一个轻量级 PTP 程序(ptp-lite)。

🔗 在线阅读/下载:ptp-book

bash 复制代码
git clone https://github.com/Lularible/ptp-book.git

⭐ 如果对您有帮助,欢迎 Star 支持,也欢迎通过 GitHub Issues 交流讨论。

相关推荐
byoass2 小时前
文件版本管理的设计与实现:解决协同编辑丢数据的核心方案
前端·javascript·网络·数据库·安全·云计算
胡图图不糊涂^_^2 小时前
网络原理笔记
java·网络·笔记·学习·tcp/ip·http·https
奇逍科技圈2 小时前
开源赋能与 BC 一体化:深度解析中企销订货系统源码如何重构批发零售增长引擎
后端·架构·开源·零售
呱牛do it2 小时前
企业级需求管理工具(关注后续开源)
开源
聊点儿技术2 小时前
广告定向总跑偏?用IP精准定位验证链路是否通畅的排查方法
服务器·网络·代理ip·广告投放·ip精准定位服务·ip地理定位api
忡黑梨2 小时前
eNSP_登录华为设备
运维·服务器·网络·华为·负载均衡
wanhengidc2 小时前
小带宽服务器都有哪些用途
运维·服务器·网络·安全·智能手机
想唱rap2 小时前
TCP套接字编程
java·linux·网络·c++·tcp/ip·mysql·ubuntu
a1117762 小时前
Web3D 在线3D模型骨骼动画编辑器(开源 Reze Studio)
前端·3d·开源·html