本文分为两篇,第一篇主要描述中断控制器和中断处理流程;第二篇主要讲述中断的下半部分处理机制包括softirq,tasklet,workqueue;
Linux中断系统(1)-中断框架
Linux中断系统(2)-中断下半部
(一)中断综述
中断硬件系统主要有各个外设、中断控制器(Interrupt Controller)和CPU组成。各个外设提供irq request line连接到中断控制器,在发生中断事件的时候,通过irq request line上的电气信号向CPU系统请求处理,Interrupt Controller是连接外设中断系统和CPU系统的桥梁。CPU的主要功能是运算,而Interrupt Controller来负责处理来自irq request line的电气信号;
根据中断控制器处理的类型对中断分类:
- SGI(Software Generated Interrupt),Interrupt IDs 0-15。系统一般用其来实现 IPI 中断。
- PPI(Private Peripheral Interrupt),Interrupt IDs16-31。私有中断,这种中断对每个 CPU 都是独立一份的,比如 per-core timer 中断。
- SPI(Shared Peripheral Interrupt),Interrupt numbers 32-1020。最常用的外设中断,中断可以发给一个或者多个 CPU。
- LPI(Locality-specific Peripheral Interrupt)。基于 message 的中断,GICv2 和 GICv1 中不支持;
linux中断框架:底层部分为跟硬件相关联的cpu部分和中断控制器部分,这两部分跟平台关联,cpu部分负责中断产生时的上下文切换,中断控制器部分完成中断控制器初始化,同时管理HW irq number,代码位于kernel-3.18\drivers\irqchip;
通用处理模块负责对不用平台硬件抽象,提供IRQ 相关API给到驱动使用,代码位于kernel-3.18\kernel\irq:
最上层为驱动层,负责不同的外设设备的中断申请和下半部份的处理;
(二)中断准备
(1) 中断控制器申明和设备匹配
在kernel启动函数start_kernel(kernel-3.18/init/main.c)通过调用arch平台init_IRQ()来实现中断的初始化,以arm64为例:
kernel-3.18/arch/arm64/kernel/irq.c:
53 void __init init_IRQ(void)
54 {
55 irqchip_init();
56 if (!handle_arch_irq)
57 panic("No interrupt controller found.");
58 }
of_irq_init在所有的device node中寻找中断控制器节点,形成树状结构。之后,从root interrupt controller节点开始,对于每一个interrupt controller的device node,扫描irq chip table,进行匹配,一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为参数传递给irq chip driver;
kernel-3.18/drivers/irqchip/irqchip.c :
24 extern struct of_device_id __irqchip_of_table[];
25
26 void __init irqchip_init(void)
27 {
28 of_irq_init(__irqchip_of_table);
29 }
kernel-3.18/drivers/of/irq.c:
484 void __init of_irq_init(const struct of_device_id *matches)
485 {
540 pr_debug("of_irq_init: init %s @ %p, parent %p\n",
541 match->compatible,
542 desc->dev, desc->interrupt_parent);
543 irq_init_cb = (of_irq_init_cb_t)match->data;
544 ret = irq_init_cb(desc->dev, desc->interrupt_parent);//调用mt_gic_of_init;
以通用的irq-gic.c为例:通过IRQCHIP_DECLARE定义若干个静态的struct of_device_id常量,编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(__irqchip_of_table),我们称这个特殊的section叫做irq chip table,这个table也就保存了kernel支持的所有的中断控制器的ID信息;
msm-3.18/drivers/irqchip/irq-gic.c:
1531 IRQCHIP_DECLARE(mt_gic, "mediatek,mt6735-gic", mt_gic_of_init);
//使用IRQCHIP_DECLARE来定义了若干个静态的struct of_device_id常量;
//gic_of_init : gic初始化函数指针;
//"mediatek,mt6735-gic" : 要匹配的device node的名字;
865 #define _OF_DECLARE(table, name, compat, fn, fn_type) \
866 static const struct of_device_id __of_table_##name \
867 __used __section(__##table##_of_table) \
868 = { .compatible = compat, \
869 .data = (fn == (fn_type)NULL) ? fn : fn }
这个of_device_id主要被用来进行Device node和driver模块进行匹配用的,这里在drvier里面先声明,然后我们看下dts里面device node:
kernel-3.18/arch/arm64/boot/dts/mt6755.dtsi:
305 gic: interrupt-controller@10230000 {
306 compatible = "mediatek,mt6735-gic";
//该中断控制器用多少个cell(一个cell就是一个32-bit的单元)
//描述一个外设的interrupt request line;
308 #address-cells = <0>;
309 interrupt-controller;//表明该device node就是一个中断控制器;
310 reg = <0 0x10231000 0 0x1000>,
311 <0 0x10232000 0 0x1000>,
312 <0 0x10200620 0 0x1000>;
313 };
以i2c0中断声明为例:
kernel-3.18/arch/arm64/boot/dts/mt6755.dtsi
1124 i2c0: i2c@11007000 {
1125 compatible = "mediatek,mt6755-i2c", "mediatek,i2c0";
1126 cell-index = <0>;
1127 reg = <0x11007000 0x1000>,
1128 <0x11000100 0x80>;
1129 interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_LOW>;
//这个属性描述了具体该外设产生的interrupt的细节信息(也就是
//interrupt specifier)。例如:HW interrupt ID(由该外设的
//device node中的interrupt-parent指向的interrupt controller
//解析)、interrupt触发类型等。
1130 clock-div = <10>;
1131 clocks = <&infrasys INFRA_I2C0>, <&infrasys INFRA_AP_DMA>;
1132 clock-names = "main", "dma";
1133 #address-cells = <1>;
1134 #size-cells = <0>;
1135 };
//三个cell的情况:第一个值:表示中断类型(SPI,PPI,SPI);第二个是中断号;第三个是中断触发条件;
(2) 中断控制器初始化
通过注册的mt_gic_of_init函数来完成控制器的初始化,kernel-3.18/drivers/irqchip/irq-mt-gic.c:
1474 int __init mt_gic_of_init(struct device_node *node, struct device_node *parent)
1475 {
1476 void __iomem *cpu_base;
1477 void __iomem *dist_base;
1478 void __iomem *pol_base;
1479 u32 percpu_offset;
1480 int irq;
1481 struct resource res;
1485
1486 if (WARN_ON(!node))
1487 return -ENODEV;
1488
1489 spin_lock_init(&irq_lock);
1490
1491 dist_base = of_iomap(node, 0);
///*映射GIC Distributor的寄存器地址空间,Distributor负
//责连接系统中所有的中断源,通过寄存器可以独立的配置每个中断的
//属性:priority、state、security、outing
//information、enable status;定义哪些中断可以转发到 CPU core。*/
1492 WARN(!dist_base, "unable to map gic dist registers\n");
1493 GIC_DIST_BASE = dist_base;
1494
1495 cpu_base = of_iomap(node, 1);
//映射GIC CPU interface的寄存器地址空间;CPU core 用来接收中断,寄
//存器主要提供的功能:mask、 identify 、control states of //interrupts forwarded to that core。每个 CPU core 拥有自己的
//CPU interface。
1496 WARN(!cpu_base, "unable to map gic cpu registers\n");
1497 GIC_CPU_BASE = cpu_base;
1498
1499 pol_base = of_iomap(node, 2);
1500 WARN(!pol_base, "unable to map pol registers\n");
1501 INT_POL_CTL0 = pol_base;
1502 if (of_address_to_resource(node, 2, &res))
1503 WARN(!pol_base, "unable to map pol registers\n");
1504
1505 INT_POL_CTL0_phys = res.start;
1506
1507 if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
1508 percpu_offset = 0;
1509
1510 mt_gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
1511
1512 if (parent) {
//处理interrupt级联;
1513 irq = irq_of_parse_and_map(node, 0);
1514 mt_gic_cascade_irq(gic_cnt, irq);
1515 }
1516 gic_cnt++;
1517
1527
1528 return 0;
1529 }
函数mt_gic_init_bases():
855 void __init mt_gic_init_bases(unsigned int gic_nr, int irq_start,
856 void __iomem *dist_base, void __iomem *cpu_base,
857 u32 percpu_offset, struct device_node *node)
858 {
859 irq_hw_number_t hwirq_base;
860 struct gic_chip_data *gic;
861 int gic_irqs, irq_base, i;
862
863 BUG_ON(gic_nr >= MAX_GIC_NR);
864
865 gic = &gic_data[gic_nr];