4、设备树地址与中断映射:地址编码、中断控制器与中断映射规则

设备树(Device Tree)是描述硬件拓扑与资源分配的核心数据结构。地址编码与中断映射是驱动移植中最容易出错的环节之一。本章系统梳理 #address-cells、#size-cells、interrupt-controller 以及中断映射规则,帮助工程师快速定位与修复相关适配问题。

4.1 地址编码:#address-cells 与 #size-cells

设备树中,每个总线节点通过 #address-cells 和 #size-cells 属性定义其子节点地址域的编码宽度。这两个属性共同决定了 reg 属性中地址与长度的解析方式。

属性 含义 常见值 说明
#address-cells 子节点地址字段占用的 32 位单元数 1、2 1 表示 32 位地址,2 表示 64 位地址
#size-cells 子节点长度字段占用的 32 位单元数 0、1 0 表示无长度(仅地址),1 表示 32 位长度
关键规则: 子节点 reg 属性中,地址部分占用 #address-cells 个 32 位单元,长度部分占用 #size-cells 个 32 位单元。解析顺序为:地址(高到低)→ 长度(高到低)。

示例:32 位地址空间

soc {
    #address-cells = <1>;
    #size-cells = <1>;

    uart@10000000 {
        reg = <0x10000000 0x1000>;
        // 地址: 0x10000000, 长度: 0x1000 (4KB)
    };
};

示例:64 位地址空间

pcie@40000000 {
    #address-cells = <3>;
    #size-cells = <2>;

    pci-ep@0,0 {
        reg = <0x0000 0x0 0x0 0x0 0x0>;
        // 地址: 3 个单元 (配置空间类型+组+编号), 长度: 2 个单元
    };
};
常见错误: 当父节点 #address-cells 为 2 时,子节点 reg 中地址部分必须写满 2 个 32 位单元,否则解析器会错位读取,导致地址或长度异常。

4.2 中断控制器:interrupt-controller 与 #interrupt-cells

中断控制器节点通过 interrupt-controller 属性声明自身为中断控制器,并通过 #interrupt-cells 指定中断描述符所需的单元数。

属性 类型 说明
interrupt-controller 空属性 标记该节点为中断控制器
#interrupt-cells u32 中断描述符占用的 32 位单元数
interrupt-parent phandle 指定设备连接的中断控制器
interrupts u32 数组 中断描述符,格式由 #interrupt-cells 定义

典型中断描述符格式:

  • ARM GIC (通用中断控制器): #interrupt-cells = <3>,格式为 <中断类型 中断号 触发方式>
  • 简单中断控制器: #interrupt-cells = <2>,格式为 <中断号 触发方式>
  • GPIO 中断: #interrupt-cells = <2>,格式为 <GPIO 引脚 触发方式>

示例:ARM GIC 中断映射

gic: interrupt-controller@f0001000 {
    compatible = "arm,gic-400";
    reg = <0xf0001000 0x1000>;
    interrupt-controller;
    #interrupt-cells = <3>;
};

uart@10000000 {
    compatible = "ns16550";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 33 4>;
    // 0: SPI 类型, 33: 中断号, 4: 高电平触发
};

4.3 中断映射规则与流程

中断映射遵循“设备 → 中断控制器 → 中断控制器 → … → CPU”的链式结构。每个设备通过 interrupt-parent 指向其直接中断控制器,中断控制器之间通过 interrupt-map 和 interrupt-map-mask 实现层级映射。

排查要点: 检查中断链中每个节点的 #interrupt-cells 是否与 interrupts 描述符的单元数一致。不一致会导致中断号解析错误。

中断映射核心步骤:

  1. 设备节点读取 interrupt-parent,找到直接中断控制器。
  2. 根据中断控制器的 #interrupt-cells 解析 interrupts 描述符。
  3. 如果中断控制器本身是子控制器,通过 interrupt-map 向上级控制器映射。
  4. 最终到达根中断控制器(如 GIC),生成 CPU 可响应的中断号。
设备树中断映射流程 设备节点 interrupt-parent = <&gic> 指向 GIC 中断控制器 #interrupt-cells = <3> 解析 interrupts 描述符 <0 33 4> → SPI, 33, 高电平 中断映射表 interrupt-map interrupt-map-mask 父控制器 phandle 父中断描述符 映射 CPU 可响应的中断号 硬件中断号 + 触发方式 生成

4.4 中断映射常见错误与排查

错误类型 现象 根因 解决方案
#interrupt-cells 不匹配 中断号解析为 0 或异常大值 设备 interrupts 单元数与控制器声明不一致 核对控制器 #interrupt-cells,调整 interrupts 数组
缺少 interrupt-parent 设备无法触发中断 设备未指定父中断控制器 添加 interrupt-parent = <&控制器节点>
中断号冲突 多个设备共享同一中断号导致误触发 设备树中中断号分配重叠 检查 SoC 手册,使用唯一中断号
触发方式错误 中断丢失或频繁误触发 interrupts 中触发类型与实际硬件不匹配 确认硬件触发方式(电平/边沿、高/低)
中断映射链断裂 中断无法传递到 CPU 中间控制器 interrupt-map 配置错误 逐级检查 interrupt-map 和 interrupt-map-mask

4.5 地址与中断联合排查案例

案例:PCIe 设备中断映射失败

某平台 PCIe 控制器下挂载的网卡无法触发中断。排查过程如下:

  1. 检查 PCIe 控制器节点:#address-cells = <3>,#size-cells = <2>,interrupt-map 存在。
  2. 检查 interrupt-map 中父中断描述符:父控制器为 GIC,#interrupt-cells = <3>,但映射表中只写了 2 个单元。
  3. 修正映射表,补全中断类型字段(如 <0 33 4>),中断恢复正常。
排查口诀: 地址看 cells,中断看 parent;映射逐级查,单元数要对齐。

4.6 地址与中断映射的协同关系

地址编码与中断映射在设备树中并非孤立存在。许多总线(如 PCIe、AHB)同时定义了地址域和中断域。驱动移植时需同时验证:

  • 地址域: reg 属性中的地址与长度是否与父节点 #address-cells / #size-cells 一致。
  • 中断域: interrupts 描述符是否与中断控制器 #interrupt-cells 匹配。
  • 跨域映射: 某些总线节点(如 PCIe)使用 ranges 进行地址转换,同时使用 interrupt-map 进行中断转换,两者需独立验证。
地址域与中断域协同关系 总线节点 (PCIe/AHB) 地址域 #address-cells / #size-cells reg → 地址 + 长度 ranges → 地址转换 中断域 interrupt-parent interrupts → 中断描述符 interrupt-map → 中断映射 定义地址编码 定义中断映射

4.7 本章小结

  • 地址编码: 父节点 #address-cells 和 #size-cells 决定子节点 reg 的解析方式,必须严格对齐。
  • 中断控制器: interrupt-controller 声明控制器身份,#interrupt-cells 定义描述符格式。
  • 中断映射: 通过 interrupt-parent 和 interrupt-map 形成链式结构,逐级解析到 CPU。
  • 排查重点: 单元数匹配、中断号唯一性、触发方式正确性、映射链完整性。
工程建议: 在设备树调试阶段,使用 dtc -I dtb -O dts 反编译 DTB,人工检查每个节点的 reg 和 interrupts 字段是否符合父节点声明。对于复杂总线(如 PCIe),建议使用 lspci -vvv 配合设备树交叉验证中断路由。