参考文档:
《corelink_gic600_generic_interrupt_controller_technical_reference_manual_100336_0106_00_en》
《IHI0069H_gic_architecture_specification》
《ECM0495013B_GIC_Stream_Protocol》
接口如下图所示:
通常,Distributor和Redistributor用于配置中断,CPU interface用于处理中断
Distributor的寄存器是内存映射的(memory-mapped),用于配置spi:
每个core都连接一个redistributor
功能 |
---|
启用和禁用SGI和PPI(Banked per PE) |
设置SGI和PPI的优先级 |
将每个PPI设置为水平触发或边缘触发(SGI都是边缘触发) |
将每个SGI和PPI分配给一个中断组 |
控制SGI和PPI的状态(active、inactive、pending、active and pending) |
控制内存中支持相关中断属性和LPI挂起状态的数据结构的基址 |
为连接的PE提供电源管理支持 |
每个core包含一个CPU interface,这是在中断处理期间使用的系统寄存器:
功能 |
---|
提供通用控制和配置以启用中断处理 |
将中断请求发送给cpu |
中断认可(Acknowledge an interrupt) |
执行优先级降权(priority drop)和中断无效(deactivation) |
配置PE的中断优先级掩码 |
定义PE的中断抢占策略 |
确定PE的最高优先级挂起中断(Determine the highest priority pending interrupt for the PE) |
对于每一个中断而言,有以下4个状态:
inactive:中断处于无效状态,既没有新的信号产生,也没有在处理中的信号
pending:中断处于有效状态,但是cpu没有响应该中断
active:cpu在响应该中断,但是还没有处理完成
active and pending:cpu在响应该中断,但是该中断源又发送中断过来(只有边缘触发型的中断才有这个状态)
PS:LPI中断没有active和active and pending两个状态
Transition A1 or A2:这种转换发生在中断变为挂起的时候,可能是由于外设产生了中断,也可能是由于软件产生了中断
Transition B1 or B2:中断被外设无效(deasserted),中断是level-sensitive(水平触发),或者当软件改变了挂起状态时,就会发生这种转换
Transition C:这种转换发生在PE对边缘触发的spi、sgi和ppi的中断确认时;对于spi、sgi和ppi,当软件从 ICC_IAR0_EL1 或 ICC_IAR1_EL1 读取INTID值时,就会发生这种转换
Transition D:这种转换发生在PE对水平触发的spi、sgi和ppi的中断确认时
Transition E1 or E2:当软件停用spi、sgi和ppi的中断时,就会发生这种转换
状态变迁:
中断认可,是指cpu响应该中断。此时中断状态从pending状态,变为active状态。通过访问ICC_IAR0_EL1或者GICC_IAR寄存器,来对中断进行认可。
ICC_IAR0_EL1、GICC_IAR:认可group0的中断
ICC_IAR1_EL1、GICC_AIAR:认可group1的中断
cpu interface会将该中断的优先级作为运行中的优先级,任何低于该优先级的中断都无法抢占该中断的处理过程。可以通过ICC_RPR_EL1查看该优先级
中断完成,是指cpu处理完中断。此时中断状态从active状态,变为inactive状态。gic中,对中断完成,定义了以下两个stage:
这里为什么要分两个阶段呢,其实是有考虑的:对于中断来说,我们是希望中断处理程序越短越好,但是有些中断处理程序,就是比较长,在这种情况下,就会使其他中断得到响应的时间变长,从而影响实时性。
比如当前cpu在响应优先级为4的中断A,但是这个中断A的中断处理程序比较长,此时如果有优先级为5的中断B到来,那么cpu是不会响应这个中断的。
在linux中,会将中断处理程序分为两部分,分为上半部分和下半部分。
在上半部分,完成中断最紧急的任务,然后就可以通知GIC,降低当前的中断处理优先级,以便其他中断能够得到响应。
在下半部分,处理该中断的其他事情。
在这种机制下,低优先级的中断,不用等待高优先级的中断完全执行完中断处理程序后,就可以被cpu所响应,提高实时性。(低优先级已经处在pending状态,就可以立马转为active)
为了实现上述机制,就将中断完成分成了2步。
还是刚刚的例子,cpu在响应优先级为4的中断A,当中断A的上半部分完成后,通知GIC,优先级降权(priority drop),GIC将当前的最高优先级中断重置,重置到响应中断A之前的优先级,比如优先级6,那么此时优先级为5的中断B,就可以被cpu响应。
最后中断A的下半部分完成后,通知GIC,将该中断A的状态,设置为inactive状态,此时中断A就真正的完成了。 当然,也可以不将中断完成分成2步,就1步。通过控制 ICC_CTLR_EL1或者GICC_CTLR寄存器的EOImode位,来决定是否将中断完成分成2步。
END OF INTERRUPT 有两种方式,由GIC DISTRIBUTOR的CTLR存器EOIMODE位决定:
中断处理流程,包含了以下几步:
gicv3中,多了很多寄存器。而且对寄存器,提供了2种访问方式,一种是memory-mapped的访问,一种是系统寄存器访问
memory-mapped访问的寄存器 |
---|
GICC:cpu interface寄存器 |
GICD:distributor寄存器 |
GICH:virtual interface控制寄存器,在hypervisor模式访问 |
GICR:redistributor寄存器 |
GICV:virtual cpu interface寄存器 |
GITS:ITS寄存器 |
系统寄存器访问的寄存器 |
---|
ICC:物理 cpu interface 系统寄存器 |
ICV:虚拟 cpu interface 系统寄存器 |
ICH:虚拟 cpu interface 控制系统寄存器 |
对于系统寄存器访问方式的gic寄存器,是实现在core内部的。而memory-mapped访问方式的gic寄存器,是在gic内部的。
gicv3架构中,没有强制,系统寄存器访问方式的寄存器,是不能通过memory-mapped方式访问的。也就是ICC, ICV, ICH寄存器,也是可以实现在gic内部,通过memory-mapped方式去访问。但是一般的实现中,是没有这样的实现的。
下图是gicv3中,各个寄存器所在的位置:
下图是系统寄存器和memory-mepped方式寄存器的对应关系:
思考:
答:上述两个问题,总接起来的原因是这两点:为了软件编写能够简单,通用;为了让中断响应能够更快。
cpu interface的寄存器,是会频繁被core所访问的,因为core需要访问cpu interface的寄存器,来认可中断,来中断完成,来无效中断。而其他的寄存器,是配置中断的,只有在core需要去配置中断的时候,才会去访问。
在gicv2中,cpu interface的寄存器,是实现在gic内部的,因此当core收到一个中断时,会通过axi总线(假设memory总线是axi总线),去访问cpu interface的寄存器。而中断在一个soc系统中,是会频繁的产生的,这就意味着,core会频繁地去访问gic的寄存器,这样会占用axi总线的带宽,从而影响中断的实时响应。而且core通过axi总线去访问cpu interface寄存器,延迟也比较大。
在gicv3中,将cpu interface从gic中抽离出来,实现在core内部,而不实现在gic中。core对cpu interface的访问,通过系统寄存器方式访问,也就是使用msr,mrs访问,那么core对cpu interface的寄存器访问就加速了,而且还不占用axi总线带宽。这样core对中断的处理就加速了。
cpu interface与gic之间,是通过专用的AXI4-Stream总线来传输信息的,这样也不会占用AXI总线的带宽。
GIC Stream Protocol接口基于单向的AXI4-Stream接口。因此,为了支持双向通信,GIC流协议接口在每个方向上都包含一个AXI4-Stream协议接口。
它用于gic的IRI组件(interrupt routing infrastructure)和cpu interface之间传输信息。 distributor,redistributor和ITS,统称为IRI组件。 gic stream协议,包含以下2个接口:
IRI组件与cpu interface通过gic stream协议传输信息,传输的信息是以包(packet)为单位,分为两类:
AXI-stream协议,每次传输2个字节,多次传输,组成一个包。不同的包,大小是不一样的,比如有的是16个字节,有的是8个字节。包传输的第一个16bit数据,表示包的类型。 如果一个组件,发送命令包,那么另一个,需要回应响应包。
redistributor给cpu interface发送2个中断,第二个中断抢占第一个中断。包的流程如下:
SGI通常用于核间通信,通过写入cpu interface中的以下SGI寄存器之一来生成:(只支持边缘触发)
软件写ICC_SGI0R_EL1产生secure状态的group0软中断
软件写ICC_SGI1R_EL1产生对应当前secure状态的group1软中断
软件写ICC_ASGI1R_EL1产生secure状态的group1软中断
注意点:
GICv3对虚拟化的支持增加了以下功能:
cpu interface 寄存器的硬件虚拟化
生成虚拟中断并发出信号的能力
维护中断,将虚拟机中的特定事件通知hypervisor
cpu interface 寄存器分为三组:
可以看到,原来的cpu interface再次细分,分成了物理cpu interface以及虚拟cpu interface,两者的功能是类似的。
细节:开启虚拟化时,当OS运行在虚拟机内部时ICC会自动映射成ICV。
为什么要这么处理呢?个人认为是为了方便虚拟化的实施,这样就可以不需要修改原来的GIC驱动代码,也不需要在代码里准备两套驱动逻辑。
虚拟中断的包传输流程和物理中断的类似,只是使用的命令变成了虚拟中断专用的。例如,下面的是中断发送的流程,使用的是VSet命令而不是Set命令:
st寄存器项指明了要发送的vINTID和原始pINTID的关系。hypervisor然后执行异常返回,将执行返回给vPE
5. 虚拟cpu interface检查虚拟中断是否可以转发到vPE。除了使用ICV寄存器之外,这些检查与物理中断相同。若可以,就会asserted一个虚拟异常
6. 虚拟异常被带到EL1。当软件读取IAR时,返回vINTID,虚拟中断现在处于Active状态
7. Guest OS处理中断。当它处理完中断后,它会写入EOIR来执行优先级降权和停用。由于List寄存器记录了pINTID,这将同时禁用vINTID和pINTID。
虚拟中断的包传输流程和物理中断的类似,只是使用的命令变成了虚拟中断专用的。例如,下面的是中断发送的流程,使用的是VSet命令而不是Set命令: