하드웨어 타임스탬핑 (Hardware Timestamping)

하드웨어 타임스탬핑은 NIC(Network Interface Card)가 패킷을 물리적으로 전송하거나 수신하는 시점을 PHC(PTP Hardware Clock) 카운터로 기록하는 기능입니다. 소프트웨어 타임스탬핑에 비해 OS 스케줄링 지터가 제거되어 PTP 동기화 정밀도를 나노초 수준으로 높입니다. 커널 인터페이스(SIOCSHWTSTAMP, hwtstamp_config), SOF_TIMESTAMPING_* 플래그, skb_hwtstamps 구조체, SO_TIMESTAMPING 소켓 옵션, NIC 드라이버 구현 패턴, TX 완료 타임스탬프 전달 경로를 체계적으로 정리합니다.

전제 조건: 네트워크 스택 개요PTP (IEEE 1588) 문서를 먼저 읽으세요. 하드웨어 타임스탬핑은 PTP 정밀 동기화의 기반이 됩니다.
일상 비유: 하드웨어 타임스탬핑은 정밀 스포츠 타이머와 같습니다. 심판(OS)이 반응하는 시간에 의존하지 않고, 선수가 결승선을 통과하는 순간 바로 전자 센서가 시각을 기록합니다. NIC의 MAC/PHY 레이어가 그 전자 센서 역할을 합니다.

핵심 요약

  • SIOCSHWTSTAMP — 인터페이스별 타임스탬핑 설정을 활성화하는 ioctl입니다. hwtstamp_config로 TX/RX 타임스탬핑 모드를 지정합니다.
  • hwtstamp_configtx_type(OFF/ON/ONESTEP_SYNC)과 rx_filter(PTP만, 전체 등)를 담는 설정 구조체입니다.
  • SOF_TIMESTAMPING_* 플래그 — 소켓에서 어떤 종류(HW/SW, TX/RX)의 타임스탬프를 요청할지 지정합니다.
  • skb_hwtstamps — sk_buff에 첨부되는 하드웨어 타임스탬프 구조체입니다. hwtstamp 필드에 PHC 기준 나노초 값을 담습니다.
  • TX 타임스탬프 전달 경로 — TX 완료 인터럽트에서 skb_tstamp_tx()를 호출하면 소켓 에러 큐로 타임스탬프가 전달됩니다.
  • SO_TIMESTAMPING — 소켓에 설정하여 recvmsg()cmsg로 타임스탬프를 받거나, TX의 경우 에러 큐(MSG_ERRQUEUE)에서 읽습니다.
  • SCM_TIMESTAMPINGrecvmsg() 보조 데이터로 전달되는 3개짜리 timespec 배열(소프트웨어, 예약, 하드웨어)입니다.
  • ethtool -T — 인터페이스가 지원하는 타임스탬핑 기능과 PHC 인덱스를 조회합니다.

단계별 이해

  1. NIC 지원 여부 확인ethtool -T <iface>로 TX/RX 하드웨어 타임스탬핑 지원 여부와 PHC 인덱스를 확인합니다.
  2. SIOCSHWTSTAMP로 활성화hwtstamp_config를 설정하여 원하는 TX/RX 필터를 활성화합니다.
  3. SO_TIMESTAMPING 소켓 옵션 설정 — 소켓에 원하는 타임스탬프 플래그를 설정합니다.
  4. recvmsg로 타임스탬프 수신 — RX는 recvmsg()cmsg로, TX는 MSG_ERRQUEUE로 타임스탬프를 읽습니다.
  5. 드라이버 구현 이해 — NIC 드라이버에서 skb_hwtstamps()로 타임스탬프를 채우고 skb_tstamp_tx()로 TX 완료를 보고하는 경로를 파악합니다.

개요 — 타임스탬핑 계층

리눅스 네트워크 스택은 패킷 타임스탬프를 세 레이어에서 찍을 수 있습니다.

