U-boot 移植及源码分析( ARM )

  • 在s5p6818上移植uboot并分析其启动过程

1. 系统移植

1. 嵌入式Linux构成与移植概述

  • 嵌入式Linux移植定义: 移植就是将Bootloader的源代码、Linux内核源代码、文件系统中用户态程序代码根据硬件做少量修改,使其能够在目标硬件平台上运行起来的过程。
  • Linux内核功能及本质:
    • 功能: 进程管理、文件系统、内存管理、网络协议。

2. Bootloader定义及重要性

  • 定义: Bootloader是在操作系统内核运行之前运行,初始化硬件设备、建立内存空间映射,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。
  • 为什么需要Bootloader:
    • 在嵌入式系统中,Bootloader是一段短小的启动程序,因为没有BIOS那样的固件程序,因此整个系统的加载启动任务就完全由Bootloader来完成。
    • 使Linux内核可以在系统主存中跑起来,系统必须符合Linux内核启动的必要条件。

3. Bootloader、Kernel、应用程序之间的关系

  • Bootloader:
    • 硬件上电后跳到固定位置执行相应代码
    • 初始化相应的硬件设备
    • 加载操作系统内核代码到内存
    • 跳到内核代码起始位置执行
  • Kernel (uImage):
    • 内核自解压 (uImage)
    • 初始化相应的硬件设备
    • 初始化静态编译进内核的驱动模块
    • 挂载根文件系统
    • 直接执行第一个用户空间程序
  • 第一个用户空间程序: 配置用户环境和执行服务进程

2. 开发板烧写并启动(先对emmc进行分区,了解整个系统在emmc上的启动过程)

1. emmc分区布局

1
2
3
4
0M   -   1M(0x80)      : Bootloader (U-Boot)
1M - 65M(0x20800) : Kernel (uImage + dtb)
65M - 819M : RootFS (ext4)
819M - 末尾 : 其他数据

2. 假设当前通过软件烧录uboot后启动在uboot中,(uboot支持emmc操作)

  • 查看emmc信息
    • mmc list / mmc dev 0 / mmc info
  • 按需擦除数据
    • mmc erase 0 1000000
  • 分区(help fdisk)
    • fdisk [part table counts] start:length….
      • fdisk 2 3 0x100000:0x4000000 0x4100000:2f200000 0x33300000:0 // 从bootloader地址之后开始分区的话
  • 通过uboot中的 loadaddr 和 bdinfo 查看DRAM信息, 然后重新烧写uboot
    • tftp 0x48000000 ubootpak.bin
      • Bytes transferred = 342960 (53bb0 hex)
    • update_mmc 2 2ndboot 0x48000000 0x200 53bb0
  • 重启进入uboot
    • re
  • Linux内核烧写
    • tftp 48000000 uImage
    • mmc write 48000000 0x800 0x3000 (写入的扇区位置和数量)

3. 配置NFS服务器

  • rootfs一般都是从网络启动和挂载,因为方便调试,主机需要配置NFS服务器。

    • 服务端配置

      • 编辑/etc/exports文件: sudo vi /etc/exports
      • 添加NFS共享目录配置: /opt/rootfs *(rw,sync,no_root_squash)
        • rw: 读写权限。
        • sync: 数据同步写入磁盘。
        • no_root_squash: 客户端以root身份访问文件。
      • 重启NFS服务使配置生效: sudo /etc/init.d/nfs-kernel-server restart
    • 客户端配置 (U-boot环境变量)

      • 设置bootargs:
        setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.255:255.0 console=ttySAC0 maxcpus=1 lcd=wy070ml tp=gslx680-linux
        • root=/dev/nfs: 指定根文件系统类型为NFS。
        • nfsroot=192.168.1.8:/opt/rootfs: NFS服务器IP地址和共享目录。
        • ip=...: 开发板的IP地址配置。
        • console=ttySAC0: 指定控制台输出设备。

3. U-boot源码分析

1. U-boot

  • U-Boot(Universal Boot Loader)是一个开源、跨平台的 Bootloader,主要用于嵌入式系统启动。它独立于操作系统,运行在裸机环境,支持多种 CPU 架构(ARM、PowerPC、MIPS、x86 等)和操作系统(Linux、VxWorks 等)。
  • 特点:模块化设计,易于移植;支持多种存储、外设和网络协议;提供丰富的命令行操作。

