02-U-Boot启动流程.md 36 KB


title: U-Boot启动流程详解 tags: [U-Boot, 启动流程, 嵌入式, Makefile, ARM] created: 2026-09-17 updated: 2026-09-17 pdf_ref:

  • "36_第三十一章 U-Boot顶层Makefile详解.pdf"
  • "37_第三十二章 U-Boot启动流程详解.pdf" ---

U-Boot启动流程详解

关联知识: [[01-ARM架构与裸机编程/02-ARM汇编与启动流程]] | [[06-系统移植与启动/01-U-Boot基础与命令]]

一、U-Boot源码结构

1.1 目录总览

mindmap
  root((U-Boot源码))
    arch/ 体系结构
    board/ 板级支持
    cmd/ 命令实现
    common/ 通用代码
    drivers/ 设备驱动
    configs/ 配置文件
    include/ 头文件
    Makefile 顶层构建

1.2 核心目录详解

目录 用途 关键子目录
arch/ 体系结构相关代码 arm/, arm64/, riscv/
board/ 板级定制代码 freescale/, ti/, rockchip/
cmd/ U-Boot命令实现 bootm.c, md.c, tftp.c
common/ 通用启动代码 main.c, bootm.c, cli.c
drivers/ 设备驱动程序 serial/, mmc/, net/
fs/ 文件系统支持 ext4/, fat/, ubifs/
include/ 头文件 configs/, asm/
net/ 网络协议栈 tftp.c, nfs.c, bootp.c
lib/ 库函数 string.c, printf.c
tools/ 构建工具 mkimage, binman

1.3 架构目录详解 (arch/arm)

arch/arm/
+-- cpu/
|   +-- armv7/
|   |   +-- start.S        # reset、save_boot_params、cpu_init_cp15、cpu_init_crit
|   |   +-- lowlevel_init.S# 低级初始化(设置 SP、调用 s_init)
|   +-- u-boot.lds         # 链接脚本模板(编译后生成根目录 u-boot.lds)
+-- imx-common/            # NXP i.MX系列通用代码
+-- mach-mx6/              # i.MX6系列SoC支持
+-- lib/
|   +-- vectors.S          # _start 与中断向量表
|   +-- crt0.S             # _main 与 C 运行时初始化
|   +-- relocate.S         # relocate_code / relocate_vectors
+-- include/asm/arch-mx6/  # i.MX6寄存器定义

1.4 板级目录结构

board/freescale/
+-- mx6ullevk/                    # NXP官方开发板
|   +-- mx6ullevk.c
|   +-- imximage-ddr512.cfg.cfgtmp
|   +-- plugin.S
+-- mx6ull_alientek_emmc/         # 正点原子EMMC版
|   +-- mx6ull_alientek_emmc.c
|   +-- imximage-ddr512.cfg.cfgtmp
|   +-- panel.c
+-- mx6ull_alientek_nand/         # 正点原子NAND版
+-- common/
    +-- etherboot.c

1.5 关键文件说明

文件 说明 重要程度
arch/arm/cpu/armv7/start.S 启动入口代码 最高
arch/arm/lib/crt0.S C运行时初始化 最高
arch/arm/lib/vectors.S 中断向量表
arch/arm/cpu/u-boot.lds 链接脚本模板
board/freescale/mx6ull_alientek_emmc/ 板级支持包
include/configs/mx6ull_alientek_emmc.h 板级配置 最高
configs/mx6ull_14x14_ddr512_emmc_defconfig 默认配置 最高

1.6 编译生成的关键文件

文件 来源 用途
u-boot ELF格式镜像 调试用
u-boot.bin objcopy生成 二进制可执行镜像
u-boot-nodtb.bin 无设备树版本 u-boot.bin的源文件
u-boot.imx mkimage添加头部 NXP专用,含IVT/DCD
u-boot.lds 链接脚本 定义内存布局
u-boot.map 映射文件 查看函数链接地址
.config 配置文件 编译选项

1.7 .u-boot.xxx_cmd 文件分析

# .u-boot.bin.cmd - 生成u-boot.bin
cmd_u-boot.bin := cp u-boot-nodtb.bin u-boot.bin

# .u-boot-nodtb.bin.cmd - ELF转二进制
cmd_u-boot-nodtb.bin := arm-linux-gnueabihf-objcopy \
    --gap-fill=0xff -j .text -j .secure_text -j .rodata -j .hash \
    -j .data -j .got -j .got.plt -j .u_boot_list -j .rel.dyn \
    -O binary u-boot u-boot-nodtb.bin

# .u-boot.imx.cmd - 生成NXP专用镜像
cmd_u-boot.imx := ./tools/mkimage \
    -n board/freescale/mx6ull_alientek_emmc/imximage.cfg.cfgtmp \
    -T imximage -e 0x87800000 -d u-boot.bin u-boot.imx

二、顶层Makefile详解

2.1 Makefile 核心变量

