GICv3/v4-软件概述
Posted tupelo-shen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GICv3/v4-软件概述相关的知识,希望对你有一定的参考价值。
目录内容
- 2 介绍→主要介绍GIC架构历史
- 3 GICv3基础→概念理解,尤其是编程模型的理解
- 4 GIC配置→如何配置GIC的各种寄存器,使其正常工作
- 5 处理中断→讲解中断的处理流程
- 6 LPI配置→理解ITS服务和基于消息的中断
- 7 SGI中断→如何发送接收软中断
- 8 虚拟化→如何在虚拟化环境下管理虚拟中断
- 9 GICv4→虚拟LPI的直接注入
2 介绍
2.1 范围
GICv3
可以支持许多不同的配置和使用场景。为了简化,本文只关注其中的一个子集,该场景下:
-
存在2个安全状态
-
2个安全状态都使能中断路由
-
所有异常级都能访问系统寄存器
-
所有的处理器遵循
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 是系统寄存器增强安全模型,独立的安全Group1中断和非安全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
新引入的,它的方式不同于其它类型的中断。也就是说,这类中断是通过写内存产生的,而不是传统的使用中断线的方式。详细的描述在第6章。注意:
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 == 0
SPI被传送到指定的PE(
A.B.C.D
),寄存器中指定的亲和力值。 -
GICD_IROUTERn.Interrupt_Routing_Mode == 1
SPI
可以被传送到中断分组中的任一个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
。 -
DeviceID
DeviceID
标识外设。产生外设标识符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节。 -
发送
DISCARD
命令。这将移除中断映射关系,并清除对应INTID
的挂起状态。 -
发送
SYNC
命令,并等待直到该命令完成。
该命令完成后,不会再有中断被发送到中断原先映射的Redistributor
上了。
6.1.9 重映射或删除设备映射关系
为了改变或删除设备映射关系,软件需要:
-
对于当前映射外设的每个
EventID
,按照第6.1.8节执行一遍。 -
发送
MAPD
命令重新映射设备。或者使用有效位为0
的MAPD
命令删除映射关系。 -
发送
SYNC
命令,并等待直到该命令完成。
6.2 Redistributor
Redistributor
掌握着所有物理LPI
中断的控制、优先级和挂起
以上是关于GICv3/v4-软件概述的主要内容,如果未能解决你的问题,请参考以下文章