目录内容
- →主要介绍GIC架构历史
- →理解概念,尤其是编程模型
- →如何配置GIC各种寄存器使其正常工作
- →解释中断的处理过程
- →理解ITS基于消息的服务和中断
- →如何发送接收软中断?
- →虚拟化环境下如何管理虚拟中断?
- →虚拟LPI的直接注入
2 介绍
2.1 范围
GICv3它可以支持许多不同的配置和使用场景。为了简化,本文只关注其中一个子集:
-
有两种安全状态
-
两种安全状态使路由中断
-
所有异常级别都可以访问系统寄存器
-
遵循所有处理器
ARMv8-A架构,实现所有异常级别并运行AArch64状态下
本文档不包括:
-
旧操作(本章除外)
-
运行在
AArch32状态下的情况
2.2 GIC架构的历史
GICv3添加了几个新特性。下表列出了不同版本GIC对比架构的关键特征。
| 版本 | 关键特性 | 搭配的CPU核 |
|---|---|---|
| GICv1 | 最多支持8个PE 最多支持1020中断ID 支持两种安全状态 |
Cortex-A5 MPCore Cortex-A9 MPCore Cortex-R7 MPCore |
| GICv2 | GICv1所有特性支持虚拟化 |
Cortex-A7 MPCore Cortex-A15 MPCore Cortex-A53 MPCore Cortex-A57 MPCore |
| GICv3 | GICv2所有特性支持超过8个PE支持基于消息的中断支持超过1020中断IDCPU interface是系统寄存器加强安全模型,独立安全Group中断和不安全Group1中断 |
Cortex-A53 MPCore Cortex-A57 MPCore Cortex-A72 MPCore |
| GICv4 | GICv3所有特性直接注入虚拟中断 |
Cortex-A53 MPCore Cortex-A57 MPCore Cortex-A72 MPCore |
GICv2m是GICv2为了支持基于消息的中断,请联系更多信息ARM公司咨询。
2.3 GICv3架构的实现
ARM? CoreLink? GIC-500是GICv3的实现。ARM? Cortex?-A53, ARM? Cortex? -A57和ARM? Cortex?-A72实现需要CPU接口。
2.4 旧版本支持
GICv3对编程模型做了许多改变。为了支持基于GICv2的旧软件,GICv3支持旧操作。
可通过编程模型GICD_CTRL寄存器中的亲和力路由使能(ARE)控制标志位:
ARE == 0,禁止亲和力路由(旧操作);ARE == 1,使用亲和力路由。
注:为了可读性,在本文档中,
GICD_CTLR.ARE_S和GICD_CTLR.ARE_NS统称为ARE。
亲和力路由可以在系统的两个安全状态下单独控制。只允许特定的组合,如下图所示: 
图-支持的ARE组合
关注本文档GICv3编程模型,即两种安全状态ARE=1情况。旧方法ARE==0不讨论。
对旧方法的支持是可选的。在实现对旧方法的支持时,复位选择旧方法。
3 GICv3基础知识
本章描述了兼容性GICv3中断控制器的基本操作。此外,还包括不同的编程接口(即寄存器)。
3.1 中断类型
GICv3中断类型:
-
SPI-共享外设中断:全局外设中断,可以路由到某个地方PE,也可以是一组PE。 -
PPI-中断私有外设:单个PE私有中断。一个例子是PE一般定时器产生的中断。 -
SGI-软件中断:通常用于核间通信,通过写作GIC中的SGI生成寄存器。 -
LPI(Locality-specific Peripheral Interrupt)-基于新闻的中断:这是GICv3新介绍的方法不同于其他类型的中断。也就是说,这种中断是通过写内存而不是传统的中断线来产生的。详细描述在第六章。注意:
LPI需要GICD_CTLR.ARE_NS==1。
3.1.1 中断ID
每个中断源使用一个中断号(ID)标识,称为INTID。如下表所示:
| INTID | 中断类型 | 注解 |
|---|---|---|
0-15 |
SGI |
每个PE都会备份 |
16-31 |
PPI |
每个PE都会备份 |
32-1019 |
SPI |
- |
1020-1023 |
特殊中断号 | 用于表示特殊情况,参考第5条.3小节 |
1024-8191 |
保留 | - |
>=8192 |
LPI |
具体上限由实现厂商定义 |
3.1.2 中断如何给中断控制器发送信号
通常,中断使用专用硬件信号从外设发送信号给中断控制器。
GICv3支持这种模型,并且支持基于消息的中断。基于消息的中断是通过写中断控制器的寄存器进行设置和清除操作的。
通过互联协议实现的基于消息的中断
使用消息转发外设到GIC的中断,移除了每个中断源必须一个专用信号的要求限制。这对于大型系统的硬件设计人员来说是一个优势,在大型系统中,可能有数百甚至数千个信号汇聚到中断控制器上。
GICv3中,SPI可以是基于消息的中断,但是LPI总是基于消息的中断。它们分别使用不同的寄存器进行设置。
| 中断类型 | 寄存器 |
|---|---|
SPI |
GICD_SETSPI_NSR声明中断 GICD_CLRSPI_NSR清除中断 |
LPI |
GITS_TRANSLATER |
3.1.3 基于消息的中断对软件的影响
至于使用消息,还是使用一个专用信号发送中断,对软件处理中断的方式几乎没有什么影响。
但是可能需要对外设进行一些必要的设置,比如,指定中断控制器的地址。这超出了本文档的范围,故不在此描述。
3.2 中断状态机
中断控制器为每一个SPI、PPI和SGI中断维护着一个状态机。包含4种状态:
-
Inactive:中断还未发生 -
Pending:中断已经发生,但是中断还没有被PE应答 -
Active:中断已经发生,也已经被PE应答过 -
Active和Pending:一个中断被应答过,另一个中断被挂起中
注意:LPI中断没有
active或active&pending状态。更多信息,参考第6.2节。
下图描述了状态机的状态转换:
中断的生命周期依赖于它被配置为电平触发还是边沿触发的。第3.2.1和3.2.2小节提供了采样序列。
3.2.1 电平触发
AP代表active和pending。
-
Inactive→Pending当中断源产生信号时,就会从
Inactive转变为Pending状态。从此刻开始,GIC发送中断信号给PE(如果中断被使能并且有足够的优先级)。 -
Pending→Active & Pending当PE通过读取IAR寄存器(中断应答寄存器)应答该中断时,就会由
Pending转变为Active & Pending状态。这种读取操作一般是中断处理程序的一部分。当然,软件也可以轮询IAR寄存器。此时,GIC解除给PE的中断信号。
-
Active & Pending→Active当外设解除给GIC的中断信号后,由
Active & Pending转变为Active状态。这通常发生在PE上的中断处理程序写外设的状态寄存器时(作为写状态寄存器的响应)。 -
Active→Inactive当PE写EOIR寄存器(中断结束寄存器)时,由
Active转变为Inactive状态。这表明PE已经完成中断的处理。
3.2.2 边沿触发
边沿触发中断的生命周期
-
Inactive→Pending当中断源产生信号时,就会从
Inactive转变为Pending状态。从此刻开始,GIC发送中断信号给PE(如果中断被使能并且有足够的优先级)。 -
Pending→Active当PE通过读取IAR寄存器应答了中断后,中断的状态
Pending转变为Active。这种读取操作通常是中断处理程序(中断异常发生后)的一部分。当然,软件也可以轮询IAR寄存器。此时,GIC解除了给PE的中断信号。
-
Active→Active & Pending如果外设重新产生该中断信号,则会由
Active状态转变为active & pending。 -
Active & Pending→Pending当PE对某个EOIR寄存器进行写操作时,中断会由
Active & Pending状态转变为Pending状态。这表明,PE已经处理完了第一次中断。此时,
GIC重新给PE发送中断信号。
3.3 亲和力路由
GICv3使用亲和力路由识别连接的PE,并将中断路由到某个PE或某组PE上。PE的亲和力通过4个8位字段表示:
<亲和力3>.<亲和力2>.<亲和力1>.<亲和力0>
下图是一个亲和力层次结构
图-一个亲和力层次架构的示例
亲和力0对应于Redistributor。也就是说,每个Redistributor连接到一个单独的CPU接口上。Redistributor可以控制SGI、PPI和LPI,参见第4章。
该亲和力方案与ARMv8-A中的相一致,与MPIDR_EL1寄存器中保存的PE的亲和力匹配。系统设计者必须保证MPIDR_EL1中的亲和力值等于GICR_TYPER中的值,后者是Redistributor和连接PE的对应关系。
不同级别的亲和力其真实意义由具体的处理器或SoC定义。下面是一个示例:
<group of groups>. <group of processors>.<processor>.<core>
<group of processors>.<processor>.<core>.<thread>
将所有可能节点在单个SoC都实现,往往不太现实。下面是一个移动设备SoC的布局:
0.0.0.[0:3] Cortex-A53处理器的0到3核
0.0.1.[0:1] Cortex-A57处理器的0到1核
在ARMv8-A中,AArch64状态支持4级亲和力。AArch32状态和ARMv7架构只支持3级亲和力。意思就是亲和力3上只有一个节点(0.x.y.z)。GICD_TYPER.A3V表明中断控制器是否支持多个亲和力3上的节点。
虽然每个L1级亲和力的节点上,最多可以承载256个
redistributor(L0级),但实际上,一般是16个或者更少。这是因为SGI中断的目标PE的编码方式,具体参考第7章。
3.4 安全模型
GICv3架构支持ARM的TrustZone技术。每个INTID必须分配到一个组(group)中,并且设置安全状态,如下表所示。
表 - 安全和分组
| 中断类型 | 示例使用 |
|---|---|
| Secure Group 0 | EL3中断(安全固件) |
| Secure Group 1 | Secure EL1中断(可信OS) |
| Non-secure Group 1 | 非安全状态的中断(OS和/或hypervisor) |
Group 0中断总是以FIQ的形式发送信号。Group 1既可以IRQ的形式,也可以以FIQ的形式发送信号,依赖于PE当前安全状态和异常等级。
表 - 安全设置和异常类型以及中断的对应关系(EL3使用AArch64)
| PE的异常等级和安全状态 | Secure Group 0 | Secure Group 1 | Non-secure Group 1 |
|---|---|---|---|
| Secure EL0/1 | FIQ | IRQ | FIQ |
| Non-secure EL0/1/2 | FIQ | FIQ | IRQ |
| EL3 | FIQ | FIQ | FIQ |
这些规则被设计用来补充ARMv8-A安全状态和异常级路由控制。下图展示了一个简化的软件栈,以及当在EL0执行不同类型的中断时发生的情况:
在本示例中,IRQ被路由到EL1(SCR_EL3.IRQ==0),FIQ被路由到(SCR_EL3.FIQ==1)。按照上表描述的规则,当在EL1或EL0执行当前安全状态的group 1中断时被视为一个IRQ。
非当前安全状态的中断一律视为FIQ,被EL3捕获。这样的设计,允许EL3的软件执行必要的上下文切换。这种情况的详细示例请参考第5.3节。
3.4.1 对软件的影响
在配置中断控制器时,软件负责将INTID分配给某个中断组。只有执行在安全状态的代码能够分配INTID给中断组。
通常只有执行在安全状态的软件能够访问安全中断的设置和状态(Group 0和Secure Group 1)。
从非安全状态设置安全中断并访问其状态是能够使能的。可以使用GICD_NSACRn和GICR_NSACR寄存器对每一个INTID单独进行控制。
注意1:在复位时,
INTID所属的中断组是SoC实现时定义的。注意2:LPI总是被视为非安全组1中断。
3.4.2 单个安全状态的支持
对于ARMv8-A和GICv3,两种安全状态是可选择的。所以,SoC在实现的时候,可以选择实现单个安全状态,也可以选择实现两个安全状态。
在GICv3实现中,支持两种安全状态。可以通过GICD_CTLR.DS控制使用几个安全状态。
-
GICD_CTLR.DS == 0:支持两个安全状态(Secure和Non-secure)。 -
GICD_CTLR.DS == 1:只支持一个安全状态。在只实现了一个安全状态的实现中,该位是RAO/WI。
当只支持单个安全状态时,中断分为2组,Group 0和Group 1。
当然,本文档主要描述支持两种安全状态的实现。
注意:如果设置
GICD_CTLR.DS为1,则只能在复位时才能清除。
3.5 编程模型
GICv3中断控制器的寄存器主要分为3组:
-
Distributor接口 -
Redistributor接口 -
CPU接口
3.5.1 Distributor(GICD_*)
Distributor寄存器是内存寄存器,包含影响所有PE的全局设置。包括:
-
SPI中断优先级和分发
-
使能、禁止SPI中断
-
配置各个SPI中断的优先级
-
每个SPI中断的路由信息
-
配置各个SPI中断的触发方式(电平或边沿)
-
产生基于消息的SPI中断
-
控制SPI中断的
active和pending状态 -
确定各个安全状态中使用的编程模型(亲和力路由还是旧方式)
3.5.2 Redistributor(GICR_*)
每个PE对应一个redistributor。redistributor提供的功能:
-
使能、禁止SGI和PPI中断
-
配置SGI和PPI中断的优先级
-
配置PPI中断的触发方式(电平或边沿)
-
给每一个SGI和PPI中断分配一个中断组
-
控制SGI和PPI中断的状态
-
控制LPI中断属性和挂起状态的数据结构在内存中的基地址
-
每个PE的电源管理
3.5.3 CPU接口(ICC_*_ELn)
每个redistributor连接到CPU接口。CPU接口提供的功能:
-
使能中断处理的控制和配置
-
应答中断
-
去掉中断的优先级并失效中断
-
为PE配置中断优先级掩码
-
为PE配置抢占策略
-
为PE确定最高优先级的挂起中断
GICv3中,CPU interface寄存器都是系统寄存器(ICC_*_ELn)。
软件必须在使用这些寄存器之前使能这些寄存器的访问。这是由ICC_SRE_ELn寄存器的SRE标志位控制的,在这儿,n表示异常级别(EL1-EL3)。
注意1:
GICv1和GICv2中,CPU接口寄存器是映射到内存上的(GICC_*)。注意2:可以通过读取寄存器
ID_AA64PFR0_EL1查看是否支持GIC系统寄存器,具体可以参考ARMv8-A架构规范。
4 配置GIC
本章描述如何在裸机环境下使能和配置兼容GICv3的中断控制器。详细的寄存器描述请参考《ARM® Generic Interrupt Controller Architecture Specification GIC architecture version 3.0 and 4》
LPI中断的配置和SPI、PPI和SGI存在着较大差异,因此,专门在第6章描述。
使用GICv3中断控制器的大部分是多核系统,或是多处理器系统。有些配置是全局的,也就是说,会影响所有连接的PE。其它的配置是专门针对单个PE的。
我们首先看一下全局配置,然后是每个PE的设置。
4.1 全局配置
Distributor的控制寄存器(GICD_CTLR)可以控制中断分组的使能,并设置路由模式。
-
使能亲和力路由(ARE标志位)
GICD_CTLR的ARE标志位控制是否使能亲和力路由。如果没有使能,GICv3使用旧操作方式。亲和力路由的使能,在安全和非安全状态下是分开控制的。 -
分发器使能
GICD_CTLR包含Group 0、Secure Group 1和Non-secure Group 1的独立使能位:GICD_CTLR.EnableGrp1S使能Secure Group 1中断的分发;GICD_CTLR.EnableGrp1NS使能Non-secure Group 1中断的分发;GICD_CTLR.EnableGrp0使能Non-secure Group 0中断的分发;
4.2 单个PE配置
4.2.1 Redistributor配置
复位时,Redistributor将其连接的PE视为休眠状态。唤醒是通过GICR_WAKER控制的。要将连接的PE标记为唤醒状态,软件必须:
-
将
GICR_WAKER.ProcessorSleep清零; -
轮询
GICR_WAKER.ChildrenAsleep,直到读到0;
使能和配置LPI在第6章。
当GICR_WAKER.ProcessorSleep==1或GICR_WAKR.ChildrenAsleep==1时,写CPU interface寄存器(除了ICC_SRE_ELn),会导致不可预知的行为。
4.2.2 CPU interface配置
CPU interface负责将中断传送到连接的PE上。为了使能CPU interface,软件必须进行下面的配置:
-
使能系统寄存器访问
通过设置
ICC_SRE_ELn的SRE位进行使能。 -
设置优先级掩码和
binary point寄存器CPU接口包含优先级掩码寄存器(
ICC_PMR_EL1)和Binary Point寄存器(ICC_BPRn_EL1)。优先级掩码寄存器设置中断最小优先级(转发给PE靠此判断)。Binary Point寄存器用于优先级分组和抢占。这些寄存器的更多使用可以参考第5章。 -
设置
EOI模式ICC_CTLR_EL1和ICC_CTLR_EL3中的EOImode标志位控制如何处理中断的完成。详细的描述参见第5.5小节。 -
各个中断分组发送信号使能
必须在某个中断组的中断转发到相应的CPU接口之前,使能中断分组发送信号的功能。为了使能中断信号的发送,可以通过
ICC_IGRPEN1_EL1设置Group 1中断,通过ICC_IGRPEN0_EL1设置Group 0中断。ICC_IGRPEN1_EL1在安全状态下有一个备份。这意味着ICC_GRPEN1_EL1可以控制安全状态下的Group 1中断。在EL3,软件即可以通过ICC_IGRPEN1_EL3,访问安全Group 1中断和非安全Group 1中断的使能。
4.2.3 PE配置
PE还需要一些配置,以允许它接收和处理中断。详细的描述超出了本文的范围。这里只描述遵循ARMv8-A的PE在AArch64状态下所需执行的基本步骤就足够了。
-
路由控制
中断路由控制的标志位在PE的
SCR_EL3和HCR_EL2寄存器中。路由控制位决定了中断被路由到哪一个异常等级。复位时,这些标志位的状态未知,所以,软件必须进行初始化。 -
中断掩码
PE在PSTATE中也有异常掩码位。设置了这些位,中断会被屏蔽。复位时被重置。
-
中断向量表
PE的中断向量表的地址保存到
VBAR_ELn寄存器中。复位时,VBAR_ELn的值未知。软件必须设置VBAR_ELn指向内存中的正确的向量表。
更多信息,参考ARMv8-A架构参考手册。
4.3 SPI、PPI和SGI配置
SPI是通过Distributor使用GICD_*寄存器配置的。PPI和SGI通过单独的Redistributor使用GICR_* 寄存器进行配置。
对于每一个INTID,软件必须配置以下内容:
-
优先级(
GICD_IPRIORITYn、GICR_IPRIORITYn)每个
INTID拥有一个相关的优先级,用8位无符号数表示。0x00是最高优先级,0xFF是最低优先级。第5章描述了GICD_IPRIORITYn和GICR_IPRIORITYn中的优先级是如何屏蔽掉低优先级的中断的,以及它如何控制抢占。中断控制器不需要实现所有的8个优先级表示位。如果GIC支持两个安全状态,最小实现5个标志位。如果仅支持一个安全状态,最小实现4个标志位即可。
-
分组(
GICD_IGROUPn、GICD_IGRPMODn、GICR_IGROUP0、GICR_IGRPMOD0)正如第3.4节中描述的,一个中断可以被指定为3个不同分组中的一个(
Group 0、Secure Group 1和Non-secure Group 1)。 -
边沿触发、电平触发(
GICD_ICFGRn、GICR_ICFGRn)如果中断作为物理信号发送,则必须配置其触发方式(边沿触发或电平触发)。
SGI总是边沿触发,因此,对于软件中断,GICR_ICFGR0总是读为1,而忽略写操作(RAO/WI)。 -
中断使能(
GICD_ISENABLERn、GICD_ICENABLER、GICR_ISENABLER0、GICR_ICENABLER0)每个中断(
INTID)都有一个使能位。设置使能寄存器(set-enable)和清除使能寄存器(clear-enable)寄存器消除了读-修改-写的竞态问题。ARM推荐:在使能INTID之前,应该配置本节概括的这些设置。
对于裸机环境,初始化配置之后通常不需要更改设置。但是,如果确实要重新配置中断,比如更变分组设置,建议首先禁止该INTID。
大部分配置寄存器的复位值都是实现定义的。也就是说,中断控制器的设计者决定这些值是多少,这些值可能因系统而已。
4.3.1 为SPI中断设置目标PE
对于SPI中断,必须配置中断的目标。由GICD_IROUTERn控制。每个SPI都有一个GICD_IROUTERn寄存器,Interrupt_Routing_Mode位控制路由策略,如下所示:
-
GICD_IROUTERn.Interrupt_Routing_Mode == 0SPI被传送到指定的PE(
A.B.C.D),寄存器中指定的亲和力值。 -
GICD_IROUTERn.Interrupt_Routing_Mode == 1SPI可以被传送到中断分组中的任一个PE。Distributor选择合适的目标PE,每次发送中断信号的时候这可能会变化。这种路由类型称为1对N模式。
PE可以选择不接收1-of-N中断。这由GICR_CTLR中的DPG1S、DPG1NS和DPG0位控制。
5 处理中断
5.1 中断挂起后会发生什么
第3.2节,描述了外设产生一个专用中断信号后,中断控制器中由inactive转换为pending状态。
当中断变为pending状态,中断控制器决定是否将中断发送给某个PE。中断控制器选择PE,依赖于下面的条件:
-
分组使能
第3.4节描述了如何将
INTID指定给某个中断分组(Group 0、Secure Group 1、或Non-secure Group 1。对于每个中断分组,在Distributor和CPU Interface中有对应的控制位。如果一个中断属于被禁止的中断分组,则不能被转发给PE。 -
中断使能
单独被禁止的中断可以被挂起(
pending),但是不会被转发给PE。 -
路由控制
依赖于中断类型,中断控制器决定哪些PE可以接收该中断。
-
对于
SPI,路由行为由寄存器GICD_IROUTERn控制。一个SPI可以指定一个特定PE作为目标,也可以指定所有连接PE中的任一个作为目标。 -
对于
LPI,路由信息来自于ITS(如果实现了ITS,参考第6.1节)。 -
对于
PPI,是PE私有的,所以只能由该PE进行处理。 -
对于
SGI,发起软件中断的PE定义目标PE的列表。将在第7章展开进一步的描述。
-
-
中断优先级&优先级掩码
每个PE有一个优先级掩码寄存器(
ICC_PMR_EL1),该寄存器属于CPU interface类寄存器。该寄存器设置将中断转发到对应的PE上所需要的最小优先级。只有优先级大于该值的中断才能被发送给对应的PE。 -
运行优先级
第5.4节讲述了
运行优先级这个概念,以及它如何影响抢占。如果相应的PE还没有处理中断(此时已经挂起),运行优先级就是空闲优先级(0xFF)。只有比当前运行优先级更高的中断可以抢占当前中断。
5.2 中断应答
CPU interface拥有两个应答寄存器(IAR)。如果读取应答寄存器(IAR),则返回INTID,并更改中断状态机。在典型的中断处理程序中,第一步往往就是先读取IAR寄存器。具体寄存器描述如下表所示:
| 寄存器 | 用途 |
|---|---|
| ICC_IAR0_EL1 | 用于应答Group 0中断 |
| ICC_IAR1_EL1 | 用于应答Group 1中断 |
5.3 伪中断
第3.1.2小节描述了中断号1020→1023是为特殊目的保留的。这些中断ID可以通过读取IAR寄存器获取,用以标识异常处理中的一些特殊情况。
| ID | 意义 | 使用场景 |
|---|---|---|
| 1020 | 只有读取ICC_IAR0_EL1才返回;最高挂起中断属于Secure Group 1;只有作为FIQ发送到EL3时才能看见该中断; |
当PE运行在非安全状态时,产生了可信OS的中断。会被当作一个FIQ发送到EL3,以便Secure Monitor能够快速切换到可信OS中 |
| 1021 | 只有读取ICC_IAR0_EL1才返回;最高挂起中断属于Non-secure Group 1;只有作为FIQ发送到EL3时才能看见该中断; |
当PE运行在安全状态时,产生了rich-OS的中断。会被当作一个FIQ发送到EL3,以便Secure Monitor能够快速切换到rich-OS中 |
| 1022 | 旧操作使用 | 本文档不讨论旧操作 |
| 1023 | 伪中断。没有使能的INTID处于pending状态,或所有处于pending状态中的INTID没有足够的优先级被处理。 |
轮询IAR寄存器时,该值表明没有可用中断需要应答。 |
5.3.1 示例
在下面的示例中,展示了移动系统,因为即将来临的电话,产生了一个modem中断信号。本中断打算是由运行在非安全状态的rich-OS处理:
图-使用中断号1021的示例
-
当
PE正在Secure EL1上执行可信OS时,modem中断会被挂起(pending)。因为modem中断属于非安全Group 1,所以它会以FIQ信号发送到PE。因为SCR_EL3.FIQ==1,异常陷入到EL3。 -
执行在
EL3的Secure Monitor软件,读取IAR寄存器,返回1021。该值表明中断本应在非安全状态下处理。于是,Secure Monitor执行必要的上下文切换操作。 -
现在
PE处于非安全状态,中断以IRQ的形式被发送,由运行在非安全EL1上的rich-OS进行处理。
在上面的示例中,非安全Group 1中断导致立即退出安全OS。但这并不总是需要的。下图展示了另一个模型,中断首先陷入到Secure EL1。
-
当
PE在Secure EL1上执行可信OS时,modem中断会被挂起(pending)。因为modem中断属于非安全Group 1,所以它会以FIQ信号发送到PE。因为SCR_EL3.FIQ==0,中断陷入到EL1。 -
可信OS保存好自身的状态。然后,调用SMC指令切换到非安全状态。 -
SMC异常陷入到EL3。执行在EL3的Secure Monitor执行必要的上下文切换操作。 -
现在
PE处于非安全状态,中断以IRQ的形式被发送,由运行在非安全EL1上的rich-OS进行处理。
5.4 运行优先级和抢占
PMR优先级掩码寄存器可以设置中断被转发到PE所需要的最小优先级。GICv3架构还有一个运行优先级的概念。当PE应答了中断后,PE的运行优先级就会成为中断的优先级。当PE写EOI寄存器时,运行优先级返回到它之前的值。
图-运行优先级随时间的变化
CPU interface寄存器ICC_RPR_EL1报告当前运行优先级。
当考虑抢占的时候,运行优先级的概念就很重要了。抢占一般发生在PE正在处理一个低优先级的中断时,一个高优先级中断信号被发送到该PE上。抢占给软件带来了复杂性,但是它阻止了低优先级中断阻塞高优先级中断的处理。
图-没有抢占的情况
图-有抢占的情况
上图只是展示了一层抢占的情况。事实上,可能存在多层抢占的情况。
在某些情况下,可能不需要抢占。GICv3架构允许将可抢占的优先级进行分组,通过Binary Point寄存器(ICC_BPRn_EL1)实现。
Binary Point寄存器将优先级分为两个域:group优先级和subpriority优先级。
图-8位优先级表示位被分为了
group和subpriority
抢占的时候,只考虑group优先级,而忽略subpriority优先级。比如,考虑下面3个中断(N=4,分组后的寄存器如下图所示):
假设,
- 中断A的优先级为
0x10 - 中断B的优先级为
0x20 - 中断C的优先级为
0x21
该场景下:
- A能够抢占B或C。
- B不能抢占C,因为B和C具有相同的优先级。
有了这种划分,B和C被认为具有相同的抢占优先级。但是,A仍然具有较高的优先级,可以抢占B或C。
抢占要求中断处理程序必须支持嵌套。详细的内容超出本文章的范围,暂时不讨论。
5.5 中断结束
当中断被处理完后,软件必须通知中断控制器,以便其中断状态机转向下一个状态。GICv3架构将该任务分为两部分:
-
优先级降落(Priority drop)这意味着将
运行优先级降落回中断被处理之前的值。 -
失效(Deactivation)这意味着更新当前处理中断的状态机。通常,从
active转换到inactive状态。
GICv3架构中,Priority drop和Deactivation即可以一起发生,也可以分开发生。这由ICC_CTLR_ELn.EOImode设置决定:
-
EOImode = 0写
ICC_EOIR0_EL1(Group 0)或ICC_EOIR1_EL1(Group 1),执行优先级降落和失效两个动作。这种模式通常用于简单的裸机程序。 -
EOImode = 1写
ICC_EOIR0_EL1(Group 0)或ICC_EOIR1_EL1(Group 1),只执行优先级降落。单独写ICC_DIR_EL1寄存器执行失效操作。这种模式通常用于虚拟化场景。
当操作这些寄存器时,INTID也必须写入。
5.6 检查系统的当前状态
5.6.1 最高优先级挂起中断和运行优先级
正如名称所表示的,最高优先级挂起中断寄存器(ICC_HPPIR0_EL1 & ICC_HPPIR1_EL1)报告该PE上挂起的最高优先级中断的中断号(INTID)。不同的PE可能不同,比如,为SPI中断设置的不同路由目标。运行优先级在ICC_RPR_EL1寄存器中。
5.6.2 单个中断号的状态
Distributor提供了表示每个SPI中断当前状态的寄存器。相似的,Redistributor也提供了表示PPI和SGI中断当前状态的寄存器。
通过设置这些寄存器,也可以将中断转换到某种状态。这在没有外设的情况下,测试配置是否正确的时候非常有用。
有单独的寄存器分别报告active状态和pending状态。下表列出了active寄存器。pending状态寄存器拥有相同的格式。
| 寄存器 | 描述 |
|---|---|
GICD_ISACTIVERn |
设置SPI的active状态。每个INTID一位。读取某一位,返回值为:* 1 – 该中断处于active状态;* 0 – 该中断处于非active状态;对某一位写1,则激活相应的中断;写0没有影响。 |
GICD_ICACTIVERn |
清除SPI的active状态。每个INTID一位。读取某一位,返回值为:* 1 – 该中断处于active状态;* 0 – 该中断处于非active状态;对某一位写1,则失效相应的中断;写0没有影响。 |
GICR_ISACTIVER0 |
设置SGI和PPI的active状态。每个INTID一位(0-31)。读取某一位,返回值为:* 1 – 该中断处于active状态;* 0 – 该中断处于非active状态;对某一位写1,则激活相应的中断;写0没有影响。 |
GICR_ICACTIVER0 |
清除SGI和PPI的active状态。每个INTID一位(0-31)。读取某一位,返回值为:* 1 – 该中断处于active状态;* 0 – 该中断处于非active状态;对某一位写1,则失效相应的中断;写0没有影响。 |
注意1:当亲和力使能时,
GICD_ISACTIVER0和GICD_ICACTIVER0被看作RES0。这是因为GICD_ISACTIVER0和GICD_ICACTIVER0在该情况下对应的中断号是0-31,而它们在每个PE中都有备份,通过每个PE的redistributor报告。注意2:运行在非安全状态的软件,不能看见
Group 0或Secure Group 1中断的状态,除非通过GICD_NASCRn或GICR_NASCRn允许访问。
6 配置LPI
LPI只有在亲和力路由使能时支持,只是它们的配置方式不同。
配置LPI涉及到:
-
Redistributor。 -
可选的
ITS(中断翻译服务)。
LPI总是基于消息的中断,并且可以由ITS支持。ITS负责接收外设中断,将其转换为LPI并转发给合适的Redistributor。一个系统可以包含多个ITS,每一个都能够单独配置。
外设也可以直接发送LPI给Redistributor,从而绕过ITS。但是,ITS提供了许多特性,允许高效地处理大量的中断源。
注意:对
LPI的支持是可选的,由GICD_TYPER.LPIS标志位控制。如果至少存在一个ITS,外设是否可以绕过ITS,直接发送LPI给Redistributor是中断控制器实现时定义的。
6.1 ITS
6.1.1 ITS的操作
外设通过写ITS的GITS_TRANSLATER可以产生LPI中断。写操作提供给ITS以下信息:
-
EventID写入到
GITS_TRANSLATER的值。EventID标识外设正在发送的中断。EventID可能与INTID相同,或者可以通过ITS翻译成INTID。 -
DeviceIDDeviceID标识外设。产生外设标识符DeviceID的方法是实现时定义的。比如,可以使用AXI用户信号。
ITS将外设写入到GITS_TRANSLATER的EventID转换成INTID。如何将EventID转换成INTID取决于具体的外设,这就是为什么需要DeviceID的原因。
LPI中断的INTID集合在一起。集合中的所有INTID都路由到相同的Redistributor。软件分配LPI INTID给集合,允许它有效的将中断从一个PE迁移到另一个PE。
ITS使用三种类型的表处理LPI的翻译和路由。分别是:
-
设备表
将
DeviceID映射到中断翻译表。 -
中断翻译表
包含与
DeviceID相关的EventID和INTID映射关系。还包含成员是INTID的集合。 -
集合表
将集合映射到
Redistributor。
图-
ITS转发LPI到Redistributor
当外设写GITS_TRANSLATER时,ITS需要:
-
使用
DeviceID从设备表中选择合适的项。该项标识使用哪个中断翻译表。 -
使用
EventID从中断翻译表中选择合适的项。该项提供INTID和集合ID。 -
使用
集合ID从集合表中选择想要的项,它会返回路由信息。 -
转发中断到目标
Redistributor。
注意:
ITS可以选择支持大量的硬件集合。硬件集合是ITS内部保存配置的地方,而不是将其存储在内存中。GITS_TYPER.HCC报告了支持的硬件集合数量。
6.1.2 命令队列
ITS通过内存中的命令队列进行控制。该命令队列是一个环形缓冲区,由3个寄存器定义。
-
GITS_CBASER这个寄存器指定了命令队列的基地址和大小。命令队列必须是
64k大小对齐的,大小必须是4K的倍数。队列中的每一项是32字节。GITS_CBASER还指定了ITS在访问命令队列时使用的缓存性和可共享性设置。 -
GITS_CREADR指向
ITS将要处理的下一个命令。 -
GITS_CWRITER指向下一个新命令将要写入的位置。
图-简化的
ITS循环命令队列
<ARM® Generic Interrupt Controller Architecture Specification GIC architecture version 3.0 and 4.0>提供了ITS支持的所有命令的详细信息,以及编码方式。
6.1.3 ITS的初始化配置
为了在系统启动的时候,配置ITS服务,软件必须:
-
为设备和集合表申请内存。
GITS_BASER[0..7]寄存器指向ITS设备和集合表的基地址和大小。软件使用这些寄存器发现ITS支持的表数量和类型。然后,软件申请所需内存,设置GITS_BASERn指向这些分配的内存表。 -
为命令队列申请内存。软件必须为命令队列分配内存,然后设置
GITS_CBASER和GITS_CWRITER指向这些内存的起始地址。 -
使能
ITS。当这些表和命令队列申请内存成功,就可以使能ITS。通过设置GITS_CTLR.Enable为1实现。一旦该标志位设置成功,则GITS_BASERn和GITS_CBASER寄存器就变成了只读的。
6.1.4 集合和设备表的大小和布局
设备和集合表的位置和大小是通过GITS_BASERn寄存器配置的。软件必须分配足够的内存给这些表,并在使能ITS之前配置好GITS_BASERn。
软件可以分配平面(单级)表,或两级表,通过GITS_BASERn.Indirect标志位指定。
注意:两级表的支持是可选的。如果
ITS仅支持平面表,则GITS_BASERn.Indirect是RAZ/WI。
RAZ/WI:read as zero, write ignored。
-
平面表
使用平面表,则是分配一块连续的物理内存给
ITS,用以记录映射关系。在使能ITS之前,软件需要对这段内存全部填充0。之后,ITS在处理命令队列的命令时填充该表。一个平面设备或集合表
表的大小依赖于
DeviceID或集合ID的宽度(位数,即ID数量)。大小计算如下:Size(单位:字节) = 2^ID_width * entry_size在这儿,
entry_size是每个表项的字节数,由GITS_BASERn.Entry_Size字段表示。配置
GITS_BASERn寄存器时,表的大小是按照page页数量进行指定的。page页的大小由GITS_BASERn.Page_Size控制,可以是4K、16K、64K。因此,上面公式计算出的结果必须是page页的大小的倍数,不足一页的按照一页分配。例如,如果实现的是8位
DeviceID,每个表项的大小是8个字节,页大小是4K,则:2^8 * 8 = 2048 字节 => 按页向上取整的算法,则表大小应该是`4K` -
两级表
使用两级表时,软件申请分配一个1级表,和一些2级表。
1级表由软件填充,每一项即可以指向一个2级表,也可以标记为
invalid。2级表在被分配给ITS之前,应该将其填充0,在ITS处理命令队列的命令时再将具体内容写入该表。ITS使能时(GITS_CTLR.Enabled==1),软件可能会申请分配新的2级表,并更新1级表的表项,以便指向这些新添加的2级表。当ITS使能时,软件不能删除已有的分配表,或更改已经存在的分配表。每个2级表的大小是一个
page页。和平面表一样,page页通过GITS_BASERn.Page_Size进行设置。所以,它包含的项数是page_size/entry_size。也就是说,每个1级表项表示(
page_size / entry_size)个ID,同时它可以指向一个2级表或被标记为无效。任何使用了无效ID的ITS命令(该ID对应无效表项)都会被放弃。1级表的大小可以通过下面公式进行计算:
Size(单位:字节) = (2^ID_width / (page_size / entry_size)) * 8和平面表类似,1级表的大小也是按页指定的。因此,上面公式向上按页取整。
6.1.5 添加一个新命令到命令队列中
为了添加新命令到命令队列,软件需要:
-
写新命令到队列中。
GITS_CWRITER指向没有包含合法命令的队列项。软件必须写命令到该项中,并保证全局可见性。 -
更新
GITS_CWRITER软件必须更新
GITS_CWRITER,让其指向没有包含新命令的下一个队列项。更新动作会告知ITS,已经添加了一个新命令。当然,软件能够同时写多个命令到队列中,只要还有足够的空间,并且相应更新
GITS_CWRITER寄存器。 -
等待命令被
ITS读取软件可以轮询
GITS_CREADR,判断命令是否被读取。当GITS_CWRITER.Offset == GITS_CREADR.Offset时,说明所有的命令已经被ITS读取。另外,还可以添加一个
INT命令,产生一个中断信号,告知一组命令已经被ITS读取。ITS按照顺序读取命令队列中的命令。但是,这些命令对于Redistributor的影响是乱序的。SYNC命令保证前面发出的命令的效果是可见的。
当
GITS_CWRITER指向GITS_CREADR前面的一个位置时,说明命令队列满了。在尝试添加新命令之前,软件必须检查队列中是否还有足够空间。
6.1.6 将中断映射到Redistributor
-
将
DeviceID映射到翻译表每个能够发送中断到
ITS的外设都有自己的DeviceID。每个DeviceID都有自己的中断翻译表(ITT),该表保存着EventID到INTID的映射关系。软件必须为ITT分配内存,然后,使用MAPD命令将DeviceID映射到ITT。MAPD <DeviceID>, <ITT_Address>, <Size> -
将
INTID映射到集合,将集合映射到Redistributor当外设的
DeviceID已经映射到ITT,那么该设备所发送的不同EventID必须映射到INTID,而这些INTID必须被映射到不同的集合。每个集合被映射到一个目标Redistributor上。中断号(
INTID)映射到集合,可以使用MAPTI和MAPI命令。当EventID和INTID相同时,使用MAPI命令:MAPI <DeviceID>, <EventID>, <Collection ID>当
EventID和INTID不同时,使用MAPTI命令:MAPTI <DeviceID>, <EventID>, <INTID>, <Collection ID>将集合映射到
Redistributor时,使用MAPC命令:MAPC <Collection ID>, <Target Redistributor>目标
Redistributor的标识依赖于GITS_TYPER.PTA:-
GITS_TYPER.PTA==0:使用ID标识Redistributor,该ID可以从GICR_TYPER.Processor_Number读取。 -
GITS_TYPER.PTA==1:使用物理地址指定Redistributor。
-
-
示例
定时器的设备ID(
DeviceID)是5,且使用2个比特位的事件ID(EventID)。我们将EventID=0映射为INTID(8725)。为定时器分配的ITT的地址是0x84500000。假设集合的编号为
3,目标Redistributor的物理地址是0x78400000。命令序列如下所示:
MAPD 5, 0x84500000, 2 Map DeviceID 5 to an ITT MAPTI 5, 0, 8725, 3 Map EventID 0 to INTID 8725 and collection 3 MAPC 3, 0x78400000 Map collection 3 to Redistributor at address 0x78400000 SYNC 0x78400000该示例假设之前没有建立任何映射,且
GITS_TYPER.PTA==1。
6.1.7 在Redistributor之间迁移中断
有2种方法将中断从一个Redistributor迁移到另一个:
-
重映射集合
软件可以通过重映射整个集合,将所有中断从一个
Redistributor迁移到另外一个Redistributor上。这通常发生在与Redistributor绑定的PE关闭电源,且所有的中断必须迁移到另一个Redistributor上时。迁移的命令序列如下所示:MAPC <Collection ID>, <RDADDR2> # 重映射集合到新的Redistributor SYNC <RDADDR2> # 同步操作,保证映射完成 MOVALL <RDADDR1>, <RDADDR2> # 将所有处于挂起状态的中断迁移到新Redistributor SYNC <RDADDR1> # 同步操作,保证中断状态迁移完成在这段命令序列中,
RDADDR1是之前的目标Redistributor,RDADDR2是新Redistributor。如果有多个集合的目标是
RDADDR1,我们可能需要多个MAPC命令,每一个命令对应一个集合。这儿,假设所有的集合都将重映射到相同的新目标Redistributor上。 -
将中断映射到不同的集合里
单个中断可以被重映射到另一个集合中。通过下面的命令序列完成:
MOVI <DeviceID>, <EventID>, <新集合ID> SYNC <RDADDR1>在该命令序列中,
RDADDR1是中断被重新映射之前,原来指定的那个集合对应的Redistributor。
6.1.8 移除中断映射
为了重新映射中断,或删除中断映射关系,软件需要:
-
禁止当前映射中断的物理中断号(
INTID)。这是在LPI配置表中完成的,参考第6.2.2节。