在阅读本章之前,可以参考笔者之前关于GIC的一些描述:
ARM通用中断控制器GIC(generic Interrupt Controller)简介
ARM架构Generic Interrupt Controller(GIC)之Distributor和CPU interface功能介绍
ARM架构Generic Interrupt Controller(GIC)详解之术语介绍
文章目录
- 一,中断处理简述
- 二,在多处理器系统中处理不同类型的中断
- 三,如何识别所支持的中断
-
- 1,GICD_ISENABLERn, Interrupt Set-Enable Registers
- 2,Interrupt Clear-Enable Registers, GICD_ICENABLERn
- 3,Interrupt Controller Type Register, GICD_TYPER
-
- GICD_TYPER[10] -- SecurityExtn
- GICD_TYPER[7:5] -- CPUNumber
- GICD_TYPER[4:0] -- ITLinesNumber
- 4,Distributor Control Register, GICD_CTLR
- 5,中断识别相关寄存器使用总结
-
- 四,中断处理一般流程
- 中断处理后动作
-
- 1,优先级下降(Priority drop)
- 2 ,中断消失(Interrupt deactivation)
- 3, Interrupt Acknowledge Register, GICC_IAR
- 4, End of Interrupt Register, GICC_EOIR
一,中断处理简述
中断处理过程包括了:
- 如何识别辨认中断
- 软件层如何配置GIC,从而控制中断
- GIC在每个CPU interface上为每个中断维护的状态机
- 处理器的异常模型如何与GIC交互
GIC架构支持单处理器和多处理器系统,无论是单处理器还是多处理器系统,GIC架构都可以包含GIC安全扩展:
- 能够识别一个与之相连的,已经实现了ARM安全扩展的处理器,对GIC寄存器是安全访问还是非安全访问。
- 实现了判断对GIC寄存器是安全访问还是非安全访问,以至于:
– 可以复用(bank)一些寄存器,使同一个寄存器能够分为安全和非安全的拷贝。
– 一些寄存器是安全的(secure),这意味着只能在安全模式下访问。
– 剩下的寄存器是公共的(common),这意味着它们可以在安全和非安全模式下被访问。
- 可以使用中断分组(interrupt grouping)特性来处理安全中断和非安全中断,比如:
- Group 0 中断为安全中断
- Group 1 中断为非安全中断
- 在多处理器系统中,可能只在它的一些CPU interface中实现了GIC的安全扩展。
除了GIC v1版本不支持GIC安全扩展,在其他GIC架构中都支持中断分组(interrupt grouping):
- 默认情况下,所有中断都是Group 0中断,并且使用 IRQ中断请求被发送给与之相连的处理器。
- 每个中断可以被配置成 Group 0 或者 Group 1 中断。
- 一个CPU interface也可以被配置成使用FIQ中断请求,对一个已连接的处理器发送 Group 0 中断信号。
二,在多处理器系统中处理不同类型的中断
GIC支持外设中断( peripheral interrupts)和软件产生的中断(software-generated interrupts):
- 软件产生的中断(SGI)使用GIC的 N-N 模型。
- 外设(硬件)中断使用 1-N 模型。
三,如何识别所支持的中断
GIC架构通过中断ID来识别不同类型的中断,为了能够正确地处理中断,软件层必须知道GIC所支持的中断ID。 ARM强烈建议将已实现的中断进行分组,以使用最低的ID号和尽可能小的中断ID范围,因为这减少了必须实现的寄存器数量以及必须检查的中断路由范围。
1,GICD_ISENABLERn, Interrupt Set-Enable Registers
为了正确地处理中断,软件必须知道GIC所支持的中断ID。GICD_ISENABLERn寄存器为每个中断提供了一个Set-enable位,Set-enable位写1,说明该位对应的中断可以从Distributor转发到CPU interface。读取该bit,也可以获知对应中断是否被使能。
使用限制:
1. 如果该寄存器bit对应的是未实现的中断,则表现为RAZ(Read As Zero)和WI(Write Ignore)。
2. 如果实现了安全扩展,非安全状态下访问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。
2,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一致。
3,Interrupt Controller Type Register, GICD_TYPER
GICD_TYPER提供GIC的一些配置信息:
- 是否实现安全扩展
- GIC最大支持的中断数量
- 已经实现的CPU interface 数量
- 如果实现了安全扩展,所实现的最大LSPI(Lockable Shared Peripheral Interrupts)数量
GICD_TYPER[10] – SecurityExtn
GICD_TYPER[10] bit为1说明实现了安全扩展,为0则无。
GICD_TYPER[7:5] – CPUNumber
GICD_TYPER[7:5]为GIC实现的CPU interface数量,如果GICD_TYPER[7:5]为0b11,说明实现了4个CPU interface。
GICD_TYPER[4:0] – ITLinesNumber
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
4,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。
5,中断识别相关寄存器使用总结
如何找到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的中断转发。
四,中断处理一般流程
在Distributor中为每个被CPU interface所支持的中断维护了一个状态机,每个中断可以具有一下四种状态:
- inactive
- pending
- active
- active and pending
当GIC获知了一个中断请求,该中断的状态会被标记为pending,重新产生一个pending状态的中断也不会影响当前中断的状态。
中断的一般处理流程如下:
- 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的中断信号。
中断处理后动作
当处理器完成对中断的处理后,它必须将通知GIC已完成处理。中断处理完成后,GIC需要一些步骤来改变它的状态:优先级下降(Priority drop)和中断消失(Interrupt deactivation)。
1,优先级下降(Priority drop)
优先级下降(Priority drop)是在有效写入EOIR(即GICC_EOIR或GICC_AEOIR)时发生的运行优先级(Running priority)下降。有效写入是指写入不会不可预测,不会被忽略,并且写入的中断ID值不会大于1019。
- Running priority(运行时优先级):在CPU interface中,具有最高优先级且处于active状态的中断的group priority,没有被有效地写入EOI寄存器。如果CPU interface中没有active的中断,并且没有对EOI进行有效的写入,当前running priority将会变为idle priority。
- Idle priority(空闲优先级):Idle priority是指可以被分配给一个中断的最低优先级,在一个实现了8-bit字段用来描述中断优先级大小的实例中,Idle priority的值为0xFF。否则,它要么是0xFF,要么是GICD_IPRIORITYRn寄存器中的最大值,并且该寄存器中断priority字段可以自己赋值。
在优先级下降时,运行的优先级从EOIR写入所引用的中断的优先级降低到:
- 没有EOI写入的,处于active状态中优先级最高的优先级
- 如果当前没有active的中断,则是空闲优先级(优先级最小)。
2 ,中断消失(Interrupt deactivation)
中断消失就是中断状态的改变:
- 从active and pending 到 pending
- 从active 到 idle
在GICv1 和GICv2中,当对GICC_CTLR寄存器的EOImode字段设置为0时,对EOIR寄存器的有效写入也会造成对应中断消失。
在GICv2中,设置GICC_CTLR寄存器的EOImode字段设置为1时,将中断处理后动作分为优先级下降和中断消失两部分,此时对于中断处理的程序,必须:
- 对EOIR寄存进行有效地写入,使得在CPU interface中的中断优先级下降。
- 然后写入 GICC_DIR,使对应中断消失。
GIC架构对EOIR寄存器的写入是有顺序要求的,以至于:
- 对GICC_EOIR的有效写入,对应的是最近一次被确认的中断。
- 对GICC_AEOIR的有效写入,对应的是最近一次被确认的Group1中断。
- 写入GICC_EOIR是否会影响Group0 中断或者Group1中断,这取决于: GICC_CTLR寄存器的AckCtl字段,并且当前GIC实现了安全扩展,这次写入是secure还是non-secure的。
在实现了安全扩展的GICv2中:
- GICC_AEOIR是GICC_EOIR的Non-secure copy的别名
- GICC_AIAR是GICC_IAR 的Non-secure copy的别名
- GICC_AIAR和GICC_AEOIR是安全寄存器,这意味着它们只能通过安全访问来访问。
对于GICC_DIR寄存器的写入则无顺序要求,但是如果GICC_CTLR寄存器的EOImode字段被设为0,或者是GICC_CTLR寄存器的EOImode字段被设为1,但是此时并没有对应写入 GICC_EOIR 或者 GICC_AEOIR寄存器,此时对GICC_DIR寄存器的写入造成的影响是不可预知的。
当虚拟化物理中断时,ARM建议,对于与运行虚拟机的处理器对应的每个CPU接口:
- GICC_CTLR的EOImode字段设为1
- 如果GIC实现了GIC安全扩展,则使用GICC_CTLR的EOImodeNS位设置为1
3, 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:
4, 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寄存器。