文主要粗略的讲述了ARM体系结构当中,GICv2版本的中断控制器逻辑和原理(现在GICv3, GICv4越来越多,这里先描述简单一点的V2)。
什么是中断控制器呢?中断控制器就是负责将众多外设中断转发给CPU的一个中间商,类似于二手车买卖市场(不赚差价)。通常在ARM cortex-M系列中,采用的是NVIC,而在ARM cortex-A系列当中,一般都是用的GIC。GIC是Generic Interrupt Controller的简称,是属于ARM核外的一个器件,集成于SOC之上。
下图为GIC的一个逻辑框架图:
从上图,我们可以看到在GICv2当中,主要分成几个模块:Distributor(分发器)、CPU interface(CPU接口)、Virtual CPU interface(CPU虚拟接口,用于hypervisor)。在GICv2当中,cpu interface是在gic上面的,这和GICv3/4不一样,GICv3/4是集成到ARM核上面的。
Distributor: 主要用于处理中断优先级,然后将中断分发给对应的CPU interface(SMP)。
CPU interface: 这个接口,每个CPU核会对应一个,比如这是一个双核CPU,那么就有两个cpu interface。这个接口主要用于中断优先级屏蔽和处理中断抢占以及和CPU核
通信。
Virtual cpu interface: 功能和cpu interface一样,只是这个用于虚拟化环境,作为虚拟CPU的interface。
中断控制器分发器Distributor
分发器Distributor主要负责集中所有的中断源,并决定每一个中断的中断优先级,然后将优先级最高的中断转发给对应的cpu interface。它提供了如下几个编程接口:
1、全局使能中断转发
2、使能或者禁用中断
3、设置中断优先级
4、设置中断的目标cpu interface
5、设置中断的触发方式,如边沿触发或者电平触发
6、设置中断所在的组(group0或者group1),注group0属于secure模式,group1属于unsecure模式
在分发器当中,分发器将中断号称为中断ID(interrupt ID),每个cpu interface能看见1020个中断,并且对这些ID做出了分类:0-15叫做SGI(Software Generated Interrupts),16-31叫做PPI(Private Peripheral Interrupts), 32-1019叫做SPI(Shared Peripheral Interrupts),1020-1023保留,做一些特殊处理。
SGI: 主要用于处理器间中断,比如可以bring up 第二个核、Linux IPI通知其他核刷新映射表等(通常由操作系统定义中断号的作用)。
PPI:PPI中断属于CPU核私有的中断(每个CPU核都会有自己独立的PPI),用于处理一些与核关联的中断。
SPI:SPI中断属于共享中断,也就是所有CPU核都可以访问,比如uart中断,GPIO中断、I2C中断等。实际使用中,我们将它配置到一个核上来处理,默认是core0,中断负载均衡的时候,会将它们调整到不同的core上面去(SPI的中断号分配由SOC定义)。
注:secure模式下通常采用fiq中断线, unsecure采用irq中断线。
注: secure模式是指ARM的secure模式:arm支持secure模式(做trust zone比较多)、unsecure模式(正常系统)、hypervisor模式(虚拟化)。
中断控制器的CPU interface
每一个cpu核都对应一个GIC的cpu interface,cpu interface支持的可编程接口如下:
1、使能CPU处理器接口上的中断信号。
2、CPU处理器应答(ACK)对应中断。(ack由操作系统中断控制器驱动实现)
3、指示中断处理完成
4、设置中断优先级屏蔽mask
5、定义处理器的中断抢占策略
6、决定处理器的挂起的最高优先级
当使能时,每一个CPU interface会获取挂起pending列表中优先级最高的中断,然后决定是否需要发送给真正的CPU核处理器。
中断控制器处理流程
如图:
默认一个中断的状态为deac
tive, 当外设触发一个中断后,GIC的distributor检查到中断信号,通过配置,distributor将该中断设置成pending状态并加入到对应优先级penging列表,然后将最高优先级的pending列表转发给对应处理器的cpu interface,cpu interface发现penging列表里面有中断penging,就通过与处理器连接的IRQ线触发ARM中断(ARM核只有两根中断线即IRQ和FIQ)。此后,ARM处理器上检测到中断,中断服务程序开始运行,通过读取GICC_IAR寄存器可以得知中断号并ACK(应答)GIC控制器。此后,该中断状态变为active或者pending+active, 中断服务程序继续处理直到程序结束,中断服务程序结束前发送EOI(End of Interrupt)信号给GIC控制(这里EOI包含2个步骤:1、priority drop;2、deactivation),此后该中断状态变为deactive。
图中橙色是硬件流程,蓝色是软件流程,el1_isr为irq线的通用中断服务函数。
中断控制器状态说明
如上图,为GIC里面的中断对应的状态,当没有任何中断的时候状态为inactive。外设触发中断,distributor会将状态改为pending。当ARM处理器响应中断服务(中断服务程序响应之前,由GIC驱动会产生一个ACK信号给控制器)的时候,cpu interface在收到ACK信号后,将状态改为active或者active+pending,什么情况下是active呢?比如,外部中断是一个gpio按钮产生,假如是低电平有效,当用户按下按钮时,电平变为低电平,此时在GIC上产生电信号变化,GIC将状态改为penging,如果此时,用户突然松开了手,那么cpu interface只将状态改为active。但是如果用户没有松开手,那么cpu interface则将状态改为active+pending。所以可以了解到如果外设持续产生中断信号,那么就会出现active+pending的状态。这就是为什么绝大部分外设驱动在中断服务程序里面都会有一个清除pending状态的动作,如果不清除,即使中断服务程序处理结束返回,也会继续触发新的无用中断。
另外,当中断服务程序处理结束后,通常GIC驱动还需要做一个动作叫做EOI,这个EOI一般是将两个操作合并成了一个(GICv1就是这样),但是到了GICv2,它允许将两个操作分开。那么是哪两个操作呢?即priority drop和deactivation。
priority drop叫做优先级还原,即把当前中断的优先级从running priority改为响应中断之前的优先级。比如,我们给中断配置了一个优先级5,running priority数字为0, 那么肯定0的优先级高于5。当这个优先级为5的中断被CPU响应时,CPU会临时将这个中断的优先级改为0(running priority),这样可以防止被同组内的其它高优先级抢占而破坏中断流程,影响关键数据读取。当这个中断服务处理完成后,GIC又通过EOI的步骤将这个优先级变回原来的优先级5。
deactivation叫做失效,即因为在GIC驱动ACK应答GIC控制器的时候,GIC控制器会把中断状态改为active或者active+penging,因此,这个deactivation可以将状态active改为inactive状态或者active+penging改为penging。
在GICv2当中,它允许将两个方法分开,由软件控制,通过配置GICC_CTLR寄存器的EOImode位达到目的。如果分开,软件在中断服务程序结束后,需要做两个动作:1、写EOIR寄存器,释放running优先级;2、写GICC_DIR寄存器,修改中断状态。如果不分开,那么久只需要做一个写写EOIR寄存器的动作就可以了。
中断控制器抢占实现
GIC有两种方式实现中断抢占:1、利用优先级组实现;2、利用EOI实现
EOI实现组内优先级抢占:
首先,我们提及一下,在GIC驱动ACK应答控制的时候,GIC控制器内部会自动为当前中断分配一个临时最高优先级叫做running priority,如上面所说,这个优先级因为是最高的,因此在一个中断优先级组内是不可能被抢占的,但是如果GIC驱动将EOI设置成分开模式,就可以达到组内优先级抢占的目的。如:在中断服务程序里面,在自己读完关键寄存器后,可以先设置priority drop(中断优先级会从最高优先级running priority变为自己原本的优先级),然后再去执行一些耗时动作,这样,这个中断就可以被抢占,嵌套了(依然只能是比当前优先级更高的中断来抢占)。
优先级组实现抢占:
优先级组的设计本来就是为了抢占而实现,这里不会过多说明,通常情况下,组优先级相同的中断是不能抢占同组内的中断服务的。只有组优先级高于中断服务程序所在中断的组优先级的中断才会产生抢占(不管子优先级有多高,只要相同组,就不会发生抢占(上述EOI方法除外))。
中断控制器虚拟化实现(hypervisor)
GIC控制器与hypervisor的说明,会在以后虚拟化的时候,单独讲解。
Linux的GICv2实现,以后讲解,这里不讲解。
附上一个来自于《gic_architecture_specification.pdf》的截图: