PTP 对时协议 IEEE1588 网络对时 硬件基础

news2024/11/19 3:24:38

前言

在很多应用场景有精确对时的需求,例如车载网络,音视频流,工业网络。本文档将会阐述对时的硬件需求。

协议

流行的协议为 IEEE1588 标准指定的对时方法,名为 PTP 对时协议。

网卡硬件要求

找到某型网卡的特性描述:Standard for a Precision Clock Synchronization Protocol for Networked Measurement and Control Systems, Standard 1588-2008, IEEE

网卡相关寄存器

  • MAC_Timestamp_Control
  • MAC_Sub_Second_Increment
  • MAC_System_Time_Seconds
  • MAC_System_Time_Nanoseconds
  • MAC_System_Time_Seconds_Update
  • MAC_System_Time_Nanoseconds_Update
  • MAC_Timestamp_Addend
  • MAC_System_Time_Higher_Word_Seconds
  • Timestamp Status register

MAC_Sub_Second_Increment 用于设置系统亚秒增加

Timestamp Addend register的值会被用于微调系统时间,在Fine Update模式下,系统会根据这个值以微小的步长来更新时间。这样可以实现对系统时间的精细调整,以满足精确时间同步的需求。

MAC_System_Time_Nanoseconds + MAC_System_Time_Seconds 从这2个寄存器读出时间

MAC_System_Time_Nanoseconds_Update + MAC_System_Time_Seconds_Update 写入这2个寄存器配置时间

配置代码

tstamp 的初始化代码

配置了 MAC_Sub_Second_Increment 和 Timestamp Addend register

/**
 * stmmac_init_tstamp_counter - init hardware timestamping counter
 * @priv: driver private structure
 * @systime_flags: timestamping flags
 * Description:
 * Initialize hardware counter for packet timestamping.
 * This is valid as long as the interface is open and not suspended.
 * Will be rerun after resuming from suspend, case in which the timestamping
 * flags updated by stmmac_hwtstamp_set() also need to be restored.
 */
int stmmac_init_tstamp_counter(struct stmmac_priv *priv, u32 systime_flags)
{
	bool xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac;
	struct timespec64 now;
	u32 sec_inc = 0;
	u64 temp = 0;
	int ret;

	if (!(priv->dma_cap.time_stamp || priv->dma_cap.atime_stamp))
		return -EOPNOTSUPP;

	ret = clk_prepare_enable(priv->plat->clk_ptp_ref);
	if (ret < 0) {
		netdev_warn(priv->dev,
			    "failed to enable PTP reference clock: %pe\n",
			    ERR_PTR(ret));
		return ret;
	}

	stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags);
	priv->systime_flags = systime_flags;

	/* program Sub Second Increment reg */// 配置 MAC_Sub_Second_Increment 
	stmmac_config_sub_second_increment(priv, priv->ptpaddr,
					   priv->plat->clk_ptp_rate,
					   xmac, &sec_inc);
	temp = div_u64(1000000000ULL, sec_inc);

	/* Store sub second increment for later use */
	priv->sub_second_inc = sec_inc;

//配置 Timestamp Addend register
	/* calculate default added value:
	 * formula is :
	 * addend = (2^32)/freq_div_ratio;
	 * where, freq_div_ratio = 1e9ns/sec_inc
	 */
	temp = (u64)(temp << 32);
	priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate);
	stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend);

	/* initialize system time */
	#ifdef CONFIG_SEMIDRIVE_TIME_SYNC
	now.tv_sec = 0;
	now.tv_nsec = 0;
	#else
	ktime_get_real_ts64(&now);
	#endif

	/* lower 32 bits of tv_sec are safe until y2106 */
	stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec);

	return 0;
}
更新时间的代码
#include "stmmac.h"
#include "stmmac_ptp.h"

/**
 * stmmac_adjust_freq
 *
 * @ptp: pointer to ptp_clock_info structure
 * @ppb: desired period change in parts ber billion
 *
 * Description: this function will adjust the frequency of hardware clock.
 */