# 版本信息(顶层 Makefile 第 5~9 行)
VERSION = 2016
PATCHLEVEL = 03
SUBLEVEL =
EXTRAVERSION =
NAME =
# VERSION/PATCHLEVEL/SUBLEVEL 共同构成 2016.03;EXTRAVERSION、NAME 一般不使用

# 交叉编译工具链(第 245~246 行:主机架构==目标架构时 CROSS_COMPILE 为空)
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
# 实际编译时通过 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- 传入,
# 也可直接在顶层 Makefile 中写死 ARCH=arm / CROSS_COMPILE=arm-linux-gnueabihf-

以下CONFIG_SYS_*来自板级配置头文件(经make xxx_defconfig写入.config),而非顶层 Makefile:

CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="mx6"
CONFIG_SYS_VENDOR="freescale"
CONFIG_SYS_BOARD="mx6ull_alientek_emmc"
CONFIG_SYS_CONFIG_NAME="mx6ull_alientek_emmc"
CONFIG_SYS_TEXT_BASE=0x87800000

2.2 主要Makefile目标

命令 说明
make xxx_defconfig 配置U-Boot
make menuconfig 图形化配置
make all 编译所有目标
make u-boot.bin 生成二进制镜像
make u-boot.imx 生成NXP专用镜像
make clean 清理编译文件
make mrproper 彻底清理

2.3 配置过程详解

flowchart TD
    A[make xxx_defconfig] --> B[silentoldconfig]
    B --> C[生成 .config]
    C --> D[生成 include/config/]
    D --> E[生成 include/generated/]
    E --> F[配置完成]

2.4 编译规则详解

# 目标依赖关系(顶层 Makefile)
u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
	$(call if_changed,u-boot__)

u-boot-nodtb.bin: u-boot FORCE
	$(call if_changed,objcopy)
	$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))

u-boot.bin: u-boot-nodtb.bin FORCE
	$(call if_changed,copy)     # 即 cp u-boot-nodtb.bin u-boot.bin

# u-boot-init = $(head-y) = arch/arm/cpu/armv7/start.o
# u-boot-main = $(libs-y) = 所有子目录的 built-in.o

展开后实际执行的链接命令(来自 .u-boot.cmd):

arm-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic -Ttext 0x87800000 \
    -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o \
    --start-group arch/arm/cpu/built-in.o arch/arm/cpu/armv7/built-in.o \
        arch/arm/imx-common/built-in.o arch/arm/lib/built-in.o \
        board/freescale/common/built-in.o \
        board/freescale/mx6ull_alientek_emmc/built-in.o \
        cmd/built-in.o common/built-in.o disk/built-in.o drivers/built-in.o \
        ... fs/built-in.o lib/built-in.o net/built-in.o ... --end-group \
    arch/arm/lib/eabi_compat.o -lgcc -Map u-boot.map

u-boot.lds 的生成规则:u-boot.lds: $(LDSCRIPT) prepare FORCE$(LDSCRIPT)arch/arm/cpu/u-boot.lds)。

2.5 链接脚本关键配置

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000;          /* 注意:链接脚本里是 0x00000000,不是 0x87800000 */
    . = ALIGN(4);
    .text :
    {
        *(.__image_copy_start)
        *(.vectors)
        arch/arm/cpu/armv7/start.o (.text*)
        *(.text*)
    }
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
    .data : { *(.data*) }
    .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); }
    .image_copy_end : { *(.__image_copy_end) }
    .rel_dyn_start : { *(.__rel_dyn_start) }
    .rel.dyn : { *(.rel*) }
    .rel_dyn_end : { *(.__rel_dyn_end) }
    .end : { *(.__end) }
    _image_binary_end = .;
    .bss_start __rel_dyn_start (OVERLAY) : { KEEP(*(.__bss_start)); __bss_base = .; }
    .bss __bss_base (OVERLAY) : { *(.bss*) . = ALIGN(4); __bss_limit = .; }
    .bss_end __bss_limit (OVERLAY) : { KEEP(*(.__bss_end)); }
    ...
}

真正的加载地址由链接时的 -Ttext 0x87800000 决定,链接脚本本身从 0x00000000 开始。入口点是 _start,定义在 arch/arm/lib/vectors.S 中,其后紧跟中断向量表(.vectors 段)。

关键地址变量(示例编译值,以实际编译为准):

变量 数值 描述
__image_copy_start 0x87800000 U-Boot拷贝首地址
__image_copy_end 0x8785dd54 U-Boot拷贝结束地址
__rel_dyn_start 0x8785dd54 .rel.dyn段起始
__rel_dyn_end 0x878668f4 .rel.dyn段结束
_image_binary_end 0x878668f4 镜像结束地址
__bss_start 0x8785dd54 BSS段起始
__bss_end 0x878a8e74 BSS段结束

2.6 编译选项详解

# 链接选项(.u-boot.cmd)
-pie                 # 位置无关可执行文件
--gc-sections        # 回收未使用的段
-Bstatic            # 静态链接
-Ttext 0x87800000   # U-Boot 链接/加载地址

