设备树驱动开发从设备节点到probe函数调用 - 第1节
1.1 设备树的诞生背景:从“硬编码”到“动态描述”
在早期的 Linux 内核(2.6 及更早版本)中,板级细节(如内存基址、外设中断号、GPIO 映射)被直接硬编码在 arch/arm/mach-xxx 的 C 语言文件中。这种“代码即配置”的方式带来了严重的维护灾难:
- 代码膨胀:每增加一款开发板,就需要新增或修改大量 board-*.c 文件。
- 耦合度高:驱动代码与板级配置深度绑定,更换外设或调整引脚时,必须重新编译内核。
- 碎片化严重:ARM 架构下,不同厂商的 SoC 和开发板各自维护一套板级文件,导致主线内核难以统一支持。
核心痛点:内核无法在运行时感知硬件拓扑,所有硬件信息必须在编译前确定。
1.1.1 从 PowerPC 到 ARM 的迁移
设备树(Device Tree)最初源于 Open Firmware(IEEE 1275),被 PowerPC 架构成功用于描述硬件。2011 年左右,Linux ARM 社区决定全面引入设备树,以解决上述痛点。设备树将硬件描述从内核源码中剥离,变成一个独立的数据结构,由 bootloader 传递给内核。
1.2 设备树的核心作用
设备树本质上是一个描述硬件资源的树形数据结构,其核心作用可归纳为以下三点:
| 作用 | 说明 | 传统方式(无设备树) | 设备树方式 |
|---|---|---|---|
| 硬件描述 | 描述 CPU、内存、外设、中断控制器、GPIO 控制器等硬件节点及其属性 | 硬编码在 board-*.c 中 | 以 .dts 文本文件描述 |
| 驱动与硬件解耦 | 驱动通过 compatible 字符串匹配设备节点,不再依赖板级代码 | 驱动直接调用板级 API | 驱动只关心 compatible 匹配 |
| 多板支持 | 同一内核镜像通过更换 .dtb 文件即可支持不同开发板 | 需编译多个内核镜像 | 只需更换 .dtb 文件 |
关键理解:设备树是“硬件配置数据”,而不是“驱动代码”。它告诉内核“有什么硬件”,但不负责“如何驱动”。
1.3 设备树的基本概念与术语
在深入设备树驱动开发之前,必须掌握以下核心术语:
- DTS (Device Tree Source):设备树源文件,文本格式,后缀为 .dts 或 .dtsi。
- DTC (Device Tree Compiler):将 .dts 编译为 .dtb 的工具。
- DTB (Device Tree Blob):二进制格式的设备树,由 bootloader 加载并传递给内核。
- Node (节点):描述一个硬件设备或总线,格式为
node-name@unit-address。 - Property (属性):节点的具体参数,如
reg、interrupts、compatible。 - compatible:最重要的属性,用于驱动匹配,格式为
"厂商,设备型号"。
1.3.1 设备树结构示意图
下图展示了一个简化的设备树结构,包含根节点、CPU 节点、内存节点和两个外设节点:
1.4 为什么 Linux 内核引入设备树?
引入设备树并非偶然,而是嵌入式 Linux 生态发展的必然选择。以下从四个维度分析其必要性:
| 维度 | 引入设备树之前 | 引入设备树之后 |
|---|---|---|
| 内核维护 | 每个 ARM 开发板都需要维护 board-*.c 文件,主线内核难以统一 | 板级代码大幅减少,硬件描述统一为 .dts 文件 |
| 驱动开发 | 驱动需要包含板级头文件,调用平台特定的 API | 驱动只通过设备树 API 获取资源,与板级无关 |
| 启动流程 | bootloader 传递 ATAG 列表,信息有限且不灵活 | bootloader 传递 .dtb 文件,信息丰富且可扩展 |
| 多板兼容 | 一个内核镜像只能支持一种开发板 | 一个内核镜像通过更换 .dtb 支持多种开发板 |
注意:设备树并不能解决所有硬件差异问题。对于运行时动态变化的硬件(如 USB 热插拔),设备树只提供控制器描述,具体设备由总线枚举发现。
1.4.1 设备树与内核启动流程
设备树在内核启动过程中的位置至关重要,下图展示了从 bootloader 到驱动 probe 的完整链路:
1.5 设备树文件体系:.dts / .dtsi / .dtb
设备树源文件采用类似 C 语言的 #include 机制,形成层次化的文件体系:
- .dtsi (Device Tree Source Include):公共的 SoC 级描述文件,类似 C 头文件,可被多个 .dts 包含。
- .dts (Device Tree Source):具体的板级描述文件,包含或覆盖 .dtsi 中的节点。
- .dtb (Device Tree Blob):编译后的二进制文件,是内核实际使用的格式。
最佳实践:SoC 厂商提供 .dtsi 文件,板卡厂商提供 .dts 文件,通过
#include 和 &node 引用机制实现复用与覆盖。
1.5.1 一个简单的 .dts 示例
/dts-v1/;
#include "imx6ul.dtsi"
/ {
model = "Freescale i.MX6 UltraLite EVK Board";
compatible = "fsl,imx6ul-evk", "fsl,imx6ul";
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
};
上述代码中:
/dts-v1/;声明 DTS 版本。#include "imx6ul.dtsi"引入 SoC 级描述。/ { }定义根节点,包含 model、compatible、memory 等属性。&uart1 { }通过引用标签覆盖 .dtsi 中 uart1 节点的属性。
1.6 本章小结
设备树的引入是 Linux 内核嵌入式生态的一次重要重构。它将硬件描述从内核代码中分离,实现了驱动与板级配置的解耦,极大地提升了内核的可维护性和可移植性。理解设备树的起源、作用与基本概念,是掌握后续设备树驱动开发的基础。
| 关键概念 | 一句话总结 |
|---|---|
| 设备树 | 描述硬件资源的树形数据结构,独立于内核源码 |
| DTS/DTB | 源文件与二进制文件,通过 DTC 编译转换 |
| compatible | 驱动匹配的核心属性,格式为 "厂商,设备型号" |
| 节点与属性 | 节点描述设备,属性描述设备的参数 |