GIC介绍和编程

1,200 阅读14分钟

本文已参与【新人创作礼】活动,一起开启掘金创作之路。

一、GIC介绍

在这里插入图片描述各种中断,都在分发器(Distributor)中分发,它可以设置优先级和可接收中断的核。外设中断到分发器时时pending状态(或者active and pending状态,触发时则是active)。分发器可以传递给CPU核的优先级最高的pending中断,并将其转发给CPU Interface。通过CPU Interface,该中断又向CPU核发出信号,此时CPU核将触发FIQ或IRQ异常。

当CPU核执行异常处理时,异常处理程序必须从CPU Interface寄存器查询中断ID,并开始为中断源提供服务。完成后,处理程序必须写入CPU Interface寄存器以报告处理结束。然后CPU Interface准备转发分发器给它的下一个中断。

中断处理时,中断状态从pending、active结束时变成inactive,中断保存在分发器寄存器中。

GIC功能划分

GIC主要功能模块有分发器(Distributor)、CPU接口单元(CPU Interface)

  • 分发器:所有的中断源都链接到这个单元,它可以通过仲裁单元的寄存器来控制各个中断源的属性。
  • CPU接口单元:CPU核通过控制器的CPU接口单元接收中断。

中断可以有多种不同的类型:

  1. 软件触发中断(SGI,Software Generated Interrupt):由软件通过写入专用的寄存器来触发软件中断寄存器(ICDSGIR)显式生成的。通常用于CPU核之间的通信。
  2. 私有外设中断(PPI,Private Peripheral Interrupt):由单个CPU核私有的外设生成的。PPI中断号为16-31。是CPU核的私有中断源,并且独立于另一个CPU核的相同中断源,比如每个核的计时器。
  3. 共享外设中断(SPI,Shared Peripheral Interrupt):由外设生成的,中断控制器可以将其路由到多个核。中断号为32-1020。SPI用于从整个系统可访问的各种外围设备发出中断信号。

中断不仅可以边沿触发,也可以电平触发。

中断可以储与多种不同状态

  1. 非活动状态(inactive):中断未触发
  2. 挂起(pending):中断源已被触发,但正在等待CPU核处理。待处理的中断要通过转发到CPU接口单元,然后再由CPU接口单元转发到内核。
  3. 活动(active):描述了一个以被内核接收并正在处理的中断。
  4. 活动和挂起(active and pending):描述了一种情况,其中CPU核正在为中断服务,而GIC又收到来自同一源的中断。

1.1 GIC配置

GIC作为内存映射的外围设备,被软件访问。所有内核都可以访问公共的distributor单元,但是CPU Interface是备份的,也就是说,每个CPU都是用相同的地址来访问其专用CPU接口,一个CPU核不能访问另一个CPU核的CPU接口。 distributor拥有多个寄存器,可以通过这些寄存器配置各个中断的属性。

  • 中断优先级:distributor使用它来确定接下来,将哪个中断转发到CPU接口。
  • 中断配置:确定中断是对电平触发还是边沿触发
  • 中断目标:中孤单可以发给哪些核
  • 中断启用或禁用状态:启用时,中断才能变为挂起状态。
  • 中断安全性:将中断分配给secure还是normal world软件
  • 中断状态:distributor还提供优先级屏蔽,可防止低于某个优先级的中断发给CPU核。让CPU专注于处理某些中断。

1.2 GIC初始化

在这里插入图片描述 分发器(Distributor)和CPU Interface在复位时均被禁用,复位后,需要初始化后,才能将中断传递给CPU。在分发器中必须配置优先级、目标核、安全性并启用单个中断,然后在控制寄存器中使能它。 每个CPU Interface必须对优先级和抢占式进行配置,处理中断前需要清除CPSR中的屏蔽位来让CPU核接收中断。

1.3 GIC中断处理

当CPU收到中断时,它会跳转到中断向量表去执行。 顶层中断处理程序读取CPU接口模块寄存器来获取中断号,并且分发器会标记为active状态,知道了ID号就能找到对应的处理函数来处理了。 当处理程序完成时,将中断号写入CPU Interface的寄存器中,指示中断处理结束。会把active转变成inactive和pending。CPU Interface就能将更多的pending中断发给CPU核。

