关于1588 PTP的IGB网卡驱动代码分析

7 篇文章 1 订阅
4 篇文章 7 订阅

最近在学习PTP的网卡驱动实现部分内容,所以分析一下IGB的相关代码,供大家参考。

1 初始化igb_ptp_init

igb_probe => igb_ptp_init,下面看一下igb_ptp_init。首先初始化clock结构体ptp_clock_info里的参数,这里以i210为例。

        for (i = 0; i < IGB_N_SDP; i++) {
            struct ptp_pin_desc *ppd = &adapter->sdp_config[i];

            snprintf(ppd->name, sizeof(ppd->name), "SDP%d", i);
            ppd->index = i;
            ppd->func = PTP_PF_NONE;
        }
        snprintf(adapter->ptp_caps.name, 16, "%pm", netdev->dev_addr);
        adapter->ptp_caps.owner = THIS_MODULE;
        adapter->ptp_caps.max_adj = 62499999;
        adapter->ptp_caps.n_ext_ts = IGB_N_EXTTS;
        adapter->ptp_caps.n_per_out = IGB_N_PEROUT;
        adapter->ptp_caps.n_pins = IGB_N_SDP;
        adapter->ptp_caps.pps = 1;
        adapter->ptp_caps.pin_config = adapter->sdp_config;
        adapter->ptp_caps.adjfine = igb_ptp_adjfine_82580;
        adapter->ptp_caps.adjtime = igb_ptp_adjtime_i210;
        adapter->ptp_caps.gettime64 = igb_ptp_gettime_i210;
        adapter->ptp_caps.settime64 = igb_ptp_settime_i210;
        adapter->ptp_caps.enable = igb_ptp_feature_enable_i210;
        adapter->ptp_caps.verify = igb_ptp_verify_pin;

这里需要了解struct ptp_clock_info。其中回调函数必须在成功时全部返回零,否则返回非零。

struct ptp_clock_info {
    struct module *owner;
    //一个简短的“友好名称”,用于识别时钟并帮助区分基于 PHY 的设备和基于 MAC 的设备。 
    //该字符串并不意味着是唯一的 ID。
    char name[16];
    //最大可能的频率调整,以十亿分之一为单位。
    s32 max_adj;
    int n_alarm;
    int n_ext_ts;
    int n_per_out;
    int n_pins;
    //时钟是否支持PPS回调。
    int pps;
    struct ptp_pin_desc *pin_config;

    //调整硬件时钟的频率。
    //参数 scaled_ppm:与标称频率的所需频率偏移量,单位为百万分之一,但具有 16 位二进制小数字段。
    int (*adjfine)(struct ptp_clock_info *ptp, long scaled_ppm);

    //调整硬件时钟的频率。 此方法已弃用。 新的驱动程序应该改为实现 @adjfine 方法。 
    //参数 delta:与标称频率的所需频率偏移,单位为十亿分之一
    int (*adjfreq)(struct ptp_clock_info *ptp, s32 delta);

    //改变硬件时钟的时间。 参数 delta:以纳秒为单位的所需变化。
    int (*adjtime)(struct ptp_clock_info *ptp, s64 delta);

    //从硬件时钟读取当前时间。 参数 ts:保存结果。
    int (*gettime64)(struct ptp_clock_info *ptp, struct timespec64 *ts);

    int (*getcrosststamp)(struct ptp_clock_info *ptp,
                  struct system_device_crosststamp *cts);

    //在硬件时钟上设置当前时间。 参数 ts:要设置的时间值。
    int (*settime64)(struct ptp_clock_info *p, const struct timespec64 *ts);

    //请求驱动程序启用或禁用辅助功能。 参数请求:要启用或禁用的所需资源。 
    //参数 on:调用者传递 1 以启用或传递 0 以禁用。
    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);
};

接下来,

    //初始化spinlock
    spin_lock_init(&adapter->tmreg_lock);

    //INIT_WORK会在你定义的_work工作队列里面增加一个工作任务,该任务就是igb_ptp_tx_work。
    INIT_WORK(&adapter->ptp_tx_work, igb_ptp_tx_work);

    if (adapter->ptp_flags & IGB_PTP_OVERFLOW_CHECK)
        INIT_DELAYED_WORK(&adapter->ptp_overflow_work,
                  igb_ptp_overflow_check);

    adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
    adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF;

    //此函数处理重新启用 PTP 设备所需的重置工作。
    igb_ptp_reset(adapter);

    //注册一个 PTP 硬件时钟驱动
    adapter->ptp_clock = ptp_clock_register(&adapter->ptp_caps,
                        &adapter->pdev->dev);
    //检查是否成功
    if (IS_ERR(adapter->ptp_clock)) {
        adapter->ptp_clock = NULL;
        dev_err(&adapter->pdev->dev, "ptp_clock_register failed\n");
    } else if (adapter->ptp_clock) {
        dev_info(&adapter->pdev->dev, "added PHC on %s\n",
             adapter->netdev->name);
        adapter->ptp_flags |= IGB_PTP_ENABLED;
    }

