1. 设备树(DTS)基础概念
1.1 什么是设备树
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树状节点来记录系统中所有可访问的设备、总线、中断控制器、时钟、GPIO等硬件信息。在嵌入式Linux系统中,设备树充当了硬件描述与内核驱动之间的“桥梁”。
核心定义:设备树是一个由节点(node)和属性(property)组成的树形结构,每个节点代表一个硬件设备或总线,属性则描述该设备的寄存器地址、中断号、时钟频率等具体参数。
设备树文件通常以文本形式编写,经过编译后生成二进制格式供内核解析。其核心思想是将硬件描述从内核源码中剥离出来,使得同一份内核镜像可以支持多种硬件平台。
1.2 为什么需要设备树
在设备树出现之前,嵌入式Linux内核通过大量的板级文件(board file)来描述硬件,每个开发板都需要在 arch/arm/mach-xxx 目录下编写数百行C代码来注册平台设备、配置资源。这种方式存在严重问题:
| 对比维度 | 传统板级文件方式 | 设备树方式 |
|---|---|---|
| 硬件描述位置 | 内核源码 arch/arm/mach-xxx |
独立的 .dts 文件 |
| 内核与硬件耦合度 | 高耦合,每块板子需修改内核 | 低耦合,同一内核镜像适配多板 |
| 维护成本 | 高,板级文件数量爆炸 | 低,设备树文件独立维护 |
| 启动时硬件发现 | 静态注册,编译时确定 | 动态解析,运行时加载 |
| 社区支持 | ARM社区已弃用 | 主流架构强制使用 |
关键驱动因素:2011年Linux 3.0之后,ARM社区正式要求所有新平台必须使用设备树。PowerPC、RISC-V、ARM64等架构从一开始就基于设备树设计。设备树使得SoC厂商只需提供一份
.dtsi文件,板级厂商在此基础上扩展即可。
设备树带来的核心收益包括:
- 硬件描述标准化:使用统一的语法和绑定规范,不同厂商的硬件描述风格一致。
- 内核镜像通用化:一个内核镜像可以启动多个硬件平台,Bootloader负责选择正确的设备树。
- 驱动与硬件解耦:驱动通过设备树中的
compatible属性匹配硬件,无需硬编码地址。 - 外设配置灵活化:修改管脚复用、时钟频率等参数只需改设备树,无需重新编译内核。
1.3 设备树文件类型
设备树涉及三种主要文件类型,它们在开发流程中扮演不同角色:
| 文件类型 | 扩展名 | 格式 | 用途 |
|---|---|---|---|
| 设备树源文件 | .dts |
文本 | 板级设备树描述,包含完整硬件信息 |
| 设备树头文件 | .dtsi |
文本 | SoC公共描述,可被多个 .dts 包含 |
| 设备树二进制 | .dtb |
二进制 | 编译后的设备树,供内核和Bootloader使用 |
文件关系说明:
.dtsi类似于C语言的.h头文件,通过#include指令被.dts引用。- 一个
.dts文件通常包含一个.dtsi(SoC级),再叠加板级特有的外设和配置。 .dtb是最终交付给Bootloader和内核的二进制产物。
注意:设备树头文件
.dtsi 使用C预处理器,因此支持 #define、#include 等宏定义。内核源码中通常将GPIO号、时钟ID等常量定义在 include/dt-bindings/ 目录下,供 .dtsi 引用。
1.4 设备树编译流程
从 .dts 源文件到 .dtb 二进制文件,需要经过预处理和编译两个阶段。整个流程由设备树编译器 dtc(Device Tree Compiler) 完成。
1.4.1 编译命令
最常用的编译命令如下:
# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o board.dtb board.dts
# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o board_decompiled.dts board.dtb
# 在内核源码树中使用 make 编译
make dtbs
make board.dtb
1.4.2 编译流程详解
完整的设备树编译流程包含以下步骤:
- 预处理阶段:C预处理器(cpp)处理
#include、#define、#ifdef等指令,将.dtsi文件和绑定头文件展开到.dts中。 - 语法解析阶段:dtc解析预处理后的文本,构建内部节点树结构,检查语法正确性。
- 标签解析阶段:处理
&node引用标签,将覆盖/追加的属性合并到对应节点。 - 二进制编码阶段:将节点树编码为扁平化设备树(FDT)二进制格式,生成
.dtb文件。
关键工具链:内核构建系统使用
scripts/dtc/dtc 作为编译器,同时依赖 cpp 进行预处理。在 arch/<arch>/boot/dts/ 目录下执行 make dtbs 即可批量编译所有设备树。
1.5 设备树语法速览
一个典型的 .dts 文件结构如下,包含根节点、子节点、属性和标签引用:
/dts-v1/;
#include "soc.dtsi"
/ {
model = "MyBoard v1.0";
compatible = "vendor,myboard", "vendor,soc";
chosen {
stdout-path = &uart0;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
&uart0 {
status = "okay";
clock-frequency = <115200>;
};
| 语法元素 | 说明 | 示例 |
|---|---|---|
/dts-v1/; |
版本声明,必须位于文件开头 | /dts-v1/; |
/ { }; |
根节点定义 | / { model = "..."; }; |
node@addr |
节点名+地址单元 | uart@fe000000 |
compatible |
驱动匹配字符串 | "vendor,device" |
reg |
寄存器地址和大小 | <0xfe000000 0x1000> |
&label |
标签引用,追加属性 | &uart0 { status = "okay"; }; |
学习建议:初学者可以从内核源码中的
arch/arm/boot/dts/ 或 arch/arm64/boot/dts/ 目录下找到实际设备的 .dts 文件进行阅读。推荐从 vexpress-v2p-ca9.dts 或 sun50i-a64.dtsi 等经典文件入手。