架构学习之通用定时器

 人与人之间的偏见和隔阂就如同一座大山,让人互相看不到真实的样子。OK,准备好plan B。

                                                                                                                                  ----- 佚名

本文翻译自文档Learn the architecture_Generic Timer。

1 Overview

        本文档介绍通用定时器,A系列PE的定时器框架。本文档介绍现代SOC中定时器框架的不同组件,并介绍对软件有用的编程接口。

        本文档的目标人群为基于ARM系统写低层软件进行初始化或使用timer的开发者。文档的使用者通常工作在低层代码。

        在文档的最后,你可以检查一下你的知识。你将学习到构成timer子系统的不同组件的名称和作用。你将能够在裸机上写代码建立起timer。你将能够基于实现的框架特性,描述哪个timer存在。

2 在开始之前

        我们假定你对ARM异常级别非常熟悉。如果不熟悉,你可以先阅读ARM v8-A Exception model指导。

        文档包括用C或汇编写的代码例子。如果你对ARM汇编语法不熟悉,可以阅读Arm v8-A Instruction Set Architecture。例子要求Arm Development Studio。如果你还没有它,可以下载一个评估版。

3 什么是通用定时器为

        通用定时器为Arm core提供标准定时器。通用定时器包括一个系统counter和一组每core定时器,如下图所示:

        系统counter为一直on的设备,它提供一个固定频率的系统计数。在系统中system count值对所有core都是广播的,让core对时间的流逝有共同的看法。系统计数值为56~64位,通常频率为1MHZ~50MHZ。

        NOTE:通用定时器仅测量时间的流逝。它不会报告时间或日期。通常一个SOC也为时间和日期包含一个RTC。

        每个core有一组timer。这些定时器是比较器,它们与系统计数器提供的广播系统计数进行比较。软件可以配置timer在将来某个点产生中断或event。软件也可以使用系统计数增加时间戳,因为系统计数对所有core提供公共参考点。

        在本指导中,我们将解释timer和系统计数器的操作和配置。

4 处理器定时器

        下图描述了处理器定时器:

计数和频率

        系统寄存器CNTPCT_EL0报告当前系统计数值。

        CNTPCT_EL0的读取可以被预测。这意味着它们可以被无序的读取。在某些情况下这特别重要,比如比较时间戳。当计数器读的时序很重要时,可以使用ISB,如下代码所示:

        CNTPCT_EL0报告系统计数的频率。但是,该寄存器不是由硬件进行populate。该寄存器在最高异常级别是可写的,在所有异常级别为可读。运行在EL3的firmware,初始化寄存器作为早期系统初始化的一部分。更多级别的软件,像OS,可以使用该寄存器获取频率。

定时器寄存器

        每个定时器有三个如下系统寄存器:

寄存器

作用

<timer>_CTL_EL<x>

控制寄存器

<timer>_CVAL_EL<x>

比较器值

<timer>_TVAL_EL<x>

timer值

        在寄存器名中,<timer>表明访问哪个timer。下列表显示可能的值:

Timer

寄存器前缀

EL<x>

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寄存器控制,使用这些域:

  1. ENABLE - 使能定时器
  2. IMASK - 中断屏蔽。使能或禁用中断的产生
  3. ISTATUS - 当ENABLE=1时,报告是否中断fire(CVAL <= 系统计数)

        为产生一个中断,软件必须设置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条件满足时,定时器将持续产生中断,直到下列条件产生:

  1. IMASK被设置为1,这将屏蔽中断
  2. ENABLE被设置为0,这将禁用中断
  3. TVAL或CVAL被写,因此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,包括:

  1. 在不同的core上执行SEV指令(Send Event)
  2. 清除core的Global Exclusive Monitor
  3. 从core的通用定时器使用event stream

        通用定时器可以被配置以固定间隔产生一系列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

        对于每个寄存器,控制如下:

  1. EVENTEN使能或禁用event的产生
  2. EVNTI控制event的速率
  3. EVNTDIR控制何时产生event

        控制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

5 系统计数器

        在“什么是通用定时器”章节,我们介绍了系统计数器。系统计数器产生会发送给系统中所有core的系统计数值,如下图所示:

 

        SOC实现负责系统计数器的设计。通常,在系统启动时系统计数器要求做初始化。Arm为系统计数器提供推荐的寄存器接口,但你需要检查你的SOC实现获取详细实现。

        一个physical system count值被广播到所有core上。这意味着所有的core共享相同的流逝时间。考虑如下例子:

  1. 设备A读取当前系统计数并将其作为时间戳添加到message中,然后将message发送给设备B;
  2. 当设备B接受到message,它会比较时间戳和当前系统计数

        在这个例子中,设备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

控制寄存器,包括:

  1. 计数器使能
  2. 计数器缩放使能
  3. 更新频率选择
  4. Halt-on-debug控制

CNTSCR

当使能缩放时增加值

CNTID

ID寄存器,报告哪些特性被实现

CNTSR

状态寄存器。报告定时器是运行还是停止

CNTCV

报告当前计数值。

返回计数的整数部分

CNTFID<n>

报告有效的更新频率

        为了使能系统计数器,软件必须选择一个更新的频率并设置计数器使能。

        CNTReadBase为仅包含CNTCV寄存器的CNTControlBase的拷贝。这意味着CNTReadBase仅报告当前系统计数值。但不像CNTControlBase,CNTReadBase可以访问非安全访问。这意味着非安全软件可以读取当前计数,但不能配置系统计时器。

6 外部定时器

        在“什么是通用计时器”章节,我们介绍了在处理器中的定时器。系统也可以包含额外的外部定时器。下图描述了一个例子:

 

        这些定时器的编程接口为内部定时器的镜像,但这些定时器通过内存映射寄存器被访问。这些寄存器的位置由SOC实现决定,并由你工作的SOC数据手册报告。

来自外部内存映射的定时器的中断通常以共享外设中断SPI传递。

 

 

 

 

 

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>