linux irq GIC控制器 Generic Interrupt Controller。ARM提供的通用中断控制器。接受硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。分为V1-V4,
GICV2 GIC是联系外设中断和CPU的桥梁,也是各CPU之间中断互联的通道,负责检测,管理,分发中断。
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发送中断。
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个中断优先级。
中断状态和处理流程
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类型,它执行的时候会禁止所有中断。 访问用户空间的虚拟地址,因为中断允许在内核空间。