转自:https://www.cnblogs.com/tureno/articles/6403408.html
转载于: http://www.wowotech.net/irq_subsystem/gic-irq-chip-driver.html
GIC驱动代码分析(废弃)
这份文档状态是:废弃,新的文档请访问http://www.wowotech.net/linux_kenrel/gic_driver.html
一、前言
GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器。GIC通过AMBA(Advanced Microcontroller Bus Architecture)这样的片上总线连接到一个或者多个ARM processor上。本文主要分析了linux kernel中GIC中断控制器的驱动代码。
具体的分析方法是按照source code为索引,逐段分析。对于每一段分析的代码,力求做到每个细节都清清楚楚。这不可避免要引入很多对GIC的硬件描述,此外,具体GIC中断控制器的驱动代码和linux kernel中断子系统的交互也会描述,但本文不会描述linux kernel的generic interrupt subsystem。
本文以OMAP4460这款SOC为例,OMAP4460内部集成了GIC的功能。具体的linux kernel的版本是linux3.14.。
二、GIC DTS描述
1、中断系统概述
对于中断系统,主要有三个角色:
(1)processor。主要用于处理中断
(2)Interrupt Generating Device。通过硬件的interrupt line表明自身需要处理器的进一步处理(例如有数据到来、异常状态等)
(3)interrupt controller。负责收集各个外设的异步事件,用有序、可控的方式通知一个或者多个processor。
2、DTS如何描述Interrupt Generating Device
对于Interrupt Generating Device,我们需要定义下面两个属性:
(1)Interrupt属性。该属性主要描述了中断的HW interrupt ID以及类型。
(2)interrupt-parent 属性。该属性主要描述了该设备的interrupt request line连接到哪一个interrupt controller。
在OMAP4460系统中,我们以一个简单的串口为例子,具体的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:
uart3: serial@48020000 {
compatible = "ti,omap4-uart";
reg = <0x48020000 0x100="">;
interrupts = <gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
ti,hwmods = "uart3";
clock-frequency = <48000000>;
};
对于uart3,interrupts属性用3个cell(对于device tree,cell是指由32bit组成的一个信息单位)表示。GIC_SPI 描述了interrupt type。对于GIC,它可以管理4种类型的中断:
(1)外设中断(Peripheral interrupt)。根据目标CPU的不同,外设的中断可以分成PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI只能分配给一个确定的processor,而SPI可以由Distributor将中断分配给一组Processor中的一个进行处理。外设类型的中断一般通过一个interrupt request line的硬件信号线连接到中断控制器,可能是电平触发的(Level-sensitive),也可能是边缘触发的(Edge-triggered)。
(2)软件触发的中断(SGI,Software-generated interrupt)。软件可以通过写GICD_SGIR寄存器来触发一个中断事件,这样的中断,可以用于processor之间的通信。
(3)虚拟中断(Virtual interrupt)和Maintenance interrupt。这两种中断和本文无关,不再赘述。
在DTS中,外设的interrupt type有两种,一种是SPI,另外一种是PPI。SGI用于processor之间的通信,和外设无关。
uart3的interrupt属性中的74表示该外设使用的GIC interrupt ID号。GIC最大支持1020个HW interrupt ID,具体的ID分配情况如下:
(1)ID0~ID31是用于分发到一个特定的process的interrupt。标识这些interrupt不能仅仅依靠ID,因为各个interrupt source都用同样的ID0~ID31来标识,因此识别这些interrupt需要interrupt ID + CPU interface number。ID0~ID15用于SGI,ID16~ID31用于PPI。PPI类型的中断会送到指定的process上,和其他的process无关。SGI是通过写GICD_SGIR寄存器而触发的中断。Distributor通过processor source ID、中断ID和target processor ID来唯一识别一个SGI。
(2)ID32~ID1019用于SPI。
uart3的interrupt属性中的IRQ_TYPE_LEVEL_HIGH用来描述触发类型。
很奇怪,uart3并没有定义interrupt-parent属性,这里定义interrupt-parent属性的是root node,具体的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:
/ {
compatible = "ti,omap4430", "ti,omap4";
interrupt-parent = <&gic>;略去无关内容
}
难道root node会产生中断到interrupt controller吗?当然不会,只不过如果一个能够产生中断的device node没有定义interrupt-parent的话,其interrupt-parent属性就是跟随parent node。因此,与其在所有的下游设备中定义interrupt-parent,不如统一在root node中定义了。
3、DTS如何描述GIC
linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中,
gic: interrupt-controller@48241000 {
compatible = "arm,cortex-a9-gic";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0x48241000 0x1000="">,
<0x48240100 0x0100="">;
};
compatible属性用来描述GIC的programming model。该属性的值是string list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个driver来驱动该设备。假设定义该属性:compatible = “a厂商,p产品”, “标准bbb类型设备”。那么linux kernel可能首先使用“a厂商,p产品”来匹配适合的driver,如果没有匹配到,那么使用字符串“标准bbb类型设备”来继续寻找适合的driver。compatible属性有两个应用场景:
(1)对于root node,compatible属性是用来匹配machine type的(参考Device Tree相关文档)
(2)对于普通的HW block的节点,例如interrupt-controller,compatible属性是用来匹配适合的driver的。
interrupt-controller这个没有定义value的属性用来表明本设备节点就是一个interrupt controller。理解#interrupt-cells这个属性需要理解interrupt specifier和interrupt domain这两个概念。interrupt specifier其实就是外设interrupt的属性值,对于uart3而言,其interrupt specifier就是<gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,也就是说,interrupt specifier定义了一个外设产生中断的规格(HW interrupt ID + interrupt type)。具体如何解析interrupt specifier?这个需要限定在一定的上下文中,不同的interrupt controller会有不同的解释。因此,对于一个包含多个interrupt controller的系统,每个interrupt controller及其相连的外设组成一个interrupt domain,各个外设的interrupt specifier只能在属于它的那个interrupt domain中得到解析。#interrupt-cells定义了在该interrupt domain中,用多少个cell来描述一个外设的interrupt specifier。
reg属性定义了GIC的memory map的地址,有两段,分别描述了CPU interface和Distributor的地址段。理解CPU interface和Distributor这两个术语需要GIC的一些基本的硬件知识,参考下节描述。
三、GIC的HW block diagram描述
1、GIC HW概述
GIC的block diagram如下图所示:
GIC可以清晰的划分成两个block,一个block是Distributor(上图的左边的block),一个是CPU interface。CPU interface有两种,一种就是和普通processor接口,另外一种是和虚拟机接口的。Virtual CPU interface在本文中不会详细描述。
2、Distributor
Distributor的主要的作用是检测各个interrupt source的状态,控制各个interrupt source的行为,分发各个interrupt source产生的中断事件到各个processor。Distributor对中断的控制包括:
(1)中断enable或者disable的控制。Distributor对中断的控制分成两个级别。一个是全局中断的控制。一旦disable了全局的中断,那么任何的interrupt source产生的interrupt event都不会被传递到CPU interface。另外一个级别是对针对各个interrupt source进行控制,disable某一个interrupt source会导致该interrupt event不会分发到CPU interface,但不影响其他interrupt source产生interrupt event的分发。
(2)控制中断事件分发到process。一个interrupt事件可以分发给一个process,也可以分发给若干个process。
(3)优先级控制。
(3)interrupt属性设定。例如是level-sensitive还是edge-triggered,是属于group 0还是group 1。
Distributor可以管理若干个interrupt source,这些interrupt source用ID来标识,我们称之interrupt ID。
2、CPU interface
CPU interface这个block主要用于和process进行接口。该block的主要功能包括:
(1)enable或者disable。对于ARM,CPU interface block和process之间的中断信号线是nIRQ和nFIQ这两个signal。如果disable了中断,那么即便是Distributor分发了一个中断事件到CPU interface,但是也不会assert指定的nIRQ或者nFIQ通知processor。
(2)ackowledging中断。processor会向CPU interface block应答中断,中断一旦被应答,Distributor就会把该中断的状态从pending状态修改成active。如果没有后续pending的中断,那么CPU interface就会deassert nIRQ或者nFIQ的signal。如果在这个过程中又产生了新的中断,那么Distributor就会把该中断的状态从pending状态修改成pending and active。这时候,CPU interface仍然会保持nIRQ或者nFIQ信号的asserted状态,也就是向processor signal下一个中断。
(3)中断处理完毕的通知。当interrupt handler处理完了一个中断的时候,会向写CPU interface的寄存器从而通知GIC CPU已经处理完该中断。做这个动作一方面是通知Distributor将中断状态修改为deactive,另外一方面,如果一个中断没有完成处理,那么后续比该中断优先级低的中断不会assert到processor。一旦标记中断处理完成,被block掉的那些比当前优先级低的中断就会递交给processor。
(4)设定priority mask。通过priority mask,可以mask掉一些优先级比较低的中断,这些中断不会通知到CPU。
(5)设定preemption的策略
(6)在多个中断事件同时到来的时候,选择一个优先级最高的通知processor
四、系统启动过程中,如何调用GIC driver的初始化函数
在linux-3.14\drivers\irqchip目录下保存在各种不同的中断控制器的驱动代码,irq-gic.c就是GIC的驱动代码。
1、IRQCHIP_DECLARE宏定义
在linux-3.14\drivers\irqchip目录下的irqchip.h文件中定义了IRQCHIP_DECLARE宏如下:
#define IRQCHIP_DECLARE(name,compstr,fn) \
static const struct of_device_id irqchip_of_match_##name \
__used __section(__irqchip_of_table) \
= { .compatible = compstr, .data = fn }
这个宏就是被各个irq chip driver用来声明其DT compatible string和初始化函数的对应关系的。IRQCHIP_DECLARE中的compstr就是DT compatible string,fun就是初始化函数。irq-gic.c文件中声明的对应关系包括:
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
从上面的定义可以看出来,A9和A15的gic初始化函数都是gic_of_init。另外两个定义是和高通的CPU相关,我猜测是高通使用了GIC,但是自己又做了一些简单的修改,但无论如何,初始化函数都是一个,那就是gic_of_init。
在linux kernel编译的时候,你可以配置多个irq chip进入内核,编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(section name是__irqchip_of_table),我们称这个特殊的section叫做irq chip table。这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和DT compatible string的对应关系。具体执行哪一个初始化函数是由bootloader传递给kernel的DTB决定的。
2、OMAP4460的machine定义
在linux-3.14/arch/arm/mach-omap2目录下的board-generic.c的文件中定义了OMAP4460的machine如下:
DT_MACHINE_START(OMAP4_DT, "Generic OMAP4 (Flattened Device Tree)")
。。。。。。删除无关代码
.init_irq = omap_gic_of_init,
。。。。。。删除无关代码
MACHINE_END
在系统初始化的时候,会调用start_kernel->init_IRQ->machine_desc->init_irq()函数。
3、omap_gic_of_init过程分析
omap_gic_of_init的代码如下(删除了无关代码):
void __init omap_gic_of_init(void)
{
irqchip_init();
}
在driver/irqchip/irqchip.c文件中定义了irqchip_init函数,如下:
void __init irqchip_init(void)
{
of_irq_init(__irqchip_begin);
}
__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和DT compatible string的对应关系。of_irq_init函数scan bootloader传递来的DTB,找到所有的中断控制器节点,并形成树状结构(系统可以有多个interrupt controller)。之后,从root interrupt controller开始,对于每一个interrupt controller,scan irq chip table,匹配DT compatible string,一旦匹配到,就调用该interrupt controller的初始化函数。具体的代码分析可以参考Device Tree代码分析文档。
五、GIC driver初始化代码分析
gic_of_init的代码如下:
int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *cpu_base;
void __iomem *dist_base;
u32 percpu_offset;
int irq;if (WARN_ON(!node))
return -ENODEV;dist_base = of_iomap(node, 0);----------------映射GIC Distributor的寄存器地址空间
WARN(!dist_base, "unable to map gic dist registers\n");cpu_base = of_iomap(node, 1);----------------映射GIC CPU interface的寄存器地址空间
WARN(!cpu_base, "unable to map gic cpu registers\n");if (of_property_read_u32(node, "cpu-offset", &percpu_offset))--------处理cpu-offset binding。
percpu_offset = 0;gic_