U-Boot知识扫盲

序言

U-Boot 是什么

BootLoader 是引导加载程序,uboot 是其中较为常用的一个, uboot 就是一个裸机程序,一般用为初始化硬件设备为启动系统做准备,一般主要工作是初始化 DDR,但是 IMX6ULL 的 DDR 不是在 uboot 实现的。

U-Boot 源码目录解析

分析前需要先编译 uboot 源码,才会生成一些文件,源码会有许多目录,一般我们只需要重点关注一下目录即可:

  • arch/:存放一些与架构相关的文件。

  • board/:board 目录是与具体的板子相关的,我们关注下面的 freescale 目录即可。

  • configs/:uboot 配置文件以 defconfig 结尾,不同板子对应不同配置文件,我们在编译 uboot 之前都是先输入命令 make xxx_defconfig 命令进行配置 uboot。

  • 顶层目录下有 README 文件,该文件描述了 uboot 的详细信息和如何编译 uboot、uboot 中各目录的含义、相应的命令等。

  • arch/arm/cpu/u-boot.lds 是整个 uboot 的连接脚本

  • board/freescale/mx6ullevk

  • configs 目录是 uboot 的默认配置文件目录,文件以 defconfig 结尾,不同的配置文件对应不同的板子。

所有使用 freescale 芯片的板子都放到此文件夹(board/freescale)中,I.MX 系列以前属于 freescale,只是 freescale 后来被 NXP 收购了。

U-Boot 顶层 Makefile 分析

分析 uboot 源码顶层目录 Makefile 文件可以快速理清 uboot 编译流程。使用 VSCode 进行分析,因为 uboot 源码目录下兼容了许多架构和板子,但是我们这里只关注 IMX6ULL 相应的目录即可,所以可以使用 VSCode 对一些无关的目录进行屏蔽(不是删除)

新建 .vscode 目录添加 settings.json 文件按需添加内容:

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.o":true,
"**/*.su":true,
"**/*.cmd":true,
"arch/arc":true,
"arch/avr32":true,
"arch/blackfin":true,
"arch/m68k":true,
"arch/microblaze":true,
"arch/mips":true,
"arch/nds32":true,
"arch/nios2":true,
"arch/openrisc":true,
"arch/powerpc":true,
"arch/sandbox":true,
"arch/sh":true,
"arch/sparc":true,
"arch/x86":true,
"arch/arm/mach*":true,
"arch/arm/cpu/arm11*":true,
"arch/arm/cpu/arm720t":true,
"arch/arm/cpu/arm9*":true,
"arch/arm/cpu/armv7m":true,
"arch/arm/cpu/armv8":true,
"arch/arm/cpu/pxa":true,
"arch/arm/cpu/sa1100":true,
"board/[a-e]*":true,
"board/[g-z]*":true,
"board/[0-9]*":true,
"board/[A-Z]*":true,
"board/fir*":true,
"board/freescale/b*":true,
"board/freescale/l*":true,
"board/freescale/m5*":true,
"board/freescale/mp*":true,
"board/freescale/c29*":true,
"board/freescale/cor*":true,
"board/freescale/mx7*":true,
"board/freescale/mx2*":true,
"board/freescale/mx3*":true,
"board/freescale/mx5*":true,
"board/freescale/p*":true,
"board/freescale/q*":true,
"board/freescale/t*":true,
"board/freescale/v*":true,
"board/freescale/imx8*":true,
"board/freescale/mx6d*":true,
"board/freescale/mx6q*":true,
"board/freescale/mx6s*":true,
"board/freescale/mult*":true,
"board/freescale/s32*":true,
"board/freescale/mx6ul_*":true,
"configs/[a-l]*":true,
"configs/[n-z]*":true,
"configs/[A-Z]*":true,
"configs/M[a-z]*":true,
"configs/M[A-Z]*":true,
"configs/M[0-9]*":true,
"configs/m[a-w]*":true,
"configs/m[0-9]*":true,
"configs/[0-9]*":true,
"configs/mx6c*":true,
"configs/mx6d*":true,
"configs/mx6q*":true,
"configs/mx6s*":true,
"configs/mx7*":true,
"configs/mx8*":true,
"configs/mx23*":true,
"configs/mx25*":true,
"configs/mx28*":true,
"configs/mx31*":true,
"configs/mx35*":true,
"configs/mx51*":true,
"configs/mx53*":true,
"configs/mx6ul_*":true,
"include/configs/[a-l]*":true,
"include/configs/[n-z]*":true,
"include/configs/[A-Z]*":true,
"include/configs/m[a-w]*":true,
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.o":true,
"**/*.su":true,
"**/*.cmd":true,
"arch/arc":true,
"arch/avr32":true,
"arch/blackfin":true,
"arch/m68k":true,
"arch/microblaze":true,
"arch/mips":true,
"arch/nds32":true,
"arch/nios2":true,
"arch/openrisc":true,
"arch/powerpc":true,
"arch/sandbox":true,
"arch/sh":true,
"arch/sparc":true,
"arch/x86":true,
"arch/arm/mach*":true,
"arch/arm/cpu/arm11*":true,
"arch/arm/cpu/arm720t":true,
"arch/arm/cpu/arm9*":true,
"arch/arm/cpu/armv7m":true,
"arch/arm/cpu/armv8":true,
"arch/arm/cpu/pxa":true,
"arch/arm/cpu/sa1100":true,
"board/[a-e]*":true,
"board/[g-z]*":true,
"board/[0-9]*":true,
"board/[A-Z]*":true,
"board/fir*":true,
"board/freescale/b*":true,
"board/freescale/l*":true,
"board/freescale/m5*":true,
"board/freescale/mp*":true,
"board/freescale/c29*":true,
"board/freescale/cor*":true,
"board/freescale/mx7*":true,
"board/freescale/mx2*":true,
"board/freescale/mx3*":true,
"board/freescale/mx5*":true,
"board/freescale/p*":true,
"board/freescale/q*":true,
"board/freescale/t*":true,
"board/freescale/v*":true,
"board/freescale/imx8*":true,
"board/freescale/mx6d*":true,
"board/freescale/mx6q*":true,
"board/freescale/mx6s*":true,
"board/freescale/mult*":true,
"board/freescale/s32*":true,
"board/freescale/mx6ul_*":true,
"configs/[a-l]*":true,
"configs/[n-z]*":true,
"configs/[A-Z]*":true,
"configs/M[a-z]*":true,
"configs/M[A-Z]*":true,
"configs/M[0-9]*":true,
"configs/m[a-w]*":true,
"configs/m[0-9]*":true,
"configs/[0-9]*":true,
"configs/mx6c*":true,
"configs/mx6d*":true,
"configs/mx6q*":true,
"configs/mx6s*":true,
"configs/mx7*":true,
"configs/mx8*":true,
"configs/mx23*":true,
"configs/mx25*":true,
"configs/mx28*":true,
"configs/mx31*":true,
"configs/mx35*":true,
"configs/mx51*":true,
"configs/mx53*":true,
"configs/mx6ul_*":true,
"include/configs/[a-l]*":true,
"include/configs/[n-z]*":true,
"include/configs/[A-Z]*":true,
"include/configs/m[a-w]*":true,
}
}

其中 search.exclude 里面是需要在搜索结果中排除的文件或者文件夹,files.exclude 是左侧工程目录中需要排除的文件或者文件夹。

一些重要的变量

版本号

1

MAKEFLAGS

命令输出

静默输出

U-Boot 启动流程

重定位的相关知识

位置相关码、位置无关码、代码段(text 段)、数据段(data 段)、BSS 段。

uboot 与 代码重定位 - schips - 博客园 (cnblogs.com)

链接器和链接脚本

我们知道源代码编译成可执行文件的步骤为:预处理、编译、汇编、链接。最后一步链接就是把一个或多个输入文件合并成一个输出文件,输入文件是目标文件或链接脚本文件,输出文件是目标文件或者可执行文件,这部分是由链接器执行的,负责从链接脚本读完一个 section 后,将定位器符号的值增加该 section 的大小。

链接脚本:链接脚本的一个主要目的是描述输入文件中的各个段(数据段,代码段,堆,栈,bss)如何被映射到输出文件中,并控制输出文件的各部分在程序地址空间内的布局,地址空间包括 ROM 和 RAM。
链接器总是使用链接脚本的,如果你不提供,则链接器会使用一个缺省的脚本,这个脚本是被编译进链接器可执行文件的。

