2. 设备树(DTS)基础语法:节点、属性、标签、包含文件

设备树源文件(.dts)是一种树形结构的文本描述语言,用于向操作系统(尤其是Linux内核)描述不可枚举的硬件信息。在ARM64启动过程中,Bootloader(如U-Boot)会将编译后的设备树二进制(.dtb)传递给内核,内核解析该结构以完成内存映射、外设驱动匹配等关键初始化步骤。

本章将系统讲解DTS的核心语法要素:节点、属性、标签与包含文件,并给出严格的书写规则。

2.1 节点(Node):设备树的骨架

设备树由一系列节点组成,每个节点代表一个硬件设备、总线或控制器。节点从根节点 / 开始,形成层级结构。

语法规则:节点名称格式为 node-name@unit-address。
node-name 描述设备类型(如 uart, i2c),unit-address 通常为设备在父总线上的起始地址(如寄存器基址),用于区分同名节点。
// 根节点
/ {
    // 子节点:CPU
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a72";
            reg = <0x0>;
        };
        cpu@1 {
            compatible = "arm,cortex-a72";
            reg = <0x1>;
        };
    };

    // 子节点:内存
    memory@80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x80000000>;  // 2GB RAM
    };

    // 子节点:串口
    uart@fe001000 {
        compatible = "ns16550a";
        reg = <0x0 0xfe001000 0x0 0x1000>;
        interrupts = <0x0 0x1a 0x4>;
    };
};
调试提示:在启动早期,内核通过 of_scan_flat_dt() 递归遍历节点树。如果节点名称中的 unit-address 与 reg 属性中的地址不匹配,可能导致设备无法被正确识别。
设备树节点层级结构 / (根节点) cpus memory@80000000 uart@fe001000 cpu@0 cpu@1 节点命名规则: • 通用格式:node-name@unit-address • node-name 应使用通用设备类型名(如 uart, i2c, spi, ethernet) • unit-address 必须与 reg 属性中的起始地址一致(十六进制小写) • 根节点固定为 "/",无 unit-address

2.2 属性(Property):描述设备的元数据

属性是附着在节点上的键值对,用于描述设备的特性、地址、中断、时钟等信息。属性值可以是空、整数、字符串、字节数组或它们的组合。

属性类型 语法示例 说明
空属性 status = "disabled"; 布尔标志,存在即表示真
32位整数 reg = <0xfe001000 0x1000>; 尖括号包裹,空格分隔
64位整数 reg = <0x0 0x80000000 0x0 0x80000000>; 高32位在前,低32位在后
字符串 compatible = "arm,cortex-a72"; 双引号包裹
字符串列表 compatible = "ns16550a", "ns16550"; 逗号分隔多个字符串
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹,十六进制字节
混合结构 interrupts = <0x0 0x1a 0x4>; 可包含整数、字符串组合
常见错误:在ARM64平台,#address-cells 和 #size-cells 必须正确设置。对于64位地址空间,通常设置为 <2> 和 <2>,否则 reg 属性解析会截断高32位地址,导致内存映射错误。

2.3 标签(Label):节点的引用别名

标签为节点提供一个全局唯一的别名,便于在其他位置引用该节点。标签以冒号结尾,放在节点名称之前。

// 定义标签
uart0: uart@fe001000 {
    compatible = "ns16550a";
    reg = <0x0 0xfe001000 0x0 0x1000>;
    interrupts = <0x0 0x1a 0x4>;
};

// 通过 & 引用标签,追加或修改属性
&uart0 {
    clock-frequency = <1843200>;
    status = "okay";
};
调试技巧:在启动日志中搜索 OF: fdt 或 of_node 可以查看内核解析节点时的标签匹配情况。如果标签引用失败,通常表现为 of_find_node_by_name() 返回 NULL。

2.4 包含文件(Include):模块化与复用

设备树支持使用 #include 预处理指令引入其他文件,实现硬件描述的模块化。常用包含文件包括:.dtsi(设备树包含文件)、.h(头文件,定义宏常量)。

// 主文件:board.dts
#include "soc.dtsi"          // 引入SoC公共定义
#include "board-common.dtsi" // 引入板级公共定义
#include "pinmux.h"          // 引入引脚复用宏

/ {
    model = "ARM64 Development Board v2";
    compatible = "arm,devboard-v2", "arm,devboard";

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

// 覆盖 soc.dtsi 中定义的节点
&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
};

包含文件搜索路径:编译器(dtc)默认在以下路径查找包含文件:

  • 当前文件所在目录
  • -i 选项指定的目录
  • 系统预置的 include/dt-bindings/ 目录

2.5 设备树书写规则总结

规则类别 具体要求 违反后果
节点命名 通用名@地址,地址与reg一致 设备无法匹配驱动
compatible 优先使用"厂商,型号"格式 驱动probe失败
reg 与地址单元 #address-cells/#size-cells 必须匹配 内存映射偏移错误
标签唯一性 全局不可重复 编译报错或引用混乱
包含文件 使用 #include,避免重复定义 节点覆盖顺序不可控
缩进与格式 使用Tab缩进,分号结尾 dtc编译警告或错误
DTS 编译与内核解析流程 board.dts (主源文件) soc.dtsi (SoC公共定义) pinmux.h (宏定义头文件) dtc 编译器 (Device Tree Compiler) board.dtb (扁平化设备树二进制) Linux 内核 (of_scan_flat_dt) 关键点:dtc 预处理阶段展开 #include,解析宏;内核启动时通过 FDT (Flattened Device Tree) 格式解析二进制数据。

2.6 实战:编写一个最小设备树

以下是一个可用于QEMU virt平台的ARM64最小设备树示例,包含必要的节点和属性:

/dts-v1/;

/ {
    model = "QEMU ARM64 Virtual Platform";
    compatible = "qemu,virt";

    #address-cells = <2>;
    #size-cells = <2>;

    // 内存节点
    memory@40000000 {
        device_type = "memory";
        reg = <0x0 0x40000000 0x0 0x40000000>;  // 1GB
    };

    // CPU 节点
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a57";
            reg = <0x0>;
            device_type = "cpu";
        };
    };

    // 系统级中断控制器
    gic: interrupt-controller@8000000 {
        compatible = "arm,gic-v3";
        reg = <0x0 0x08000000 0x0 0x10000>,
              <0x0 0x080a0000 0x0 0x20000>;
        interrupt-controller;
        #interrupt-cells = <3>;
    };

    // 串口
    uart0: uart@9000000 {
        compatible = "ns16550a";
        reg = <0x0 0x09000000 0x0 0x1000>;
        interrupts = <0x0 0x1 0x4>;
        interrupt-parent = <&gic>;
        clock-frequency = <3686400>;
    };

    chosen {
        bootargs = "console=ttyAMA0 earlycon=uart,mmio,0x09000000";
        stdout-path = &uart0;
    };
};
编译命令:
dtc -I dts -O dtb -o board.dtb board.dts
dtc -I dtb -O dts -o board_decompiled.dts board.dtb (反编译验证)