2. 设备树文件格式:DTS、DTSI、DTB、DTC编译器、设备树源文件语法基础

设备树在嵌入式系统中以多种文件形态存在,从人类可读的源文件到内核解析的二进制块,每一层都有明确的格式与工具链。理解这些文件格式及其转换关系,是掌握设备树加载与传递的前提。

2.1 四种核心文件形态

文件后缀 全称 格式性质 用途
.dts Device Tree Source 文本(ASCII/UTF-8) 顶层设备树源文件,描述单板硬件
.dtsi Device Tree Source Include 文本(ASCII/UTF-8) 公共设备树头文件,被 .dts 引用
.dtb Device Tree Blob 二进制(FDT格式) 编译后的设备树,Bootloader加载并传递给内核
.dtbo Device Tree Blob Overlay 二进制(FDT Overlay格式) 运行时叠加片段,用于动态修改设备树
关键理解: .dts 是“源文件”,.dtsi 是“头文件”,.dtb 是“可执行二进制”,.dtbo 是“运行时补丁”。开发阶段主要操作 .dts/.dtsi,部署阶段使用 .dtb。

2.2 DTC编译器:从文本到二进制

DTC(Device Tree Compiler)是设备树的官方编译器,负责将 .dts/.dtsi 编译为 .dtb,也可反编译 .dtb 回 .dts。其核心工作流程如下:

DTC 编译器工作流程 .dts 源文件 (单板描述) .dtsi 头文件 (公共定义) DTC 编译器 dtc -I dts -O dtb .dtb 二进制 (FDT格式) .dts 反编译 常用命令: 编译:dtc -I dts -O dtb -o board.dtb board.dts 反编译:dtc -I dtb -O dts -o board.dts board.dtb
编译命令示例:
# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o imx6ull-evk.dtb imx6ull-evk.dts

# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o imx6ull-evk-decompiled.dts imx6ull-evk.dtb

# 查看 .dtb 的详细信息
dtc -I dtb -O dts --sort imx6ull-evk.dtb | head -50

2.3 设备树源文件语法基础

.dts 文件采用树形结构描述硬件,核心语法元素包括:节点(node)、属性(property)、标签(label)、引用(phandle)、包含(include)。

2.3.1 节点与属性

每个设备树由根节点 / 开始,内部嵌套子节点。节点名称格式为 node-name@unit-address,其中 unit-address 通常对应设备寄存器基地址。

/dts-v1/;

/ {
    compatible = "vendor,board-name";    // 根节点兼容性
    #address-cells = <1>;               // 地址宽度:32位
    #size-cells = <1>;                  // 大小宽度:32位

    memory@80000000 {                    // 内存节点
        device_type = "memory";
        reg = <0x80000000 0x20000000>;   // 基地址 0x80000000,大小 512MB
    };

    chosen {                             // 内核启动参数节点
        bootargs = "console=ttymxc0,115200 root=/dev/mmcblk0p2";
    };

    soc {                                // 片上系统总线节点
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        uart1: serial@02020000 {         // UART控制器节点,带标签 uart1
            compatible = "vendor,uart-ip";
            reg = <0x02020000 0x4000>;
            interrupts = <0 58 4>;
            status = "okay";
        };
    };
};

2.3.2 属性数据类型

数据类型 语法示例 说明
字符串 compatible = "vendor,device"; 双引号包裹,用于匹配驱动
32位整数 reg = <0x02020000 0x4000>; 尖括号包裹,空格分隔
64位整数 reg = <0x00000000 0x80000000>; 用两个32位表示
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹,十六进制字节
布尔值 status = "disabled"; 通过字符串"okay"/"disabled"表示
字符串列表 compatible = "vendor,dev1", "vendor,dev2"; 逗号分隔多个字符串
混合类型 gpios = <&gpio1 3 0>; 可包含引用和整数

2.3.3 标签与引用(phandle)

标签(label)为节点提供别名,方便在其他位置引用。引用通过 &label 语法实现,编译器自动生成 phandle(指针句柄)。