链接脚本 u-boot.lds

链接脚本.lds(详细)总结附实例快速掌握-CSDN博客

没有编译过 uboot 的话链接脚本为 arch/arm/cpu/u-boot.lds,编译之后就会在这个脚本基础上在根目录下生成最终的脚本文件 u-boot.lds

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
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.__efi_runtime_start : {
*(.__efi_runtime_start)
}
.efi_runtime : {
*(efi_runtime_text)
*(efi_runtime_data)
}
.__efi_runtime_stop : {
*(.__efi_runtime_stop)
}
.efi_runtime_rel_start :
{
*(.__efi_runtime_rel_start)
}
.efi_runtime_rel : {
*(.relefi_runtime_text)
*(.relefi_runtime_data)
}
.efi_runtime_rel_stop :
{
*(.__efi_runtime_rel_stop)
}
. = ALIGN(8);
.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 = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
.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));
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}

u-boot.map 是 uboot 的映射文件,这个文件可以看出各个文件或函数链接的地址。

u-boot.lds 中有一些跟地址相关的重要变量,可以在 u-boot.map 中找到对应地址:

变量 数值 描述
__image_copy_start 0x87800000 uboot 拷贝的首地址
__image_copy_end 0x8786ba30 uboot 拷贝的结束地址
__rel_dyn_start 0x8786ba30 .rel.dyn 段起始地址
__rel_dyn_end 0x87875f68 .rel.dyn 段结束地址
_image_binary_end 0x87875f68 镜像结束地址
__bss_start 0x8786ba30 .bss 段起始地址
__bss_end 0x878a3930 .bss 段结束地址

其中只有 __image_copy_start 的地址是固定的 0x87800000,其余的变量会根据 uboot 的配置改变。

U-Boot 启动流程详解

u-boot.lds 中可以得知入口点是 arch/arm/lib/vectors.S 文件中的 _start,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
*************************************************************************
*
* Exception vectors as described in ARM reference manuals
*
* Uses indirect branch to allow reaching handlers anywhere in memory.
*
*************************************************************************
*/

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG /* .word表示四字节对齐 */
#endif

b reset /* 跳转到reset处,b为的无条件跳转 */
ldr pc, _undefined_instruction /* 未定义指令异常向量 */
ldr pc, _software_interrupt /* 预取指令异常向量 */
ldr pc, _prefetch_abort /* 预取指令异常向量 */
ldr pc, _data_abort /* 数据操作异常向量 */
ldr pc, _not_used /* 没有使用 */
ldr pc, _irq /* irq中断向量 */
ldr pc, _fiq /* fiq中断向量 */

当 cpu 产生异常时,便会将对应的异常入口地址加载到 pc 中,进而处理相应的异常处理程序。 各个异常向量具体描述如下表格所示:

地址 异常 进入模式 描述
0x00000000 复位 特权模式(SVC) 位电平有效时,产生复位异常,程序跳转到复位处理程序处执行
0x00000004 未定义指令 未定义指令中止模式(Undef) 遇到不能处理的指令时,产生未定义指令异
0x00000008 软件中断 特权模式(SVC) 执行 SWI 指令产生,用于用户模式下的程序调用特权操作指令
0x0000000c 预存指令 中止模式 处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常
0x00000010 数据操作 中止模式 处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常
0x00000014 未使用 未使用 未使用
0x00000018 IRQ 外部中断模式(IRQ) 外部中断请求有效,且 CPSR 中的 I 位为 0时,产生 IRQ 异常
0x0000001c FIQ 快速中断模式(FIQ) 快速中断请求引脚有效,且 CPSR 中的 F 位为 0 时,产生 FIQ 异常

其中复位异常向量指令 b reset 决定 uboot 启动或者复位后自动跳转到 reset 标志处执行,搜索可知 reset 在 arch/arm/cpu/armv7/start.S 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*************************************************************************
*
* Startup Code (reset vector)
*
* Do important init only if we don't start from memory!
* Setup memory and board specific bits prior to relocation.
* Relocate armboot to ram. Setup stack.
*
*************************************************************************/

.globl reset
.globl save_boot_params_ret
reset:
/* Allow the board to save important registers */
b save_boot_params
save_boot_params_ret:

reset 只有一条跳转指令 b save_boot_params,搜索得到:

1
2
3
4
5
6
7
8
9
10
11
/*************************************************************************
*
* void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
* __attribute__((weak));
*
* Stack pointer is not yet initialized at this moment
* Don't save anything to stack even if compiled with -O0
*
*************************************************************************/
ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller

又是一条跳转指令 b save_boot_params_ret 标志代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
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

主要工作:设置 cpsr(Current Program Status Register)的值,将 CPU 模式设置为 SVC32 模式,禁止 FIQ 和 IRQ。

打开《arm_architecture_reference_manual ARMv7-A and ARMv7-R edition》ARMv7架构参考手册,具体看下 cpsr 的位域结构,如下图所示:

红色方框是 save_boot_params_ret 标志要设置的位域,位域 M[4:0] 决定了当前 cpu 的工作模式,而位域F[6] 为 FIQ 中断屏蔽位,位域 I[7] 为 IRQ 中断屏蔽位,位域 T[5] 为 Thumb 执行状态位(此位没有设置,可忽略),模式位域 M[4:0] 详情如下表格所示:

接着执行后续代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register

/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif

主要工作:将 SCTLR 中位域 V[13] 清零,使得软件可以重定位向量表,然后设置 VBAR 指向向量表以实现向量表定位到 0x87800000 地址处。

由注释可知这段代码要设置 SCTLR(系统控制寄存器)

未找到图片04|

SCTLR 寄存器用于控制标准内存和系统设备,并且为在硬件内核中实现的功能提供状态信息,其中位域 V[13] 的作用是选择异常向量表的基地址,根据 ARMv7 架构参考手册描述可知,当往 V[13] 填 0 时,异常向量表的基地址=0x00000000,并且该地址可以被 re-mapped(重映射);当往 V[13] 填 1 时,异常向量表的基地址=0xffff0000,此时该地址不能被重映射。 源码中大量用到了 mrc 和 mcr 指令,mrc 为协处理器寄存器到 ARM 处理器寄存器的数据传送指令,mcr 为 ARM 处理器寄存器到协处理器寄存器的数据传送指令。

接着后续代码:

1
2
3
4
5
6
7
	/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
bl cpu_init_crit
#endif
#endif

搜索 cpu_init_cp15

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*************************************************************************
*
* cpu_init_cp15
*
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*
*************************************************************************/
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
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 bits 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

#ifdef CONFIG_ARM_ERRATA_716044
mrc p15, 0, r0, c1, c0, 0 @ read system control register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif

#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 4 @ set bit #4
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif

#ifdef CONFIG_ARM_ERRATA_743622
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 6 @ set bit #6
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif

#ifdef CONFIG_ARM_ERRATA_751472
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 21 @ set bit #21
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_845369
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 22 @ set bit #22
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif

mov r5, lr @ Store my Caller
mrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (MIDR)
mov r3, r1, lsr #20 @ get variant field
and r3, r3, #0xf @ r3 has CPU variant
and r4, r1, #0xf @ r4 has CPU revision
mov r2, r3, lsl #4 @ shift variant field for combined value
orr r2, r4, r2 @ r2 has combined CPU variant + revision

#ifdef CONFIG_ARM_ERRATA_798870
cmp r2, #0x30 @ Applies to lower than R3p0
bge skip_errata_798870 @ skip if not affected rev
cmp r2, #0x20 @ Applies to including and above R2p0
blt skip_errata_798870 @ skip if not affected rev

mrc p15, 1, r0, c15, c0, 0 @ read l2 aux ctrl reg
orr r0, r0, #1 << 7 @ Enable hazard-detect timeout
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_l2aux_ctrl
isb @ Recommended ISB after l2actlr update
pop {r1-r5} @ Restore the cpu info - fall through
skip_errata_798870:
#endif

#ifdef CONFIG_ARM_ERRATA_801819
cmp r2, #0x24 @ Applies to lt including R2p4
bgt skip_errata_801819 @ skip if not affected rev
cmp r2, #0x20 @ Applies to including and above R2p0
blt skip_errata_801819 @ skip if not affected rev
mrc p15, 0, r0, c0, c0, 6 @ pick up REVIDR reg
and r0, r0, #1 << 3 @ check REVIDR[3]
cmp r0, #1 << 3
beq skip_errata_801819 @ skip erratum if REVIDR[3] is set