从这里开始,接下来都是IGB中ptp_clock_info init的接口。

igb_ptp_adjfreq_82580这个函数是用于微调的,对应于ptp4l中的s2之后的每次微调。如下所示,master offset在一次一次的微调。

ptp4l[202.051]: master offset        -44 s2 freq   +1184 path delay      3830
ptp4l[203.052]: master offset         76 s2 freq   +1290 path delay      3821
ptp4l[204.052]: master offset        -47 s2 freq   +1190 path delay      3830
ptp4l[205.052]: master offset        -47 s2 freq   +1176 path delay      3830
ptp4l[206.053]: master offset         15 s2 freq   +1224 path delay      3830
ptp4l[207.053]: master offset         51 s2 freq   +1265 path delay      3830
ptp4l[208.053]: master offset        -84 s2 freq   +1145 path delay      3840
ptp4l[209.054]: master offset         68 s2 freq   +1272 path delay      3840
ptp4l[210.054]: master offset         49 s2 freq   +1273 path delay      3845
ptp4l[211.055]: master offset        -37 s2 freq   +1202 path delay      3852
ptp4l[212.055]: master offset         18 s2 freq   +1246 path delay      3849
ptp4l[213.056]: master offset        -24 s2 freq   +1209 path delay      3857
ptp4l[214.056]: master offset         36 s2 freq   +1262 path delay      3857
ptp4l[215.056]: master offset        -36 s2 freq   +1201 path delay      3846
ptp4l[216.057]: master offset        -82 s2 freq   +1144 path delay      3833
ptp4l[217.057]: master offset         95 s2 freq   +1296 path delay      3833
ptp4l[218.058]: master offset        -13 s2 freq   +1217 path delay      3846
ptp4l[219.058]: master offset        -99 s2 freq   +1127 path delay      3833
static int igb_ptp_adjfreq_82580(struct ptp_clock_info *ptp, s32 ppb)
{
    struct igb_adapter *igb = container_of(ptp, struct igb_adapter,
                           ptp_caps);
    struct e1000_hw *hw = &igb->hw;
    int neg_adj = 0;
    u64 rate;
    u32 inca;

    //如果ppb是负数
    if (ppb < 0) {
        neg_adj = 1;
        ppb = -ppb;
    }
    rate = ppb;
    rate <<= 26;
    rate = div_u64(rate, 1953125);

    /* At 2.5G speeds, the TIMINCA register on I354 updates the clock 2.5x
     * as quickly. Account for this by dividing the adjustment by 2.5.
     */
    if (hw->mac.type == e1000_i354) {
        u32 status = E1000_READ_REG(hw, E1000_STATUS);

        if ((status & E1000_STATUS_2P5_SKU) &&
            !(status & E1000_STATUS_2P5_SKU_OVER)) {
            rate <<= 1;
            rate = div_u64(rate, 5);
        }
    }

    inca = rate & INCVALUE_MASK;
    if (neg_adj)
        inca |= ISGN;

    //这个reg第一位是看正负的Increment sign,其余是Increment value。
    E1000_WRITE_REG(hw, E1000_TIMINCA, inca);

    return 0;
}

igb_ptp_adjtime_i210执行后,对应ptp4l中s1到s2的那一次大跳。由于master和slave之间的master offset可能较大,如果一直都是微调,需要很久,所以需要一次大跳,一次大幅度拉近两个平台之间的时间差距。如下所示,s2的master offset明显要更小。

