目录
2. 分析启动流程
2.1 u-boot.lds链接脚本
2.2 start.S启动文件
2.2.1 设置异常向量表
2.2.2 设置SVC管理模式、关看门狗、关中断、设置时钟频率
2.2.3 禁用Cache和MMU、初始化存储控制器
2.2.4 设置栈
2.2.5 调用第一个C函数board_init_f
2.2.6 重定位
2.2.6.1 拷贝代码到SDRAM
2.2.6.2 修改动态链接地址数据
2.2.7 清除bss段
2.2.8 调用第二个C函数board_init_r(跳到SDRAM中运行)
2. 分析启动流程
使用移植u-boot-2012.04.01到JZ2440(一:创建单板)生成u-boot.bin下载到开发板上,开发板无法启动。本节对uboot启动流程进行分析(未经修改的u-boot-2012.04.01源码执行jz2440_config也就是smdk2410_config配置的情况),不进行修改源码。
2.1 u-boot.lds链接脚本
通过上一节编译,会在根目录生成一个u-boot.lds链接脚本文件,可以先通过u-boot.lds链接文件详解该文章对u-boot.lds进行基本了解,u-boot.lds代码如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm) /* 设置输出文件的体系架构 */
ENTRY(_start) /* 指定程序的入口为_start全局符号 */
SECTIONS /* SECTIONS 就是整个链接脚本的指定 */
{
/* 指定程序的链接地址有2种方法:一种是通过在include/configs/单板头文件.h(例如上一节的
jz2410.h)中定义CONFIG_SYS_TEXT_BASE,编译时Makefile会通过-Ttext来指定它;第二种是在链接脚本的SECTIONS
开头用.=0x20000000来指定。两种都可以实现相同效果。这两种技巧是可以共同配合使用的,也就是说既在链
接脚本中指定也用CONFIG_SYS_TEXT_BASE指定。两个都指定的话以后者为准。这里的uboot的最终链接起始
地址就是在include/configs/jz2410.h里的CONFIG_SYS_TEXT_BASE中指定的。*/
. = 0x00000000; /* 链接地址 */
. = ALIGN(4); /* 4字节对齐 */
/* 代码段 */
.text :
{
__image_copy_start = .; /* 映像文件赋值起始地址 */
arch/arm/cpu/arm920t/start.o (.text) /* 首先存放arch/arm/cpu/arm920t/start.o的代码段 */
*(.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 = .; /* 命令结束地址 */
. = ALIGN(4);
__image_copy_end = .; /* 映像文件赋值结束地址 */
.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}
.dynsym : {
__dynsym_start = .;
*(.dynsym)
}
_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
/* bss 段,.bss节包含了程序中所有未初始化的全局变量 */
.bss __rel_dyn_start (OVERLAY) : {
__bss_start = .; /* bss段起始地址 */
*(.bss)
. = ALIGN(4);
__bss_end__ = .; /* bss段结束地址 */
}
/* 其他段,这些节都是在编译链接时自动生成的,主要用于动态链接或调试使用 */
/DISCARD/ : { *(.dynstr*) } /* 动态字符串表`dynamic string`,用于保存符号名的字符串表; */
/DISCARD/ : { *(.dynamic*) } /* 保存了动态链接所需要的基本信息,例如依赖哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址等; */
/DISCARD/ : { *(.plt*) } /* 程序连接表`Procddure Linkage Table`,是实现动态链接的必要数据; */
/DISCARD/ : { *(.interp*) } /* 解释器`interpreter`的缩写 */
/DISCARD/ : { *(.gnu*) }
}
可以看到其中使用了许多符号,如_start、__image_copy_start、__bss_end__等,符号就类似于指针,用来表示一个地址,汇编中可以通过ldr得到符号表示的链接地址上的值(如: ldr r0, _start),通过adr得到符号代表的运行地址(如:adr r0, _start)。链接地址与运行地址在链接地址与运行地址中有介绍。
U-Boot编译之后会在其顶级目录中生成System.map和u-boot.map文件,System.map文件按链接地址由小到大的顺序列出了所有符号与其代表的链接地址,u-boot.map中包含了链接过程中涉及的目标文件将其所依赖的库文件(包含所有的text、data、bss等)的具体链接地址,System.map文件部分代码如下:
00000000 T __image_copy_start
00000000 T _start
00000020 t _undefined_instruction
00000024 t _software_interrupt
00000028 t _prefetch_abort
0000002c t _data_abort
00000030 t _not_used
00000034 t _irq
00000038 t _fiq
00000040 T _TEXT_BASE
00000044 T _bss_start_ofs
00000048 T _bss_end_ofs
... ...
u-boot.map文件部分代码如下:
... ...
Linker script and memory map
0x00000000 . = 0x0
0x00000000 . = ALIGN (0x4)
.text 0x00000000 0x57c00
0x00000000 __image_copy_start = .
arch/arm/cpu/arm920t/start.o(.text)
.text 0x00000000 0x480 arch/arm/cpu/arm920t/start.o
0x00000000 _start
0x00000040 _TEXT_BASE
0x00000044 _bss_start_ofs
0x00000048 _bss_end_ofs
0x0000004c _end_ofs
0x00000050 IRQ_STACK_START_IN
0x000000a8 relocate_code
... ...
这些链接地址就是通过u-boot.lds链接脚本与Makefile中用-Text指定共同决定的。
2.2 start.S启动文件
从上面可知,uboot的入口为_start符号代表的地址。_start这个全局符号在arch/arm/cpu/arm920t/start.S这个汇编文件中(汇编代码只要不遇到b或bl跳转指令,基本是按代码的从上到下的顺序执行的)。下面分几部分进行分析start.S。
2.2.1 设置异常向量表
.globl _start
_start: b start_code /* 跳转到start_code执行0x00000000复位异常*/
/* LDR{条件} 目的寄存器 <存储器地址> */
/* 当异常发生的时候,由硬件机制处理器自动的跳到一个固定地址去执行相关异常处理程序,而这个固定地址就是所谓的异常向量。 */
ldr pc, _undefined_instruction /* 0x00000004 未定义指令异常 */
ldr pc, _software_interrupt /* 0x00000008 软中断异常 */
ldr pc, _prefetch_abort /* 0x0000000c 预取异常 */
ldr pc, _data_abort /* 0x00000010 数据异常 */
ldr pc, _not_used /* 0x00000014 未使用异常,多余的指令 */
ldr pc, _irq /* 0x00000018 外部中断异常 */
ldr pc, _fiq /* 0x0000001c 快速中断异常 */
_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
.globl把_start这个标号全局化,是编译器的操作,并不是汇编指令。_start代表程序start.S的入口。这段代码的功能是设置异常向量表。b start_code所处的位置是与异常向量表基地址偏移量为的0的地方,所以当复位异常发生时(开机也属于复位异常),CPU会自动跳转入异常表基址偏移量为0处执行复位异常程序,即跳转执行start_code部分的代码。
这部分代码只是构建了异常向量表,当每个异常向量指向的内容为空(除start_code异常外)。因为U-Boot比较简单,只是作为引导程序,所以不需要很细致的处理各种异常。
2.2.2 设置SVC管理模式、关看门狗、关中断、设置时钟频率
在上电或者重启后,处理器取得第一条指令就是b start_code,所以会直接跳转到start_code地址处。代码如下:
start_code:
/*设置CPSR寄存器,让CPU进入SVC管理模式*/
mrs r0, cpsr //读出cpsr的值
bic r0, r0, #0x1f //清位
orr r0, r0, #0xd3 //位或
msr cpsr, r0 //写入cpsr
... ...
#ifdef CONFIG_S3C24X0
/* 关看门狗*/
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //关看门狗,使WTCON寄存器=0
/*关中断*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0] //关闭所有中断
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0] //关闭次级所有中断
# endif
/* 设置时钟频率, FCLK:HCLK:PCLK = 1:2:4 ,而FCLK默认为120Mhz*/
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C24X0 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //禁用Cache和MMU,初始化存储控制器
#endif
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80
bic sp, sp, #7 //sp=0x30000f80
ldr r0,=0x00000000
bl board_init_f
该部分工作内容如下:
1. 设置SVC管理模式
2. 关看门狗
3. 关中断
4. 设置时钟频率
接着往下看代码。
2.2.3 禁用Cache和MMU、初始化存储控制器
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
通过bl跳转指令跳到cpu_init_crit地址,代码如下:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* 禁用mmu和cashe
*/
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
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr /* 下面使用了双层bl跳转即双层函数调用,但是只有一个lr寄存器,所以先保存lr寄存器值 */
bl lowlevel_init /* 初始化存储控制器 */
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
该部分主要工作内容如下:
1. 禁用MMU和cashe
2. 跳转到lowlevel_init初始化存储控制器
3. 使用mov pc, lr指令返回(lr是连接寄存器,保存了子程序返回地址)
cache是CPU的缓冲区,它的作用是存放常用的数据和指令,提高CPU与内存之间数据与指令的传输速率。MMU是CPU的内存管理单元,它的作用是转换虚拟地址与物理地址。
关闭cache的原因:上电初始,DDR未初始化,当CPU从cache中取数据时,可能导致数据预取异常。另一方面,当汇编指令读取缓存数据,而实际物理地址对应的数据发生变化,导致CPU不能获取最新的数据。在C语言中无需关闭caches,因为C语言中可以使用volatile关键字避免上述情况。
关闭MMU的原因:U-Boot的作用是硬件初始化和引导操作系统,纯粹的初始化阶段,开启MMU会导致这个过程更复杂。
接着便初始化存储控制器,通过使用bl跳转到lowlevel_init,lowlevel_init定义在具体单板目录下(如上一节创建的board/samsung/jz2440/lowlevel_init.S)的lowlevel_init.S文件,在里面做了两件事:
1. 设置_TEXT_BASE为CONFIG_SYS_TEXT_BASE(重定位地址,源码默认为0)
2. lowlevel_init里设置存储控制器
3. 使用mov pc, lr指令返回(lr是连接寄存器,保存了子程序返回地址)
从lowlevel_init返回后再次取出之前保存的lr寄存器值返回。
2.2.4 设置栈
从bl cpu_init_crit往下继续执行:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80
bic sp, sp, #7 //sp=0x30000f80
ldr r0,=0x00000000
首先将CONFIG_SYS_INIT_SP_ADDR定义的值加载到栈指针sp中,这个宏定义在单板相关头文件(上一节创建的include/configs/smdk2440.h)中默认指定,通过反汇编u-boot文件(根目录执行arm-linux-objdump -d u-boot > u-boot.dis,得到的u-boot.dis就是反汇编文件)可以得到该宏等于0x30000f80。这段代码是为后面的第一个C函数board_init_f调用提供环境,也就是栈指针sp初始化。
2.2.5 调用第一个C函数board_init_f
下面看看调用的第一个C函数board_init_f里做了什么(函数定义在arch/arm/lib/board.c):
void board_init_f(ulong bootflag)
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp;
#ifdef CONFIG_PRAM
ulong reg;
#endif
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); //gd=0x30000f80
__asm__ __volatile__("": : :"memory");
memset((void *)gd, 0, sizeof(gd_t)); //清零gd结构体
首先将gd指针指向CONFIG_SYS_INIT_SP_ADDR,该宏在2.2.3 禁用Cache和MMU、初始化存储控制器中分析到等于0x30000f80。gd是一个保存在ARM的r8寄存器中的gd_t结构体的指针,该结构体包括了u-boot中所有重要的全局变量,在arch/arm/include/asm/Global_data.h文件中定义。
/*register表示尽量让cpu放在寄存器中,以提高其读写速度;asm (“r8”)是指定放在寄存器的r8*/
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
继续往下看:
gd->mon_len = _bss_end_ofs; //从u-boot反汇编得到_bss_end_ofs为000aefe4
#ifdef CONFIG_OF_EMBED
/* Get a pointer to the FDT */
gd->fdt_blob = _binary_dt_dtb_start;
#elif defined CONFIG_OF_SEPARATE
/* 得到文件结束地址,从u-boot反汇编得到_end_ofs=000741ec,正是u-boot.bin文件的大小 */
gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE);
#endif
/* Allow the early environment to override the fdt address */
gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
(uintptr_t)gd->fdt_blob);
/* 调用init_sequence[]数组中所有的初始化函数 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) { //执行函数,若函数执行出错,则进入hang()
hang ();
}
}
该部分代码主要作用如下:
1. 设置部分gd结构体
设置gd->mon_len = _bss_end_ofs,_bss_end_ofs标号在start.S中定义,_bss_end_ofs的值等于__bss_end__ - _start,通过反汇编文件得到_bss_end_ofs=000aefe4,可以认为是u-boot.bin的大小加上bss段大小。
2. 调用init_sequence数组的初始化函数
利用for循环遍历init_sequence全局数组(这里使用了全局变量,也就是位置相关码),并执行初始化函数。若函数返回值非0,则说明初始化函数执行错误,挂起程序,进入死循环hang。init_sequence数组部分内容如下:
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f, /* 设置系统时钟,设置各个GPIO引脚 */
#endif
#ifdef CONFIG_OF_CONTROL
fdtdec_check_fdt,
#endif
timer_init, /* 初始化定时器 */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* 设置gd的成员变量 */
init_baudrate, /* 设置波特率 */
serial_init, /* 初始化串口 */
console_init_f, /* 完成第一阶段的控制台初始化 */
display_banner, /* 用来打印输出一些信息 */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* 输出CPU信息 */
#endif
dram_init, /* 设置gd->ram_size= 0x04000000(64MB) */
NULL,
};
1. board_early_init_f函数(在board/samsung/smdk2410目录下的smdk2410.c文件内):完成ARM的时钟频率和IO的设置;
2. timer_init函数(在arch/arm/cpu/arm920t/s3c24x0目录下的timer.c文件内):完成定时器4的设置;
3. env_init函数(在common目录下的env_flash.c文件内,完成环境变量的设置;
4. init_baudrate函数(在arch/arm/lib目录下的board.c文件内):完成波特率的设置;
5. serial_init函数(在drivers/serial目录下的serial_s3c24x0.c文件内):完成串口通讯的设置;
6. console_init_f函数(在common目录下的console.c文件内):完成第一阶段的控制台初始化;
7. display_banner函数(在arch/arm/lib目录下的board.c文件内):用来打印输出一些信息;
8. print_cpuinfo函数(在arch/arm/cpu/arm920t/s3c24x0目录下的cpu_info.c文件内):输出CPU信息;
8. dram_init函数(在board/samsung/smdk2410目录下的smdk2410.c文件内):用来配置SDRAM的大小。
继续往下看board_init_f()的部分代码(去掉无关项):
/* addr=0x30000000+0x04000000=0x34000000
CONFIG_SYS_SDRAM_BASE是SDRAM基地址,为0X30000000
在init_sequence数组的dram_init函数里设置了gd->ram_size等于0x04000000 */
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
addr -= (4096 * 4); //addr=0x34000000-0x4000=0x33FFC000
addr &= ~(0x10000 - 1); // addr=0x33FF0000
gd->tlb_addr = addr; //将64kB分配给TLB,所以TLB地址为33FF0000~33FFFFFF
#endif
/* round down to next 4 kB limit */
addr &= ~(4096 - 1); //4kb对齐, addr=33FF0000
debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len; // 在前面分析过gd->mon_len=000aefe4
//所以addr=33FF0000-000aefe4=33F4101C
addr &= ~(4096 - 1); //4095=0xfff,4kb对齐, addr=33F41000
/* 所以分配给uboot的重定位起始地址为33F41000~33FFFFFF,0x33FFFFFF-0x33F41000=0xBEFFF=764k,
这段空间就是存放u-boot.bin的大小+bss段大小+零碎空间(如TLB、4kb对齐等) */
debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);
#ifndef CONFIG_SPL_BUILD
/*
* 因为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内存池
*/
addr_sp = addr - TOTAL_MALLOC_LEN;
addr_sp -= sizeof (bd_t); //分配一段bd_t结构体大的空间
bd = (bd_t *) addr_sp; //bd指向刚刚分配出来的bd_t结构体
gd->bd = bd; // 0x30000f80处的gd变量的成员bd等于bd_t基地址
addr_sp -= sizeof (gd_t); //分配一个gd_t结构体大的空间
id = (gd_t *) addr_sp; //id指向刚刚分配的gd_t结构体
gd->irq_sp = addr_sp; //0x30000f80处的gd结构体的成员irq_sp等于gd_t基地址
/* leave 3 words for abort-stack */
addr_sp -= 12;
/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
... ...
gd->relocaddr = addr; //设置存放uboot的重定位地址为33F41000
/* 设置栈顶,该栈顶向上的位置用来存放gd->irq_sp、id gd->bd、malloc、uboot、TLB(64kb) */
gd->start_addr_sp = addr_sp;
... ...
memcpy(id, (void *)gd, sizeof(gd_t)); //拷贝gd结构体到id
relocate_code(addr_sp, id, addr); //进入relocate_code()函数,重定位代码,以及各个符号
}
总结board_init_f函数的主要工作内容:
1. 设置gd结构体(划分SDRAM的使用空间)
2. 调用init_sequence数组的初始化函数
3. 重定位
在上一节创建的单板配置文件/include/configs/jz2440.h中,定义了SDRAM基地址(定义宏CONFIG_SYS_SDRAM_BASE等于0X30000000)、SDMRAM大小(定义宏PHYS_SDRAM_1_SIZE等于0x04000000),然后在board_init_f函数中根据这两个信息划分SDRAM的使用空间,即通过描述gd结构体来划分SDRAM的空间分配信息,为后面拷贝u-boot到SDRAM中(uboot重定位)做准备。
在执行完board_init_f函数后SDRAM内存划分如下图所示(未经修改的u-boot-2012.04.01源码执行jz2440_config也就是smdk2410_config配置的情况):
此时uboot还在flash中运行,然后会进入start.S里的relocate_code进行uboot重定位。
2.2.6 重定位
重定位,即把代码从flash中搬运到ram中,这个过程是需要汇编这个低级语言来完成的。传递给relocate_code函数的三个参数分别为栈顶指针SP、保存有SDRAM分布信息的id结构体指针、想要在SDRAM中存储U-Boot代码的起始地址(重定位地址)。
重定位又分为两部分,拷贝代码到SDRAM与修改动态链接地址数据。
2.2.6.1 拷贝代码到SDRAM
start.S里的relocate_code重定位第一部分拷贝代码到SDRAM代码如下:
relocate_code:
mov r4, r0 /* save addr_sp */ // r4=addr_sp栈顶值
mov r5, r1 /* save addr of gd */ // r5=id值
mov r6, r2 /* save addr of destination */ // r6=addr值:uboot重定位地址
/* Set up the stack */
stack_setup:
mov sp, r4 //设置栈addr_sp
adr r0, _start //r0=_start标号的运行地址(通过偏移量计算得到)
cmp r0, r6 /* 判断运行时uboot所处的地址与uboot重定位地址是否相同,相同表示uboot
已经在SDRAM中运行了,不需要拷贝代码到SDRAM中了,直接跳到clear_bss */
beq clear_bss /* 如果一样表示uboot在sdram中了,不需要重定位,直接跳到后面的清bss */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r3, _bss_start_ofs //r3=_bss_start_ofs标号地址上的值=__bss_start-_start=uboot代码大小
add r2, r0, r3 /* r2= uboot重定位之前的结束地址 */
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
//将r0处的两个32位数据拷到r9-r10中,然后r0+=8
stmia r1!, {r9-r10} /* copy to target address [r1]*/
//将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8
cmp r0, r2 /* until source end address [r2]*/ //判断拷贝的数据是否到结束地址
blo copy_loop
此时只是把代码复制到SDRAM上,如果指定的链接地址(指定链接地址方法在2.1 u-boot.lds链接脚本中有说明)与重定位地址不一样时,直接跳到SDRAM上执行u-boot代码就会造成一个问题,下面进行说明。
2.2.6.2 修改动态链接地址数据
假设PC执行到uboot的第二条指令,如下:
之前拷贝uboot代码到SDRAM中,当跳到SDRAM假设PC也执行uboot的第二条指令,如下(假如拷贝到SDRAM的起始地址就是0x33f41000):
可以看到这样的话相对于u-boot偏移地址为20的地方保存的数据没有变,这样运行PC又会跳到1e0地址也就是跳回flash上运行了。
如果指定链接地址与重定位地址相同,即指定链接地址=重定位地址=0x33f80000,编译uboot时会将例如000001e0改为33f801e0,这样就只需要拷贝uboot到SDRAM的基地址0x33f80000,重定位部分内容就完成了(这里指的是不考虑位置相关码的情况下!!!不然直接修改链接地址为0x33f80000,程序运行到board_init_f函数就会出错,因为里面使用了全局变量,是位置相关码,链接地址必须与运行地址一致)。
但是源码的重定位地址是程序计算得到的,而默认链接地址指定为0,明显不同,所以就要使用uboot采用的动态链接地址方法,在链接脚本uboot.lds中,可以看到这两个段(.rel.dyn和.dynsym):
.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}
.dynsym : {
__dynsym_start = .;
*(.dynsym)
}
这两个段里保存了各个文件的相对动态信息(.rel.dyn)、动态链接地址的符号(.dynsym)。修改动态链接地址数据就是例如将000001e0改为33f411e0。循环修改所有这样的动态信息,也就是加上在SDRAM的uboot空间相对于0地址的偏移值。
relocate_code重定位第二部分修改动态链接地址数据代码如下:
#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0=_TEXT_BASE地址上的值=CONFIG_SYS_TEXT_BASE(默认地址0x0) */
sub r9, r6, r0 /* r9=程序计算的重定位地址-链接时指定的重定位地址(默认为0x0) */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
#endif
... ...
_rel_dyn_start_ofs:
.word __rel_dyn_start - _start
_rel_dyn_end_ofs:
.word __rel_dyn_end - _start
_dynsym_start_ofs:
.word __dynsym_start - _start
2.2.7 清除bss段
继续往下执行start.S,有如下代码:
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldr r0, _bss_start_ofs /* r0=bss段起始地址与u-boot起始地址的偏移值 */
ldr r1, _bss_end_ofs /* r1=bss段结束地址与u-boot起始地址的偏移值 */
mov r4, r6 /* r4=r6=uboot重定位地址,在relocate_code重定位时从参数3得到的 */
add r0, r0, r4 /* r0=SDRAM中的bss段起始地址 */
add r1, r1, r4 /* r1=SDRAM中的bss段结束地址 */
mov r2, #0x00000000 /* r2=0 */
clbss_l:str r2, [r0] /* 将SDRAM中的bss段起始地址到bss段结束地址上的数据清0 */
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_led_on
#endif
2.2.8 调用第二个C函数board_init_r(跳到SDRAM中运行)
继续往下执行start.S,有如下代码:
#ifdef CONFIG_NAND_SPL /* 未定义,执行else */
ldr r0, _nand_boot_ofs
mov pc, r0
_nand_boot_ofs:
.word nand_boot
#else
ldr r0, _board_init_r_ofs /* r0=board_init_r函数起始地址与uboot起始地址0x0的偏移值 */
adr r1, _start /* r1=_start标号的相对地址(运行时的地址) */
add lr, r0, r1 /* lr=board_init_r函数的链接起始地址 */
add lr, lr, r9 /* 在2.2.6.2中修改动态链接地址数据时得到r9为程序计算的重定位起始地址与链接时指定的
重定位起始地址(0x0)偏移值,所以lr=board_init_r函数的链接起
始地址+r9=board_init_r函数重定位的起始地址 */
/* setup parameters for board_init_r */
mov r0, r5 /* r0=gd_t结构体首地址 */
mov r1, r6 /* r1=uboot重定位起始地址 */
/* PC跳到board_init_r函数重定位的起始地址(SDRAM中) */
mov pc, lr
_board_init_r_ofs:
.word board_init_r - _start
#endif
经过对board_init_r函数起始地址与uboot起始地址、重定位起始地址之间的偏移计算,得到重定位到SDRAM中的board_init_r函数地址,PC跳到该地址执行board_init_r函数(跳到SDRAM运行),传入的参数1为r0(gd_t结构体指针),传入的参数2为r1(重定位地址)。board_init_r函数部分代码如下(位于arch/arm/lib/board.c):
void board_init_r(gd_t *id, ulong dest_addr)
{
ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
ulong flash_size;
#endif
gd = id;
gd->flags |= GD_FLG_RELOC; /* 已经重定位标志 */
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
monitor_flash_len = _end_ofs;
... ...
board_init(); /* 设置单板id,boot参数存放地址 */
#ifdef CONFIG_CLOCKS
set_cpu_clk_info(); /* Setup clock information */
#endif
#ifdef CONFIG_SERIAL_MULTI
serial_initialize(); /* 串口初始化 */
#endif
debug("Now running in RAM - U-Boot at: %08lx\n", dest_addr);
... ...
malloc_start = dest_addr - TOTAL_MALLOC_LEN; /* 得到malloc空间起始地址 */
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN); /* 清0 malloc空间 */
#if !defined(CONFIG_SYS_NO_FLASH)
puts("Flash: ");
flash_size = flash_init(); /* 初始化nor flash */
if (flash_size > 0) {
... ...
print_size(flash_size, "");
... ...
} else {
puts(failed);
hang();
}
#endif
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init(); /* 初始化nand flash */
#endif
... ...
/* 环境变量重定位 */
env_relocate();
... ...
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr("ipaddr");
stdio_init(); /* get the devices list going. */
jumptable_init(); /* 跳转表初始化 */
console_init_r(); /* 控制台的第二部分的初始化 */
... ...
/* 中断初始化 */
interrupt_init();
/* 使能中断 */
enable_interrupts();
... ...
#if defined(CONFIG_CMD_NET)
{
char *s = getenv("bootfile");
if (s != NULL)
copy_filename(BootFile, s, sizeof(BootFile));
}
... ...
#endif
#if defined(CONFIG_CMD_NET)
puts("Net: ");
eth_initialize(gd->bd); /* 网卡初始化 */
... ...
#endif
... ...
/* 进入main_loop循环 */
for (;;) {
main_loop();
}
}
该部分主要内容如下:
1. 设置单板id
2. 初始化串口
3. 初始化堆内存
4. 初始化外部存储设备
5. 环境变量的重定位
6. 网卡初始化
7. 进入main_loop循环
到这里整个U-Boot启动完成,进入main_loop循环。若控制台有任意输入,则进入控制台命令解析-执行的循环。若无,则U-Boot将启动内核。