1. U-Boot与设备树概述
大家好,我是这次课程的主讲。咱们直接进入正题——设备树。这东西现在做嵌入式开发几乎天天见,但很多人只是会用,不清楚它到底解决了什么问题。今天这一章,我就带大家把设备树的来龙去脉理清楚。
1.1 设备树(DTS)的起源与作用
先说说设备树是怎么来的。早些年,Linux内核里充斥着大量的板级代码。什么意思呢?就是每换一块板子,内核源码里就要加一堆 arch/arm/mach-xxx 的文件,里面全是硬件寄存器地址、中断号、GPIO映射这些信息。我刚开始做嵌入式那会儿,光维护这些板级文件就够头疼的。
后来大家发现这条路走不下去了。尤其是ARM架构,芯片厂商多、板子型号杂,内核里塞了几百个板级文件,每次改动都牵一发动全身。于是设备树(Device Tree)被引入了。
设备树的核心思想:把硬件描述从内核源码中剥离出来,放到一个独立的数据结构里。内核启动时解析这个结构,就知道自己跑在什么硬件上了。
设备树的源文件是 .dts,编译后变成 .dtb(二进制格式)。U-Boot和内核都能读懂它。说白了,它就是一张硬件配置清单。
举个例子,一块板子上有串口、网卡、LED灯。传统做法是在内核里写死地址:
// 传统板级代码风格
#define UART_BASE 0x10000000
#define ETH_BASE 0x20000000
#define LED_PIN 12
换成设备树后,这些信息写在 .dts 文件里:
/ {
uart@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
};
eth@20000000 {
compatible = "dwc,eqos";
reg = <0x20000000 0x4000>;
};
led {
gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
};
};
这样内核源码就干净多了。换板子?换一个 .dts 文件就行,不用改内核。
1.2 U-Boot中设备树的生命周期
设备树在U-Boot里是怎么流转的?我画了一张图,大家一看就明白。
这张图展示了设备树在U-Boot里的完整旅程。我拆开来讲:
- 编译阶段:开发机上用
dtc工具把.dts编译成.dtb。这一步通常在U-Boot或内核的构建系统里自动完成。 - 烧录/存储:
.dtb文件被烧录到板子的存储介质上——可以是SPI Flash、eMMC、SD卡,甚至网络加载。位置很关键,U-Boot得知道去哪找它。 - U-Boot加载dtb:U-Boot启动后,根据环境变量或默认配置,从存储介质里把
.dtb读到内存中。我习惯把加载地址设成0x40000000附近,避开内核和ramdisk的区域。 - U-Boot解析设备树:U-Boot会解析dtb里的部分节点,比如内存大小、串口配置、启动参数。注意,U-Boot只解析它关心的部分,不会全量解析。
- 传递给Linux内核:最后一步,U-Boot把dtb在内存中的地址通过寄存器(比如ARM的
r2)传给内核。内核接管后,自己再完整解析一遍。
我的经验:曾经有个项目,U-Boot加载dtb后内核死活起不来。查了半天,发现是dtb加载地址和内核解压地址重叠了。所以加载地址一定要避开内核的加载区域,这个坑我踩过。
1.3 为什么U-Boot需要设备树
这个问题其实可以反过来问:没有设备树,U-Boot怎么知道硬件信息?
传统做法是硬编码。U-Boot源码里写死内存基址、外设地址、GPIO配置。换一块板子?重新编译U-Boot。这在小规模项目里还能忍,但到了产品线丰富的公司,维护成本就爆炸了。
设备树给U-Boot带来了三个核心好处:
| 好处 | 说明 | 实际影响 |
|---|---|---|
| 硬件与代码解耦 | 硬件描述从源码中分离 | 换板子只需换dtb,不用改U-Boot源码 |
| 统一接口 | U-Boot和内核共用同一份dtb | 减少维护两套配置的工作量 |
| 动态适配 | U-Boot启动时动态解析硬件信息 | 同一份U-Boot镜像可适配多种硬件变体 |
举个例子,我做过一个项目,同一款芯片有3种板型,内存大小不同、外设略有差异。没有设备树的话,我得维护3份U-Boot配置。有了设备树,U-Boot镜像完全一样,只在烧录时选不同的dtb文件。省事多了。
注意:U-Boot对设备树的解析是有限的。它不会像内核那样把所有节点都解析一遍。U-Boot只关心启动阶段必须知道的信息——内存布局、串口、存储设备、网络接口。其他外设的配置,U-Boot直接忽略,留给内核去处理。
你可能会问:U-Boot能不能完全不依赖设备树?理论上可以,但实际中几乎不可能。现在的SoC越来越复杂,一个芯片可能有几十个外设模块。不用设备树,U-Boot的板级文件会膨胀到难以维护。我见过一些老项目,一个 board.c 文件几千行,全是寄存器地址和初始化代码。后来迁移到设备树,代码量直接砍掉三分之二。
嗯,这里要注意一点:设备树不是万能的。它只是描述硬件,不负责驱动逻辑。U-Boot里该写的驱动代码还是得写,只是硬件参数不再硬编码了。
总结一下这一章的核心:
- 设备树把硬件描述从源码中抽离出来,用
.dts文件表达 - U-Boot负责加载、解析和传递dtb,但不修改它
- 设备树让U-Boot更灵活,同一份镜像可以适配多种硬件