ptp4l[177.904]: selected /dev/ptp0 as PTP clock
ptp4l[177.904]: port 1: INITIALIZING to LISTENING on INITIALIZE
ptp4l[177.904]: port 0: INITIALIZING to LISTENING on INITIALIZE
ptp4l[177.904]: port 1: link up
ptp4l[178.043]: port 1: new foreign master 00e04c.fffe.680022-1
ptp4l[182.043]: selected best master clock 00e04c.fffe.680022
ptp4l[182.044]: port 1: LISTENING to UNCALIBRATED on RS_SLAVE
ptp4l[183.044]: master offset -1549493099 s0 freq      +0 path delay         0
ptp4l[184.044]: master offset -1549495341 s1 freq   -2242 path delay      3466
ptp4l[185.045]: master offset        3468 s2 freq   +1226 path delay      3466
ptp4l[185.045]: port 1: UNCALIBRATED to SLAVE on MASTER_CLOCK_SELECTED
ptp4l[186.045]: master offset        3495 s2 freq   +2294 path delay      3466
ptp4l[187.045]: master offset        2005 s2 freq   +1852 path delay      3751
static int igb_ptp_adjtime_i210(struct ptp_clock_info *ptp, s64 delta)
{
    struct igb_adapter *igb = container_of(ptp, struct igb_adapter,
                           ptp_caps);
    unsigned long flags;
    struct timespec64 now, then = ns_to_timespec64(delta);

    spin_lock_irqsave(&igb->tmreg_lock, flags);

    igb_ptp_read_i210(igb, &now);
    now = timespec64_add(now, then);
    igb_ptp_write_i210(igb, (const struct timespec64 *)&now);

    spin_unlock_irqrestore(&igb->tmreg_lock, flags);

    return 0;
}
static int igb_ptp_gettime_i210(struct ptp_clock_info *ptp,
                struct timespec64 *ts)
{
    struct igb_adapter *igb = container_of(ptp, struct igb_adapter,
                           ptp_caps);
    unsigned long flags;

    spin_lock_irqsave(&igb->tmreg_lock, flags);

    igb_ptp_read_i210(igb, ts);

    spin_unlock_irqrestore(&igb->tmreg_lock, flags);

    return 0;
}
static int igb_ptp_settime_i210(struct ptp_clock_info *ptp,
                const struct timespec64 *ts)
{
    struct igb_adapter *igb = container_of(ptp, struct igb_adapter,
                           ptp_caps);
    unsigned long flags;

    spin_lock_irqsave(&igb->tmreg_lock, flags);

    igb_ptp_write_i210(igb, ts);

    spin_unlock_irqrestore(&igb->tmreg_lock, flags);

    return 0;
}
static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
                       struct ptp_clock_request *rq, int on)
{
    struct igb_adapter *igb =
        container_of(ptp, struct igb_adapter, ptp_caps);
    struct e1000_hw *hw = &igb->hw;
    u32 tsauxc, tsim, tsauxc_mask, tsim_mask, trgttiml, trgttimh, freqout;
    unsigned long flags;
    struct timespec64 ts;
    int use_freq = 0, pin = -1;
    s64 ns;