static int stmmac_adjust_freq(struct ptp_clock_info *ptp, s32 ppb)
{
	struct stmmac_priv *priv =
	    container_of(ptp, struct stmmac_priv, ptp_clock_ops);
	unsigned long flags;
	u32 diff, addend;
	int neg_adj = 0;
	u64 adj;

	if (ppb < 0) {
		neg_adj = 1;
		ppb = -ppb;
	}

	addend = priv->default_addend;
	adj = addend;
	adj *= ppb;
	diff = div_u64(adj, 1000000000ULL);
	addend = neg_adj ? (addend - diff) : (addend + diff);

	spin_lock_irqsave(&priv->ptp_lock, flags);
	stmmac_config_addend(priv, priv->ptpaddr, addend);
	spin_unlock_irqrestore(&priv->ptp_lock, flags);

	return 0;
}

/**
 * stmmac_adjust_time
 *
 * @ptp: pointer to ptp_clock_info structure
 * @delta: desired change in nanoseconds
 *
 * Description: this function will shift/adjust the hardware clock time.
 */
static int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta)
{
	struct stmmac_priv *priv =
	    container_of(ptp, struct stmmac_priv, ptp_clock_ops);
	unsigned long flags;
	u32 sec, nsec;
	u32 quotient, reminder;
	int neg_adj = 0;
	bool xmac;

	xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac;

	if (delta < 0) {
		neg_adj = 1;
		delta = -delta;
	}

	quotient = div_u64_rem(delta, 1000000000ULL, &reminder);
	sec = quotient;
	nsec = reminder;

	spin_lock_irqsave(&priv->ptp_lock, flags);
	stmmac_adjust_systime(priv, priv->ptpaddr, sec, nsec, neg_adj, xmac);
	spin_unlock_irqrestore(&priv->ptp_lock, flags);

	return 0;
}

/**
 * stmmac_get_time
 *
 * @ptp: pointer to ptp_clock_info structure
 * @ts: pointer to hold time/result
 *
 * Description: this function will read the current time from the
 * hardware clock and store it in @ts.
 */
static int stmmac_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
	struct stmmac_priv *priv =
	    container_of(ptp, struct stmmac_priv, ptp_clock_ops);
	unsigned long flags;
	u64 ns = 0;

	spin_lock_irqsave(&priv->ptp_lock, flags);
	stmmac_get_systime(priv, priv->ptpaddr, &ns);
	spin_unlock_irqrestore(&priv->ptp_lock, flags);

	*ts = ns_to_timespec64(ns);

	return 0;
}

/**
 * stmmac_set_time
 *
 * @ptp: pointer to ptp_clock_info structure
 * @ts: time value to set
 *
 * Description: this function will set the current time on the
 * hardware clock.
 */
static int stmmac_set_time(struct ptp_clock_info *ptp,
			   const struct timespec64 *ts)
{
	struct stmmac_priv *priv =
	    container_of(ptp, struct stmmac_priv, ptp_clock_ops);
	unsigned long flags;

	spin_lock_irqsave(&priv->ptp_lock, flags);
	stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec);
	spin_unlock_irqrestore(&priv->ptp_lock, flags);

	return 0;
}

static int stmmac_enable(struct ptp_clock_info *ptp,
			 struct ptp_clock_request *rq, int on)
{
	struct stmmac_priv *priv =
	    container_of(ptp, struct stmmac_priv, ptp_clock_ops);
	struct stmmac_pps_cfg *cfg;
	int ret = -EOPNOTSUPP;
	unsigned long flags;

	switch (rq->type) {
	case PTP_CLK_REQ_PEROUT:
		/* Reject requests with unsupported flags */
		if (rq->perout.flags)
			return -EOPNOTSUPP;

		cfg = &priv->pps[rq->perout.index];

		cfg->start.tv_sec = rq->perout.start.sec;
		cfg->start.tv_nsec = rq->perout.start.nsec;
		cfg->period.tv_sec = rq->perout.period.sec;
		cfg->period.tv_nsec = rq->perout.period.nsec;

		spin_lock_irqsave(&priv->ptp_lock, flags);
		ret = stmmac_flex_pps_config(priv, priv->ioaddr,
					     rq->perout.index, cfg, on,
					     priv->sub_second_inc,
					     priv->systime_flags);
		spin_unlock_irqrestore(&priv->ptp_lock, flags);
		break;
	default:
		break;
	}

