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 板级初始化、设备树解析、加载内核
Bootloader 三阶段启动流程 Stage 1: ROM Code 加载SPL到SRAM 初始化时钟/PLL Stage 2: SPL 初始化DDR控制器 加载U-Boot到DDR Stage 3: U-Boot 板级初始化 加载内核/DTB 说明: • ROM Code 由芯片厂商固化,不可修改。 • SPL 负责最小化硬件初始化,通常小于 64KB。 • U-Boot Proper 提供完整命令行与网络/存储驱动支持。

3.2 U-Boot启动流程详解

U-Boot的启动流程分为汇编阶段和C语言阶段。汇编阶段完成CPU模式设置、异常向量表安装、堆栈指针初始化;C语言阶段完成板级初始化、设备树处理、命令行交互。

3.2.1 汇编阶段关键步骤

  1. 设置CPU为SVC模式:关闭中断,确保系统处于特权模式。
  2. 初始化异常向量表:将向量表复制到0x00000000或0xFFFF0000。
  3. 初始化堆栈指针:为IRQ、FIQ、SVC模式分别设置栈指针。
  4. 清零BSS段:清除未初始化全局变量区域。
  5. 跳转到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 进入命令行循环或自动启动内核
U-Boot C语言阶段函数调用链 board_init_f() relocate_code() board_init_r() main_loop()

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 /          # 查看设备树根节点
U-Boot设备树传递流程 DTS源文件 myboard.dts DTC编译器 dtc工具 DTB二进制 myboard.dtb 内核 关键命令: 1. tftp 0x83000000 myboard.dtb // 通过网络加载DTB 2. fdt addr 0x83000000 // 设置DTB地址 3. bootz 0x80800000 - 0x83000000 // 启动内核并传递DTB

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
=>
i.MX6ULL U-Boot移植流程 获取源码 git clone 配置 make defconfig 编译 make flash.bin 烧写 dd to SD卡 启动 关键检查点: • 串口输出是否正常(波特率115200,8N1) • DRAM容量是否正确识别(512MB) • MMC/SD卡设备是否枚举成功(MMC: FSL_SDHC: 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 中的控制台参数