    switch (rq->type) {
    case PTP_CLK_REQ_EXTTS:
        if (on) {
            pin = ptp_find_pin(igb->ptp_clock, PTP_PF_EXTTS,
                       rq->extts.index);
            if (pin < 0)
                return -EBUSY;
        }
        if (rq->extts.index == 1) {
            tsauxc_mask = TSAUXC_EN_TS1;
            tsim_mask = TSINTR_AUTT1;
        } else {
            tsauxc_mask = TSAUXC_EN_TS0;
            tsim_mask = TSINTR_AUTT0;
        }
        spin_lock_irqsave(&igb->tmreg_lock, flags);
        tsauxc = rd32(E1000_TSAUXC);
        tsim = rd32(E1000_TSIM);
        if (on) {
            igb_pin_extts(igb, rq->extts.index, pin);
            tsauxc |= tsauxc_mask;
            tsim |= tsim_mask;
        } else {
            tsauxc &= ~tsauxc_mask;
            tsim &= ~tsim_mask;
        }
        wr32(E1000_TSAUXC, tsauxc);
        wr32(E1000_TSIM, tsim);
        spin_unlock_irqrestore(&igb->tmreg_lock, flags);
        return 0;

    case PTP_CLK_REQ_PEROUT:
        if (on) {
            pin = ptp_find_pin(igb->ptp_clock, PTP_PF_PEROUT,
                       rq->perout.index);
            if (pin < 0)
                return -EBUSY;
        }
        ts.tv_sec = rq->perout.period.sec;
        ts.tv_nsec = rq->perout.period.nsec;
        ns = timespec64_to_ns(&ts);
        ns = ns >> 1;
        if (on && ((ns <= 70000000LL) || (ns == 125000000LL) ||
               (ns == 250000000LL) || (ns == 500000000LL))) {
            if (ns < 8LL)
                return -EINVAL;
            use_freq = 1;
        }
        ts = ns_to_timespec64(ns);
        if (rq->perout.index == 1) {
            if (use_freq) {
                tsauxc_mask = TSAUXC_EN_CLK1 | TSAUXC_ST1;
                tsim_mask = 0;
            } else {
                tsauxc_mask = TSAUXC_EN_TT1;
                tsim_mask = TSINTR_TT1;
            }
            trgttiml = E1000_TRGTTIML1;
            trgttimh = E1000_TRGTTIMH1;
            freqout = E1000_FREQOUT1;
        } else {
            if (use_freq) {
                tsauxc_mask = TSAUXC_EN_CLK0 | TSAUXC_ST0;
                tsim_mask = 0;
            } else {
                tsauxc_mask = TSAUXC_EN_TT0;
                tsim_mask = TSINTR_TT0;
            }
            trgttiml = E1000_TRGTTIML0;
            trgttimh = E1000_TRGTTIMH0;
            freqout = E1000_FREQOUT0;
        }
        spin_lock_irqsave(&igb->tmreg_lock, flags);
        tsauxc = rd32(E1000_TSAUXC);
        tsim = rd32(E1000_TSIM);
        if (rq->perout.index == 1) {
            tsauxc &= ~(TSAUXC_EN_TT1 | TSAUXC_EN_CLK1 | TSAUXC_ST1);
            tsim &= ~TSINTR_TT1;
        } else {
            tsauxc &= ~(TSAUXC_EN_TT0 | TSAUXC_EN_CLK0 | TSAUXC_ST0);
            tsim &= ~TSINTR_TT0;
        }
        if (on) {
            int i = rq->perout.index;
            igb_pin_perout(igb, i, pin, use_freq);
            igb->perout[i].start.tv_sec = rq->perout.start.sec;
            igb->perout[i].start.tv_nsec = rq->perout.start.nsec;
            igb->perout[i].period.tv_sec = ts.tv_sec;
            igb->perout[i].period.tv_nsec = ts.tv_nsec;
            wr32(trgttimh, rq->perout.start.sec);
            wr32(trgttiml, rq->perout.start.nsec);
            if (use_freq)
                wr32(freqout, ns);
            tsauxc |= tsauxc_mask;
            tsim |= tsim_mask;
        }
        wr32(E1000_TSAUXC, tsauxc);
        wr32(E1000_TSIM, tsim);
        spin_unlock_irqrestore(&igb->tmreg_lock, flags);
        return 0;

    //这里稍微看一下pps
    case PTP_CLK_REQ_PPS:
        spin_lock_irqsave(&igb->tmreg_lock, flags);
        tsim = rd32(E1000_TSIM);
        if (on)
            tsim |= TSINTR_SYS_WRAP;
        else
            tsim &= ~TSINTR_SYS_WRAP;
        igb->pps_sys_wrap_on = !!on;
        wr32(E1000_TSIM, tsim);
        spin_unlock_irqrestore(&igb->tmreg_lock, flags);
        return 0;
    }

    return -EOPNOTSUPP;
}

2 TX

2.1 发包igb_xmit_frame_ring

igb_xmit_frame_ring会查看skb内的HW timestamp的标志即check skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP(master的SYNC packet,slave的DelayRequest packet)。

    //检查 SKBTX_HW_TSTAMP 标志,该标志表示用户请求了硬件时间戳。
    if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) {
        struct igb_adapter *adapter = netdev_priv(tx_ring->netdev);

        //确认为传出数据包启用硬件时间戳;一次只能给一个数据包加时间戳
        if (adapter->tstamp_config.tx_type & HWTSTAMP_TX_ON &&
            !test_and_set_bit_lock(__IGB_PTP_TX_IN_PROGRESS,
                       &adapter->state)) {

            //给正在打时间戳的 skb 上设置了 SKBTX_IN_PROGRESS 标志。
            skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;

            //然后更新 tx_flags,将 IGB_TX_FLAGS_TSTAMP 标志置位。tx_flags 变量稍后将被复制到缓冲区信息结构中。
            tx_flags |= IGB_TX_FLAGS_TSTAMP;

            adapter->ptp_tx_skb = skb_get(skb);
            
            //当前的 jiffies 值赋给 ptp_tx_start。驱动程序中的其他代码将使用这个值,以确保 TX 硬件打时间戳不会 hang 住。
            adapter->ptp_tx_start = jiffies;

            //如果这是一个 82576 以太网硬件网卡,将用 schedule_work 函数启动工作队列。            
            if (adapter->hw.mac.type == e1000_82576)
                schedule_work(&adapter->ptp_tx_work);
        } else {
            adapter->tx_hwtstamp_skipped++;
        }
    }