2. U-boot编译

  • 清理环境(防止残留配置):
    • make distclean
  • 加载目标板默认配置:
    • make _defconfig
      • 示例:make x6818_defconfig
  • 可选修改配置(菜单界面):
    • make menuconfig
  • 编译(指定交叉编译器):
    • make CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
  • 查看产物:
    • u-boot:ELF 格式,可调试,为可执行的ELF格式文件。
    • ubootpak.binu-boot通过objcopy转换得到的二进制文件(用于烧写)。
    • u-boot-dtb.bin:带设备树版本(部分平台)。

3. 入口点文件分析

编译链接过程 (make V=1)

当执行 make V=1 时,可以看到详细的编译链接过程。例如:
make -f scripts/Makefile.build obj=examples/standalone arm-cortex_a9-linux-gnueabi-ld -pte --gc-sections -Bstatic -Ttext 0x43C0000 -o u-boot u-boot.lds arch/arm/cpu/slsiap/start.o --start-group arch/arm/cpu/slsiap/built-in.o arch/arm/cpu/slsiap/s5p6818/built-in.o arch/arm/cpu/slsiap/s5

这里关键的是 -Ttext 0x43C0000 -o u-boot u-boot.lds,它指定了U-boot的起始地址和链接脚本。

u-boot.lds 链接脚本

链接脚本定义了U-boot各段在内存中的布局。

1
2
3
4
5
6
7
8
9
10
11
. = 0x00000000;
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata))) }
.data : { }
.text :
{
*(.__image_copy_start)
arch/arm/cpu/slsiap/s5p6818/start.o (.text*)
arch/arm/cpu/slsiap/s5p6818/vectors.o (.text*)
*(.text*)
}
  • . = 0x00000000;: 指定代码的加载起始地址为0x00000000 (通常是内存或Flash的起始地址)。
  • arch/arm/cpu/slsiap/s5p6818/start.o (.text*): 这表明start.o文件中的.text段(代码段)是U-boot的第一个执行部分。
start.S (入口点汇编文件)

start.o是由start.S汇编文件编译而来,其中包含了U-boot的真正入口点。

1
2
3
4
5
6
7
8
9
10
.globl _start
_start:
b reset // 跳转到reset函数
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
  • _start: 这是U-boot的入口点符号。
  • b reset: 上电后CPU执行的第一个指令,直接跳转到reset函数。reset函数是U-boot初始化过程的起点。

4. U-boot启动过程

  • U-boot启动分为两个主要阶段:
阶段一: 最底层初始化 (汇编代码)

U-Boot 启动的第一阶段:从复位到 C 运行环境

  • 异常向量表设置
  • CPU 模式切换
  • 缓存与 MMU 初始化
  • 代码重定位
  • BSS 段清零
  • 栈和全局数据结构(gd)初始化
  • 跳转到 board_init_f()(进入 C 语言环境)

start.S主要代码分析:

  1. 异常向量表设置

ARM 处理器上电后,从 0x00000000 开始取指(或由 VBAR 指定的基地址),所以必须先建立异常向量表。

1
2
3
4
5
6
7
8
9
10
.globl _stext
_stext:
b reset @ 复位时跳到 reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
  • 定义 _stext 作为代码的起始地址

  • ARM 异常类型:reset, undefined, SWI, prefetch abort, data abort, IRQ, FIQ

    • 通过 ldr pc, xxx 跳转到对应处理函数地址
    • 这一步的目的:保证 CPU 出现异常不会直接崩溃,有跳转入口。
    • ARM 架构规定,当某种异常发生时,硬件会自动做两件事:
      • 切换到对应的异常模式(如 Supervisor、IRQ、FIQ)。
      • 把 PC 设置到异常向量表的固定偏移地址(通常是 0x00000000 或者 VBAR 里的值)。
        • 例如:
          • 发生 Reset → PC = 0x00000000 → 执行 b reset
          • 发生 Undefined → PC = 0x00000004 → 执行 ldr pc, _undefined_instruction
          • 发生 IRQ → PC = 0x00000018 → 执行 ldr pc, _irq
  • b handler 是 相对跳转,需要 handler 在当前 32MB 范围内。

  • ldr pc, _xxx 是 绝对跳转,通过取一个常量地址,可以跳到 U-Boot 任意地方。

  • 这样设计是为了灵活,后面如果重定位代码,这个表不用改,直接改 _xxx 变量的值。

  1. reset:从复位到基本初始化

