4. 设备树地址与中断映射

设备树的核心职责之一是描述硬件设备的地址空间和中断连接。本章深入解析地址编码、地址转换以及中断映射的完整机制,这是编写正确设备树源文件(.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
    };
};
ranges 地址转换流程 CPU 地址空间 父总线 (soc) 子设备 (ethernet) 物理地址访问 ranges 转换 ranges 映射规则 子地址 → 父地址 → 长度 ranges = <0x0 0x20000000 0x100000> 子 reg=0x0 实际物理地址=0x20000000 范围=0x100000 (1MB)

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,下降沿触发
};
中断控制器级联与中断路由 GIC (一级中断控制器) GPIO 控制器 (二级) 外设 (keypad) interrupts = <0 45 4> interrupt-parent = <&gpio0> interrupts = <3 2> 中断路由路径:外设 → GPIO 控制器 → GIC → CPU

4.5 地址与中断映射实战要点

  1. 地址对齐:reg 中的地址必须与父节点 ranges 定义的子地址空间一致,而非 CPU 物理地址。
  2. 中断继承:子节点未指定 interrupt-parent 时,沿设备树向上查找第一个 interrupt-controller 节点。
  3. overlay 注意事项:在 overlay 中新增节点时,若目标父节点使用 ranges,必须确保子节点 reg 地址在父节点地址范围内。
  4. 中断类型编码:ARM GIC 使用 3 个 cell:中断类型(0=PPI, 1=SPI)、中断号、触发标志(4=高电平, 8=低电平, 1=上升沿, 2=下降沿)。
调试建议:使用 dtc -I dtb -O dts 反编译 dtb 文件,检查 reg 和 interrupts 是否被正确解析。在 Linux 内核中,通过 /proc/device-tree 查看实际设备树结构。