애플리케이션 (ptp4l 등) 소켓 레이어 (SO_TIMESTAMPING) 네트워크 스택 (커널) IP / UDP 계층 dev_queue_xmit() skb_tstamp_tx() 호출 에러 큐 알림 NIC 드라이버 DMA 디스크립터 설정 TX SKBTX_HW_TSTAMP TX 완료 IRQ → TS 읽기 PHC (MAC/PHY) TX: SFD 기록 ● RX: SFD 기록 ptp_clock (/dev/ptp0) PHC 읽기/쓰기 · 주파수 보정 TX TS → errqueue HW TS 기록
레이어시점정밀도지터 원인
소프트웨어 (SW)드라이버 RX 핸들러 진입 시수십 μsIRQ 처리 지연, CPU 스케줄링
NIC 드라이버 (SW-nic)DMA 완료 직후수 μsCPU 캐시 미스
하드웨어 (HW)MAC/PHY 레이어 패킷 경계수 ns~수십 nsPHC 클럭 해상도

PTP에서 나노초 수준의 정밀도를 달성하려면 반드시 하드웨어 타임스탬핑이 필요합니다.

ethtool로 지원 기능 조회

ethtool -T <인터페이스>는 NIC 드라이버의 get_ts_info()를 호출하여 하드웨어가 지원하는 타임스탬핑 기능을 나열합니다. PTP Hardware Clock 항목이 있으면 해당 인터페이스는 PHC를 가지며 /dev/ptpN으로 접근할 수 있습니다. Hardware Transmit Timestamp Modeson이 있어야 TX 타임스탬핑이 가능하고, Hardware Receive Filter Modesptpv2-event 또는 all이 있어야 RX 타임스탬핑이 됩니다. 이 정보를 먼저 확인한 후에 SIOCSHWTSTAMP ioctl로 실제 설정을 적용합니다.

ethtool -T eth0

# 출력 예시:
# Time stamping parameters for eth0:
# Capabilities:
#     hardware-transmit
#     software-transmit
#     hardware-receive
#     software-receive
#     software-system-clock
#     hardware-raw-clock
# PTP Hardware Clock: 0          ← /dev/ptp0
# Hardware Transmit Timestamp Modes:
#     off
#     on
#     onestep-sync
# Hardware Receive Filter Modes:
#     none
#     ptpv1-l4-event
#     ptpv2-l4-event
#     ptpv2-l2-event
#     ptpv2-event
#     all

hwtstamp_config 설정 (SIOCSHWTSTAMP)

하드웨어 타임스탬핑을 활성화하려면 SIOCSHWTSTAMP ioctl로 hwtstamp_config를 전달합니다.

#include <linux/net_tstamp.h>
#include <sys/ioctl.h>
#include <net/if.h>

int enable_hw_timestamp(const char *ifname)
{
    struct ifreq ifr = {};
    struct hwtstamp_config config = {};
    int sock, ret;

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
        return -errno;

    strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);

    /* TX: 모든 패킷에 타임스탬프 */
    config.tx_type   = HWTSTAMP_TX_ON;

    /* RX: PTPv2 이벤트 패킷만 타임스탬프 */
    config.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;

    /* 또는 RX 전체 패킷 */
    /* config.rx_filter = HWTSTAMP_FILTER_ALL; */

    ifr.ifr_data = (void *)&config;
    ret = ioctl(sock, SIOCSHWTSTAMP, &ifr);

    /* config.rx_filter에 실제 적용된 필터가 반환됨 */
    printf("applied rx_filter: %d\n", config.rx_filter);

    close(sock);
    return ret;
}

TX 타임스탬프 모드

이름설명
0HWTSTAMP_TX_OFFTX 타임스탬핑 비활성
1HWTSTAMP_TX_ON모든 패킷에 TX 타임스탬핑
2HWTSTAMP_TX_ONESTEP_SYNCPTP Sync 메시지를 원패스로 처리 (Follow_Up 불필요)
3HWTSTAMP_TX_ONESTEP_P2PSync + Pdelay_Resp를 원패스 처리

RX 필터 모드 주요 값

이름설명
0HWTSTAMP_FILTER_NONERX 타임스탬핑 비활성
1HWTSTAMP_FILTER_ALL모든 수신 패킷
9HWTSTAMP_FILTER_PTP_V2_EVENTPTPv2 이벤트 메시지 (Sync, Delay_Req 등)
10HWTSTAMP_FILTER_PTP_V2_SYNCPTPv2 Sync 메시지만
12HWTSTAMP_FILTER_PTP_V2_DELAY_REQPTPv2 Delay_Req만