reset 是启动的第一个执行函数,主要工作:

  • 保存启动参数
  • 切换到 SVC 模式
  • 关闭看门狗
  • 初始化 CPU 关键寄存器
  • 如果需要,跳到低级硬件初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
reset:
bl save_boot_params @ 保存启动参数(调用 save_boot_params 函数,保存启动时的参数)

/* 切换到 SVC32 模式 */
mrs r0, cpsr @ 读取当前的 CPSR(Current Program Status Register,当前程序状态寄存器)
bic r0, r0, #0x1f @ 清除 CPSR 中的模式字段(低 5 位)以准备切换模式
orr r0, r0, #0xd3 @ 设置 CPSR 的模式为 SVC 模式 (SVC32),并禁用所有中断
msr cpsr, r0 @ 将修改后的 CPSR 写回,切换到 SVC 模式并禁用中断

/* 禁用看门狗 */
ldr r0, =0xC0019000 @ 加载看门狗控制寄存器的地址(地址是 0xC0019000)
mov r1, #0 @ 将寄存器 r1 设置为 0,表示禁用看门狗
str r1, [r0] @ 将 r1 的值(0)存储到看门狗控制寄存器,禁用看门狗

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15 @ 调用 cpu_init_cp15 函数,关闭 MMU、清除缓存等
bl cpu_init_crit @ 调用 cpu_init_crit 函数,执行板级低级初始化,如 PLL 和 DDR 配置
#endif

关键点:

  • cpsr 切换 CPU 模式为 SVC(超级用户模式),同时关中断
  • 关闭看门狗,避免启动过程中被复位
  • cpu_init_cp15:处理 CP15 寄存器,关闭 MMU,invalidate I/D cache
  • cpu_init_crit:执行板级初始化(比如 DRAM 控制器),确保后面可以使用内存
  1. 代码重定位(Relocation)-> 如果平台 开启 CONFIG_RELOC_TO_TEXT_BASE

很多 ARM 平台启动时,U-Boot 最初加载在 NOR Flash 或 SRAM,执行地址并不是最终运行地址。
为了能正确访问全局变量、函数指针,必须把 U-Boot 拷贝到 DRAM 的 TEXT_BASE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
relocate_to_text:
adr r0, _stext @ 获取当前代码位置(即 _stext 地址),并将其存入寄存器 r0
ldr r1, TEXT_BASE @ 加载目标地址(TEXT_BASE,即代码重定位后存放的位置)
cmp r0, r1 @ 比较当前代码地址(r0)与目标地址(r1)
beq clear_bss @ 如果当前地址已经是目标地址,则跳转到 clear_bss,避免重复搬迁

ldr r2, _bss_start_ofs @ 加载 BSS 段起始地址的偏移量(_bss_start_ofs)
add r2, r0, r2 @ 计算拷贝结束地址(当前地址 + BSS 段的偏移量)

copy_loop_text:
ldmia r0!, {r3-r10} @ 从 r0(当前代码地址)加载 8 个寄存器的内容,并将 r0 地址增加 8 字节
stmia r1!, {r3-r10} @ 将加载的寄存器内容存储到 r1(目标地址),并将 r1 地址增加 8 字节
cmp r0, r2 @ 比较当前地址(r0)与拷贝结束地址(r2)
ble copy_loop_text @ 如果 r0 小于等于 r2,说明还没有到达结束地址,继续拷贝

ldr r1, TEXT_BASE @ 重新加载目标地址 TEXT_BASE
mov pc, r1 @ 跳转到 DRAM 中的 TEXT_BASE 继续执行
  • 比较当前执行地址和目标地址(TEXT_BASE)
  • 如果不一样,循环拷贝整个 U-Boot 代码段
  • 最后 mov pc, r1,跳转到 DRAM 里的代码继续执行
  1. 清空 BSS 段

BSS 段用于存放未初始化的全局变量,必须清零。

