1. 设备树基础:DTS 的起源、作用与基本语法结构
1.1 设备树的起源与设计动机
在早期的嵌入式 Linux 系统中,板级硬件信息被硬编码在 arch/arm/ 或 arch/powerpc/ 目录下的 C 源文件中(如 board-xxx.c)。这种“硬编码”方式导致每次硬件变更都需要重新编译内核,且不同架构的板级代码难以复用。设备树(Device Tree)正是为了解决这一问题而引入的。
| 对比维度 | 传统板级文件(Board File) | 设备树(Device Tree) |
|---|---|---|
| 硬件描述方式 | C 语言结构体 + 宏定义 | 文本化 .dts 文件 |
| 内核耦合度 | 高,硬件变更需重新编译内核 | 低,仅需更新 .dtb 文件 |
| 跨架构支持 | 差,各架构独立维护 | 好,统一描述模型 |
| 启动参数传递 | ATAG 列表(ARM 特有) | 设备树二进制块(DTB) |
| 维护成本 | 高,每块板卡需独立文件 | 低,通过 .dtsi 复用 |
设备树最早源于 Open Firmware(IEEE 1275),后被 PowerPC 架构采用,最终在 ARM Linux 社区推动下成为嵌入式 Linux 的标准硬件描述语言。其核心思想是:将硬件拓扑、寄存器地址、中断号、时钟频率等静态信息从内核源码中剥离,以结构化文本形式独立描述。
关键理解: 设备树不是“驱动”,而是“硬件配置清单”。内核在启动时解析 DTB,根据其中的节点信息自动匹配并初始化对应的驱动。
1.2 设备树的作用与数据流
设备树在嵌入式 Linux 启动流程中扮演“硬件翻译官”的角色。从源码到内核使用的完整数据流如下:
- DTS(Device Tree Source):开发者编写的文本源文件,后缀为
.dts或.dtsi。 - DTC(Device Tree Compiler):将 DTS 编译为二进制 DTB 的工具,位于内核源码
scripts/dtc/目录。 - DTB(Device Tree Blob):二进制格式的硬件描述,由 Bootloader(如 U-Boot)加载到内存并传递给内核。
- 内核解析:内核启动早期调用
unflatten_device_tree()将 DTB 展开为树状结构,驱动通过of_*系列 API 读取节点信息。
数据流简图:
.dts / .dtsi → DTC 编译器 → .dtb → Bootloader → 内核解析 → 驱动匹配
1.3 基本语法结构:节点、属性与值
设备树源文件采用树形层级结构,每个硬件设备或总线被抽象为一个节点(Node),节点内包含若干属性(Property),属性拥有具体的值(Value)。
1.3.1 节点(Node)
节点是设备树的基本组成单元,使用 节点名@地址 的格式定义。根节点用 / 表示,所有设备节点都是根节点的子节点或后代节点。
/dts-v1/;
/ {
compatible = "vendor,board-name";
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x00>;
device_type = "cpu";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1GB RAM */
};
serial@10000000 {
compatible = "ns16550a";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
};
};
| 节点语法要素 | 说明 | 示例 |
|---|---|---|
| 节点名 | 描述设备类型,通常为通用名称 | cpu, memory, serial |
| 单元地址 | 可选,用于区分同名节点,通常为寄存器基址 | @0, @80000000 |
| 路径 | 从根节点到目标节点的全路径 | /soc/serial@10000000 |
| 标签 | 为节点起别名,方便引用 | uart0: serial@10000000 |
1.3.2 属性(Property)与值(Value)
属性描述节点的特征,每个属性有一个名称和一个或多个值。值的类型可以是 u32 整数、64 位整数、字符串、字节数组 或 phandle(指针引用)。
| 属性值格式 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 32 位整数 | <value> |
<0x10000000> |
单个或多个 u32 值 |
| 64 位整数 | <high low> |
<0x0 0x80000000> |
高 32 位 + 低 32 位 |
| 字符串 | "string" |
"ns16550a" |
双引号包裹 |
| 字节数组 | [hex bytes] |
[00 01 02 03] |
十六进制字节序列 |
| 字符串列表 | "str1", "str2" |
"ti,omap3-uart", "ns16550" |
逗号分隔 |
| 布尔值 | 仅属性名,无值 | status = "disabled"; |
存在即真,不存在即假 |
| phandle | &label |
&uart0 |
引用其他节点 |
常见错误: 忘记在
.dts 文件第一行声明 /dts-v1/; 会导致 DTC 编译失败。此外,reg 属性的 #address-cells 和 #size-cells 必须与父节点定义一致,否则地址解析会出错。
1.4 标准属性详解
设备树规范定义了一组标准属性,用于描述设备的核心资源。以下是最常用的几个:
compatible:驱动匹配的关键,格式为"厂商,设备型号",内核通过该属性查找对应的of_match_table。reg:描述设备地址空间,格式为<基址 长度>,长度由父节点的#address-cells和#size-cells决定。interrupts:描述中断号、触发类型,格式依赖interrupt-parent和#interrupt-cells。status:设备状态,常用值"okay"(启用)、"disabled"(禁用)。device_type:早期规范遗留属性,新设备树中已不推荐使用,仅cpu和memory节点保留。
1.5 设备树包含与复用机制
实际项目中,一个 SoC 可能被多个板卡使用。设备树通过 #include 和 .dtsi 文件实现复用:
.dtsi:设备树包含文件(Device Tree Source Include),通常存放 SoC 公共定义,如 CPU 集群、中断控制器、时钟等。.dts:板级文件,通过#include引入.dtsi,然后添加或覆盖板级特有的节点和属性。/include/:DTC 原生包含指令,功能与 C 语言的#include类似,但推荐使用#include以支持 C 预处理宏。
/* 文件:soc.dtsi — SoC 公共定义 */
/ {
compatible = "vendor,soc";
#address-cells = <2>;
#size-cells = <2>;
gic: interrupt-controller@10400000 {
compatible = "arm,gic-v3";
reg = <0x0 0x10400000 0x0 0x20000>;
interrupt-controller;
#interrupt-cells = <3>;
};
uart0: serial@10000000 {
compatible = "ns16550a";
reg = <0x0 0x10000000 0x0 0x1000>;
interrupts = <0 33 4>;
status = "disabled";
};
};
/* 文件:board.dts — 板级文件 */
/dts-v1/;
#include "soc.dtsi"
/ {
model = "Vendor Board Rev2";
compatible = "vendor,board-rev2", "vendor,board";
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x80000000>;
};
&uart0 {
status = "okay";
clock-frequency = <24000000>;
};
};
复用技巧: 使用
&标签 语法可以在板级文件中引用并覆盖 SoC 级节点中的属性,无需重写整个节点。这是设备树实现“增量修改”的核心机制。
1.6 设备树编译与反编译
DTC 工具是设备树开发者的日常工具,支持正向编译和反向反编译:
| 操作 | 命令 | 说明 |
|---|---|---|
| 编译 DTS → DTB | dtc -I dts -O dtb -o board.dtb board.dts |
将文本源文件编译为二进制 |
| 反编译 DTB → DTS | dtc -I dtb -O dts -o board.dts board.dtb |
将二进制还原为可读文本 |
| 查看 DTB 内容 | dtc -I dtb -O dts /sys/firmware/fdt |
查看内核当前使用的设备树 |
| 检查 DTB 有效性 | dtc -I dtb -O dtb board.dtb |
重新编译以验证格式 |
调试建议: 当内核启动时设备无法识别,首先通过
dtc -I dtb -O dts /sys/firmware/fdt 反编译当前 DTB,确认节点和属性是否与预期一致。这是设备树调试的第一步。