2. DTS语法入门

设备树源文件(.dts)是一种结构化的、人类可读的硬件描述语言。其核心语法围绕四个基本元素展开:节点(Node)、属性(Property)、标签(Label) 和 包含文件(#include)。掌握这些基础语法是编写和调试 DTS 文件的起点。

💡 学习提示: DTS 语法与 C 语言风格类似,使用大括号 { } 表示层级,分号 ; 结束语句。注释支持 // 和 /* */ 两种形式。

2.1 节点(Node):硬件拓扑的骨架

节点是设备树的基本组成单元,用于描述一个硬件设备或总线。每个节点代表一个设备,节点内部可以包含子节点,形成树状结构。根节点 / 是整个设备树的起点。

/ {
    // 根节点,代表整个 SoC 板级系统
    model = "Embedded Linux Board v1.0";
    compatible = "vendor,board-name";

    // 子节点:CPU 描述
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a7";
            reg = <0x0>;
        };
    };

    // 子节点:内存描述
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 起始地址 0x80000000,大小 512MB
    };

    // 子节点:串口设备
    uart0: serial@10000000 {
        compatible = "ns16550a";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
    };
};
📌 节点命名规则: 节点名格式为 node-name@unit-address。其中 node-name 是通用名称(如 serial、i2c),unit-address 是设备在总线上的地址(如寄存器基地址、I2C 从机地址),用于区分同一级别的多个相同类型节点。
设备树节点层级结构图 根节点 / cpus memory@80000000 uart0: serial@10000000 cpu@0 📐 节点层级说明: • 根节点 / 是设备树的唯一入口,所有设备节点都挂载在根下。 • cpus 节点包含子节点 cpu@0,描述具体 CPU 核心。 • uart0 节点前的 "uart0:" 是标签(Label),用于在 DTS 中引用该节点。

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

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

属性类型 语法格式 示例 说明
空属性 property-name; status = "disabled"; 布尔标志,存在即表示 true
字符串 property-name = "string"; compatible = "ns16550a"; 用双引号包裹
整数(32位) property-name = <value>; reg = <0x10000000 0x1000>; 尖括号内可包含多个 32 位值
64位整数 property-name = <high low>; reg = <0x0 0x80000000 0x0 0x20000000>; 用两个 32 位值拼接表示
字节数组 property-name = [hex bytes]; local-mac-address = [00 11 22 33 44 55]; 方括号内为十六进制字节
字符串列表 property-name = "str1", "str2"; compatible = "vendor,device", "generic-device"; 逗号分隔多个字符串
混合类型 property-name = <value>, "string", [bytes]; gpios = <&gpio0 17 0>; 不同类型值用逗号组合
⚠️ 关键属性:compatible 是设备与驱动匹配的核心属性。格式为 "厂商,设备型号",如 "ti,am335x-uart"。内核通过该字符串查找对应的 platform_driver。

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

标签是给节点起的一个别名,用于在 DTS 文件的其他位置引用该节点。标签定义在节点名前,使用冒号 : 分隔。引用时使用 &label 语法。

// 定义标签
uart0: serial@10000000 {
    compatible = "ns16550a";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;
};

// 在其他地方引用标签,追加或修改属性
&uart0 {
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};
🔗 标签的典型用途:
  • 板级覆盖: 在 .dtsi 文件中定义基础节点并打上标签,在 .dts 文件中通过 &label 引用并覆盖属性。
  • 引脚复用: 引用 &gpio0 或 &pinctrl 节点来配置 GPIO 或引脚功能。
  • 中断连接: 在 interrupt-parent 属性中引用中断控制器节点。

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

DTS 支持 C 预处理器指令 #include,用于引入头文件(.h)或其他设备树源文件(.dtsi)。这是实现设备树模块化和复用的关键机制。

// 包含 SoC 级设备树头文件,定义宏和通用节点
#include "am335x.dtsi"

// 包含板级特定配置
#include "am335x-bone-common.dtsi"

// 包含 GPIO 编号宏定义
#include <dt-bindings/gpio/gpio.h>

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    // 使用宏定义 GPIO 引脚
    led0 {
        compatible = "gpio-leds";
        pinctrl-0 = <&user_led_pins>;
        led@0 {
            label = "usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;  // GPIO1_21
            default-state = "on";
        };
    };
};
文件类型 扩展名 用途 示例
设备树源文件 .dts 板级主文件,包含根节点和板级特定配置 am335x-boneblack.dts
设备树包含文件 .dtsi SoC 级或通用板级定义,被 .dts 包含 am335x.dtsi
头文件 .h 定义宏常量(如 GPIO 编号、中断类型) dt-bindings/gpio/gpio.h
DTS 包含文件与编译流程 board.dts soc.dtsi gpio.h C 预处理器 (cpp) 展开 #include 替换宏定义 合并 .dtsi 编译后的 完整 .dtb 📂 编译流程:.dts + .dtsi + .h → C 预处理器 → 完整设备树 → dtc 编译器 → .dtb 二进制文件

2.5 实战要点与常见错误

🚨 常见语法错误:
  • 缺少分号: 每个属性赋值和节点结束大括号后必须有 ;。
  • 地址格式错误: reg 属性中的地址和长度必须与 #address-cells、#size-cells 匹配。
  • 标签重复: 同一 DTS 编译单元内标签名必须唯一。
  • 路径引用错误: 使用 &label 时确保标签已定义,且引用位置在标签作用域内。
🛠️ 调试技巧: 使用 dtc -I dts -O dtb -o output.dtb input.dts 编译 DTS 文件。如果语法有误,dtc 会给出具体的行号和错误描述。使用 dtc -I dtb -O dts -o output.dts input.dtb 可以反编译 .dtb 文件,用于验证编译结果。