1、概述
ARMv7-A Generic Timer 是 ARM 架构提供的一套标准化计时器框架,其功能并不局限于单个处理器核内部,而是分布在 SoC 中的多个硬件组件之间。 每个处理器核都拥有自己的 Generic Timer 控制接口,而所有核共享同一个 SoC 级系统计数器 System counter,从而为多核系统提供统一的时间基准。
图 B8-1 展示了一个以 Generic Timer 作为系统计时器的 SoC 示例。其中:
- 本手册仅定义多处理器模块中各个处理器核的架构实现;
- GIC 的架构由 ARM Generic Interrupt Controller Architecture Specification 定义;
- Generic Timer 的功能由多个组件协同完成,包括 SoC 级的系统计数器 和各处理器核内部的定时器接口 。

Generic Timer 主要提供三类能力: - 提供一个持续递增的系统计数器(System counter),用于度量真实时间的流逝。
- 在支持虚拟化 的系统中,Generic Timer 还提供虚拟计数器,用于度量虚拟机视角下的虚拟时间。
- Generic Timer 提供可编程定时器,当计数达到指定条件时,会 assert 定时器输出信号。
- 这些定时器既可以按递增 计数方式使用,也可以按递减计数方式使用;
- 在支持虚拟化的系统中,它们还可以分别工作在真实时间或虚拟时间模式下。
2、System counter
Generic Timer 提供的 system counter 需满足以下规格:
| 规格 | 要求 |
|---|---|
| 位宽 | 至少 56 位;以 64 位宽度读取时,返回值按无符号数零扩展至 64 位 |
| 频率 | 以固定频率递增,典型范围为 1 MHz ~ 50 MHz |
| 低功耗模式 | 可支持一种或多种替代运行模式,在较低频率下以较大步长递增,通常用于降低功耗 |
| 回绕时间 | 不少于 40 年 |
| 精度 | ARM 未规定强制精度,但建议 24 小时内累计误差不超过 10 秒;进入低频模式时不得降低已实现的计时精度 |
| 启动行为 | 从零开始计数 |
system counter 必须为整个系统提供一致的时间视图。更严格地说,必须保证系统时间不会出现回退:若设备 A 先读取系统计数器,再与系统中的另一代理设备 B 通信,而设备 B 在收到该通信后也读取系统计数器,则设备 B 读到的时间不应早于设备 A 读到的时间。
system counter 必须实现于 always-on 电源域中,以保证在处理器核休眠、掉电或低功耗状态下仍能持续提供系统时间。
为支持低功耗(lower-power)运行,system counter 可以在降低时钟频率的同时增大每次递增的步长。例如,一个标称 10 MHz 的 system counter 可以工作在:
- 10 MHz 时钟下,每个周期递增 1(system counter 每次递增 1);
- 20 kHz 时钟下,每个周期递增 500。
这里怎么理解呢? 我们定义一个 effective frequency,例如 10MHz,每个周期递增 1,他的 system counter 可以每秒递增 10,000,000。为了达到同样的 effective frequency,且降低时钟输入,只能增大步长,就是每次递增的步长。
在这种设计中,system counter 必须能够在高频高精度模式与低频低精度模式之间切换,同时不影响其应达到的计时精度。软件需要能够访问 CNTFRQ 寄存器,以读取 system counter 的时钟频率。
system counter 向其他系统组件分发的方式由具体实现定义。但任何通过 system control register interface 访问 system counter 的处理器,都必须提供计数器输入,能够捕获计数器的每一次递增。
需要特别澄清的是,"能够捕获计数器的每一次递增"并不等同于"处理器每次读取都能看到计数器值按 +1 递增"。在步长为 1 的前提下,System Counter 内部确实每次递增 1,但由于 System Counter 通常运行在较低频率,而 CPU 运行在较高频率,计数器值从 System Counter 分发到各处理器核需要经过跨时钟域同步、采样和传输,这一过程存在实现相关的延迟。因此,软件连续读取时可能观察到多次返回值相同、随后一次性跳变的现象。ARM 所要求的"捕获每一次递增",主要保证每核定时器的比较器不会漏掉计数器递增事件,从而确保定时器中断能够正确触发,而不保证软件每次读取都能逐 tick 观察到计数器值变化。
system counter 频率的初始化与读取
通常,系统计数器由 SoC 以固定频率驱动;在系统启动过程中,必须将该频率写入 CNTFRQ 寄存器。 在包含 ARM Security Extensions 的实现中,只有运行于 Secure PL1 模式的软件才能写入 CNTFRQ。 如果系统允许对系统计数器频率进行配置,则必须确保 CNTFRQ 始终被设置为正确的系统计数器频率。
CNTFRQ 在复位后的值为 UNKNOWN,因此系统计数器频率必须在启动阶段由软件显式写入。
软件可在以下状态下读取 CNTFRQ,以获取当前系统计数器频率:
- Non-secure PL2 模式;
- Secure PL1 和 Non-secure PL1 模式;
- 当
CNTKCTL.PL0PCTEN置 1 时,Secure PL0 和 Non-secure PL0 模式也可读取。

