1. 设备树中的中断描述:interrupt-parent、interrupts、interrupt-cells
大家好,我是你们的嵌入式系统讲师。今天咱们来聊聊设备树里中断描述这块硬骨头。说实话,我刚入行那会儿,看到设备树里一堆 interrupt 开头的属性,头都大了。后来踩了不少坑,才慢慢摸清楚这里面的门道。
中断描述,说白了就是告诉内核:这个设备用哪根中断线,怎么触发,找谁去处理。设备树里用三个核心属性来完成这件事:interrupt-parent、interrupts 和 interrupt-cells。咱们一个一个来看。
1.1 interrupt-parent:你的中断归谁管?
每个设备要使用中断,首先得知道谁能处理它的中断请求。这个「谁」就是中断控制器,比如 GIC(通用中断控制器)、GPIO 控制器,甚至可能是另一个芯片。
interrupt-parent 属性就是一个 phandle(指针),指向设备的中断控制器节点。举个例子:
// 设备节点
uart0: serial@10000000 {
compatible = "arm,pl011";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 指向 GIC 中断控制器
interrupts = <0 33 4>;
};
// 中断控制器节点
gic: interrupt-controller@2c001000 {
compatible = "arm,cortex-a15-gic";
reg = <0x2c001000 0x1000>;
interrupt-controller; // 声明自己是中断控制器
#interrupt-cells = <3>; // 每个中断描述需要 3 个 cell
};
这里有个细节:如果某个设备没有显式指定 interrupt-parent,内核会沿着设备树向上查找,直到找到某个节点的父节点带有 interrupt-controller 属性。嗯,这个机制我当年调试时吃过亏——明明配了中断,就是触发不了,最后发现是父节点没设对。
interrupts-extended 属性,这个咱们后面章节会讲。
1.2 interrupts:告诉内核中断怎么用
interrupts 属性描述的是设备具体使用的中断信息。它的格式由中断控制器的 #interrupt-cells 决定。每个 cell 是一个 32 位整数,组合起来描述一个中断。
拿最常见的 ARM GICv2 来说,#interrupt-cells = <3>,三个 cell 的含义是:
| Cell 位置 | 含义 | 取值说明 |
|---|---|---|
| cell 0 | 中断类型 | 0 = 共享外设中断 (SPI) 1 = 私有外设中断 (PPI) |
| cell 1 | 中断号 | SPI 范围:32~1019 PPI 范围:16~31 |
| cell 2 | 触发类型 | 1 = 上升沿触发 2 = 下降沿触发 4 = 高电平触发 8 = 低电平触发 |
举个例子:interrupts = <0 33 4> 表示这是一个 SPI 中断(类型 0),中断号 33,高电平触发(值 4)。
我个人习惯在写设备树时,把中断触发类型单独用宏定义出来,比如:
#define IRQ_TYPE_EDGE_RISING 1
#define IRQ_TYPE_EDGE_FALLING 2
#define IRQ_TYPE_LEVEL_HIGH 4
#define IRQ_TYPE_LEVEL_LOW 8
interrupts = <0 33 IRQ_TYPE_LEVEL_HIGH>;
这样可读性高很多,别人一看就知道是电平触发还是边沿触发。
Documentation/devicetree/bindings/interrupt-controller/ 目录下找对应的 binding 文档。我每次对接新芯片时,第一件事就是翻这个目录。
1.3 interrupt-cells:定义中断描述的长度
#interrupt-cells 是中断控制器节点上的属性,它告诉系统:我这个控制器描述一个中断需要几个 32 位整数。这个值完全由硬件设计决定。
常见的几种情况:
- 简单中断控制器(如老式 8259 PIC):
#interrupt-cells = <1>,只需要一个中断号。 - ARM GIC:
#interrupt-cells = <3>,需要类型、中断号、触发方式。 - GPIO 控制器作为中断控制器:通常是
#interrupt-cells = <2>,第一个 cell 是 GPIO 引脚号,第二个 cell 是触发类型。
来看一个 GPIO 中断的例子:
gpio0: gpio@50000000 {
compatible = "snps,dw-apb-gpio";
reg = <0x50000000 0x1000>;
#gpio-cells = <2>;
#interrupt-cells = <2>; // 两个 cell:引脚号 + 触发类型
interrupt-controller; // 这个 GPIO 也能处理中断
};
button {
compatible = "gpio-keys";
interrupt-parent = <&gpio0>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>; // GPIO5,下降沿触发
};
这里有个坑:GPIO 控制器既要管理 GPIO 输出,又要处理中断输入。我在一个项目里就遇到过,GPIO 的中断触发类型配错了,导致按键按下时中断没响应,松开时反而触发了。后来查了半天,发现是 interrupts 里的触发类型和 GPIO 的电气特性不匹配。
1.4 三者如何协同工作?
咱们用一张图来理清这三个属性的关系:
从这张图可以看得很清楚:interrupt-parent 是连接设备和中断控制器的桥梁,#interrupt-cells 定义了这座桥上跑的数据格式,而 interrupts 就是实际传输的数据内容。三者缺一不可。
1.5 实战中的避坑指南
我在几个项目里积累了一些经验,分享给大家:
- 中断号冲突:我曾经在一个 SoC 上同时接了多个外设,结果两个设备用了同一个中断号。内核启动时直接报 "irq already mapped",系统直接挂掉。排查方法是用
/proc/interrupts查看已注册的中断。 - 触发类型不匹配:硬件设计是高电平触发,设备树里配成了下降沿触发。结果中断要么不触发,要么疯狂触发。我建议拿到硬件原理图后,先确认中断信号的电气特性,再写设备树。
- 忘记声明 interrupt-controller:中断控制器节点上必须加
interrupt-controller;属性,否则内核不认。这个我犯过两次,都是低级错误。
好了,这一章的内容就到这里。中断描述是设备树里最基础也最容易出错的部分,建议大家动手写几个例子,在开发板上实际跑一跑。下一章咱们会深入 interrupts-extended 和中断映射的底层机制,到时候见。