之前也发表过关于《Bootloader启动过程分析》的文章,但是内容表达得比较抽象,大多是文字叙述,所以这里从系统和代码的角度来深入分析bootloader的启动过程。
U-Boot工程结构
学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。
U-Boot的源代码布局和Linux类似,使用了按照模块划分的结构,并且充分考虑了体系结构和跨平台问题。
U-Boot源代码目录结构
子目录名
作用
board
开发板相关的定义和结构
common
包含U-Boot用到的各种处理函数
cpu
各种不同类型的处理器相关代码
doc
U-Boot文档
drivers
常用外部设备驱动程序
examples
存放U-Boot开发代码样例
fs
文件系统有关的代码,包括cramfs、ext2、fat等常见文件系统
include
U-Boot用到的头文件
lib_arm
ARM体系结构有关的数据定义和操作
lib_generic
U-Boot通用的操作函数
net
常用的网络协议,包括bootp、rarp、arp、tftp等
post
上电自检相关代码
rtc
实时钟有关操作
tools
U-Boot有关的数据代码
U-Boot总体工作流程
与大多数Bootloader类似,U-Boot的启动分成stage1和stage2两个阶段。
stage1使用汇编语言编写,通常与CPU体系紧密相关,如处理器初始化和设备初始化代码等,该阶段在start.S文件中实现。
上图是U-Boot中Stage1工作流程。Stage1的代码都是与平台相关的,使用汇编语言编写占用空间小而且执行速度快。
Stage1负责建立Stage1阶段使用堆栈和代码段,然后复制Stage2阶段的代码到内存。
Stage2阶段一般包括:初始化Flash器件、swim
系统内存映射、初始化网络设备、进入命令循环,接收用户从串口发送的命令然后进行相应的处理。
Stage2使用C语言编写,用于加载操作系统内核,该阶段主要是board.c中是start_armboot()函数实现。下图为U-Boot的Stage1和Stage2在Flash和RAM中的分配。
从上图中可以看出,U-Boot在加载到内存后,使用了操作系统空余的内存空间。
U-Boot启动流程分析
从图中可以看出U-Boot的启动代码分布在start.S、low_level_init.S、board.c和main.c文件中
Start.S是U-Boot整个程序的入口,该文件使用汇编语言编写,不同体系结构的启动代码不同
low_level_init.S是特定开发板的设置代码;
board.c包含开发板底层设备驱动;
main.c是一个与平台无关的代码,U-Boot应用程序的入口在此文件中。
①_start标号
在U-Boot工程中,每种处理器目录下都有一个start.S文件,该文件中有一个_start标号,是整个U-Boot代码的入口点。
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
.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 //快速中断向量
_undefined_instruction:
.word undefined_instruction //定义中断向量表入口地址
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
.balignl
16,0xdeadbeef
_TEXT_BASE:
.word TEXT_BASE //定义整个錟-Boot镜像文件在内存加载的地址
.globl _armboot_start
_armboot_start:
.word
_start
.globl _bss_start
_bss_start:
.word
__bss_start //定义代码段起始
.globl _bss_end
_bss_end:
.word
_end //定义代码段结束地址
#ifdef CONFIG_USE_IRQ
.globl
IRQ_STACK_START //定义IRQ的堆栈地址
IRQ_STACK_START:
.word 0x0badc0de
.globl
FIQ_STACK_START //定义FIQ的堆栈地址
FIQ_STACK_START:
.word
0x0badc0de
#endif
_start标号下面的代码主要是一些伪指令,设置全局变量,供启动程序把U-Boot映像从Flash存储器复制到内存中。
其中比较重要的变量是TEXT_BASE,该变量是通过连接脚本得到的。TEXT_BASE变量需要根据开发板的情况自己修改,具体地址需要根据硬件设计确定。
_start标号一开始定义了ARM处理器7个中断向量的向量表,对应ARM处理器的7种模式。
由于上电一开始处理器会从0地址执行指令,因此第一个指令直接跳转到reset标号。
reset执行机器初始化的一些操作,此处的跳转指令,无论是冷启动还是热启动开发板都会执行reset标号的代码。
reset也属于一种异常模式,并且该模式的代码不需要返回。
②reset标号
reset标号的代码在处理器启动的时候最先被执行。
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
reset:
mrs
r0,cpsr //保存CPSR寄存器的值到r0寄存器
bic
r0,r0,#0x1f //清除中断
orr
r0,r0,#0xd3
msr
cpsr,r0 //设置CPSR为超级保护模式
//关闭看门狗
#if
defined(CONFIG_S3C2400)
# define
pWTCON 0x15300000 //看门狗地址
# define
INTMSK 0x14400008 //中断控制器基址
# define
CLKDIVN 0x14800014
#elif
defined(CONFIG_S3C2410)
# define
pWTCON 0x53000000
# define
INTMSK 0x4A000008
# define
INTSUBMSK 0x4A00001C
# define
CLKDIVN 0x4C000014
#endif
#if defined(CONFIG_S3C2400)
|| defined(CONFIG_S3C2410)
ldr r0, =pWTCON //取出当前看门狗控制寄存器的地址到r0
mov r1,
#0x0 //设置r1寄存器的值为0
str r1,
[r0] //写入看门狗控制寄存器
mov
r1, #0xffffffff //设置r1
ldr
r0, =INTMSK //取出中断屏蔽寄存器地址到r0
str
r1,
[r0] //r1的值写入中断屏蔽寄存器
# if
defined(CONFIG_S3C2410)
ldr
r1, =0x3ff
ldr
r0, =INTSUBMSK
str
r1, [r0]
# endif
ldr
r0,
=CLKDIVN //取出时钟寄存器地址到r0
mov
r1, #3 //设置r1的值
str
r1,
[r0] //写入时钟配置
#endif
#ifndef
CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //跳转到开发板相关初始化代码
#endif
注意,最后根据CONFIG_SKIP_LOWLEVEL_INIT宏的值是否跳到cpu_init_crit标号执行。
请注意这里使用的是bl指令,在执行完cpu_init_crit标号的代码后会返回。
③cpu_init_crit标号
cpu_init_crit标号处的代码初始化ARM处理器关键的寄存器。
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
#ifndef
CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
mov
r0, #0
mcr
p15, 0, r0, c7, c7,
0 //刷新cache
mcr
p15, 0, r0, c8, c7,
0 //刷新TLB
mrc
p15, 0, r0, c1, c0, 0
bic
r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic
r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr
r0, r0, #0x00000002 @ set bit 2 (A) Align
orr
r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr
p15, 0, r0, c1, c0, 0
mov
ip, lr
bl lowlevel_init //跳转到lowlevel_init
mov
lr, ip
mov
pc, lr
#endif
注意刷新cache和TLB。
cache是一种高速缓存存储器,用于保存CPU频繁使用的数据,在使用Cache技术的处理器上,当一条指令要访问内存的数据时,首先查询cache缓存中是否有数据以及数据是否过期,如果数据未过期则从cache读出数据,处理器会定期回写cache中的数据到内存。根据程序的局部性原理,使用cache后可以大大加快处理器访问内存数据的速度。
TLB的作秀是在处理器访问内存数据的时候做地址转换。TLB的全称是Translation
Lookaside Buffer,可以翻译做旁路缓冲。TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。当应用程序访问一个虚拟地址的时候,会从TLB中查询出对就的物理地址,然后访问物理地址。TLB通常是一个分层结构,使用与cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。
MMU是内存管理单元(Memory Management
Unit)的缩写,在现代计算机体系结构上,MMU被广泛应用。使用MMU技术可以向应用程序提供一个巨大的虚拟地址空间。在U-Boot初始化的时候,程序看到的地址都是物理地址,无须使用MMU。
④lowlevel_init标号
lowlevel_init标号,执行与开发板相关的初始化配置。
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
.globl lowlevel_init
lowlevel_init:
ldr r0,
=SMRDATA //读取SMRDATA变量地址
ldr
r1,
_TEXT_BASE //读取_TEXT_BASE变量地址
sub
r0, r0, r1
ldr
r1,
=BWSCON //读取总线宽度寄存器
add r2, r0,
#13*4 //得到SMRDATA占用的大小
0:
ldr r3, [r0],
#4 //加载SMRDATA到内存
str r3, [r1], #4
cmp r2, r0
bne 0b
mov
pc, lr
.ltorg
SMRDATA: //定义SMRDATA的值
.word
(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word
((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word
((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word
((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word
((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word
((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word
((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word
((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word
((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word
((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word
0x32
.word
0x30
.word
0x30
程序中需要计算SMRDATA需要加载的内存地址和大小。
首先读取SMRDATA的变量地址,之后计算存放的内存地址并且记录在r0寄存器,然后根据总线宽度计算需要加载的SMRDATA大小,并且把加载结束的地址存放在r2寄存器。
最后复制SMRDATA到内存。SMRDATA是开发板上内存映射的配置。
⑤relocate标号
relocate部分的代码负责把U-Boot
Stage2的代码从Flash存储器加载到内存。
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
#ifndef
CONFIG_SKIP_RELOCATE_UBOOT
relocate:
adr
r0,
_start
//获取当前代码存放地址
ldr
r1,
_TEXT_BASE
//获取内存存放代码地址
cmp r0,
r1
//检查是否需要加载
beq stack_setup
ldr
r2,
_armboot_start //获取stage2代码存放地址
ldr
r3,
_bss_start //获取内存代码段起始地址
sub
r2, r3,
r2 //计算stage2代码长度
add
r2, r0,
r2 //计算stage2代码结束地址
copy_loop:
ldmia r0!,
{r3-r10}
//从Flash复制代码到内存
stmia r1!,
{r3-r10}
cmp
r0,
r2
ble
copy_loop
#endif
stack_setup: //在内存中建立堆栈
ldr
r0,
_TEXT_BASE
sub
r0, r0,
#CFG_MALLOC_LEN //分配内存区域
sub
r0, r0, #CFG_GBL_DATA_SIZE
#ifdef CONFIG_USE_IRQ
sub
r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub
sp, r0,
#12
clear_bss: //初始化内存bss段内容为0
ldr
r0,
_bss_start
//查找bss段起始地址
ldr
r1,
_bss_end
//查找bss段结束地址
mov r2,
#0x00000000
clbss_l:str r2,
[r0]
add
r0, r0, #4
cmp
r0, r1
ble
clbss_l
#if 0
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
mov
r1, #0xffffffff
ldr
r0, =INTMR
str
r1, [r0]
ldr
r0, =CLKDIVN
mov
r1, #3
str
r1, [r0]
#endif
ldr
pc,
_start_armboot //设置程序指针为start_armboot()函数地址
_start_armboot: .word
start_armboot
程序首先检查当前是否在内存中执行代码,根据结果决定是否需要从Flash存储器加载代码。
程序通过获取_start和_TEXT_BASE所在的地址比较,如果地址相同说明程序已经在内存中,无须加载。
然后计算要加载的stage2代码起始地址和长度,然后在循环复制Flash的数据到内存,每次可以复制8个字长的数据。stage2程序复制完成后,程序设置系统堆栈,最后清空内存bss段内容。
relocate程序最后在设置程序指针寄存器为start_armboot()函数地址,程序跳转到stage2部分执行,注意最后的定义,_start_armboot全局变量的值是C语言函数start_armboot()函数的地址,使用这种方式可以在汇编中调用C语言编写的函数。
另外,有一种NOR类型Flash存储器,可以像使用内存一样直接执行程序,NOR
Flash被映射到地址0开始的内存空间。
注意,程序中第12行的_armboot_start即标号⑥_armboot_start
⑦start_armboot()函数
start_armboot()函数主要初始化ARM系统的硬件和环境变量,包括Flash存储器、FrameBuffer、网卡等,最后进入U-Boot应用程序主循环。
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
void start_armboot (void)
{
init_fnc_t
**init_fnc_ptr;
char *s;
#ifndef CFG_NO_FLASH
ulong
size;
#endif
#if defined(CONFIG_VFD) ||
defined(CONFIG_LCD)
unsigned long addr;
#endif
gd
= (gd_t*)(_armboot_start - CFG_MALLOC_LEN
- sizeof(gd_t));
__asm__
__volatile__("": : :"memory");
memset ((void*)gd,
0, sizeof (gd_t));
gd->bd
= (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd,
0, sizeof (bd_t));
monitor_flash_len
= _bss_start - _armboot_start;
for (init_fnc_ptr
= init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)()
!= 0) {
hang
();
}
}
#ifndef CFG_NO_FLASH
size
= flash_init
(); //初始化Flash存储器配置
display_flash_config
(size); //显示Flash存储器配置
#endif
#ifdef CONFIG_VFD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
addr
= (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE -
1); //计算FrameBuffer内存地址
size
= vfd_setmem
(addr); //计算FrameBuffer占用内存大小
gd->fb_base
=
addr; //设置FrameBuffer内存起始地址
#endif
#ifdef CONFIG_LCD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
addr
= (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE -
1); //计算rameBuffer内存地址
size
= lcd_setmem
(addr); //计算FrameBuffer占用内存大小
gd->fb_base
=
addr; //设置FrameBuffer内存起始地址
#endif
mem_malloc_init
(_armboot_start - CFG_MALLOC_LEN);
#if (CONFIG_COMMANDS &
CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); //初始化NAND
Flash存储器
#endif
#ifdef
CONFIG_HAS_DATAFLASH
AT91F_DataflashInit(); //初始化Hash表
dataflash_print_info();
#endif
env_relocate
(); //重新设置环境变量
#ifdef CONFIG_VFD
drv_vfd_init(); //初始化虚拟显示设置
#endif
gd->bd->bi_ip_addr
= getenv_IPaddr
("ipaddr"); //设置网卡的IP地址
{
int i;
ulong
reg;
char *s,
*e;
char tmp[64];
i
= getenv_r ("ethaddr",
tmp, sizeof (tmp)); //从网卡寄存器读取MAC地址
s
= (i > 0) ? tmp : NULL;
for (reg
= 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg]
= s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s
= (*e) ? e + 1 : e;
}
#ifdef
CONFIG_HAS_ETH1
i
= getenv_r ("eth1addr",
tmp, sizeof (tmp)); //读取Hash值
s
= (i > 0) ? tmp : NULL;
for (reg
= 0; reg < 6; ++reg) {
gd->bd->bi_enet1addr[reg]
= s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s
= (*e) ? e + 1 : e;
}
#endif
}
devices_init
(); //初始化开发板上的设备
#ifdef CONFIG_CMC_PU2
load_sernum_ethaddr
();
#endif
jumptable_init
(); //初始化跳转表
console_init_r
(); //初始化控制台
#if
defined(CONFIG_MISC_INIT_R)
misc_init_r
(); //初始化其他设备
#endif
enable_interrupts
(); //打开中断
#ifdef
CONFIG_DRIVER_CS8900
cs8900_get_enetaddr
(gd->bd->bi_enetaddr); //获取CS8900网卡MAC地址
#endif
#if
defined(CONFIG_DRIVER_SMC91111) || defined
(CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr"))
{
smc_set_mac_addr(gd->bd->bi_enetaddr); //设置SMC网卡MAC地址
}
#endif
if ((s
= getenv ("loadaddr")) != NULL)
{
load_addr
= simple_strtoul (s, NULL, 16);
}
#if (CONFIG_COMMANDS &
CFG_CMD_NET)
if ((s
= getenv ("bootfile")) != NULL)
{
copy_filename
(BootFile,
s, sizeof (BootFile)); //保存FrameBuffer
}
#endif
#ifdef
BOARD_LATE_INIT
board_late_init
(); //开发板相关设备初始化
#endif
#if (CONFIG_COMMANDS &
CFG_CMD_NET)
#if
defined(CONFIG_NET_MULTI)
puts ("Net: ");
#endif
eth_initialize(gd->bd);
#endif
for (;;)
{
main_loop
(); //进入主循环
}
}
void hang
(void)
{
puts ("###
ERROR ### Please RESET the board ###\n");
for (;;);
}
start_armboot()函数代码里有许多的宏相关,这个根据开发板的情况进行配置。函数里面的board_late_init()函数,该函数是开发板提供的,供不同的开发板做一些特有的初始化工作。
在start_armboot()函数中,使用宏开关括起来的代码是在各种开发板是最常用的功能,如CS8900网卡配置。整个函数配置完毕后,进入一个for死循环,调用main_loop()函数。这里需要注意,在main_loop()函数中也有一个for死循环。
start_armboot()函数使用死循环调用main_loop()函数,作用是防止main_loop()函数开始的初始化代码如果调用失败后重新执行初始化操作,保证程序能进入到U-Boot的命令行。
⑧main_loop()函数
main_loop()函数做的都是与具体平台无关的工作,主要包括初始化启动次数限制机制、设置软件版本号、打印启动信息、解析命令等。
❶设置启动次数有关参数。在进入main_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
void main_loop (void)
{
#ifndef
CFG_HUSH_PARSER
static char lastcommand[CFG_CBSIZE]
= { 0, };
int len;
int rc
= 1;
int flag;
#endif
#if
defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=
0)
char *s;
int bootdelay;
#endif
#ifdef CONFIG_PREBOOT
char *p;
#endif
#ifdef
CONFIG_BOOTCOUNT_LIMIT
unsigned long bootcount
= 0;
unsigned long bootlimit
= 0;
char *bcs;
char bcs_set[16];
#endif
#if defined(CONFIG_VFD)
&& defined(VFD_TEST_LOGO)
ulong
bmp =
0;
extern int trab_vfd
(ulong bitmap);
#ifdef
CONFIG_MODEM_SUPPORT
if (do_mdm_init)
bmp
=
1;
#endif
trab_vfd
(bmp);
#endif
#ifdef
CONFIG_BOOTCOUNT_LIMIT
bootcount
=
bootcount_load(); //加载保存的启动次数
bootcount++; //启动次数加1
bootcount_store
(bootcount); //更新启动次数
sprintf (bcs_set, "%lu",
bootcount); //打印启动次数
setenv
("bootcount", bcs_set);
bcs
= getenv ("bootlimit");
bootlimit
= bcs ? simple_strtoul (bcs, NULL, 10) :
0; //转换启动次数字符串为UINT类型
#endif
函数启动次数限制功能,启动次数限制可以被用户设置一个启动次数,然后保存在Flash存储器的特定位置,当到达启动次数后,U-Boot无法启动,该功能适合一些商业产品,通过配置不同的License限制用户重新启动系统。
❷接下来是Modem功能。如果系统中有Modem,打开该功能可以接受其他用户通过电话网络的拨号请求。Modem功能通常供一些远程控制的系统使用
1
2
3
4
5
6
7
8
9
10
#ifdef
CONFIG_MODEM_SUPPORT
debug
("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);
if (do_mdm_init)
{ //判断是否需要初始化Modem
char *str
=
strdup(getenv("mdm_cmd")); //获取Modem参数
setenv
("preboot", str);
if (str
!= NULL)
free (str);
mdm_init(); //初始化Modem
}
#endif
❸然后设置U-Boot版本号,初始化命令自动完成功能等。
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
#ifdef
CONFIG_VERSION_VARIABLE
{
extern char version_string[];
setenv
("ver",
version_string); //设置版本号
}
#endif
#ifdef
CFG_HUSH_PARSER
u_boot_hush_start
(); //初始化Hash功能
#endif
#ifdef
CONFIG_AUTO_COMPLETE
install_auto_complete(); //初始化命令自动完成功能
#endif
#ifdef CONFIG_PREBOOT
if ((p
= getenv ("preboot")) != NULL)
{
# ifdef
CONFIG_AUTOBOOT_KEYED
int prev
=
disable_ctrlc(1);
//关闭Crtl+C组合键
# endif
# ifndef
CFG_HUSH_PARSER
run_command
(p, 0); //运行Boot参数
# else
parse_string_outer(p,
FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef
CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev);
//恢复Ctrl+C组合键
# endif
}
#endif
程序开始是动态版本号功能支持代码,version_string变量是在其他文件定义的一个字符串变量,当用户改变U-Boot版本的时候会更新该变量。打开动态版本支持功能后,U-Boot在启动的时候会显示最新的版本号。
install_auto_comlpete()函数设置命令行自动完成功能,该功能与linux的shell类似,当用户输入一个部分命令后,可以通过按下键盘上的Tab键补全命令的剩余部分,main_loop()函数不同的功能使用宏开关控制不仅能提高代码模块化,理主要的是针对嵌入式系统Flash存储器大小设计的。在嵌入式系统上,不同的系统Flash存储空间不同。对于一些Flash空间比较紧张的设备来说,通过宏开关关闭一些不是特别必要的功能如命令行自动完成,可以减小U-Boot编译后的文件大小。
❹在进入主循环之前,如果配置了启动延迟功能,需要等待用户从串口或者网络接口输入。如果用户按下任意键打断,启动流程,会向终端打印出一个启动菜单。
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
#if
defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=
0)
s
= getenv ("bootdelay");
bootdelay
= s ? (int)simple_strtol(s, NULL, 10) :
CONFIG_BOOTDELAY; //启动延迟
debug
("### main_loop entered: bootdelay=%d\n\n", bootdelay);
# ifdef
CONFIG_BOOT_RETRY_TIME
init_cmd_timeout
(); //初始化命令行超时机制
# endif
#ifdef
CONFIG_BOOTCOUNT_LIMIT
if (bootlimit
&& (bootcount > bootlimit))
{ //检查是否超出启动次数限制
printf ("Warning:
Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s
= getenv ("altbootcmd");
}
else
#endif
s
= getenv ("bootcmd"); //获取启动命令参数
debug
("### main_loop: bootcmd=\"%s\"\n", s ? s
: "");
if (bootdelay
>= 0 && s && !abortboot (bootdelay))
{ //检查是否支持启动延迟功能
# ifdef
CONFIG_AUTOBOOT_KEYED
int prev
=
disable_ctrlc(1);
//关闭Ctrl+C组合键
# endif
# ifndef
CFG_HUSH_PARSER
run_command
(s, 0); //运行启动命令行
# else
parse_string_outer(s,
FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef
CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev);
//打开Ctrl+C组合键
# endif
}
# ifdef
CONFIG_MENUKEY
if (menukey
== CONFIG_MENUKEY)
{ //检查是否支持菜单键
s
= getenv("menucmd");
if (s)
{
# ifndef
CFG_HUSH_PARSER
run_command
(s, 0);
# else
parse_string_outer(s,
FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
}
}
#endif
#endif
#ifdef
CONFIG_AMIGAONEG3SE
{
extern void video_banner(void);
video_banner(); //打印启动图标
}
#endif
❺在各功能设置完毕后,程序进入一个for死循环,该循环不断使用readline()函数从控制台(一般是串口)读取用户的输入,然后解析,有关如何解析命令则可以参考U-Boot代码中run_command()函数的定义。
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
#ifdef
CFG_HUSH_PARSER
parse_file_outer();
for (;;);
#else
for (;;)
{ //进入命令行超时
#ifdef
CONFIG_BOOT_RETRY_TIME
if (rc
>= 0) {
reset_cmd_timeout(); //设置命令行超时
}
#endif
len
= readline
(CFG_PROMPT); //读取命令
flag
= 0;
if (len
> 0)
strcpy (lastcommand,
console_buffer);
else if (len
== 0)
flag
|= CMD_FLAG_REPEAT;
#ifdef
CONFIG_BOOT_RETRY_TIME
else if (len
== -2) {
puts ("\nTimed
out waiting for command\n");
# ifdef
CONFIG_RESET_TO_RETRY
do_reset
(NULL, 0, 0, NULL);
# else
return;
# endif
}
#endif
if (len
== -1)
puts ("\n");
else
rc
= run_command (lastcommand,
flag); //运行命令
if (rc
<= 0) {
lastcommand[0]
= 0;
}
}
#endif
}