2. DTS、DTC、DTB、DTBO 文件格式详解:源文件、编译器、二进制文件、叠加层的关系与编译流程

在嵌入式 Linux 开发中,设备树(Device Tree)是描述硬件资源的标准化数据结构。理解其文件格式与编译流程,是编写正确设备树的基础。本章将深入剖析 DTS、DTC、DTB、DTBO 四种文件类型的定义、关系及编译过程。

2.1 文件格式概述

设备树体系包含四种核心文件类型,它们分别对应源代码、编译器、二进制镜像和运行时叠加层。下表总结了每种文件的核心特征:

文件类型 全称 本质 可读性 用途
DTS Device Tree Source 文本源文件 人类可读 编写硬件描述
DTC Device Tree Compiler 编译工具 不可读 将 DTS 编译为 DTB
DTB Device Tree Blob 二进制文件 机器可读 Bootloader 传递给内核
DTBO Device Tree Blob Overlay 二进制叠加层 机器可读 运行时动态修改 DTB
关键概念: DTS 是开发者编写的“图纸”,DTC 是“施工队”,DTB 是“成品建筑”,DTBO 是“后期装修方案”。理解这个类比有助于快速掌握四者的关系。

2.2 文件格式详解

2.2.1 DTS(Device Tree Source)

DTS 是设备树的源文件,采用树形结构描述硬件。它使用大括号表示层级,分号结束语句,支持 #include 预处理指令。一个典型的 DTS 文件包含根节点、CPU 节点、内存节点、外设节点等。

// 示例:simple.dts
/dts-v1/;

/ {
    #address-cells = <1>;
    #size-cells = <1>;
    model = "Example Board";
    compatible = "vendor,example-board";

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  // 256MB
    };

    uart0: serial@10000000 {
        compatible = "vendor,uart";
        reg = <0x10000000 0x1000>;
        interrupts = <0 5 4>;
        status = "okay";
    };
};

2.2.2 DTC(Device Tree Compiler)

DTC 是设备树的编译工具,通常集成在 Linux 内核源码的 scripts/dtc/ 目录下。它负责将 DTS 源文件解析、检查并生成 DTB 二进制文件。DTC 支持以下关键功能:

  • 语法检查: 检测节点名称、属性格式、标签引用等错误。
  • 宏展开: 处理 #include 和 #define 预处理指令。
  • 叠加层生成: 通过 -@ 标志生成支持叠加的 DTB。
  • 反编译: 使用 -I dtb -O dts 将 DTB 还原为 DTS。

2.2.3 DTB(Device Tree Blob)

DTB 是设备树的二进制表示,由 DTC 编译生成。它采用扁平化结构,包含节点、属性、字符串表和内存保留映射。Bootloader(如 U-Boot)在启动内核前,将 DTB 加载到内存中,并将地址传递给内核。内核解析 DTB 后,根据其中的硬件描述初始化驱动。

2.2.4 DTBO(Device Tree Blob Overlay)

DTBO 是设备树叠加层文件,用于在运行时动态修改基础 DTB。它包含 __overlay__ 和 __fixups__ 等特殊节点,通过符号引用与基础 DTB 合并。典型应用场景包括:FPGA 动态加载、外设热插拔、多板卡兼容。

// 示例:overlay.dts
/dts-v1/;
/plugin/;

&uart0 {
    status = "disabled";
};

&gpio_leds {
    led-extra {
        label = "extra:green:user";
        gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
    };
};

2.3 编译流程与关系

从 DTS 到 DTB 的编译流程是一个多阶段过程,涉及预处理、解析、检查和二进制生成。叠加层(DTBO)的编译需要额外的 -@ 标志和符号表支持。下图展示了完整的编译流程与文件关系:

设备树文件编译流程与关系图 DTS 源文件 simple.dts DTSI 头文件 soc.dtsi / board.dtsi DTS 叠加层 overlay.dts CPP 预处理器 展开 #include / #define DTC 编译器 dtc -I dts -O dtb DTB 二进制文件 simple.dtb DTC(叠加模式) dtc -@ -I dts -O dtb DTBO 叠加层 overlay.dtbo 运行时合并:内核 / configfs 将 DTBO 与基础 DTB 动态合并,生成最终设备树

2.4 编译命令与实战

在实际开发中,DTC 编译器的使用非常频繁。以下列出最常用的编译命令及其参数含义:

命令 说明 典型参数
dtc -I dts -O dtb -o output.dtb input.dts 将 DTS 编译为 DTB -I 输入格式,-O 输出格式,-o 输出文件
dtc -I dtb -O dts -o output.dts input.dtb 反编译 DTB 为 DTS 用于调试或查看二进制内容
dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts 编译叠加层 DTBO -@ 生成符号表,支持叠加
dtc -I dts -O dtb -o output.dtb -@ input.dts 编译支持叠加的基础 DTB 基础 DTB 也需要 -@ 才能接收叠加
实战要点: 编译叠加层时,必须同时为基础 DTB 和叠加层 DTS 添加 -@ 标志。否则,运行时合并会因缺少符号表而失败。检查 DTB 是否支持叠加,可使用 fdtdump 工具查看是否包含 __symbols__ 节点。

2.5 运行时合并机制

DTBO 与基础 DTB 的合并发生在内核启动后或运行时,通过 configfs 文件系统实现。合并流程如下:

  1. 加载基础 DTB: Bootloader 将基础 DTB 传递给内核,内核解析并建立初始设备树。
  2. 加载 DTBO: 用户空间通过 configfs 将 DTBO 文件写入 /sys/kernel/config/device-tree/overlays/ 目录。
  3. 符号解析: 内核根据 DTBO 中的 __fixups__ 节点,在基础 DTB 的 __symbols__ 中查找对应符号。
  4. 节点合并: 将 DTBO 中的 __overlay__ 节点内容合并到基础 DTB 的对应位置。
  5. 驱动重绑定: 合并完成后,内核触发受影响设备的驱动重绑定,使新硬件生效。
注意事项: 叠加层合并是单向的,不能撤销。如果叠加层引用了不存在的符号,合并会失败并返回错误。建议在开发阶段使用 fdtoverlay 工具(dtc 工具集的一部分)在用户空间预验证叠加层。

2.6 常见问题与避坑指南

  • 编译错误: 检查 DTS 语法,特别是分号、大括号和标签引用。使用 dtc -I dts -O dtb -o /dev/null input.dts 进行语法检查。
  • 叠加层不生效: 确认基础 DTB 和 DTBO 都使用 -@ 编译。使用 fdtdump 检查 __symbols__ 和 __fixups__ 节点是否存在。
  • 内存地址冲突: 叠加层中的 reg 属性不能与基础 DTB 中的已有区域重叠,否则内核会拒绝合并。
  • 驱动未加载: 叠加层添加的新节点需要对应的驱动支持。确保驱动编译进内核或作为模块加载。
调试技巧: 使用 dtc -I dtb -O dts -o debug.dts merged.dtb 将合并后的 DTB 反编译为 DTS,可以直观地查看叠加层是否正确合并。这是排查叠加层问题的“杀手锏”。