uboot启动流程如下:
一、首先查看arch/arm/cpu/u-boot.lds链接脚本
二、_start位于arch/arm/cpu/arm920t/start.S
三、_start会跳转到start_code处
四、进入第一个C函数:board_init_f()
五、接下来进入重定位
六、清除bss段
七、跳转到board_init_r()函数
1、首先查看arch/arm/cpu/u-boot.lds链接脚本
在进行uboot的链接启动流程分析之前,我们要先看一下uboot的链接脚本。在C语言中,程序可以分为以下几个部分:
代码段(.text)
数据段(.data)
只读数据段(.rodata)
未初始化的数据段(.bss)
堆和栈
其中堆和栈属于动态区域,在程序运行时动态分配和释放。而代码段、数据段、只读数据段在连接之后产生。一个C语言程序分为映像和运行时两种状态:
映象只包含代码段、数据段、只读数据段;
运行时还将包含动态形成的堆和栈区域
而链接脚本的作用就是:
指定代码段和数据段、只读数据段在内存中的存放地址
指定代码的入口地址
如:uboot-2012.04.01的uboot的lds文件,其部分代码
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
__image_copy_start = .;
CPUDIR/start.o (.text)
board/samsung/smdk2440/libsmdk2440.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data)
}
. = ALIGN(4);
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
...
}
从上面的代码我们可以看到.text段,其指定了所有uboot文件的.text段的存放位置。往下是.rodata段,其指定只读数据的存放位置,紧跟着.text段,并且进行了四字节的对齐操作,再往下是数据段.data的存放位置。其中有一个__u_boot_cmd_start,它充当一个标号的作用,其指定了boot_cmd的起始地址,cmd也就是我们在boot的shell使用的那些指令,而__u_boot_cmd_end指定的是结束地址。
uboot程序的入口地址,在第3行的ENTRY(_start)处指定了
uboot代码追踪
我们可以使用arm-linux-nm u-boot显示我们生成的uboot文件的符号信息
...
33f9964c T xyzModem_stream_open
33f993c0 T xyzModem_stream_read
33f9905c T xyzModem_stream_terminate
...
有了这个命令后,我们就可以很方便的查找到我们的uboot启动代码的地址,通过arm-linux-nm u-boot | grep _start,我们找到我们的起始地址
33fada14 A __bss_start
33fad4bc A __u_boot_cmd_start
33f80044 T _armboot_start
33f80048 T _bss_start
33f80000 T _start
33f80184 t _start_armboot
33fb2cbc b bin_start_address
33fadaf8 b mem_malloc_start
33f9daf8 t setup_start_tag
知道了地址,我们怎么定位文件的位置?可以通过arm-linux-addr2line来定位,其可以将地址转成行号输出显示出来
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
我们可以通过arm-linux-addr2line -e u-boot 33f80000找到我们起始地址所在的文件,以及其所在的文件中的位置

