根据网上资料整理
项目链接
https://github.com/openbmc/u-boot
处理器型号
AST2400:arm926ejs
目录介绍
/arch 架构特定文件
- /arc ARC架构的通用文件
- /arm ARM架构的通用文件
- /avr32 AVR32架构的通用文件
- /blackfin Analog Devices Blackfin架构的通用文件
- /m68k m68k架构的通用文件
- /microblaze microblaze架构的通用文件
- /mips MIPS架构的通用文件
- /nds32 NDS32架构的通用文件
- /nios2 Altera NIOS2架构的通用文件
- /openrisc OpenRISC架构的通用文件
- /powerpc PowerPC架构的通用文件
- /sandbox 通用于独立于硬件的“沙箱”的文件
- /sh SH架构的通用文件
- /sparc SPARC架构的通用文件
- /x86 x86架构的通用文件
/api 用于外部应用程序的机器/架构独立API
/board 板依赖文件
/common 其他架构独立功能
/configs Board默认配置文件
/disk 磁盘驱动器分区处理代码
/doc 文档(不要期望太多)
/drivers 常用的设备驱动程序
/dts 包含用于构建内部U-Boot fdt的Makefile
/examples 独立应用程序等的示例代码
/fs 文件系统代码(cramfs,ext2,jffs2等)
/include 头文件
/lib 对所有体系结构都通用的库
/Licenses 各种许可证文件
/net 网络代码
/post 开机自检
/scripts 各种构建脚本和Makefile
/test 各种单元测试文件
/tools 用于构建S-Record或U-Boot映像等的工具
U-Boot启动流程
u-boot.lds
首先查看arch/arm/cpu/u-boot.lds链接脚本
#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*指定输出可执行文件是elf格式,32位ARM指令,小端*/
OUTPUT_ARCH(arm)
/*指定输出可执行文件的平台为ARM*/
ENTRY(_start)
/*指定输出可执行文件的起始代码段为_start*/
SECTIONS
{
#ifndef CONFIG_CMDLINE
/DISCARD/ : { *(.u_boot_list_2_cmd_*) }
#endif
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)
/*
* If CONFIG_ARMV7_SECURE_BASE is true, secure code will not
* bundle with u-boot, and code offsets are fixed. Secure zone
* only needs to be copied from the loading address to
* CONFIG_ARMV7_SECURE_BASE, which is the linking and running
* address for secure code.
*
* If CONFIG_ARMV7_SECURE_BASE is undefined, the secure zone will
* be included in u-boot address space, and some absolute address
* were used in secure code. The absolute addresses of the secure
* code also needs to be relocated along with the accompanying u-boot
* code.
*
* So DISCARD is only for CONFIG_ARMV7_SECURE_BASE.
*/
/DISCARD/ : { *(.rel._secure*) }
#endif
/*指定可执行文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成*/
. = 0x00000000;
/*从0x0位置开始*/
. = ALIGN(4);
/*代码以4字节对齐*/
.text :
/*代码段*/
{
*(.__image_copy_start)
/*u-boot将自己copy到RAM,此为需要copy的程序的start*/
*(.vectors)
/*arch/arm/lib/vectors.S,异常向量表*/
CPUDIR/start.o (.text*)
/*arch/arm/cpu/arm920t/start.S*/
*(.text*)
/*其他的代码段放在这里,即start.S/vector.S之后*/
}
#ifdef CONFIG_ARMV7_NONSEC
#ifndef CONFIG_ARMV7_SECURE_BASE
#define CONFIG_ARMV7_SECURE_BASE
#define __ARMV7_PSCI_STACK_IN_RAM
#endif
.__secure_start : {
. = ALIGN(0x1000);
*(.__secure_start)
}
.secure_text CONFIG_ARMV7_SECURE_BASE :
AT(ADDR(.__secure_start) + SIZEOF(.__secure_start))
{
*(._secure.text)
}
. = LOADADDR(.__secure_start) +
SIZEOF(.__secure_start) +
SIZEOF(.secure_text);
#ifdef __ARMV7_PSCI_STACK_IN_RAM
/* Align to page boundary and skip 2 pages */
. = (. & ~ 0xfff) + 0x2000;
#undef __ARMV7_PSCI_STACK_IN_RAM
#endif
__secure_end_lma = .;
.__secure_end : AT(__secure_end_lma) {
*(.__secure_end)
LONG(0x1d1071c); /* Must output something to reset LMA */
}
#endif
. = ALIGN(4);
/*代码段结束后,有可能4bytes不对齐了,此时做好4bytes对齐,以开始后面的.rodata段*/
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
/*在代码段之后,存放read only数据段*/
. = ALIGN(4);
/*和前面一样,4bytes对齐,以开始接下来的.data段*/
.data : {
*(.data*)
/*可读写数据段*/
}
. = ALIGN(4);
/*和前面一样,4bytes对齐*/
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
/*.data段结束后,紧接着存放u-boot自有的一些function,例如u-boot command等*/
}
. = ALIGN(4);
/* UEFI段开始处 */
.__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(4);
/* UEFI段结束 */
.image_copy_end :
{
*(.__image_copy_end)
/*至此,u-boot需要自拷贝的内容结束,总结一下,包括代码段,数据段,u_boot_list以及UEFI*/
}
*在老的uboot中,如果我们想要uboot启动后把自己拷贝到内存中的某个地方,只要把要拷贝的地址写给TEXT_BASE即可,然后boot启动后就会把自己拷贝到TEXT_BASE内的地址处运行,在拷贝之前的代码都是相对的,不能出现绝对的跳转,否则会跑飞。在新版的uboot里(2013.07),TEXT_BASE的含义改变了。它表示用户要把这段代码加载到哪里,通常是通过串口等工具。然后搬移的时候由uboot自己计算一个地址来进行搬移。新版的uboot采用了动态链接技术,在lds文件中有__rel_dyn_start和__rel_dyn_end,这两个符号之间的区域存放着动态链接符号,只要给这里面的符号加上一定的偏移,拷贝到内存中代码的后面相应的位置处,就可以在绝对跳转中找到正确的函数。*/
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
/*动态链接符存放在的段*/
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
/*动态链接符段结束*/
}
.end :
{
*(.__end)
}
_image_binary_end = .;
/*bin文件结束*/
/*
* Deprecated: this MMU section is used by pxa at present but
* should not be used by new boards/CPUs.
*/
. = ALIGN(4096);
/* MMU 表项 */
.mmutable : {
*(.mmutable)
}
/*
* Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c
* __bss_base and __bss_limit are for linker only (overlay ordering)
*/
/*bss段的描述*/
.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));
}
/*bss段的描述结束*/
/* 其他段,这些节都是在编译链接时自动生成的,主要用于动态链接或调试使用 */
.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.*) }
}
bootloader通常stage1和stage2两步骤,u-boot也不例外。
1.Stage1:依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在这个程序段,且可以用汇编语言来实现;
2.stage2:通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
具体的说,
1.Stage1 start.S代码结构
u-boot的stage1代码通常放在start.S文件中,用汇编语言写成,其主要代码部分如下:
(1) 定义入口。由于一个可执行文件必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM(Flash)的0x0地址。因此,必须通知编译器以使其知道这个入口,该工作可通过修改链接器脚本(.lds文件)来完成。
(2) 设置异常向量(Exception Vector)。
(3) 设置CPU的速度、时钟频率及终端控制寄存器。
(4) 初始化内存控制器。
(5) 将ROM中的代码复制到RAM中。
(6) 初始化堆栈。
(7) 转到RAM中执行,该工作可使用指令ldr pc来完成。
2.Stage2 C语言代码部分
common/board_f.c中的board_init_f()是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:
(1) 调用一系列的初始化函数。
(2) 初始化Flash设备。
(3) 初始化系统内存分配函数。
(4) 如果目标系统拥有NAND设备,则初始化NAND设备。
(5) 如果目标系统有显示设备,则初始化该类设备。
(6) 初始化相关网络设备,填写IP、MAC地址等。
(7) 进去命令循环(即整个boot的工作循环),接收用户从串口输入的命令,然后进行相应的工作。
vectors.S
_start位于arch/arm/lib/vectors.S
_start:
/*程序的全局入口,u-boot.lds设置此入口地址为0x00000000*/
.globl _start
/*
*************************************************************************
*
* Vectors have their own section so linker script can map them easily
*
*************************************************************************
*/
.section ".vectors", "ax"
/*
*************************************************************************
*
* Exception vectors as described in ARM reference manuals
*
* Uses indirect branch to allow reaching handlers anywhere in memory.
*
*************************************************************************
*/
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
/*ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳转指令,通过硬件实现。他们就是异常向量表*/
/*ldr,用于加载32bit的立即数或一个地址值到指定寄存器*/
b reset
ldr pc, _undefined_instruction /*未定义指令异常,0x04*/
ldr pc, _software_interrupt /*软中断异常,0x08*/
ldr pc, _prefetch_abort /*内存操作异常,0x0c*/
ldr pc, _data_abort /*数据异常,0x10*/
ldr pc, _not_used /*未使用,0x14*/
ldr pc, _irq /*慢速中断异常,0x18*/
ldr pc, _fiq /*快速中断异常,0x1c*/
.globl把_start这个标号全局化,是编译器的操作,并不是汇编指令。_start代表程序vectors.S的入口。这段代码的功能是设置异常向量表。b reset所处的位置是与异常向量表基地址偏移量为的0的地方,所以当复位异常发生时(开机也属于复位异常),CPU会自动跳转入异常表基址偏移量为0处执行复位异常程序,即跳转执行reset部分的代码(在start.S文件中)。
这部分代码只是构建了异常向量表,当每个异常向量指向的内容为空(除reset异常外)。因为uboot比较简单,只是作为引导程序,所以不需要很细致的处理各种异常。
start.S
reset这个全局符号在arch/arm/cpu/arm926ejs/start.S这个汇编文件中。下面分几部分进行分析start.S。
设置SVC管理模式
在上电或者重启后,处理器取得第一条指令就是b reset,所以会直接跳转到reset地址处。代码如下
.globl reset
reset:
/*设置CPSR寄存器,让CPU进入SVC管理模式*/
mrs r0,cpsr //读出cpsr的值
bic r0,r0,#0x1f //清位
orr r0,r0,#0xd3 //位或
msr cpsr,r0 //写入cpsr
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //跳到cpu_init_crit里禁用Cache和MMU,初始化存储控制器
#endif
bl _main //跳到_main
设置完时钟频率后执行bl cpu_init_crit跳转指令,cpu_init_crit也定义在start.S里。
禁用Cache和MMU、初始化存储控制器
通过bl跳转指令跳到cpu_init_crit地址,代码如下(arch/arm/cpu/926ejs/start.S文件中):
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush D cache before disabling it
*/
mov r0, #0
flush_dcache:
mrc p15, 0, r15, c7, c10, 3
bne flush_dcache
mcr p15, 0, r0, c8, c7, 0 /* cache清零 */
mcr p15, 0, r0, c7, c5, 0 /* TLB清零 */
/*禁用mmu和cache*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00000300 /* clear bits 9:8 (---- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
#ifdef CONFIG_SYS_EXCEPTION_VECTORS_HIGH
orr r0, r0, #0x00002000 /* set bit 13 (--V- ----) */
#else
bic r0, r0, #0x00002000 /* clear bit 13 (--V- ----) */
#endif
orr r0, r0, #0x00000002 /* set bit 1 (A) Align */
#ifndef CONFIG_SYS_ICACHE_OFF
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
#endif
mcr p15, 0, r0, c1, c0, 0
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY /* 未定义CONFIG_SKIP_LOWLEVEL_INIT_ONLY */
/*
* Go setup Memory and board specific bits prior to relocation.
*/
mov ip, lr /* 下面使用了双层bl跳转即双层函数调用,但是只有一个lr寄存器,所以先保存lr寄存器值 */
bl lowlevel_init /* 设置时钟频率 */
mov lr, ip /* restore link */
#endif
mov pc, lr /* 返回 */
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
cache是CPU的缓冲区,它的作用是存放常用的数据和指令,提高CPU与内存之间数据与指令的传输速率。MMU是CPU的内存管理单元,它的作用是转换虚拟地址与物理地址。
关闭cache的原因:上电初始,DDR未初始化,当CPU从cache中取数据时,可能导致数据预取异常。另一方面,当汇编指令读取缓存数据,而实际物理地址对应的数据发生变化,导致CPU不能获取最新的数据。在C语言中无需关闭caches,因为C语言中可以使用volatile关键字避免上述情况。
关闭MMU的原因:U-Boot的作用是硬件初始化和引导操作系统,纯粹的初始化阶段,开启MMU会导致这个过程更复杂。
接着便设置时钟频率,通过使用bl跳转到lowlevel_init,lowlevel_init定义在具体单板目录下(arch/arm/cpu/arm926ejs/lpc32xx/lowlevel_init.S)的lowlevel_init.S文件,该文件部分代码如下:
lowlevel_init位于arch/arm/cpu/arm926ejs/lpc32xx/lowlevel_init.S中
.globl lowlevel_init
lowlevel_init:
/* Set ARM, HCLK, PCLK dividers for normal mode */
ldr r0, =0x0000003D
ldr r1, =0x40004040
str r0, [r1]
/* Start HCLK PLL for 208 MHz */
ldr r0, =0x0001401E
ldr r1, =0x40004058
str r0, [r1]
/* wait for HCLK PLL to lock */
1:
ldr r0, [r1]
ands r0, r0, #1
beq 1b
/* switch to normal mode */
ldr r1, =0x40004044
ldr r0, [r1]
orr r0, #0x00000004
str r0, [r1]
/* Return to U-Boot via saved link register */
mov pc, lr /* 返回 */
从lowlevel_init返回后再次取出之前保存的lr寄存器值从cpu_init_crit返回。返回后执行的是bl _main,跳转到_main处执行。
crt0.S
start.S中执行到了bl _main,跳转到_main,_main入口在arch/arm/lib/crt0.S文件中,下面分几部分进行分析crt0.S。
设置栈
_main被ENTRY宏定义在crt0.S文件中, start.S中执行bl _main跳转到该文件首先执行的就是设置栈,如下代码:
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) /* 未定义,执行else语句 */
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* 未定义 */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* SP的8字节对齐 */
#endif
第一阶段C函数调用
设置完栈SP后继续往下执行代码:
mov r0, sp
bl board_init_f_alloc_reserve /* 以栈SP值作为参数调用board_init_f_alloc_reserve函数 */
mov sp, r0 /* 重新设置栈SP */
/* set up gd here, outside any C code */
mov r9, r0 /* 设置R9寄存器等于栈SP值 */
bl board_init_f_init_reserve /* 以新的栈SP值作为参数调用board_init_f_alloc_reserve函数 */
mov r0, #0
bl board_init_f /* 执行board_init_f函数 */
首先以栈SP值作为参数调用board_init_f_alloc_reserve函数。
调用board_init_f_alloc_reserve函数
board_init_f_alloc_reserve函数代码如下(conmmon/init/board_init.c文件中):
/* include/linux/kernei.h中有如下定义:
#define rounddown(x, y) ( \
{ \
typeof(x) __x = (x); \ /* 定义x类型的__x变量等于x */
__x - (__x % (y)); \ /* 栈向下增长,所以向下16字节对齐 */
} \
)
*/
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
/* 由传入的栈SP减去global_data结构体大小(反汇编查到该大小为168字节)并16字节对齐后返回 */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}
该函数将传入的栈SP减去global_data结构体大小(反汇编查到该大小为168字节)并16字节对齐后返回(其中调用的rounddown宏定义里面调用的typeof类似于C++里面的auto类型说明符,能自动推导表达式类型)
从board_init_f_alloc_reserve函数返回后设置新的栈SP后调用board_init_f_init_reserve函数。
调用board_init_f_init_reserve函数
board_init_f_init_reserve函数代码如下(conmmon/init/board_init.c文件中):
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr; /* 定义global_data结构体类型指针 */
#ifndef _USE_MEMCPY
int *ptr;
#endif
/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/
gd_ptr = (struct global_data *)base; /* global_data结构体指针指向base即栈SP地址处 */
/* zero the area */
#ifdef _USE_MEMCPY
memset(gd_ptr, '\0', sizeof(*gd)); /* 清0 global_data结构体 */
/* 无关代码 */
#else
for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
*ptr++ = 0;
#endif
/* 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
}
可以看到该函数只是将栈SP往上的global_data结构体大小空间清0。继续往下看代码,执行bl board_init_f跳到board_init_f函数。
调用board_init_f函数
board_init_f函数代码代码后如下(common/board_f.c文件中):
void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
/*
* For some archtectures, 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 vaule of gd->have_console.
*/
zero_global_data();
#endif
gd->flags = boot_flags; /* 设置gd->flags=0 */
gd->have_console = 0;
if (initcall_run_list(init_sequence_f)) /* 依次执行init_sequence_f数组里的初始化函数 */
hang(); /* 初始化失败则打印错误并死循环 */
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
首先设置gd指向的结构体成员,gd在中定义如下(arch/arm/include/asm/global_data.h文件中):
#define gd get_gd() /* gd等于get_gd()函数返回值 */
static inline gd_t *get_gd(void)
{
gd_t *gd_ptr;
#ifdef CONFIG_ARM64
/*
* Make will already error that reserving x18 is not supported at the
* time of writing, clang: error: unknown argument: '-ffixed-x18'
*/
__asm__ volatile("mov %0, x18\n" : "=r" (gd_ptr));
#else
__asm__ volatile("mov %0, r9\n" : "=r" (gd_ptr)); /* C语言嵌入汇编语法,设置gd_ptr=R9寄存器值 */
#endif
return gd_ptr; /* 返回R9寄存器值 */
}
由于在调用board_init_f函数之前设置了R9寄存器等于栈SP值,所以board_init_f函数里设置gd(gd是一个global_data结构体类型的全局变量)指向的结构体成员,设置的就是栈地址往上的global_data结构体,接着使用initcall_run_list函数循环调用init_sequence_f数组里的初始化函数,init_sequence_f数组去掉无用宏代码后如下(判断是否定义了宏方法:1、查看根目录的.config是否定义,2、Makefile是否-D指定):
static init_fnc_t init_sequence_f[] = {
/* setup_mon_len函数:
* 设置(ulong)&__bss_end - (ulong)_start;
* 这里gd->mon_len等于uboot.bin大小加上bss段的大小,_start为0
*/
setup_mon_len,
initf_malloc, /* 空函数 */
initf_console_record, /* 空函数 */
arch_cpu_init, /* 空函数 */
initf_dm, /* 空函数 */
arch_cpu_init_dm, /* 空函数 */
mark_bootstage, /* 标记名字 */
board_early_init_f, /* 设置系统时钟,设置各个GPIO引脚 */
timer_init, /* 初始化定时器 */
env_init, /* 设置gd的成员,初始化环境变量 */
init_baud_rate, /* 设置波特率 */
serial_init, /* 初始化串口 */
console_init_f, /* 完成第一阶段的控制台初始化 */
display_options, /* 打印uboot版本等信息 */
display_text_info, /* 打印uboot代码信息 */
print_cpuinfo, /* 打印uboot时钟频率信息 */
announce_dram_init, /* 打印“ DRAM: ” */
dram_init, /* 设置gd->ram_size= 0x04000000(64MB) */
setup_dest_addr, /* 将gd->relocaddr、gd->ram_top指向SDRAM最顶端 */
reserve_round_4k, /* gd->relocaddr 4KB对齐 */
reserve_mmu, /* 预留16KB的MMU页表并且64KB对齐 */
reserve_trace, /* 空函数 */
/* reserve_uboot函数:
* gd->relocaddr -= gd->mon_len;
* gd->relocaddr &= ~(4096 - 1);
* gd->start_addr_sp = gd->relocaddr;
* 预留uboot空间(u-boot.bin大小加上bss段大小)
*/
reserve_uboot,
/* reserve_malloc函数:
* gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN;
* 因为jz2440.h默认定义了CONFIG_ENV_ADDR,所以此时在include/common.h中
* 执行#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
* 也就是TOTAL_MALLOC_LEN=4*1024*1024+0x10000=4MB+64KB
* 预留4MB+64KB MALLOC内存池
*/
reserve_malloc,
/* reserve_board函数:
* gd->start_addr_sp -= sizeof(bd_t); 预留bd_t结构体空间,查看反汇编可知为80字节
* gd->bd = (bd_t *)gd->start_addr_sp; 指定重定位bd地址
* memset(gd->bd, '\0', sizeof(bd_t)); 清零
*/
reserve_board,
setup_machine, /* gd->bd->bi_arch_number = CONFIG_MACH_TYPE */
reserve_global_data, /* 预留gd结构体空间,查看反汇编可知为168字节。并设置gd->new_gd */
reserve_fdt, /* 如果设置了gd->fdt_blob则预留fdt设备树空间,这里没有设置,忽略 */
reserve_arch, /* 空函数 */
/* reserve_stacks函数:
* gd->start_addr_sp -= 16;
* gd->start_addr_sp &= ~0xf;
* return arch_reserve_stacks();这里调用的不是board_f.c里的arch_reserve_stacks函数
* 因为该函数被__weak修饰符声明,调用的是arch/arm/lib/stack.c里的arch_reserve_stacks函数
* gd->irq_sp = gd->start_addr_sp;
* gd->start_addr_sp -= 16;
*/
reserve_stacks, /* 设置栈SP16字节对齐 */
setup_dram_config, /* 设置gd结构体的SDRAM地址与大小 */
show_dram_config, /* 打印SDRAM信息 */
display_new_sp, /* 打印新的栈地址 */
reloc_fdt, /* 没有设置gd->new_fdt,忽略 */
/* setup_reloc函数:
* gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE; 计算重定位地址与链接地址偏移值
* 由于jz2440.h中CONFIG_SYS_TEXT_BASE默认为0,所以gd->reloc_off=gd->relocaddr
* memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
* 将原来的gd复制到gd->new_gd上去
*/
setup_reloc,
NULL,
};
board_init_f函数的主要工作内容就是调用init_sequence_f数组的初始化函数并设置其他gd结构体成员,gd结构体是用来描述划分SDRAM的使用空间的结构。
board_init_f函数就通过这些宏还有__bss_end等其他相关信息来划分SDRAM的使用空间,即通过描述gd结构体来划分SDRAM的空间分配信息,为后面拷贝u-boot到SDRAM中(uboot重定位)做准备。
此时uboot还在flash中运行,执行完board_init_f函数后接着往下分析代码。
重新设置栈
从board_init_f函数执行完后,接着返回汇编文件crt0.S执行,紧接着第一阶段C函数调用的代码往下分析,部分代码如下:
ldr sp, [r9, #GD_START_ADDR_SP] /* 设置栈sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* 未定义,执行#else分支 */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8字节对齐 */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
上面的GD_START_ADDR_SP、GD_BD、GD_SIZE定义如下(lib/asm-offsets.c文件中):
/*定义GD_SIZE为global_data结构体大小 */
DEFINE(GD_SIZE, sizeof(struct global_data));
/* 定义GD_BD为bd成员存放在global_data结构体中的偏移值 */
DEFINE(GD_BD, offsetof(struct global_data, bd));
/* 定义GD_START_ADDR_SP为start_addr_sp成员存放在global_data结构体中的偏移值 */
DEFINE(GD_START_ADDR_SP, offsetof(struct global_data, start_addr_sp));
其中GD_START_ADDR_SP表示start_addr_sp成员存放在global_data结构体中的偏移值,GD_BD为bd成员存放在global_data结构体中的偏移值,GD_SIZE为global_data结构体大小。
uboot重定位
继续往下分析代码,如下:
adr lr, here /* 链接寄存器lr指向here,此时指向地址还是在flash中 */
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off=gd->relocaddr - CONFIG_SYS_TEXT_BASE */
add lr, lr, r0 /* 链接指向地址指向重定位后的here,此时指向地址在SDRAM中 */
#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 /* uboot重定位,注意这里用的是b跳转,lr值不变 */
here:
/*
* now relocate vectors
*/
bl relocate_vectors /* 这里使用的是bl,lr指向下一条指令,后面再详细分析 */
上面的GD_RELOC_OFF、GD_RELOCADDR定义如下(lib/asm-offsets.c文件中):
/* 定义GD_RELOCADDR为relocaddr成员存放在global_data结构体中的偏移值 */
DEFINE(GD_RELOCADDR, offsetof(struct global_data, relocaddr));
/* 定义GD_RELOC_OFF为reloc_off成员存放在global_data结构体中的偏移值 */
DEFINE(GD_RELOC_OFF, offsetof(struct global_data, reloc_off));
拷贝uboot到SDRAM
relocate_code第一部分代码如下(arch/arm/lib/relocate.S文件中):
ENTRY(relocate_code)
/* 第一部分:拷贝uboot到SDRAM */
ldr r1, =__image_copy_start /* r1 =__image_copy_start的链接地址,这里为0 */
/* r4 =重定位地址-__image_copy_start的链接地址=重定位地址-0*/
/* 通过r4的值是否等于0判断uboot是否已经在重定位地址上了,如果r4=0表示uboot已经在重定位地址上了,
不需要再重定位了直接跳到lr指向的地方去(也就是here标号处) */
subs r4, r0, r1
beq relocate_done /* 重定位完成,跳到lr指向的地方去(也就是here标号处) */
ldr r2, =__image_copy_end /* 令r2=__image_copy_end的链接地址 */
/* 循环uboot到重定位地址*/
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
第一部分通过将flash中的uboot拷贝到SDRAM中,此时只是把代码复制到SDRAM上,如果指定的链接地址(与重定位地址不一样时,直接跳到SDRAM上执行u-boot代码就会造成一个问题,下面进行说明。
修改动态链接地址数据
relocate_code第二部分代码如下(arch/arm/lib/relocate.S文件中):
/* 第二部分:修改动态链接地址数据 */
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, #23 /* relative fixup? */
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)
当执行完第二部分修改动态链接地址数据后,例如与__bss_start、__bss_end这些标号还有所有函数地址相关数据等等这些数据都被修改加上了重定位基地址与链接基地址的偏移量。然后执行bx lr也就是PC跳到uboot重定位中的here标号处(在flash中)接着运行。
异常向量表重定位
在relocate_code里执行bx lr后程序就从flash中的here标号处继续往下运行,代码如下:
here:
bl relocate_vectors
relocate_vectors去掉无关代码如下(arch/arm/lib/relocate.S文件中):
ENTRY(relocate_vectors)
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}
bx lr
ENDPROC(relocate_vectors)
relocate_vectors主要是进行异常向量表的重定位,将异常向量表拷贝到SDRAM的正确的地址中去,然后执行bx lr(因为执行bl relocate_vectors使用的是bl指令,此时lr链接寄存器指向下一条指令地址,所以执行bx lr是执行bl relocate_vectors的下一条指令)。
清bss段、调用第二阶段C函数board_init_r
bl relocate_vectors之后(去掉无关代码)的代码如下(arch/arm/lib/crt0.S文件中):
bl c_runtime_cpu_setup /* mov pc, lr,跳到下一条指令 */
ldr r0, =__bss_start /* 得到bss段起始地址 */
ldr r1, =__bss_end /* 得到bss段结束地址 */
mov r2, #0x00000000 /* r2设为0 */
/* 循环将SDRAM中的bss段清0 */
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
mov r0, r9 /* r0等于新的gd结构体 */
ldr r1, [r9, #GD_RELOCADDR] /* r1等于gd->relocaddr也就是重定位地址 */
/* pc跳到board_init_r函数的链接地址处(已经动态修改,加上了重定位偏移值,所以在SDRAM中) */
ldr pc, =board_init_r /* this is auto-relocated! */
ENDPROC(_main)
接着使用ldr pc, =board_init_r让PC跳到board_init_r函数的链接地址处,该函数的链接地址在修改动态链接地址数据中加上了重定位偏移值,所以此时board_init_r函数的链接地址在SDRAM中了。这样就完成了从flash中跳到SDRAM中运行程序。
board_init_r函数去掉无关代码后如下(common/board_r.c文件中):
```c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
与board_init_f函数类似,使用initcall_run_list函数循环调用init_sequence_r数组里的初始化函数,init_sequence_r数组去掉无用宏代码后如下(判断是否定义了宏方法:1、查看根目录的.config是否定义,2、Makefile是否-D指定):
init_fnc_t init_sequence_r[] = {
initr_trace, /* 空函数 */
initr_reloc, /* gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT; */
#ifdef CONFIG_ARM
initr_caches, /* 打印"WARNING: Caches not enabled\n" */
#endif
initr_reloc_global_data, /* monitor_flash_len = _end - __image_copy_start; */
initr_barrier, /* 空函数 */
/* initr_malloc函数:
* malloc_start = gd->relocaddr - TOTAL_MALLOC_LEN;
* mem_malloc_init((ulong)map_sysmem(malloc_start, TOTAL_MALLOC_LEN),
* TOTAL_MALLOC_LEN);
*/
initr_malloc,
initr_console_record, /* 空函数 */
bootstage_relocate,
initr_bootstage, /* 标记名字 */
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
/* board_init函数:
* gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
* gd->bd->bi_boot_params = 0x30000100;
* icache_enable();
* dcache_enable();
*/
board_init,
#endif
stdio_init_tables,
initr_serial, /* 调用serial_initialize() */
initr_announce, /* debug "Now running in RAM - U-Boot at: %08lx\n" */
power_init_board, /* 空函数 */
#ifndef CONFIG_SYS_NO_FLASH
initr_flash, /* 初始化NOR Flash */
#endif
#ifdef CONFIG_CMD_NAND
initr_nand, /* 初始化NAND Flash */
#endif
initr_env, /* 初始化环境变量 */
initr_secondary_cpu, /* 空函数 */
stdio_add_devices, /* 空函数 */
/* initr_jumptable函数:
* gd->jt = malloc(sizeof(struct jt_funcs));
* #include <_exports.h>
*/
initr_jumptable,
console_init_r, /* 控制台初始化 */
interrupt_init, /* 中断初始化 */
#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
initr_enable_interrupts, /* 使能中断 */
#endif
#ifdef CONFIG_CMD_NET
initr_ethaddr, /* eth_getenv_enetaddr("ethaddr", bd->bi_enetaddr); */
#endif
#ifdef CONFIG_CMD_NET
INIT_FUNC_WATCHDOG_RESET
initr_net, /* 网卡初始化 */
#endif
run_main_loop, /* 进入main_loop */
};
到这里整个U-Boot启动完成,进入main_loop循环。若控制台有任意输入,则进入控制台命令解析-执行的循环。若无,则U-Boot将启动内核。
run_main_loop
run_main_loop
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在common\main.c中定义
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
//bootstage_mark_name函数调用了show_boot_progress,记录执行到哪一步了
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifdef CONFIG_VERSION_VARIABLE
//setenv设置环境变量ver为version_string
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */
//如果定义了CONFIG_SYS_HUSH_PARSER,那么配置u-boot使用hush shell来作为执行器。hush shell是一种轻量型的shell。cli_init用来初始化hush shell使用的一些变量。hush shell的实现机制比较复杂,这里不做过多解释
cli_init();
//run_preboot_environment_command函数从环境变量中获取"preboot"的定义,该变量包含了一些预启动命令,一般环境变量中不包含该项配置。
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
//bootdelay_process从环境变量中取出"bootdelay"和"bootcmd"的配置值,将取出的"bootdelay"配置值转换成整数,赋值给全局变量stored_bootdelay,最后返回"bootcmd"的配置值。bootdelay为u-boot的启动延时计数值,计数期间内如无用户按键输入干预,那么将执行"bootcmd"配置中的命令。
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
//在执行的时间stored_bootdelay(秒)内,如无用户按键输入干预,那么abortboot_normal函数将返回0,否则返回1。 当无用户按键干预时,接下来将调用run_command_list执行上述从环境变量中读取的"bootcmd"配置值。注意该函数的参数s。run_command_list中调用了hush shell的命令解释器(parse_stream_outer函数),解释bootcmd中的启动命令。环境变量bootcmd中的启动命令,用来设置linux必要的启动环境,然后加载和启动linux内核。u-boot启动linux内核后,将控制权交给linux内核,至此不再返回。
autoboot_command(s);
//若启动内核,则不会执行到cli_loop,若按键,则进入cli_loop函数,循环等待执行命令。
cli_loop();
panic("No CLI available");
}
main_loop中执行延时函数,并决定是否启动内核,或者解析命令。