uboot源码分析
1.U-Boot系统加载器
U-Boot是一个规模庞大的开源Bootloader软件,最初是由denx(www.denx.de)发起。U-Boot的前身是PPCBoot,目前是SourceForge(www.sourceforge.net)的一个项目。
最初的U-Boot仅支持PowerPC架构的系统,称做PPCBoot。从0.3.2官方版本之后开始逐步支持多种架构的处理器,目前可以支持PowerPC(MPC5xx、MPC8xx、MPC82xx、MPC7xx、MPC74xx)、ARM(ARM7、ARM9、StrongARM、Xscale)、MIPS(4kc、5kc)、X86等处理器,支持的嵌入式操作系统有Linux、Vx-Works、NetBSD、QNX、RTEMS、ARTOS、LynxOS等,是PowerPC、ARM9、Xscale、X86等系统通用的Boot方案。
U-Boot支持的处理器和操作系统很多,但是它对PowerPC系列处理器和Linux操作系统支持最好。U-Boot支持的功能也较多,对于嵌入式开发常用的查看、修改内存,从网络下载操作系统镜像等功能都提供了很好的支持。U-Boot的项目更新较快,支持的目标板众多,是学习底层开发很好的示例。
2.ViVi系统加载器
ViVi是韩国的mizi公司专门针对ARM9处理器设计的一款Bootloader。它的特点是操作简便,同时提供了完备的命令体系,目前在三星系列的ARM9处理器上ViVi也比较流行。
与U-Boot相比,由于ViVi支持的处理器单一,ViVi的代码也要小很多。同时,ViVi的软件架构和配置方法采用和Linux内核类似的风格,对于有过配置编译Linux内核经验的读者,ViVi更容易上手。
与其他的Bootloader一样,ViVi有两种工作模式:启动加载模式和下载模式。使用启动加载模式,在目标板上电后,ViVi会从预先配置好的Flash分区读取Linux或者其他系统的镜像并且启动系统;使用下载模式,ViVi向用户提供了一个命令行接口,通过该接口用户可以使用ViVi提供的命令。ViVi主要提供了5个命令如下:
Load:把二进制文件载入Flash或RAM。
Part:操作MTD分区信息。显示、增加、删除、复位、保存MTD分区。
Param:设置参数。
Boot:启动系统。
Flash:管理Flash,如删除Flash的数据。
与Linux内核的组织类似,ViVi的源代码主要包括arch、init、lib、drivers和include等几个目录,共200多个代码文件。各目录的具体功能请参考ViVi相关的信息。
=====================================================================》》》
=====================================================================》》》
=====================================================================》》》
基本目录分类:
common目录是与体系结构无关的文件,包括实现各种命令的C语言源代码文件。
cpu目录
通用设备的驱动程序存放在drivers目录下。U-Boot自带了许多设备的驱动,包括显示芯片、网络接口控制器、USB控制器、I2C器件等,对于大多数用户而言已经够用,用户也可以按照自己的需求增加或者修改设备驱动。
fs
net目录
post
rtc目录
tools目录
==========================================
hao:
start_armboot => bootm.c
向量表在_start开始阶段已经汇编搞定。
其实主要就是个寄存器和内存的基本处理。
===》》》
列出了U-Boot在ARM处理器启动过程中的几个关键点,
从图中看出U-Boot的启动代码分布在start.S、low_level_init.S、board.c和main.c文件中。
start.S
low_level_init.S
board.c
main.c是一个与平台无关的代码,U-Boot应用程序的入口在此文件中
取出CPSR寄存器的值,CPSR寄存器保存当前系统状态,
使用比特清除命令清空了CPSR寄存器的中断控制位,表示清除中断。
设置了CPSR寄存器的处理器模式位为管理模式,然后在第117行写入CPSR的值强制切换处理器为超级保护模式。
定义看门狗控制器有关的变量,
根据平台设置看门狗定时器。
设置时钟分频寄存器的值。
需要根据CONFIG_SKIP_LOWLEVEL_INIT宏的值是否跳转到cpu_init_crit标号执行
===========cpu_init_crit==========
- 228
- 238
- 239
- 240
#ifndef CONFIG_SKIP_LOWLEVEL_INIT - 241
cpu_init_crit: - 242
- 245
mov r0, #0 - 246
mcr p15, 0, r0, c7, c7, 0 // 1.刷新cache - 247
mcr p15, 0, r0, c8, c7, 0 // 2.刷新TLB - 248
- 249
- 252
mrc p15, 0, r0, c1, c0, 0 - 253
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) - 254
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) - 255
orr r0, r0, #0x00000002 @ set bit 2 (A) Align - 256
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache - 257
mcr p15, 0, r0, c1, c0, 0 - 258
- 259
- 264
mov ip, lr - 265
bl lowlevel_init // 跳转到lowlevel_init - 266
mov lr, ip - 267
mov pc, lr - 268
#endif
==>2.
TLB的作用是在处理器访问内存数据的时候做地址转换。TLB的全称是TranslationLookaside Buffer,可以翻译做旁路缓冲。
TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。当应用程序访问一个虚拟地址的时候,会从TLB中查询出对应的物理地址,然后访问物理地址。TLB通常是一个分层结构,使用与Cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。
==>3.
程序第252~257行关闭MMU。MMU是内存管理单元(MemoryManagementUnit)的缩写。在现代计算机体系结构上,MMU被广泛应用。使用MMU技术可以向应用程序提供一个巨大的虚拟地址空间。在U-Boot初始化的时候,程序看到的地址都是物理地址,无须使用MMU。
=========================lowlevel_init=========================
- 133
lowlevel_init: - 134
- 135
- 136
- 137
ldr r0, =SMRDATA // 读取SMRDATA变量地址 - 138
ldr r1, _TEXT_BASE // 读取_TEXT_BASE变量地址 - 139
sub r0, r0, r1 //得出相对偏移 - 140
ldr r1, =BWSCON //主要是了解BANK的位宽,16位 - 141
add r2, r0, #13*4 // 得到SMRDATA占用的大小,结尾处的偏移
- 142
0: - 143
ldr r3, [r0], #4 // 加载SMRDATA到内存 ,相当于一个while循环 - 144
str r3, [r1], #4 - 145
cmp r2, r0 - 146
bne 0b //循环,相当于while循环 - 147
- 148
- 149
mov pc, lr - 152
- 153
- 154
SMRDATA: // 定义SMRDATA值 - 155
.word (0+(B1_BWSCON<</SPAN><</SPAN>4)+(B2_BWSCON<</SPAN><</SPAN>8)+(B3_BWSCON<</SPAN><</SPAN>12)+(B4_BWSCON <</SPAN><</SPAN>16)+(B5_BWSCON<</SPAN><</SPAN>20)+(B6_BWSCON<</SPAN><</SPAN>24)+(B7_BWSCON<</SPAN><</SPAN>28)) - 156
.word ((B0_Tacs<</SPAN><</SPAN>13)+(B0_Tcos<</SPAN><</SPAN>11)+(B0_Tacc<</SPAN><</SPAN>8)+(B0_ Tcoh<</SPAN><</SPAN>6)+ (B0_Tah<</SPAN><</SPAN>4)+(B0_Tacp<</SPAN><</SPAN>2)+(B0_PMC)) - 157
.word ((B1_Tacs<</SPAN><</SPAN>13)+(B1_Tcos<</SPAN><</SPAN>11)+(B1_Tacc<</SPAN><</SPAN>8)+(B1_Tcoh<</SPAN><</SPAN>6)+ (B1_Tah<</SPAN><</SPAN>4)+(B1_Tacp<</SPAN><</SPAN>2)+(B1_PMC)) - 158
.word ((B2_Tacs<</SPAN><</SPAN>13)+(B2_Tcos<</SPAN><</SPAN>11)+(B2_Tacc<</SPAN><</SPAN>8)+(B2_Tcoh<</SPAN><</SPAN>6)+ (B2_Tah<</SPAN><</SPAN>4)+(B2_Tacp<</SPAN><</SPAN>2)+(B2_PMC)) - 159
.word ((B3_Tacs<</SPAN><</SPAN>13)+(B3_Tcos<</SPAN><</SPAN>11)+(B3_Tacc<</SPAN><</SPAN>8)+(B3_Tcoh<</SPAN><</SPAN>6)+ (B3_Tah<</SPAN><</SPAN>4)+(B3_Tacp<</SPAN><</SPAN>2)+(B3_PMC)) - 160
.word ((B4_Tacs<</SPAN><</SPAN>13)+(B4_Tcos<</SPAN><</SPAN>11)+(B4_Tacc<</SPAN><</SPAN>8)+(B4_Tcoh<</SPAN><</SPAN>6)+ (B4_Tah<</SPAN><</SPAN>4)+(B4_Tacp<</SPAN><</SPAN>2)+(B4_PMC)) - 161
.word ((B5_Tacs<</SPAN><</SPAN>13)+(B5_Tcos<</SPAN><</SPAN>11)+(B5_Tacc<</SPAN><</SPAN>8)+(B5_Tcoh<</SPAN><</SPAN>6)+ (B5_Tah<</SPAN><</SPAN>4)+(B5_Tacp<</SPAN><</SPAN>2)+(B5_PMC)) - 162
.word ((B6_MT<</SPAN><</SPAN>15)+(B6_Trcd<</SPAN><</SPAN>2)+(B6_SCAN)) - 163
.word ((B7_MT<</SPAN><</SPAN>15)+(B7_Trcd<</SPAN><</SPAN>2)+(B7_SCAN)) - 164
.word ((REFEN<</SPAN><</SPAN>23)+(TREFMD<</SPAN><</SPAN>22)+(Trp<</SPAN><</SPAN>20)+(Trc<</SPAN><</SPAN>18)+ (Tchr<</SPAN><</SPAN>16)+REFCNT) - 165
.word 0x32 - 166
.word 0x30 - 167
.word 0x30
程序第137~141行计算SMRDATA需要加载的内存地址和大小。首先在137行读取SMRDATA的变量地址,之后计算存放的内存地址并且记录在r0寄存器,然后根据总线宽度计算需要加载的SMRDATA大小,并且把加载结束地址存放在r2寄存器。
程序第142~146行复制SMRDATA到内存。SMRDATA是开发板上内存映射的配置
正式开始了第二阶段:
relocate部分的代码负责把U-BootStage2的代码从Flash存储器加载到内存,代码如下:
- 163
#ifndef CONFIG_SKIP_RELOCATE_UBOOT - 164
relocate: - 165
adr r0, _start // 获取当前代码存放地址 00000000 - 166
ldr r1, _TEXT_BASE // 获取内存存放代码地址 33f80000 - 167
cmp r0, r1 //地址相同说明程序已经在内存中则不需要加载 - 168
beq stack_setup - 169
//开始加载
- 170
ldr r2,_armboot_start // 获取stage2代码存放地址 - 171
ldr r3, _bss_start // 获取内存代码段起始地址 - 172
sub r2, r3, r2 //不包括向量表,U-BOOT的整个大小
- 173
add r2, r0, r2 33f80000size // 计算stage2代码结束地址 - 174
- 175
copy_loop: - 176
ldmia r0!, {r3-r10} // 从Flash复制代码到内存 - 177
stmia r1!, {r3-r10} - 178
cmp r0, r2 - 179
ble copy_loop - 180
#endif - 181
- 182
// 在内存中建立堆栈 - 183
stack_setup: - 184
ldr r0, _TEXT_BASE - 185
sub r0, r0, #CFG_MALLOC_LEN // 分配内存区域 - 186
sub r0, r0, #CFG_GBL_DATA_SIZE - 187
#ifdef CONFIG_USE_IRQ - 188
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) - 189
#endif - 190
sub sp, r0, #12 - 191
- 192
clear_bss: // 初始化内存bss段内容为0 - 193
ldr r0, _bss_start // 查找bss段起始地址 - 194
ldr r1, _bss_end // 查找bss段结束地址 - 195
mov r2, #0x00000000 // 清空bss段内容 - 196
- 197
clbss_l: str r2, [r0] - 198
add r0, r0, #4 - 199
cmp r0, r1 - 200
ble clbss_l - 223
ldr pc, _start_armboot // 设置程序指针为start_armboot()函数地址 - 224
- 225
_start_armboot: - .word
start_armboot//这里是个C的函数名字,也就是入口地址
代码解释:
main_loop()函数
main_loop()函数做的都是与具体平台无关的工作,主要包括初始化启动次数限制机制、设置软件版本号、打印启动信息、解析命令等。
(1)设置启动次数有关参数。在进入main_loop()函数后,首先是根据配置加载已经保留的启动次数,并且根据配置判断是否超过启动次数。代码如下:
- 295
void main_loop (void) - 296
{ - 297
#ifndef CFG_HUSH_PARSER - 298
static char lastcommand[CFG_CBSIZE] = { 0, }; - 299
int len; - 300
int rc = 1; - 301
int flag; - 302
#endif - 303
- 304
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) - 305
char *s; - 306
int bootdelay; - 307
#endif - 308
#ifdef CONFIG_PREBOOT - 309
char *p; - 310
#endif - 311
#ifdef CONFIG_BOOTCOUNT_LIMIT - 312
unsigned long bootcount = 0; - 313
unsigned long bootlimit = 0; - 314
char *bcs; - 315
char bcs_set[16]; - 316
#endif - 317
- 318
#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO) - 319
ulong bmp = 0; - 320
extern int trab_vfd (ulong bitmap); - 321
- 322
#ifdef CONFIG_MODEM_SUPPORT - 323
if (do_mdm_init) - 324
bmp = 1; - 325
#endif - 326
trab_vfd (bmp); - 327
#endif - 328
- 329
#ifdef CONFIG_BOOTCOUNT_LIMIT - 330
bootcount = bootcount_load(); // 加载保存的启动次数 - 331
bootcount++; // 启动次数加1 - 332
bootcount_store (bootcount); // 更新启动次数 - 333
sprintf (bcs_set, "%lu", bootcount); // 打印启动次数 - 334
setenv ("bootcount", bcs_set); - 335
bcs = getenv ("bootlimit"); - 336
bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0; // 转换启动次数字符串为UINT类型 - 337
#endif
第329~337行是启动次数限制功能,启动次数限制可以被用户设置一个启动次数,然后保存在Flash存储器的特定位置,当到达启动次数后,U-Boot无法启动。该功能适合一些商业产品,通过配置不同的License限制用户重新启动系统。
(2)程序第339~348行是Modem功能。如果系统中有Modem,打开该功能可以接受其他用户通过电话网络的拨号请求。Modem功能通常供一些远程控制的系统使用,代码如下:
- 339
#ifdef CONFIG_MODEM_SUPPORT - 340
debug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init); - 341
if (do_mdm_init) { // 判断是否需要初始化Modem - 342
char *str = strdup(getenv("mdm_cmd")); // 获取Modem参数 - 343
setenv ("preboot", str); - 344
if (str != NULL) - 345
free (str); - 346
mdm_init(); // 初始化Modem - 347
} - 348
#endif
(3)接下来设置U-Boot的版本号,初始化命令自动完成功能等。代码如下:
- 350
#ifdef CONFIG_VERSION_VARIABLE - 351
{ - 352
extern char version_string[]; - 353
- 354
setenv ("ver", version_string); // 设置版本号 - 355
} - 356
#endif - 357
- 358
#ifdef CFG_HUSH_PARSER - 359
u_boot_hush_start (); // 初始化Hash功能 - 360
#endif - 361
- 362
#ifdef CONFIG_AUTO_COMPLETE - 363
install_auto_complete(); // 初始化命令自动完成功能 - 364
#endif - 365
- 366
#ifdef CONFIG_PREBOOT - 367
if ((p = getenv ("preboot")) != NULL) { - 368
# ifdef CONFIG_AUTOBOOT_KEYED - 369
int prev = disable_ctrlc(1); // 关闭Crtl+C组合键 - 370
# endif - 371
- 372
# ifndef CFG_HUSH_PARSER - 373
run_command (p, 0); // 运行Boot参数 - 374
# else - 375
parse_string_outer(p, FLAG_PARSE_SEMICOLON | - 376
FLAG_EXIT_FROM_LOOP); - 377
# endif - 378
- 379
# ifdef CONFIG_AUTOBOOT_KEYED - 380
disable_ctrlc(prev); // 恢复Ctrl+C组合键 - 381
# endif - 382
} - 383
#endif
程序第350~356行是动态版本号功能支持代码,version_string变量是在其他文件定义的一个字符串变量,当用户改变U-Boot版本的时候会更新该变量。打开动态版本支持功能后,U-Boot在启动的时候会显示最新的版本号。
程序第363行设置命令行自动完成功能,该功能与Linux的shell类似,当用户输入一部分命令后,可以通过按下键盘上的Tab键补全命令的剩余部分。
main_loop()函数不同的功能使用宏开关控制不仅能提高代码模块化,更主要的是针对嵌入式系统Flash存储器大小设计的。在嵌入式系统上,不同的系统Flash存储空间不同。对于一些Flash空间比较紧张的设备来说,通过宏开关关闭一些不是特别必要的功能如命令行自动完成,可以减小U-Boot编译后的文件大小。
(4)在进入主循环之前,如果配置了启动延迟功能,需要等待用户从串口或者网络接口输入。如果用户按下任意键打断,启动流程,会向终端打印出一个启动菜单。代码如下:
- 385
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) - 386
s = getenv ("bootdelay"); - 387
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; // 启动延迟 - 388
- 389
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay); - 390
- 391
# ifdef CONFIG_BOOT_RETRY_TIME - 392
init_cmd_timeout (); // 初始化命令行超时机制 - 393
# endif - 394
- 395
#ifdef CONFIG_BOOTCOUNT_LIMIT //一般不会检查这破玩意。 - 396
if (bootlimit && (bootcount > bootlimit)) { // 检查是否超出启动次数限制 - 397
printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n", - 398
(unsigned)bootlimit); - 399
s = getenv ("altbootcmd"); - 400
} - 401
else - 402
#endif - 403
s = getenv ("bootcmd"); // 获取启动命令参数 - 404
- 405
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<</SPAN>UNDEFINED>"); - 406
- 407
if (bootdelay >= 0 && s && !abortboot (bootdelay)) { //检查是否支持启动延迟功能 - 408
# ifdef CONFIG_AUTOBOOT_KEYED - 409
int prev = disable_ctrlc(1); // 关闭Ctrl+C组合键 - 410
# endif - 411
- 412
# ifndef CFG_HUSH_PARSER - 413
run_command (s, 0); // 运行启动命令行 - 414
# else - 415
parse_string_outer(s, FLAG_PARSE_SEMICOLON | - 416
FLAG_EXIT_FROM_LOOP); - 417
# endif - 418
- 419
# ifdef CONFIG_AUTOBOOT_KEYED - 420
disable_ctrlc(prev); // 打开Ctrl+C组合键 - 421
# endif - 422
} - 423
- 424
# ifdef CONFIG_MENUKEY - 425
if (menukey == CONFIG_MENUKEY) { // 检查是否支持菜单键 - 426
s = getenv("menucmd"); - 427
if (s) { - 428
# ifndef CFG_HUSH_PARSER - 429
run_command (s, 0); - 430
# else - 431
parse_string_outer(s, FLAG_PARSE_SEMICOLON | - 432
FLAG_EXIT_FROM_LOOP); - 433
# endif - 434
} - 435
} - 436
#endif - 437
#endif - 438
- 439
#ifdef CONFIG_AMIGAONEG3SE - 440
{ - 441
extern void video_banner(void); - 442
video_banner(); // 打印启动图标 - 443
} - 444
#endif
(5)在各功能设置完毕后,程序第454行进入一个for死循环,该循环不断使用readline()函数(第463行)从控制台(一般是串口)读取用户的输入,然后解析。有关如何解析命令请参考U-Boot代码中run_command()函数的定义,
- 446
- 449
#ifdef CFG_HUSH_PARSER - 450
parse_file_outer(); - 451
- 452
for (;;); - 453
#else - 454
for (;;) { // 进入命令行循环 - 455
#ifdef CONFIG_BOOT_RETRY_TIME - 456
if (rc >= 0) { - 457
- 460
reset_cmd_timeout(); // 设置命令行超时 - 461
} - 462
#endif - 463
len = readline (CFG_PROMPT); // 读取命令 - 464
- 465
flag = 0; - 466
if (len > 0) - 467
strcpy (lastcommand, console_buffer); - 468
else if (len == 0) - 469
flag |= CMD_FLAG_REPEAT; - 470
#ifdef CONFIG_BOOT_RETRY_TIME - 471
else if (len == -2) { - 472
- 474
puts ("\nTimed out waiting for command\n"); - 475
# ifdef CONFIG_RESET_TO_RETRY - 476
- 477
do_reset (NULL, 0, 0, NULL); - 478
# else - 479
return; - 480
# endif - 481
} - 482
#endif - 483
- 484
if (len == -1) - 485
puts ("<</SPAN>INTERRUPT>\n"); - 486
else - 487
rc = run_command (lastcommand, flag); // 运行命令 - 488
- 489
if (rc <</SPAN>= 0) { - 490
- 491
lastcommand[0] = 0; - 492
} - 493
} // deadloop - 494
#endif - 495
}
U-BOOT的功能设计 基本就在这里