2、ARM64设备树基础:设备树(DTB/DS)结构、节点与属性、地址编码与中断映射

2.1 设备树概述

设备树(Device Tree)是一种描述硬件配置的数据结构,用于将硬件信息从固件传递给操作系统。在ARM64平台中,设备树取代了传统x86平台的ACPI,成为主流的硬件描述机制。设备树的核心文件包括:

  • .dts(Device Tree Source):人类可读的文本源文件,使用类似C语言的语法描述硬件。
  • .dtb(Device Tree Blob):由DTS编译生成的二进制文件,是UEFI固件与操作系统内核之间传递硬件信息的标准格式。
  • .dtsi(Device Tree Source Include):可被多个DTS文件引用的公共头文件,用于复用SoC级硬件定义。
关键概念:在EFI Stub引导流程中,UEFI固件负责将DTB放置在约定的内存位置,并通过启动参数将DTB地址传递给Linux内核。内核在启动早期解析DTB,完成设备枚举与初始化。

2.2 设备树结构

设备树采用树形层次结构,从单一的根节点(/)开始,向下扩展出多个子节点,每个节点代表一个硬件设备或总线。下图展示了典型的ARM64设备树结构:

ARM64设备树层次结构图 根节点 "/" cpus { } memory { } soc { } cpu@0 { } cpu@1 { } serial@... { } i2c@... { } spi@... { } device@0 { } device@1 { } 设备树层次结构说明 根节点代表整个硬件平台,子节点按功能域(CPU、内存、SoC外设)组织。 节点名称中的 "@" 后跟地址或单元号,用于区分同类型设备。

2.3 节点与属性

2.3.1 节点定义

每个设备树节点由节点名称和一对花括号组成,节点内可以包含属性(Property)和子节点。节点名称的格式为 name@unit-address,其中 unit-address 通常对应设备的寄存器基地址或单元编号。

/dts-v1/;

/ {
    model = "ARM64 Reference Platform";
    compatible = "arm,arm64-ref-platform";
    #address-cells = <2>;
    #size-cells = <2>;

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a76";
            reg = <0x0>;
            enable-method = "psci";
        };

        cpu@1 {
            device_type = "cpu";
            compatible = "arm,cortex-a76";
            reg = <0x1>;
            enable-method = "psci";
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x80000000>;
    };
};

2.3.2 核心属性

属性名 类型 说明 示例
compatible 字符串列表 设备兼容性标识,内核据此匹配驱动 "arm,cortex-a76"
reg 整数数组 设备寄存器地址与长度,格式由 #address-cells 和 #size-cells 决定 <0x0 0x80000000 0x0 0x80000000>
device_type 字符串 设备类型,主要用于 CPU 和内存节点 "cpu"、"memory"
interrupt-parent phandle 指定该设备的中断控制器父节点 <&gic>
interrupts 整数数组 描述设备的中断号、触发类型等信息 <0 42 4>
clocks phandle列表 设备使用的时钟源引用 <&uart_clk>
status 字符串 设备状态:"okay" 启用,"disabled" 禁用 "okay"
调试要点:在EFI Stub引导过程中,如果设备树中的 compatible 属性与内核驱动不匹配,会导致设备无法被识别。建议使用 dtc -I dtb -O dts 反编译DTB,检查属性值是否正确。

2.4 地址编码

ARM64设备树使用 #address-cells 和 #size-cells 属性定义地址和长度的编码宽度。这两个属性在父节点中定义,影响所有子节点的 reg 属性解析。

2.4.1 地址编码规则

  • #address-cells:表示地址字段占用的32位单元数。ARM64平台通常设为 <2>,支持64位地址空间。
  • #size-cells:表示长度字段占用的32位单元数。ARM64平台通常设为 <2>,支持64位长度描述。
  • reg:格式为 <address1 length1 address2 length2 ...>,每个地址/长度对描述一段连续的寄存器或内存区域。
// 64位地址空间示例
soc {
    #address-cells = <2>;   // 地址用2个32位单元表示(64位)
    #size-cells = <2>;      // 长度用2个32位单元表示(64位)

    uart@1c090000 {
        compatible = "arm,pl011";
        reg = <0x0 0x1c090000 0x0 0x1000>;
        // 地址: 0x1c090000, 长度: 0x1000
        interrupts = <0 39 4>;
    };

    dma@1c0a0000 {
        compatible = "arm,pl330";
        reg = <0x0 0x1c0a0000 0x0 0x1000>;
        // 地址: 0x1c0a0000, 长度: 0x1000
    };
};

2.4.2 地址映射与ranges属性

ranges 属性用于定义父节点地址空间与子节点地址空间之间的映射关系。当子节点地址需要转换为父节点地址空间时,ranges 提供转换表。