system counter 的内存映射控制接口
system counter 的部分控制功能只能通过其 "内存映射接口" 访问。这些控制包括:
- 使能和禁止 counter:控制计数器是否继续递增。
- 设置计 counter 值:直接写入计数器的当前值,通常用于系统初始化或时间同步。
- 切换工作模式:改变计数器的更新频率 和递增步长,例如在高频小步长与低频大步长模式之间切换,以支持低功耗运行。
- 使能 Halt-on-debug:允许调试器在调试事件发生时暂停计数器递增,便于进行确定性的时序调试。
为什么需要内存映射接口?
这一点其实很关键:处理器核通过系统控制寄存器接口(如 CNTFRQ、CNTPCT_EL0、CNTP_TVAL_EL0 等)只能做读取计数器值、配置私有定时器这类操作,而 system counter 本身的全局控制 ------启停、设值、切模式------必须通过 SoC 级的内存映射接口完成。原因在于:
- System Counter 是 SoC 级资源,不属于任何一个核,所以不能由某个核通过私有寄存器随意控制;
- 启停和设值会影响整个系统的时间视图,必须由固件或可信软件通过统一的内存映射寄存器操作;
- 低功耗模式切换涉及时钟源和步长配置,通常由 SCP、PMIC 或固件统一管理,而非由每个核各自决定
实际开发中。普通内核代码一般不会直接操作这些寄存器,而是由固件(如 ATF/BL31)在启动阶段完成初始化。
c
void arm_bl31_platform_setup(void)
{
......
/* Enable and initialize the System level generic timer */
mmio_write_32(ARM_SYS_CNTCTL_BASE + CNTCR_OFF,
CNTCR_FCREQ(0U) | CNTCR_EN);
/* Allow access to the System counter timer module */
arm_configure_sys_timer();
......
}

3、The physical counter
处理器提供一个 physical counter,用于保存 system counter 的当前计数值。CNTPCT 寄存器即保存当前物理计数器值。

具备足够权限的软件可以通过 64 位系统控制寄存器读指令(MRRC)读取 CNTPCT。
在不包含虚拟化扩展 的实现中,CNTPCT 始终可在 PL1 模式下访问,且不受安全状态限制 。 PL0 能否访问,只看 CNTKCTL.PL0PCTEN。


在包含虚拟化扩展 的实现中(需要额外关注一个寄存器:CNTHCTL)。CNTPCT 的访问规则如下:
- 始终可从 Secure PL1 模式和 Non-secure Hyp 模式访问。
- 仅当
CNTHCTL.PL1PCTEN置 1 时,才允许从 Non-secure PL1 模式访问。若CNTHCTL.PL1PCTEN为 0,则从 Non-secure PL1 模式访问CNTPCT将触发 "Hyp Trap 异常"。
总结:实际上可以这么理解。PL0 访问
CNTPCT需要同时满足两个条件:第一,当前安全状态下的 PL1 本身允许访问;第二,CNTKCTL.PL0PCTEN= 1。这个规则在 Secure 和 Non-secure 侧都成立;但在有虚拟化扩展时,Non-secure PL0 还会额外受CNTHCTL.PL1PCTEN影响(其实这里影响的也是 PL1,间接影响了 PL0)。
CNTKCTL就是 PL1 用来控制 PL0 的访问;CNTHCTL就是用来,Hypervisor 场景下,PL1 和 PL0 能否直接访问,还是 trap 陷入 Hyperviosr 中,让 Hypervisor 处理。