二、GIC寄存器

GIC分为两部分:Disctributor和CPU Interface,她们寄存器前缀为GICD_ 和GICC_。这些寄存器都映射为内存接口,CPU可以直接读写。

1.2.1 Distributor 寄存器描述

1. Distributor Control Register, GICD_CTLR

位域读写描述
1EnableGrp1R/W用于将pending Group 1中断从Distributor转发到CPU interfaces 0:group 1中断不转发 1:根据优先级规则转发Group 1中断
0EnableGrp0R/W用于将pending Group 0中断从Distributor转发到CPU interfaces 0:group 0中断不转发 1:根据优先级规则转发Group 0中断

2. Interrupt Controller Type Register, GICD_TYPER

位域读写描述
15:11LSPIR如果GIC实现了安全扩展,则此字段的值是已实现的可锁定SPI的最大数量,范围为0(0b00000)到31(0b11111)。 如果此字段为0b00000,则GIC不会实现配置锁定。 如果GIC没有实现安全扩展,则保留该字段。
10SecurityExtnR表示GIC是否实施安全扩展: 0未实施安全扩展; 1实施了安全扩展
7:5CPUNumberR表示已实现的CPU interfaces的数量。 已实现的CPU interfaces数量比该字段的值大1。 例如,如果此字段为0b011,则有四个CPU interfaces。
4:0ITLinesNumberR表示GIC支持的最大中断数。 如果ITLinesNumber = N,则最大中断数为32*(N+1)。 中断ID的范围是0到(ID的数量– 1)。 例如:0b00011最多128条中断线,中断ID 0-127。 中断的最大数量为1020(0b11111)。 无论此字段定义的中断ID的范围如何,都将中断ID 1020-1023保留用于特殊目的

3. Distributor Implementer Identification Register, GICD_IIDR

位域读写描述
31:24ProductIDR产品标识ID
23:20保留
19:16VariantR通常是产品的主要版本号
15:12RevisionR通常此字段用于区分产品的次版本号
11:0ImplementerR含有实现这个GIC的公司的JEP106代码; [11:8]:JEP106 continuation code,对于ARM实现,此字段为0x4; [7]:始终为0; [6:0]:实现者的JEP106code,对于ARM实现,此字段为0x3B

4. Interrupt Group Registers, GICD_IGROUPRn

位域读写描述
31:0Group status bitsR/W组状态位,对于每个位: 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中哪一位来表示interrtups ID m?
bit = m mod 32

5. Interrupt Set-Enable Registers, GICD_ISENABLERn

位域读写描述
31:0Set-enable bitsR/W对于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)
使用GICD_ISENABLERn中哪一位来表示interrtups ID m?
bit = m mod 32

6. Interrupt Clear-Enable Registers, GICD_ICENABLERn

位域读写描述
31:0Clear-enable bitsR/W对于SPI和PPI类型的中断,每一位控制对应中断的转发行为:从Distributor转发到CPU interface: 读: 0:表示当前是禁止转发的; 1:表示当前是使能转发的; 写: 0:无效 1:禁止转发

对于一个中断,如何找到GICD_ICENABLERn并确定相应的位?

对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ICENABLERn里的n就确定了;
GICD_ICENABLERn在GIC内部的偏移地址是多少?0x180+(4*n)
使用GICD_ICENABLERn中哪一位来表示interrtups ID m?
bit = m mod 32

7. Interrupt Set-Active Registers, GICD_ISACTIVERn

位域读写描述
31:0Set-active bitsR/W读: 0:表示相应中断不是active状态; 1:表示相应中断是active状态; 写: 0:无效 1:把相应中断设置为active状态,如果中断已处于Active状态,则写入无效

对于一个中断,如何找到GICD_ISACTIVERn并确定相应的位?

对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ISACTIVERn里的n就确定了;
GICD_ISACTIVERn在GIC内部的偏移地址是多少?0x300+(4*n)
使用GICD_ISACTIVERn 中哪一位来表示interrtups ID m?
bit = m mod 32

