1. 设备树基础概念
做嵌入式Linux开发,绕不开设备树。我记得刚入行那会儿,内核里还全是板级文件,一个平台文件动辄上千行。后来换了RK3288的板子,第一次接触设备树,说实话有点懵——这玩意儿到底干嘛用的?
今天咱们就把设备树这事儿聊透。从三个角度入手:它是什么、为什么需要它、它在内核里扮演什么角色。
1.1 什么是设备树
设备树,英文叫Device Tree,简称DT。说白了,它就是一个描述硬件的数据结构。用树形的方式,把CPU、内存、外设、总线这些硬件资源组织起来。
你想想看,一个嵌入式板子上有多少硬件?CPU核、中断控制器、GPIO、I2C、SPI、UART、DMA、时钟、电源管理……每个外设的地址、中断号、时钟频率都不一样。设备树就是把这些信息写成一种结构化的文本,内核启动时解析它,就知道板子上有什么硬件、怎么访问它们。
设备树的源文件是.dts,编译后生成.dtb(二进制格式),内核在启动早期就把.dtb加载到内存里解析。
核心要点:设备树 = 硬件描述语言,把板级硬件信息从内核源码中剥离出来,变成独立的数据文件。
1.2 为什么需要设备树
这个问题,得从Linux内核的历史说起。
早期Linux内核,每个ARM板子都在arch/arm/mach-xxx目录下有一个board-xxx.c文件。里面用C代码硬编码了所有硬件信息:寄存器地址、中断号、时钟配置……
这样做有什么问题?
- 代码冗余——同一款SoC,换个板子就要写一个新的board文件,大量重复代码
- 维护困难——内核主线要支持几百种板子,每个板子一个文件,维护成本爆炸
- 闭源厂商不友好——有些厂商不想公开硬件细节,但board文件必须开源
- 启动方式僵化——内核编译时就定死了硬件配置,换板子得重新编译内核
我在项目中遇到过最典型的情况:同一款RK3568芯片,A公司做成了AI盒子,B公司做成了工控板,C公司做成了NAS。外设连接完全不同。如果用老方式,得维护三个不同的board文件,内核镜像也得三个。有了设备树,只需要一份内核镜像,配三个不同的.dtb文件就行。
所以,设备树解决了几个核心痛点:
| 问题 | 老方式(board文件) | 设备树方式 |
|---|---|---|
| 硬件描述位置 | 嵌入内核C代码 | 独立.dts文件 |
| 更换板子 | 重新编译内核 | 更换.dtb文件 |
| 代码复用 | 差,每个板子一份 | 好,SoC级.dtsi共享 |
| 厂商保密 | 必须开源 | 可只发布.dtb |
| 内核主线维护 | 压力巨大 | 大幅降低 |
个人经验:我建议团队里每个硬件工程师都学会看设备树。调试新板子时,硬件改了一根中断引脚,软件这边改一行设备树就行,不用动内核代码。效率提升非常明显。
1.3 设备树在Linux内核中的角色
设备树在内核启动过程中,扮演的是"硬件配置官"的角色。它的生命周期大致是这样的:
- Bootloader加载——U-Boot从存储介质(eMMC、SD卡、网络)读取
.dtb文件,放到内存指定地址 - 内核早期解析——内核启动入口
start_kernel()之前,汇编代码就把.dtb地址传给内核。内核在setup_arch()中解析设备树,获取内存大小、CPU数量、中断控制器等基础信息 - 驱动匹配——内核遍历设备树中的每个节点,根据
compatible属性找到对应的驱动,然后调用驱动的probe()函数 - 运行时访问——驱动运行过程中,通过
of_系列API(如of_property_read_u32())读取设备树中的属性值
说白了,设备树就是内核和硬件之间的"翻译官"。内核不直接知道板子上有什么硬件,它问设备树,设备树告诉它。
下面这张图展示了设备树在内核启动流程中的位置:
避坑指南:我曾经在一个项目里,设备树中GPIO的gpio-hog配置写错了,导致某个引脚在驱动probe之前就被拉高了。外设上电时序不对,死活初始化失败。查了两天才发现是设备树的问题。所以,设备树不只是"配置文件",它直接影响硬件行为,写错了真的会炸。
1.4 设备树的核心语法速览
虽然本章是概念篇,但我还是想让你先看一眼设备树长什么样。这样后面讲具体语法时,你心里有个底。
一个最简单的设备树节点:
/dts-v1/;
/ {
model = "RK3568 EVB Board";
compatible = "rockchip,rk3568-evb", "rockchip,rk3568";
memory@a00000 {
device_type = "memory";
reg = <0x0 0xa00000 0x0 0x80000000>;
};
uart2: serial@fe660000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfe660000 0x0 0x100>;
interrupts = <0 116 4>;
clock-frequency = <24000000>;
status = "disabled";
};
};
这里有几个关键点:
/dts-v1/;——版本声明,现在都用v1/——根节点,整个设备树的起点model和compatible——描述板子型号和兼容性memory节点——告诉内核内存布局uart2节点——串口外设,有地址、中断、时钟、状态
嗯,这里要注意:status = "disabled"表示这个串口默认不启用。很多板子为了省电,会把不用的外设先关掉,需要时再通过设备树或内核动态打开。
个人习惯:我写设备树时,喜欢先把所有外设节点都列出来,状态设为disabled。然后根据实际硬件连接,逐个使能。这样不容易漏掉外设,也方便后期调试。
1.5 本章小结
设备树不是什么玄学。它就是一套硬件描述规范,让内核和硬件之间的沟通变得标准化、灵活化。
记住三个核心点:
- 是什么——描述硬件资源的树形数据结构
- 为什么需要——解决board文件泛滥、维护困难、灵活性差的问题
- 什么角色——内核启动时的"硬件配置官",驱动匹配和运行时查询的数据源