最近在学习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~