4、设备树基础概念:设备树源文件(DTS)、编译(DTC)、扁平设备树(FDT)与绑定文档

设备树(Device Tree)是一种描述硬件资源的数据结构,用于将非可枚举的硬件信息从内核源码中剥离,传递给操作系统。在 ARM、RISC-V 等架构中,设备树已取代了传统的 board-*.c 硬编码方式。本节将深入剖析设备树的四个核心概念:DTS(源文件)、DTC(编译器)、FDT(扁平二进制)与绑定文档(Binding Documentation)。

核心认知:设备树是“硬件描述语言”,DTS 是源码,DTC 是编译器,FDT 是运行时二进制,绑定文档是“语法说明书”。四者共同构成了从硬件设计到内核驱动的完整描述链路。
设备树四要素关系图 设备树四要素关系图 DTS 源文件 *.dts / *.dtsi DTC 编译器 dtc 工具 FDT 扁平树 *.dtb / *.dtbo 绑定文档 YAML / 文本规范 流程说明 ① 编写 DTS 源码 ② DTC 编译为 DTB ③ Bootloader 加载 FDT ④ 内核解析 FDT ⑤ 绑定文档约束格式 ⑥ 驱动匹配设备节点

4.1 设备树源文件(DTS)

DTS(Device Tree Source)是设备树的文本表示形式,采用树形结构描述硬件拓扑。每个硬件设备对应一个节点(node),节点包含属性(property),属性以键值对形式定义设备参数。

4.1.1 DTS 文件类型

扩展名 类型 说明
.dts 主源文件 描述单板级硬件,通常对应一个具体硬件平台
.dtsi 包含文件 SoC 级通用描述,通过 #include 被主文件引用
.dtbo 叠加层 运行时动态加载的设备树片段,用于 FPGA 或外设热插拔

4.1.2 DTS 基本语法

/dts-v1/;

#include "soc.dtsi"

/ {
    model = "Embedded Device Board v1.0";
    compatible = "vendor,board-v1", "vendor,board";

    chosen {
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
    };

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

    uart0: serial@1c090000 {
        compatible = "vendor,uart-ip", "ns16550a";
        reg = <0x1c090000 0x1000>;
        interrupts = <0 37 4>;
        clock-frequency = <24000000>;
        status = "okay";
    };
};
关键语法点:
  • /dts-v1/; — 版本声明,必须位于文件首行。
  • #include — 引入 .dtsi 头文件,支持 C 预处理器宏。
  • label: node-name@unit-address — 标签(label)用于引用,节点名+地址唯一标识。
  • compatible — 最重要的属性,用于驱动匹配,格式为 "厂商,设备型号"。
  • reg — 地址/长度对,描述 MMIO 或总线地址空间。
  • status — 设备状态:"okay"、"disabled"、"reserved"。
DTS 节点树结构 DTS 节点树结构示例 / (根节点) chosen memory@80000000 uart0: serial@1c090000 i2c@1c0a0000 eeprom@50 属性:bootargs

4.2 DTC 编译器

DTC(Device Tree Compiler)将人类可读的 DTS 源文件编译为内核可解析的扁平设备树二进制(FDT)。DTC 也支持反编译(DTB → DTS)和叠加层编译。

4.2.1 编译流程

  1. 预处理:通过 C 预处理器(cpp)展开 #include 和宏定义。
  2. 语法解析:DTC 解析 DTS 的树形结构,构建内部节点树。
  3. 属性合并:处理 /delete-node/、/delete-property/ 等覆盖操作。
  4. 扁平化编码:将树形结构转换为 FDT 格式的二进制块。
  5. 输出:生成 .dtb 文件,或通过 -@ 选项生成带符号的叠加层。

4.2.2 常用命令

# 编译 DTS → DTB
dtc -I dts -O dtb -o board.dtb board.dts

# 反编译 DTB → DTS
dtc -I dtb -O dts -o board.dts board.dtb

# 编译叠加层(带符号)
dtc -I dts -O dtb -@ -o overlay.dtbo overlay.dts

# 检查 DTB 格式
dtc -I dtb -O dts board.dtb | head -50
常见陷阱:
  • DTC 版本差异可能导致 interrupts 或 gpio 宏展开错误,建议使用内核源码中的 scripts/dtc 版本。
  • 叠加层编译必须加 -@ 选项,否则内核无法动态加载。
  • 反编译后的 DTS 可能丢失标签(label)和注释,仅用于调试参考。

4.3 扁平设备树(FDT)

FDT(Flattened Device Tree)是设备树的运行时二进制格式,由 DTC 编译生成。FDT 是一个紧凑的、可线性遍历的二进制块,包含节点、属性、字符串表和内存保留映射。

4.3.1 FDT 二进制布局