8. Interrupt Clear-Active Registers, GICD_ICACTIVERn

位域读写描述
31:0Clear-active bitsR/W读: 0:表示相应中断不是active状态; 1:表示相应中断是active状态; 写: 0:无效 1:把相应中断设置为deactive状态,如果中断已处于dective状态,则写入无效

对于一个中断,如何找到GICD_ICACTIVERn并确定相应的位?

对于interrtups ID m,如下计算:
n = m DIV 32,GICD_ICACTIVERn里的n就确定了;
GICD_ICACTIVERn 在GIC内部的偏移地址是多少?0x380+(4*n)
使用GICD_ICACTIVERn中哪一位来表示interrtups ID m?
bit = m mod 32

9. Interrupt Priority Registers, GICD_IPRIORITYRn

位域读写描述
31:24Priority, byte offset 3R/W对于每一个中断,都有对应的8位数据用来描述:它的优先级。 每个优先级字段都对应一个优先级值,值越小,相应中断的优先级越高
23:16Priority, byte offset 2R/W
15:8Priority, byte offset 1R/W
7:0Priority, byte offset 0R/W

对于一个中断,如何设置它的优先级(Priority),首先找到对应的GICD_IPRIORITYRn寄存器,即n是多少?还要确定使用这个寄存器里哪一个字节。

对于interrtups ID m,如下计算:
n = m DIV 4,GICD_IPRIORITYRn里的n就确定了;
GICD_IPRIORITYRn在GIC内部的偏移地址是多少?0x400+(4*n)
使用GICD_IPRIORITYRn中4个字节中的哪一个来表示interrtups ID m的优先级?
byte offset = m mod 4。
byte offset 0对应寄存器里的[7:0];
byte offset 1对应寄存器里的[15:8];
byte offset 2对应寄存器里的[23:16];
byte offset 3对应寄存器里的[31:24]

10. Interrupt Processor Targets Registers, GICD_ITARGETSRn

位域读写描述
31:24CPU targets, byte offset 3R/W对于每一个中断,都有对应的8位数据用来描述:这个中断可以发给哪些CPU。 处理器编号从0开始,8位数里每个位均指代相应的处理器。 例如,值0x3表示将中断发送到处理器0和1。 当读取GICD_ITARGETSR0~GICD_ITARGETSR7时,读取里面任意字节,返回的都是执行这个读操作的CPU的编号。
23:16CPU targets, byte offset 2R/W
15:8CPU targets, byte offset 1R/W
7:0CPU targets, byte offset 0R/W

对于一个中断,如何设置它的目杯CPU?优先级(Priority),首先找到对应的GICD_ITARGETSRn寄存器,即n是多少?还要确定使用这个寄存器里哪一个字节。

对于interrtups ID m,如下计算:
n = m DIV 4,GICD_ITARGETSRn里的n就确定了;
GICD_ITARGETSRn在GIC内部的偏移地址是多少?0x800+(4*n)
使用GICD_ITARGETSRn中4个字节中的哪一个来表示interrtups ID m的目标CPU?
byte offset = m mod 4。
byte offset 0对应寄存器里的[7:0];
byte offset 1对应寄存器里的[15:8];
byte offset 2对应寄存器里的[23:16];
byte offset 3对应寄存器里的[31:24]

11. Interrupt Configuration Registers, GICD_ICFGRn

位域读写描述
[2F+1:2F]Int_config, field FR/W对于每一个中断,都有对应的2位数据用来描述:它的边沿触发,还是电平触发。 对于Int_config [1],即高位[2F + 1],含义为: 0:相应的中断是电平触发; 1:相应的中断是边沿触发。 对于Int_config [0],即低位[2F],是保留位。

对于一个中断,如何找到GICD_ICFGRn并确定相应的位域F?

对于interrtups ID m,如下计算:
n = m DIV 16,GICD_ICFGRn里的n就确定了;
GICD_ICACTIVERn 在GIC内部的偏移地址是多少?0xC00+(4*n)
F = m mod 16

12. Identification registers: Peripheral ID2 Register, ICPIDR2

