人与人之间的偏见和隔阂就如同一座大山,让人互相看不到真实的样子。OK,准备好plan B。
----- 佚名
本文翻译自文档Learn the architecture_Generic Timer。
本文档介绍通用定时器,A系列PE的定时器框架。本文档介绍现代SOC中定时器框架的不同组件,并介绍对软件有用的编程接口。
本文档的目标人群为基于ARM系统写低层软件进行初始化或使用timer的开发者。文档的使用者通常工作在低层代码。
在文档的最后,你可以检查一下你的知识。你将学习到构成timer子系统的不同组件的名称和作用。你将能够在裸机上写代码建立起timer。你将能够基于实现的框架特性,描述哪个timer存在。
我们假定你对ARM异常级别非常熟悉。如果不熟悉,你可以先阅读ARM v8-A Exception model指导。
文档包括用C或汇编写的代码例子。如果你对ARM汇编语法不熟悉,可以阅读Arm v8-A Instruction Set Architecture。例子要求Arm Development Studio。如果你还没有它,可以下载一个评估版。
通用定时器为Arm core提供标准定时器。通用定时器包括一个系统counter和一组每core定时器,如下图所示:
系统counter为一直on的设备,它提供一个固定频率的系统计数。在系统中system count值对所有core都是广播的,让core对时间的流逝有共同的看法。系统计数值为56~64位,通常频率为1MHZ~50MHZ。
NOTE:通用定时器仅测量时间的流逝。它不会报告时间或日期。通常一个SOC也为时间和日期包含一个RTC。
每个core有一组timer。这些定时器是比较器,它们与系统计数器提供的广播系统计数进行比较。软件可以配置timer在将来某个点产生中断或event。软件也可以使用系统计数增加时间戳,因为系统计数对所有core提供公共参考点。
在本指导中,我们将解释timer和系统计数器的操作和配置。
下图描述了处理器定时器:
计数和频率
系统寄存器CNTPCT_EL0报告当前系统计数值。
CNTPCT_EL0的读取可以被预测。这意味着它们可以被无序的读取。在某些情况下这特别重要,比如比较时间戳。当计数器读的时序很重要时,可以使用ISB,如下代码所示:
CNTPCT_EL0报告系统计数的频率。但是,该寄存器不是由硬件进行populate。该寄存器在最高异常级别是可写的,在所有异常级别为可读。运行在EL3的firmware,初始化寄存器作为早期系统初始化的一部分。更多级别的软件,像OS,可以使用该寄存器获取频率。
定时器寄存器
每个定时器有三个如下系统寄存器:
寄存器 |
作用 |
|
控制寄存器 |
|
比较器值 |
|
timer值 |
在寄存器名中,
Timer |
寄存器前缀 |
EL |
EL1 physical timer |
CNTP |
EL0 |
EL1 virtual timer |
CNTV |
EL0 |
Non-secure EL2 physical timer |
CNTHP |
EL2 |
Non-secure EL2 virtual timer |
CNTHV |
EL2 |
EL3 physical timer |
CNTPS |
EL1 |
Secure EL2 physical timer |
CNTHPS |
EL2 |
Secure EL2 virtual timer |
CNTHVS |
EL2 |
比如CNTP_CVAL_EL0为EL1 physical timer的比较器寄存器。
自测试
EL3 physical timer和non-secure EL2 virtual timer的控制器寄存器名称是什么?
访问定时器
对于一些定时器,可以配置哪些异常级别可以访问定时器:
EL1 physical and virtual timer: EL0访问这些定时器由CNTKCTL_EL1控制。
EL2 physical and virtual timer: 当HCR_EL2.{TGE,E2H}={1,1}时,EL0访问这些定时器由CNTKCTL_EL2控制。
这些定时器作为支持Armv8.1-A Virtualization Host Extension的一部分被添加。哪个不在本文档中介绍?EL3 physical timer:S.EL1和S.EL2访问定时器由SCR_EL3.ST控制。
配置定时器
这里有两种方式来配置定时器,或使用CVAL寄存器,或使用TVAL寄存器。
比较器寄存器CVAL为64位寄存器。软件写值到这个寄存器,当计数达到或超过时,定时器触发,你将看到:
定时器寄存器TVAL为32位寄存器。当软件写TVAL时,处理器读取当前系统计时,增加所写值,并populate CVAL:
在软件中你可以看到CVAL的值。如果你读当前系统计数,写1000到TVAL,然后读取CVAL,你将看到CVAL为1000+系统计数。这个值是合适的,因为在指令时序时时间仍会前进。
读回TVAL将它减少到0,但系统计时仍增加。TVAL报告一个signed值,将在定时器fire后将继续减少,这允许软件决定该定时器在多久之前fire。TVAL和CVAL给软件两种不同的方式如何使用定时器。如果软件需要在时钟的x ticks中需要一个定时器时,软件能写X到TVAL。相反,当系统计数达到y时如果软件想要一个event,软件写y到CVAL。
记住TVAL和CVAL为两种不同方式对相同的定时器编程。它们非不同的定时器。
中断
定时器可以被配置为产生一个中断。一个core定时器的中断仅能被传递给该core。这意味着一个core的定时器不能产生一个目标为不同core的中断。
中断的产生由CTL寄存器控制,使用这些域:
为产生一个中断,软件必须设置ENABLE为1并清IMASK为0。当定时器fire(CVAL <= 系统计数)时,产生一个中断信号。在Armv8-A系统中,中断控制器通常为GIC。
中断ID(INTID)用于由SBSA定义的每个定时器,如下:
Timer |
SBSA推荐的INTID |
EL1 physical timer |
30 |
EL1 virtual timer |
27 |
Non-secure EL2 physical timer |
26 |
Non-secure EL2 virtual timer |
28 |
EL3 physical timer |
29 |
Secure EL2 physical timer |
20 |
Secure EL2 virtual timer |
19 |
定时器产生的中断为电平敏感的方式。这意味着,一旦定时器fire条件满足时,定时器将持续产生中断,直到下列条件产生:
当写定时器的中断handler时,软件在GIC中deactivate中断之前清除中断非常重要。否则GIC将重发相同的信号。
GIC的操作和配置超出本文档的范围。
定时器虚拟化
早期我们在一个处理器上引入不同的定时器。这些定时器被分成两组:virtual定时器和physical定时器。
physical定时器,像EL3 physical timer, CNTPS, 与系统计数提供的计数值进行比较。该值被当作physical count,并由CNTPCT_EL0报告。
virtual定时器,像EL1 virtual timer, CNTV,与虚拟计数进行比较。该虚拟计数值由下面来计算:
偏移值由寄存器CNTVOFF_EL2指定,这仅由EL2或EL3访问。该配置由下图呈示:
虚拟计数允许hypervisor将虚拟时间显示给虚拟机。比如,当虚拟机没有被调度到时,一个hypervisor可能使用offset隐藏流逝过的时间。这意味着虚拟计数由虚拟机的当前时间决定,而不是wall clock时间。
Event stream
通用定时器也可以用于产生event stream,作为event机制的等待的一部分。WFE指令将core置于低功耗状态,可以通过event将core唤醒。
WFE机制的细节不在本文档介绍范围。
这里有几种方式产生event,包括:
通用定时器可以被配置以固定间隔产生一系列event。一个使用该配置产生超时。当等待资源有效时WFE通常会被使用,且当等待不会太久时。来自定时器的event stream意味着core保持在低功耗状态的最大时间达到。
从physical count CNTPCT_EL0或从virtual count CNTPVT_EL0产生的event stream:
CNTKCTL_EL1 - 控制CNTVCT_EL0产生的event stream
CNTKCTL_EL2 - 控制CNTPCT_EL0产生的event stream
对于每个寄存器,控制如下:
控制EVNTI指定0~15范围的bit位置。当在选择的位置的位修改,产生一个event。比如,如果EVNTI被设置为3时,当bit[3]的计数变化,产生一个event。
控制EVNTDIR控制当选择位从1到0或从0到1时,是否产生event。
总结表
该表总结本节讨论的不同定时器的信息:
定时器 |
寄存器 |
通常用于 |
trappable |
使用counter |
INTID |
EL1 physical timer |
CNTP_<>_EL0 |
EL0/EL1 |
EL2 |
CNTPCT_EL0 |
30 |
EL1 virtual timer |
CNTHP_<>_EL2 |
NS.EL2 |
CNTPCT_EL0 |
27 |
|
Non-secure EL2 physical timer |
CNTHPS_<>_EL2 |
S.EL2 |
CNTPCT_EL0 |
26 |
|
Non-secure EL2 virtual timer |
CNTPS_<>_EL1 |
S.EL1/EL3 |
EL3 |
CNTPCT_EL0 |
28 |
EL3 physical timer |
CNTV_<>_EL0 |
EL0/1 |
CNTPCT_EL0 |
29 |
|
Secure EL2 physical timer |
CNTHV_<>_EL2 |
NS.EL2 |
CNTPCT_EL0 |
20 |
|
Secure EL2 virtual timer |
CNTHVS_<>_EL2 |
S.EL2 |
CNTPCT_EL0 |
19 |
在“什么是通用定时器”章节,我们介绍了系统计数器。系统计数器产生会发送给系统中所有core的系统计数值,如下图所示:
SOC实现负责系统计数器的设计。通常,在系统启动时系统计数器要求做初始化。Arm为系统计数器提供推荐的寄存器接口,但你需要检查你的SOC实现获取详细实现。
一个physical system count值被广播到所有core上。这意味着所有的core共享相同的流逝时间。考虑如下例子:
在这个例子中,设备B看到的系统计数值不可能比message中时间戳更早。
系统计数器测量真实时间。这意味着它不会被电源管理技术如DVFS或将core置于低功耗状态。计数必须以固定频率继续增加。实际上,这要求系统计数器一直处于开启的电源域。
为了节省电源,系统计数器可以修改速率来更新计数。比如,系统计数器可以以每10个tick更新计数。当连接的core处于低功耗状态时这非常有用。系统计数仍需要反映时间的推进,但电源可以通过广播更少计时更新达到节省的目的。
Counter scale
将系统计数进行缩放的选项在Armv8.4-A引入的。代替时钟的每个tick增加一,计数可以每次增加x,在系统初始化时软件配置x。该特性允许计数有效的更快或更慢增加计数。
为了支持缩放,系统计时器内部将计数器值扩展到88位,如你所看到下图所示:
计数是由88位固定值表示,其中64位为整数部分,24位为分数部分。计数的整数部分由连接的处理器的CNTPCT_EL0报告。分数部分被内部的系统计数器使用。
增量部分来自32位称为CNTSCR的寄存器,它的格式如下所示:
增加值分成8位的整数部分和24位的分数部分。
当缩放被使能,每次tick时计数增加CNTSCR。比如,如果CNTSCR被设置为0x01800000, 这意味着每次tick时计数增加1.5(其中整数部分0x1,分数部分为0x800000)。这由下表表示:
tick |
内部计数器值 整数部分/分数部分 |
呈现的计数器值 通过CNTPCT_EL0可见 |
0 |
0x0000_0000_0000_0000_0000_00 |
0x0000_0000_0000_0000 |
1 |
0x0000_0000_0000_0001_8000_00 |
0x0000_0000_0000_0001 |
2 |
0x0000_0000_0000_0003_0000_00 |
0x0000_0000_0000_0003 |
3 |
0x0000_0000_0000_0004_8000_00 |
0x0000_0000_0000_0004 |
4 |
0x0000_0000_0000_0006_0000_00 |
0x0000_0000_0000_0006 |
5 |
0x0000_0000_0000_0007_8000_00 |
0x0000_0000_0000_0007 |
6 |
0x0000_0000_0000_0009_0000_00 |
0x0000_0000_0000_0009 |
当系统计时器被禁用时也可以配置缩放。当计数器正在运行时,修改缩放使用或禁用,或缩放因子,它会导致不可知的计数值返回。
基础编程
在本节假定系统计时器实现了Arm推荐的寄存器接口。
系统计数器提供两个寄存器组:CNTControlBase和CNTReadBase。
寄存器组CNTControlBase用于配置系统计数器,且它可以安全访问支持trustzone的系统。寄存器组中的寄存器如下表所示:
寄存器 |
描述 |
CNTCR |
控制寄存器,包括:
|
CNTSCR |
当使能缩放时增加值 |
CNTID |
ID寄存器,报告哪些特性被实现 |
CNTSR |
状态寄存器。报告定时器是运行还是停止 |
CNTCV |
报告当前计数值。 返回计数的整数部分 |
CNTFID |
报告有效的更新频率 |
为了使能系统计数器,软件必须选择一个更新的频率并设置计数器使能。
CNTReadBase为仅包含CNTCV寄存器的CNTControlBase的拷贝。这意味着CNTReadBase仅报告当前系统计数值。但不像CNTControlBase,CNTReadBase可以访问非安全访问。这意味着非安全软件可以读取当前计数,但不能配置系统计时器。
在“什么是通用计时器”章节,我们介绍了在处理器中的定时器。系统也可以包含额外的外部定时器。下图描述了一个例子:
这些定时器的编程接口为内部定时器的镜像,但这些定时器通过内存映射寄存器被访问。这些寄存器的位置由SOC实现决定,并由你工作的SOC数据手册报告。
来自外部内存映射的定时器的中断通常以共享外设中断SPI传递。