1. 设备树基础:起源、作用与基本语法结构
1.1 设备树的起源与设计动机
在早期的 Linux 内核(如 2.6 及更早版本)中,板级硬件信息被硬编码在 arch/arm/mach-xxx 目录下的 C 语言源文件中。这种“硬编码”方式带来了严重的维护问题:每更换一个 SoC 或外设,都需要修改内核源码并重新编译。
硬编码时代的痛点:
- 内核源码与特定板卡高度耦合,无法实现“一次编译,多处运行”。
- 厂商发布新硬件时,必须提供完整的内核源码补丁。
- 同一 SoC 的不同开发板需要维护多份重复的板级文件。
为了解决上述问题,PowerPC 架构率先引入了 设备树(Device Tree) 技术,随后被 ARM、RISC-V 等架构广泛采纳。设备树的核心思想是:将硬件描述信息从内核源码中剥离出来,形成一个独立的数据结构,由 Bootloader(如 U-Boot)传递给内核,内核在启动时解析该结构来动态注册设备。
一句话理解: 设备树是描述硬件拓扑和配置的“硬件数据库”,它让内核不再需要硬编码板级信息,从而实现了内核与硬件的解耦。
1.2 设备树的作用与价值
| 作用维度 | 具体说明 | 对驱动开发的影响 |
|---|---|---|
| 硬件描述 | 描述 CPU、内存、总线、外设的地址、中断、时钟等资源 | 驱动通过设备树节点获取硬件资源,无需硬编码地址 |
| 平台解耦 | 同一内核镜像可运行在不同板卡上,只需更换设备树文件 | 驱动开发者只需关注设备树节点匹配规则 |
| 配置灵活 | 通过设备树属性可动态启用/禁用设备、调整参数 | 无需重新编译内核即可修改硬件配置 |
| 标准化接口 | 提供 compatible、reg、interrupts 等标准属性 |
驱动编写遵循统一规范,提高代码复用性 |
1.3 三种文件类型:.dts / .dtsi / .dtb
设备树涉及三种文件格式,理解它们的关系是学习设备树的第一步。
| 文件后缀 | 全称 | 格式 | 作用 |
|---|---|---|---|
.dts |
Device Tree Source | 文本文件(可读) | 设备树源文件,描述具体板卡的硬件信息 |
.dtsi |
Device Tree Source Include | 文本文件(可读) | 设备树头文件/包含文件,存放 SoC 公共定义 |
.dtb |
Device Tree Blob | 二进制文件(不可读) | 编译后的设备树,由 Bootloader 加载并传递给内核 |
文件关系流程:
- SoC 厂商提供
.dtsi文件,定义 SoC 内部所有外设的默认配置。 - 板卡厂商编写
.dts文件,通过#include引入.dtsi,并添加或覆盖板级特有的设备。 - 使用
dtc(Device Tree Compiler)工具将.dts编译为.dtb二进制文件。 - Bootloader 读取
.dtb并传递给内核,内核解析后完成设备注册。
1.4 设备树基本语法结构
设备树源文件采用树形层次结构,以 节点(Node) 和 属性(Property) 为核心元素。以下是一个典型的设备树片段:
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "EmbedFire i.MX6ULL Board";
compatible = "embedfire,imx6ull", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
};
1.5 核心语法要素解析
| 语法要素 | 示例 | 说明 |
|---|---|---|
| 根节点 | / { ... }; |
设备树的顶层节点,所有设备节点都是它的子节点 |
| 节点名称 | memory@80000000 |
格式为 名称@地址,地址用于区分同类型设备 |
| 属性 | compatible = "vendor,device" |
驱动匹配的关键属性,格式为 厂商,设备名 |
| reg 属性 | reg = <0x80000000 0x20000000> |
描述设备地址空间,格式为 <起始地址 长度> |
| 标签引用 | &uart1 { ... }; |
通过 & 引用已定义的节点,用于追加或覆盖属性 |
| 包含指令 | #include "imx6ull.dtsi" |
引入 SoC 公共定义文件,支持 C 语言预处理 |
常见错误:
- 忘记在
.dts文件开头写/dts-v1/;,导致 dtc 编译失败。 compatible属性值顺序错误:内核优先匹配列表中的第一个值,应将最具体的匹配项放在前面。reg属性中的地址和长度单位不匹配,导致内核无法正确解析地址空间。
1.6 设备树在驱动调试中的核心地位
对于驱动调试工程师而言,设备树是连接硬件与驱动的“桥梁”。当驱动无法正常工作时,首先应检查设备树节点是否正确描述了硬件资源:
- 匹配失败: 检查
compatible属性是否与驱动中的of_match_table一致。 - 资源冲突: 检查
reg和interrupts属性是否与其他设备重叠。 - 状态禁用: 检查
status属性是否为"okay",若为"disabled"则内核不会注册该设备。 - 引脚复用错误: 检查
pinctrl子节点中的引脚配置是否正确。
调试技巧: 在 Linux 内核启动时,通过
earlycon 和 initcall_debug 参数可以查看设备树的解析过程。使用 dtc -I dtb -O dts -o dump.dts board.dtb 命令可以将 .dtb 反编译为可读的 .dts 文件,方便对比实际加载的设备树与源文件是否一致。
1.7 本章小结
设备树是现代 Linux 驱动开发的基石。通过本章的学习,你应该掌握:
- 设备树解决了内核与板级硬件耦合的问题,是驱动调试的第一道关卡。
.dts是源文件,.dtsi是公共头文件,.dtb是编译后的二进制文件,三者通过dtc工具串联。- 设备树的基本语法包括根节点、子节点、属性(
compatible、reg、status等)和标签引用。 - 在驱动调试中,设备树是排查硬件资源冲突、匹配失败、引脚复用错误的首要检查点。