一、设备树基础概念
什么是设备树?
设备树,英文叫 Device Tree,是一套描述硬件的数据结构。说白了,它就是一个文本文件,用树形结构把板子上的所有硬件信息都列出来。
我刚开始接触嵌入式Linux时,觉得这东西挺玄乎的。后来做项目多了才明白——它就是一份硬件清单,告诉内核:你有哪些外设、它们挂在哪条总线上、地址是多少、中断号是什么。
举个例子,一个简单的设备树节点长这样:
uart0: serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000>;
interrupts = <0 45 4>;
clock-names = "uartclk", "apb_pclk";
clocks = <&uart_clk &apb_pclk>;
status = "okay";
};
这段代码描述了一个串口控制器。compatible 告诉内核用哪个驱动,reg 指定了寄存器地址范围,interrupts 是中断信息,clocks 是时钟配置。
核心要点:设备树是硬件描述语言,不是程序代码。它不执行任何逻辑,只是提供数据。
为什么需要设备树?
这个问题我经常被问到。在设备树出现之前,Linux内核是怎么处理硬件的?
答案是——硬编码。每个板子都在内核源码里写一堆 platform_device 结构体,填上地址、中断、时钟等信息。换一个板子就要改代码,重新编译内核。
你想想看,这有多痛苦?
- 维护成本高——每个板子都要维护一份内核源码
- 代码膨胀——arch/arm 目录下塞满了各种板级文件
- 升级困难——换一颗Flash芯片都要改内核
我记得2011年左右,我在做一个基于TI OMAP的平板项目。当时为了支持一个新版本的开发板,我得在 arch/arm/mach-omap2 里加几百行板级代码。改完还要重新编译整个内核,烧写验证,一个周期下来半天就没了。
设备树解决了这个问题。它把硬件描述从内核源码中剥离出来,变成独立的数据文件。内核启动时解析这个文件,动态构建设备模型。
我的经验:设备树让板级支持变得像配菜一样简单。换一个板子,只需要换一个 .dts 文件,内核不用动。这在产品快速迭代时特别有用。
设备树在Linux内核中的角色
设备树在内核里扮演什么角色?我习惯把它比作「硬件数据库」。
内核启动流程中,设备树的工作大致是这样的:
- Bootloader(如U-Boot)把设备树二进制文件(.dtb)加载到内存
- 内核启动时,从指定地址读取设备树
- 内核解析设备树,创建 platform_device、i2c_client、spi_device 等设备对象
- 驱动根据 compatible 匹配设备,完成初始化
下面这张图展示了设备树在内核中的核心位置:
从这张图可以看出,设备树是硬件和驱动之间的桥梁。它不参与具体的数据处理,但决定了驱动能看到什么硬件。
设备树文件体系
实际项目中,设备树文件通常分三层:
| 文件类型 | 后缀 | 作用 |
|---|---|---|
| 设备树源文件 | .dts | 描述具体板子的硬件,如树莓派3B、某款开发板 |
| 设备树头文件 | .dtsi | 描述SoC公共部分,如CPU核心、内存控制器、时钟等 |
| 设备树二进制 | .dtb | 编译后的二进制文件,Bootloader加载给内核 |
我习惯把 .dtsi 比作「芯片手册」,.dts 比作「板级原理图」。芯片厂商提供 .dtsi,板级开发者写 .dts 来引用和定制。
注意:设备树不是万能的。它只描述「静态」硬件信息。动态硬件(如USB热插拔)还是靠内核枚举机制。我曾经见过有人试图用设备树描述USB设备,结果搞得一团糟。
设备树与时钟、Reset的关系
这门课的核心是时钟与Reset控制。为什么要把它们放在设备树里讲?
因为时钟和Reset是SoC里最基础、最关键的资源。一个外设要正常工作,必须先拿到时钟,然后释放Reset。设备树就是描述这些依赖关系的载体。
举个例子,一个I2C控制器需要两个时钟:
i2c1: i2c@40012000 {
compatible = "st,stm32f4-i2c";
reg = <0x40012000 0x400>;
interrupts = <31>;
clocks = <&rcc 0 STM32F4_APB1_CLOCK(I2C1)>;
resets = <&rcc STM32F4_APB1_RESET(I2C1)>;
status = "disabled";
};
这里的 clocks 和 resets 属性,就是告诉内核:这个I2C控制器依赖哪个时钟源、哪个Reset信号。驱动初始化时,内核会按顺序处理这些依赖。
嗯,这里要注意:时钟和Reset的编号不是随便写的。它们必须和时钟控制器(如RCC)驱动里的定义一致。我在项目中就踩过这个坑——时钟号写错了,外设怎么都唤不醒,查了两天才发现是设备树里少了一个偏移量。
避坑指南:写设备树时钟属性时,一定要对照芯片参考手册的时钟树图。我曾经因为看错时钟分频比,导致UART波特率差了一倍。从那以后,我每次写时钟属性都会先画一遍时钟路径。
小结
设备树不是什么高深的技术。它就是一份硬件描述文件,让内核知道板子上有什么、怎么访问。它的出现,让嵌入式Linux开发从「改内核源码」变成了「改配置文件」。
后面几章,我们会深入设备树的语法、时钟框架、Reset框架。但不管讲得多深,记住一点:设备树的核心价值是解耦——把硬件描述和内核代码分开。
这个理念,贯穿了整个嵌入式Linux生态。