2. 设备树基础概念:DTS、DTC、DTB、DTBO 的定义与关系
设备树(Device Tree)是一种描述硬件资源的数据结构,它通过树形节点将 CPU、内存、外设、中断控制器、GPIO 控制器等硬件信息传递给操作系统。在嵌入式 Linux 中,设备树是驱动与硬件解耦的核心机制。本章将系统讲解 DTS、DTC、DTB、DTBO 的定义、关系以及设备树在 Linux 启动流程中的位置。
2.1 核心概念定义
| 术语 | 全称 | 定义 | 文件格式 |
|---|---|---|---|
| DTS | Device Tree Source | 设备树源文件,由开发者编写的可读文本文件,描述硬件拓扑和配置 | .dts |
| DTSI | Device Tree Source Include | 设备树包含文件,用于存放公共硬件定义,通过 #include 引入 DTS |
.dtsi |
| DTC | Device Tree Compiler | 设备树编译器,将 DTS/DTSI 编译为 DTB 二进制文件 | 可执行程序 |
| DTB | Device Tree Blob | 设备树二进制文件,Bootloader 传递给内核的硬件描述数据 | .dtb |
| DTBO | Device Tree Blob Overlay | 设备树叠加层,用于在运行时动态修改或扩展 DTB 中的节点 | .dtbo |
💡 关键理解: DTS 是“源代码”,DTC 是“编译器”,DTB 是“编译产物”,DTBO 是“运行时补丁”。四者构成完整的设备树工具链。
2.2 DTS、DTC、DTB、DTBO 的关系
设备树的整个生命周期从文本描述开始,经过编译生成二进制,最终由内核解析使用。DTBO 作为叠加层,可以在不修改原始 DTB 的前提下动态调整硬件配置。
📌 编译与使用要点:
- DTS + DTSI → DTC → DTB: 标准编译流程,生成内核启动时使用的硬件描述。
- DTS + DTC → DTBO: 使用
-@选项编译生成叠加层,支持运行时动态加载。 - DTB + DTBO → 内核: Bootloader 将 DTB 加载到内存,内核在启动早期解析;DTBO 在系统运行后通过
configfs或of_overlay_apply()动态应用。
2.3 设备树在 Linux 启动流程中的位置
设备树在 Linux 启动过程中扮演“硬件信息传递者”的角色。从 Bootloader 加载 DTB 到内核完成设备初始化,设备树贯穿整个启动早期阶段。
⚠️ 常见误区: 设备树不是设备驱动,它只是硬件描述数据。驱动仍然需要编写
probe() 函数来读取设备树节点中的属性(如 reg、interrupts、compatible),并初始化硬件。设备树只解决“硬件在哪里、怎么配置”的问题,不解决“硬件怎么工作”的问题。
2.4 设备树启动流程关键步骤详解
- Bootloader 加载 DTB: U-Boot 等 Bootloader 从存储介质(eMMC、NAND、网络)读取 DTB 文件,放入内存指定地址。
- 寄存器传递 DTB 地址: ARM32 使用
r2寄存器,ARM64 使用x0寄存器传递 DTB 物理地址给内核。 - 早期汇编保存指针: 内核入口代码(如
head.S)保存 DTB 指针,供后续 C 代码使用。 - setup_arch() 解析: 在
start_kernel()中调用架构相关的setup_arch(),初步解析 DTB 获取内存大小、CPU 数量等基本信息。 - unflatten_device_tree(): 将扁平的 DTB 二进制数据展开为内核可遍历的
device_node树结构。 - of_platform_populate(): 遍历设备树节点,为匹配
compatible属性的节点创建platform_device。 - 驱动 probe(): 总线驱动匹配后调用驱动的
probe()函数,驱动通过of_*API 读取设备树属性完成硬件初始化。
🔧 实战提示: 在调试设备树问题时,可以通过内核启动参数
earlycon 和 dtoc 打印 DTB 解析日志。使用 fdtdump 工具可以反编译 DTB 为可读格式:fdtdump board.dtb。
2.5 设备树与驱动适配的桥梁
| 设备树节点属性 | 驱动中对应的 API | 作用 |
|---|---|---|
compatible = "vendor,device" |
of_match_table |
驱动与设备匹配的关键标识 |
reg = <0x100000 0x1000> |
of_iomap() / platform_get_resource() |
获取设备基地址和内存范围 |
interrupts = <0 31 4> |
irq_of_parse_and_map() |
解析中断号并映射 |
clocks = <&clk 1> |
of_clk_get() |
获取时钟句柄 |
gpios = <&gpio0 5 0> |
of_get_named_gpio() |
获取 GPIO 编号 |
💡 学习路径建议: 理解设备树概念后,下一步应掌握 DTS 语法(节点、属性、标签、引用)和常用属性(
compatible、reg、interrupts、status),这是编写正确设备树文件的基础。