mrc p15, 0, r0, c1, c0, 1 @ read auxilary control register
orr r0, r0, #3 << 27 @ Disables streaming. All write-allocate
@ lines allocate in the L1 or L2 cache.
orr r0, r0, #3 << 25 @ Disables streaming. All write-allocate
@ lines allocate in the L1 cache.
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
skip_errata_801819:
#endif

#ifdef CONFIG_ARM_ERRATA_454179
cmp r2, #0x21 @ Only on < r2p1
bge skip_errata_454179

mrc p15, 0, r0, c1, c0, 1 @ Read ACR
orr r0, r0, #(0x3 << 6) @ Set DBSM(BIT7) and IBE(BIT6) bits
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through

skip_errata_454179:
#endif

#ifdef CONFIG_ARM_ERRATA_430973
cmp r2, #0x21 @ Only on < r2p1
bge skip_errata_430973

mrc p15, 0, r0, c1, c0, 1 @ Read ACR
orr r0, r0, #(0x1 << 6) @ Set IBE bit
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through

skip_errata_430973:
#endif

#ifdef CONFIG_ARM_ERRATA_621766
cmp r2, #0x21 @ Only on < r2p1
bge skip_errata_621766

mrc p15, 0, r0, c1, c0, 1 @ Read ACR
orr r0, r0, #(0x1 << 5) @ Set L1NEON bit
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through

skip_errata_621766:
#endif

mov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)

主要工作:关闭 ICache 和 MMU。

到这里我们再总结一下上面这段代码的功能含义,首先,我们为何要关闭mmu?mmu负责从虚拟地址到物理地址之间的转换,但是我们现在的汇编都是直接操作物理寄存器, 此时如果打开了mmu,而我们并没有有效的TLB,这样cpu可以说是胡乱运行的,所以我们需要关闭mmu,不需要它转换地址,直接操作寄存器方便快捷。 然后,再发出灵魂拷问,为何要关闭cache?因为cache和MMU是通过cp15管理的,刚上电的时候,CPU并不能管理他们。所以上电的时候mmu必须关闭,指令cache可关闭,可不关闭,但数据cache一定要关闭, 否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM中数据还没有cache过来,导致数据预取异常

搜索 cpu_init_crit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#if !defined(CONFIG_SKIP_LOWLEVEL_INIT) && \
!defined(CONFIG_SKIP_LOWLEVEL_INIT_ONLY)
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
#endif

lowlevel_init 函数

跳转到 lowlevel_init 函数(arch/arm/cpu/armv7/lowlevel_init.S):

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
ENTRY(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =CONFIG_SPL_STACK
#else
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr}

/*
* Call the very early init function. This should do only the
* absolute bare minimum to get started. It should not:
*
* - set up DRAM
* - use global_data
* - clear BSS
* - try to start a console
*
* For boards with SPL this should be empty since SPL can do all of
* this init in the SPL board_init_f() function which is called
* immediately after this.
*/
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)

其中 sp 指向 CONFIG_SYS_INIT_SP_ADDR 这个地址,CONFIG_SYS_INIT_SP_ADDRinclude/configs/mx6ullevk.h 文件中定义如下:

1
2
3
4
5
6
7
8
#define CONFIG_SYS_SDRAM_BASE		PHYS_SDRAM
#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE

#define CONFIG_SYS_INIT_SP_OFFSET \
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

IRAM_BASE_ADDRIRAM_SIZEarch/arm/include/asm/arch-mx6/imx-regs.h 文件中定义如下:

1
2
3
4
5
6
7
8
9
10
#define IRAM_BASE_ADDR			0x00900000

······

#if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \
defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
#define IRAM_SIZE 0x00040000
#else
#define IRAM_SIZE 0x00020000
#endif

因为在根目录下 .config 文件中配置了 CONFIG_MX6UL=y 选项,所以最终 IRAM_SIZE = 0x00020000,IRAM_BASE_ADDR = 0x00900000。

GENERATED_GBL_DATA_SIZEinclude/generated/generic-asm-offsets.h 文件中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef __GENERIC_ASM_OFFSETS_H__
#define __GENERIC_ASM_OFFSETS_H__
/*
* DO NOT MODIFY.
*
* This file was generated by Kbuild
*/

#define GENERATED_GBL_DATA_SIZE 256 /* (sizeof(struct global_data) + 15) & ~15 @ */
#define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */
#define GD_SIZE 248 /* sizeof(struct global_data) @ */
#define GD_BD 0 /* offsetof(struct global_data, bd) @ */
#define GD_MALLOC_BASE 188 /* offsetof(struct global_data, malloc_base) @ */
#define GD_RELOCADDR 48 /* offsetof(struct global_data, relocaddr) @ */
#define GD_RELOC_OFF 68 /* offsetof(struct global_data, reloc_off) @ */
#define GD_START_ADDR_SP 64 /* offsetof(struct global_data, start_addr_sp) @ */

#endif

可知 GENERATED_GBL_DATA_SIZE = 256 = 0x00000100,由此可得:

1
2
CONFIG_SYS_INIT_SP_OFFSET = 0x000200000x00000100 = 0x0001FF00
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0X0001FF00 = 0X0091FF00

由注释 8-byte alignment for ABI compliance 可知 CONFIG_SYS_INIT_SP_ADDR 需要遵从 ABI 的 8 字节对齐。

接下来是 s_init 函数,在文件 arhc/arm/cpu/armv7/mx6/soc.c 中定义:

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
void s_init(void)
{
struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
u32 mask480;
u32 mask528;
u32 reg, periph1, periph2;

#if defined(CONFIG_ANDROID_SUPPORT)
/* Enable RTC */
writel(0x21, 0x020cc038);
#endif
if (is_mx6sx() || is_mx6ul() || is_mx6ull() || is_mx6sll())
return;

/* Due to hardware limitation, on MX6Q we need to gate/ungate all PFDs
* to make sure PFD is working right, otherwise, PFDs may
* not output clock after reset, MX6DL and MX6SL have added 396M pfd
* workaround in ROM code, as bus clock need it
*/

mask480 = ANATOP_PFD_CLKGATE_MASK(0) |
ANATOP_PFD_CLKGATE_MASK(1) |
ANATOP_PFD_CLKGATE_MASK(2) |
ANATOP_PFD_CLKGATE_MASK(3);
mask528 = ANATOP_PFD_CLKGATE_MASK(1) |
ANATOP_PFD_CLKGATE_MASK(3);

reg = readl(&ccm->cbcmr);
periph2 = ((reg & MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_MASK)
>> MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_OFFSET);
periph1 = ((reg & MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK)
>> MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_OFFSET);

/* Checking if PLL2 PFD0 or PLL2 PFD2 is using for periph clock */
if ((periph2 != 0x2) && (periph1 != 0x2))
mask528 |= ANATOP_PFD_CLKGATE_MASK(0);

if ((periph2 != 0x1) && (periph1 != 0x1) &&
(periph2 != 0x3) && (periph1 != 0x3))
mask528 |= ANATOP_PFD_CLKGATE_MASK(2);

writel(mask480, &anatop->pfd_480_set);
writel(mask528, &anatop->pfd_528_set);
writel(mask480, &anatop->pfd_480_clr);
writel(mask528, &anatop->pfd_528_clr);
}

可以看出如果 CPU 为 MX6SX、MX6UL、MX6ULL 或 MX6SLL中的任意一种,那么就会直接返回,相当于什么都没做。

cpu_init_crit 函数执行完接着进入 _main 函数,该函数主要是初始化 C 语言的运行环境。

1
bl	_main

搜索 _main,在 arch/arm/lib/crt0.S 中定义:

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
ENTRY(_main)

/*
* Set up initial C runtime environment and call board_init_f(0).
*/

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve

mov r0, #0
bl board_init_f

board_init_f_alloc_reserve 函数在 common/init/board_init.c 文件定义:

1
2
3
4
5
6
7
8
9
10
11
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);

return top;
}

