4. 设备树中断控制器节点:interrupt-controller属性、#interrupt-cells、interrupt-parent
中断是嵌入式系统响应异步事件的核心机制。在设备树中,中断控制器节点通过三个关键属性描述中断的拓扑结构:interrupt-controller、#interrupt-cells 和 interrupt-parent。理解这三者的协作关系,是编写正确中断映射和调试中断相关驱动的基石。
4.1 中断控制器节点核心属性
设备树中,一个中断控制器节点必须声明自己能够接收和分发中断,并定义中断描述符的格式。以下是三个核心属性的详细说明:
| 属性名 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
interrupt-controller |
空属性(boolean) | 是 | 标记该节点是一个中断控制器,可以接收中断请求。 |
#interrupt-cells |
u32 | 是 | 指定该中断控制器使用多少个 cell(32位整数)来描述一个中断源。例如 GIC 通常为 3。 |
interrupt-parent |
phandle | 否(但常用) | 指定该节点或子节点的中断请求连接到哪个中断控制器。若未指定,则继承父节点的值。 |
关键区别:
interrupt-controller 是“我是中断控制器”的声明;#interrupt-cells 是“我如何描述中断”的格式定义;interrupt-parent 是“我连到谁”的连接关系。
4.2 中断描述符的组成:#interrupt-cells
#interrupt-cells 的值决定了设备节点中 interrupts 属性的格式。不同的中断控制器使用不同的 cell 数量来描述中断。以下是常见的中断控制器及其 cell 定义:
| 中断控制器类型 | #interrupt-cells | 中断描述符格式 | 示例 |
|---|---|---|---|
| ARM GIC v2/v3 | 3 | <中断类型 中断号 触发方式> | <0 89 4>(SPI, 89号, 高电平触发) |
| 简单中断控制器(GPIO) | 2 | <中断源引脚 触发方式> | <5 2>(GPIO5, 下降沿触发) |
| 老旧/自定义控制器 | 1 | <中断号> | <7>(硬件中断线7) |
实践要点: 在编写设备树时,必须查阅对应中断控制器的绑定文档(binding document),确认
#interrupt-cells 的值以及每个 cell 的含义。错误的 cell 数量会导致内核无法正确解析中断号。
4.3 interrupt-parent:中断连接关系
interrupt-parent 属性通过 phandle(指向中断控制器节点的引用)建立设备与中断控制器之间的连接。它可以在以下位置出现:
- 根节点:作为默认中断父节点,所有未显式指定
interrupt-parent的子节点都继承此值。 - 总线节点:覆盖根节点的默认值,该总线下的设备继承此值。
- 设备节点:显式指定该设备的中断连接到哪个控制器,覆盖所有继承值。
以下是一个典型的中断连接关系示例:
/ {
interrupt-parent = &gic; /* 根节点默认中断父节点为 GIC */
gic: interrupt-controller@f0001000 {
compatible = "arm,gic-400";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0x0 0xf0001000 0x0 0x1000>;
};
uart0: serial@ff000000 {
compatible = "arm,pl011";
reg = <0x0 0xff000000 0x0 0x1000>;
interrupts = <0 33 4>; /* 继承根节点的 interrupt-parent,连接到 gic */
};
gpio0: gpio@ff001000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x0 0xff001000 0x0 0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
/* 该 GPIO 控制器本身的中断连接到 GIC */
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
};
};
4.4 中断映射关系 SVG 架构图
下图展示了设备树中中断控制器、设备节点与 interrupt-parent 之间的连接关系:
4.5 中断控制器节点完整示例
以下是一个包含两级中断控制器的完整设备树片段,展示了 interrupt-controller、#interrupt-cells 和 interrupt-parent 的协作:
/dts-v1/;
/ {
compatible = "acme,custom-board";
interrupt-parent = &gic; /* 默认中断父节点 */
gic: interrupt-controller@f0001000 {
compatible = "arm,gic-400";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0x0 0xf0001000 0x0 0x1000>;
};
/* 第一级中断控制器:GPIO */
gpio0: gpio@ff001000 {
compatible = "acme,gpio-controller";
reg = <0x0 0xff001000 0x0 0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>; /* GPIO 控制器本身连接到 GIC */
interrupts = <0 89 4>; /* GIC 描述符:SPI, 89, 高电平 */
};
/* 连接到 GPIO 控制器的设备 */
button@0 {
compatible = "acme,button";
interrupt-parent = <&gpio0>; /* 显式指定父中断控制器 */
interrupts = <3 2>; /* GPIO 描述符:引脚3, 下降沿 */
};
/* 直接连接到 GIC 的设备 */
uart0: serial@ff000000 {
compatible = "arm,pl011";
reg = <0x0 0xff000000 0x0 0x1000>;
interrupts = <0 33 4>; /* 继承根节点 interrupt-parent,即 GIC */
};
};
4.6 常见错误与调试技巧
常见错误 1: 忘记在中断控制器节点中添加
interrupt-controller; 属性。内核在解析时会忽略该节点,导致所有引用该节点的设备无法获取中断。
常见错误 2:
#interrupt-cells 的值与设备节点中 interrupts 属性的 cell 数量不匹配。例如 GIC 要求 3 个 cell,但设备只提供了 2 个,内核会解析出错误的中断号。
调试技巧: 使用
cat /proc/interrupts 查看系统中断分配情况;使用 dtc -I dtb -O dts 反编译设备树,检查中断属性是否正确;在驱动中使用 devm_request_irq() 后检查返回值,确认中断号是否有效。
4.7 中断映射流程总结
设备树中断映射的完整流程可以归纳为以下步骤:
- 声明中断控制器:在节点中添加
interrupt-controller;属性。 - 定义中断描述符格式:设置
#interrupt-cells指定每个中断需要多少个 cell。 - 建立连接关系:在设备节点或父节点中设置
interrupt-parent,指向对应的中断控制器。 - 提供中断描述符:在设备节点的
interrupts属性中,按照#interrupt-cells定义的格式填写中断信息。 - 内核解析:Linux 内核在启动时遍历设备树,通过
of_irq_parse_one()等函数解析中断映射,最终注册中断号。
核心要点: 中断控制器节点是中断拓扑的锚点。
interrupt-controller 声明角色,#interrupt-cells 定义语言,interrupt-parent 建立连接。三者缺一不可,共同构成了设备树中断系统的完整骨架。