# 生成 u-boot-nodtb.bin 时(.u-boot-nodtb.bin.cmd)
--gap-fill=0xff
$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))   # 修正 .rel.dyn 重定位表

# 交叉工具变量(顶层 Makefile 第 333~347 行)
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld.bfd(若 ld.bfd 不可用则回退为 $(CROSS_COMPILE)ld)
OBJCOPY = $(CROSS_COMPILE)objcopy
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
OBJDUMP = $(CROSS_COMPILE)objdump

2.7 Kbuild系统

flowchart TD
    A[Kconfig] --> B[配置系统]
    B --> C[.config]
    C --> D[Kbuild]
    D --> E[编译规则]
    E --> F[OBJ文件]
    F --> G[链接]
    G --> H[U-Boot镜像]

2.8 编译日志分析

$ make mx6ull_14x14_ddr512_emmc_defconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  SHIPPED scripts/kconfig/conf
  # configuration written to .config

$ make -j8
  arm-linux-gnueabihf-gcc ... arch/arm/cpu/armv7/start.S
  arm-linux-gnueabihf-gcc ... arch/arm/lib/crt0.S
  ...
  arm-linux-gnueabihf-ld ... u-boot
  arm-linux-gnueabihf-objcopy ... u-boot-nodtb.bin
  cp u-boot-nodtb.bin u-boot.bin
  ./tools/mkimage ... u-boot.imx

三、U-Boot启动流程

3.1 启动流程总览

flowchart TD
    A[上电/复位] --> B[reset向量]
    B --> C[save_boot_params]
    C --> D[save_boot_params_ret]
    D --> E[cpu_init_cp15]
    E --> F[cpu_init_crit]
    F --> G[lowlevel_init]
    G --> H[_main入口]
    H --> I[board_init_f_alloc_reserve]
    I --> J[board_init_f]
    J --> K[relocate_code重定位]
    K --> L[relocate_vectors]
    L --> M[c_runtime_cpu_setup]
    M --> N[清零BSS段]
    N --> O[board_init_r]
    O --> P[main_loop主循环]

3.2 启动阶段划分

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识(书中未讲 SPL;此处用于说明 I.MX6U 与常见 SPL 架构的区别)。

I.MX6U 的 U-Boot 不是 SPL 架构(默认直接使用完整的u-boot.imx)。上电后由芯片内部的 Boot ROM 把u-boot.imx加载到 DDR 的0x87800000处执行,之后 U-Boot 自身再经历“重定位前 → 重定位 → 重定位后”三个阶段:

flowchart LR
    subgraph ROM[Boot ROM]
        A1[解析 u-boot.imx 头部] --> A2[加载到 DDR 0x87800000]
    end
    subgraph F[board_init_f(重定位前)]
        B1[初始化串口/定时器/DDR] --> B2[规划内存并计算重定位地址]
    end
    subgraph R[board_init_r(重定位后)]
        C1[初始化外设/MMC/网络] --> C2[环境变量/控制台] --> C3[main_loop 命令行]
    end
    subgraph Linux
        D1[加载内核与设备树] --> D2[传递参数] --> D3[跳转执行]
    end
    A2 --> B1
    B2 --> R
    C3 --> D1

各阶段职责:

阶段 职责 关键函数
Boot ROM 从启动设备加载u-boot.imx到 DDR 芯片固化代码
board_init_f(重定位前) 初始化串口/定时器/DDR,init_sequence_f[],计算重定位目的地址 board_init_f()
重定位 将 U-Boot 拷贝到 DDR 高地址,修正.rel.dyn,重定位向量表 relocate_code()relocate_vectors()
board_init_r(重定位后) 初始化 MMC/NAND/网络/环境变量,进入命令行 board_init_r()main_loop()
Linux 挂载rootfs,启动init boot_jump_linux()kernel_entry()

3.3 内存布局

OCRAM (内部RAM, 128KB):
0x00900000 +---------------------+ <- IRAM_BASE_ADDR
           |       OCRAM         |
0x0091FE08 +---------------------+ <- lowlevel_init 中 sp -= GD_SIZE(248)
0x0091FF00 +---------------------+ <- CONFIG_SYS_INIT_SP_ADDR (sp 初始值)
0x00920000 +---------------------+ OCRAM结束 (0x20000, 128KB)

DRAM (外部DDR, 512MB):
0x80000000 +---------------------+ <- DRAM起始 (gd->ram_size=0x20000000)
0x80800000 +---------------------+ <- Linux内核(zImage)加载地址
0x83000000 +---------------------+ <- 设备树(dtb)加载地址
0x87800000 +---------------------+ <- U-Boot链接/初始加载地址
           |    U-Boot代码     |
0x878a8e74 +---------------------+ <- BSS段结束 (__bss_end)
           |         ...       |
0x9EF44E90 +---------------------+ <- 重定位后最终的 sp (gd->start_addr_sp)
0x9FF47000 +---------------------+ <- U-Boot重定位后的运行地址 (gd->relocaddr)
           |         ...       |