上图是uboot的启动代码流程分析
首先会先执行start.S,其主要完成的工作将CPU设置为SVC模式(保护模式)、关闭中断、看门狗等
接着会执行lowlevel_init.S的代码,主要完成一些初始化工作
接着开始进入C语言程序,在进行C语言程序之前需要先开辟一个栈空间,由ctr0.S来完成
进入C语言程序后,执行board_init_f()函数,进行自搬移操作前期的内存分配
自搬移由crt0.S来完成,然后第二次初始化C语言的运行环境,接着程序就完全进入C语言环境执行
board_init_r()会进行MMC和网络初始化,然后进入自启动模式,如果在倒计时之前按下回车就进入main loop循环,也就是我们看到的uboot的shell界面。
注:uboot启动以后会进入3秒倒计时,如果 在3秒倒计时结束之前按下按键,那么就会进入uboot模式,如果倒计时结束都没有按下回车键,那么就会自动启动linux内核,这个功能就是由run_main_loop函数来完成的。run_main_loop函数定义在文件common/board_r.c中。
uboot源码分析
了解了整个uboot大致的启动流程后,我们就可以开始着手进行uboot的源代码分析。我们从start.S文件开始分析,也就是uboot的入口地址_start
...
.globl _start
_start: b 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
...
在第3行有一条跳转指令b reset跳转到reset标号处去执行,在reset下面的那些ldr xxxx指令适用于后面设置中断向量表使用的,reset标号处的代码如下
reset:
bl save_boot_params
/*
* 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
从注释中我们可以看到,其作用就是关闭中断,将CPU设置为SVC32模式
接着_start的代码往下走
/*
* 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 SCTRL register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main
从注释中可以看到,行2~14大致完成内容是设置SCTRL寄存器,也就是系统控制器以及设置中断向量表,这个中断向量表的内容就是我们前面在reset下面的那些ldr xxxx指令,这个需要借助CPU的协处理器CP15进行设置
完成上面的操作后,会跳转到cpu_init_cp15和cpu_init_crit标号执行,最后转到_main函数执行
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_ARM_ERRATA_742230
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
...
ENDPROC(cpu_init_cp15)
从代码的注释可以看出,该标号的代码抓哟完成关闭缓存、关闭虚拟内存MMU的作用,以及设置了diagnostic寄存器
接着回到reset的cpu_init_crit继续往下追踪
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)
cpu_init_crit处的代码很简单,就是调用lowlevel_init进行初始化
lowlevel_init主要完成板级相关的一些初始化工作,如:时钟、内存、网卡、串口的初始化
接着我们回到reset的_main继续往下追踪。_main标号定义在文件arch/arm/lib/crt0.S里面,可以通过arm-linux-nm和arm-linux-address2line进行定位
下面我们对_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
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
sub sp, #GD_SIZE /* allocate one GD above SP */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r8, sp /* GD is above SP */
mov r0, #0
bl board_init_f
#if ! defined(CONFIG_SPL_BUILD)
从注释看出,_main首先初始化了C的运行环境,接着调用board-init_f函数。除此以外进行sp寄存器的设置。然后是board_init_f;
void board_init_f(ulong bootflag)
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp;
...
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
...
}
其中有两个最重要的全局变量bd_t *bd和gd_t *id它们用于存储一些信息,这两个全局变量的存储空间就是在调用board_init_f之前已经在栈中分配了
board_init_f主要完成两个工作,一是对gd_t *id进行初始化,二是调用一系列的函数完成一些工作
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, [r8, #GD_START_ADDR_SP] /* r8 = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r8, [r8, #GD_BD] /* r8 = gd->bd */
sub r8, r8, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r8, #GD_RELOC_OFF] /* lr = gd->start_addr_sp */
add lr, lr, r0
ldr r0, [r8, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
在最后一行有一个b relocate_code跳转指令,就是对代码进行自搬移操作
here:
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
ldr r0, =__bss_start /* this is auto-relocated! */
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 */
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
bl coloured_LED_init
bl red_led_on
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r8 /* gd_t */
ldr r1, [r8, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
ldr pc, =board_init_r /* this is auto-relocated! */
/* we should not return here. */
#endif
搬移结束后,就进入C语言环境运行
从注释可以看到设置了bss段,也就是对bss段进行了清零,标号clbss_1标号处的循环代码。
之后就会通过board_init_r标号,跳转到C语言函数
void board_init_r(gd_t *id, ulong dest_addr)
{
ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
ulong flash_size;
#endif
...
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop();
}
...
}
board_init_r会打印一些信息到串口,然后进入到main_loop()循环,如果此时按下回车就会进入到uboot的shell交互界面,否则就会自动引导os系统
uboot的启动如下,假设我们使用SD卡进行启动;
iROM会将SD卡内的BL1加载到iRAM中,然后将控制器交给BL1
BL1会将BL2以及填充,一共16K的代码,加载到iRAM
uboot由两部分组成,一部分是SPL完成硬件相关操作,另一部分是uboot代码
接着SPL部分会将uboot代码加载到DRAM之中,此时uboot位于DRAM的内存地址0x43E00000处
uboot加载需要加载linux系统的uImage到内存,一般加载到0x4008000处,为了防止把uboot自己给覆盖了,所以uboot还要进行一次自搬移操作
然后CPU跳转新的uboot地址执行,这是我们就能看到串口大一些信息了
正常宿主机对u-boot完成编译后,要对编译好的uboot进行移植烧录,将一个编译好的u-boot.bin文件烧写到sd卡或者是开发板的flash存储器中
在开发板上电后,板载CPU来引导internal iROM引导flash开始执行工作,检测boot loader是否有效并将boot loader读取到SRAM
在SRAM将boot loader启动的同时初始化DRAM控制器以扩展内存,之后再boot loader的引导下将u-boot的镜像读取到DRAM中,u-boot启动开始等待指令输出或者加载启动内核
所对应的函数存放在arch/arm/cpu/u-boot.lds链接脚本中
参考
https://blog.youkuaiyun.com/m0_55873979/article/details/124852411