一、前言
OpenAtom OpenHarmony(以下简称"OpenHarmony")采用多内核架构,支持Linux内核的标准系统、LiteOS-A的小型系统、LiteOS-M的轻量系统。
其中LiteOS-A要求设备具备一定的处理能力,对比LiteOS-M,LiteOS-A支持以下特性:
(1)MMU支持:通过MMU支持内核态和用户态分离,支持虚拟单元;
(2)支持独立进程:调度对象分别为进程、线程;
(3)支持文件系统:包括虚拟文件和块设备等;
(4)支持更复杂的IPC:包括LiteIPC等;
(5)支持多核调度:支持双核MCU,支持双核调度;
(6)支持POSIX3接口:为APP开发提供更多帮助。
LiteOS-A内核特性都是建立在CPU硬件的基础上,而中断控制器在支持LiteOS-A内核的CPU中发挥着巨大的作用:它管理和控制可屏蔽中断并对可屏蔽中断进行优先权判定,减少CPU的负载,使得CPU更加专注于计算。
在嵌入式领域,ARM公司提供的芯片目前是市场的主流,OpenHarmony LiteOS-M内核目前支持的ARM公司的Cortex-M系列的芯片,而LiteOS-A内核支持的则是ARM公司功能更强大的Cortex-A/R系列的芯片,GIC是ARM公司给Cortex-A/R系列芯片提供的一个中断控制器,在移植OpenHarmony LiteOS-A内核到特定板子的实践中,我们遇到了很多GIC中断控制器相关的技术问题,所以需要深入了解ARM体系架构下GIC中断控制器的原理和使用方法,特此总结并共享给各位网友。
二、GIC控制器概述
1、GIC简介
GIC是ARM公司给Cortex-A/R核提供的一个中断控制器,类似Cortex-M中的NVIC。目前GIC有4个版本:V1~V4:V1是最老的版本,已经被废弃了;V2~V4目前正在被大量地使用。GIC V2是给ARMv7-A架构使用的,比如Cortex-A5,Cortex-A7、Cortex-A9、Cortex-A15等,V3和V4是给ARMv8-A/R架构使用的,也就是64位芯片使用的。
GIC V2最多支持8个核。ARM会根据GIC版本的不同研发出不同的IP核,半导体厂商直接购买对应的IP核即可,比如ARM针对GIC V2就开发出了GIC400中断控制器IP核。注意,具体产品是GIC400,设计规范是V2。当GIC接收到外部中断信号以后汇报给ARM内核,但是ARM内核只提供四个信号给GIC来汇报中断情况:VFIQ、VIRQ、FIQ和IRQ,他们之间的关系如图所示:
在图中,GIC接收众多的外部中断,并对其进行处理,最终只通过四个信号报给 ARM 内核,这四个信号的含义如下:
● VFIQ: 虚拟快速 FIQ
● VIRQ: 虚拟外部 IRQ
● FIQ: 快速中断 IRQ
● IRQ: 外部中断 IRQ
2、GIC整体实现
下图左侧部分是中断源,中间部分是GIC控制器,最右侧是中断控制器向处理器内核发送中断信息。我们重点要看的是中间的 GIC 部分,GIC 将众多的中断源分为三类:
①SPI(Shared Peripheral Interrupt),共享中断,即所有Core共享的中断,外部中断都属于SPI中断。比如按键中断、串口中断等,这些中断所有的Core都可以处理,不限定特定Core。
在图中,每个SPI设计是共享的,在SMP系统中每个SPI都需要连接一个线到各个CPU中。
②PPI(Private Peripheral Interrupt),私有中断,GIC是支持多核的,每个核有自己独有的中断,需要指定的核心处理。
在图中,每个CPU都有属于自己的PPIs,对应关系是1对1。
③SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统使用SGI中断来完成多核之间的通信。
在图中,SGI是软件(软件运行在CPU上)触发,所以SGI中断源头是各个CPU上的应用,另外SGI和PPI一样,每个CPU都有属于自己的SGI,所以必须指定CPU。
3、中断 ID
中断源有很多,为了区分不同的中断源要给它们分配唯一ID,也就是中断ID。每一个CPU最多支持1020个中断ID,中断ID号为ID0~ID1019。1020 个ID包含了PPI、SPI和SGI,那么这三类中断是如何分配这 1020 个中断 ID 的呢?分配如下:
● ID0~ID15:这16个ID分配给SGI
● ID16~ID31:这16个ID分配给PP
● ID32~ID1019:这988个ID分配给SPI,像GPIO中断、串口中断等这些外部中断
至于具体到某个ID对应哪个中断,那就由半导体厂商根据实际情况去定义。比如 I.MX6U 的总共使用了128个中断ID,加上前面属于PPI和SGI的32个ID,I.MX6U的中断源共有128+32=160个。
4、GIC逻辑模块
GIC架构分为了两个逻辑块:Distributor和CPU Interface,也就是分发器端和CPU接口端。
Distributor(分发器端):参考GIC整体实现的图,此逻辑块负责处理各个中断事件的分发问题,确定中断事件应该发送到哪个CPU Interface上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU接口端。分发器端要做的主要工作如下:
① 全局中断使能控制
② 控制每一个中断的使能或者关闭
③ 设置每个中断的优先级
④ 设置每个中断的目标处理器列表
⑤ 设置每个外部中断的触发模式:电平触发或边沿触发
⑥ 设置每个中断属于组0还是组1,此设置涉及到另外一个领域安全领域
CPU Interface(CPU接口端):CPU接口端和CPU Core相连接,因此每个CPU Core都可以在GIC中找到一个与之对应的CPU Interface。CPU接口端是分发器和CPU Core之间的桥梁,CPU接口端主要工作如下:
① 使能或者关闭发送到CPU Core的中断请求信号
② 应答中断
③ 通知中断处理完成
④ 设置优先级掩码,通过掩码来设置哪些中断不需要上报给CPU Core
⑤ 定义抢占策略
⑥ 当多个中断到来的时候,选择优先级最高的中断通知给CPU Core
三、GIC400原理
1、整体框架图
GIC-400实现了以下的中断类型:
● 16个软件产生的中断(SGI)
● 每个处理器有6个外部私有外设中断(PPI)
● 每个处理器有1个内部PP
● 可配置的共享外设中断(SPI)的数量
GIC-400的BD如下图所示,GIC-400从中断输入信号中检测PPI和SPI。每个处理器的每个PPI中断ID都有一个信号。每个SPI中断ID只有一个输入信号,与SoC中处理器的数量无关。SGI没有输入信号,在GIC-400中使用AXI编程接口生成。
2、输入信号
上图中左边就是来自外设的interrupt source输入信号。分成两种类型,分别是PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI中断信号是CPU私有的,每个CPU都有其特定的PPI信号线。而SPI是所有CPU之间共享的。通过寄存器GICD_TYPER可以配置SPI的个数(最多480个)。GIC-400支持多少个SPI中断,其输入信号线就有多少个SPI interrupt request signal。同样的,通过寄存器GICD_TYPER也可以配置CPU interface的个数(最多8个),GIC-400支持多少个CPU interface,其输入信号线就提供多少组PPI中断信号线。一组PPI中断信号线包括6个实际的signal:
(1)nLEGACYIRQ信号线。对应interrupt ID 31,在bypass mode下(这里的bypass是指bypass GIC functionality,直接连接到某个processor上),nLEGACYIRQ可以直接连到对应CPU的nIRQCPU信号线上。在这样的设置下,该CPU不参与其他属于该CPU的PPI以及SPI中断的响应,而是特别为这一根中断线服务。
(2)nCNTPNSIRQ信号线。来自Non-secure physical timer的中断事件,对应interrupt ID 30。
(3)nCNTPSIRQ信号线。来自secure physical timer的中断事件,对应interrupt ID 29。
(4)nLEGACYFIQ信号线。对应interrupt ID 28。概念与nLEGACYIRQ信号线相同。
(5)nCNTVIRQ信号线。对应interrupt ID 27。Virtual Timer Event,和虚拟化相关,这里不予描述。
(6)nCNTHPIRQ信号线。对应interrupt ID 26。Hypervisor Timer Event,和虚拟化相关,这里不予描述。
3、输出信号
所谓输出信号,就是GIC和各个CPU直接的接口,这些接口包括:
(a)触发CPU中断的信号。nIRQCPU和nFIQCPU信号线,主要用来触发ARM CPU进入IRQ mode和FIQ mode。
(b)Wake up信号。nFIQOUT和nIRQOUT信号线,协助ARM CPU的电源管理模块,用来唤醒CPU。
(c)AXI slave interface signals。AXI(Advanced eXtensible Interface)是一种总线协议,属于AMBA规范的一部分。通过这些信号线,ARM CPU可以和GIC硬件block进行通信(例如寄存器访问)。
4、触发路径
下面总结下SGI、PPI、SPI三种类型中断的触发路径:
SGI(ID0-ID15):是由CPU内部软件触发,所以从CPU CORE-->CPU interface-->Distributor-->CPU Interface-->CPU CORE。
PPI(ID16-ID31):是由外部器件触发,从Peripheral-->Distributor-->CPU interface-->CPU CORE。
SPI(ID32-ID1019):也是外部器件触发,从Peripheral-->Distributor-->CPU interface-->CPU CORE。
5、中断状态迁移图
对于每一个中断而言,有以下4个状态:
inactive:中断处于无效状态。
pending:中断处于有效状态,但是cpu没有响应该中断。
active:cpu在响应该中断。
active and pending:cpu在响应该中断,但是该中断源又发送中断过来。
PS:对于电平触发的中断而言,一个中断的处理阶段也会有active and pending的阶段,这是比较特殊的。
下面是官方提供中断迁移的条件和场景说明,要注意的是文中either表示转移条件满足其一即可:
四、GIC400寄存器
1、GIC-400寄存器地图
所有GIC-400寄存器都有短名称。在这些名称中,前三个字符是GIC,第四个字符表示 GIC-400 的功能块:
● GICD_ 分配器
● GICC_ 中央处理器接口
● GICH_ 虚拟接口控制块
● GICV_ 虚拟CPU接口。
GIC-400寄存器是内存映射的,下表列出了地址范围:
内存映射的含义:对于ARM体系架构而言,IO和内存是统一编制,寄存器会占用内存的地址范围。至于GIC-400寄存器首地址映射在哪个内存地址上,这是硬件厂商设计,并通过CP15协处理器来获取。
2、具体寄存器
下面解释几个经典的寄存器,更多的寄存器请参考ARM出品的官方文档。
(1)GICD_CTRL寄存器
寄存器GICD_CTRL各个位的含义如下:
GICD_CTRL_reserved_0:保留。
EnableGrp1:Global enable for forwarding pending Group 1 interrupts from the Distributor to the CPU interfaces:
EnableGrp0:Global enable for forwarding pending Group 0 interrupts from the Distributor to the CPU interfaces:
个人见解:第一,EnableGrp1和EnableGrp0是全局开关,第二,这是Distributor侧的开关,至于CPU interfaces侧继续参考其它寄存器。
(2)2 GICD_TYPER
GICD_TYPER_reserved_0:
个人见解:保留字段,不使用
LSPI:
If the GIC implements the Security Extensions, the value of this field is the maximum number of implemented lockable SPIs, from 0 (0b00000) to 31 (0b11111), see Configuration lockdown on page 4-82. If this field is 0b00000 then the GIC does not implement configuration lockdown. If the GIC does not implement the Security Extensions, this field is reserved.
个人见解:在不使能ARM安全体系的功能前提下,此处保留,安全体系的功能暂时不使用。
SecurityExtn:
Indicates whether the GIC implements the Security Extensions.0 Security Extensions not implemented.1 Security Extensions implemented.
个人见解:ARM安全体系的功能,使能后,0组和1组含义不一样,具体需要另外篇幅来阐述
GICD_TYPER_reserved_1:
CPUNumber:
Indicates the number of implemented CPU interfaces. The number of implemented CPUinterfaces is one more than the value of this field, for example if this field is 0b011, thereare four CPU interfaces. If the GIC implements the Virtualization Extensions, this is also the number of virtual CPU interfaces.
个人见解:the number of implemented CPU interfaces = CPUNumber+1,域值是从0开始,含义却是从1开始。
ITLinesNumber:
Indicates the maximum number of interrupts that the GIC supports. If ITLinesNumber=N, the maximum number of interrupts is 32(N+1). The interrupt ID range is from 0 to (number of IDs - 1).
For example: 0b00011 Up to 128 interrupt lines, interrupt IDs 0-127.
The maximum number of interrupts is 1020 (0b11111). See the text in this section for more information. Regardless of the range of interrupt IDs defined by this field, interrupt IDs 1020-1023 are reserved for special purposes.
个人见解:最大的中断数量是32*(ITLinesNumber +1)
(3)GICD_IDDR
ProductID:
个人见解:值0x020意味着GIC-400
GICD_IDDR_reserved_0:
个人见解:reserved
Variant:
An IMPLEMENTATION DEFINED variant number. Typically, this field is used to distinguishproduct variants, or major revisions of a product.
个人见解:产品主版本号,0x2代表版本为2.0
Revision:
An IMPLEMENTATION DEFINED revision number. Typically, this field is used to distinguish minor revisions of a product.
个人见解:产品小版本号,0x1代表修订版为r0p1。
Implementer:
Contains the JEP106 code of the company that implemented the GIC Distributor: Bits [11:8] The JEP106 continuation code of the implementer. For an ARM implementation, this field is 0x4. Bits [7] Always 0. Bits [6:0] The JEP106 identity code of the implementer. For an ARM implementation, bits[7:0] are 0x3B.
个人见解:实现CPU接口的公司代码,0x43B代表ARM公司。
(4)GICD_IGROUPn_0
Group_Status_Bits:
The GICD_IGROUPR registers provide a status bit for each interrupt supported by the GIC.
Each bit controls whether the corresponding interrupt is in Group 0 or Group 1. Accessible by Secure accesses Only.
For each bit:
0 The corresponding interrupt is Group 0.
1 The corresponding interrupt is Group 1.
For interrupt ID m, when DIV and MOD are the integer division and modulo operations:
a. the corresponding GICD_IGROUPRn number, n, is given by n = m DIV 32
b. the offset of the required GICD_IGROUPR is (0x080 + (4*n))
c. the bit number of the required group status bit in this register is m MOD 32.
个人见解:
① 组状态位,对于每个位:"0"表示相应的中断为Group 0;"1"表示相应的中断为Group 1。
② 问:对于一个中断,如何设置它的Group?
答:首先找到对应的GICD_IGROUPRn寄存器,即n是多少?还要确定使用这个寄存器里哪一位。对于interrtups ID m,如下计算:n = m DIV 32,GICD_IGROUPRn里的n就确定了;
③ GICD_IGROUPRn在GIC内部的偏移地址是多少?
答:0x080+(4*n),这个信息是为了定位到具体寄存器。
④ 使用GICD_IPRIORITYRn中哪一位来表示?
答:bit = m mod 32。
⑤ GICD_IGROUPn_0只是GICD_IGROUPn寄存器组的第一个,总共有4个寄存器:GICD_IGROUPn_0到GICD_IGROUPn_3
(5)GICD_ISENABLER0
Set_Enable_Bits:
The GICD_ISENABLERs provide a Set-enable bit for each interrupt supported by the GIC. For SPIs and PPIs, each bit controls the forwarding of the corresponding interrupt from the Distributor to the CPU interfaces:
Reads:
0 Forwarding of the corresponding interrupt is disabled.
1 Forwarding of the corresponding interrupt is enabled.
Writes:
0 Has no effect.
1 Enables the forwarding of the corresponding interrupt.
After a write of 1 to a bit, a subsequent read of the bit returns the value 1. For interrupt ID m, when DIV and MOD are the integer division and modulo operations:
a.the corresponding GICD_ISENABLER number, n, is given by n = m DIV 32
b.the offset of the required GICD_ISENABLER is (0x100 + (4*n))
c.the bit number of the required Set-enable bit in this register is m MOD 32.
个人见解:
① 对于SPI和PPI类型的中断,每一位控制对应中断的转发行为:从Distributor转发到CPU interface:
读:"0"表示当前是禁止转发的;"1"表示当前是使能转发的;
写:"0"表示无效;"1"表示使能转发。
② 对于一个中断,如何找到GICD_ISENABLERn并确定相应的位?
答:对于interrtups ID m,如下计算:n = m DIV 32,GICD_ISENABLERn里的n就确定了;
GICD_ISENABLERn在GIC内部的偏移地址是多少?计算方法为0x100+(4*n),因为一个寄存器大小为4个字节。
使用GICD_ISENABLERn中哪一位来表示?计算方法为?bit = m mod 32。
③ 这里的开关也是Distributor侧的开关,GICV硬件设计思想和软件设计思想类似。
④ GICD_ISENABLER0是此类寄存器组的第一个,总共是4个。
(6)GICD_ICFGR0
Int_Config:
The GICD_ICFGRs provide a 2-bit Int_config field for each interrupt supported by the GIC.
For Int_config[1], the most significant bit, bit [2F+1], the encoding is:
0 Corresponding interrupt is level-sensitive.
1 Corresponding interrupt is edge-triggered.
Int_config[0], the least significant bit, bit [2F], reserved
For SGIs: Int_config[1] Not programmable, RAO/WI.
For PPIs: Int_config[1] Not programmable, RAZ/WI.
For SPIs: Int_config[1] this bit is programmable.
A read of this bit always returns the correct value to indicate whether the corresponding interrupt is level-sensitive or edge-triggered.
For interrupt ID m, when DIV and MOD are the integer division and modulo operations:
a. the corresponding GICD_ICFGR number, n, is given by n = m DIV 16
b. the offset of the required GICD_ICFGRn is (0xC00 + (4*n))
c. the required Priority field in this register, F, is given by F = m MOD 16, where field 0 refers to register bits [1:0], field 1 refers to bits [3:2], up to field 15 that refers to bits [31:30].
个人见解:对于每一个中断,都有对应的2位数据用来描述:它的边沿触发,还是电平触发。对于Int_config [1](即高位[2F + 1]),含义为:"0"表示相应的中断是电平触发;"1"表示相应的中断是边沿触发。对于Int_config [0],即低位[2F],是保留位,这一位就是以后才使用。
五、问题与建议
1、ARM芯片设计了FIQ和IRQ的2个处理模式,移植过程中没有用到FIQ。
2、虚拟中断相关的功能也没有用到。
3、安全相关的中断功能前期我们也没有用到,后续在可信执行环境中会用到。
4、如果代码对中断保护写得不好的话,会出现Systick发生多次中断,中断状态为active &pending,在移植过程中OpenHarmony版本不支持嵌套中断。
5、中断处理分为边沿触发处理和电平触发处理。这就给我们不同的应用功能提供了选择,使得我们可以在不同个工作下选择适合的模式,边沿触发适用于对时间要求高的,比如中断中有计数之类的(GATE门控位置1时),而电平触发则适合报警装置。
6、在基于ARM的嵌入式应用系统中,存储系统通常是通过系统控制协处理器CP15完成的。ARM处理器使用协处理器15(CP15)的寄存器来控制cache、TCM和存储器管理。CP15包含16个32位的寄存器,其编号为0~15,具体请参考ARM官方文档。
7、givc2将中断分成了group0和group1,默认情况下,所有中断都是组0中断,并使用IRQ向连接的处理器发送信号中断请求。个人理解如果都改为组1也是没有问题的,但是没有验证。
8、GIC-400通用初始化流程:
(1)设置distributor和CPU interface寄存器组的基地址;
(2)读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源;
(3)初始化distributor:
a.disable distributor;
b.设置中断分组;
c.设置SPI中断的路由;
d.设置SPI中断的触发类型;
e.disactive和disable所有中断源;
f.enable distributor;
(4)初始化CPU Interface:
a.设置GIC_CPU_PRIMASK,设置中断优先级mask level;
b.enable CPU interface;
六、总结
本篇文章从GIC控制器概述、GIC400原理、GIC400寄存器等方面介绍了OpenHarmony内核之基础硬件------中断控制器GIC400的内容,希望开发者能从中学有所获,学以致用。
为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:
OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy
《OpenHarmony源码解析》:https://qr18.cn/CgxrRy
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ......
系统架构分析:https://qr18.cn/CgxrRy
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ......