所有使用Arm处理器的系统中都会包含一个标准化的通用定时器(Generic Timer)框架。这个通用定时器系统提供了一个系统计数器(System Counter)和一组定时器(Timer)。其结构如下图:
可以看到,系统计数器是全局唯一的,并且全局共享,对系统中的所有Arm核心进行广播。这个计数器以一个固定的频率递增(频率范围通常从1MHz到50MHz不等)。同时,这个系统计数器还是一直存在的,哪怕系统处于待机状态,所有内核都被关闭了,它仍然可以工作。计数器的宽度有56位至64位,计数到达最大值后会回滚。
每一个Arm核都配备一组专门为自己服务的定时器。定时器到期了之后会通过私有的PPI(Private Peripheral Interrupt)向通用中断控制器发中断请求。按照不同的指令集扩展,每组都有最多7个定时器,但无论如何最基本的都会提供4个,它们分别是:
- EL1 physical timer:给操作系统用的物理定时器;
- EL1 virtual timer:给操作系统用的虚拟定时器;
- Non-secure EL2 physical timer:给虚拟机的宿主系统用的定时器;
- EL3 physical timer:给运行于EL3内的固件程序使用的定时器。
对于系统计数器来说,可以通过读取控制寄存器CNTPCT_EL0来获得当前的系统计数值(无论处于哪个异常级别),也就是通过以下汇编指令:
MRS Xn, CNTPCT_EL0
这条指令是可以乱序执行的,使用的时候要适当保护。确切的说,这是读取物理计数器的值,系统中其实还存在一个虚拟计数器的值,这个虚拟计数器主要也是给虚拟机的宿主系统用的。虚拟计数器的值和物理计数器的值有如下对应关系:
虚拟计数器 = 物理计数器 - 偏移
这个偏移的值是通过控制寄存器CNTVOFF_EL2设置的,看名字就知道只能在EL2或EL3层才有权限设置和访问,如果不设置的话,默认值是0,也就是虚拟计数器和物理计数器的值一致。如果想得到虚拟计数器的值,可以通过读取CNTVCT_EL0控制寄存器来获得。
系统计数器的频率主要通过控制寄存器CNTFRQ_EL0来控制。频率是可以随意设定的,但只能在EL3下设置,也就是说在系统固件程序里。在其它的异常级别里(EL2到EL0)都不能设置,但是可以通过读取这个CNTFRQ_EL0寄存器来获得在固件中设置好的频率。
对于Arm定时器来说,总体有两种工作方式:
- 到一个绝对时间之后就触发;
- 从现在开始再过一定时间间隔之后触发。
Arm定时器通过两类寄存器来实现以上两种工作方式。一类叫做比较寄存器(CVAL),还有一类叫做定时寄存器(TVAL)。
比较寄存器有64位,如果设置了之后,当系统计数器达到或超过了这个值之后(CVAL<系统计数器),就会触发定时器中断。通过这种方式来实现第一类定时任务,。
定时寄存器有32位,如果设置了之后,会将比较寄存器设置成当前系统计数器加上设置的定时寄存器的值(CVAL=系统计数器+TVAL),后面就一样了,当系统计数器达到或超过了这个值后,就会触发定时中断。通过这种方式来实现第二种定时任务。
可以看出来,无论那种类型的定时器都是单次出发的(One Shot),如果想要周期触发,必须在中断处理程序中重新设置。这也刚好满足Linux系统中对于高精度定时器的要求。
除了设置定时条件的寄存器,其实每组定时器都还有一个控制寄存器(CTL),其只有最低三位有意义,其它的60位全是保留的,设置成0:
最低三位分别是:
- ENABLE:是否打开定时器,使其工作;
- IMASK:中断掩码,如果设置成1,则即使定时器是工作的,仍然不会发出中断;
- ISTATUS:如果定时器打开的话,且满足了触发条件,则将这一位设置成1。
所以很简单,如果想让定时器按照要求发出中断的话,必须将Enable位设置成1,且IMASK位必须设置成0。
定时中断满足触发条件后,其并不会自己消失。如果在中断处理程序中不做处理的话,那同一个触发条件会不停的触发中断。
前面说到了,每个Arm核都有4个私有定时器,每个定时器都有一个比较寄存器、一个定时寄存器、一个控制寄存器,所以一共应该有12个寄存器可以操作,将它们的命名总结如下:
定时器类型 | 比较寄存器名 | 定时寄存器名 | 控制寄存器名 | 访问异常级别 |
EL1 physical timer | CNTP_CVAL_EL0 | CNTP_TVAL_EL0 | CNTP_CTL_EL0 | EL0和EL1 |
EL1 virtual timer | CNTV_CVAL_EL0 | CNTV_TVAL_EL0 | CNTV_CTL_EL0 | EL0和EL1 |
Non-secure EL2 physical timer | CNTHP_CVAL_EL2 | CNTHP_TVAL_EL2 | CNTHP_CTL_EL2 | NS.EL2 |
EL3 physical timer | CNTPS_CVAL_EL1 | CNTPS_TVAL_EL1 | CNTPS_CTL_EL1 | EL3和S.EL1 |