2. 设备树语法入门:DTS文件结构、节点、属性、标签与包含文件

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点来记录CPU、内存、外设、中断、GPIO等硬件信息。在嵌入式Linux中,设备树源文件(.dts)经过编译生成设备树二进制文件(.dtb),由Bootloader传递给内核,内核据此完成设备驱动与硬件资源的绑定。

2.1 DTS文件整体结构

一个典型的.dts文件由以下部分组成:

  • 根节点:/,整个设备树的起点。
  • 节点(node):描述一个硬件设备或总线,可嵌套子节点。
  • 属性(property):键值对,描述节点的特性(地址、中断、时钟等)。
  • 标签(label):为节点或属性起别名,便于引用。
  • 包含文件(dtsi):通过#include引入公共硬件描述,实现复用。
💡 核心原则:设备树只描述硬件“有什么、在哪里”,不描述驱动“怎么做”。驱动通过匹配compatible属性来绑定节点。

2.2 节点(node)

节点是设备树的基本单元,每个节点代表一个硬件设备、总线或功能块。节点可以嵌套,形成树状层级。

/dts-v1/;

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

    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a8";
        reg = <0x0>;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    &i2c0 {
        status = "okay";
        eeprom@50 {
            compatible = "atmel,24c32";
            reg = <0x50>;
        };
    };
};
节点语法要素 说明 示例
节点名称 格式:name@unit-address,unit-address通常为寄存器基址 cpu@0、i2c@4802a000
子节点 在父节点花括号内定义,缩进表示层级 eeprom@50 是 i2c0 的子节点
节点引用 使用 &label 在根节点外追加或修改节点 &i2c0 { status = "okay"; };
特殊节点 / 根节点、aliases、chosen、memory chosen { bootargs = "..."; };

2.3 属性(property)

属性是节点的数据载体,驱动通过读取属性获取硬件参数。属性值支持多种数据类型。

属性类型 语法 示例 用途
字符串 "string" compatible = "ti,am335x-bone"; 设备型号、驱动匹配
32位整数 <value> reg = <0x4802a000 0x1000>; 地址、大小、中断号
64位整数 <0x00000000 0x80000000> reg = <0x00000000 0x80000000 0x00000000 0x20000000>; 64位地址空间
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; MAC地址、UUID
布尔值 仅属性名,无值 status = "disabled"; 或 status = "okay"; 设备启用/禁用
字符串列表 "str1", "str2" compatible = "ti,am335x-bone-black", "ti,am335x-bone"; 多级兼容匹配
🔑 关键属性:
compatible:驱动匹配的核心,格式为 "厂商,型号"。
reg:描述设备地址空间,格式为 <基址 长度>。
interrupts:描述中断号与触发类型。
status:"okay" 启用,"disabled" 禁用。

2.4 标签(label)

标签为节点或属性提供别名,便于在多个.dts/.dtsi文件中引用和修改节点。

// 定义标签
i2c0: i2c@4802a000 {
    compatible = "ti,omap4-i2c";
    reg = <0x4802a000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};

// 在其他文件中通过 & 引用标签,追加或修改属性
&i2c0 {
    status = "okay";
    clock-frequency = <400000>;
};
标签用法 语法 说明
定义标签 label: node-name@addr { ... }; 标签放在节点名称前,用冒号分隔
引用标签 &label { ... }; 在根节点外使用 & 引用,追加或覆盖属性
标签作用域 全局可见 跨文件引用,常用于 .dtsi 定义,.dts 定制

2.5 包含文件(dtsi)

.dtsi 文件是设备树的“头文件”,用于存放SoC公共硬件描述。一个SoC平台通常有一个主.dtsi,多个板级.dts通过#include引入并定制。

// am335x.dtsi (SoC公共定义)
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    compatible = "ti,am335x";
    ocp {
        uart0: serial@44e09000 {
            compatible = "ti,am335x-uart";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
            status = "disabled";
        };
    };
};

// am335x-boneblack.dts (板级定制)
/dts-v1/;
#include "am335x.dtsi"

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

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

&uart0 {
    status = "okay";
};
⚠️ 常见错误:
1. 忘记写 /dts-v1/; 版本声明,导致编译失败。
2. #include 路径错误,找不到 .dtsi 文件。
3. 在 .dtsi 中直接设置 status = "okay",导致所有板级都启用该设备,失去定制灵活性。
4. 标签名冲突,不同 .dtsi 中定义了同名标签。

2.6 设备树语法结构总览

设备树语法结构总览 / (根节点) model / compatible aliases / chosen memory / cpus soc / ocp 总线 uart0: serial@44e09000 i2c0: i2c@4802a000 gpio0: gpio@44e07000 compatible / reg / interrupts compatible / reg / #address-cells compatible / reg / gpio-controller eeprom@50 (子节点) &uart0 { status = "okay"; }; &i2c0 { clock-frequency = <400000>; }; #include "am335x.dtsi" → 复用SoC公共定义

2.7 设备树编译与反编译

操作 命令 说明
编译 dtc -I dts -O dtb -o output.dtb input.dts 将 .dts 编译为 .dtb
反编译 dtc -I dtb -O dts -o output.dts input.dtb 将 .dtb 反编译为 .dts,用于调试
检查 dtc -I dtb -O dts -o /dev/null input.dtb 仅检查语法,不输出文件
生成符号 dtc -I dts -O dtb -@ -o output.dtb input.dts -@ 生成符号节点,支持插件
📌 工程实践要点:
  • SoC公共定义放在 .dtsi,板级定制放在 .dts,通过 #include 组合。
  • 使用标签 + & 引用修改节点,避免重复定义整个节点。
  • 所有设备默认 status = "disabled",板级文件按需启用。
  • 编译时使用 -@ 选项生成符号,便于后续设备树插件(overlay)加载。