在包含虚拟化扩展的实现中:
CNTKCTL控制的优先级高于CNTHCTL。当以下两个条件同时满足时,从 Non-secure PL0 模式访问CNTPCT将触发未定义指令异常(Undefined Instruction exception):CNTHCTL.PL1PCTEN 为 0,禁止从 Non-secure PL1 模式访问;CNTKCTL.PL0PCTEN 为 0,禁止从 PL0 模式访问。
- 当 PL0 访问被使能时,
CNTHCTL同样作用于 Non-secure PL0 访问。当以下两个条件同时满足时,从 Non-secure PL0 模式访问CNTPCT将触发 Hyp Trap 异常:CNTHCTL.PL1PCTEN为 0,禁止从 Non-secure PL1 模式访问;CNTKCTL.PL0PCTEN为 1,允许从 PL0 模式访问。
CNTPCT 的读取可能发生乱序 和推测执行 ,相对于同一处理器上执行的其他指令不保证顺序。例如,若通过内存读取获取来自其他代理的信号,该信号指示需要读取 CNTPCT,则必须使用 ISB 指令确保 CNTPCT 的读取发生在从内存读取该信号之后,如下列代码序列所示。
c
loop ; polling for some communication to indicate a requirement to read the timer
LDR R1, [R2]
CMP R1, #1
BNE loop
ISB ; without this, CNTPCT might be read before the memory location in [R2]
; has had the value 1 written to it
MRRC p15, 0, R1, R2, c14 ; Read 64-bit CNTPCT into R1 (low word) and R2 (high word)
4、Generic Timer 工作方式
4.1 CompareValue 模式
定时器的 CompareValue 模式作为一个 64 位递增 计数器工作。当相应的计数器(system counter)达到 CompareValue 寄存器中编程的值时,定时器条件成立。当定时器条件成立时,只有在定时器已使能,并且对应定时器控制寄存器 CNTP_CTL、CNTHP_CTL 或 CNTV_CTL 中未屏蔽该信号时,定时器输出信号才会被置位。


注意:
- 定时器输出信号可用作电平敏感中断信号。
- 在 CompareValue 视图的伪代码描述中,EventTriggered 仅表示定时器条件是否成立(met),并不表示定时器输出信号是否被置位(asserted)。
工作方式如下:
c
EventTriggered = (((Counter[63:0] -- Offset[63:0])[63:0] - CompareValue[63:0]) >= 0)
其中:
-
EventTriggered:如果该定时器的条件成立,则为 TRUE;否则为 FALSE。
-
Counter:物理计数器值,可通过
CNTPCT寄存器读取。 -
Offset:对于物理定时器,以及不包含虚拟化扩展实现中的虚拟定时器,Offset 为零。对于包含虚拟化扩展实现中的虚拟定时器,Offset 为虚拟偏移量,保存在 CNTVOFF 寄存器中(仅在虚拟化场景下使用)。
-
CompareValue:对应 CompareValue 寄存器的值,即
CNTP_CVAL、CNTHP_CVAL或CNTV_CVAL。
在该模式中,Counter、Offset 和 CompareValue 均为 64 位无符号值。
注意:
这意味着,如果 CompareValue 的值接近 0xFFFFFFFFFFFFFFFF,定时器条件可能永远不会成立。但在实际应用中,没有理由使用接近计数器回绕值的数值。
4.2 TimerValue 模式
在 TimerValue 模式下,定时器表现为一个有符号 32 位递减 计数器。通过向 TimerValue 寄存器写入一个计数值来编程。该值会随着相应计数器(system counter)每次递增而递减,当该值减到零时,定时器条件成立(met)。当定时器条件成立时,只有在定时器已使能、且对应控制寄存器(CNTP_CTL、CNTHP_CTL 或 CNTV_CTL)中未屏蔽该信号的情况下,定时器输出信号才会被置位(assert)。


- 读取时:TimerValue = (CompareValue -- (Counter - Offset))31:0
- 写入时:CompareValue = ((Counter - Offset)63:0 + SignExtend(TimerValue))63:0
这里我们要理解,本质上,TimerValue 只是 CompareValue - Counter 的实时投影。写 TimerValue 实际上也是写 CompareValue,写完后,如果 CompareValue 不变,TimerValue 便不断递减(因为 Counter 在不断递增)。TimerValue 会一直随 CompareValue 和 Counter 变化而变化
该模式的实际工作方式为:
c
EventTriggered = (TimerValue ≤ 0)
在该模式下,所有值均为有符号数,采用标准二进制补码表示。
当定时器条件成立后,读取 TimerValue 寄存器会返回条件成立至今已经过的时间(即负值,表示"超时多久了")。
注意:
- 如果将 TimerValue 编程为一个负数,且其绝对值大于 (Counter - Offset),可能导致算术溢出,使 CompareValue 变成一个极大的正数。这可能导致定时器条件在极长时间内都不会成立。
4.3 定时器输出信号的工作方式
当以下所有条件同时满足时,定时器输出信号会被置位(asserted):
- 至少有一个定时器条件成立( CompareValue & TimerValue 模式工作方式 条件成立(met));
- 在定时器控制寄存器
CNTP_CTL、CNTHP_CTL或CNTV_CTL中:- 定时器已使能;
- 定时器输出信号未被屏蔽。
5、Generic Timer 寄存器介绍
虚拟化相关的 Timer 寄存器这里不会详细介绍。我们这里仅关注非虚拟化场景下的寄存器:

上面章节已经描述过的寄存器,这里不再讲解,仅描述上面暂未出现过的。


