linux irq

GIC控制器

Generic Interrupt Controller。ARM提供的通用中断控制器。接受硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。分为V1-V4,

GICV2

GIC是联系外设中断和CPU的桥梁,也是各CPU之间中断互联的通道,负责检测,管理,分发中断。

image-20250714085007704

1
2
3
4
5
6
主要负责
使能/禁止中断。
把中断分组到group0还是group1,0作为安全模式使用连接FIQ,1作为非安全模式,连接IRQ.
多核系统中将中断分配到不同处理器上。
设置电平的触发方式。
虚拟化扩展。

ARM CPU对外的连接只有2个中断,IRQ & FIQ,相对应的处理模式分别是 IRQ 一般中断处理模式 和 FIQ 快速中断处理模式,所以GIC最后要把中断汇集成2条线,与CPU对接。

在GICV2中,gic由两个大模块distributor和interface组成。

distributor

主要负责中断源的管理,优先级,中断使能,中断屏蔽等。中断分发,对于PPI,SGI是各个core独有的中断,不参与目的core的仲裁,SPI 是所有 core 共享的,根据配置决定中断发往的core。中断优先级的处理,将最高优先级的中断发送给cpu interface。寄存器使用GICD作为前缀,一个gic中,只有一个GICD。

主要的作用是检测各个中断源的状态,控制各个中断源的行为,分发各个中断源产生的中断事件到指定的一个/多个CPU接口上。虽然分发器可以管理多个中断源,但是它总是把优先级最高的那个中断请求送往CPU接口。分发器对中断的控制包括:

1
2
3
4
5
6
7
8
打开或关闭每个中断,Distributor对中断的控制分成两个级别,一个是全局中断的控制(GIC_DIST_CTRL),一旦关闭了全局中断,那么任何的中断源产生的中断事件都不会被传递到cpu interface。另一个级别是针对各个中断源进行控制,(GIC_DIST_ENABLE_CLEAR),关闭一个中断源会导致该中断事件不会分发到CPU interface,但不影响其他中断源产生中断事件的分发。
控制将当前优先级最高的中断事件分发到一个或者一组CPU interface,当一个中断事件分发到多个CPU interface的时候,GIC的内部逻辑应该只保证assert一个CPU。
优先级控制
interrupt属性设定。电平触发,边缘触发等等。
interrupt group设定。设置每个中断的group。
将SGI中断分发到目标CPU上。
每个中断状态可见。
提供软件机制来设置和清楚外设终端的pending状态。

cpu interface

用于连接器,与处理器进行交互,将GICD发送的中断信息,通过IRQ,FIQ等管脚,传输给core。寄存器使用GICC作为前缀,每一个core,有一个cpu interface。

1
2
3
4
5
6
打开或关闭cpu interface 向连接的CPU assert中断事件,对于arm,cpu interface和cpu之间的中断信号线是nIRQCPU 和 nFIQCPU, 如果关闭了中断,即便是distributor分发了一个中断事件到CPU interface,也不会assert指定的IRQ或者FIQ通知core。
中断的确认。core会向cpu interface应答中断,应答当前优先级最高的那个中断,中断一旦被应答,distributor就会把该中断的状态从pending修改为active。ack了之后,cpu就会deassert nirqcpu和nfiqcpu信号线。
中断处理完毕的通知。当interruput handler处理完了一个中断的时候,会向写CPU interface的寄存器通知GIC CPU已经处理完该中断,做这个动作一方面是通知 Distributor 将中断状态修改为 deactive,另外一方面,CPU interface 会 priority drop,从而允许其他的 pending 的中断向 CPU 提交。
为 CPU 设置中断优先级掩码。通过 priority mask,可以 mask 掉一些优先级比较低的中断,这些中断不会通知到 CPU。
设置中断抢占策略。
在多个中断同时到来的时候,选择一个优先级最高的通知CPU。

virtual cpu interface

将GICD发送的虚拟中断信息,通过VIRQ,VFIQ管脚,传输给core,每一个core,有一个virtual cpu interface,而在这个virtual cpu interface中,又包含以下两个组件,virtual interface control,virtual cpu interface。

gic中断类别

gicv2,将中断,分成了group0,安全,FIQ 和group1,非安全,IRQ。

支持三种类型的中断。

1
2
3
4
5
6
7
8
9
GICV2:

SGI software generated interrupt。软件触发的中断,件可以通过写 GICD_SGIR 寄存器来触发一个中断事件,一般用于核间通信,内核中的 IPI:inter-processor interrupts 就是基于 SGI。
PPI private peripheral interrupt。私有外设中断,是每个核心私有的中断,PPI会送达到指定的cpu上,应用场景有CPU的本地时钟。
SPI Shared peripheral interrupt。公用的外部设备中断,也定义为共享中断。中断产生后,可以分发到某一CPU上,中断号ID32 - ID1019用于SPI,ID1020 - ID1023保留用于特殊用途;

GICV3:

SGI,SPI, LPI(locality spicific peripheral interrupt)GICV3中引入,是基于消息的中断,他们的配置保存在表中而不是寄存器。

