1. 设备树基础:起源、作用与基本概念
1.1 为什么Linux内核需要设备树?
在早期的Linux内核(2.6及更早版本)中,嵌入式平台的硬件描述信息被直接硬编码在板级文件(Board File)中。每个开发板都需要在 arch/arm/mach-xxx 目录下维护一个庞大的 board-xxx.c 文件,其中包含了内存地址、中断号、GPIO映射、时钟配置等硬件细节。这种模式带来了严重的维护灾难:
- 代码冗余:同一款SoC的不同开发板,板级文件大量重复。
- 耦合过高:驱动代码与硬件地址、中断号等具体参数深度绑定。
- 社区分裂:每提交一个新板子,就需要合并大量板级代码,导致内核主线难以统一。
- 启动困难:引导加载程序(Bootloader)无法向内核传递灵活的硬件拓扑信息。
核心痛点:内核需要一种与架构无关、与驱动解耦、可动态传递的硬件描述机制。设备树(Device Tree)正是为解决这一问题而诞生。
1.2 设备树的起源
设备树的概念源自Open Firmware(IEEE 1275标准),最初用于PowerPC架构。2011年左右,Linux ARM社区开始大规模引入设备树,并在Linux 3.x内核中逐步取代了传统的板级文件。如今,设备树已成为ARM、RISC-V、MIPS、x86(部分场景)等主流架构的标准硬件描述方式。
| 阶段 | 时间 | 描述 |
|---|---|---|
| 起源 | 1990s | Open Firmware标准定义设备树概念,用于PowerPC/Mac |
| 引入ARM | 2011 | Linux ARM社区开始将设备树作为板级描述的替代方案 |
| 成熟 | 2013+ | Linux 3.10+ 内核中,设备树成为ARM平台的标准配置方式 |
| 扩展 | 2016+ | RISC-V、MIPS、x86(ACPI替代)等架构广泛采用 |
1.3 设备树的基本概念
设备树是一种树形数据结构,用于描述硬件平台中所有可寻址的设备及其连接关系。它由三个核心部分组成:
- 设备树源文件(.dts):人类可读的文本文件,使用设备树语法描述硬件。
- 设备树源包含文件(.dtsi):公共的SoC级描述,可被多个.dts文件包含。
- 设备树二进制文件(.dtb):由DTC(Device Tree Compiler)编译生成的二进制文件,供内核解析。
类比理解:.dts 相当于C语言源文件,.dtsi 相当于头文件,.dtb 相当于编译后的二进制文件。Bootloader负责将.dtb加载到内存,并将地址传递给内核。
1.4 设备树的核心作用
| 作用 | 说明 | 传统方式 |
|---|---|---|
| 硬件描述标准化 | 统一描述CPU、内存、外设、中断、GPIO、时钟等 | 板级文件硬编码 |
| 驱动与硬件解耦 | 驱动通过设备树节点名称和属性匹配硬件,不依赖具体地址 | 驱动直接引用板级宏定义 |
| 多板支持 | 同一内核镜像通过更换.dtb即可支持不同开发板 | 需重新编译内核 |
| Bootloader传递 | U-Boot等引导程序可动态修改.dtb(如内存大小、MAC地址) | 内核启动参数有限 |
| 社区协作 | SoC厂商只需提供.dtsi,板卡厂商补充.dts,减少重复工作 | 每个板子独立维护 |
1.5 设备树的基本结构
设备树采用单根树状结构,根节点为 /,所有设备节点都是根节点的子节点或后代节点。每个节点包含:
- 节点名称:格式为
name@unit-address,如uart@fe001000。 - 属性(Properties):键值对,描述设备的配置参数,如
reg、interrupts、compatible。 - 子节点:嵌套的子设备,如I2C总线下的从设备。
// 一个简化的设备树示例
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB内存
};
chosen {
stdout-path = &uart0;
};
ocp {
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
clock-frequency = <48000000>;
status = "okay";
};
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
interrupts = <70>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
};
};
};
};
1.6 设备树与内核probe流程的关系
设备树是驱动probe流程的起点。内核启动时,会解析.dtb并构建一个内存中的设备树(struct device_node树)。当驱动注册时,内核通过 compatible 属性进行匹配,匹配成功后调用驱动的probe函数。这一流程将在后续章节中详细展开。
1.7 设备树文件编译与使用流程
- 编写.dts/.dtsi文件:根据硬件原理图和数据手册,描述设备节点和属性。
- 编译为.dtb:使用DTC工具执行
dtc -I dts -O dtb -o output.dtb input.dts。 - 打包或烧录:将.dtb放入boot分区,或与内核镜像打包在一起。
- Bootloader加载:U-Boot等引导程序从存储介质读取.dtb到内存。
- 内核解析:内核启动早期,从Bootloader传递的地址解析.dtb,构建设备树内存结构。
- 驱动匹配与probe:驱动注册时,内核遍历设备树节点,通过compatible属性匹配,调用probe。
常见误区:设备树不是编程语言,不包含执行逻辑。它只是静态数据,所有硬件行为(如时序、初始化顺序)仍需由驱动代码实现。设备树只描述“有什么设备”和“设备怎么连接”,不描述“设备怎么工作”。