changelog:
2019年02月17日 初稿
2020年03月1日 fix typos以及增加中断路由
GIC,Generic Interrupt Controller。是ARM公司提供的一个通用的中断控制器。主要作用为:
接受硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。
当前GIC 有四个版本,GIC v1~v4, 主要区别如下表:
本文主要介绍GIC v3控制器, 基于linux kernel 4.19.0。
2. GIC v3中断类别GICv3定义了以下中断类型:
SPI (Shared Peripheral Interrupt)
公用的外部设备中断,也定义为共享中断。可以多个Cpu或者说Core处理,不限定特定的Cpu。比如按键触发一个中断,手机触摸屏触发的中断。
PPI (Private Peripheral Interrupt)
私有外设中断。这是每个核心私有的中断。PPI会送达到指定的CPU上,应用场景有CPU本地时钟。
SGI (Software Generated Interrupt)
软件触发的中断。软件可以通过写GICD_SGIR寄存器来触发一个中断事件,一般用于核间通信。
LPI (Locality-specific Peripheral Interrupt)
LPI是GICv3中的新特性,它们在很多方面与其他类型的中断不同。LPI始终是基于消息的中断,它们的配置保存在表中而不是寄存器。比如PCIe的MSI/MSI-x中断。
GICv3控制器由以下部分组成:
distributor: SPI中断的管理,将中断发送给redistributor
redistributor: PPI,SGI,LPI中断的管理,将中断发送给cpu interface
cpu interface: 传输中断给core
ITS: Interrupt Translation Service, 用来解析LPI中断
其中,cpu interface是实现在core内部的,distributor,redistributor,ITS是实现在gic内部的.
Distributor 详述
Distributor的主要的作用是检测各个interrupt source的状态,控制各个interrupt source的行为,分发各个interrupt source产生的中断事件分发到指定的一个或者多个CPU interface上。虽然Distributor可以管理多个interrupt source,但是它总是把优先级最高的那个interrupt请求送往CPU interface。
Distributor对中断的控制包括:
(1)中断enable或者disable的控制。Distributor对中断的控制分成两个级别。一个是全局中断的控制(GIC_DIST_CTRL)。一旦disable了全局的中断,那么任何的interrupt source产生的interrupt event都不会被传递到CPU interface。另外一个级别是对针对各个interrupt source进行控制(GIC_DIST_ENABLE_CLEAR),disable某一个interrupt source会导致该interrupt event不会分发到CPU interface,但不影响其他interrupt source产生interrupt event的分发。
(2)控制将当前优先级最高的中断事件分发到一个或者一组CPU interface。当一个中断事件分发到多个CPU interface的时候,GIC的内部逻辑应该保证只assert 一个CPU。
(3)优先级控制。
(4)interrupt属性设定。例如是level-sensitive还是edge-triggered
(5)interrupt group的设定
Distributor可以管理若干个interrupt source,这些interrupt source用ID来标识,我们称之interrupt ID。
Redistributor详述
对于每个连接的PE,都有一个Redistributor.
该block的主要功能包括:
(1)启用和禁用SGI和PPI。
(2)设置SGI和PPI的优先级。
(3)将每个PPI设置为电平触发或边缘触发。
(4)将每个SGI和PPI分配给中断组。
(5)控制SGI和PPI的状态。
(6)内存中数据结构的基址控制,支持LPI的相关中断属性和挂起状态。
(7)电源管理支持。
CPU interface详述
CPU interface这个block主要用于和process进行接口。
该block的主要功能包括:
(a)enable或者disable CPU interface向连接的CPU assert中断事件。对于ARM,CPU interface block和CPU之间的中断信号线是nIRQCPU和nFIQCPU。如果disable了中断,那么即便是Distributor分发了一个中断事件到CPU interface,但是也不会assert指定的nIRQ或者nFIQ通知processor。
(b)ackowledging中断。processor会向CPU interface block应答中断(应答当前优先级最高的那个中断),中断一旦被应答,Distributor就会把该中断的状态从pending状态修改成active或者pending and active(这是和该interrupt source的信号有关,例如如果是电平中断并且保持了该asserted电平,那么就是pending and active)。processor ack了中断之后,CPU interface就会deassert nIRQCPU和nFIQCPU信号线。
(c)中断处理完毕的通知。当interrupt handler处理完了一个中断的时候,会向写CPU interface的寄存器从而通知GIC CPU已经处理完该中断。做这个动作一方面是通知Distributor将中断状态修改为deactive,另外一方面,CPU interface会priority drop,从而允许其他的pending的interrupt向CPU提交。
(d)设定priority mask。通过priority mask,可以mask掉一些优先级比较低的中断,这些中断不会通知到CPU。
(e)设定preemption的策略
(f)在多个中断事件同时到来的时候,选择一个优先级最高的通知processor
gicv3使用hierarchy来标识一个具体的core, 如下图是一个4层的结构(aarch64)
<affinity level 3>.<affinity level 2>.<affinity level 1>.<affinity level 0> 组成一个PE的路由。
每一个core的affnity值可以通过MPDIR_EL1寄存器获取, 每一个affinity占用8bit.
配置对应core的MPIDR值,可以将中断路由到该core上。
各个affinity的定义是根据SOC自己的定义
比如可能affinity3代表socketid,affinity2 代表clusterid, affnity1代表coreid, affnity0代表thread id.
以gic 设置中断路由为例:
中断亲和性的设置的通用函数为irq_set_affinity, 具体调用如下:
gic_set_affinity先判断当前中断是否使能,如果使能则disable掉该中断;
然后根据gic_mpidr_to_affinity函数获取需要绑定中断到对应core的路由,
static u64 gic_mpidr_to_affinity(unsigned long mpidr){u64 aff;aff = ((u64)MPIDR_AFFINITY_LEVEL(mpidr, 3) << 32 | MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 | MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 | MPIDR_AFFINITY_LEVEL(mpidr, 0));return aff;}aff 就是通过对应core的MPIDR_EL1寄存器获取affinity0~3的值,并组成一个新的32bit value;
最后将获取的value写进gic irouter寄存器并更新中断的亲和性配置。
4. 中断处理流程
从上图可以看出,中断处理可以分成两类:
(1)中断要通过distributor的中断流程
–>外设发起中断,发送给distributor
–>distributor将该中断,分发给合适的re-distributor
–>re-distributor将中断信息,发送给cpu interface。
–>cpu interface产生合适的中断异常给处理器
–>处理器接收该异常,并且软件处理该中断
它的中断状态机如下图所示
有四种中断状态:
processor ack了一个中断后,该中断会被设定为active。当处理完成后,仍然要通知GIC,中断已经处理完毕了。这时候,如果没有pending的中断,GIC就会将该interrupt设定为inactive状态。操作GIC中的End of Interrupt Register可以完成end of interrupt事件通知。
(2)中断不通过distributor,比如LPI中断
外设发起中断,发送给ITS
–>ITS分析中断,决定将来发送的re-distributor
–>ITS将中断发送给合适的re-distributor
LPI是基于消息的中断。中断信息不在通过中断线进行传递,而是通过memory。
gic内部提供一个寄存器,当外设往这个地址写入数据时,就往gic发送了一个中断。
在soc系统中,外设想要发送中断给gic,是需要一根中断线的。如果现在一个外设,需要增加一个中断,那么就要增加一根中断线,然后连接到gic。这样就需要修改设计。而引入了LPI之后,当外设需要增加中断,只需要使用LPI方式,传输中断即可,不需要修改soc设计。
传统的GIC流程:
在传统的GIC流程中如上图,外围设备的中断触发线是引出到GIC上的,这样可以理解为一个物理的SIGNAL,比如一个高电平信号,边沿触发信号。
消息中断流程:
使用消息将中断从外设转发到中断控制器,无需每个中断源提供专用信号。 这样的一个好处是,可以减少中断线的个数。
在GICv3中,SPI可以是基于消息的中断,但LPI始终是基于消息的中断。
5.ITS引入了LPI之后,gicv3中,还加入了ITS组件,interrupt translation service。ITS将接收到的LPI中断,进行解析,然后发送到对应的redistributor,再由redistributor将中断信息,发送给cpu interface。
外设,通过写GITS_TRANSLATER寄存器,发起LPI中断。写操作,给ITS提供2个信息:
EventID: 值保存在GITS_TRANSLATER寄存器中,表示外设发送中断的事件类型
DeviceID: 表示哪一个外设发起LPI中断。该值的传递,是实现自定义,例如,可以使用AXI的user信号来传递。
ITS将DeviceID和eventID,通过一系列查表,得到LPI中断号,再使用LPI中断号查表,得到该中断的目标cpu。
ITS将LPI中断号,LPI中断对应的目标cpu,发送给对应的redistributor。redistributor再将该中断信息,发送给CPU。
6.参考资料GICv3_Software_Overview_Official_Release_B