1
2
3
4
5
6
7
8
9
10
11
12
13
clear_bss:
ldr r0, _bss_start_ofs @ 加载 BSS 段的起始偏移地址
ldr r1, _bss_end_ofs @ 加载 BSS 段的结束偏移地址
ldr r4, TEXT_BASE @ 加载 TEXT_BASE 地址
add r0, r0, r4 @ 计算实际的 BSS 段起始地址(BSS 起始地址 + TEXT_BASE)
add r1, r1, r4 @ 计算实际的 BSS 段结束地址(BSS 结束地址 + TEXT_BASE)
mov r2, #0 @ 设置 r2 为 0,准备清空 BSS 段

clbss_l:
str r2, [r0] @ 将 0 存储到 BSS 段中的当前地址
add r0, r0, #4 @ 移动到下一个 4 字节地址(每次清 4 字节)
cmp r0, r1 @ 比较当前地址(r0)与 BSS 结束地址(r1)
bne clbss_l @ 如果没有到达结束地址,继续清空 BSS 段
  • 把 BSS 段所有字节清 0
  • 避免后面 C 代码访问到脏数据
  1. 初始化栈和 GD(global data)结构

C 代码需要栈,U-Boot 还需要 gd_t 全局数据结构保存运行状态。

1
2
3
4
5
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)   @ 设置栈指针 sp 为初始化的栈地址(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 @ 确保栈指针 sp 8 字节对齐,清除 sp 的最低三位
sub sp, #GD_SIZE @ 给全局数据(GD)预留空间,调整栈指针,分配 GD 所需的内存
bic sp, sp, #7 @ 再次确保栈指针 8 字节对齐
mov r9, sp @ 将栈指针保存到 r9,r9 将作为全局数据(GD)的基地址
  • CONFIG_SYS_INIT_SP_ADDR:通常是 DRAM 高地址(安全)
  • GD_SIZE:U-Boot 的全局数据结构
  • r9(IP 寄存器)专门存放 gd 指针
  1. board_init_f函数执行,跳转到 board_init_f 所在的board_f.c 文件中
  • board_init_f 阶段

    • 运行位置:Flash 或 SRAM(未 relocation 前)
    • 作用:
      • 初始化 PLL/时钟。
      • 初始化 DDR 控制器(重点)。
      • 初始化 串口(用于输出 early log)。
      • 计算 relocation 参数:
        • gd->relocaddr:U-Boot 在 DRAM 的目标地址。
        • gd->start_addr_sp:新的栈顶地址(在 DRAM)。
        • gd->new_gd:新全局数据区域(DRAM)。
      • 调用 jump_to_copy() → relocate_code()。
  • relocate_code 阶段

    • 运行位置:仍然在 Flash/SRAM(搬家前)。
    • 作用:
      • 将 .text、.data 从 当前位置 复制到 gd->relocaddr(DRAM)。
      • 清空 .bss 段(DRAM)。
      • 切换栈到 DRAM(sp = gd->start_addr_sp)。
      • 跳转到 board_init_r 的地址(现在在 DRAM 中)。

这一步才是真正把 U-Boot 搬到 DRAM 并切换执行位置。

  1. board_init_r函数执行,跳转到 board_init_r 所在的board_r.c 文件中
  • 运行位置:DRAM(relocation 完成)。

  • 作用:

    • cif (initcall_run_list(init_sequence_r)) hang();
      • 初始化全局变量。
      • 初始化设备驱动(I2C、SPI、USB、存储设备)。
      • 初始化环境变量(env)。
      • 初始化控制台。
      • 调用 run_main_loop() 进入命令行。
        • for (;;) main_loop(); // to common/Main.c
  • common/main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main_loop(void)
{
const char *s;

modem_init();

cli_init();

run_preboot_environment_command();

// get bootdelay of uboot and store it in a global variable: stored_bootdelay
// return bootcmd through: getenv("bootcmd")
s = bootdelay_process();

// abortboot(stored_bootdelay)进行倒计时
// 未打断:run_command_list(s, -1, 0); 启动内核
// 打断: 返回
autoboot_command(s);

// 调用cli_simple_loop(): for(;;) cli_readline(CONFIG_SYS_PROMPT);// 接受用户端命令:common/cli_readline.c
// ... run_command_repeatable(lastcommand, flag); // 执行用户端命令
cli_loop();
}
  • cli_loop时执行输入命令的核心函数(详解)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
int cli_simple_run_command(const char *cmd, int flag)
{
// 定义一个缓冲区,用来存放传入的命令字符串的副本。
// 不能直接操作 cmd,因为 getenv() 返回的环境变量字符串可能在执行过程中被修改。
char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */
// token 指向当前解析的命令起始位置。
char *token; /* start of token in cmdbuf */
// sep 用于找到命令分隔符(比如 ';')的位置。
char *sep; /* end of token (separator) in cmdbuf */
// finaltoken 存放替换宏后的命令(比如把 $bootcmd 替换为实际值)。
char finaltoken[CONFIG_SYS_CBSIZE];
// str 是解析循环中当前处理位置的指针。
char *str = cmdbuf;
// argv 保存分解后的参数列表,argv[0] 是命令名,argv[1..n] 是参数。
char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */

int argc, inquotes;
int repeatable = 1; // 标记命令是否可以重复执行(大多数命令是可以的)。
int rc = 0; // 返回值,默认成功。

debug_parser("[RUN_COMMAND] cmd[%p]=\"", cmd);
if (DEBUG_PARSER) {
/* 打印调试信息,显示传入的命令内容 */
puts(cmd ? cmd : "NULL");
puts("\"\n");
}

// 清除 Ctrl-C 标志,防止上一次命令中断状态影响当前命令。
clear_ctrlc(); /* forget any previous Control C */

// 如果命令为空或是空字符串,直接返回 -1。
if (!cmd || !*cmd)
return -1; /* empty command */

// 检查命令长度是否超出缓冲区大小。
if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {
puts("## Command too long!\n");
return -1;
}
// 把命令复制到 cmdbuf 中,后续操作基于这个副本。
strcpy(cmdbuf, cmd);
/*
* 主循环,处理命令分隔符 ';',即一行命令可以包含多个子命令。
* 比如: "setenv bootargs console=ttyS0; bootm 0x80007fc0"
*/
debug_parser("[PROCESS_SEPARATORS] %s\n", cmd);
while (*str) {
/*
* 查找命令分隔符 ';',如果命令中有引号,要忽略引号中的 ';'
* 允许用 "\;" 转义分号。
*/
for (inquotes = 0, sep = str; *sep; sep++) {
if ((*sep == '\'') &&
(*(sep - 1) != '\\'))
inquotes = !inquotes;

if (!inquotes &&
(*sep == ';') && /* 找到分隔符 */
(sep != str) && /* 确保不是第一个字符 */
(*(sep - 1) != '\\')) /* 并且没有被转义 */
break;
}
/*
* token = 当前命令字符串(不包括 ';')
*/
token = str;
if (*sep) {
// 如果找到 ';',终止当前命令,并移动 str 到下一个命令起始位置。
str = sep + 1; /* start of command for next pass */
*sep = '\0'; /* 把 ';' 替换成 '\0',形成单独的字符串 */
} else {
// 如果没有 ';',说明这是最后一个命令。
str = sep; /* no more commands for next pass */
}
debug_parser("token: \"%s\"\n", token);
/*
* 处理宏替换,比如 $bootcmd 替换成 "bootm 0x80007fc0"。
*/
process_macros(token, finaltoken);
/*
* 把替换后的命令按空格切分成 argv[]。
* 例如:"bootm 0x80007fc0" → argv[0]="bootm", argv[1]="0x80007fc0"
*/
argc = cli_simple_parse_line(finaltoken, argv);
if (argc == 0) {
rc = -1; /* 没有有效命令 */
continue;
}
/*
* 调用命令处理函数:
* - 根据 argv[0] 查找命令表 cmd_tbl_t
* - 找到后调用对应的执行函数,比如 do_bootm()
*/
if (cmd_process(flag, argc, argv, &repeatable, NULL))
rc = -1;
/*
* 如果用户按了 Ctrl-C,立即中止,不再执行后续命令。
*/
if (had_ctrlc())
return -1; /* if stopped then not repeatable */
}

/*
* 返回 rc,如果 rc 为 0,则返回 repeatable,表示该命令是否可以重复执行。
*/
return rc ? rc : repeatable;
}
  • 核心为:

    • 调用命令处理函数:根据 argv[0] 查找命令表 cmd_tbl_t, 找到后调用对应的执行函数,比如 do_bootm()
    • cmd_process(flag, argc, argv, &repeatable, NULL)
      • cmdtp = find_cmd(argv[0]); /* Look up command in command table */

        for (cmdtp = table;
             cmdtp != table + table_len;
             cmdtp++) {
            if (strncmp (cmd, cmdtp->name, len) == 0) {
                if (len == strlen (cmdtp->name))
                    return cmdtp;    /* full match */
        
                cmdtp_temp = cmdtp;    /* abbreviated command ? */
                n_found++;
            }
        }
        
      • if (argc > cmdtp->maxargs) /* found - check max args */
      • rc = cmd_call(cmdtp, flag, argc, argv); /* If OK so far, then do the command */
        • // 直接调用命令函数指针,相当于执行 do_bootm(cmdtp, flag, argc, argv)
        • result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
  • U-Boot bootm 命令流程解析

bootm 命令是 U-Boot 用来启动 Linux 内核或其他操作系统映像的入口命令。内部核心是 do_bootm(),它会依次执行几个状态,每个状态对应不同的操作。

1
2
3
4
5
6
7
do_bootm()
└─> do_bootm_states()
├─ BOOTM_STATE_START
├─ BOOTM_STATE_FINDOS
├─ BOOTM_STATE_FINDOTHER
├─ BOOTM_STATE_LOADOS
└─ BOOTM_STATE_OS_GO
  1. BOOTM_STATE_START → bootm_start()
  • 初始化启动上下文 (bootm_headers 等结构体)。
  • 检查镜像类型(uImage、FIT、Legacy 等)。
  • 验证镜像合法性(如 magic number、checksum)。
  • 确定后续加载流程是否需要解压。

关键点:

  • 这里是整个 bootm 流程的“起点”,决定后续镜像解析路径。
  • 如果启动 FIT 镜像,还会解析 FIT 描述符。
  1. BOOTM_STATE_FINDOS → bootm_find_os() → boot_get_kernel()
  • 找到操作系统内核镜像在内存中的位置。
  • 设置内核映像地址(image->load)和类型。
  • 对不同内核格式(zImage、uImage)做处理:
    • uImage:提取 header,获取 load address。
    • zImage:直接获取 load 地址,通常包含自解压代码。

关键点:

  • 这个阶段不执行实际加载,只是确定内核在哪,大小是多少。
  • 对于 FIT 镜像,会解析 kernel 节点及其 load 地址。
  1. BOOTM_STATE_FINDOTHER → bootm_find_other()
  • 查找其他启动必需镜像:
    • ramdisk(initrd/initramfs)
    • FDT(Device Tree Blob,硬件描述)
  • 根据镜像类型和配置设置内存地址。
  • 为内核启动准备参数。

关键点:

  • 确保内核启动时能够找到根文件系统和硬件信息。
  • FIT 镜像会在这里解析 DTB 节点、ramdisk 节点。
  1. BOOTM_STATE_LOADOS → bootm_load_os() → decomp_image()
  • 将内核镜像从存储介质(flash、SD、TFTP 等)加载到内存。
  • 如果内核是压缩的(如 gzip/zImage/uImage),调用 decomp_image() 解压到目标地址。
  • 同时加载 ramdisk 和 FDT 到内存指定位置。

关键点:

  • 这里是真正把内核和相关资源搬到内存执行区的步骤。
  • decomp_image() 内部处理压缩算法和解压回调。
  1. BOOTM_STATE_OS_GO → bootm_os_get_boot_func() → 跳转内核
  • 获取内核启动入口(kernel_entry)。
  • 根据内核类型和架构,设置启动参数(如 ATAGs、FDT 指针、ramdisk 地址)。
  • 调用函数指针,真正跳转到内核执行。

关键点:

  • U-Boot 在这里退出,CPU 权限切换到内核环境。
  • 后续流程由内核接管。
  • 初始化本阶段要使用的硬件设备
  • 检测系统内存映射
  • 将内核映像和根文件系统映像从Flash读到RAM空间
  • 为内核设置启动参数
  • 调用内核

系统上电后的完整流程 (示意):
系统上电 -> 设置为SVC工作模式 -> 关闭看门狗 -> 清空CACHE -> 禁止MMU -> 清空BSS段 -> 一系列硬件的初始化 -> 执行bootcmd中的命令 (加载linux内核) -> 设置为SVC工作模式 -> 检查CPUid是否支持 -> 创建页表 -> 开启MMU -> 创建子线程 -> 子线程中挂载指定的根文件系统 -> 启动用户空间1号进程 -> 开启后续用户空间进程 -> 启动一个shell -> 用户可以输入命令

imx6ull Uboot 启动详细函数调用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)
|-->_start:(arch/arm/lib/vectors.S)
|-->reset(arch/arm/cpu/armv7/start.S)
|-->save_boot_params(arch/arm/cpu/armv7/start.S)/*厂商自定义,将引导参数保存到内存中*/
|-->save_boot_params_ret(arch/arm/cpu/armv7/start.S)/* 根据reset的运行地址和链接地址进行计算偏移量,遍历.rel.dyn 执行重定位(在flash/ SRAM上的重定位)。同时检查CPU模式,关闭中断,设置中断向量表*/
|-->cpu_init_cp15(arch/arm/cpu/armv7/start.S)/*初始化CPU: 失效 L1 I/D Cache,配置CP15协处理器(MMU/catch相关寄存器)*/
|-->cpu_init_crit(arch/arm/cpu/armv7/start.S)
|-->lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)/* 设置临时栈,设置r9(gd:始终频率/内存基址/设备树指针..) */
|-->_main(arch/arm/lib/crt0.S)
|-->board_init_f_alloc_reserve(common/init/board_init.c)/*为u-boot的gd结构体分配空间*/
|-->board_init_f_init_reserve(common/init/board_init.c) /*将gd结构体清零*/
|-->board_init_f(common/board_f.c)
|-->initcall_run_list(include/initcall.h) /*初始化序列函数*/
|-->init_sequence_f[](common/board_f.c) /* 初始化序列函数数组 */
|-->board_early_init_f(board/freescale/mx6ull_toto/mx6ull_toto.c)/*初始化串口的IO配置*/
|-->timer_init(arch/arm/imx-common/timer.c) /*初始化内核定时器,为uboot提供时钟节拍*/
|-->init_baud_rate(common/board_f.c) /*初始化波特率*/
|-->serial_init(drivers/serial/serial.c) /*初始化串口通信设置*/
|-->console_init_f(common/console.c) /*初始化控制台*/
|-->...
|-->relocate_code(arch/arm/lib/relocate.S) /*主要完成镜像拷贝和重定位*/
|-->relocate_vectors(arch/arm/lib/relocate.S)/*重定位向量表*/
|-->board_init_r(common/board_r.c)/*板级初始化*/
|-->initcall_run_list(include/initcall.h)/*初始化序列函数*/
|-->init_sequence_r[](common/board_f.c)/*序列函数*/
|-->initr_reloc(common/board_r.c) /*设置 gd->flags,标记重定位完成*/
|-->serial_initialize(drivers/serial/serial-uclass.c)/*初始化串口*/
|-->serial_init(drivers/serial/serial-uclass.c) /*初始化串口*/
|-->initr_mmc(common/board_r.c) /*初始化emmc*/
|-->mmc_initialize(drivers/mmc/mmc.c)
|-->mmc_do_preinit(drivers/mmc/mmc.c)
|-->mmc_start_init(drivers/mmc/mmc.c)
|-->console_init_r(common/console.c) /*初始化控制台*/
|-->interrupt_init(arch/arm/lib/interrupts.c) /*初始化中断*/
|-->initr_net(common/board_r.c) /*初始化网络设备*/
|-->eth_initialize(net/eth-uclass.c)
|-->eth_common_init(net/eth_common.c)
|-->phy_init(drivers/net/phy/phy.c)
|-->uclass_first_device_check(drivers/core/uclass.c)
|-->uclass_find_first_device(drivers/core/uclass.c)
|-->device_probe(drivers/core/device.c)
|-->device_of_to_plat(drivers/core/device.c)
|-->drv->of_to_plat
|-->fecmxc_of_to_plat(drivers/net/fec_mxc.c)/*解析设备树信息*/
|-->device_get_uclass_id(drivers/core/device.c)
|-->uclass_pre_probe_device(drivers/core/uclass.c)
|-->drv->probe(dev)
/*drivers/net/fec_mxc.c*/
U_BOOT_DRIVER(fecmxc_gem) = {
.name = "fecmxc",
.id = UCLASS_ETH,
.of_match = fecmxc_ids,
.of_to_plat = fecmxc_of_to_plat,
.probe = fecmxc_probe,
.remove = fecmxc_remove,
.ops = &fecmxc_ops,
.priv_auto = sizeof(struct fec_priv),
.plat_auto = sizeof(struct eth_pdata),
};
|-->fecmxc_probe(drivers/net/fec_mxc.c)/*探测和初始化*/
|-->fec_get_miibus(drivers/net/fec_mxc.c)
|-->mdio_alloc(drivers/net/fec_mxc.c)
|-->bus->read = fec_phy_read;
|-->bus->write = fec_phy_write;
|-->mdio_register(common/miiphyutil.c)
|-->fec_mii_setspeed(drivers/net/fec_mxc.c)
|-->fec_phy_init(drivers/net/fec_mxc.c)
|-->device_get_phy_addr(drivers/net/fec_mxc.c)
|-->phy_connect(drivers/net/phy/phy.c)
|-->phy_find_by_mask(drivers/net/phy/phy.c)
|-->bus->reset(bus)
|-->get_phy_device_by_mask(drivers/net/phy/phy.c)
|-->create_phy_by_mask(drivers/net/phy/phy.c)
|-->phy_device_create(drivers/net/phy/phy.c)
|-->phy_probe(drivers/net/phy/phy.c)
|-->phy_connect_dev(drivers/net/phy/phy.c)
|-->phy_reset(drivers/net/phy/phy.c)
|-->phy_config(drivers/net/phy/phy.c)
|-->board_phy_config(drivers/net/phy/phy.c)
|-->phydev->drv->config(phydev)
/*drivers/net/phy/smsc.c*/
static struct phy_driver lan8710_driver = {
.name = "SMSC LAN8710/LAN8720",
.uid = 0x0007c0f0,
.mask = 0xffff0,
.features = PHY_BASIC_FEATURES,
.config = &genphy_config_aneg,
.startup = &genphy_startup,
.shutdown = &genphy_shutdown,
};
|-->genphy_config_aneg(drivers/net/phy/phy.c)
|-->phy_reset(需要手动调用)(drivers/net/phy/phy.c)
|-->genphy_setup_forced(drivers/net/phy/phy.c)
|-->genphy_config_advert(drivers/net/phy/phy.c)
|-->genphy_restart_aneg(drivers/net/phy/phy.c)
|-->uclass_post_probe_device(drivers/core/uclass.c)
|-->uc_drv->post_probe(drivers/core/uclass.c)
/*net/eth-uclass.c*/
UCLASS_DRIVER(ethernet) = {
.name = "ethernet",
.id = UCLASS_ETH,
.post_bind = eth_post_bind,
.pre_unbind = eth_pre_unbind,
.post_probe = eth_post_probe,
.pre_remove = eth_pre_remove,
.priv_auto = sizeof(struct eth_uclass_priv),
.per_device_auto = sizeof(struct eth_device_priv),
.flags = DM_UC_FLAG_SEQ_ALIAS,
};
|-->eth_post_probe(net/eth-uclass.c)
|-->eth_write_hwaddr(drivers/core/uclass.c)
|-->...
|-->run_main_loop(common/board_r.c)/*主循环,处理命令*/
|-->main_loop(common/main.c)
|-->bootdelay_process(common/autoboot.c) /*读取环境变量bootdelay和bootcmd的内容*/
|-->autoboot_command(common/autoboot.c) /*倒计时按下执行,没有操作执行bootcmd的参数*/
|-->abortboot(common/autoboot.c)
|-->printf("Hit any key to stop autoboot: %2d ", bootdelay);
/*到这里就是我们看到uboot延时3s启动内核的地方*/
|-->cli_loop(common/cli.c) /*倒计时按下space键,执行用户输入命令*/

图示
Uboot-struct

U-boot 移植及源码分析( ARM )

https://goko-son626.github.io/post/u-boot.html

作者

GoKo Mell

发布于

2024-09-30

更新于

2025-09-14

许可协议

评论

:D 一言句子获取中...