	return ret;
}

时间使用

上面配置了网卡相关的硬件时间寄存器,那么网卡具有了硬件计时的能力了,在 PTP 对时中如何使用这个能力呢?

发送报文时间

如何获得报文从网卡发送的精确时间?很容易想到的办法就是在触发网卡DMA发送的时刻马上读上面提到的时间寄存器。这种方法可能引入几个问题:若是这个连续操作被中断打断了怎么办?若是网卡队列里还有其他没有发送的其他包,软件触发了DMA发送,但是硬件并没有及时发送出去怎么办?
实际解决办法是:由网卡的硬件实现的,网卡在把网络包发送出去的同时,往指定的内存里保存发送的时间数据,这个指定的内存就是发送描述符。
在这里插入图片描述
代码里这样读取

static inline void dwmac4_get_timestamp(void *desc, u32 ats, u64 *ts)
{
	struct dma_desc *p = (struct dma_desc *)desc;
	u64 ns;

	ns = le32_to_cpu(p->des0);  // 读描述符
	/* convert high/sec time stamp value to nanosecond */
	ns += le32_to_cpu(p->des1) * 1000000000ULL;

	*ts = ns;
}


/* stmmac_get_tx_hwtstamp - get HW TX timestamps
 * @priv: driver private structure
 * @p : descriptor pointer
 * @skb : the socket buffer
 * Description :
 * This function will read timestamp from the descriptor & pass it to stack.
 * and also perform some sanity checks.
 */
static void stmmac_get_tx_hwtstamp(struct stmmac_priv *priv,
				   struct dma_desc *p, struct sk_buff *skb)
{
	struct skb_shared_hwtstamps shhwtstamp;
	bool found = false;
	u64 ns = 0;

	if (!priv->hwts_tx_en)
		return;

	/* exit if skb doesn't support hw tstamp */
	if (likely(!skb || !(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)))
		return;

	/* check tx tstamp status */
	if (stmmac_get_tx_timestamp_status(priv, p)) {
		stmmac_get_timestamp(priv, p, priv->adv_ts, &ns);
		found = true;
	} else if (!stmmac_get_mac_tx_timestamp(priv, priv->hw, &ns)) {
		found = true;
	}

	if (found) {
		memset(&shhwtstamp, 0, sizeof(struct skb_shared_hwtstamps));
		shhwtstamp.hwtstamp = ns_to_ktime(ns);

		netdev_dbg(priv->dev, "get valid TX hw timestamp %llu\n", ns);
		/* pass tstamp to stack */
		skb_tstamp_tx(skb, &shhwtstamp); // 保存到协议栈里
	}
}

/**
 * stmmac_tx_clean - to manage th
 * e transmission completion
 * @priv: driver private structure
 * @budget: napi budget limiting this functions packet handling
 * @queue: TX queue index
 * Description: it reclaims the transmit resources after transmission completes.
 */
