4、设备树基础概念:设备树源文件(DTS)、编译(DTC)、扁平设备树(FDT)与绑定文档
设备树(Device Tree)是一种描述硬件资源的数据结构,用于将非可枚举的硬件信息从内核源码中剥离,传递给操作系统。在 ARM、RISC-V 等架构中,设备树已取代了传统的 board-*.c 硬编码方式。本节将深入剖析设备树的四个核心概念:DTS(源文件)、DTC(编译器)、FDT(扁平二进制)与绑定文档(Binding Documentation)。
核心认知:设备树是“硬件描述语言”,DTS 是源码,DTC 是编译器,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"。
4.2 DTC 编译器
DTC(Device Tree Compiler)将人类可读的 DTS 源文件编译为内核可解析的扁平设备树二进制(FDT)。DTC 也支持反编译(DTB → DTS)和叠加层编译。
4.2.1 编译流程
- 预处理:通过 C 预处理器(cpp)展开
#include和宏定义。 - 语法解析:DTC 解析 DTS 的树形结构,构建内部节点树。
- 属性合并:处理
/delete-node/、/delete-property/等覆盖操作。 - 扁平化编码:将树形结构转换为 FDT 格式的二进制块。
- 输出:生成
.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一致。
4.5 本章小结
- DTS 是设备树的文本源文件,采用树形结构描述硬件,
.dtsi用于 SoC 级复用。 - DTC 将 DTS 编译为 FDT 二进制,支持预处理、覆盖、叠加层等高级功能。
- FDT 是内核运行时解析的紧凑二进制格式,Bootloader 通过寄存器传递 FDT 地址。
- 绑定文档 是设备树的“语法书”,YAML 格式支持自动化校验,确保 DTS 与驱动的一致性。
- 四者形成完整链路:绑定文档约束 DTS → DTC 编译为 FDT → 内核解析 FDT → 驱动匹配设备。
实战建议:在开发新板级支持时,先编写绑定文档(YAML),再编写 DTS,最后实现驱动。这种“文档驱动”的开发模式可大幅减少调试时间,并确保设备树与内核主线兼容。