根据 ARM 函数调用规则,top=r0=0x0091FF00,该函数主要作用是保留早期 malloc 区域,且为 GD(全局数据区)留出空间,返回值也是 r0 = 0x0091FA00,其中 CONFIG_SYS_MALLOC_F_LEN = 0X400 ( 在文件include/generated/autoconf.h 中定义) ,sizeof(struct global_data) = 248(GD_SIZE 值) = 0x100,计算可得 r0 = 0x0091FF00 - 0x400 - 0x100 = 0x0091FA00

mov r9, r0 将 r0 寄存器的值写到寄存器 r9 里面,因为 r9 寄存器存放着全局变量 gd 的地址
在文件 arch/arm/include/asm/global_data.h 定义:

1
#define DECLARE_GLOBAL_DATA_PTR		register volatile gd_t *gd asm ("r9")

可知 uboot 定义了一个指向 gd_t 的指针 gd,gd 存放在寄存器 r9,也就是说 gd 是个全局变量。gd_t 是一个结构体在 include/asm-generic/global_data.h 文件中定义:

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
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned int baudrate;
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
unsigned long pci_clk;
unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
unsigned long fb_base; /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)
unsigned long post_log_word; /* Record POST activities */
unsigned long post_log_res; /* success of POST test */
unsigned long post_init_f_time; /* When post_init_f started */
#endif
#ifdef CONFIG_BOARD_TYPES
unsigned long board_type;
#endif
unsigned long have_console; /* serial_init() was called */
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)
unsigned long precon_buf_idx; /* Pre-Console buffer index */
#endif
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */

unsigned long ram_top; /* Top address of RAM used by U-Boot */
unsigned long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /* RAM size */
unsigned long mon_len; /* monitor len */
unsigned long irq_sp; /* irq stack pointer */
unsigned long start_addr_sp; /* start_addr_stackpointer */
unsigned long reloc_off;
struct global_data *new_gd; /* relocated global data */

#ifdef CONFIG_DM
struct udevice *dm_root; /* Root instance for Driver Model */
struct udevice *dm_root_f; /* Pre-relocation root instance */
struct list_head uclass_root; /* Head of core tree */
#endif
#ifdef CONFIG_TIMER
struct udevice *timer; /* Timer instance for Driver Model */
#endif

const void *fdt_blob; /* Our device tree, NULL if none */
void *new_fdt; /* Relocated FDT */
unsigned long fdt_size; /* Space reserved for relocated FDT */
struct jt_funcs *jt; /* jump table */
char env_buf[32]; /* buffer for getenv() before reloc. */
#ifdef CONFIG_TRACE
void *trace_buff; /* The trace buffer */
#endif
#if defined(CONFIG_SYS_I2C)
int cur_i2c_bus; /* current used i2c bus */
#endif
#ifdef CONFIG_SYS_I2C_MXC
void *srdata[10];
#endif
unsigned long timebase_h;
unsigned long timebase_l;
#ifdef CONFIG_SYS_MALLOC_F_LEN
unsigned long malloc_base; /* base address of early malloc() */
unsigned long malloc_limit; /* limit address */
unsigned long malloc_ptr; /* current address */
#endif
#ifdef CONFIG_PCI
struct pci_controller *hose; /* PCI hose for early use */
phys_addr_t pci_ram_top; /* top of region accessible to PCI */
#endif
#ifdef CONFIG_PCI_BOOTDELAY
int pcidelay_done;
#endif
struct udevice *cur_serial_dev; /* current serial device */
struct arch_global_data arch; /* architecture-specific data */
#ifdef CONFIG_CONSOLE_RECORD
struct membuff console_out; /* console output */
struct membuff console_in; /* console input */
#endif
#ifdef CONFIG_DM_VIDEO
ulong video_top; /* Top of video frame buffer area */
ulong video_bottom; /* Bottom of video frame buffer area */
#endif
} gd_t;

gd_t 和 bd_t 都 uboot 中两个重要的数据结构,在初始化操作很多都要靠这两个数据结构来保存或传递,bd_t 结构体在 include/asm-generic/u-boot.h 文件中声明:

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
typedef struct bd_info {
unsigned long bi_memstart; /* start of DRAM memory */
phys_size_t bi_memsize; /* size of DRAM memory in bytes */
unsigned long bi_flashstart; /* start of FLASH memory */
unsigned long bi_flashsize; /* size of FLASH memory */
unsigned long bi_flashoffset; /* reserved area for startup monitor */
unsigned long bi_sramstart; /* start of SRAM memory */
unsigned long bi_sramsize; /* size of SRAM memory */
#ifdef CONFIG_AVR32
unsigned char bi_phy_id[4]; /* PHY address for ATAG_ETHERNET */
unsigned long bi_board_number;/* ATAG_BOARDINFO */
#endif
#ifdef CONFIG_ARM
unsigned long bi_arm_freq; /* arm frequency */
unsigned long bi_dsp_freq; /* dsp core frequency */
unsigned long bi_ddr_freq; /* ddr frequency */
#endif
#if defined(CONFIG_5xx) || defined(CONFIG_8xx) || defined(CONFIG_MPC8260) \
|| defined(CONFIG_E500) || defined(CONFIG_MPC86xx)
unsigned long bi_immr_base; /* base of IMMR register */
#endif
#if defined(CONFIG_MPC5xxx) || defined(CONFIG_M68K)
unsigned long bi_mbar_base; /* base of internal registers */
#endif
#if defined(CONFIG_MPC83xx)
unsigned long bi_immrbar;
#endif
unsigned long bi_bootflags; /* boot / reboot flag (Unused) */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* OLD: see README.enetaddr */
unsigned short bi_ethspeed; /* Ethernet speed in Mbps */
unsigned long bi_intfreq; /* Internal Freq, in MHz */
unsigned long bi_busfreq; /* Bus Freq, in MHz */
#if defined(CONFIG_CPM2)
unsigned long bi_cpmfreq; /* CPM_CLK Freq, in MHz */
unsigned long bi_brgfreq; /* BRG_CLK Freq, in MHz */
unsigned long bi_sccfreq; /* SCC_CLK Freq, in MHz */
unsigned long bi_vco; /* VCO Out from PLL, in MHz */
#endif
#if defined(CONFIG_MPC512X)
unsigned long bi_ipsfreq; /* IPS Bus Freq, in MHz */
#endif /* CONFIG_MPC512X */
#if defined(CONFIG_MPC5xxx) || defined(CONFIG_M68K)
unsigned long bi_ipbfreq; /* IPB Bus Freq, in MHz */
unsigned long bi_pcifreq; /* PCI Bus Freq, in MHz */
#endif
#if defined(CONFIG_EXTRA_CLOCK)
unsigned long bi_inpfreq; /* input Freq in MHz */
unsigned long bi_vcofreq; /* vco Freq in MHz */
unsigned long bi_flbfreq; /* Flexbus Freq in MHz */
#endif
#if defined(CONFIG_405) || \
defined(CONFIG_405GP) || \
defined(CONFIG_405EP) || \
defined(CONFIG_405EZ) || \
defined(CONFIG_405EX) || \
defined(CONFIG_440)
unsigned char bi_s_version[4]; /* Version of this structure */
unsigned char bi_r_version[32]; /* Version of the ROM (AMCC) */
unsigned int bi_procfreq; /* CPU (Internal) Freq, in Hz */
unsigned int bi_plb_busfreq; /* PLB Bus speed, in Hz */
unsigned int bi_pci_busfreq; /* PCI Bus speed, in Hz */
unsigned char bi_pci_enetaddr[6]; /* PCI Ethernet MAC address */
#endif

#ifdef CONFIG_HAS_ETH1
unsigned char bi_enet1addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH2
unsigned char bi_enet2addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH3
unsigned char bi_enet3addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH4
unsigned char bi_enet4addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH5
unsigned char bi_enet5addr[6]; /* OLD: see README.enetaddr */
#endif

#if defined(CONFIG_405GP) || defined(CONFIG_405EP) || \
defined(CONFIG_405EZ) || defined(CONFIG_440GX) || \
defined(CONFIG_440EP) || defined(CONFIG_440GR) || \
defined(CONFIG_440EPX) || defined(CONFIG_440GRX) || \
defined(CONFIG_460EX) || defined(CONFIG_460GT)
unsigned int bi_opbfreq; /* OPB clock in Hz */
int bi_iic_fast[2]; /* Use fast i2c mode */
#endif
#if defined(CONFIG_4xx)
#if defined(CONFIG_440GX) || \
defined(CONFIG_460EX) || defined(CONFIG_460GT)
int bi_phynum[4]; /* Determines phy mapping */
int bi_phymode[4]; /* Determines phy mode */
#elif defined(CONFIG_405EP) || defined(CONFIG_405EX) || defined(CONFIG_440)
int bi_phynum[2]; /* Determines phy mapping */
int bi_phymode[2]; /* Determines phy mode */
#else
int bi_phynum[1]; /* Determines phy mapping */
int bi_phymode[1]; /* Determines phy mode */
#endif
#endif /* defined(CONFIG_4xx) */
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
#ifdef CONFIG_NR_DRAM_BANKS
struct { /* RAM configuration */
phys_addr_t start;
phys_size_t size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#endif /* CONFIG_NR_DRAM_BANKS */
} bd_t;

看完这两个结构体再回头看 _main 函数后这部分:

1
2
3
4
5
mov	r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0

主要作用就是:保留早期 malloc 区域,且为 GD(全局数据区)留出空间,并设置 gd 所指向的位置为 0x0091FA00。

接着继续后续代码:

1
bl  board_init_f_init_reserve

board_init_f_init_reserve 函数再 common/init/board_init.c 文件中定义:

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
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;

/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/

gd_ptr = (struct global_data *)base;
/* zero the area */
memset(gd_ptr, '\0', sizeof(*gd));
/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
/* next alloc will be higher by one GD plus 16-byte alignment */
base += roundup(sizeof(struct global_data), 16);

/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/

#if defined(CONFIG_SYS_MALLOC_F)
/* go down one 'early malloc arena' */
gd->malloc_base = base;
/* next alloc will be higher by one 'early malloc arena' size */
base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}