GICV3的组成部分,GICV3中,主要由Distributor,cpu interface,redistributor,its,GICV3中,将cpu interface从GIC中抽离,放入到了cpu中,cpu interface通过AXI Stream,与gic进行通信。 当GIC要发送中断,GIC通过AXI stream接口,给cpu interface发送中断命令,cpu interface收到中断命令后,根据中断线映射配置,决定是通过IRQ还是FIQ管脚,向cpu发送中断。

image-20250715092053150

image-20250714093922084

1
2
3
4
5
6
7
8
root@root:~# cat /proc/interrupts 

虚拟中断号 硬件中断号
CPU0 CPU1 CPU2 CPU3
10: 1 0 0 0 GICv2 84 Level CC_IRQ
12: 356258 361084 352728 352728 GICv2 30 Level arch_timer (本地时钟)
15: 0 0 0 0 GICv2 225 Level clocksource@2,f0106000
16: 0 0 0 0 GICv2 340 Level arm-pmu

gic中断处理流程

1
2
3
4
5
6
7
GIC决定每个中断的 使能 状态,不使能的中断,是不能发送中断的
如果某个中断的中断源有效,GIC将该中断的状态设置为pending状态,然后判断该中断的目标core
对于每一个core,GIC将当前处于pending状态的优先级最高的中断,发送给该core的cpu interface
cpu interface接收GIC发送的中断请求,判断优先级是否满足要求,如果满足,就将中断通过nFIQ或nIRQ管脚,发送给core。
core响应该中断,通过读取 GICC_IAR 寄存器,来认可该中断。读取该寄存器,如果是软中断,返回源处理器ID,否则返回中断号。
当core认可该中断后,GIC将该中断的状态,修改为active状态
当core完成该中断后,通过写 EOIR (end of interrupt register)来实现优先级重置,写 GICC_DIR 寄存器,来无效该中断。

gic中断优先级

gicv2,支持最小16个,最大256个中断优先级。

中断状态和处理流程

image-20250715092647205

1
2
3
4
5
6
每个中断都维护了一个状态机。

inactive: 无中断状态,没有pending也没有active。
pending:硬件或软件触发了中断,该中断事件已经通过硬件信号通知到了GIC,等待GIC分配的CPU进行处理,在电平触发模式下,产生中断的同时保持pengding状态。
Active:cpu已经应答该中断请求,并且正在处理中。
Active and pending:当一个中断源处于Active时,同一中断源又触发了中断,进入pending状态,挂起来状态。

软件框架

1
2
3
4
5
6
主要分为四部分:

1.硬件无关代码
2.cpu架构相关的中断处理
3.中断控制器代码
4.普通其他驱动
1
2
3
4
5
常见术语

irq number 软件定义,和硬件无关,CPU需要为每一个外设中断编号,
irq domain,irq域,将某一类资源划分成不同的领域,相同的域下共享一些共同的属性。irq domain负责GIC中hwirq到 虚拟irq的映射。
中断上半部/下半部:中断上半部处理简单的紧急的功能,清楚中断处理标志。大部分任务放到下半部处理。

中断设备树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gic: interrupt-controller@fd400000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>; // 参数个数
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;

reg = <0x0 0xfd400000 0 0x10000>, /* GICD */
<0x0 0xfd460000 0 0xc0000>; /* GICR */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
its: interrupt-controller@fd440000 { // 在gic设备节点下,有一个子设备节点its,ITS设备用于将消息信号中断(MSI)路由到cpu
compatible = "arm,gic-v3-its";
msi-controller;
#msi-cells = <1>; // MSI设备的DeviceID
reg = <0x0 0xfd440000 0x0 0x20000>; /*ITS寄存器的物理地址*/
};
};

中断控制器code

1
2
3
4
5
6
7
8
9
10
11
12
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);  //初始化一个struct of_device_id的静态常量,并放置在__irqchip_of_table中

drivers/irqchip/irq-gic.c
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
--> gic_of_init


init/main.c asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
--> early_irq_init(); init_IRQ();
--> arch/arm64/kernel/irq.c --> irqchip_init();
--> driver/irqchip/irqchip.c --> of_irq_init(__irqchip_of_table);
--> drivers/of/irq.c ????
1
https://doc.embedfire.com/linux/rk356x/driver/zh/latest/linux_driver/subsystem_interrupt.html

中断上下部分的处理手段

运行在进程上下文的内核代码是可抢占的,但中断上下文会一直运行到结束,不会被抢占。所以中断处理程序代码要受到一些限制。

1
2
3
4
5
中断代码不能:
睡眠/放弃CPU,因为内核在进入中断前会 关闭进程调度,一旦睡眠/放弃CPU,这时内核无法调度别的进程来执行,系统就会死掉。
尝试获得信号量,如果获得不到信号量,代码就会睡眠,导致如上的结果。
执行耗时的任务,中断处理应该尽可能快,如果一个处理程序是IRQF_DISABLE类型,它执行的时候会禁止所有中断。
访问用户空间的虚拟地址,因为中断允许在内核空间。