// 定义标签
uart1: serial@02020000 {
    compatible = "vendor,uart-ip";
    reg = <0x02020000 0x4000>;
    pinctrl-0 = <&pinctrl_uart1>;   // 引用 pinctrl 节点
};

// 在根节点下引用并追加属性
&uart1 {
    status = "okay";
    pinctrl-names = "default";
};
常见错误: 标签名不能以数字开头,不能包含特殊字符(除下划线)。引用时 &label 必须与标签定义完全匹配,否则 DTC 编译报错。

2.3.4 包含机制(.dtsi)

.dtsi 文件用于存放 SoC 公共定义、IP 核描述、时钟配置等复用内容。.dts 通过 #include 指令引入 .dtsi,支持 C 预处理器宏定义。

// imx6ull.dtsi(SoC公共定义)
/ {
    soc {
        uart1: serial@02020000 {
            compatible = "vendor,uart-ip";
            reg = <0x02020000 0x4000>;
            interrupts = <0 58 4>;
            status = "disabled";   // 默认关闭
        };
    };
};

// imx6ull-evk.dts(单板文件)
#include "imx6ull.dtsi"           // 引入 SoC 定义
#include "imx6ull-evk.dtsi"       // 引入板级公共定义

/ {
    model = "i.MX6ULL EVK Board";
    compatible = "vendor,imx6ull-evk";
};

&uart1 {
    status = "okay";              // 在单板中使能 UART1
};

2.4 设备树语法结构图

设备树源文件语法结构 /(根节点) compatible #address-cells / #size-cells chosen memory@80000000 soc uart1: serial@02020000 compatible reg = <0x02020000 0x4000> interrupts = <0 58 4> status = "disabled" pinctrl-0 = <&pinctrl_uart1> 语法要点: 节点用花括号 {} 包裹,属性用分号 ; 结尾,标签用冒号 : 定义 引用用 &label,包含用 #include,注释用 /* */ 或 //

2.5 编译与反编译实战

以下展示一个完整的设备树编译流程,从源文件到二进制,再到反编译验证:

# 1. 查看源文件结构
$ cat myboard.dts
/dts-v1/;
#include "soc.dtsi"

/ {
    model = "MyBoard v1";
    compatible = "vendor,myboard";

    leds {
        compatible = "gpio-leds";
        led0: led@0 {
            label = "heartbeat";
            gpios = <&gpio1 3 0>;
            linux,default-trigger = "heartbeat";
        };
    };
};

# 2. 编译为 DTB
$ dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 3. 查看 DTB 文件大小
$ ls -lh myboard.dtb
-rw-r--r-- 1 user user 2.3K Mar 15 10:30 myboard.dtb

# 4. 反编译验证
$ dtc -I dtb -O dts -o myboard_reverse.dts myboard.dtb
$ diff myboard.dts myboard_reverse.dts   # 应无差异(除注释外)
调试技巧: 使用 dtc -I dtb -O dts --sort myboard.dtb 可以按字母顺序排序输出节点,便于对比不同版本的设备树差异。使用 fdtdump myboard.dtb 可以快速查看 DTB 的原始结构。

2.6 常见编译错误与排查

错误信息 原因 解决方案
ERROR: Input tree has errors 语法错误,如缺少分号或花括号 检查节点闭合、属性结尾分号
WARNING: label 'xxx' not found 引用了未定义的标签 确认标签定义位置,检查拼写
ERROR: phandle is not contiguous phandle 编号冲突或溢出 减少节点数量或使用 -@ 选项
ERROR: Missing property '#address-cells' 总线节点缺少地址描述属性 添加 #address-cells 和 #size-cells
FATAL: Cannot open input file 源文件路径错误或不存在 检查文件路径和权限
重要提示: 设备树编译错误通常会导致内核无法启动。在开发阶段,建议使用 dtc -I dts -O dtb -o /dev/null myboard.dts 进行语法检查,不生成实际文件,快速验证语法正确性。