位域读写描述
[31:0]-R/W由实现定义
[7:4]ArchRevR该字段的值取决于GIC架构版本: 0x1:GICv1; 0x2:GICv2。
[3:0]-R/W由实现定义

1.2.2 CPU interface寄存器描述

1. CPU Interface Control Register, GICC_CTLR

​ 此寄存器用来控制CPU interface传给CPU的中断信号。对于不同版本的GIC,这个寄存器里各个位的含义大有不同。以GICv2为例,有如下2种格式:

​ 以GIC2 with Security Extensions, Non-secure copy 为例,GICC_CTLR中各个位的定义如下:

位域读写描述
[31:10]-保留
[9]EOImodeNSR/W控制对GICC_EOIR和GICC_DIR寄存器的非安全访问: 0:GICC_EOIR具有降低优先级和deactivate中断的功能; 对GICC_DIR的访问是未定义的。 1:GICC_EOIR仅具有降低优先级功能; GICC_DIR寄存器具有deactivate中断功能。
[8:7]-保留
[6]IRQBypDisGrp1R/W当CPU interface的IRQ信号被禁用时,该位控制是否向处理器发送bypass IRQ信号: 0:将bypass IRQ信号发送给处理器; 1:将bypass IRQ信号不发送到处理器。
[5]FIQBypDisGrp1R/W当CPU interface的FIQ信号被禁用时,该位控制是否向处理器发送bypass FIQ信号: 0:将bypass FIQ信号发送给处理器; 1:旁路FIQ信号不发送到处理器
[4:1]-保留
[0]-R/W使能CPU interface向连接的处理器发出的组1中断的信号: 0:禁用中断信号 1:使能中断信号

2. Interrupt Priority Mask Register, GICC_PMR

​ 提供优先级过滤功能,优先级高于某值的中断,才会发送给CPU。

位域读写描述
[31:8]-保留
[7:0]-R/W优先级高于这个值的中断,才会发送给CPU

[7:0]共8位,可以表示256个优先级。但是某些芯片里的GIC支持的优先级少于256个,则某些位为RAZ / WI,如下所示:

如果有128个级别,则寄存器中bit[0] = 0b0,即使用[7:1]来表示优先级;
如果有64个级别,则寄存器中bit[1:0] = 0b00,即使用[7:2]来表示优先级;
如果有32个级别,则寄存器中bit[2:0] = 0b000,即使用[7:3]来表示优先级;
如果有16个级别,则寄存器中bit[3:0] = 0b0000,即使用[7:4]来表示优先级;

注意:imx6ull最多为32个级别

3. Binary Point Register, GICC_BPR

​ 此寄存器用来把8位的优先级字段拆分为组优先级和子优先级,组优先级用来决定中断抢占。

位域读写描述
[31:3]-保留
[2:0]Binary pointR/W此字段的值控制如何将8bit中断优先级字段拆分为组优先级和子优先级,组优先级用来决定中断抢占。 更多信息还得看看GIC手册。

4. Interrupt Acknowledge Register, GICC_IAR

​ CPU读此寄存器,获得当前中断的interrtup ID。

位域读写描述
[31:13]-保留
[12:10]CPUIDR对于SGI类中断,它表示谁发出了中断。例如,值为3表示该请求是通过对CPU interface 3上的GICD_SGIR的写操作生成的。
[9:0]Interrupt IDR中断ID

5. Interrupt Register, GICC_EOIR

​ 写此寄存器,表示某中断已经处理完毕。GICC_IAR的值表示当前在处理的中断,把GICC_IAR的值写入GICC_EOIR就表示中断处理完了。

位域读写描述
[31:13]-保留
[12:10]CPUIDW对于SGI类中断,它的值跟GICD_IAR. CPUID的相同。
[9:0]EOIINTIDW中断ID,它的值跟GICD_IAR里的中断ID相同

1.3 GIC编程

使用cortex A7处理器的芯片,一般都是使用GIC v2的中断控制器。 处理GIC的基地址不一样外,对GIC的操作都是一样的。 在NXP官网可以找到IMX6ULL的SDK包。 下载后可以参考这个文件:core_ca7.h,里面含有GIC的初始化代码。