可以看出,该函数用于初始化 gd(清零处理),此函数还设置了 gd->malloc_base 为 gd 基地址+ gd 大小=0X0091FA00+248=0X0091FAF8,在做 16 字节对齐,最终 gd->malloc_base=0X0091FB00,这个也就是early malloc 的起始地址。

image-20240731213212871

接着 _main 函数后续代码:

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
    mov	r0, #0
bl board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/

ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */

adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
here:
/*
* now relocate vectors
*/

bl relocate_vectors

/* Set up final (full) environment */

bl c_runtime_cpu_setup /* we still call old routine here */
#endif

这一部分代码的主要工作:初始化 gd 的所有成员变量,重新设置环境(sp 和 gd)并且将 uboot 拷贝到新的存放地址(原来的起始地址为 0x87800000,目的地址为 0x9FF47000)。

函数 board_init_f 中会初始化 gd 的所有成员变量,其中 gd->start_addr_sp=0X9EF44E90, 所以这里相当于设置 sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90 是DDR 中的地址,说明新的 sp 和 gd 将会存放到 DDR 中,而不是内部的RAM 了

board_init_f 函数是初始化 DDR和定时器等功能,后面分析。

relocate_code 函数是代码重定位函数:负责将 uboot 拷贝到新的地址,后续分析。

relocate_vectors 函数是对中断向量表做重定位,后续分析。

接着看 _main 函数后续的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ldr	r0, =__bss_start	/* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */

subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */

clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif

这部分代码主要是用来清除 BSS 段。

board_init_f 函数

  • 初始化一系列外设:串口、定时器等
  • 初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。

board_init_f 函数 common/board_f.c 文件中定义:

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
void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
/*
* For some architectures, global data is initialized and used before
* calling this function. The data should be preserved. For others,
* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
* here to host global data until relocation.
*/
gd_t data;

gd = &data;

/*
* Clear global data before it is accessed at debug print
* in initcall_run_list. Otherwise the debug print probably
* get the wrong value of gd->have_console.
*/
zero_global_data();
#endif

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

if (initcall_run_list(init_sequence_f))
hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}

设置 gd 的 have_console 为 0,表示还没有初始化串口,接着调用 initcall_run_list 函数来运行初始化序列 init_sequence_f,其里面包含了一系列初始化函数。

initcall_run_list 函数在文件 include/initcall.h 中定义:

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
DECLARE_GLOBAL_DATA_PTR;

int initcall_run_list(const init_fnc_t init_sequence[])
{
const init_fnc_t *init_fnc_ptr;

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
unsigned long reloc_ofs = 0;
int ret;

if (gd->flags & GD_FLG_RELOC)
reloc_ofs = gd->reloc_off;
#ifdef CONFIG_EFI_APP
reloc_ofs = (unsigned long)image_base;
#endif
debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs);
if (gd->flags & GD_FLG_RELOC)
debug(" (relocated to %p)\n", (char *)*init_fnc_ptr);
else
debug("\n");
ret = (*init_fnc_ptr)();
if (ret) {
printf("initcall sequence %p failed at call %p (err=%d)\n",
init_sequence,
(char *)*init_fnc_ptr - reloc_ofs, ret);
return -1;
}
}
return 0;
}

遍历执行函数指针数组 init_sequence[] 里面放的所有函数并测试其返回值。

init_sequence_f 序列在文件 common/board_f.c 中定义:去除一些条件编译

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
static const init_fnc_t init_sequence_f[] = {
setup_mon_len, /* 设置gd->mon_len为编译出来的u-boot.bin+bss段的大小 */
fdtdec_setup, /* 和设备树有关 */
initf_malloc, /* 初始化并设置内存池 */
initf_console_record, /* 平台信息记录初始化 */
arch_cpu_init, /* 空函数 */
mach_cpu_init, /* 空函数 */
initf_dm, /* 驱动模型初始化 */
arch_cpu_init_dm, /* 空函数 */
mark_bootstage, /* need timer, go after init dm */
board_early_init_f, /* 设置时钟和GPIO */
timer_init, /* 定时器初始化 */
board_postclk_init, /* 设置VDDSOC电压 */
get_clocks,
env_init, /* 找到最适合存放环境变量的地址,并初始化 */
init_baud_rate, /* 波特率初始化 */
serial_init, /* 串口初始化 */
console_init_f, /* 使能在重定位之前用的串口功能 gd->have_console = 1 */
display_options, /* 显示banner,如u-boot版本、编译时间等信息 */
display_text_info, /* 显示调试信息 */
print_cpuinfo, /* 显示cpu信息,如cpu速度 */
show_board_info, /* 显示板子信息 */
INIT_FUNC_WATCHDOG_INIT,
INIT_FUNC_WATCHDOG_RESET,
init_func_i2c,
announce_dram_init, /* 准备显示DRAM大小,在u-boot启动时可以看到DRAM大小的信息 */
dram_init, /* DRAM初始化,对于本imx6ull设置dg->ram_size = 512 MiB */
setup_dest_addr, /* 设置重定位地址,gd->relocaddr = gd->ram_top */
reserve_round_4k, /* 4字节对齐,将内存指针调到下一个4 kB */
reserve_mmu, /* 为mmu区域腾出空间 */
reserve_video, /* 预留video显示内存 */
reserve_trace,
reserve_uboot, /* 预留U-Boot代码、data和bss区 */
reserve_malloc, /* 预留malloc区 */
reserve_board, /* 预留存放板子信息区 */
setup_machine, /* 板子ID,这里没有用到 */
reserve_global_data, /* 预留GD区域,栈gd->start_addr_sp指向gd段基地址*/
reserve_fdt, /* 预留设备树区域 */
reserve_bootstage,
reserve_bloblist,
reserve_arch, /* 架构相关预留区 */
reserve_stacks, /* 预留栈区,gd->start_addr_sp指向栈底基地址 */
setup_dram_config, /* DRAM的大小初始化 */
show_dram_config, /* 显示DRAM的配置 */
display_new_sp, /* 显示新的栈地址 */
reloc_fdt, /* 和设备树有关 */
reloc_bootstage, /* 和u-boot阶段有关 */
reloc_bloblist, /* 和blob列表有关 */
setup_reloc, /* 重定位 */
NULL,
};
  • setup_mon_len 函数设置 gd 的 mon_len 成员变量为 __bss_end - _start,也就是整个 uboot 的大小,便于后续重定位 uboot。
  • initf_malloc 函数初始化并设置内存池。
  • initf_console_record 函数,IMX6ULL 的 uboot 没有定义宏 CONFIG_CONSOLE_RECORD,所以此函数直接返回 0。
  • board_early_init_f 设置时钟和 GPIO。
  • timer_init 定时器初始化。
  • get_clocks 获取一些时钟值,imx6ull 获取的是 sdhc_clk 时钟—— SD 卡外设的时钟。
  • env_init 设置 gd 的 env_addr 成员变量,即环境变量的保存地址。
  • init_baud_rate 初始化波特率,根据环境变量 baudrate 来初始化 gd->baudrate。
  • serial_init 串口初始化。
  • console_init_f 设置 gd->have_console 为1,表示有个控制台,将前面暂存在缓冲区中的数据通过控制台打印出来。
  • display_options 显示 banner,如 u-boot版本、编译时间等信息。
  • display_text_info 显示调试信息。
  • print_cpuinfo 显示cpu信息,如cpu速度。
  • show_board_info 显示板子信息。
  • INIT_FUNC_WATCHDOG_INITINIT_FUNC_WATCHDOG_RESET 用来初始化和复位看门狗,对于 imx6ull 为空函数。
  • announce_dram_init 输出字符串 DRAM:
  • dram_init DRAM 初始化,设置 DDR 的大小。
  • setup_dest_add 设置重定位地址,gd->relocaddr = gd->ram_top。
  • reserve_round_4k 对 gd->relocaddr 做 4KB 对齐。
  • setup_machine 设置机器ID,linux 启动的时候会和这个机器 ID 匹配,如果匹配的话 linux 就会启动正常。但是 I.MX6ULL 不用这种方式了,这是以前老版本的 uboot 和 linux 使用的,新版本使用设备树了,因此此函数无效
  • setup_dram_config 设置 gd->bd->bi_dram[0].start 和 gd->bd->bi_dram[0].size,告诉 Linux DRAM 的起始地址和大小