0xA0000000 +---------------------+ <- DRAM最高地址 (gd->ram_top)

3.4 重定位前阶段(board_init_f)

_main中先调用board_init_f_alloc_reserve预留早期 malloc 与 gd 区域,再调用board_init_f运行init_sequence_f[],主要工作是初始化串口、定时器、DDR 等,并“自顶向下”规划内存,最终算出重定位目的地址。

board_init_f(重定位前)职责:
+-- 1. setup_mon_len:设置 gd->mon_len = __bss_end - _start
+-- 2. arch_cpu_init / board_early_init_f:CPU 与板级早期初始化(串口 IO)
+-- 3. timer_init / get_clocks:定时器与时钟
+-- 4. env_init / init_baud_rate / serial_init / console_init_f:环境变量、波特率、串口、控制台
+-- 5. dram_init:设置 gd->ram_size(正点原子 EMMC 版为 512MB)
+-- 6. setup_dest_addr:设置 gd->ram_size、gd->ram_top=0xA0000000、gd->relocaddr=0xA0000000
+-- 7. reserve_*:从高地址向下依次预留 MMU TLB、U-Boot、malloc、bd、gd、栈等
+-- 8. setup_reloc:把旧 gd 拷贝到 gd->new_gd

关键中间结果(示例):gd->relocaddr = 0xA0000000reserve_mmu0x9FFF0000reserve_uboot0x9FF47000reserve_malloc(16MB+8KB)后 0x9EF45000reserve_board(80B)→ reserve_global_data(248B)→ reserve_stacks 后最终 gd->start_addr_sp = 0x9EF44E90

3.5 U-Boot主阶段详解

U-Boot主阶段职责:
+-- 1. 初始化更多外设 (NAND/EMMC/USB/网络/显示)
+-- 2. 网络初始化 (网卡驱动/IP配置/TFTP/NFS)
+-- 3. 命令初始化 (注册命令/解析bootargs/启动脚本)
+-- 4. 环境变量处理 (读取/设置默认值/保存)
+-- 5. 启动Linux内核 (加载zImage/设备树/传递参数/跳转)

3.6 重定位过程

flowchart TD
    A[board_init_f执行] --> B[计算重定位地址]
    B --> C[复制U-Boot到新位置]
    C --> D[修正重定位表]
    D --> E[更新全局数据指针]
    E --> F[跳转到新地址继续执行]
    F --> G[board_init_r执行]

3.7 网络初始化

网络初始化流程:
+-- 1. 网卡驱动初始化 (读取MAC/配置PHY/初始化DMA)
+-- 2. 网络协议栈初始化 (ARP/IP/UDP/TFTP/NFS)
+-- 3. 网络参数配置
|   +-- ipaddr      - 开发板IP
|   +-- serverip    - 服务器IP
|   +-- gatewayip   - 网关
|   +-- netmask     - 子网掩码
+-- 4. 网络测试 (ping/TFTP下载)

3.8 命令初始化

// 常用启动命令
bootm [addr] [initrd] [fdt]    // 启动内核镜像
bootz [addr] [initrd] [fdt]    // 启动zImage内核
run <script>                    // 执行启动脚本
boot                            // 执行bootcmd

四、关键代码分析

4.1 vectors.S / start.S - 启动入口

入口_start定义在arch/arm/lib/vectors.S,其后就是中断向量表(.vectors 段),第一个向量b reset跳到arch/arm/cpu/armv7/start.S中的reset

/ * arch/arm/lib/vectors.S * /
_start:
    b   reset                     @ 0x00: 复位向量
    ldr pc, _undefined_instruction @ 0x04
    ldr pc, _software_interrupt    @ 0x08
    ldr pc, _prefetch_abort        @ 0x0c
    ldr pc, _data_abort            @ 0x10
    ldr pc, _not_used              @ 0x14
    ldr pc, _irq                   @ 0x18
    ldr pc, _fiq                   @ 0x1c

/ * arch/arm/cpu/armv7/start.S * /
reset:
    b   save_boot_params           @ 允许板子保存重要寄存器

ENTRY(save_boot_params)
    b   save_boot_params_ret       @ back to my caller

save_boot_params_ret:
    mrs  r0, cpsr
    and  r1, r0, #0x1f   @ mask mode bits
    teq  r1, #0x1a       @ test for HYP mode
    bicne r0, r0, #0x1f  @ clear all mode bits
    orrne r0, r0, #0x13  @ set SVC mode
    orr  r0, r0, #0xc0   @ disable FIQ and IRQ
    msr  cpsr, r0

    @ 设置向量表重定位:清 SCTLR.V,把 _start 写入 VBAR
    mrc p15, 0, r0, c1, c0, 0      @ Read CP15 SCTLR
    bic r0, #CR_V                  @ CR_V = (1 << 13)
    mcr p15, 0, r0, c1, c0, 0
    ldr r0, =_start
    mcr p15, 0, r0, c12, c0, 0     @ Set VBAR

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl  cpu_init_cp15
    bl  cpu_init_crit