SO_TIMESTAMPING 소켓 옵션

SO_TIMESTAMPING을 소켓에 설정하면 recvmsg()의 보조 데이터(cmsg)로 타임스탬프를 받을 수 있습니다.

#include <linux/net_tstamp.h>

int flags = 0;
/* 하드웨어 RX 타임스탬프 요청 */
flags |= SOF_TIMESTAMPING_RX_HARDWARE;
/* RAW 하드웨어 클럭(PHC) 기준 타임스탬프 */
flags |= SOF_TIMESTAMPING_RAW_HARDWARE;
/* TX 완료 타임스탬프 (에러 큐로 전달) */
flags |= SOF_TIMESTAMPING_TX_HARDWARE;

setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));

SOF_TIMESTAMPING_* 플래그

플래그의미
SOF_TIMESTAMPING_TX_HARDWARETX 하드웨어 타임스탬프 요청
SOF_TIMESTAMPING_TX_SOFTWARETX 소프트웨어 타임스탬프 요청
SOF_TIMESTAMPING_RX_HARDWARERX 하드웨어 타임스탬프 요청
SOF_TIMESTAMPING_RX_SOFTWARERX 소프트웨어 타임스탬프 요청
SOF_TIMESTAMPING_SOFTWARE시스템 시간(CLOCK_REALTIME) 기준
SOF_TIMESTAMPING_RAW_HARDWAREPHC 기준 원시 하드웨어 클럭 타임스탬프
SOF_TIMESTAMPING_OPT_IDTX 타임스탬프에 패킷 식별자 포함
SOF_TIMESTAMPING_OPT_TX_SWHWTX SW+HW 타임스탬프 둘 다 요청

RX 타임스탬프 수신

수신 패킷의 타임스탬프는 recvmsg()ancillary data(보조 데이터)로 전달됩니다. 소켓에 SO_TIMESTAMPING 옵션을 설정하고 MSG_WAITFORONE 또는 일반 recvmsg()를 호출하면, 커널이 SCM_TIMESTAMPING cmsg 헤더와 함께 scm_timestamping 구조체를 반환합니다. 이 구조체의 세 필드(ts[0]: 소프트웨어, ts[2]: 하드웨어 RAW)에서 원하는 타임스탬프 종류를 읽습니다. 하드웨어 타임스탬프(ts[2])는 PHC 도메인의 시각이므로, CLOCK_REALTIME과의 오프셋을 보정해야 합니다.

char buf[1500];
char control[512];
struct msghdr msg = {};
struct iovec iov = { buf, sizeof(buf) };
struct cmsghdr *cmsg;
struct scm_timestamping *ts;

msg.msg_iov        = &iov;
msg.msg_iovlen     = 1;
msg.msg_control    = control;
msg.msg_controllen = sizeof(control);

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

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET &&
        cmsg->cmsg_type  == SCM_TIMESTAMPING) {

        ts = (struct scm_timestamping *)CMSG_DATA(cmsg);

        /* ts->ts[0]: 소프트웨어 타임스탬프 (CLOCK_REALTIME) */
        /* ts->ts[1]: 예약 (항상 0) */
        /* ts->ts[2]: 하드웨어 원시 타임스탬프 (PHC) */

        printf("HW RX timestamp: %ld.%09ld\n",
               ts->ts[2].tv_sec, ts->ts[2].tv_nsec);
    }
}

TX 타임스탬프 수신 (에러 큐)

TX 타임스탬프는 패킷 전송 완료 후 소켓 에러 큐로 전달됩니다.

/* TX 타임스탬프를 위해 MSG_ERRQUEUE에서 읽기 */
char control[512];
struct msghdr msg = {};
struct iovec iov = { buf, sizeof(buf) };
struct cmsghdr *cmsg;
struct scm_timestamping *ts;
struct sock_extended_err *serr;

msg.msg_iov        = &iov;
msg.msg_iovlen     = 1;
msg.msg_control    = control;
msg.msg_controllen = sizeof(control);