其他网卡是通过igb_tsync_interrupt来调用ptp_tx_work的,如下igb_msix_other。

static irqreturn_t igb_msix_other(int irq, void *data)
{
    struct igb_adapter *adapter = data;
    struct e1000_hw *hw = &adapter->hw;
    u32 icr = rd32(E1000_ICR);
    /* reading ICR causes bit 31 of EICR to be cleared */

    if (icr & E1000_ICR_DRSTA)
        schedule_work(&adapter->reset_task);

    if (icr & E1000_ICR_DOUTSYNC) {
        /* HW is reporting DMA is out of sync */
        adapter->stats.doosync++;
        /* The DMA Out of Sync is also indication of a spoof event
         * in IOV mode. Check the Wrong VM Behavior register to
         * see if it is really a spoof event.
         */
        igb_check_wvbr(adapter);
    }

    /* Check for a mailbox event */
    if (icr & E1000_ICR_VMMB)
        igb_msg_task(adapter);

    if (icr & E1000_ICR_LSC) {
        hw->mac.get_link_status = 1;
        /* guard against interrupt when we're going down */
        if (!test_bit(__IGB_DOWN, &adapter->state))
            mod_timer(&adapter->watchdog_timer, jiffies + 1);
    }
    /********************** 这里这里 ****************************/
    if (icr & E1000_ICR_TS)
        igb_tsync_interrupt(adapter);

    wr32(E1000_EIMS, adapter->eims_other);

    return IRQ_HANDLED;
}
#define TSINTR_SYS_WRAP  BIT(0) /* SYSTIM Wrap around. */
#define TSINTR_TXTS      BIT(1) /* Transmit Timestamp. */
#define TSINTR_RXTS      BIT(2) /* Receive Timestamp. */
#define TSINTR_TT0       BIT(3) /* Target Time 0 Trigger. */
#define TSINTR_TT1       BIT(4) /* Target Time 1 Trigger. */
#define TSINTR_AUTT0     BIT(5) /* Auxiliary Timestamp 0 Taken. */
#define TSINTR_AUTT1     BIT(6) /* Auxiliary Timestamp 1 Taken. */
#define TSINTR_TADJ      BIT(7) /* Time Adjust Done. */

static void igb_tsync_interrupt(struct igb_adapter *adapter)
{
    struct e1000_hw *hw = &adapter->hw;
    struct ptp_clock_event event;
    struct timespec64 ts;
    u32 ack = 0, tsauxc, sec, nsec, tsicr = rd32(E1000_TSICR);

    if (tsicr & TSINTR_SYS_WRAP) {
        event.type = PTP_CLOCK_PPS;
        if (adapter->ptp_caps.pps)
            ptp_clock_event(adapter->ptp_clock, &event);
        ack |= TSINTR_SYS_WRAP;
    }

    if (tsicr & E1000_TSICR_TXTS) {
        /* retrieve hardware timestamp */
        schedule_work(&adapter->ptp_tx_work);
        ack |= E1000_TSICR_TXTS;
    }

    if (tsicr & TSINTR_TT0) {
        spin_lock(&adapter->tmreg_lock);
        ts = timespec64_add(adapter->perout[0].start,
                    adapter->perout[0].period);
        /* u32 conversion of tv_sec is safe until y2106 */
        wr32(E1000_TRGTTIML0, ts.tv_nsec);
        wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
        tsauxc = rd32(E1000_TSAUXC);
        tsauxc |= TSAUXC_EN_TT0;
        wr32(E1000_TSAUXC, tsauxc);
        adapter->perout[0].start = ts;
        spin_unlock(&adapter->tmreg_lock);
        ack |= TSINTR_TT0;
    }

    if (tsicr & TSINTR_TT1) {
        spin_lock(&adapter->tmreg_lock);
        ts = timespec64_add(adapter->perout[1].start,
                    adapter->perout[1].period);
        wr32(E1000_TRGTTIML1, ts.tv_nsec);
        wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
        tsauxc = rd32(E1000_TSAUXC);
        tsauxc |= TSAUXC_EN_TT1;
        wr32(E1000_TSAUXC, tsauxc);
        adapter->perout[1].start = ts;
        spin_unlock(&adapter->tmreg_lock);
        ack |= TSINTR_TT1;
    }

    if (tsicr & TSINTR_AUTT0) {
        nsec = rd32(E1000_AUXSTMPL0);
        sec  = rd32(E1000_AUXSTMPH0);
        event.type = PTP_CLOCK_EXTTS;
        event.index = 0;
        event.timestamp = sec * 1000000000ULL + nsec;
        ptp_clock_event(adapter->ptp_clock, &event);
        ack |= TSINTR_AUTT0;
    }

    if (tsicr & TSINTR_AUTT1) {
        nsec = rd32(E1000_AUXSTMPL1);
        sec  = rd32(E1000_AUXSTMPH1);
        event.type = PTP_CLOCK_EXTTS;
        event.index = 1;
        event.timestamp = sec * 1000000000ULL + nsec;
        ptp_clock_event(adapter->ptp_clock, &event);
        ack |= TSINTR_AUTT1;
    }

    /* acknowledge the interrupts */
    wr32(E1000_TSICR, ack);
}

