1、Linux 中如何标识一个外部中断?
在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断:
a -- IRQ number
CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
b -- HW interrupt ID
对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对外设中断进行编码。Interrupt controller用HW interrupt ID来标识外设的中断(即硬件中断号是对于中断控制器来讲的)。在interrupt controller级联的情况下,仅仅用HW interrupt ID已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不同的Interrupt controller上是会重复编码的)。
系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(我们称之interrupt source),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。但这个编号仅仅限制在本interrupt controller范围内。
要注意,这里的中断控制器并不是GIC,而是每一个中断的 interrupt - parent,下面讲解设备树时会提到。
2、中断节点在设备树中的描述
在Device Tree Source文件中,对于那些产生中断的外设,我们需要定义interrupt-parent和interrupts属性:
(1)interrupt-parent
表明该外设的interrupt request line物理的连接到了哪一个中断控制器上,中断控制器会对中断源进行描述;
(2)interrupts
这个属性描述了具体该外设产生的interrupt的细节信息(也就是传说中的interrupt specifier)。例如:HW interrupt ID(由该外设的device node中的interrupt-parent指向的interrupt controller解析)、interrupt触发类型等。
对于Interrupt controller,我们需要定义interrupt-controller和#interrupt-cells的属性:
(1)interrupt-controller
表明该device node就是一个中断控制器
(2)#interrupt-cells
该中断控制器用多少个cell(一个cell就是一个32-bit的单元)描述一个外设的interrupt request line。具体每个cell表示什么样的含义由interrupt controller自己定义。
(3)interrupts和interrupt-parent
对于那些不是root 的interrupt controller,其本身也是作为一个产生中断的外设连接到其他的interrupt controller上,因此也需要定义interrupts和interrupt-parent的属性。
举个例子,这是上面编写按键驱动时的中断描述:
- fs4412-key{
- compatible = "fs4412,key";
- interrupt-parent = <&gpx1>;
- interrupts = <1 2>,<2 2>;
- };
3、Linux 内核对中断的初始化过程
ARM linux内核启动时,首先运行的是linux/arch/arm/kernel/head.S,进行一些初始化工作,然后调用main.c->start_kernel()函数,进而调用early_irq_init()函数进行初始化、init_IRQ()函数进行中断初始化、建立异常向量表
【init_IRQ ---> irqchip_init ---> of_irq_init】
- extern struct of_device_id __irqchip_begin[];
- void __init irqchip_init(void)
- {
- of_irq_init(__irqchip_begin);
- }
extern struct of_device_id __irqchip_begin[];
- struct of_device_id
- {
- char name[32];------要匹配的device node的名字
- char type[32];-------要匹配的device node的类型
- char compatible[128];---匹配字符串(DT compatible string),用来匹配适合的device node
- const void *data;--------对于clock source,这里是初始化函数指针
- };
这个数据结构主要被用来进行Device node和driver模块进行匹配用的。从该数据结构的定义可以看出,在匹配过程中,device name、device type和DT compatible string都是考虑的因素。更细节的内容请参考__of_device_is_compatible函数。
__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的ID信息(用于和device node的匹配)。
void __init of_irq_init(const struct of_device_id *matches)
of_irq_init函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点都已经形成了一个树状结构,每个节点代表一个设备的device node。of_irq_init是在所有的device node中寻找中断控制器节点,形成树状结构(系统可以有多个interrupt controller,之所以形成中断控制器的树状结构,是为了让系统中所有的中断控制器驱动按照一定的顺序进行初始化)。之后,从root interrupt controller节点开始,对于每一个interrupt controller的 device node,扫描irq chip table,进行匹配,一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为参数传递给irq chip driver。
4、中断触发后的处理流程
a -- 具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler;
b -- machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number
c -- 调用该IRQ number 对应的high level irq event handler,在这个high level的handler中,会通过和interupt controller交互,进行中断处理的flow control(处理中断的嵌套、抢占等),当然最终会遍历该中断描述符的IRQ action list,调用外设的specific handler来处理该中断
d -- 具体CPU architecture相关的模块会进行现场恢复。