/* sendmsg() 후 잠시 대기 (비동기) */
ssize_t n = recvmsg(sock, &msg, MSG_ERRQUEUE);
if (n < 0 && errno == EAGAIN) {
    /* 아직 TX 완료 안 됨 */
    return;
}

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET &&
        cmsg->cmsg_type  == SCM_TIMESTAMPING) {

        ts = (struct scm_timestamping *)CMSG_DATA(cmsg);
        printf("HW TX timestamp: %ld.%09ld\n",
               ts->ts[2].tv_sec, ts->ts[2].tv_nsec);
    }

    /* SOF_TIMESTAMPING_OPT_ID 사용 시 패킷 ID */
    if (cmsg->cmsg_level == SOL_IP &&
        cmsg->cmsg_type  == IP_RECVERR) {
        serr = (struct sock_extended_err *)CMSG_DATA(cmsg);
        printf("TX pkt id: %u\n", serr->ee_data);
    }
}

커널 내 타임스탬프 경로

커널에서 타임스탬프는 struct sk_buff에 부착됩니다. RX 경로에서는 NIC 드라이버가 DMA 완료 후 skb_hwtstamps(skb)->hwtstamp에 PHC 값을 기록하고 스택으로 전달합니다. TX 경로에서는 소켓 레이어가 SKBTX_HW_TSTAMP 플래그를 설정하고 ndo_start_xmit이 DMA에 제출한 뒤, TX 완료 인터럽트에서 skb_tstamp_tx()를 호출하여 타임스탬프를 소켓 에러 큐에 복사합니다. 이 메커니즘을 통해 유저스페이스는 recvmsg(MSG_ERRQUEUE)로 TX 완료 시각을 나노초 단위로 확인합니다.

RX 경로 — NIC → 소켓

/* NIC 드라이버 RX 핸들러 (drivers/net/ethernet/*/xxxx.c) */
static void my_nic_rx_complete(struct my_nic_priv *priv, struct sk_buff *skb)
{
    struct skb_shared_hwtstamps *hwts = skb_hwtstamps(skb);
    u64 ns;

    /* 하드웨어 타임스탬프 레지스터에서 읽기 */
    ns = my_nic_read_rx_timestamp(priv);

    /* ktime_t로 변환하여 skb에 저장 */
    hwts->hwtstamp = ns_to_ktime(ns);

    /* 스택으로 전달 */
    napi_gro_receive(&priv->napi, skb);
}

TX 경로 — 소켓 → NIC → 완료 통보

/* net_device_ops.ndo_start_xmit */
static netdev_tx_t my_nic_start_xmit(struct sk_buff *skb,
                                     struct net_device *dev)
{
    struct my_nic_priv *priv = netdev_priv(dev);

    /* TX 타임스탬프가 요청된 패킷인지 확인 */
    if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))
        skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;

    /* DMA에 패킷 제출 */
    my_nic_submit_tx(priv, skb);
    return NETDEV_TX_OK;
}

/* TX 완료 인터럽트 핸들러 */
static void my_nic_tx_complete(struct my_nic_priv *priv, int idx)
{
    struct sk_buff *skb = priv->tx_ring[idx].skb;
    struct skb_shared_hwtstamps hwts = {};

    if (skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS) {
        u64 ns = my_nic_read_tx_timestamp(priv, idx);
        hwts.hwtstamp = ns_to_ktime(ns);

        /* 소켓 에러 큐로 타임스탬프 전달 */
        skb_tstamp_tx(skb, &hwts);
    }

    dev_kfree_skb_any(skb);
}

ndo_get_tstamp — 지연 타임스탬프 읽기

일부 NIC는 TX 완료 인터럽트가 아닌 별도 큐에서 타임스탬프를 제공합니다. 이 경우 ndo_get_tstamp()를 구현합니다.

/* netdev_ops */
static int my_nic_get_tstamp(struct net_device *dev,
                             const struct skb_shared_info *shinfo,
                             struct kernel_hwtstamps *hwtstamps)
{
    struct my_nic_priv *priv = netdev_priv(dev);
    u64 ns;

    if (!my_nic_ts_ready(priv, shinfo->tskey))
        return -EAGAIN;  /* 아직 준비 안 됨 */

    ns = my_nic_read_deferred_ts(priv, shinfo->tskey);
    hwtstamps->hwtstamp = ns_to_ktime(ns);
    return 0;
}

ethtool_ops 타임스탬핑 구현

