目录内容
- 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
中断的控制、优先级和挂起状态信息,这些保存在内存表中。
LPI
中断的配置信息存储在内存的一张表上。这就是LPI
配置表,地址存储在GICR_PROPBASER
寄存器中。LPI
的配置是全局的,也就是说,所有的Redistributor
看见的是相同的配置。通常,一个系统使用一张LPI
配置表。
同样,LPI
中断的状态信息也存储在内存表上,不过是多张表。这就是LPI
挂起状态表,地址存储在GICR_PENDBASER
寄存器中。每个Redistributor
拥有自己的LPI
挂起状态表,也就是说,该表是私有的。
图-
LPI
配置和挂起状态表
6.2.1 初始化Redistributor的配置
初始化Redistributor
的步骤如下:
-
为
LPI
配置表分配内存,并使用每个LPI
中断合适的配置信息初始化该表。 -
设置
GICR_PROPBASER
,使其都指向该LPI
配置表。 -
为每个
Redistributor
的LPI
挂起状态表分配内存,并分别初始化每个表。系统启动时,将表所在内存填充0
,这意味着所有的LPI INTID
都处于未激活状态(inactive
)。 -
设置每个
Redistributor
自己的GICR_PENDBASER
,使其指向自己私有的LPI
挂起状态表。 -
设置每个
Redistributor
自己的GICR_CTLR.EnableLPIs
为1
,使能LPI
中断。该标志位设为1
后,GICR_PENDBASER
和GICR_PROPBASER
变为只读。
-
LPI
配置表LPI
配置表中,每个INTID
占用一个字节。下图展示了该字节中各个字段的内容。
我们知道,`SPI`、`PPI`、`SGI`使用8位表示优先级,但是`LPI`只使用`6`位表示优先级,其最低两位总是`0b00`。而且没有字段记录安全状态。因为`LPI`中断总是被视为非安全`Group 1`中断。`LPI`配置表的大小,也就是其将要占用的内存依赖于`LPI`中断的数量。GIC支持的`SPI`、`PPI`、`SGI`和`LPI`中断的最大中断号数量是由`GICD_TYPER.IDbits`表示的。`LPI`配置表中使用的中断号大于`8191`。因此,为了支持所有可能的`LPI`中断,配置表大小的计算公式如下:Size(单位:字节) = 2^(GICD_TYPER.IDbits+1) – 8192但是,有时候可能需要支持更少的中断号,而`GICR_PROPBASER`中包含一个字段`IDbits`,表示`LPI`配置表支持的中断号数量。这个数量必须小于等于`GICD_TYPER`的值。软件必须为这些中断号分配足够的配置表内存。在这种情况下,配置表大小的计算公式如下:Size(单位:字节) = 2^(GICR_PROPBASER.IDbits+1) – 8192中断控制器必须能够读取为`LPI`配置表分配的内存,否则,它无法对这段内存进行写操作。
-
LPI
挂起状态表前面已经讲过,
LPI
中断的状态信息也存储在内存中。LPI
只有两个状态,inactive
和pending
。图-
LPI
中断的状态机当
LPI
中断被应答后,由pending
转变为inactive
状态。因为LPI
只有两种状态,所以LPI
挂起状态表中每个LPI
中断只有1位表示。因此,为了支持所有可能的中断号,该表的大小为:Size(单位:字节) =2^(GICD_TYPER.IDbits+1) / 8
不像
LPI
配置表,LPI
挂起状态表的大小没有考虑从中断号8192
开始计算。因为该表的前1k大小(对应的中断号是0-8191
)存储的是中断控制器在实现时的定义状态。正如前面描述的,硬件可以支持更小范围的中断号。
GICR_PROPBASER.IDBits
控制中断号的范围。因此,它既影响了LPI
配置表的大小,也影响了挂起状态表的大小。所以,挂起状态表的大小也可以通过下面公式计算:Size(单位:字节) = 2^(GICR_PROPBASER.IDbits+1)/8
同样,中断控制器也必须能够读写该段内存。通常,
Redistributor
内部可以缓存最高优先级的挂起中断,所以,一般情况下,会在无法再缓存更多状态信息或者进入低功耗模式时,将状态信息写入到LPI
挂起状态表中。
6.2.2 重新配置LPI
LPI
的配置信息位于内存,而不是寄存器中。Redistributor
允许缓存LPI
配置信息。这意味,为了重新配置LPI
,软件需要:
- 更新
LPI
配置表中的项。 - 同步更新操作。
- 失效
Redistributor
中的缓存配置信息。
失效配置信息的缓存,可以通过发送INV
或INVALL
命令给ITS
实现。INV
失效单个中断,所以常用于重新配置少量LPI
中断的情况。INVALL
命令失效指定集合中所有中断。
如果没有实现ITS
,软件必须写GICR_INVLPIR
或GICR_INVALLR
寄存器失效缓存。
7 发送和接收SGI中断
软件中断SGI
,是由软件写中断控制器的特定寄存器触发的中断。
7.1 产生软件中断
写CPU Interface
中的任何一个SGI
寄存器,就会产生软件中断。
下表是使能了系统寄存器访问的前提下,可以使用的SGI
寄存器:
系统寄存器接口 | 描述 |
---|---|
ICC_SGI0R_EL1 | 产生Secure Group 0 中断 |
ICC_SGI1R_EL1 | 为PE当前安全状态,产生Group 1 中断 |
ICC_ASGI1R_EL1 | 为PE的其它安全状态,产生Group 1 中断 |
SGI
寄存器的各个字段如下所示:
SGI
寄存器的格式(SRE=1
)
-
SGI ID
控制产生的软件中断号。如第3.1.2节描述,软件中断的ID是
0-15
。 -
IRM
IRM(Interrupt Routing Mode)
该字段控制软件中断的目标PE(可能是多个),有两个选项:-
IRM = 0
目标PE由
<aff3>.<aff2>.<aff1>.<Target List>
决定,在这儿,<target list>
中,每个aff0
上的节点(也就是PE)占用一位。这意味着,该中断最多可以发送给16个PE,可能包含产生中断的PE。 -
IRM = 1
该中断被发送到除了产生中断的PE之外的所有连接的PE上。
正如第3.3节描述的,分层的亲和力真实的意义依赖于特定的设计。通常,亲和力1表示多核处理器,亲和力0表示该处理器上的PE单元(也就是我们常说的
CPU Core
)。
-
安全状态和中断分组的控制:
ICC_SGI0R_EL1
、ICC_SGI1R_EL1
或ICC_ASGIR_EL1
控制安全状态(产生软件中断的PE的寄存器)
GICR_IGROUPR0
和GICR_IGRPMODR0
控制中断分组(目标PE的寄存器)
执行在安全状态的软件可以发送安全和非安全SGI
软件中断。非安全状态的代码是否可以产生安全SGI
中断是由寄存器GICR_NSACR
控制的。这个寄存器只能由运行在安全状态下的软件访问。下表展示了源PE
的安全状态,目标PE的中断处理配置和SGI
寄存器是如何决定中断是否转发的各种情况。
源PE的安全状态 | SGI寄存器 | 目标PE的配置 | 是否转发 |
---|---|---|---|
Secure EL3/EL1 | ICC_SGI0R_EL1 | Secure Group 0 | 是 |
Secure Group 1 | 否 | ||
Non-secure Group 1 | 否 | ||
ICC_SGI1R_EL1 | Secure Group 0 | 否 (*) | |
Secure Group 1 | 是 | ||
Non-Secure Group 1 | 否 | ||
ICC_ASGI1R_EL1 | Secure Group 0 | 否 | |
Secure Group 1 | 否 | ||
Non-secure Group 1 | 是 | ||
Non-secure EL2/EL1 | ICC_SGI0R_EL1 | Secure Group 0 | GICR_NSACR 配置决定(*) |
Secure Group 1 | 否 | ||
Non-secure Group 1 | 否 | ||
ICC_SGI1R_EL1 | Secure Group 0 | GICR_NSACR 配置决定(*) | |
Secure Group 1 | GICR_NSACR 配置决定 | ||
Non-secure Group 1 | 是 | ||
ICC_ASGI1R_EL1 | Secure Group 0 | GICR_NSACR 配置决定(*) | |
Secure Group 1 | GICR_NSACR 配置决定 | ||
Non-secure Group 1 | 否 |
注意:上表假设
GICD_CTLR.DS==0
(也就是支持两个安全状态)。如果GICD_CTLR.DS==1
,被标记为(*)
的SGI
可以转发。
7.2 GICv3和GICv2对比
在GICv2
中,源PE和目标PE都备份SGI
中断号。这意味着,系统中,一个给定的PE可以备份8次相同SGI
中断号的pending
状态(因为GICv2
最多可以支持8个核)。
在GICv3
中,只有目标PE备份SGI
中断号。这意味着给定PE只有一个SGI
中断号挂起(pending
)。
下面使用一个例子来阐述这种差异。假设,PE-A
和PE-B
同时发送SGI
中断号5
给PE C
:
C
能够看见几个中断呢?
-
GICv2
:2个。它将同时看到来自A和B的中断。两个中断的顺序依赖于具体的设计和精确的采样时间。
GICC_IAR
寄存器中的INTID
的前缀是源PE的ID,可以通过这个事实区分这两个中断。 -
GICv3
:1个。因为源PE不备份
SGI
,所以,相同的中断不能在两个源PE上挂起(处于pending
状态)。因此,C
只能看见一个中断(中断号为5
)。
该例子假设同时(或几乎同时)发送两个中断。如果C
能够在第二个SGI
中断到达之前,能够应答第一个,则在GICv3
架构中也能看见两个中断。
如果设置支持旧操作,也就是当
GICD_CTLR.ARE=0
,SGI
中断的行为与GICv2
相同。
8 虚拟化
ARMv8-A
支持虚拟化。为了实现虚拟化,GICv3
也应该支持虚拟化。为此,GICv3
需要添加以下功能:
-
CPU interface
寄存器的硬件虚拟化 -
虚拟中断
-
维护中断
GIC架构没有考虑
Distributor
、Redistributor
或ITS
的硬件虚拟化。所以,这些接口的虚拟化需要软件进行处理。这些内容不在本文档的讨论范围之内。
8.1 术语
Hypervisor
负责创建、控制和调度虚拟机(VM)。一个虚拟机从功能上说等价于一个物理系统,包含一个或多个虚拟处理器。每个虚拟处理器包含一个或多个vPE
。
上图是虚拟机、虚拟处理器和vPE
的关系。本章讨论的大部分控制工作都在vPE
层。
8.2 接口
CPU Interface
被分为3组:
- 物理
CPU interface
寄存器 - 虚拟化控制寄存器
- 虚拟
CPU interface
寄存器
上图为支持虚拟化的CPU interface
寄存器
-
物理CPU接口(
ICC_*_ELn
)hypervisor
软件使用常规的ICC_*_ELn
寄存器处理物理中断。 -
虚拟控制寄存器(
ICH_*_EL2
)hypervisor
软件使用这类寄存器控制虚拟化特性。这些特性包括:- 使能、禁止
vCPU interface
。 - 用于上下文切换时保存、恢复虚拟寄存器的状态。
- 配置维护中断。
- 控制虚拟中断。
这些寄存器只能控制访问它们的物理PE的虚拟化特性,不能访问其它PE的状态。也就是说,
PEx
的软件不能访问PEy
的状态。 - 使能、禁止
-
虚拟CPU接口(
ICV_*_ELn
)虚拟机内部软件(运行在
EL1
)使用ICV_*_EL1
寄存器处理中断。这些寄存器的格式和功能与ICC_*_EL1
寄存器相同。ICV*
和ICC*
寄存器拥有相同的指令编码。在EL2
、EL3
和Secure EL1
,都能访问ICC*
寄存器。在非安全EL1
,是否能够访问ICV*
和ICC*
寄存器是由HCR_EL2
寄存器中的路由标志位决定的。ICV*
寄存器分为3组:-
Group 0
用于处理
Group 0
中断,比如ICC_IAR0_EL1/ICC_IAR0_EL1
。当HCR_EL2.FMO==1
时,非安全EL1
访问ICV*
寄存器,而不是ICC*
寄存器。 -
Group 1
用于处理
Group 1
中断,比如ICC_IAR1_EL1/ICC_IAR1_EL1
。当HCR_EL2.FMO==1
时,非安全EL1
访问ICV*
寄存器,而不是ICC*
寄存器。 -
通用
用于处理
Group 0或1
中断,比如ICC_DIR_EL1/ICV_DIR_EL1
和ICC_PMR_EL1/ICV_PMR_EL1
。当HCR_EL2.IMO==1
或HCR_EL2.FMO==1
,非安全EL1
访问ICV*
寄存器,而不是ICC*
寄存器。
下图,展示了同一个指令如何基于
HCR_EL2
的路由控制访问ICC*
或ICV*
寄存器的示例。 -
8.3 管理虚拟中断
hypervisor
使用List列表寄存器ICH_LRn_EL2
产生虚拟中断。每个寄存器表示一个虚拟中断,并记录:
-
虚拟中断号(
vINTID
)这是报告给虚拟机内部的中断ID号。
-
虚拟中断状态
虚拟中断状态(
Pending
、Active
、Active&Pending
或Inactive
。随着虚拟机中的软件和GIC的交互,状态机自动更新。比如,hypervisor
创建一个新的虚拟中断,首先设置中断状态为pending
。当执行在vPE
上的软件,读取ICV_IARn_EL1
,中断控制器中,该中断的状态机更新为Active
。 -
中断分组(
group
)虚拟机只有一个安全状态(也就是
GICD_CTLR.DS==1
)。因此,虚拟中断分为两组:Group 0
和Group 1
。Group 0
中断以vFIQ
信号的形式发送,Group 1
中断以vIRQ
信号的形式发送。 -
物理中断号(
pINTID
)虚拟中断使用物理中断的
INTID
进行标记(可选)。如果vINTID
的状态机更新时,pINTID
的状态也就更新了。
8.3.1 一个物理中断被转发到vPE的例子
下图展示了把一个物理中断转发到vPE的过程:
-
一个物理非安全
Group 1
中断从Redistributor
转发到物理CPU接口; -
物理CPU接口检查是否将物理中断转发到对应的PE。这个过程已经在第5.1小节描述过。假设,检查通过,物理中断生效。
-
该中断被
EL2
捕获。hypervisor
读取IAR寄存器
,返回物理中断号(pINTID
)。该物理中断(pINTID
)现在处于active
状态。hypervisor
决定将该中断转发给正在运行的vPE
。hypervisor
将物理中断号(pINTID
)写入到ICC_EOIR1_EL1
寄存器。此时,ICC_CTLR_EL1.EOImode==1
,只执行优先级降落,并没有将该物理中断失效。 -
hypervisor
写一个List寄存器
,产生一个虚拟中断,并将状态设置为pending
。List寄存器
中会指定虚拟中断号(vINTID
),同时记录原始的物理中断号(pINTID
)。然后,hypervisor
通过执行ERET
指令,从异常中返回,重新将执行权限交给vPE
。 -
vCPU
接口会检查是否将虚拟中断转发给vPE
。这些过程与物理中断的处理一样,只不过它们使用的是ICV
寄存器。本示例中,假设检查通过,虚拟中断生效。 -
虚拟中断被非安全的
EL1
捕获。当软件读取IAR
,返回虚拟中断号(vINTID
),虚拟中断此时处于active
状态。 -
Guest OS
处理该虚拟中断。当它完成中断的处理,写EOIR
寄存器,执行优先级降落,并失效该中断。因为List寄存器
记录了物理中断号(pINTID
),所以,同时将虚拟中断和物理中断失效。
8.4 维护中断
如果虚拟CPU接口中的某些条件为真,则可以配置CPU接口,产生物理中断。
这类中断是PPI
中断,中断号是25
。通常配置为非安全Group 1
中断,由运行在EL2
上的hypervisor
处理。是否产生维护中断,由ICH_HCR_EL2
控制,产生的中断可以通过ICH_MISR_EL2
寄存器读取。
如果vPE
清除了vCPU
接口中的Group
使能位中的一个,就可以产生维护中断。一旦收到该中断,hypervisor
就可以从List
寄存器的项中,删除属于被禁用Group
中,挂起的虚拟中断。
8.5 旧虚拟机
使用了GICv3
系统寄存器的hypervisor
(ICC_SRE_EL2.SRE==1
),照样可以托管使用旧方式(ICC_SRE_EL1(NS)==0
)的虚拟机。在这种情况下,运行在虚拟机中的软件使用内存映射的GICV
寄存器,就跟在GICv2
中一样。
8.6 上下文切换
当在vPE
之间切换上下文时,hypervisor
软件保存一个vPE
的状态,加载另一个的上下文内容。vCPU interface
寄存器的状态是vPE
上下文的一部分,这些状态包括:
-
ICV
寄存器的状态 -
有效的虚拟优先级
-
处于
pending
、active
或active&pending
状态的虚拟中断
ICV
寄存器可以被EL2
使用ICH
寄存器进行访问。下图,展示了ICH_VMCR_EL2
的各个位域对ICV
寄存器状态的映射关系。
切换vPE
时,必须保存和恢复有效虚拟优先级。当前vPE
的有效优先级可以通过ICV_APnR_EL2
寄存器访问。
正如第8.3节描述,虚拟中断是通过List
寄存器管理的。这些寄存器的状态特定于当前vPE
。因此,在上下文切换的时候,必须保存和恢复这些寄存器。
9 GICv4:虚拟LPI的直接注入
GICv4
添加了对直接注入虚拟LPI(vLPI
)的支持。此功能允许软件向ITS
描述物理事件(EventID
和DeviceID
的组合)如何映射成虚拟中断。如果中断的目标vPE
正在运行,则直接转发虚拟中断,而无需先进入hypervisor
。这可以减少虚拟化中断带来的开销。
9.1 Redistributor
、vLPI
状态和配置
为了支持vLPI
的直接注入,Redistributor
添加了两个寄存器:
-
GICR_VPROPBASER
设置
vLPI
配置表地址。和物理LPI
配置表一样,vLPI
配置表记录了vLPI
中断的配置。该配置对于同一个虚拟机中所有的vPE
都是可见的。ARM
期望VM中的所有vPE
都将使用虚拟配置表的相同副本。 -
GICR_VPENDBASER
设置
vLPI
挂起状态表(简称VPT
)。与物理LPI
挂起表一样,VPT
记录了vLPI
的挂起状态。每个vPE
都有自己私有的VPT
。
9.1.1 调度vPE
多个vPE
可能都托管在单个物理PE上,hypervisor
在它们之间执行上下文切换。正在运行的vPE
被称为正在被调度
。当GICR_VPENDBASER
被设置指向一个vPE
的VPT
时,该vPE
被定义为正在被调度
。
对于被调度的vPE
,可以直接注入虚拟中断。如果目标vPE
没有被调度,虚拟中断在正确的VPT
表中被记录为pending
状态。
当在vPE
之间执行上下文切换时,hypervisor
必须更新Redistributor
相关寄存器。这意味着hypervisor
必须:
-
清除
GICR_VPENDBASER.Valid
标志位清除
Valid
标志位,告知Redistributor
,正在执行上下文切换。Redistributor
将会检索vCPU
接口中的任何挂起虚拟中断,保证内存中的VPT
表是正确的。 -
轮询
GICR_VPENDBASER.Dirty
标志位,直到读到0
Dirty
标志位反映了Redistributor
已经完成了VPT
的更新。在该值为0
之前,不能调度新vPE
。 -
更新
GICR_VPROPBASER
如果是同一个VM的不同
vPE
,不需要这一步。 -
更新
GICR_VPENDBASER
,设置Valid==1
设置
Valid
标志位为1
,告知Redistributor
,新vPE
是合法的,此时,该vPE
的虚拟中断可以被转发到vCPU
接口上了。
VPT
的前1K内容是实现时定义的。ARM
期望实现将使用此空间来记录信息,从而在上下文切换时更快地解析VPT
。 当一个vPE
被调度时,必须通知Redistributor
该区域是否包含有效数据。软件使用GICR_VPENDBASER.IDAI
指示空间是否有效:
-
GICR_VPENDBASER.IDAI==1
(无效)保留的1K空间不是合法的,
Redistributor
必须解析整个VPT
。下列情况必须设置IDAI
:vPE
被迁移到不同GIC中断控制器的Redistributor
时VPT
被分配后,第一次调度对应的vPE
时,并且在分配整个表时未填充0
-
GICR_VPENDBASER.IDAI==0
(有效)保留的1K空间是合法的,
Redistributor
能够依赖该空间的值。ARM期望这是最常用的方式。当发生以下情况时,可以清除IDAI
标志位:- 调度的
vPE
位于相同的Redistributor
上 - 调度的
vPE
位于不同的Redistributor
上,但是被连接到相同的GIC控制器 VPT
被分配后,第一次调度对应的vPE
时,并且在分配整个表时填充0
(意味着没有挂起的中断)
-
注意:限制是
VPT
在分配时是全零,而不是在第一次被调度时全零。如果vPE
的ITS
映射存在,可能会在创建到第一次驻留期间设置虚拟中断。
-
- 调度的
9.2 GICv4中的ITS操作
GICv4
添加了许多新命令,添加了新的表类型给ITS
使用。允许软件:
-
为某个PE,将
EventID-DeviceID
组合映射成虚拟中断号(vINTID
)- (可选)可以指定一个门铃中断。这是当中断产生,但是
vPE
没有被调度的话,产生的一个物理中断(pINTID
)。
- (可选)可以指定一个门铃中断。这是当中断产生,但是
-
将
vPE
映射到物理Redistributor
。
下图展示了ITS
转发虚拟中断时遵循的流程,将虚拟LPI
中断直接注入虚拟机:
外设写GITS_TRANSLATER
:
-
ITS
使用设备ID(DeviceID
),从设备表中选择正确的项。该项可以标识将要使用的中断翻译表。 -
ITS
使用事件ID(EventID
),从中断翻译表中选择正确的项。这会得到:a. 物理中断号(
pINTID
)和集合ID,正如第6.1.1节描述的。b. 虚拟中断号(
vINTID
)和vPE ID
,(可选)一个物理中断号(pINTID
)用于门铃中断。 -
ITS
使用vPE ID
选择vPE
表中想要的项,vPE
表返回目标Redistributor
和vPE
的VPT
地址。 -
ITS
转发虚拟中断号(vINTID
)、门铃中断和VPT
地址到目标Redistributor
。 -
Redistributor
比较来自ITS
的VPT
地址与当前GICR_VPENDBASER
中的值:a. 如果
VPT
地址和GICR_VPENDBASER
匹配,则调度vPE
,并将虚拟中断号(vINTID
)转发到虚拟CPU
接口。b. 如果不匹配,则
vPE
不被调度。相应的中断(vINTID
)在VPT
表中设为挂起状态。如果提供了门铃中断,则物理中断号(pINTID
)被转发到物理CPU
接口。
9.3 vPE和vINTID的映射关系
EventID-DeviceID
组合被映射到虚拟中断号(vINTID
)和虚拟PE(vPE
)。当EventID
和vINTID
相同时,使用VMAPI
命令。
VMAPI <DeviceID>, <EventID>, <Doorbell pINTID>, <vPE ID>
当EventID
和vINTID
不同时,使用VMAPTI
命令。
VMAPTI <DeviceID>, <EventID>, <vINTID>, <pINTID>, <vPE ID>
在这些命令中:
-
<DeviceID>
和<EventID>
一起标识正在被重映射的中断。 -
<vPE ID>
是虚拟PE的ID。对于包含多个ITS
的系统,必须为一个给定PE在所有ITS
上指定相同的vPE ID
。 -
<pINTID>
是门铃中断的物理中断号,是因为vPE
没有被调度而产生的。指定1023
意味着没有门铃中断。 -
<vINTID>
是虚拟LPI
中断的中断号。对于VMAPI
命令,EventID
和vINTID
具有相同的值。
ITS
必须知道vPE
将在哪个物理PE
上调度执行。VMAPP
命令将vPE
映射到物理Redistributor
:
VMAPP <vPE ID>, <RDADDR>, <VPT>, <VPT size>
在该命令中:
-
<vPE ID>
是vPE
的标识符。 -
<RDADDR>
是目标Redistributor
的地址。 -
<VPT>
和<VPT size>
标识了vPE
相应的虚拟LPI
挂起状态表。如第9.1.1节描述的,当GICR_VPENDBASER
指向它的VPT
时,vPE
被调度。转发虚拟中断给Redistributor
时,ITS
包含VPT
地址。这允许Redistributor
判断vPE
是否被调度执行,如果没有调度,则更新VPT
,这样中断不会丢失。
9.3.1 例子
假设,定时器的设备ID是5
。它产生两个事件ID:0
和1
。两个事件ID都映射成vPE
的虚拟中断号,vPE ID
为6
:
-
EventID 0
– 虚拟中断号为8725
,门铃中断号为8192
-
EventID 1
– 虚拟中断号为9000
,没有门铃中断
vPE(6)
被映射到地址为0x78400000
的Redistributor
,同时,它的VPT
表的地址是0x97500000
。
命令序列是:
VMAPTI 5, 0, 8725, 8192, 6
VMAPTI 5, 1, 9000, 1023, 6
VMAPP 6, 0x78400000, 0x97500000, 12 VSYNC 6
注意:该示例假设
GITS_TYPER.PTA==1
(即使用物理地址标识目标Redistributor
),并且之前已经发出MAPD
命令来映射ITT
。
9.4 将vPE
映射到不同的Redistributor
如果hypervisor
将vPE
映射到不同的物理PE上,ITS
映射关系必须被更新,以便虚拟中断能够发送到正确的物理PE上。更新ITS
映射关系使用VMOVP
命令,使用VSYNC
命令同步内容。
一个系统可以包含多个ITS
。如果有不止一个ITS
有vPE
的映射关系,任何更改都必须反映到所有的ITS
中。GICv4
支持两个模型实现这个操作,GITS_TYPER.VMOVP
标志位控制使用哪个模型。
9.4.1 GITS_TYPER.VMOVP==0
必须给所有的ITS
(有该vPE
的映射关系)发送VMOVP
命令:
VMOVP <vPE ID>, <RDADDR>, <ITS List>, <Sequence Number>
在该命令中:
-
<vPE ID>
是虚拟PE的标识符。 -
<RDADDR>
是vPE
被重映射到的Redistributor
的地址。 -
<ITS List>
是所有ITS
的列表。每个ITS
占用一位,bit 0
对应ITS 0
。ITS
的编号由GITS_CTLR.ITS_Number
寄存器报告。 -
<Sequence Number>
是同步点。软件在向不同的ITS
发出VMOVP
时必须使用相同的值,并且在所有ITS
上的命令完成之前不得重复使用相同的值。
9.4.2 GITS_TYPER.VMOVP==1
VMOVP
命令只能被发送给一个ITS
,不管有多少个ITS
有该vPE
的映射关系。更改的传送与同步的处理需要硬件完成。这意味着不需要ITS List
和SequenceNumber
两个字段了。
VMOVP <vPE ID>, <RDADDR>
9.5 重映射或删除vPE/vINTID
之间的关系
VMOVI
重映射一对EventID-DeviceID
组合到不同的vINTID
或vPE
。
VMOVI <DeviceID>, <EventID>, <vPE ID>, <Doorbell pINTID>
在该命令中:
-
<DeviceID>
-<EventID>
一起标识将要被重映射的中断。 -
<vPE ID>
是中断将要迁移到的虚拟PE的ID。 -
<Doorbell pINTID>
是vPE
将要被重映射到的Redistributor
。
9.6 改变vLPI
配置
同物理LPI
中断一样,允许Redistributor
缓存vLPI
的配置。如果一个vLPI
的配置发生改变,则缓存的备份必须失效。有两个ITS
命令用于该操作。
INV
命令通常用于改变单个或小范围的vLPI
中断时使用。
VINVALL
命令通常用于失效指定vPE
上所有的vLPI
中断。也就是,修改大范围的vLPI
时使用。
9.7 混合GICv3
和GICv4
使用
支持GICv3
的CPU接口和支持GICv4
的CPU接口可能混合连接到一个GIC中断控制器上。下图就展示了这样的一个示例:
图-
GICv4
中断控制器,附加了支持GICv3
架构的CPU接口
只有实现了GICv4
的CPU接口能够接收直接注入的虚拟LPI
中断。在支持GICv3
的CPU接口上调度vPE
(也就是设置GICR_VPENDBASER.Valid==1
)是不可预测的。软件可以读取ICH_VTR_EL2.nV4
判断是否支持直接注入的虚拟中断。