2.2 igb_ptp_tx_work

不管如何,都会执行schedule_work(&adapter->ptp_tx_work);这句代码,进而执行这个函数igb_ptp_tx_work。此工作函数轮询 TSYNCTXCTL 有效位以确定何时为当前存储的 skb 获取了时间戳。

static void igb_ptp_tx_work(struct work_struct *work)
{
    struct igb_adapter *adapter = container_of(work, struct igb_adapter,
                           ptp_tx_work);
    struct e1000_hw *hw = &adapter->hw;
    u32 tsynctxctl;

    if (!adapter->ptp_tx_skb)
        return;

    //如果time_is_before_jiffies,那么这次无效
    if (time_is_before_jiffies(adapter->ptp_tx_start +
                   IGB_PTP_TX_TIMEOUT)) {
        dev_kfree_skb_any(adapter->ptp_tx_skb);
        adapter->ptp_tx_skb = NULL;
        clear_bit_unlock(__IGB_PTP_TX_IN_PROGRESS, &adapter->state);
        adapter->tx_hwtstamp_timeouts++;
        dev_warn(&adapter->pdev->dev, "clearing Tx timestamp hang\n");
        return;
    }

    tsynctxctl = rd32(E1000_TSYNCTXCTL);
    //发送时间戳有效(当在 Tx 时间戳寄存器中捕获 Tx 时间戳的有效值时等于 1b,
    //通过读取 Tx 时间戳寄存器 TXSTMPH 清除)。
    if (tsynctxctl & E1000_TSYNCTXCTL_VALID)
        igb_ptp_tx_hwtstamp(adapter);
    else
        /* reschedule to check later */
        schedule_work(&adapter->ptp_tx_work);
}
/**
 * igb_ptp_tx_hwtstamp - utility function which checks for TX time stamp
 * @adapter: Board private structure.
 *
 * If we were asked to do hardware stamping and such a time stamp is
 * available, then it must have been for this skb here because we only
 * allow only one such packet into the queue.
 * 如果我们被要求做硬件标记,并且有这样的时间戳,
 * 那么这里肯定是针对这个 skb 的,因为我们只允许一个这样的数据包进入队列。
 **/