NIC 드라이버가 하드웨어 타임스탬핑을 지원하려면 ethtool_ops에 두 콜백을 구현해야 합니다. get_ts_info()는 드라이버가 지원하는 타임스탬프 플래그 세트와 연결된 PHC 인덱스를 ethtool -T와 소켓 API에 알립니다. get_set_hwtstamp() (또는 ndo_hwtstamp_set)는 SIOCSHWTSTAMP ioctl을 받아 하드웨어에 TX/RX 필터 설정을 적용합니다. 이 두 함수가 정확하게 구현되어야 ptp4l과 애플리케이션이 올바른 타임스탬프를 얻습니다.

static int my_nic_get_ts_info(struct net_device *dev,
                              struct kernel_ethtool_ts_info *info)
{
    struct my_nic_priv *priv = netdev_priv(dev);

    info->so_timestamping =
        SOF_TIMESTAMPING_TX_HARDWARE |
        SOF_TIMESTAMPING_TX_SOFTWARE |
        SOF_TIMESTAMPING_RX_HARDWARE |
        SOF_TIMESTAMPING_RX_SOFTWARE |
        SOF_TIMESTAMPING_SOFTWARE     |
        SOF_TIMESTAMPING_RAW_HARDWARE;

    if (priv->ptp_clock)
        info->phc_index = ptp_clock_index(priv->ptp_clock);
    else
        info->phc_index = -1;

    info->tx_types =
        BIT(HWTSTAMP_TX_OFF)        |
        BIT(HWTSTAMP_TX_ON)         |
        BIT(HWTSTAMP_TX_ONESTEP_SYNC);

    info->rx_filters =
        BIT(HWTSTAMP_FILTER_NONE)           |
        BIT(HWTSTAMP_FILTER_PTP_V2_EVENT)   |
        BIT(HWTSTAMP_FILTER_ALL);

    return 0;
}

static int my_nic_hwtstamp_set(struct net_device *dev,
                               struct kernel_hwtstamp_config *config,
                               struct netlink_ext_ack *extack)
{
    struct my_nic_priv *priv = netdev_priv(dev);

    priv->hwts_tx_type   = config->tx_type;
    priv->hwts_rx_filter = config->rx_filter;

    /* 하드웨어에 설정 적용 */
    my_nic_configure_hwtstamp(priv);

    /* 실제 적용된 필터 반환 */
    config->rx_filter = priv->hwts_rx_filter;
    return 0;
}

One-Step 타임스탬핑

2-step 모드에서는 Sync 패킷 전송 후 Follow_Up 패킷으로 TX 타임스탬프를 별도 전달합니다. One-step 모드는 하드웨어가 Sync 패킷을 전송하는 동시에 패킷 본문에 타임스탬프를 인라인으로 기록하여 Follow_Up 패킷을 없앱니다.

/* one-step TX: 드라이버에서 패킷 body의 correctionField를 직접 업데이트 */
if (hwtstamp_config.tx_type == HWTSTAMP_TX_ONESTEP_SYNC) {
    /* PTP 헤더의 correctionField 오프셋 = 8 */
    ptp_header = skb->data + skb_network_offset(skb) + /* IP/UDP 오프셋 */;
    correction = my_nic_get_egress_latency(priv);
    put_unaligned_be64(correction, ptp_header + 8);
}

NIC 드라이버 완전 구현 패턴

NIC 드라이버가 하드웨어 타임스탬핑을 완전히 지원하려면 ndo_set_tstamp, ethtool_ops.get_ts_info, TX 완료 인터럽트에서의 타임스탬프 읽기, PTP 클럭 등록을 모두 구현해야 합니다.

ndo_set_tstamp 구현

static int my_nic_set_tstamp(struct net_device *ndev,
                              struct ifreq *ifr)
{
    struct my_nic_priv *priv = netdev_priv(ndev);
    struct hwtstamp_config cfg;

    if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
        return -EFAULT;

    switch (cfg.tx_type) {
    case HWTSTAMP_TX_OFF:
        priv->hwts_tx_en = false;
        break;
    case HWTSTAMP_TX_ON:
        priv->hwts_tx_en = true;
        break;
    case HWTSTAMP_TX_ONESTEP_SYNC:
        priv->hwts_tx_en   = true;
        priv->onestep_en   = true;
        break;
    default:
        return -ERANGE;
    }