可以看出这个数组存放的一系列函数的主要工作:设置和初始化一些设备的信息(如 malloc 内存池大小、MMU、定时器、GPIO、DRAM 等)、打印设备的相关信息、提前保留一些设备的内存区域、设置重定位后一些重要的地址为接下来的重定位 uboot 做准备。简单来说就是内存分配的一些东西。

image-20240802153258986

relocate_code 函数

relocate_code 函数在 arch/arm/lib/relocate.S 文件中定义:

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
/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an R_ARM_ABS32
* relocation record type, we never refer to linker-defined symbols directly.
* Instead, we declare literals which contain their relative location with
* respect to relocate_code, and at run time, add relocate_code back to them.
*/

ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */

copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop

/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #R_ARM_RELATIVE
bne fixnext

/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop

relocate_done:

#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif

/* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif

ENDPROC(relocate_code)

r0 是传入参数为前面计算好的重定位后的目标地址 gd->relocaddr。

copy_loop 函数完成代码拷贝工作,从 __image_copy_start 地址开始到 __image_copy_end 地址的 uboot 代码。

1
2
3
4
5
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop

解析:将 r1 指向的地址加载数据寄存器 r10 和 r11,再将数据存放到 r0,比较 r1 和 r2 的地址,r1 < r2 则继续循环,其中感叹号 ! 表示指向完该指令后地址会自增。

fixloop 函数是重定位 .rel_dyn 段,.rel_dyn 段是存放 .text 段中需要重定位地址的集合。重定位就是 uboot 将自身拷贝到 DRAM 的另一个地址去继续运行(DRAM 的高地址处)。

relocate_vectors 函数

relocate_vectors 函数在 arch/arm/lib/relocate.S 文件中定义:

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
ENTRY(relocate_vectors)

#ifdef CONFIG_CPU_V7M
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1, =V7M_SCB_BASE
str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
ands r2, r2, #(1 << 13)
ldreq r1, =0x00000000 /* If V=0 */
ldrne r1, =0xFFFF0000 /* If V=1 */
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
#endif
#endif
bx lr

ENDPROC(relocate_vectors)

该函数主要工作就是实现异常向量表的重定位,拷贝到正确的地址中。Cortex-A7 是支持向量表偏移的,需要将新的向量表首地址写入到寄存器 VBAR 中,设置向量表偏移。

board_init_r 函数

board_init_r 函数在 common/board_r.c 文件中定义:

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
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
/*
* Set up the new global data pointer. So far only x86 does this
* here.
* TODO(sjg@chromium.org): Consider doing this for all archs, or
* dropping the new_gd parameter.
*/
#if CONFIG_IS_ENABLED(X86_64)
arch_setup_gd(new_gd);
#endif

#ifdef CONFIG_NEEDS_MANUAL_RELOC
int i;
#endif

#ifdef CONFIG_AVR32
mmu_init_r(dest_addr);
#endif

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
gd = new_gd;
#endif

#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();
}

判断板子使用的架构,最后使用 initcall_run_list 函数来执行初始化序列 init_sequence_r。

init_sequence_r 序列在文件 common/board_r.c 中定义:去除一些条件编译

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
/*
* Over time we hope to remove these functions with code fragments and
* stub funtcions, and instead call the relevant function directly.
*
* We also hope to remove most of the driver-related init and do it if/when
* the driver is later used.
*
* TODO: perhaps reset the watchdog in the initcall function after each call?
*/
static init_fnc_t init_sequence_r[] = {
initr_trace, /* 初始化与跟踪调试相关部分 */
initr_reloc, /* 标记重定位完成 */
initr_caches, /* 使能cache */
initr_reloc_global_data, /* 初始化重定位后的gd成员 */
initr_barrier, /* imx6ull未用到 */
initr_malloc, /* 初始化malloc */
log_init, /* log初始化 */
initr_bootstage, /* Needs malloc() but has its own timer */
initr_console_record, /* 初始化控台 */
bootstage_relocate,
initr_dm, /* 设备模型初始化 */
board_init, /* 板级初始化 */
efi_memory_init, /* efi_memory初始化 */
stdio_init_tables, /* 标准输入输出及标准错误等初始化 */
initr_serial, /* 串口初始化 */
initr_announce, /* 跟调试相关 */
power_init_board, /* 电源芯片初始化 */
initr_nand, /* nandflash初始化 */
initr_mmc, /* mmc初始化 */
initr_env, /* 环境变量初始化 */
initr_secondary_cpu, /* 其他cpu初始化,由于imx6ull为单核cpu故忽略 */
stdio_add_devices, /* 输入输出设备初始化 */
initr_jumptable, /* 初始化跳转表 */
console_init_r, /* 控制台初始化 */
interrupt_init, /* 中断初始化 */
initr_enable_interrupts, /* 中断使能 */
/* PPC has a udelay(20) here dating from 2002. Why? */
initr_ethaddr, /* 网络初始化 */
board_late_init, /* 板子后续初始化 */
initr_fastboot_setup,
initr_net, /* 网络初始化 */
initr_check_fastboot,
run_main_loop, /* 运行主循环 */
};

run_main_loop 函数

run_main_loop 函数在文件 common/board_r.c 中定义:

1
2
3
4
5
6
7
8
9
10
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}

有一个死循环,如果 main_loop 中启动不成功,就会不断重新启动。

main_loop 函数在文件 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
24
25
26
27
28
29
30
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;

// 打印启动进度
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

// 打印uboot版本号
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

cli_init();

run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);

autoboot_command(s);

cli_loop();
panic("No CLI available");
}
  • cli_init 函数,跟命令初始化有关,初始化 hush shell 相关的变量。
  • run_preboot_environment_command 函数,获取环境变量 perboot 的内容,perboot 是一些预启动命令,一般不使用这个环境变量。
  • bootdelay_process 函数,此函数会读取环境变量 bootdelay 和 bootcmd 的内容,然后将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值。
  • autoboot_command 函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?

autoboot_command 函数在 common/autoboot.c 文件中定义:去除条件编译

1
2
3
4
5
6
7
8
void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
run_command_list(s, -1, 0);
}
}

条件主要看最后一个,abortboot 函数在 common/autoboot.c 文件中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int abortboot(int bootdelay)
{
int abort = 0;

if (bootdelay >= 0)
abort = __abortboot(bootdelay);

#ifdef CONFIG_SILENT_CONSOLE
if (abort)
gd->flags &= ~GD_FLG_SILENT;
#endif

return abort;
}

