3. Linux内核与ARM平台适配
U-Boot 移植,说白了就是给一块“裸”的 ARM 板装上第一个能跑的程序。我做了这么多年驱动,每次拿到新板子,第一件事就是搞定 U-Boot。它不亮,后面 Linux 内核、根文件系统全是白搭。
这一章,咱们就一步步走通 U-Boot 移植的完整流程。从源码获取、目录结构分析,到配置编译,最后烧录验证。嗯,都是实战干货。
3.1 U-Boot 源码获取与目录结构
先说说源码。U-Boot 官方源码托管在 https://source.denx.de/u-boot/u-boot,也可以用 git 直接拉:
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout v2024.01 # 选一个稳定版本,我习惯用 LTS
我个人习惯用 v2024.01 这个版本。为什么?因为它在 ARMv7 和 ARMv8 上支持都比较全,而且社区活跃,遇到问题好找补丁。
拿到源码后,第一件事是看目录结构。别急着编译,先搞清楚每个目录是干嘛的。
| 目录/文件 | 作用 | 我的经验 |
|---|---|---|
arch/ |
架构相关代码,ARM、x86、RISC-V 等 | ARM 移植主要看 arch/arm/ |
board/ |
板级支持包,每个板子一个目录 | 移植时重点修改的地方 |
configs/ |
默认配置文件,make xxx_defconfig 用的 |
先找最接近的板子配置 |
include/configs/ |
板级头文件,定义内存、时钟、外设参数 | 这里改错了,板子直接不跑 |
drivers/ |
驱动框架,串口、网卡、MMC、NAND 等 | 一般不用动,除非外设很特殊 |
dts/ |
设备树源文件,新版本 U-Boot 用 | 越来越重要,建议学一下 |
tools/ |
烧录工具、打包脚本 | mkimage 就在这里 |
configs/ 里搜一下芯片型号,比如 ls configs/ | grep stm32mp。找到最接近的配置,然后复制一份改。这样比从头写快得多。
3.2 配置与编译:针对特定 ARM 板
假设我们手上的板子是 STM32MP157D-EV1(意法半导体的一款双核 Cortex-A7 板子)。配置编译的步骤其实很固定:
- 设置交叉编译工具链
- 执行默认配置
- 自定义配置(可选)
- 编译
先看第一步。交叉编译链我推荐用 Linaro 的 gcc-arm-none-eabi 或者 ARM 官方的 arm-linux-gnueabihf-。设置环境变量:
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
然后执行默认配置:
make stm32mp15_defconfig
这一步会从 configs/ 目录下读取 stm32mp15_defconfig,生成 .config 文件。如果你想微调,可以用 make menuconfig 打开图形界面。我个人习惯先编译一次默认配置,跑通了再慢慢加功能。
编译命令很简单:
make -j$(nproc)
编译完成后,会在当前目录生成 u-boot.bin、u-boot.img、spl/u-boot-spl.bin 等文件。对于 STM32MP1 系列,还需要一个 tf-a(Trusted Firmware-A)配合。嗯,这里要注意,不同芯片的启动流程不一样,后面我会细说。
ARCH=arm,结果编译出一堆 x86 的代码。烧进去板子毫无反应。后来查了半天才发现是环境变量没设对。所以,每次编译前,先 echo $ARCH 确认一下。
3.3 烧录与启动验证
编译出镜像后,怎么烧到板子上?这取决于板子的启动方式。常见的有:
- SD 卡启动:把镜像写到 SD 卡的特定偏移位置
- eMMC/NAND 启动:通过 USB 或串口烧录
- SPI NOR Flash 启动:用烧录器或 U-Boot 自身更新
以 STM32MP157D-EV1 为例,它支持 SD 卡启动。烧录命令如下:
# 假设 SD 卡设备是 /dev/sdb
sudo dd if=u-boot-spl.stm32 of=/dev/sdb bs=1k seek=1 conv=fsync
sudo dd if=u-boot.img of=/dev/sdb bs=1k seek=69 conv=fsync
这里 seek=1 和 seek=69 是 STM32MP1 的固定偏移,不能搞错。烧完后,插上 SD 卡,上电,通过串口(115200 8N1)看输出。
如果一切正常,你会看到类似这样的启动信息:
U-Boot 2024.01 (Jan 15 2025 - 10:30:00 +0800)
CPU: STM32MP157DAC
Model: STMicroelectronics STM32MP157D-EV1
DRAM: 512 MiB
MMC: mmc@58005000: 0
Loading Environment from MMC... OK
In: serial
Out: serial
Err: serial
Net: eth0: ethernet@5800a000
Hit any key to stop autoboot: 0
=>
看到 => 提示符,说明 U-Boot 已经跑起来了。这时候你可以输入 help 看看支持哪些命令,或者用 printenv 查看环境变量。
- 串口输出是否正常?有没有乱码?
- DRAM 大小是否识别正确?
- MMC/SD 卡能不能读写?
- 网络能不能 ping 通?
3.4 U-Boot 启动流程与核心逻辑
你想想看,U-Boot 从上电到出现提示符,中间经历了什么?我画了一张图,帮你理清整个流程。
这张图展示了 U-Boot 的典型启动流程。对于 ARM 芯片,大部分都遵循这个模式:ROM Code → SPL → U-Boot 主镜像。SPL 的作用很关键,它就像一个小型的 bootloader,负责把真正的 U-Boot 从 Flash 或 SD 卡搬到 DDR 里。
我在项目中遇到过一块板子,SPL 能跑,但 U-Boot 主镜像死活加载不起来。后来发现是 DDR 初始化参数不对,SPL 里配的时序和实际硬件不匹配。嗯,这种问题最头疼,因为 SPL 阶段还没有串口输出,你根本不知道它卡在哪。
3.5 常见问题与避坑指南
- 串口无输出:检查串口引脚配置、波特率、时钟。有时候是 pinmux 没配对。
- DDR 识别错误:用
md命令读内存,看看能不能正常读写。如果读出来全是 0 或 0xdeadbeef,说明 DDR 没初始化好。 - SD 卡启动失败:确认烧录偏移是否正确。不同芯片的偏移不一样,查芯片手册。
- 网络不通:先
ping自己,再ping网关。检查 PHY 地址和复位时序。
CONFIG_DEBUG_UART,可以在 SPL 阶段就输出调试信息。打开这个选项,能帮你定位很多早期启动问题。我每次移植新板子,第一件事就是开这个。
3.6 本章小结
U-Boot 移植,说白了就是三板斧:拿源码、配板子、烧进去。但每一板斧里都有不少细节。源码目录要熟,配置要准,烧录偏移不能错。启动流程要心里有数,出了问题才知道从哪查起。
我个人觉得,U-Boot 移植是嵌入式 Linux 开发中最有成就感的一步。当你看到串口输出第一个字符时,那种感觉,嗯,就像给一块石头注入了生命。后面的事情,就顺理成章了。