4、设备树中断控制器:中断控制器节点、interrupt-parent、interrupt-cells、中断映射
中断,说白了就是硬件主动找CPU“喊一嗓子”。
没有中断的时候,CPU只能轮询——一遍遍问“你好了没?你好了没?”效率低得可怜。有了中断,外设干完活了直接通知CPU,CPU该干嘛干嘛去。嗯,这里要注意,设备树里描述中断的方式,跟我们在裸机里写中断向量表完全是两码事。
4.1 中断控制器节点
在设备树里,中断控制器也是一个节点。它得有一个 interrupt-controller 属性,告诉内核:“我是管中断的”。
我个人习惯,拿到一块新板子,先看它的中断控制器是GIC(通用中断控制器)还是老式的NVIC。GIC在ARM Cortex-A系列里很常见,NVIC多见于Cortex-M。
一个典型的中断控制器节点长这样:
/ {
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xf0001000 0x1000>,
<0xf0000100 0x0100>;
};
};
这里有个关键点:#interrupt-cells 决定了这个中断控制器需要多少个cell来描述一个中断。GIC用3个cell,分别表示中断类型(SPI、PPI、SGI)、中断号、触发方式。
重要: 中断控制器节点必须声明 interrupt-controller 属性,否则内核不认。
4.2 interrupt-parent:谁是我的“上级”?
每个需要中断的外设节点,都得告诉系统:“我的中断信号发给谁?”
这个“谁”就是 interrupt-parent。它指向一个中断控制器节点。
举个例子:
uart0: serial@10000000 {
compatible = "ns16550a";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 33 4>;
};
这里 interrupt-parent = <&gic> 表示UART的中断信号连接到GIC上。我在项目中遇到过有人忘记加这个属性,结果中断死活不触发,查了半天才发现是父节点没指定。
小技巧: 如果整个系统只有一个中断控制器,可以在根节点设置 interrupt-parent,子节点默认继承。但我不建议这么做——万一以后加个二级中断控制器,代码改起来很麻烦。
4.3 interrupt-cells:中断描述符的“格式”
#interrupt-cells 定义了每个中断需要几个32位整数来描述。这个值由中断控制器自己决定。
常见的几种情况:
| 中断控制器类型 | #interrupt-cells | 含义 |
|---|---|---|
| ARM GIC (v1/v2) | 3 | 中断类型、中断号、触发方式 |
| ARM GIC (v3/v4) | 1 或 3 | 简化版只用中断号 |
| 老式中断控制器 | 2 | 中断号、触发方式 |
| 简单中断控制器 | 1 | 只有中断号 |
你想想看,如果 #interrupt-cells 写错了,内核解析中断信息时就会“对不上号”,轻则中断号错乱,重则直接panic。
避坑指南: 我曾经在移植一个老驱动时,发现中断控制器用了3个cell,但外设节点只给了2个。结果中断号被左移了8位,触发了一个完全不相关的中断。排查了整整一个下午。
4.4 中断映射:从硬件到软件
中断映射,说白了就是“硬件中断号”怎么变成“Linux IRQ号”。
这个过程由中断控制器驱动完成。设备树里描述的是硬件信息,内核启动时会解析这些信息,建立映射表。
一个典型的中断映射流程:
- 内核解析设备树,找到外设节点的
interrupts属性 - 通过
interrupt-parent找到对应的中断控制器 - 调用中断控制器的
.xlate回调函数,把设备树里的cell转换成硬件中断号 - 分配一个Linux IRQ号,建立映射关系
- 驱动里调用
request_irq()时,传入的就是这个Linux IRQ号
下面这张图展示了整个映射关系:
嗯,这里要注意,中断映射不是简单的“硬件号=软件号”。GIC里SPI中断号从32开始,但Linux内部可能从16开始。所以驱动里看到的IRQ号,跟数据手册里的中断号,往往对不上。
调试技巧: 在驱动里加 printk("IRQ number: %d\n", irq);,然后跟 /proc/interrupts 里的值对比,就能验证映射是否正确。
4.5 实际项目中的经验
我在项目中遇到过最典型的问题,就是中断号冲突。两个外设用了同一个硬件中断号,结果一个触发,另一个也跟着响应。
排查方法其实很简单:
- 先看
/proc/interrupts,确认每个IRQ号对应的设备 - 再看设备树,确认每个外设的
interrupts属性 - 最后查中断控制器的
#interrupt-cells定义
还有一个坑:有些SoC内部有多个中断控制器,比如一个GIC加一个GPIO中断控制器。这时候 interrupt-parent 必须明确指定,不能依赖默认继承。我曾经见过一个工程师,把所有外设的 interrupt-parent 都指向了GIC,结果GPIO中断全跑到了GIC上,触发方式完全不对。
避坑指南: 如果外设连接到二级中断控制器(比如GPIO控制器),interrupt-parent 要指向这个二级控制器,而不是直接指向GIC。二级控制器自己再通过 interrupts 连接到GIC。
说白了,设备树里的中断描述,就是一张“谁的中断发给谁”的关系图。搞清楚了这张图,中断问题就解决了一半。