4、设备树地址与中断映射:reg属性与地址编码、ranges属性与地址转换、interrupt-parent与interrupts属性、中断控制器
设备树的核心职责之一是描述硬件设备的地址空间和中断连接。本章深入解析地址编码、地址转换以及中断映射的完整机制,这是编写正确设备树源文件(.dts)和覆盖文件(.dtso)的必备知识。
4.1 reg属性与地址编码
reg 属性定义了设备在父总线上的地址范围。其格式由父节点的 #address-cells 和 #size-cells 属性决定。
- #address-cells:表示地址字段占用的 32 位整数个数(通常为 1 或 2)。
- #size-cells:表示长度字段占用的 32 位整数个数(通常为 1 或 2,0 表示无长度)。
reg 属性是一个数组,每个条目由 (地址,长度) 对组成。格式为:reg = <地址1 长度1 地址2 长度2 ...>。
| 场景 | #address-cells | #size-cells | reg 示例 | 含义 |
|---|---|---|---|---|
| 32位外设 | 1 | 1 | reg = <0x10000000 0x1000> |
基址 0x10000000,范围 4KB |
| 64位外设 | 2 | 2 | reg = <0x0 0xFE000000 0x0 0x1000000> |
基址 0xFE000000,范围 16MB |
| 无内存映射 | 1 | 0 | reg = <0x48> |
仅表示 I2C 地址 0x48 |
关键规则:子节点的
reg 地址是相对于父节点地址空间的偏移。父节点通过 ranges 属性决定是否以及如何将子地址映射到父地址空间。
// 示例:32位系统上的 UART 节点
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>; // 基址 0x10000000,大小 4KB
interrupts = <0 33 4>;
};
};
4.2 ranges属性与地址转换
ranges 属性定义了子地址空间到父地址空间的映射关系。它是设备树实现地址层级转换的核心机制。
- ranges 存在且非空:表示子地址可以转换为父地址。格式为:
ranges = <子地址 父地址 长度>。 - ranges 为空(ranges;):表示子地址与父地址是 1:1 直接映射。
- ranges 不存在:表示子地址空间与父地址空间隔离,子设备不能被 CPU 直接访问(如 PCIe 桥)。
实战要点:在 overlay 中,如果父总线使用
ranges 进行地址转换,子节点 reg 中的地址必须填写子地址空间的值,而非 CPU 看到的物理地址。
// 示例:通过 ranges 将子地址 0x0 映射到父地址 0x20000000
bus@20000000 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x20000000 0x100000>; // 子地址0 → 父地址0x20000000,范围1MB
ethernet@0 {
reg = <0x0 0x1000>; // 子地址 0x0,实际物理地址为 0x20000000
};
};
4.3 interrupt-parent与interrupts属性
中断连接是设备树中除地址外的第二大关键描述。每个产生或消费中断的设备必须声明其中断连接关系。
- interrupt-parent:指定该设备的中断控制器父节点(phandle)。如果未指定,则继承父节点的中断父节点。
- interrupts:描述中断的编号、类型和触发方式。格式由中断控制器的
#interrupt-cells决定。
| 中断控制器类型 | #interrupt-cells | interrupts 格式 | 说明 |
|---|---|---|---|
| GIC (ARM) | 3 | <0 33 4> |
0=SPI, 33=中断号, 4=高电平触发 |
| 简单中断控制器 | 1 | <5> |
仅中断号 5 |
| 带触发类型 | 2 | <5 2> |
中断号 5,下降沿触发 |
常见错误:在 overlay 中新增设备节点时,如果未显式指定
interrupt-parent,系统会沿设备树向上查找。若父节点也未定义,则中断无法正确路由,导致驱动 probe 失败。
// 完整的中断连接示例
gic: interrupt-controller@1e000000 {
compatible = "arm,gic-v3";
reg = <0x1e000000 0x10000>;
interrupt-controller;
#interrupt-cells = <3>;
};
uart0: serial@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 显式指定中断父节点
interrupts = <0 33 4>; // SPI 33, 高电平触发
};
4.4 中断控制器
中断控制器节点是设备树中负责管理中断路由和优先级的核心节点。它必须声明 interrupt-controller 属性以标识自身角色。
- interrupt-controller:空属性,标记该节点为中断控制器。
- #interrupt-cells:指定中断描述符所需的 32 位整数个数。
- interrupts:中断控制器自身可能连接到上一级中断控制器(级联场景)。
级联中断控制器:在复杂 SoC 中,GPIO 控制器或 I2C 控制器可能作为二级中断控制器,它们自身也连接到 GIC。此时,二级控制器节点需要同时包含
interrupt-controller 和 interrupts 属性。
// 级联中断控制器示例
gic: interrupt-controller@1e000000 {
compatible = "arm,gic-v3";
reg = <0x1e000000 0x10000>;
interrupt-controller;
#interrupt-cells = <3>;
};
gpio0: gpio@10010000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x10010000 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 45 4>; // GPIO 控制器连接到 GIC
interrupt-controller; // GPIO 自身也是中断控制器
#interrupt-cells = <2>; // 子设备使用 <引脚号 触发类型>
};
// 连接到 GPIO 的外设
keypad: keypad@0 {
compatible = "gpio-keys";
interrupt-parent = <&gpio0>;
interrupts = <3 2>; // GPIO 引脚 3,下降沿触发
};
4.5 地址与中断映射实战要点
- 地址对齐:
reg中的地址必须与父节点ranges定义的子地址空间一致,而非 CPU 物理地址。 - 中断继承:子节点未指定
interrupt-parent时,沿设备树向上查找第一个interrupt-controller节点。 - overlay 注意事项:在 overlay 中新增节点时,若目标父节点使用
ranges,必须确保子节点reg地址在父节点地址范围内。 - 中断类型编码:ARM GIC 使用 3 个 cell:中断类型(0=PPI, 1=SPI)、中断号、触发标志(4=高电平, 8=低电平, 1=上升沿, 2=下降沿)。
调试建议:使用
dtc -I dtb -O dts 反编译 dtb 文件,检查 reg 和 interrupts 是否被正确解析。在 Linux 内核中,通过 /proc/device-tree 查看实际设备树结构。