__abortboot 函数也在该文件中定义:

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
static int __abortboot(int bootdelay)
{
int abort = 0;
unsigned long ts;

printf("Hit any key to stop autoboot: %2d ", bootdelay);

/*
* Check if key already pressed
*/
if (tstc()) { /* we got a key press */
(void) getc(); /* consume input */
puts("\b\b\b 0");
abort = 1; /* don't auto boot */
}

while ((bootdelay > 0) && (!abort)) {
--bootdelay;
/* delay 1000 ms */
ts = get_timer(0);
do {
if (tstc()) { /* we got a key press */
abort = 1; /* don't auto boot */
bootdelay = 0; /* no more delay */
(void) getc(); /* consume input */
break;
}
udelay(10000);
} while (!abort && get_timer(ts) < 1000);

printf("\b\b\b%2d ", bootdelay);
}

putc('\n');

return abort;
}

这段代码主要工作就是判断键盘是否有按下,按下就设置 abort 为 1,bootdelay 为 0,并跳出循环返回 abort 为 1;若 bootdelay 自然减到 0 也会退出,返回 abort,此时为 0。

回到 autoboot_command 函数中:

1
2
3
4
5
6
7
8
void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
run_command_list(s, -1, 0);
}
}
  • 如果 abort 为 0 即倒计时为自然结束,条件成立,执行函数 run_command_list,会执行参数 s 指定的一系列命令——环境变量 bootcmd 的命令,bootcmd 保存默认的启动命令,接着就会启动 linux 内核。
  • 如果 abort 为 1 即在倒计时结束前按下了键盘的按键,条件不成立,autoboot_command 相当于空函数。

再往回到 main_loop 函数中,倒计时结束前按下键盘的按键,就会执行 cli_loop 函数——命令处理函数,负责接收好处理输入的命令。

1
2
3
4
5
6
7
8
9
10
11
12
void cli_loop(void)
{
#ifdef CONFIG_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#elif defined(CONFIG_CMDLINE)
cli_simple_loop();
#else
printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_HUSH_PARSER*/
}

CONFIG_HUSH_PARSER 在 include/generated/autoconf.h 文件中有定义。

parse_file_outer 函数在 common/cli_hush.c 文件中定义:去除条件编译

1
2
3
4
5
6
7
8
9
10
int parse_file_outer(void)
{
int rcode;
struct in_str input;

// 初始化input成员变量
setup_file_in_str(&input);
rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
return rcode;
}

parse_stream_outer 函数是 hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令,该函数也在该文件下定义:

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
/* most recursion does not come through here, the exeception is
* from builtin_source() */
static int parse_stream_outer(struct in_str *inp, int flag)
{

struct p_context ctx;
o_string temp=NULL_O_STRING;
int rcode;
#ifdef __U_BOOT__
int code = 1;
#endif
do {
ctx.type = flag;
initialize_context(&ctx);
update_ifs_map();
if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset((uchar *)";$&|", 0);
inp->promptmode=1;
rcode = parse_stream(&temp, &ctx, inp,
flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');
#ifdef __U_BOOT__
if (rcode == 1) flag_repeat = 0;
#endif
if (rcode != 1 && ctx.old_flag != 0) {
syntax();
#ifdef __U_BOOT__
flag_repeat = 0;
#endif
}
if (rcode != 1 && ctx.old_flag == 0) {
done_word(&temp, &ctx);
done_pipe(&ctx,PIPE_SEQ);
#ifndef __U_BOOT__
run_list(ctx.list_head);
#else
code = run_list(ctx.list_head);
if (code == -2) { /* exit */
b_free(&temp);
code = 0;
/* XXX hackish way to not allow exit from main loop */
if (inp->peek == file_peek) {
printf("exit not allowed from main input shell.\n");
continue;
}
break;
}
if (code == -1)
flag_repeat = 0;
#endif
} else {
if (ctx.old_flag != 0) {
free(ctx.stack);
b_reset(&temp);
}
#ifdef __U_BOOT__
if (inp->__promptme == 0) printf("<INTERRUPT>\n");
inp->__promptme = 1;
#endif
temp.nonnull = 0;
temp.quote = 0;
inp->p = NULL;
free_pipe_list(ctx.list_head,0);
}
b_free(&temp);
/* loop on syntax errors, return on EOF */
} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&
(inp->peek != static_peek || b_peek(inp)));
#ifndef __U_BOOT__
return 0;
#else
return (code != 0) ? 1 : 0;
#endif /* __U_BOOT__ */
}

command_process 函数

uboot 中使用 U_BOOT_CMD 来定义一个命令,最终的目的就是为了定义一个 cmd_tbl_t 类型的变量,并初始化这个变量的各个成员。uboot 中的每个命令都存储在 .u_boot_list 段中,每个命令都有一个名为 do_xxx(xxx 为具体的命令名)的函数,这个 do_xxx 函数就是具体的命令处理函数。

U-Boot 启动 Linux 内核过程

U-Boot 启动 Linux 内核有 bootz 和 bootm 两种方法,这里只介绍 bootz。

bootz 和 bootm 命令都用到了一个重要的全局变量:images,在文件 cmd/bootm.c 中定义:

1
bootm_headers_t images; /* pointers to os/initrd/fdt images */

bootm_headers_t 结构体在文件 include/images.h 中定义:去除条件编译

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
/*
* Legacy and FIT format headers used by do_bootm() and do_bootm_<os>()
* routines.
*/
typedef struct bootm_headers {
/*
* Legacy os image header, if it is a multi component image
* then boot_get_ramdisk() and get_fdt() will attempt to get
* data from second and third component accordingly.
*/
image_header_t *legacy_hdr_os; /* image header pointer */
image_header_t legacy_hdr_os_copy; /* header copy */
ulong legacy_hdr_valid;

#ifndef USE_HOSTCC
image_info_t os; /* os image info */
ulong ep; /* entry point of OS */

ulong rd_start, rd_end;/* ramdisk start/end */

char *ft_addr; /* flat dev tree address */
ulong ft_len; /* length of flat device tree */

ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif

int verify; /* getenv("verify")[0] != 'n' */

#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO (0x00000400)
int state;

#ifdef CONFIG_LMB
struct lmb lmb; /* for memory mgmt */
#endif
} bootm_headers_t;

其中 BOOTM_STATE_ 开头的宏定义为 boot 的不同阶段。

image_info_t 类型为系统镜像信息结构体,在文件 include/image.h 中定义:

1
2
3
4
5
6
7
typedef struct image_info {
ulong start, end; /* start/end of blob */
ulong image_start, image_len; /* start of image within blob, len of image */
ulong load; /* load addr for the image */
uint8_t comp, type, os; /* compression, type of image, os type */
uint8_t arch; /* CPU architecture */
} image_info_t;

bootz 启动 Linux 内核过程

do_bootz 函数

bootz 命令的执行函数为 do_bootz ,在文件 cmd/bootz.c 或者 cmd/bootm.c 中定义:去除条件编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;

/* Consume 'bootz' */
argc--; argv++;

if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;

/*
* We are doing the BOOTM_STATE_LOADOS state ourselves, so must
* disable interrupts ourselves
*/
bootm_disable_interrupts();

images.os.os = IH_OS_LINUX;
ret = do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);

return ret;
}
  • 调用 bootz_start 函数,后续分析。

  • 调用 bootm_disable_interrupts 函数关闭中断。

  • 设置 images.os.os 为 IH_OS_LINUX,也就是设置系统镜像为 Linux表示我们要启动的是 Linux 系统

  • 调用 do_bootm_states 函数执行 boot 阶段三个阶段:BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO。

bootz_start 函数

bootz_start 函数在文件 cmd/bootz.c 或者 cmd/bootm.c 中定义:

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
/*
* zImage booting support
*/
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images)
{
int ret;
ulong zi_start, zi_end;

ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
images, 1);

/* Setup Linux kernel zImage entry point */
if (!argc) {
images->ep = load_addr;
debug("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
images->ep = simple_strtoul(argv[0], NULL, 16);
debug("* kernel: cmdline image address = 0x%08lx\n",
images->ep);
}

ret = bootz_setup(images->ep, &zi_start, &zi_end);
if (ret != 0)
return 1;

lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);

/*
* Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
* have a header that provide this informaiton.
*/
if (bootm_find_images(flag, argc, argv))
return 1;