static void igb_ptp_tx_hwtstamp(struct igb_adapter *adapter)
{
    struct sk_buff *skb = adapter->ptp_tx_skb;
    struct e1000_hw *hw = &adapter->hw;
    struct skb_shared_hwtstamps shhwtstamps;
    u64 regval;
    int adjust = 0;

    //Tx timestamp value Low
    regval = rd32(E1000_TXSTMPL);
    //Tx timestamp value High
    regval |= (u64)rd32(E1000_TXSTMPH) << 32;

    igb_ptp_systim_to_hwtstamp(adapter, &shhwtstamps, regval);
    /* adjust timestamp for the TX latency based on link speed */
    if (adapter->hw.mac.type == e1000_i210) {
        switch (adapter->link_speed) {
        case SPEED_10:
            adjust = IGB_I210_TX_LATENCY_10;
            break;
        case SPEED_100:
            adjust = IGB_I210_TX_LATENCY_100;
            break;
        case SPEED_1000:
            adjust = IGB_I210_TX_LATENCY_1000;
            break;
        }
    }

    shhwtstamps.hwtstamp =
        ktime_add_ns(shhwtstamps.hwtstamp, adjust);

    /* Clear the lock early before calling skb_tstamp_tx so that
     * applications are not woken up before the lock bit is clear. We use
     * a copy of the skb pointer to ensure other threads can't change it
     * while we're notifying the stack.
     * 在调用 skb_tstamp_tx 之前尽早清除锁定,以便在锁定位清除之前应用程序不会被唤醒。 
     * 我们使用 skb 指针的副本来确保在我们通知堆栈时其他线程无法更改它。
     */
    adapter->ptp_tx_skb = NULL;
    clear_bit_unlock(__IGB_PTP_TX_IN_PROGRESS, &adapter->state);

    /* Notify the stack and free the skb after we've unlocked */
    //解锁后通知堆栈并释放 skb
    skb_tstamp_tx(skb, &shhwtstamps);
    dev_kfree_skb_any(skb);
}
/**
 * igb_ptp_systim_to_hwtstamp - convert system time value to hw timestamp
 * 将系统时间值转换为硬件时间戳
 * @adapter: board private structure
 * @hwtstamps: timestamp structure to update
 * @systim: unsigned 64bit system time value.
 *
 * We need to convert the system time value stored in the RX/TXSTMP registers
 * into a hwtstamp which can be used by the upper level timestamping functions.
 * 我们需要将存储在RX/TXSTMP 寄存器中的系统时间值转换成hwtstamp 供上层时间戳函数使用
 *
 * The 'tmreg_lock' spinlock is used to protect the consistency of the
 * system time value. This is needed because reading the 64 bit time
 * value involves reading two (or three) 32 bit registers. The first
 * read latches the value. Ditto for writing.
 * 'tmreg_lock' 自旋锁用于保护系统时间值的一致性。 
 * 这是必需的,因为读取 64 位时间值涉及读取两个(或三个)32 位寄存器。 
 * 第一次读取锁存值。 写也是如此。
 *
 * In addition, here have extended the system time with an overflow
 * counter in software.
 * 另外,这里在软件中用溢出计数器延长了系统时间。
 **/
static void igb_ptp_systim_to_hwtstamp(struct igb_adapter *adapter,
                       struct skb_shared_hwtstamps *hwtstamps,
                       u64 systim)
{
    unsigned long flags;
    u64 ns;

    switch (adapter->hw.mac.type) {
    case e1000_82576:
    case e1000_82580:
    case e1000_i354:
    case e1000_i350:
        spin_lock_irqsave(&adapter->tmreg_lock, flags);

        ns = timecounter_cyc2time(&adapter->tc, systim);

        spin_unlock_irqrestore(&adapter->tmreg_lock, flags);

        memset(hwtstamps, 0, sizeof(*hwtstamps));
        hwtstamps->hwtstamp = ns_to_ktime(ns);
        break;
    case e1000_i210:
    case e1000_i211:
        memset(hwtstamps, 0, sizeof(*hwtstamps));
        /* Upper 32 bits contain s, lower 32 bits contain ns. */
        hwtstamps->hwtstamp = ktime_set(systim >> 32,
                        systim & 0xFFFFFFFF);
        break;
    default:
        break;
    }
}

3 RX

在RX函数中,最终都会调用检索函数igb_ptp_rx_pktstamp,这个函数会把timestamp给skb。

3.1 igb_ptp_rx_pktstamp

/**
 * igb_ptp_rx_pktstamp - retrieve Rx per packet timestamp
 * 检索每个数据包时间戳的 Rx
 * @q_vector: Pointer to interrupt specific structure
 * @va: Pointer to address containing Rx buffer
 * @skb: Buffer containing timestamp and packet
 *
 * This function is meant to retrieve a timestamp from the first buffer of an
 * incoming frame.  The value is stored in little endian format starting on
 * byte 8.
 * 此函数旨在从传入帧的第一个缓冲区中检索时间戳。 该值以从字节 8 开始的小端格式存储。
 **/
void igb_ptp_rx_pktstamp(struct igb_q_vector *q_vector, void *va,
             struct sk_buff *skb)
{
    __le64 *regval = (__le64 *)va;
    struct igb_adapter *adapter = q_vector->adapter;
    int adjust = 0;

    /* The timestamp is recorded in little endian format.
     * DWORD: 0        1        2        3
     * Field: Reserved Reserved SYSTIML  SYSTIMH
     */
    //这个函数上面有
    igb_ptp_systim_to_hwtstamp(adapter, skb_hwtstamps(skb),
                   le64_to_cpu(regval[1]));

    /* adjust timestamp for the RX latency based on link speed */
    if (adapter->hw.mac.type == e1000_i210) {
        switch (adapter->link_speed) {
        case SPEED_10:
            adjust = IGB_I210_RX_LATENCY_10;
            break;
        case SPEED_100:
            adjust = IGB_I210_RX_LATENCY_100;
            break;
        case SPEED_1000:
            adjust = IGB_I210_RX_LATENCY_1000;
            break;
        }
    }
    //注意这里是sub而上面是add,这是tx和rx的区别
    skb_hwtstamps(skb)->hwtstamp =
        ktime_sub_ns(skb_hwtstamps(skb)->hwtstamp, adjust);
}

