Bootloader启动大多数都分为两个阶段。第一阶段主要包含依赖于CPU的体系结构硬件初始化的代码,通常都用汇编语言来实现;第二阶段通常用C语言完成,以便实现更复杂的功能,也使程序有更好的可读性和可移植性。
U-Boot的启动代码分布在start.S、low_level_init.S、 board.c和main.c文件中。
start.S 是U-Boot整个程序的入口,该文件使用汇编语言编写,不同体系结构的启动代码是不同的;
low_level_init.S 是特定开发板的设置代码;
board.c 包含开发板底层设备驱动;
main.c 是一个与平台无关的代码,U- Boot应用程序的入口在此文件中。
第一阶段对应的文件是cpu/XXX/start.S和board/samsung/XXX/lowlevel_init.S
第二阶段对应的文件是lib_arm/board.c,最后跳转到common/main.c,main_loop在标准转入设备中接受命令行,然后分析,查找,执行。
一个可执行的image 必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里,入口点是通过链接脚本来实现的,由此我们可以找到程序的入口点是在cpu/arm_cortexa8/u-boot.lds 中指定的,其中ENTRY(_start) 说明程序从_start 开始运行,而它指向的是cpu/arm_cortexa8/start.o 文件。
因为我们用的是 cortex-a8 的 cpu 架构,在CPU复位后从iROM地址0x00000000取它的第一条指令,执行iROM代码的功能是把flash中的前16K的代码加载到iRAM中,系统上电后将首先执行 u-boot 程序。
首先我们来看一下u-boot.lds链接脚本,通过它我们可以知道它整个程序的各个段是怎么存放的。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm_cortexa8/start.o (.text)
board/samsung/fsc100/lowlevel_init.o
board/samsung/fsc100/mem_setup.o
board/samsung/fsc100/nand_cp.o
*(.text)//所有的其他程序的代码段以四字节对齐放在后面
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }//只读数据段
. = ALIGN(4);
.data : { *(.data) }//指定读/写数据段
. = ALIGN(4);
.got : { *(.got) }//指定got段,got段式是uboot自定义的一个段,非标准段
__u_boot_cmd_start = .;//把__u_boot_cmd_start赋值为当前位置,即起始位置
.u_boot_cmd : { *(.u_boot_cmd) }//指定u_boot_cmd段,uboot把所有的uboot命令放在该段
__u_boot_cmd_end = .;//把 __u_boot_cmd_end赋值为当前位置,即结束位置
. = ALIGN(4);
__bss_start = .;__bss_start//赋值为当前位置,即bss段得开始位置
.bss : { *(.bss) }
_end = .;//把_end赋值为当前位置,即bss段得结束地址
}
1.stage1:cpu/arm_cortexa8/start.S
2.当系统启动时, ARM CPU 会跳到 0x00000000去执行,一般 BootLoader 包括如下几个部分:
1. 建立异常向量表
2. 显示的切换到 SVC 且 32 指令模式
3. 设置异常向量表
4. 关闭 TLB,MMU,cache,刷新指令 cache 数据 cache
5. 关闭内部看门狗
6. 禁止所有的中断
7. 串口初始化
8. tzpc(TrustZone Protection Controller)
9. 配置系统时钟频率和总线频率
10. 设置内存区的控制寄存器
11. 设置堆栈
12. 跳到 C 代码部分执行
#include <config.h>//@由顶层的mkconfig生成
#include <version.h>
@设置异常向量
.globl _start @ 全局变量,_start是GNU汇编的默认入口标签
_start: b reset @0x0,复位向量,直接跳转到reset,并且不返回,正常情况下,系统 reset 后进入的入口
ldr pc, _undefined_instruction @0x4,未定义指令,系统出错处理的入口
ldr pc, _software_interrupt @0x8,软中断,monitor 程序的入口
ldr pc, _prefetch_abort @0x0c,预取中止错误
ldr pc, _data_abort @0x10,取数据失中止错误(通常是保护现场)
ldr pc, _not_used @0x14 保留
ldr pc, _irq @0x18,中断请求
ldr pc, _fiq @0x1c 快速中断请求
@8*4 = 32 Byte
_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
_pad: .word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
@.word为GNU ARM汇编特有的伪操作,为分配一段字内存单元(分配的单元为字对齐的),可以使用.word把标志符作为常量使用。如_fiq:.word fiq即把fiq存入内存变量_fiq中,也即是把fiq放到地址_fiq中。
@.align伪操作用于表示对齐方式:通过添加填充字节使当前位置,.balignl是.balign的变体,在以当前地址开始,在地址为16的倍数的位置的前面填入四个字节内容为0xdeadbeef;.balignl的最后一个字母l代表4字节对齐,因此地址就是16*4=64,而前面已经占了15*4=60个字节,故在地址60处开始填充0xdeadbeef,0xdeadbeef作用就是为内存做标记,插在那里,就表示从这个位置往后的一段有特殊作用的内存,而这个位置往前,禁止访问。
/*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* setup Memory and board specific bits prior to relocation.
* relocate armboot to ram
* setup stack
*当没有从内存启动时做一些重要的初始化,启动内存和板子上特殊位来重映射。重映射armboot到RAM,并初始化建立好栈
*************************************************************************/
_TEXT_BASE:
.word TEXT_BASE
/*TEXT_BASE这个标号的定义在如下文件中定义:
*board/samsung/smdkc100/config.mk
*TEXT_BASE = 0x34800000 @本程序运行的基地址为TEXT_BASE
*/
.globl _armboot_start
_armboot_start:
.word _start @_start 是uboot的第一行代码的标号,代表的是第一行代码的地址
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
@在cpu/arm_cortexa8/u-boot.lds中定义,这样赋值是因为代码所在地址非编译时的地址,直接取得该标号对应地址。
#ifdef CONFIG_USE_IRQ @这个宏没有定义,故不执行
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de @在IRQ_STACK_START处插入0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de @ 在FIQ_STACK_START处插入0x0badc0de
#endif
/*
* the actual reset code @真正的复位代码
*/
@CPU进入SVC模式
reset:
/*CPU一上电以后就是跳到这里执行的
* set the cpu to SVC32 mode
*/
@更改处理器模式为管理模式
@对状态寄存器的修改要按照:读-改-写的顺序执行
CPSR
31 30 29 28 --- 7 6 - 4 3 2 1 0
N Z C V I F M4 M3 M2 M1 M0
1 0 0 0 0 User模式
1 0 0 0 1 FIQ模式
1 0 0 1 0 IRQ模式
1 0 1 1 1 SVC模式
1 1 0 1 1 Abort模式
1 1 1 1 1 Undef模式
1 0 0 1 1 System模式
1 0 1 1 0 Moniter模式(Cortex)
mrs r0, cpsr @将cpsr的值读到r0中
bic r0, r0, #0x1f @清除M0~M4
orr r0, r0, #0xd3 @禁止IRQ,FIQ中断,并将处理器置于管理模式
msr cpsr,r0
#if (CONFIG_OMAP34XX) @这个宏没有定义,下面的代码不会预编译
/* Copy vectors to mask ROM indirect addr */
adr r0, _start @ r0 <- current position of code
add r0, r0, #4 @ skip reset vector
mov r2, #64 @ r2 <- size to copy
add r2, r0, r2 @ r2 <- source end address
mov r1, #SRAM_OFFSET0 @ build vect addr
mov r3, #SRAM_OFFSET1
add r1, r1, r3
mov r3, #SRAM_OFFSET2
add r1, r1, r3
next:
ldmia r0!, {r3 - r10} @ copy from source address [r0]
stmia r1!, {r3 - r10} @ copy to target address [r1]
cmp r0, r2 @ until source end address [r2]
bne next @ loop until equal */
#if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
/* No need to copy/exec the clock code - DPLL adjust already done
* in NAND/oneNAND Boot.
*/
bl cpy_clk_code @ put dpll adjust code behind vectors
#endif /* NAND Boot */
#endif
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT @这个宏没有定义,条件成立,下面的代码需要执行
bl cpu_init_crit
#endif
@执行CPU初始化,BL完成跳转的同时会把后面紧跟的一条指令地址保存到连接寄存器LR(R14)中。以使子程序执行完后正常返回。
ldr r0, =0xe03001c0
ldr r1, =0x1111
str r1, [r0]
ldr r0, =0xe03001c4
ldr r1, =0x3
str r1, [r0]
/* added */
ldr r0, =_TEXT_BASE
adr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup
ldr r0, =0xe03001c4
ldr r1, =0xf
str r1, [r0]
#ifdef CONFIG_CMD_NAND
ldr sp, =(0x22000000)
bl copy_uboot_to_ram
b stack_setup
#endif /* CONFIG_CMD_NAND */
#ifndef CONFIG_SKIP_RELOCATE_UBOOT @这个宏没有定义,条件成立,下面的代码能够执行
relocate: @ relocate U-Boot to RAM U-boot自搬移到RAM
adr r0, _start @ r0 <- current position of code 装载_start的地址到r0中
ldr r1, _TEXT_BASE @ test if we run from flash or RAM 装载连接地址,这个地址是TEXT_BASE = 0x34800000
cmp r0, r1 @ don't reloc during debug
beq stack_setup
@调试阶段的代码是直接在RAM中运行的,而最后需要把这些代码固化到Flash中,因此U-Boot需要自己从Flash转移到RAM中运行,这@也是重定向的目的所在。
@通过adr指令得到当前代码的地址信息:如果U-boot是从TEXT_BASE = 0x34800000,如果U-boot从Flash开始运行,即从处理器对应的地址运行,则r0=0x0000,这时将会执行copy_loop标识的那段代码了。
@判断 当uboot在nand当中引导时,会把前16K的代码放到ram中,ram的地址和连接地址不一致, r0不等于r1的值,beq条件不成立;当从usb引导是这个条件就成立.成立后后面的代码就不在执行了,后面的搬移代码就不在执行.
ldr r2, _armboot_start @功能是装载_start的地址
/ * .globl _armboot_start
* _armboot_start:
* .word _start
* /
ldr r3, _bss_start @ 功能是装载
/ *.globl _bss_start
* _bss_start:
* .word __bss_start
* __bss_start这个标号在cpu/arm_cortexa8/u-boot.lds 中定义,是bss段的开始也是bss段以前的一个结束标志
* 因此r3的值是uboot的除去bss的末尾地址,在搬移的时候是不搬移bss段的,bss段放的是未初始化的变量
* /
sub r2, r3, r2 @ r2 <- size of armboot 计算armboot的大小
add r2, r0, r2 @ r2 <- source end address 计算源代码结束地址
copy_loop: @ copy 32 bytes at a time
ldmia r0!, {r3 - r10} @ copy from source address [r0]
@从源地址[r0]读取8个字节到寄存器,每读一个就更新一次r0地址 ldmia:r0安字节增长
stmia r1!, {r3 - r10} @ copy to target address [r1]
cmp r0, r2 @ until source end addreee [r2] 等到搬移完成后,r0和r2的值相等
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
@LDM(STM)用于在寄存器所指的一片连续存储器和寄存器列表的寄存@器间进行数据移动,或是进行压栈和出栈操作。
@格式为:LDM(STM){条件}{类型}基址寄存器{!},寄存器列表{^}
@对于类型有以下几种情况:
IA 每次传送后地址加1,用于移动数据块
IB 每次传送前地址加1,用于移动数据块
DA 每次传送后地址减1,用于移动数据块
DB 每次传送前地址减1,用于移动数据块
FD 满递减堆栈,用于操作堆栈(即先移动指针再操作数据,相当于DB)
ED 空递减堆栈,用于操作堆栈(即先操作数据再移动指针,相当于DA)
FA 满递增堆栈,用于操作堆栈(即先移动指针再操作数据,相当于IB)
EA 空递增堆栈,用于操作堆栈(即先操作数据再移动指针,相当于IA)
/* Set up the stack */ @设置堆栈,规划内存的使用的
stack_setup:
ldr r0, =0xe03001c4
ldr r1, =0x0
str r1, [r0]
ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot
sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area 向下内存分配,为malloc预留分配空间
/* CONFIG_SYS_MALLOC_LEN include/configs/smdkc100.h
*#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (1 << 20))
*环境变量大小#define CONFIG_ENV_SIZE (128 << 10) /* 128KiB, *0x20000
* 这句话的功能是r0 的值向低地址减去128K +1M的大小
*/
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo 预留初始化的数据的空间
/ * CONFIG_SYS_GBL_DATA_SIZE include/configs/smdkc100.h
* #define CONFIG_SYS_GBL_DATA_SIZE 128 /* size in bytes */
* 这句话是把地址继续减去128 bytes
* /
#ifdef CONFIG_USE_IRQ @这个宏没有定义,下面的代码不会执行
sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ) @如果定义了中断则还需要向下预留中断空间
#endif
sub sp, r0, #12 @ leave 3 words for abort-stack @ 预留3个字给溢出堆栈
and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
/* Clear BSS (if any). Is below tx (watch load addr - need space) */
clear_bss: @对bss段进行初始化
ldr r0, _bss_start @ find start of bss segment
ldr r1, _bss_end @ stop here
mov r2, #0x00000000 @ clear value
clbss_l:
str r2, [r0] @ clear BSS location
cmp r0, r1 @ are we at the end yet
add r0, r0, #4 @ increment clear index pointer
bne clbss_l @ keep clearing till at end
ldr r0, =0xe03001c4
ldr r1, =0x1
str r1, [r0]
ldr pc, _start_armboot @ jump to C code 进入C代码
_start_armboot: .word start_armboot
@进入lib_arm/board.c文件中的 void start_armboot (void)
@这句话使得pc指针也就从第一阶段的汇编语言跳到了第二阶段的C语言了
/*************************************************************************
*
* CPU_init_critical registers @初始化关键的寄存器
*
* setup important registers
* setup memory timing
*
*************************************************************************/
cpu_init_crit:
/*
* Invalidate L1 I/D
*/
@初始化CACHES
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
/*
* disable MMU stuff and caches
*/
@关闭MMU和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 12 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* 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.
*/
mov ip, lr @ persevere link reg across call 保存LR,以便正常返回,注意前面是通过BL跳到cpu_init_crit来的
bl lowlevel_init @ go setup pll,mux,memory 在重定向代码之前,必须初始化内存时序,重定向时需要将flash中的代码复制到内存中
@lowlevel_init 这个函数在board/samsung/smdk100/lowlevel_init.S文件当中定义
mov lr, ip @ restore link
mov pc, lr @ back to my caller
/*
*************************************************************************
*
* Interrupt handling
*
*************************************************************************
*/
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
sub sp, sp, #S_FRAME_SIZE @ carve out a frame on current
@ user stack
stmia sp, {r0 - r12} @ Save user registers (now in
@ svc mode) r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_SYS_MALLOC_LEN)
sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ set base 2 words into abort
@ stack
ldmia r2, {r2 - r3} @ get values for "aborted" pc
@ and cpsr (into parm regs)
add r0, sp, #S_FRAME_SIZE @ grab pointer to old stack
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp @ save current stack into r0
@ (param register)
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
add r8, sp, #S_PC @ !! R8 NEEDS to be saved !!
@ a reserved stack spot would
@ be good.
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into
@ cpsr
.endm
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack (enter
@ in banked mode)
sub r13, r13, #(CONFIG_SYS_MALLOC_LEN) @ move past malloc pool
sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ move to reserved a couple
@ spots for abort stack
str lr, [r13] @ save caller lr in position 0
@ of saved stack
mrs lr, spsr @ get the spsr
str lr, [r13, #4] @ save spsr in position 1 of
@ saved stack
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13 @ switch modes, make sure
@ moves will execute
mov lr, pc @ capture return pc
movs pc, lr @ jump to next instruction &
@ switch modes.
.endm
.macro get_bad_stack_swi
sub r13, r13, #4 @ space on current stack for
@ scratch reg.
str r0, [r13] @ save R0's value.
ldr r0, _armboot_start @ get data regions start
sub r0, r0, #(CONFIG_SYS_MALLOC_LEN) @ move past malloc pool
sub r0, r0, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ move past gbl and a couple
@ spots for abort stack
str lr, [r0] @ save caller lr in position 0
@ of saved stack
mrs r0, spsr @ get the spsr
str lr, [r0, #4] @ save spsr in position 1 of
@ saved stack
ldr r0, [r13] @ restore r0
add r13, r13, #4 @ pop stack entry
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
/*
* exception handlers
*/
@异常向量处理
@每一个异常向量处其实只放了一条跳转指令(因为每个异常向量只有4个字节不能放太多的程序),跳到相应的异常处理程序中。
.align 5 @.align 5就是2的5次方对齐
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack_swi
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effective fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif