今天花点时间研究一下ARMv8-A中的一个概念:通用计时器(Generic Timer)。
通用计时器为ARM的处理器核提供了一个标准化的计时器框架。通用计时器包含一个系统计数器(System Counter)和每个处理器核自己的计时器(per-core timer),如下图。图中的PE(Processor Element)代表处理器核。
系统计数器需要保证在芯片上电启动后一直保持工作,且以固定的时钟频率单调递增计数。系统计数器的数值需要广播给所有的处理器。在多核处理器架构中,即使某些处理器核出于某些原因(节省功耗,生成故障等等)关闭电源,系统计数器仍能为处于工作状态的处理器核提供计数值。
ARM建议系统计数器是56-64bit宽度,工作频率在1-50MHz。我们以最小宽度56bit和最高速度50MHz来计算,计数器溢出需要大概 2^56/(50*10^6*60*60*24*365),约为45年。此处只是给出大概的计算,具体的计数器设计要根据实际需求确定。
不知道大家看到这里发现没有,系统计数器只是单调的递增,并不能反映真实物理世界的时间(年,月,日,时,分,秒)。也就是说,SoC还需要板级提供一个RTC(Real-Time Clock),以供给真实时间。
每个处理器有一组计时器。这些计时器本质上是比较器,软件可以设置这些处理器本地计时器的数值,并且与系统计数器广播来的值作比较。当计满后,触发中断(Interrupt)或者事件(Event)。前面讲GIC(Generic Interrupt Controller)和虚拟化的时候都有提及相关内容。
ARMv8-A中的处理器计时器如下表:
好了,基本的概念介绍完了,接下来看一看Generic Timer在具体实现时会遇到的一些问题。
首先,系统计数器需要传输给处理器核,系统计数器工作在低频下(MHz),而处理器工作在高频下(GHz),如何在两个时钟频率下传输一组数值?这个问题可以通过二进制和格雷码转换来解决。系统计数器的数值转换成格雷码,以格雷码的形式在芯片中传播;在处理器端,先做跨时钟域采样,这样会保证采样不会采错,然后格雷码转换成二进制。
第二个问题,是多核处理器芯片中要面临的。系统计数器的值要传播给所有的处理器。这时,需要一定的机制保证该值同一时刻(此处不是绝对意义的分毫不差)到达每个处理器端。否则的话,可能会引发错误。例如,假设两个处理器A核B,处理器A端的系统计数器值更新快于处理器B端。处理器A以接收到的系统计数器值为时间戳,发送一个消息给处理器B;处理器B接收到消息后,看到的本地系统计数值如果早于消息中的时间戳,那就肯定不对了(不能接收来自未来的消息吧)。我在ARM的文档中没有找到ARM有什么推荐方案,个人感觉可以通过格雷码打多拍的方式在整个SoC中传播。这样可以保证从系统计数器传播到每个处理器入口端的延时一样,至于每个处理器内部跨时钟域转换造成的偏差,可以认为是系统计数器时钟的抖动(jitter),忽略不计。
第三个问题,是多芯片间的同步问题。一个SMP可能由多个处理器芯片组成,每个处理器芯片有自己的系统计数器。但是SMP要求多芯片内的所有处理器时间保持一致(类似单芯片中的多核间需要一致)。ARM在其参考设计中提供了一个解决方案,使用两个芯片管脚SYNCREQ和SYNCACK实现一组握手协议。芯片间的同步通过这两个专用接口和CCIX消息来完成。
在以下情况下,通过CCIX将消息写入memory-mapped寄存器:
- 主设备向从设备发送未来的Tupdate时间值
- 从机使用接口重置主状态机的任何错误状态
1. 从机在T0时刻置位SYNCACK,以指示主机开始同步;作为响应,主机置位SYNCREQ;随后从机取消SYNCACK置位;主机取消SYNCREQ置位。
2. 主机在T2时刻置位SYNCREQ,发起同步;从机在T3时刻置位SYNCACK,表示接受同步;主机在T4时刻检测到SYNCACK,随后主机取消SYNCREQ的置位;从机取消SYNCACK置位。从主机发起到从机响应,之间的延迟Tdelay\=(T4-T2)/2,这里的时间T4,T2都是主机端的时间,所以计算单向延迟要除以2。
3. 主机在T5时刻通过CCIX发送Tupdtvalue;从机在T6时刻接收到。
未来时间值:Tupdtvalue\=T5+X,X=Tdelay+Tccix\_network\_delay
4. 主机在T7(\= Tupdtvalue- Tdelay)时刻置位发起同步更新给从机;从机在T8时刻响应,置位SYNCACK,注意这个T8是指从机端的时刻。
- 如果Tupdtsave<T8,向软件发出中断。或者,暂停从机计数器(Tupdtsave-T8),然后使用Tupdtsave更新计数器并继续
- 如果|Tupdtsave-T8|>OFFSET\_THRESHOLD,向SCP发中断
- 否则,从机将使用先前接收到的Tupdtvalue更新其本地时间
- 如果从机在T8没有从主机接收到Tupdate值,则从机将等待直到接收到该值。然后从机立即启动另一个同步,并忽略收到的Tupdate值
上面是文档里的内容,我再通俗的解释一下第4步。主机预估了一个时间值Tupdtsave(这个时间包含了第2步计算出的接口延时和CCIX上的传输延时),通过CCIX发给从机;从机在T8时刻收到该值,并开始比较,如果T8大,说明从机时间超前了需要停下来等待;如果T8和Tupdtsave大于门限值,说明主机和从机之间的时间差距较大;只有两者接近时,说明主机和从机的差距可以忽略。
该同步过程按软件编程的固定时间间隔重复。下图是同步成功和失败的时序图。
此同步逻辑在每个主机和从机之间成对复制,所以对于具有四芯片系统,有三个这种实例化模块。
个人认为这种,这种方案有一个好处,就是冗余度大。
关于chip-chip的时间同步,其实还有很多种方法,比如IEEE,PCIe等,都提供了解决方案。笔者认为,具体在SoC中的实现方案取决于软件对于时间偏差的容忍度。
是不是以为到了这里就结束了?然而并没有。在Armv8.4-A中,引入了一个概念,计数器缩放(Counter Scaling)。在此之前,系统计数器在每个时钟周期内加1。有了计数器缩放,可以每个周期增加X,X不必是1。实现方式如下,系统计数器扩展为88-bit,其中高64-bit表示整数部分,低24-bit表示分数部分。
并且定义了一个32-bit的增量寄存器CNTSCR,其中高8-bit表示整数部分,低24-bit表示分数部分。例如,当设置CNTSCR为0x0180\_0000时,每个时钟周期系统计数器增加1.5(整数部分是1,分数部分是0.5)。
系统计数器提供两个寄存器组:CNTControlBase和CNTReadBase。CNTControlBase用于安全访问,定义如下:
CNTReadBase是CNTControlBase的副本,仅包括CNTCV寄存器。也就是说CNTReadBase只报告当前系统计数值。与CNTControlBase不同的是,非安全访问可以访问CNTReadBase。即非安全软件可以读取当前计数,但不能配置系统计数器。
今天就到这里吧,是不是没用的知识又增加了一些呢?
作者:处芯积律
文章来源:处芯积律
欢迎大家点赞留言,更多Arm技术文章动态请关注极术社区Arm技术专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。