static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue)
{
	struct stmmac_tx_queue *tx_q = &priv->tx_queue[queue];
	unsigned int bytes_compl = 0, pkts_compl = 0;
	unsigned int entry, count = 0;


省略部分代码
		/* Make sure descriptor fields are read after reading
		 * the own bit.
		 */
		dma_rmb();

		/* Just consider the last segment and ...*/
		if (likely(!(status & tx_not_ls))) {
			/* ... verify the status error condition */
			if (unlikely(status & tx_err)) {
				priv->dev->stats.tx_errors++;
			} else {
				priv->dev->stats.tx_packets++;
				priv->xstats.tx_pkt_n++;
			}
			stmmac_get_tx_hwtstamp(priv, p, skb); // 获取时间戳
		}

接收报文时间

与发送的类似,也是保存在描述符里,不赘述。

总结

至此,由网卡硬件实现的硬件精确时间对时的基础已经分析完毕。主要精髓是网卡会自动保存发送和接收的时间到描述符里,这个时刻及其精确,不受代码运行抖动的影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1657562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL STRING_SPLIT函数,将指定的分隔符将字符串拆分为子字符串行

文章目录 STRING_SPLIT (Transact-SQL)1、语法2、参数3、样例样例1样例2 STRING_SPLIT (Transact-SQL) STRING_SPLIT 是一个表值函数&#xff0c;它根据指定的分隔符将字符串拆分为子字符串行。 1、语法 STRING_SPLIT ( string , separator [ , enable_ordinal ] ) 2、参数…

需求为何如此多

同事的心态炸了 最近各种需求倒排给M同事的心态整炸了&#xff0c;直接撂挑子&#xff0c;从一个TL转为安静的开发人员了。我目睹了整个过程&#xff0c;大抵是理解他的心情的。早年从PMP培训&#xff0c;到哈啰火种计划培训&#xff0c;到后来也带项目&#xff0c;有一些看法…

Powerdesigner导入mysql8之后注释丢失

目录 一、问题描述及解决思路 二、导入的步骤 1.先按正常步骤建立一个物理数据模型 &#xff08;1&#xff09;点击“文件-新建模型” &#xff08;2&#xff09;选择物理模型和数据库 2.从sql文件导入表 &#xff08;1&#xff09;点击“数据库-Update Model from Data…

【ai早报-01 project】

今天和大家分享一款有趣的开源项目 01 Project。 The 01 Project is building an open-source ecosystem for AI devices. 其主旨是基于开源生态&#xff0c;构建以LLM为核心的产品&#xff0c;提供软硬件方案。 市面上类似产品: Rabbit R1, Humane Pin。 如上图所示的这款产…

kafka(七)——消息偏移(消费者)

概念 消费者消费完消息后&#xff0c;向_consumer_offset主题发送消息&#xff0c;用来保存每个分区的偏移量。 流程说明 consumer发送JoinGroup请求&#xff1b;coordinator选出一个consumer作为leader&#xff0c;并将topics发送给leader消费者&#xff1b;leader consumer…

如何使用Transformer-TTS语音合成模型

1、技术原理及架构图 ​ Transformer-TTS主要通过将Transformer模型与Tacotron2系统结合来实现文本到语音的转换。在这种结构中&#xff0c;原始的Transformer模型在输入阶段和输出阶段进行了适当的修改&#xff0c;以更好地处理语音数据。具体来说&#xff0c;Transformer-TT…

NSSCTF Web方向的例题和相关知识点(一)

[SWPUCTF 2021 新生赛]jicao 解题&#xff1a; 打开环境&#xff0c;是一段php代码 包含了flag.php文件&#xff0c;设定了一个POST请求的id和GET请求的json 语句会对GET请求的数据进行json解码 如果id和json变量的值都等于设定字符串&#xff0c;则得到 flag 我们可以使用…

如何让加快OpenHarmony编译速度?

OpenHarmony 有两种编译方式&#xff0c;一种是通过 hb 工具编译&#xff0c;一种是通过 build.sh 脚本编译。本文笔者将提升 build.sh 方式编译速度的方法整理如下&#xff1a; 因为笔者只用 build.sh 脚本编译&#xff0c;没用过 hb 工具&#xff0c;好像下面的选项也可以用于…

Python中使用tkinter模块和类结构的结合使用举例——编写制作一个简单的加数GUI界面

Python中使用tkinter模块和类结构的结合使用举例——编写制作一个简单的加数GUI界面 这里写目录标题 Python中使用tkinter模块和类结构的结合使用举例——编写制作一个简单的加数GUI界面一、tkinter模块和类的简述1.1 tkinter的简要介绍1.2 类结构的简要介绍 二、基于类机构和t…

拼多多强付费二阶段断流怎么办?分几种情况解决

关于断流的问题应该有不少人遇到过&#xff0c;即使是强付费&#xff0c;也不是一直有流量&#xff0c;到了二阶段说断流就断流&#xff0c;同样不能幸免。那么强付费二阶段直接断流是什么原因呢?今天跟大家讲一下强付费断流可能遇到的几种情况&#xff0c;要怎么应对。 第一…

利用自动获客软件实现高效精准获客

在数字化时代的浪潮中&#xff0c;企业之间的竞争愈发激烈。客户资源的获取成为企业生存和发展的关键。传统的获客方式如广告投放、线下推广等不仅成本高昂&#xff0c;而且效率和准确性难以保证。随着科技的进步&#xff0c;自动获客软件应运而生&#xff0c;它以其独特的优势…

C语言洛谷题目分享(11)回文质数

目录 1.前言 2.题目&#xff1a;回文质数 1.题目描述 2.输入格式 3.输出格式 4.输入输出样例 5.题解 3.小结 1.前言 哈喽大家好&#xff0c;今儿继续为大家分享一道蛮有价值的一道题&#xff0c;希望大家多多支持喔~ 2.题目&#xff1a;回文质数 1.题目描述 因为 151 …

【MySQL数据库】详解数据库审核工具SQLE的部署及接口调用

SQLE部署及使用 1. 部署SQLE SQLE相信大家都不陌生吧&#xff0c;它是一款开源&#xff0c;支持多场景审核&#xff0c;支持标准化上线流程&#xff0c;原生支持 MySQL 审核且数据库类型可扩展的 SQL审核工具。我们可以基于此工具进行数据库SQL审核&#xff0c;提升SQL脚本质量…

ue引擎游戏开发笔记(36)——为射击落点添加特效

1.需求分析&#xff1a; 在debug测试中能看到子弹落点后&#xff0c;需要给子弹添加击中特效&#xff0c;更真实也更具反馈感。 2.操作实现&#xff1a; 1.思路&#xff1a;很简单&#xff0c;类似开枪特效一样&#xff0c;只要在头文件声明特效变量&#xff0c;在fire函数中…

数据挖掘(一)数据类型与统计

前言 打算新开一个笔记系列&#xff0c;基于国防科技大学 丁兆云老师的《数据挖掘》 数据挖掘 1、数据类型与统计 数据统计 最大值&#xff0c;最小值&#xff0c;平均值&#xff0c;中位数&#xff0c;位数&#xff0c;方差等统计指标 df.describe() #当调用df.describe(…

分布式锁与秒杀

分布式锁与秒杀 1. 分布式锁1.1 常用Redis分布式锁方案三&#xff1a;使用Lua脚本(包含SETNX EXPIRE两条指令) 秒杀 1. 分布式锁 https://www.cnblogs.com/shoshana-kong/p/17519673.html 1.1 常用Redis分布式锁方案三&#xff1a;使用Lua脚本(包含SETNX EXPIRE两条指令) …

【JAVA基础之装箱和拆箱】自动装箱和自动拆箱

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;mysql专栏&#xff1a;小林同学的专栏 目录 1.包装类 1.1 概述 1.2 Integer类 1.3 装箱和拆箱 1.4 自动装箱和自动拆箱 1.5 基本类型与字符串之间的转换 1.5.1 基本类型转换为字符串 1.5.2 字符串转…

力扣每日一题111:二叉树的最小深度

题目 简单 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2示例 2&#x…

银行职员向媒体投稿发文章我找到了好方法

作为一名基层银行的媒体联络专员,我的日常工作中有一项至关重要的任务,那就是代表我所在的支行向各大媒体投稿,传播我们的金融服务、产品动态以及社会责任实践。起初,这项看似简单的工作却成了我职业生涯中的一大挑战。传统的邮件投稿方式,不仅耗时费力,而且审核流程严格,稿件从…

python 和 MATLAB 都能绘制的母亲节花束!!

hey 母亲节快到了&#xff0c;教大家用python和MATLAB两种语言绘制花束~这段代码是我七夕节发的&#xff0c;我对代码进行了简化&#xff0c;同时自己整了个python版本 MATLAB 版本代码 function roseBouquet_M() % author : slandarer% 生成花朵数据 [xr,tr]meshgrid((0:24).…