    switch (cfg.rx_filter) {
    case HWTSTAMP_FILTER_NONE:
        priv->hwts_rx_en = false;
        cfg.rx_filter = HWTSTAMP_FILTER_NONE;
        break;
    case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
    case HWTSTAMP_FILTER_PTP_V2_EVENT:
    case HWTSTAMP_FILTER_ALL:
        priv->hwts_rx_en = true;
        cfg.rx_filter = HWTSTAMP_FILTER_ALL;
        break;
    default:
        return -ERANGE;
    }

    /* 하드웨어에 설정 적용 */
    my_nic_hw_set_tstamp(priv, priv->hwts_tx_en, priv->hwts_rx_en);

    return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
}

TX 완료 인터럽트에서 타임스탬프 읽기

static void my_nic_tx_complete(struct my_nic_priv *priv,
                                struct sk_buff *skb)
{
    struct skb_shared_hwtstamps hwts = {};

    if (priv->hwts_tx_en && (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) {
        /* 하드웨어 TX 타임스탬프 레지스터 읽기 */
        u64 ns = my_nic_read_tx_timestamp(priv);

        hwts.hwtstamp = ns_to_ktime(ns);

        /* skb를 에러 큐로 반환 (소켓에 MSG_ERRQUEUE로 전달됨) */
        skb_tstamp_tx(skb, &hwts);
    }
}

RX 경로 타임스탬프 기록

static void my_nic_rx_timestamp(struct my_nic_priv *priv,
                                 struct sk_buff *skb,
                                 struct my_nic_rx_desc *desc)
{
    if (priv->hwts_rx_en && (desc->flags & MY_RX_HW_TSTAMP)) {
        struct skb_shared_hwtstamps *hwts = skb_hwtstamps(skb);

        /* DMA 디스크립터에서 타임스탬프 읽기 */
        hwts->hwtstamp = ns_to_ktime(le64_to_cpu(desc->timestamp));
    }
}

ethtool get_ts_info

static int my_nic_get_ts_info(struct net_device *ndev,
                               struct ethtool_ts_info *info)
{
    struct my_nic_priv *priv = netdev_priv(ndev);

    info->so_timestamping =
        SOF_TIMESTAMPING_TX_HARDWARE |
        SOF_TIMESTAMPING_TX_SOFTWARE |
        SOF_TIMESTAMPING_RX_HARDWARE |
        SOF_TIMESTAMPING_RX_SOFTWARE |
        SOF_TIMESTAMPING_RAW_HARDWARE;

    if (priv->ptp_clock)
        info->phc_index = ptp_clock_index(priv->ptp_clock);
    else
        info->phc_index = -1;

    info->tx_types =
        BIT(HWTSTAMP_TX_OFF) |
        BIT(HWTSTAMP_TX_ON)  |
        BIT(HWTSTAMP_TX_ONESTEP_SYNC);

    info->rx_filters =
        BIT(HWTSTAMP_FILTER_NONE)             |
        BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT)  |
        BIT(HWTSTAMP_FILTER_PTP_V2_EVENT)     |
        BIT(HWTSTAMP_FILTER_ALL);

    return 0;
}
관련 페이지: PTP (IEEE 1588) — PTP 프로토콜과 ptp4l/phc2sys 설정, 이더넷 — 네트워크 드라이버 구조, ktime / Clock — 커널 나노초 시각 API

문제 해결

증상원인진단/해결
HW 타임스탬프가 0으로 나옴SIOCSHWTSTAMP 미설정HWTSTAMP_TX_ON/HWTSTAMP_FILTER_ALL 설정
TX 타임스탬프가 오지 않음MSG_ERRQUEUE로 읽지 않음recvmsg(, MSG_ERRQUEUE) 확인
ptp4l "tx timestamp timeout"TX 완료 타임스탬프 지연tx_timestamp_timeout 증가, 드라이버 TX TS 구현 확인
ethtool -T에 phc_index -1PHC 미등록드라이버의 ptp_clock_register() 확인
타임스탬프 정밀도 μs 수준소프트웨어 타임스탬핑 사용 중SOF_TIMESTAMPING_RAW_HARDWARE + HW filter 확인