Linux内核中断子系统1 - 中断控制器GIC讲解
中断控制器
CPU需要集中“精力”做计算,其他的事务例如IO,例如中断管理,需要其他部件负责。IO可以通过DMA去负责,中断管理就需要中断控制器来做。中断控制器与CPU之间最好只建立少量的连接,比如GIC控制器与cpu cluster之间只有IRQ和FIQ连接用于通知CPU。
中断控制器还需要负责对各个外设中断的管理,如优先级分配,中断屏蔽,中断认可,中断路由,中断使能等
ARM提供了一个可用于ARMv8-A系统的通用中断控制器GIC,这个中断控制器的编程接口在GIC架构中定义。通用中断控制器(GIC)支持多核系统中cores之间由软件生成(SGI)、私有(PPI)和共享外设(SPI)中断的路由。
GIC架构还提供了可用于管理中断源和行为的寄存器,以及(在多核系统中)将中断路由到各个core的寄存器。它使软件能够屏蔽、启用和禁用来自单个源的中断,对单个中断源(在硬件上)进行优先级排序,并生成软件中断。GIC接受在系统级别断言的中断,并可以向它所连接的每个core发出信号,可能导致IRQ或FIQ异常。
GIC的作用
GIC可用于ARM A系列和R系列的单core或者多core的架构,在至少1个core的系统中管理中断,它支持:
- 有相关寄存器来管理中断源(interrupt sources),中断行为(interrupt behavior)以及中断路由(interrupt routing)
- ARM 架构的安全扩展
- ARM架构虚拟化扩展(实现ARM虚拟化扩展的处理器也必须实现ARM安全扩展)
- 从硬件(外设)中断源中,使能、禁止以及产生处理器中断
- 软件生成中断(SGI)
- 中断屏蔽以及优先级
- 支持单核和多核环境
- 在电源管理环境(power-management environments)中被事件唤醒
GIC控制器的结构
GIC控制器在结构上分为两个部分:
- distributor
- cpu interface
gic控制器的概览,与CPU之间的连接
其中:
分发器:
主要用于仲裁和分发中断请求给CPU。Distributor有寄存器可以控制每个中断的属性,比如优先级、状态、安全状态、路由信息以及使能状态等。Distributor通过连接的CPU interface,可以决定将哪个中断转发到指定的core中。这种机制使操作系统能够在core之间进行共享和分发中断。
Distributor是一个全局的组件,所有CPU共享,负责处理 Shared Peripheral Interrupts (SPIs),即系统中共享的外设中断,它是整个中断控制器的核心模块。
Distributor提供接口来设置每个中断的优先级,还负责SPIs中断的路由,决定哪个中断会发送到哪个处理器核心。Distributor可以配置每个SPI是电平敏感的(Level-sensitive)还是边沿触发的(Edge-triggered)。
除了传统的中断信号,Distributor还能生成消息信号中断(MSIs),这是一个更灵活的中断触发机制,常用于PCIe总线设备。还可以确定在每个安全状态中使用的程序员模型:亲和性路由还是传统遗留模式;
- 全局中断使能
- 每个中断的使能
- 中断的优先级
- 中断的分组
- 中断的目的core
- 中断触发方式
- 对于SGI中断,传输中断到指定的core
- 每个中断的状态管理 提供软件,可以修改中断的pending状态
CPU接口:负责与CPU内核连接,通过nIRQ和nFIQ与CPU Core交流。系统中的所有中断源都与之相连接。
- 将中断请求发送给cpu
- 对中断进行认可(acknowledging an interrupt)
- 中断完成识别(indicating completion of an interrupt)
- 设置中断优先级屏蔽
- 定义中断抢占策略
- 决定当前处于pending状态最高优先级中断
一个中断的几种典型的状态
在Distributor中为每个被CPU interface所支持的中断维护了一个状态机,每个中断可以具有一下四种状态:
每一个中断支持的状态有:
- 不活跃状态(inactive): 中断处于无效状态
- 等待状态(pending):中断处于有效状态,但是等待CPU响应该中断。
- 挂起的中断将会被转发到CPU interface,然后会被core进行处理。
- 活跃状态(active):CPU已经响应该中断,已被core确认,正在处理,但尚未完成。
- 活跃并等待(active and pending):CPU正在处理该中断,但是该中断源又发了中断过来。
一个中断状态变化过程:
Inactive -> Pending:当中断被外设断言时
Pending -> Active:当中断处理器(handler)获知了当前中断
Active -> Inactive:handler处理完成了中断
在Distributor中有相关寄存器能显示不同中断ID的当前中断状态
中断触发方式
针对外设中断,有两种触发方式:
- 边沿触发
- 电平触发
GIC还支持软件产生的中断(SGI - Software-generated interrupts)
中断号
GIC会为每一个硬件外设,也就是每一个硬件中断源,都分配一个中断号,标识中断。SoC设计工程师在设计GIC时会同时出一份中断路由表,告知驱动工程师每个外设在某个中断控制器上的对应的中断号。驱动工程师和操作系统工程师需要在软件层面建立外设的中断号-中断处理函数之间的映射。当外设产生中断时,中断控制器将中断信号分发给某个CPU,CPU进入操作系统通用的处理代码,通过读取GIC(或其他中断控制器)的Interrupt Acknowledge Register(即IAR寄存器)能够获取到中断号,并通过预先建立的映射,找到该外设的处理函数。
(需要注意的是,在一些操作系统层面会衍生出虚拟中断号和硬件中断号的概念,这是由于中断控制器级联的情况下产生的概念划分,从GIC中读取到的是硬件中断号,而软件在注册中断时用的是虚拟中断号,操作系统提供通用机制来建立这层映射(如linux下irq_domain机制,irq_of_parse_and_map接口)。从GIC中读取到硬件中断号后,通过查表或者其他方式来获取虚拟中断号,访问中断号-中断处理函数之间的映射,找到处理函数。)
GIC将中断类型分为三类,中断号有不同的范围:
对SDI PPI SPI的解释:
类型 | 全称 | ID范围 | 核心特性 |
---|---|---|---|
SGI | Software Generated Interrupt | 0-15 | 软件触发核间通信,支持跨CPU核心定向发送 |
PPI | Private Peripheral Interrupt | 16-31 | 私有外设中断,绑定到特定CPU核(如本地定时器) |
SPI | Shared Peripheral Interrupt | 32-1019+ | 共享外设中断,可路由至任意CPU核(如以太网控制器) |
中断流程
GIC 检测当前的中断流程如下:
- 当GIC检测到一个中断发生时,会将该中断标记为pending
- 对于每一个pending状态的中断,分发器负责为其确定目标CPU,将请求发送到该CPU
- 对于每个CPU,分发器会从众多pending状态的中断中,选择一个优先级最高的中断请求发送到与目标CPU对接的CPU接口。
- CPU接口会决定这个中断是否可以发送给目标CPU。如果优先级满足,GIC会发送一个中断请求给目标CPU。
- GIC进入中断异常,读取GICC_IAR来响应这个中断(由软件的异常处理程序负责,这个过程也被叫做中断认可)。GICC_IAR里- 保存的是硬件中断号。对于SGI来说,返回源CPI的ID。
- GIC在感知到软件读取该寄存器后会做如下处理:
- 如果中断处于pending,则pending –> active
- 如果中断重新产生,则pending –> active and pending
- 如果中断处于active,则active –> active and pending
- 处理器执行完中断的处理,发送一个EOI信号给GIC。
- GIC首先确认哪些中断已被使能
- 对于每个处于pending状态的中断,GIC将确认该中断的目标处理器,即要被转发到何处。
- 对于每个CPU interface,Distributor 将转发处于pending状态,并且优先级最高的中断到对于CPU interface。
- 每个CPU interface决定是否给与它相连的处理器发送中断请求信号。
- 处理器获知了中断,GIC将返回中断ID,并且更新中断状态。
在处理完中断后,处理器将会发送EOI(End Of Interrupt)给GIC
在更多的细节中,还有以下步骤:
- GIC可以决定哪个中断被使能,一个没有被使能的中断在GIC中无影响。
- 对于每个被使能的处于pending状态的中断,Distributor决定要转发到哪里。
- 对于每个处理器,Distributor基于它保存的每个中断的优先级信息,判断出优先级最高的中断,并将它转发到目标CPU interface中。
- 如果Distributor正在转发一个中断请求到CPU interface中,CPU interface将会判断该中断是否有足够的优先级,然后再给处理器发送中断信号。
- 当处理器发生中断异常时,它会读取其CPU interface的GICC_IAR寄存器以确认该中断,这个读操作将返回一个中断ID。
- 当处理器完成对中断的处理操作时,它必须发信号通知GIC已完成处理,总是需要有效地写入EOIR寄存器,并且可能也需要写入deactivate interrupt register, GICC_DIR寄存器。
对于每个CPU interface,GIC体系结构要求对EOIR的有效写入的顺序与从GICC_IAR或GICC_AIAR读取的顺序相反,因此每个有效的EOIR写入都将引用最近的一次中断确认。
如果在EOIR写入之后,没有具有足够优先级的pending中断,则CPU interface将向处理器取消中断异常请求。
CPU interface绝不会向与之相连的处理器发送处于active and pending状态的中断信号,它只会发送处理pending状态,并且有足够的优先级的中断:
对于PPI和SGI中断(N-N),特定中断ID的活跃状态在CPU interface之间被复用(banked),这意味着:如果一个特定中断ID的中断在一个CPU interface中处于active或者active and pending状态,那么在该CPU interface上就不会发出具有相同ID的中断信号。
对于SPI来讲(1-N),中断的活跃状态对所有CPU interface是共享的,这意味着:如果一个特定中断ID的中断在一个CPU interface中处于active或者active and pending状态,那么在任何 CPU interface上就不会发出具有相同ID的中断信号。
相关寄存器
GICD_ISENABLERn, Interrupt Set-Enable Registers
为了正确地处理中断,软件必须知道GIC所支持的中断ID。GICD_ISENABLERn寄存器为每个中断提供了一个Set-enable位,Set-enable位写1,说明该位对应的中断可以从Distributor转发到CPU interface。读取该bit,也可以获知对应中断是否被使能。
使用限制:
- 如果该寄存器bit对应的是未实现的中断,则表现为RAZ(Read As Zero)和WI(Write Ignore)。
- 如果实现了安全扩展,非安全状态下访问Group 0(安全中断)中断对应的寄存器bit,结果为RAZ和WI。
GICD_ISENABLERn里每个bit位对应一个中断ID的中断,比如GICD_ISENABLER0寄存器控制着中断ID为0至31的中断。
GICD_ISENABLER0 可以控制:
SGIs, 中断ID为15-0, 对应的寄存器bit为[15:0]
PPIs, 中断ID为31-16, 对应的寄存器bit为 [31:16]
剩余的GICD_ISENABLERn,从GICD_ISENABLER1开始,则是为SPI中断提供对应的控位。
假设某个中断的ID为M,则GICD_ISENABLERn的n为M ÷ 32的整数部分,它在GICD_ISENABLERn中对应bit位序号为 M mod 32。
比如中断ID为66的中断,它对应的寄存器为GICD_ISENABLER2,并且GICD_ISENABLER2中的GICD_ISENABLER2[2]为该中断对应的bit位。
此外,对于SGI中断,对GICD_ISENABLERn的对应bit的读写是未定义的,对于PPI和SPI是有效的。
禁用中断仅禁用将中断从Distributor转发到任何CPU interface。它不会阻止中断改变状态,例如如果它已经为active,则不能阻止其变为pending或者active and pending。
Interrupt Clear-Enable Registers, GICD_ICENABLERn
中断清除寄存器GICD_ICENABLERn为每个中断提供了一个Clear-enable bit,写1将会禁止Distributor将对应的中断转发给CPU interface,读取对应bit位可以知道该中断是否被使能。
对于SGI,GICD_ICENABLERn对应bit的读写无影响。
对于PPI和SPI:
读到0:说明对应中断被禁止转发
读到1:说明对应中断可以被转发
写入0:无影响
写入1:禁止对应中断被转发,写入1后立即读取该bit位,将会返回0。
GICD_ICENABLERn其他特性和GICD_ISENABLERn一致。
Interrupt Controller Type Register, GICD_TYPER
GICD_TYPER提供GIC的一些配置信息:
- 是否实现安全扩展 — SecurityExtn( bit为1说明实现了安全扩展,为0则无。)
- GIC最大支持的中断数量 –– ITLinesNumber
- 已经实现的CPU interface 数量 — CPUNumber( 如果GICD_TYPER[7:5]为0b11,说明实现了4个CPU interface。)
- 如果实现了安全扩展,所实现的最大LSPI(Lockable Shared Peripheral Interrupts)数量
GICD_TYPER[4:0]为GIC所支持的最大的中断数量。
若GICD_TYPER[4:0]=N,则说明最多支持 32*(N+1)个中断。
比如N=3,则最多支持128个中断,中断号为0~127。GICD_TYPER[4:0]=0b11111=1020为所支持的最大的中断ID号,并且1020至1023作为其他用途的中断ID而被保留使用。
由于SGI和PPI中断的数量固定,所以ITLinesNumber能说明GIC所能支持的SPI中断的最大数量,ITLinesNumber的值也是以下寄存器的n值:
- GICD_IGROUPRn
- GICD_ISENABLERn
- GICD_ICENABLERn
- GICD_ISPENDRn
- GICD_ICPENDRn
- GICD_ISACTIVERn
- GICD_IPRIORITYRn
- GICD_ITARGETSRn
- GICD_ICFGRn
Distributor Control Register, GICD_CTLR
该寄存器将控制Distributor是否能将处于pending状态的中断转发到CPU interfaces中。
如果没有实现安全扩展,比如在GICv1版本中,GICD_CTLR寄存器只有一个enable bit,用于控制Distributor向CPU interface转发处于pending状态的中断。
在GICv2中,如果实现了安全扩展,则有两个enable为,分别为Group0和Group1。
如果enable位置0,说明Distributor不能将中断转发,置1则表明Distributor可以在优先级规则下将中断转发。
需要注意的是,即使将Distributor的全局enable位置0,禁止了Distributor的转发功能,其他GIC寄存器仍可以正常进行读写,这意味着在重新enable Distributor之前,软件层可以改变PPI和SPI中断的中断状态,比如:
通过写入对应的GICD_ISPENDRn寄存器使中断处于pending状态。
通过写入对应GICC_EOIR 或GICC_AEOIR寄存器 ,可以移除中断的active状态。
当 GICD_CTLR.的{EnableGrp1, EnableGrp0}被设置为不能转发中断,读取 GICC_IAR 或者GICC_AIAR寄存器时将返回一个虚假的中断ID。
Interrupt Acknowledge Register, GICC_IAR
处理器可以读取GICC_IAR寄存器来获取被接收的中断的ID,读该寄存的动作相当于一个获知中断的过程。
GICC_IAR[12:10], CPUID,对于多处理器系统中的SGI中断,这个字段标识了请求中断的那个处理器。它将返回产生这个中断请求的CPU interface的编号,比如3,则表示是core3写入GICD_SGIR寄存器来产生中断的,对于其他中断来讲,该字段是RAZ(Read As Zero)
GICC_IAR[9:0] ,Interrupt ID,中断ID。
处理器读取 GICC_IAR寄存将返回对应CPU interface中,处于pending状态且优先级最高的中断的ID,以下情况下,将会读到虚假中断ID:- Distributor的转发功能被禁止。
- CPU interface的发送中断信号功能被禁止。
- CPU interface中没有足够的优先级的,且处于pending状态的中断可以被发送到对应处理器。
End of Interrupt Register, GICC_EOIR
处理器写入GICC_EOIR寄存器将会通知CPU interface 当前中断已经处理完成,如果是GICv2架构,当GICC_CTLR.EOImode字段被设为1时,CPU interface 还会为当前中断进行中断优先级下降操作(Priority Drop)。
- GICC_EOIR[12:10],CPUID字段,在多处理器实现中,如果当前写是针对SGI中断,这个字段将包含访问对应GICC_IAR所得到的CPUID,对于其他中断来说,该字段将会被写入0(SBZ,Should Be Zero)。
- GICC_EOIR[9:0],EOIINTID,中断ID,访问对于的GICC_IAR寄存器所获得的值。
每一次从GICC_IAR中有效地读取中断ID时,与之相连的处理器将会对应地写入GICC_EOIR寄存器。写入GICC_EOIR寄存器的中断ID必须是从GICC_IAR中读取到的值。
如果 从GICC_IAR中读到的是虚假中断ID( spurious interrupt ID),软件就没有必要将这个值写入EOIR寄存器,应为写入虚假中断ID将会被GIC忽略。
此外,ARM强烈建议将GICC_IAR寄存器的值整个保留下来,然后写入对应的EOIR寄存器。
中断识别相关寄存器使用总结
如何找到GIC所支持的中断?
- 首先读取GICD_TYPER寄存器, 从GICD_TYPER寄存器的ITLinesNumber字段可以知道所实现的GICD_ISENABLERn寄存器的数量,也就是n的大小,从中也可知当前GIC可支持多少个SPI。
- 通过写入GICD_CTLR寄存器,可以禁止Distributor将中断转发到CPU interface。
- 对于每个已经实现的GICD_ISENABLERn,从GICD_ISENABLER0开始:
- 对GICD_ISENABLERn.写入0xFFFFFFFF
- 然后读取GICD_ISENABLERn.的值,如果读到1,则表明GIC支持对应的中断ID。
软件可以使用GICD_ICENABLERn寄存器来发现哪些中断被永久enable了。对于每个已经实现的GICD_ICENABLERn,从GICD_ICENABLER0开始: - 对GICD_ICENABLERn.写入0xFFFFFFFF,将会禁止所有可以被禁止的中断。
- 读取GICD_ICENABLERn的值,如果读到1,说明对应中断被永久使能。
- 然后再对GICD_ISENABLERn中对应需要被重新enable的中断bit位写入1。
当软件完成这次搜索,通常会写入GICD_CTLR寄存器来重新使能Distributor的转发功能。
此外,如果软件使用的是非安全访问,则只能搜索并控制被配置为非安全的中断。如果GIC实现了中断分组,软件可以:
写入GICD_IGROUPRn寄存器,将中断配置成Group0还是Group1.
使用GICD_CTLR.EnableGrp0 和GICD_CTLR.EnableGrp1 两个bit位来单独控制Group0和Group1的中断转发。