#endif
    bl  _main

4.2 cpu_init_cp15 - 协处理器初始化

ENTRY(cpu_init_cp15)
    /* Invalidate L1 I/D */
    mov r0, #0
    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache
    mcr p15, 0, r0, c7, c5, 6   @ invalidate BP array
    mcr p15, 0, r0, c7, c10, 4  @ DSB
    mcr p15, 0, r0, c7, c5, 4   @ ISB

    /* disable MMU stuff and caches */
    mrc p15, 0, r0, c1, c0, 0
    bic r0, r0, #0x00002000     @ clear bit 13 (--V-)
    bic r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
    orr r0, r0, #0x00000002     @ set bit 1 (--A-) Align
    orr r0, r0, #0x00000800     @ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFF
    bic r0, r0, #0x00001000     @ clear bit 12 (I) I-cache
#else
    orr r0, r0, #0x00001000     @ set bit 12 (I) I-cache
#endif
    mcr p15, 0, r0, c1, c0, 0
    ...
    mov pc, r5                  @ back to my caller
ENDPROC(cpu_init_cp15)

说明:I.MX6U 的 U-Boot 默认打开 I-Cache(未定义 CONFIG_SYS_ICACHE_OFF),并关闭 MMU、D-Cache。

4.3 lowlevel_init - 低级初始化

/ * arch/arm/cpu/armv7/lowlevel_init.S * /
ENTRY(lowlevel_init)
    ldr sp, =CONFIG_SYS_INIT_SP_ADDR
    bic sp, sp, #7              @ 8 字节对齐
#ifdef CONFIG_SPL_BUILD
    ldr r9, =gdata
#else
    sub sp, sp, #GD_SIZE        @ 为 gd 预留空间(GD_SIZE=248)
    bic sp, sp, #7
    mov r9, sp                  @ r9 保存 gd 指针
#endif
    push {ip, lr}
    bl  s_init                  @ 早期初始化(I.MX6ULL 为空函数)
    pop {ip, pc}
ENDPROC(lowlevel_init)

CONFIG_SYS_INIT_SP_ADDR = CONFIG_SYS_INIT_RAM_ADDR + (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE) = 0x00900000 + (0x20000 - 256) = 0x0091FF00,再减去 GD_SIZE(248) 后为 0x0091FE08

CPSR模式位对照表:

位置 模式 说明
[4:0] USR 10000 用户模式
[4:0] SVC 10011 管理模式 (启动初始化)
[4:0] IRQ 10010 IRQ中断
[4:0] FIQ 10001 FIQ中断
[5] T 1 快速中断入口
[6] F 1 FIQ禁用
[7] I 1 IRQ禁用

4.4 _main - C运行时初始化

