1. 设备树概述:设备树(DTS)的起源、作用与基本概念
1.1 为什么需要设备树?——从“硬编码”到“数据驱动”的演进
在早期的 Linux 内核中,每块开发板或 SoC 的硬件信息(内存基址、外设寄存器地址、中断号、时钟频率等)都被直接硬编码在 C 语言源文件中。这种“板级文件”方式导致内核源码充斥着大量平台相关的 arch/arm/mach-xxx 代码,每增加一块新板卡就需要修改内核源码并重新编译,严重阻碍了内核的通用性和维护效率。
痛点总结:
- 代码膨胀:同一份内核需要为每块板卡维护独立的板级文件。
- 耦合度高:硬件描述与内核驱动逻辑深度绑定,修改硬件必须修改内核。
- 启动僵化:Bootloader 无法向内核传递灵活的硬件拓扑信息。
设备树(Device Tree)正是为解决上述问题而生。它将硬件描述从内核源码中剥离,形成一份独立的数据结构文件(.dts),由 Bootloader 传递给内核。内核只需解析这份数据,即可动态获知硬件布局,实现“一套内核,多板通用”。
核心思想:设备树是“硬件配置数据”,而非“硬件驱动代码”。驱动代码仍然在内核中,但硬件拓扑、地址、中断等参数由设备树提供。
1.2 设备树的起源与历史
| 时间 | 事件 | 影响 |
|---|---|---|
| 2005 年 | PowerPC 架构率先引入设备树(Open Firmware 衍生) | 解决了 PowerPC 平台板级文件泛滥问题 |
| 2011 年 | ARM Linux 社区开始强制要求使用设备树 | Linux 3.x 系列内核逐步淘汰 ARM 板级文件 |
| 2012 年 | 设备树成为 ARM 架构的唯一硬件描述方式 | 所有新 ARM SoC 必须提供 .dts 文件 |
| 至今 | RISC-V、X86(ACPI 替代)、MIPS 等架构广泛采用 | 设备树成为嵌入式 Linux 的事实标准 |
设备树的规范源自 Open Firmware(IEEE 1275),但 Linux 社区对其进行了精简和扩展,形成了当前广泛使用的 Flattened Device Tree (FDT) 格式。
1.3 设备树的核心概念与文件体系
设备树涉及三种主要文件类型,它们之间的关系可以用下图清晰表达:
- .dts (Device Tree Source): 设备树源文件,文本格式,描述具体板卡的硬件树结构。
- .dtsi (Device Tree Source Include): 设备树头文件,通常存放 SoC 公共定义,被 .dts 通过
#include引用。 - .dtb (Device Tree Blob): 设备树二进制文件,由 DTC 编译 .dts 生成,是内核实际加载的格式。
- DTC (Device Tree Compiler): 设备树编译器,负责将 .dts 编译为 .dtb,也支持从 .dtb 反编译为 .dts。
1.4 设备树的基本语法结构
一个典型的 .dts 文件采用树形层次结构,根节点 / 下包含若干子节点,每个节点描述一个硬件设备或总线。以下是一个精简示例:
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "Embedded Linux Board v1.0";
compatible = "vendor,board-name";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
&uart1 {
status = "okay";
pinctrl-0 = <&pinctrl_uart1>;
};
};
| 语法元素 | 说明 | 示例 |
|---|---|---|
/dts-v1/; |
版本标识,必须位于文件开头 | 表示使用设备树 v1 规范 |
/ { }; |
根节点,所有设备树的起点 | 包含 model、compatible 等属性 |
node-name@unit-address |
节点名 + 单元地址(可选) | memory@80000000 |
property = value; |
属性赋值,描述设备参数 | reg = <0x80000000 0x20000000>; |
&label { }; |
引用已定义的节点并追加/覆盖属性 | &uart1 { status = "okay"; }; |
注意:设备树不是编程语言,它没有循环、条件分支或函数调用。它纯粹是描述性数据,所有逻辑处理由内核驱动完成。
1.5 设备树的作用与优势
设备树在嵌入式 Linux 启动流程中扮演着关键角色,其核心作用可以归纳为以下四点:
- 硬件拓扑描述:向内核传递 CPU、内存、总线、外设的层次关系。
- 资源参数配置:提供寄存器地址、中断号、DMA 通道、时钟频率等驱动所需参数。
- 板级差异隔离:同一 SoC 的不同板卡只需更换 .dts 文件,无需修改内核源码。
- Bootloader 与内核的契约:U-Boot 等 Bootloader 加载 DTB 到内存,并将地址通过寄存器传递给内核。
1.6 设备树与传统板级文件的对比
| 对比维度 | 传统板级文件(Board File) | 设备树(Device Tree) |
|---|---|---|
| 描述方式 | C 语言结构体 + 宏定义 | 树形文本文件(.dts) |
| 修改成本 | 需修改内核源码,重新编译内核 | 仅修改 .dts,重新编译 DTB,无需重编内核 |
| 可移植性 | 差,每块板卡需要独立内核镜像 | 强,同一内核镜像可适配多块板卡 |
| 维护性 | 内核源码膨胀,社区维护困难 | 硬件描述集中,社区协作高效 |
| 启动时传递 | Bootloader 通过 ATAG 传递少量参数 | Bootloader 传递完整 DTB 二进制 |
实践建议:在开发新板卡时,优先查找同系列 SoC 的参考 .dts 文件(位于内核源码
arch/arm/boot/dts/ 或 arch/arm64/boot/dts/),在其基础上修改,而不是从零编写。