ranges格式 说明
ranges;(空) 子节点地址与父节点地址空间一一对应,无需转换
ranges = <child-bus-address parent-bus-address length>; 将子节点地址 child-bus-address 映射到父节点地址 parent-bus-address,映射长度为 length
不定义 ranges 子节点地址空间与父节点隔离,设备驱动需通过特定方法访问
// ranges 映射示例
pcie@40000000 {
    #address-cells = <3>;
    #size-cells = <2>;
    ranges = <0x01000000 0x0 0x00000000  0x0 0x3eff0000  0x0 0x00010000
              0x02000000 0x0 0x40000000  0x0 0x40000000  0x0 0x40000000>;
    // 第1行: I/O空间映射
    // 第2行: 32位非预取内存映射
};

2.5 中断映射

ARM64设备树的中断映射涉及中断控制器、中断描述符和中断父节点三个核心概念。中断映射的准确性直接影响设备中断功能的正常使用。

2.5.1 中断控制器节点

中断控制器节点使用 interrupt-controller 属性标识自身为中断控制器,#interrupt-cells 属性定义中断描述符的字段数量。

// GICv3 中断控制器节点示例
gic: interrupt-controller@2f000000 {
    compatible = "arm,gic-v3";
    reg = <0x0 0x2f000000 0x0 0x10000>,
          <0x0 0x2f100000 0x0 0x200000>;
    interrupt-controller;
    #interrupt-cells = <3>;
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;
};

2.5.2 中断描述符格式

ARM64平台常用的中断描述符格式由 #interrupt-cells 决定。以GICv3为例,#interrupt-cells = <3>,三个字段的含义如下:

字段 位置 说明 取值
中断类型 第1个单元 0=SPI共享外设中断,1=PPI私有外设中断 0 或 1
中断号 第2个单元 SPI中断号范围32~1019,PPI中断号范围16~31 42、29
触发类型 第3个单元 1=上升沿触发,4=高电平触发,8=下降沿触发 1、4、8
// 设备中断映射示例
uart@1c090000 {
    compatible = "arm,pl011";
    reg = <0x0 0x1c090000 0x0 0x1000>;
    interrupt-parent = <&gic>;          // 引用GIC中断控制器
    interrupts = <0 39 4>;              // SPI中断39,高电平触发
};

eth@1c100000 {
    compatible = "realtek,rtl8169";
    reg = <0x0 0x1c100000 0x0 0x10000>;
    interrupt-parent = <&gic>;
    interrupts = <0 45 1>;              // SPI中断45,上升沿触发
};

2.5.3 中断映射流程

下图展示了设备树中断映射的完整流程,从设备节点到中断控制器再到CPU:

ARM64设备树中断映射流程图 设备树中断映射流程 设备节点 interrupt-parent = <&gic> 中断描述符 interrupts = <0 39 4> 中断控制器 #interrupt-cells = <3> 中断路由与分发 SPI/PPI 路由到目标PE CPU核心 (PE) IRQ 异常处理 中断映射关键检查点 1. interrupt-parent 必须指向有效的中断控制器节点 2. interrupts 字段数量必须匹配 #interrupt-cells

2.6 设备树编译与调试

2.6.1 常用工具

工具 功能 常用命令
dtc 设备树编译器/反编译器 dtc -I dtb -O dts -o output.dts input.dtb
fdtdump 以可读格式转储DTB内容 fdtdump input.dtb
fdtget 从DTB中读取指定节点/属性 fdtget input.dtb /cpus/cpu@0 compatible
fdtput 向DTB中写入指定节点/属性 fdtput input.dtb /chosen bootargs "console=ttyAMA0"

2.6.2 常见调试问题

  • DTB地址对齐错误:UEFI固件放置DTB时未按8字节对齐,导致内核解析失败。检查 device-tree-base 启动参数。
  • 中断映射不匹配:设备节点的 interrupts 字段数量与中断控制器的 #interrupt-cells 不一致。使用 dtc 反编译验证。
  • 地址编码错误:#address-cells 和 #size-cells 设置错误导致 reg 解析异常。ARM64平台应统一使用 <2>。
  • compatible属性缺失:设备节点缺少 compatible 属性,内核无法匹配驱动。检查设备树源文件。
EFI Stub调试提示:在UEFI Shell下,可以使用 dmem 命令查看DTB所在内存区域的内容,验证DTB魔数(0xD00DFEED)是否正确。内核启动参数中增加 earlycon 和 debug 可以输出设备树解析阶段的调试信息。

2.7 本章小结

设备树是ARM64平台硬件描述的核心机制,理解其结构、节点属性、地址编码和中断映射是进行EFI Stub引导调试的基础。关键要点包括:

  • 设备树采用树形层次结构,根节点下包含CPU、内存、SoC外设等子节点。
  • 节点通过 compatible、reg、interrupts 等属性描述硬件特征。
  • 地址编码由 #address-cells 和 #size-cells 控制,ARM64平台通常使用64位编码。
  • 中断映射通过 interrupt-parent 和 interrupts 属性建立设备到中断控制器的连接。
  • 使用 dtc、fdtdump 等工具可以高效调试设备树问题。