/ * arch/arm/lib/crt0.S * /
ENTRY(_main)
    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)   @ sp = 0x0091FF00
    bic sp, sp, #7                        @ 8 字节对齐
    mov r0, sp
    bl  board_init_f_alloc_reserve        @ 预留 malloc 与 gd 区域,返回新 top
    mov sp, r0
    mov r9, r0                            @ r9 = gd 指针
    bl  board_init_f_init_reserve         @ 清零 gd 区域
    mov r0, #0
    bl  board_init_f                      @ 重定位前初始化

    ldr sp, [r9, #GD_START_ADDR_SP]       @ sp = gd->start_addr_sp
    bic sp, sp, #7
    ldr r9, [r9, #GD_BD]                  @ r9 = gd->bd
    sub r9, r9, #GD_SIZE                  @ 新 gd 位于 bd 之下
    adr lr, here
    ldr r0, [r9, #GD_RELOC_OFF]           @ r0 = gd->reloc_off
    add lr, lr, r0
    ldr r0, [r9, #GD_RELOCADDR]           @ r0 = gd->relocaddr
    b   relocate_code                     @ 代码重定位
here:
    bl  relocate_vectors                  @ 向量表重定位
    bl  c_runtime_cpu_setup
    ldr r0, =__bss_start
    ...                                   @ 清零 BSS(memset 或 clbss_l 循环)
    mov r0, r9                            @ r0 = gd_t
    ldr r1, [r9, #GD_RELOCADDR]           @ r1 = dest_addr
    ldr pc, =board_init_r                 @ 跳转 board_init_r(不返回)
ENDPROC(_main)

board_init_f_alloc_reserve自栈顶向下预留早期 malloc 区(CONFIG_SYS_MALLOC_F_LEN)与 gd 区并返回新的栈顶,board_init_f_init_reserve再把该区域清零并把 gd 指针存入 r9

4.5 board_init_f - 全局数据初始化

/* common/board_f.c */
void board_init_f(ulong boot_flags)
{
    gd_t data;

    gd = &data;
    gd->flags = boot_flags;
    gd->have_console = 0;

    /* 通过 initcall_run_list 依次执行 init_sequence_f[] */
    if (initcall_run_list(init_sequence_f))
        hang();
}

init_sequence_f[](去掉条件编译后的主要函数,按执行顺序):

static init_fnc_t init_sequence_f[] = {
    setup_mon_len,        /* gd->mon_len = __bss_end - _start */
    initf_malloc,         /* gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN = 0x400 */
    initf_console_record, /* I.MX6ULL 未定义 CONFIG_CONSOLE_RECORD,直接返回 */
    arch_cpu_init,
    initf_dm,
    arch_cpu_init_dm,     /* 未实现 */
    mark_bootstage,
    board_early_init_f,   /* 板级早期初始化(I.MX6ULL 初始化串口 IO) */
    timer_init,           /* 初始化 Cortex-A 内核定时器 */
    board_postclk_init,   /* I.MX6ULL:设置 VDDSOC 电压 */
    get_clocks,           /* I.MX6ULL:获取 sdhc_clk */
    env_init,             /* 设置 gd->env_addr */
    init_baud_rate,       /* 根据 baudrate 环境变量设置 gd->baudrate */
    serial_init,
    console_init_f,       /* gd->have_console = 1 */
    display_options,
    display_text_info,
    print_cpuinfo,
    show_board_info,
    INIT_FUNC_WATCHDOG_INIT,
    INIT_FUNC_WATCHDOG_RESET,
    init_func_i2c,
    announce_dram_init,   /* 输出 "DRAM:" */
    dram_init,            /* 设置 gd->ram_size,EMMC 版为 512MB */
    post_init_f,
    INIT_FUNC_WATCHDOG_RESET,
    testdram,             /* 空函数 */
    INIT_FUNC_WATCHDOG_RESET,
    INIT_FUNC_WATCHDOG_RESET,
    setup_dest_addr,      /* gd->ram_size/ram_top/relocaddr */
    reserve_round_4k,
    reserve_mmu,          /* TLB 表,64K 对齐 */
    reserve_trace,
    reserve_uboot,        /* 预留 U-Boot 空间(gd->mon_len),4K 对齐 */
    reserve_malloc,       /* TOTAL_MALLOC_LEN = 16MB + 8KB */
    reserve_board,        /* bd_t 大小 80B */
    setup_machine,        /* I.MX6ULL 走设备树,此函数无效 */
    reserve_global_data,  /* gd_t 大小 248B */
    reserve_fdt,          /* I.MX6ULL 未用到 */
    reserve_arch,         /* 空函数 */
    reserve_stacks,       /* 预留栈空间,16 字节对齐 */
    setup_dram_config,    /* 设置 gd->bd->bi_dram[0].start/size */
    show_dram_config,
    display_new_sp,
    INIT_FUNC_WATCHDOG_RESET,
    reloc_fdt,            /* 未用到 */
    setup_reloc,          /* 拷贝 gd 到 gd->new_gd */
    NULL,
};

执行完毕后关键值(示例):gd->ram_size=0x20000000gd->ram_top=0xA0000000gd->relocaddr=0x9FF47000gd->start_addr_sp=0x9EF44E90gd->reloc_off=0x18747000gd->new_gd=0x9EF44EB8

4.6 board_init_r - 主阶段初始化

/* common/board_r.c */
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
        init_sequence_r[i] += gd->reloc_off;
#endif
    if (initcall_run_list(init_sequence_r))
        hang();
    /* NOTREACHED - run_main_loop() does not return */
    hang();
}

init_sequence_r[](去掉条件编译后的主要函数):

init_fnc_t init_sequence_r[] = {
    initr_trace,
    initr_reloc,               /* 设置 gd->flags,标记重定位完成 */
    initr_caches,              /* 使能 cache */
    initr_reloc_global_data,
    initr_barrier,             /* I.MX6ULL 未用到 */
    initr_malloc,
    initr_console_record,      /* 空函数 */
    bootstage_relocate,
    initr_bootstage,
    board_init,                /* 板级初始化:I2C/FEC/USB/QSPI 等 */
    stdio_init_tables,
    initr_serial,
    initr_announce,
    INIT_FUNC_WATCHDOG_RESET,
    INIT_FUNC_WATCHDOG_RESET,
    INIT_FUNC_WATCHDOG_RESET,
    power_init_board,          /* 正点原子板未用到 */
    initr_flash,               /* 定义了 CONFIG_SYS_NO_FLASH,无效 */
    INIT_FUNC_WATCHDOG_RESET,
    initr_nand,                /* NAND 版本核心板初始化 NAND */
    initr_mmc,                 /* EMMC 版本核心板初始化 EMMC */
    initr_env,                 /* 初始化环境变量 */
    INIT_FUNC_WATCHDOG_RESET,
    initr_secondary_cpu,       /* 单核,未用 */
    INIT_FUNC_WATCHDOG_RESET,
    stdio_add_devices,         /* 初始化 LCD 等设备 */
    initr_jumptable,
    console_init_r,            /* 控制台初始化 */
    INIT_FUNC_WATCHDOG_RESET,
    interrupt_init,
    initr_enable_interrupts,
    initr_ethaddr,             /* 读取环境变量 ethaddr 作为 MAC */
    board_late_init,           /* 定义在 mx6ull_alientek_emmc.c,切换 MMC 设备 */
    INIT_FUNC_WATCHDOG_RESET,
    INIT_FUNC_WATCHDOG_RESET,
    INIT_FUNC_WATCHDOG_RESET,
    initr_net,                 /* initr_net -> eth_initialize -> board_eth_init */
    INIT_FUNC_WATCHDOG_RESET,
    run_main_loop,             /* 进入主循环 */
};

4.7 main_loop - 主循环

/* common/board_r.c */
static int run_main_loop(void)
{
    /* main_loop() can return to retry autoboot, if so just run it again */
    for (;;)
        main_loop();
    return 0;
}
/* common/main.c */
void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifdef CONFIG_VERSION_VARIABLE
    setenv("ver", version_string);   /* 设置 version 环境变量 */
#endif

    cli_init();                      /* 初始化 hush shell 相关变量 */
    run_preboot_environment_command(); /* 执行 preboot 环境变量(一般不用) */

    s = bootdelay_process();         /* 读取 bootdelay 和 bootcmd,返回 bootcmd */
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);             /* 倒计时;未打断则运行 bootcmd */

    cli_loop();                      /* 进入命令行 */
}

autoboot_command核心:stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)三条都成立时执行run_command_list(s, -1, 0);否则(按键打断)走cli_loop()

4.8 boot_jump_linux - 跳转内核

/* arch/arm/lib/bootm.c */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number;   /* 机器 ID */
    const char *s;
    void (*kernel_entry)(int zero, int arch, uint params);
    unsigned long r2;

    kernel_entry = (void (*)(int, int, uint))images->ep;  /* 内核入口 = images->ep */

    s = getenv("machid");
    if (s) {
        if (strict_strtoul(s, 16, &machid) < 0)
            return;
    }

    if (images->ft_len)
        r2 = (unsigned long)images->ft_addr;   /* 使用设备树:r2 = DTB 地址 */
    else
        r2 = gd->bd->bi_boot_params;           /* 不使用设备树:r2 = 参数地址 */

    kernel_entry(0, machid, r2);               /* 进入 Linux 内核,一去不复返 */
}

