1. U-Boot与设备树概述
大家好,我是你们这门课的老朋友。做嵌入式底层开发十几年了,U-Boot 和设备树几乎是我每天都要打交道的两样东西。今天咱们先不急着写代码,把基础概念理清楚,后面调试起来才不慌。
1.1 U-Boot 启动流程——从上电到加载内核
U-Boot 的全称是 "Universal Boot Loader",说白了就是个引导程序。它的任务很简单:把操作系统内核从存储介质里读出来,放到内存里,然后跳过去执行。
但这个过程其实挺讲究的。我画了一张流程图,你看一眼就明白了:
嗯,这里要注意:现在的 SoC 基本都带 BootROM,它先初始化最基本的硬件(比如时钟、DDR 控制器),然后从 SD 卡、eMMC 或 NAND Flash 里把 SPL 读进来。SPL 再加载完整的 U-Boot。U-Boot 跑起来之后,解析设备树,最后把控制权交给内核。
1.2 设备树(DTS/DTB)基本概念
设备树,英文叫 Device Tree,是一种描述硬件信息的数据结构。它用树形结构把 CPU、内存、外设、中断、GPIO 这些东西组织起来。
你想想看,以前没有设备树的时候,Linux 内核里塞满了各种板级文件(board-xxx.c)。每换一款板子,就得重新编译内核。后来大家觉得这样太累了,于是设备树就诞生了。
设备树涉及三个文件格式:
| 文件格式 | 全称 | 说明 |
|---|---|---|
| .dts | Device Tree Source | 文本格式的源文件,人类可读写 |
| .dtsi | Device Tree Source Include | 公共头文件,类似 C 语言的 .h |
| .dtb | Device Tree Blob | 编译后的二进制文件,U-Boot 和内核直接使用 |
举个例子,一个简单的 .dts 文件长这样:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
cpu@0 {
compatible = "arm,cortex-a8";
clock-frequency = <1000000000>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
这段代码描述了一颗 Cortex-A8 的 CPU、512MB 内存、一个 UART 控制器。每个节点都有 compatible 属性,内核通过它来匹配对应的驱动。
1.3 为什么需要设备树——我踩过的坑
说实话,我刚入行那会儿用的是旧版 U-Boot,没有设备树。每次移植都要改一堆 board 文件,改完还得重新编译整个 U-Boot。有一次我改错了一个 GPIO 的宏定义,整块板子的 LCD 不亮了,查了两天才找到问题。
设备树解决了三个核心痛点:
- 硬件描述标准化——所有外设信息统一写在 .dts 里,不用散落在各处。
- 内核与板级解耦——同一份内核镜像可以跑在不同板子上,只要换 .dtb 就行。
- U-Boot 与内核共享——U-Boot 解析 DTB 后,把硬件信息直接传给内核,避免重复配置。
举个例子,我在做 i.MX6Q 的板子移植时,客户要求支持两种不同容量的 DDR3 颗粒。以前我得准备两套 U-Boot 镜像,现在只需要在 .dts 里改 memory 节点的 reg 属性,重新编译 .dtb 就搞定了。
1.4 设备树在 U-Boot 中的角色
U-Boot 使用设备树的方式和内核不太一样。U-Boot 本身也会读取 DTB,用来初始化一些外设(比如网卡、MMC、显示控制器)。然后 U-Boot 会把 DTB 的地址通过寄存器或参数传递给内核。
我整理了一个对比表,帮你理解两者的差异:
| 对比项 | U-Boot 中的设备树 | Linux 内核中的设备树 |
|---|---|---|
| 解析时机 | 启动阶段,初始化外设前 | 内核初始化过程中 |
| 主要用途 | 配置时钟、DDR、存储、网卡 | 驱动匹配、资源分配、中断映射 |
| 修改方式 | 可在 U-Boot 命令行用 fdt 命令动态修改 | 通常只读,需重新编译 .dtb |
| 传递方式 | 通过 bootm 命令将 DTB 地址传给内核 | 从 U-Boot 指定的地址读取 |
嗯,这里有个细节:U-Boot 的 fdt 命令非常强大。我调试时经常用它来临时修改 DTB 里的参数,比如关掉某个没用的外设,或者调整内存大小。这样不用重新编译,效率高很多。
fdt list / 可以查看整个设备树的结构。如果某个节点没显示出来,说明 DTB 里根本没包含它,或者编译时被裁剪掉了。
好了,这一章的内容就到这里。设备树的概念虽然简单,但它是整个 U-Boot 移植和调试的基石。后面我们会一步步深入,从 DTS 的编写、编译,到 U-Boot 中设备树的解析和修改,再到实际项目中的调试技巧。你先把这些基础概念消化掉,后面学起来会轻松很多。