(1)确定链接脚本文件:
uboot根目录下Makefile中的LDSCRIPT宏值,就是指定链接脚本(如:arch/arm/cpu/u-boot.lds)路径用的。
(2)从脚本文件找入口:
在链接脚本中可以看到ENTRY()指定的入口,如:ENTRY(_start), _start就是入口
(3)链接脚本简要分析:
#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") /*指定输出可执行文件是elf格式,32位ARM指令,小端*/
OUTPUT_ARCH(arm) /*指定输出可执行文件的平台为ARM*/
ENTRY(_start) /*指定输出可执行文件的起始代码段为_start*/
SECTIONS
{
/*指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成*/
. = 0x00000000; /*从0x0位置开始*/
. = ALIGN(4); /*代码以4字节对齐*/
.text :
{
*(.__image_copy_start) /*映像文件复制起始地址,*/
*(.vectors) /*.vectors标记的代码段*/
CPUDIR/start.o (.text*)
*(.text*)
}
.....
}
__image_copy_start
在arch/arm/lib/sections.c中定义:
char __image_copy_start[0] __attribute__((section(".__image_copy_start"))); //C文件中利用这种方式把一个变量或者函数标记到指定段。
在文件common/board_r.c中被调用:
static int initr_reloc_global_data(void)
{
#ifdef __ARM__
monitor_flash_len = _end - __image_copy_start;
/###################################分###################################割###################################线###################################/
分析文件arch/arm/lib/vectors.S
.globl _start /*声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl与.global相同*/
_start:
b reset /* b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号处执行程序*/
ldr pc, _undefined_instruction /*未定义指令异常向量,ldr的作用是,将符号_undefined_instruction指向的地址的内容加载到pc*/
ldr pc, _software_interrupt /*软件中断向量*/
ldr pc, _prefetch_abort /*预取指令异常向量*/
ldr pc, _data_abort /*数据操作异常向量*/
ldr pc, _not_used /*未使用*/
ldr pc, _irq /*irq中断向量*/
ldr pc, _fiq /*fiq中断向量*/
.globl _undefined_instruction
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _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 /*下面的代码开始16字节对齐,即当上段的代码运行完后不是16字节对齐,就填充0xdeadbeef,直到使下段的代码开始处16字节对齐。*/
/* IRQ stack memory (calculated at run-time) + 8 bytes, 不能确定堆栈指针地址,所以填充无效数字,当堆栈指针确定以后,将其加8填充到这里*/
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de
/*下面是IRQ中断,放了一个 IRQ_STACK_START 标号,标号内容为: 0x0badc0de ,此内容没有意义,只是为了占一个坑。程序刚刚运行,还没有进入初始化,根本不知
道堆栈指针现在应该放在什么地方,所以一开始,作者做了一个小技巧,填充一个无效的数字,等初始化以后堆栈指针建立好后,再将实际的堆栈指针写到这里。*/
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif /* CONFIG_USE_IRQ */
/###################################分###################################割###################################线###################################/
vectors.S中第一条指令_start: b reset
reset定义在文件arch/arm/cpu/armv7/start.S中
start.S的执行流程如下图:
reset:
/*如果没有重新定义save_boot_params,则使用<arch/arm/cpu/armv7/start.S>中的save_boot_params。其不做任何事情,直接返回。*/
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 /*将cpsr寄存器的内容传送到r0寄存器*/
and r1, r0, #0x1f /*标志位清零*/
teq r1, #0x1a /*测试处理器是否处于HYP模式,HYP是armv-7a为cortex-A15处理器提供硬件虚拟化引进的管理模式。*/
bicne r0, r0, #0x1f /*工作模式位清零*/
orrne r0, r0, #0x13 /*设置成SVC管理模式*/
orr r0, r0, #0xc0 /*关闭FIQ和IRQ中断*/
msr cpsr,r0 /*将r0的值赋给cpsr*/
/*
* 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))
/* 设置异常向量的基地址,正常异常模式下异常向量的基地址为0x00000000,高异常模式下异常向量的基地址为0xffff0000,这里V=0设置成正常异常模式 */
/* 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
/* 重新设置异常向量的基地址,只有上面V=0的情况下,这里才能去重新配置异常向量表的基地址!*/
/* 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 //调用c代码
/###################################分###################################割###################################线###################################/
cpu_init_cp15, arch/arm/cpu/armv7/start.S
/*************************************************************************
*
* cpu_init_cp15
*
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*
*************************************************************************/
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs /*禁止从TLB中取地址描述符,也就是禁止虚拟地址到物理地址的转换,因为刚开始操作的都是物理寄存器!*/
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache /*关闭指令cache*/
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array /*关闭分支预测*/
mcr p15, 0, r0, c7, c10, 4 @ DSB /*多核cpu之间进行数据同步*/
mcr p15, 0, r0, c7, c5, 4 @ ISB /*进行指令同步,放弃流水线中已经取到的指令,重新取指令*/
/*
* disable MMU stuff and caches
*/
/*******************************************************
*1、为什么要关闭mmu?因为MMU是把虚拟地址转化为物理地址得作用而我们现在是要设置控制寄存器,而控制寄存器本来就是实地址(物理地址),再使能MMU,不就是多此一举了吗?
********************************************************/
/*******************************************************
*2、为什么要关闭cache?*catch和MMU是通过CP15管理的,刚上电的时候,CPU还不能管理他们。所以上电的时候MMU必须关闭,指令cache可关闭,可不关闭,但数据cache一定要关闭
*否则可能导致刚开始的代码里面,去取数据的时候,从catch里面取,而这时候RAM中数据还没有cache过来,导致数据预取异常
********************************************************/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-) /*设置成正常异常模式,即异常向量表的基地址为0x00000000*/
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) /*关闭指令cache,关闭指令对齐检测,关闭mmu*/
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align /*使能对齐检测*/
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB /*使能分支预测*/
#ifdef CONFIG_SYS_ICACHE_OFF
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache /*时能指令cache*/
#endif
mcr p15, 0, r0, c1, c0, 0
#ifdef CONFIG_ARM_ERRATA_716044
mrc p15, 0, r0, c1, c0, 0 @ read system control register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif
#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
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
#ifdef CONFIG_ARM_ERRATA_743622
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 6 @ set bit #6
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_751472
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 21 @ set bit #21
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
mov pc, lr @ back to my caller /*程序返回*/
ENDPROC(cpu_init_cp15)
/###################################分###################################割###################################线###################################/
cpu_init_crit
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
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)
lowlevel_init标号对应的源码:(这个代码要初始化内存等,是和具体平台有关的,所以应该去对应平台的目录下找lowlevel_init.S文件/arch/arm/cpu/armv7/rk32xx)
ENTRY(lowlevel_init)
/*
* Setup a temporary stack
*/
/**
为调用c函数准备一个临时堆栈而已,这个堆栈在cpu的片上内存!
**/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr} /*将ip和lr寄存器压入堆栈保存*/
/*
* go setup pll, mux, memory
*/
bl s_init /*针对相应的平台设置系统时钟,这是c函数,结合cpu的datasheet阅读即可*/
pop {ip, pc} /*将ip和lr寄存器从堆栈弹出*/
ENDPROC(lowlevel_init)
/###################################分###################################割###################################线###################################/
bl _main //调用c代码, _main实现在 /arch/arm/lib/crt0.S
_main执行流程图如下
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
/*
这里首先为调用board_init_f准备一个临时堆栈,CONFIG_SYS_INIT_SP_ADDR这个宏就是cpu片上内存的高地址(片上内存的大小减去GD_SIZE)。然后将堆栈初始的地址保存在r9,所以r9就是gd的起始地址,后面需要靠r9访问gd的成员。然后将r0赋值成0,r0就是要调用的board_init_f函数的第一个参数!
*/
#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, sp, #GD_SIZE /* allocate one GD above SP */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r9, sp /* GD is above SP */
mov r0, #0
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.
*/
/*
*这段代码的主要功能就是将uboot搬移到内存的高地址去执行,为kernel腾出低端空间,防止kernel解压覆盖uboot。
*adr lr, here
*ldr r0, [r9, #GD_RELOC_OFF]
*add lr, lr, r0 的功能就是,将relocate后的here标号的地址保存到lr寄存器,这样等到relocate完成后,就可以直接跳到relocate后的here标号去执行了。
*relocate_code函数的原理及流程,是uboot的重要代码,下面详解!
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
here:
/* Set up final (full) environment */
/*
*relocate完成后,uboot的代码被搬到了内存的顶部,所以必须重新设置异常向量表的
*地址,c_runtime_cpu_setup这个函数的主要功能就是重新设置异常向量表的地址。
*/
bl c_runtime_cpu_setup /* we still call old routine here */
/*
*在relocate的过程中,并没有去搬移bss段。bss段是auto-relocated的!为什么?
*可以自己思考一下,又或许看完我后面介绍的relocate的原理后你会明白!
*/
ldr r0, =__bss_start /* this is auto-relocated! */
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
/*
*清空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
/*
*这两行代码无视之,点灯什么的,和这里要讲的uboot的原理及过程没有半毛钱关系。
*/
bl coloured_LED_init
bl red_led_on
/*
*将relocate后的gd的地址保存到r1,然后调用board_init_r函数,进入uboot的新天地!
*/
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
ldr pc, =board_init_r /* this is auto-relocated! */
/* we should not return here. */
#endif
ENDPROC(_main)
/###################################分###################################割###################################线###################################/