使用设备树后,内核靠 DTB 的 "compatible" 属性匹配机器,machid 已不再关键;I.MX6U 实际就使用设备树方式启动。

调用关系图:

flowchart TD
    A[reset] --> B[cpu_init_cp15]
    B --> C[cpu_init_crit]
    C --> D[lowlevel_init]
    D --> E[_main]
    E --> F[board_init_f_alloc_reserve]
    F --> G[board_init_f]
    G --> H[relocate_code]
    H --> I[board_init_r]
    I --> J[main_loop]
    J --> K[autoboot_command]
    K --> L[bootm/bootz]
    L --> M[boot_jump_linux]
    M --> N[kernel_entry]

4.9 bootz_setup - 内核启动准备

/* arch/arm/lib/bootm.c */
#define LINUX_ARM_ZIMAGE_MAGIC  0x016f2818

int bootz_setup(ulong image, ulong *start, ulong *end)
{
    struct zimage_header *zi;

    zi = (struct zimage_header *)map_sysmem(image, 0);
    if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
        puts("Bad Linux ARM zImage magic!\n");
        return 1;
    }

    *start = zi->zi_start;
    *end = zi->zi_end;

    printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n",
           image, *start, *end);
    return 0;
}

关键魔数:

魔数 说明
LINUX_ARM_ZIMAGE_MAGIC 0x016f2818 ARM Linux zImage 标识
ZIMAGE_MAGIC(旧书稿) 0x016f2818 同一魔数,命名不同

五、U-Boot配置与编译

5.1 编译命令流

flowchart LR
    A[make xxx_defconfig] --> B[make menuconfig]
    B --> C[make all]
    C --> D[u-boot.bin]
    C --> E[u-boot.imx]

5.2 配置文件说明

configs/mx6ull_14x14_ddr512_emmc_defconfig
+-- 定义板级配置
+-- 包含必选组件
+-- 设置默认环境变量

5.3 menuconfig 说明

Boot options ->
+-- Default device tree name: imx6ull-14x14-alientek-emmc
+-- Default boot command: bootm

General setup ->
+-- U-Boot version string: 2016.03-alientek

ARM architecture ->
+-- 选择i.MX6 SoC支持
+-- 选择开发板型号

5.4 编译命令详解

# 1. 配置
make mx6ull_14x14_ddr512_emmc_defconfig

# 2. 编译
make all -j$(nproc)

# 3. 查看编译信息
make --just-print  # 仅打印命令不执行

# 4. 查看配置
cat .config | grep CONFIG

# 5. 清理
make clean   # 清理编译文件
make mrproper # 彻底清理(含.config)