偏移 字段 大小 说明
0x00 magic 4 字节 固定值 0xd00dfeed(大端)
0x04 totalsize 4 字节 整个 FDT 块的总字节数
0x08 off_dt_struct 4 字节 结构块(节点/属性)偏移
0x0C off_dt_strings 4 字节 字符串块偏移
0x10 off_mem_rsvmap 4 字节 内存保留映射偏移
0x14 version 4 字节 FDT 版本号(当前为 17)
0x18 last_comp_version 4 字节 向后兼容的最低版本
0x1C boot_cpuid_phys 4 字节 引导 CPU 的物理 ID
0x20 size_dt_strings 4 字节 字符串块大小
0x24 size_dt_struct 4 字节 结构块大小

4.3.2 FDT 在内核中的传递

Bootloader(如 U-Boot)将 FDT 二进制块加载到内存中,通过特定寄存器(ARM 的 r2、RISC-V 的 a1)将 FDT 地址传递给内核。内核启动早期调用 unflatten_device_tree() 将 FDT 展开为内存中的 struct device_node 树。

// 内核 FDT 解析核心函数(位于 drivers/of/fdt.c)
void __init unflatten_device_tree(void)
{
    // 从 boot_params 获取 FDT 基地址
    void *fdt = initial_boot_params;
    if (!fdt)
        return;

    // 校验 FDT 魔数
    if (fdt_check_header(fdt) != 0)
        panic("Invalid device tree blob");

    // 展开为 device_node 树
    of_fdt_unflatten_tree(fdt, NULL, &of_root);
}
调试技巧:在 Linux 内核启动参数中添加 dtdebug 或 of_devlink 可打印 FDT 解析日志。使用 fdtdump 工具可查看 DTB 的原始二进制布局。

4.4 绑定文档(Binding Documentation)

绑定文档是设备树的规范说明书,定义了每个 compatible 字符串对应的节点必须包含哪些属性、可选属性、属性类型和取值范围。绑定文档是驱动开发者与设备树编写者之间的契约。

4.4.1 绑定文档的演进

阶段 格式 位置 特点
传统 纯文本 .txt Documentation/devicetree/bindings/ 自由格式,易读但难以自动化校验
当前 YAML 模式 .yaml Documentation/devicetree/bindings/ 结构化,支持 dt-schema 工具自动校验

4.4.2 YAML 绑定文档示例

# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: "http://devicetree.org/schemas/serial/vendor,uart.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"

title: Vendor UART Controller

maintainers:
  - Your Name <your@email.com>

properties:
  compatible:
    items:
      - enum:
          - vendor,uart-ip
          - vendor,uart-ip-v2
      - const: ns16550a

  reg:
    maxItems: 1

  interrupts:
    maxItems: 1

  clock-frequency:
    description: UART 参考时钟频率,单位 Hz
    $ref: /schemas/types.yaml#/definitions/uint32

  status:
    description: 设备状态
    enum: [okay, disabled, reserved]

required:
  - compatible
  - reg
  - interrupts

additionalProperties: false
绑定文档的作用:
  • 格式约束:确保 DTS 中的属性名、类型、取值范围符合规范。
  • 自动校验:使用 dt_binding_check 和 dtbs_check 在编译时发现错误。
  • 文档即代码:YAML 绑定文档可直接生成内核文档(通过 scripts/device-tree-doc.py)。
  • 驱动匹配依据:驱动中的 of_match_table 必须与绑定文档中的 compatible 一致。
绑定文档与驱动匹配流程 绑定文档 → DTS → 驱动匹配流程 绑定文档 YAML 规范 定义属性约束 DTS 源文件 编写设备节点 遵循绑定规范 内核驱动 of_match_table 匹配 compatible dt_binding_check → dtbs_check → 驱动 probe 时 of_match_node() 绑定文档是“契约”:DTS 必须遵守绑定规范,驱动必须声明匹配的 compatible 字符串 三者一致时,设备才能被正确识别和驱动

4.5 本章小结

  • DTS 是设备树的文本源文件,采用树形结构描述硬件,.dtsi 用于 SoC 级复用。
  • DTC 将 DTS 编译为 FDT 二进制,支持预处理、覆盖、叠加层等高级功能。
  • FDT 是内核运行时解析的紧凑二进制格式,Bootloader 通过寄存器传递 FDT 地址。
  • 绑定文档 是设备树的“语法书”,YAML 格式支持自动化校验,确保 DTS 与驱动的一致性。
  • 四者形成完整链路:绑定文档约束 DTS → DTC 编译为 FDT → 内核解析 FDT → 驱动匹配设备。
实战建议:在开发新板级支持时,先编写绑定文档(YAML),再编写 DTS,最后实现驱动。这种“文档驱动”的开发模式可大幅减少调试时间,并确保设备树与内核主线兼容。