return 0;
}
  • 调用函数 do_bootm_states,执行 BOOTM_STATE_START 阶段。
  • 设置 images 的 ep 成员变量,也就是系统镜像的入口点,使用 bootz 命令启动系统的时候就会设置系统在 DRAM 中的存储位置,这个存储位置就是系统镜像的入口点,因此 images->ep=0X80800000
  • 调用 bootz_setup 函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息
  • 调用函数 bootm_find_images 查找 ramdisk 和设备树(dtb)文件,但是没有用到 ramdisk,所以仅仅是查找设备树文件。

bootz_setup 函数

bootz_setup 函数在文件 arch/arm/lib/bootm.c 中定义:去除条件编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int bootz_setup(ulong image, ulong *start, ulong *end)
{
struct arm_z_header *zi = (struct arm_z_header *)image;

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 是 ARM Linux 系统魔术数/幻数,linux 内核镜像文件 zImage 的魔术数/幻数为 0x016f2818,在第 36 个字节处。

系统的魔术数通常指的是文件系统中的文件类型标识符,它用于识别文件的类型,比如是可执行文件、脚本文件、库文件等。在 Linux系统中,这些魔术数通常存储在文件的头部,由文件系统或程序用来确定文件的类型。

该函数通过传递进来的 image,即系统镜像地址中获取 zImage 头,接着根据结构体中的魔术数判断是否为 Linux 系统镜像,不是就直接返回,打印 Bad Linux ARM zImage magic!;如果是就初始化参数 start 和 end,并打印启动信息。

bootm_find_images 函数

bootm_find_images 函数在文件 common/bootm.c 中定义:

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
/**
* bootm_find_images - wrapper to find and locate various images
* @flag: Ignored Argument
* @argc: command argument count
* @argv: command argument list
*
* boot_find_images() will attempt to load an available ramdisk,
* flattened device tree, as well as specifically marked
* "loadable" images (loadables are FIT only)
*
* Note: bootm_find_images will skip an image if it is not found
*
* @return:
* 0, if all existing images were loaded correctly
* 1, if an image is found but corrupted, or invalid
*/
int bootm_find_images(int flag, int argc, char * const argv[])
{
int ret;

/* find ramdisk */
ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end);
if (ret) {
puts("Ramdisk image is corrupt or invalid\n");
return 1;
}

#if IMAGE_ENABLE_OF_LIBFDT
/* find flattened device tree */
ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
&images.ft_addr, &images.ft_len);
if (ret) {
puts("Could not find a valid device tree\n");
return 1;
}
set_working_fdt_addr((ulong)images.ft_addr);
#endif


return 0;
}

boot_get_fdt 函数查找设备树文件并将设备树的起始地址和长度分别写入 images 的 ft_addr 和 ft_len 成员变量中。

do_bootm_states 函数

do_bootm_states 函数在文件 common/bootm.c 中定义:去除条件编译

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* Execute selected states of the bootm command.
*
* Note the arguments to this state must be the first argument, Any 'bootm'
* or sub-command arguments must have already been taken.
*
* Note that if states contains more than one flag it MUST contain
* BOOTM_STATE_START, since this handles and consumes the command line args.
*
* Also note that aside from boot_os_fn functions and bootm_load_os no other
* functions we store the return value of in 'ret' may use a negative return
* value, without special handling.
*
* @param cmdtp Pointer to bootm command table entry
* @param flag Command flags (CMD_FLAG_...)
* @param argc Number of subcommand arguments (0 = no arguments)
* @param argv Arguments
* @param states Mask containing states to run (BOOTM_STATE_...)
* @param images Image header information
* @param boot_progress 1 to show boot progress, 0 to not do this
* @return 0 if ok, something else on error. Some errors will cause this
* function to perform a reboot! If states contains BOOTM_STATE_OS_GO
* then the intent is to boot an OS, so this function will not return
* unless the image type is standalone.
*/
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;

images->state |= states;

/*
* Work through the states and see how far we get. We stop on
* any error.
*/
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);

if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);

if (!ret && (states & BOOTM_STATE_FINDOTHER))
ret = bootm_find_other(cmdtp, flag, argc, argv);

/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;

iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
if (ret == 0)
lmb_reserve(&images->lmb, images->os.load,
(load_end - images->os.load));
else if (ret && ret != BOOTM_ERR_OVERLAP)
goto err;
else if (ret == BOOTM_ERR_OVERLAP)
ret = 0;
}

#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)
if (!ret && (states & BOOTM_STATE_FDT)) {
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
&images->ft_len);
}
#endif

/* From now on, we need the OS boot function */
if (ret)
return ret;
boot_fn = bootm_os_get_boot_func(images->os.os);
need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
if (boot_fn == NULL && need_boot_fn) {
if (iflag)
enable_interrupts();
printf("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}


/* Call various other states that are not generally used */
if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_BD_T))
ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_PREP)) {
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
}

#ifdef CONFIG_TRACE
/* Pretend to run the OS, then run a user command */
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("fakegocmd");

ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif

/* Check for unsupported subcommand. */
if (ret) {
puts("subcommand not supported\n");
return ret;
}

/* Now run the OS! We hope this doesn't return */
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);

/* Deal with any fallout */
err:
if (iflag)
enable_interrupts();

if (ret == BOOTM_ERR_UNIMPLEMENTED)
bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
else if (ret == BOOTM_ERR_RESET)
do_reset(cmdtp, flag, argc, argv);

return ret;
}

根据不同的 boot 状态执行不同的代码段。

bootm_os_get_boot_func 函数

其中 bootm_os_get_boot_func 函数是用来查找对应系统的启动函数,在文件 common/bootm_os.c 中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
boot_os_fn *bootm_os_get_boot_func(int os)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
static bool relocated;

if (!relocated) {
int i;

/* relocate boot function table */
for (i = 0; i < ARRAY_SIZE(boot_os); i++)
if (boot_os[i] != NULL)
boot_os[i] += gd->reloc_off;

relocated = true;
}
#endif
return boot_os[os];
}

条件编译:判断是否需要手动重定位,这里没有用到,所以直接返回 boot_os[os] ,**boot_os 数组存放着不同系统对应的启动函数,**在文件 common/bootm_os.c 中定义:

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
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
[IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
[IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
[IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
[IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) && \
(defined(CONFIG_PPC) || defined(CONFIG_ARM))
[IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
[IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
[IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
#ifdef CONFIG_BOOTM_OPENRTOS
[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};

do_bootm_linux 函数

do_bootm_linux 函数在文件 arch/arm/lib/bootm.c 中定义:

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
/* Main Entry point for arm bootm implementation
*
* Modeled after the powerpc implementation
* DIFFERENCE: Instead of calling prep and go at the end
* they are called if subcommand is equal 0.
*/
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;

if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}

if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
boot_jump_linux(images, flag);
return 0;
}

boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}

boot_selected_os 函数会将 flag 设置为 BOOTM_STATE_OS_GO,执行函数 boot_jump_linux 在文件arch/arm/lib/bootm.c 中定义:去除条件编译

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
/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
unsigned long machid = gd->bd->bi_arch_number;
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

kernel_entry = (void (*)(int, int, uint))images->ep;

s = getenv("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!\n");
return;
}
printf("Using machid 0x%lx from environment\n", machid);
}

debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);

if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;

if (!fake) {
#ifdef CONFIG_ARMV7_NONSEC
kernel_entry(0, machid, r2);
}
#endif
}
  • 变量 machid 保存机器 ID,如果不使用设备树的话这个机器 ID 会被传递给 Linux 内核,Linux 内核会在自己的机器 ID 列表里面查找是否存在与 uboot 传递进来的 machid 匹配的项目,如果存在就说明 Linux 内核支持这个机器,那么 Linux 就会启动!如果使用设备树的话这个 machid 就无效了,设备树存有一个“兼容性”这个属性,Linux 内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。

  • kernel_entry 函数是进入 Linux 内核,三个参数 zero、arch 和 params,arch 为机器 ID,第三个参数为 ATAGS 或者设备树首地址,ATAGS 是传统的方法,用于传递一些命令行信息。

  • 函数 kernel_entry 并不是 uboot 定义的,而是 Linux 内核定义的,Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux 内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码。kernel_entry 函数进入内核后,uboot 的使命就完成了。

函数 announce_and_cleanup 来打印一些信息并做一些清理工作,此函数文件 arch/arm/lib/bootm.c 中定义,