5.5 烧录流程

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识(书中第三十章使用imxdownload烧写u-boot.bin;此处为通用 dd 方式)。

u-boot.imx需烧写到 SD 卡偏移 1024 字节处(即跳过前 2 个 512 字节扇区):

flowchart LR
    A[获取u-boot.imx] --> B[插入SD卡]
    B --> C[sudo dd if=u-boot.imx of=/dev/sdb bs=512 seek=2 conv=fsync]
    C --> D[确认烧录完成]
    D --> E[插入开发板启动]

dd命令参数说明:

参数 说明
if=u-boot.imx 输入文件
of=/dev/sdb 输出设备(切勿写系统盘)
bs=512 块大小 = 一个扇区
seek=2 跳过 2 个扇区 = 1024 字节偏移
conv=fsync 同步写入,确保数据落盘(可选 notrunc)

六、跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

6.1 i.MX6ULL vs STM32MP1 vs RK3568

对比项 i.MX6ULL STM32MP1 RK3568
内核加载地址 0x80800000 0xC2000000 0x00208000
U-Boot加载地址 0x87800000 0xC4000000 0x00200000
DTB加载地址 0x83000000 0xC4F00000 0x00F00000
OCRAM 128KB 256KB
SPL存储 eMMC/SD/USB eMMC/SD eMMC/SD/SPI
烧录工具 dd/mkimage STM32CubeProgrammer rkdevtool
特殊处理 IVT+DCD头 FSBL+SSBL TPL+SPL

6.2 启动流程对比

flowchart LR
    subgraph i.MX6ULL
        A1[Boot ROM] --> A2[SPL]
        A2 --> A3[U-Boot]
        A3 --> A4[Linux]
    end
    subgraph STM32MP1
        B1[Boot ROM] --> B2[FSBL]
        B2 --> B3[SSBL]
        B3 --> B4[Linux]
    end
    subgraph RK3568
        C1[Boot ROM] --> C2[TPL]
        C2 --> C3[SPL]
        C3 --> C4[U-Boot]
        C4 --> C5[Linux]
    end

七、面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识(答案基于书中事实整理)。

Q1: 为什么U-Boot加载地址是0x87800000?

0x87800000是 NXP 为 i.MX6 系列定义的CONFIG_SYS_TEXT_BASE(写在板级头文件 mx6ullevk.h / mx6ull_alientek_emmc.h 中),编译时通过-Ttext 0x87800000链接,也是上电后 Boot ROM 把 U-Boot 加载到 DDR 的位置。正点原子裸机例程的链接起始地址同样选0x87800000,目的是与 U-Boot 保持一致。

注意:这是 U-Boot 的初始加载/链接地址;重定位后 U-Boot 实际运行在 DDR 高地址(示例为0x9FF47000)。

Q2: U-Boot重定位的作用是什么?为什么要重定位?

作用:

  1. 将U-Boot从初始加载地址拷贝到最终运行地址
  2. 修正全局变量引用和函数指针
  3. 释放低地址内存给SPL使用

原因:

  • SPL加载U-Boot到固定地址0x87800000
  • 最终运行地址可能不同,需要重定位到未来运行地址
  • 释放初始加载区域,避免内存浪费

Q3: CONFIG_SYS_INIT_SP_ADDR是如何计算的?

CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000
CONFIG_SYS_INIT_RAM_SIZE = IRAM_SIZE = 0x00020000 (128KB)
GENERATED_GBL_DATA_SIZE = (sizeof(struct global_data) + 15) & ~15 = 256
CONFIG_SYS_INIT_SP_OFFSET = 0x20000 - 256 = 0x1FF00
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0x1FF00 = 0x0091FF00

随后在lowlevel_init中执行sub sp, sp, #GD_SIZE(GD_SIZE=248)并 8 字节对齐,得到0x0091FE08

Q4: U-Boot如何传递参数给Linux内核?

  1. r0 = 0: 未使用
  2. r1 = machid: 机器类型ID(gd->bd->bi_arch_number);使用设备树后内核靠 DTB 的 compatible 匹配,machid 已不再关键
  3. r2 = dtb地址: 使用设备树时为images->ft_addr(DTB 在 DRAM 中的地址),否则为gd->bd->bi_boot_params
  4. bootargs: 通过 bootargs 环境变量传递,I.MX6ULL 上典型值:
    • console=ttymxc0,115200 串口控制台配置
    • root=/dev/mmcblk1p2 根文件系统分区
    • rootwaitrwrootfstype=ext4

Q5: 如何分析U-Boot启动失败?

1. 串口无输出: 检查串口驱动、时钟配置
2. DDR初始化失败: 检查DDR参数配置
3. SPL加载失败: 检查存储驱动、分区表
4. U-Boot加载失败: 检查存储器、镜像格式
5. 内核启动失败: 检查内核地址、DTB地址
6. rootfs挂载失败: 检查rootfs路径、分区表

参考资料:


内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第31章 U-Boot顶层Makefile详解、第32章 U-Boot启动流程详解