如果觉得这篇文章有用的话,可以点赞、评论或者收藏,万分感谢,goodbye~

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1588 PTP 2019是指2019年的一场PTP(Press to Press)比赛。PTP比赛是指在印刷行业中的一种竞技方式,参赛者需要在规定时间内完成印刷作业,并且保证印刷品的质量和数量达到要求。 在1588 PTP 2019比赛中,参赛者需要通过操控印刷机器进行印刷工作,具体的印刷任务可能涉及不同的印刷产品,如书籍、杂志、宣传单张等。 这个比赛的目的是为了检验参赛者的印刷技术水平和操作能力。在规定的时间内,参赛者需要快速准确地进行印刷工作,确保印刷品的质量符合标准要求。评判标准通常包括印刷准确度、色彩还原度、印刷线条清晰度等。 参加1588 PTP 2019比赛的参赛者通常是来自印刷行业的专业人士,他们需要具备熟练的印刷技术知识和操作经验。通过这样的比赛,不仅可以展示自己的技术实力,还可以与其他同行进行技术交流和学习。 PTP比赛在印刷行业中具有一定的影响力,参赛者的表现也可以为他们的个人发展和职业发展增加一定的竞争力。因此,1588 PTP 2019比赛对于参赛者来说是一个重要的机会,他们可以通过这个平台展示自己的专业素养和能力。 总之,1588 PTP 2019是一场印刷行业中的比赛,目的是检验参赛者的印刷技术水平和操作能力。通过比赛,参赛者可以展示自己的专业素养和能力,也可以与其他同行进行技术交流和学习。这对于参赛者的个人发展和职业发展有一定的影响。 ### 回答2: 1588 PTP 是指1588 Precision Time Protocol,是一种用于实现精确时间同步的网络协议。2019年,1588 PTP 继续发展并被广泛应用于各个领域。 1588 PTP 的主要作用是通过在网络中传输时间信息,实现精确的时间同步。它通过在网络节点之间进行时钟同步和时间戳的传输,使得各个节点的时间保持一致。这在一些对时间同步要求较高的领域非常重要,比如金融交易、电力系统、工业自动化等。 1588 PTP 使用主从结构,其中主节点负责发送时间信息,从节点接收并根据主节点的时间源进行时钟同步。主节点一般是高精度时钟,例如GPS信号或原子钟,从节点则可以是一般的网络设备。主节点通过发送时间戳报文,从节点接收并根据报文计算时间误差,从而进行时钟调整。 2019年,1588 PTP 被广泛应用于各个领域,特别是在物联网和工业互联网方面有着重要的作用。随着物联网设备和传感器的普及,对时间同步的需求也越来越高。1588 PTP 作为一种高精度的时间同步技术,为物联网设备提供了时间基准,确保各个设备之间的数据同步和协调。 总之,1588 PTP 是一种重要的网络协议,用于实现精确时间同步。2019年,它继续发展并广泛应用于各个领域,为物联网和工业互联网等领域提供了高精度的时间同步技术。 ### 回答3: 1588 PTP是一种精确时间协议,用于同步网络设备中的时钟。它是一种同步网络时钟的方式,能够精确到微秒级别。1588 PTP使得网络中的各个设备能够以统一的时基进行工作,提供了可靠的时钟同步。 2019年的1588 PTP主要是指该年份中在1588 PTP技术上的一些进展和应用。在2019年,1588 PTP技术得到了广泛应用和发展,被多个领域的网络设备所采用。 在电信领域,1588 PTP被用于同步通信基站和网络节点之间的时钟,确保数据通信的准确性和可靠性。在工业自动化领域,1588 PTP可以同步各种工控设备的时钟,实现高精度的数据采集和控制。在金融领域,1588 PTP被用于高频交易系统中,确保交易的准确性和时序一致性。 此外,1588 PTP在医疗、交通、能源等领域也得到了应用。它的高精度同步能够提供可靠的数据传输和控制,使得网络设备能够在同一时刻进行协同工作。 综上所述,1588 PTP是一种精确时间协议,用于同步网络设备中的时钟。2019年的1588 PTP在多个领域得到了广泛应用,为各行业的网络设备提供了可靠的时钟同步功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值