3. Bootloader原理与U-Boot移植
3.1 Bootloader在嵌入式系统中的角色
Bootloader是系统上电后最先执行的代码,负责完成硬件初始化、加载操作系统内核并传递启动参数。在ARM平台中,Bootloader通常存储在NOR Flash、NAND Flash或SD卡等非易失性存储介质中。
核心职责: 初始化CPU、内存控制器、串口等关键外设 → 从存储介质读取内核映像 → 解压并跳转到内核入口。
3.1.1 典型启动阶段
| 阶段 | 执行位置 | 主要工作 |
|---|---|---|
| Stage 1 (ROM Code) | 芯片内部ROM | 加载SPL/MLO到SRAM,初始化时钟与DDR |
| Stage 2 (SPL) | SRAM | 初始化DDR控制器,加载完整U-Boot到DDR |
| Stage 3 (U-Boot Proper) | DDR | 板级初始化、设备树解析、加载内核 |
3.2 U-Boot启动流程详解
U-Boot的启动流程分为汇编阶段和C语言阶段。汇编阶段完成CPU模式设置、异常向量表安装、堆栈指针初始化;C语言阶段完成板级初始化、设备树处理、命令行交互。
3.2.1 汇编阶段关键步骤
- 设置CPU为SVC模式:关闭中断,确保系统处于特权模式。
- 初始化异常向量表:将向量表复制到0x00000000或0xFFFF0000。
- 初始化堆栈指针:为IRQ、FIQ、SVC模式分别设置栈指针。
- 清零BSS段:清除未初始化全局变量区域。
- 跳转到C入口:调用
board_init_f()函数。
3.2.2 C语言阶段核心函数调用链
| 函数 | 所在文件 | 功能描述 |
|---|---|---|
board_init_f() |
common/board_f.c | 初始化DRAM、串口、时钟,建立全局数据结构gd |
relocate_code() |
arch/arm/lib/relocate.S | 将U-Boot自身从Flash重定位到DRAM |
board_init_r() |
common/board_r.c | 初始化完整外设、环境变量、命令解析器 |
main_loop() |
common/main.c | 进入命令行循环或自动启动内核 |
3.3 板级配置与移植方法
U-Boot通过板级目录和配置文件来支持不同硬件平台。移植新板卡时,通常需要创建新的板级目录、配置头文件和链接脚本。
3.3.1 板级目录结构
board/
└── vendor/
└── myboard/
├── Kconfig # 板级Kconfig配置
├── Makefile # 编译规则
├── myboard.c # 板级初始化代码
├── lowlevel_init.S # 底层初始化汇编
└── imximage.cfg # 镜像配置(i.MX系列)
3.3.2 配置文件关键宏
| 宏定义 | 说明 | 典型值 |
|---|---|---|
CONFIG_SYS_TEXT_BASE |
U-Boot在DRAM中的运行基址 | 0x87800000 |
CONFIG_SYS_INIT_SP_ADDR |
初始堆栈指针地址 | 0x87800000 - 0x1000 |
CONFIG_SYS_MALLOC_LEN |
动态内存池大小 | 16MB |
CONFIG_SYS_LOAD_ADDR |
内核加载地址 | 0x80800000 |
移植技巧: 选择与目标板最接近的参考板(如NXP i.MX6Q SabreSD),复制其板级目录并修改DDR时序、GPIO复用、时钟频率等参数,可大幅减少移植工作量。
3.4 U-Boot设备树支持
从U-Boot 2014.10版本开始,设备树(Device Tree)成为U-Boot的标准配置方式。U-Boot使用设备树来描述板级硬件信息,并在启动时将其传递给Linux内核。
3.4.1 设备树在U-Boot中的角色
- 板级描述:替代传统的板级头文件宏定义,描述内存映射、外设地址、时钟配置。
- 内核传递:U-Boot将设备树二进制(DTB)加载到内存,通过寄存器r2传递给内核。
- 运行时修改:U-Boot支持在命令行中修改设备树节点(
fdt set、fdt add)。
3.4.2 设备树编译与加载流程
# 编译设备树源文件(DTS)为二进制(DTB)
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 在U-Boot命令行中加载DTB
tftp 0x83000000 myboard.dtb
fdt addr 0x83000000
fdt list / # 查看设备树根节点
3.5 常用U-Boot命令
U-Boot提供丰富的命令行工具,用于调试、烧写、网络传输和系统启动。以下是最常用的命令分类。
3.5.1 内存与存储操作
| 命令 | 语法 | 用途 |
|---|---|---|
md |
md [addr] [len] |
显示内存内容 |
mw |
mw [addr] [value] [len] |
填充内存 |
mmc |
mmc read/write [addr] [blk] [cnt] |
读写MMC/SD卡 |
sf |
sf read/write [addr] [offset] [len] |
读写SPI Flash |
3.5.2 网络与启动命令
| 命令 | 语法 | 用途 |
|---|---|---|
tftp |
tftp [addr] [filename] |
通过TFTP下载文件 |
ping |
ping [ip] |
测试网络连通性 |
bootz |
bootz [kernel_addr] [initrd_addr] [fdt_addr] |
启动zImage内核 |
bootm |
bootm [addr] |
启动uImage内核 |
3.5.3 设备树与环境变量
# 设备树操作
fdt addr 0x83000000 # 设置DTB地址
fdt list /memory # 查看内存节点
fdt set /serial baudrate 115200 # 修改串口波特率
# 环境变量操作
setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk0p2
setenv bootcmd 'tftp 0x80800000 zImage; tftp 0x83000000 myboard.dtb; bootz 0x80800000 - 0x83000000'
saveenv # 保存环境变量到Flash
注意事项: 使用
saveenv 前请确认环境变量存储区域(如SPI Flash或MMC)已正确配置,否则可能导致存储介质损坏。
3.6 实战:为i.MX6ULL开发板移植U-Boot
以NXP i.MX6ULL EVK为例,演示U-Boot移植的完整步骤。
3.6.1 获取源码与配置
# 下载U-Boot源码
git clone https://github.com/u-boot/u-boot.git
cd u-boot
git checkout v2023.04
# 配置i.MX6ULL EVK
make mx6ull_14x14_evk_defconfig
# 编译
make CROSS_COMPILE=arm-linux-gnueabihf- -j4
3.6.2 生成启动映像
# i.MX系列需要生成带IVT头的映像
make flash.bin
# 输出文件:flash.bin(包含IVT + U-Boot)
3.6.3 烧写与验证
# 通过SD卡烧写
sudo dd if=flash.bin of=/dev/sdb bs=1k seek=1 conv=fsync
# 上电后串口输出示例
U-Boot 2023.04 (Jan 15 2025 - 10:30:00 +0800)
CPU: i.MX6ULL rev1.1 at 792 MHz
Board: MX6ULL EVK
DRAM: 512 MiB
MMC: FSL_SDHC: 0
Net: FEC
Hit any key to stop autoboot: 0
=>
3.7 常见问题与调试方法
调试黄金法则: 从串口输出入手,逐阶段确认硬件初始化是否成功。使用
md 命令检查内存内容,使用 ping 验证网络通路。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无串口输出 | 时钟配置错误、GPIO复用错误 | 检查 lowlevel_init.S 中的时钟初始化代码 |
| DRAM容量错误 | DDR时序参数不匹配 | 核对DDR数据手册,调整 board_init_f() 中的时序配置 |
| TFTP下载失败 | MAC地址未设置、PHY驱动问题 | 使用 setenv ethaddr 00:11:22:33:44:55 设置MAC地址 |
| 内核启动卡住 | DTB地址错误、bootargs参数错误 | 确认 fdt addr 和 bootargs 中的控制台参数 |