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_STARTIRQ_STACK_START: .word 0x0badc0de @在IRQ_STACK_START处插入0x0badc0de/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_STARTFIQ_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, r3next: 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, =0x1111str r1, [r0]ldr r0, =0xe03001c4ldr r1, =0x3str r1, [r0]/* added */
ldr r0, =_TEXT_BASEadr r1, _TEXT_BASEcmp r0, r1beq stack_setupldr r0, =0xe03001c4
ldr r1, =0xfstr r1, [r0]#ifdef CONFIG_CMD_NAND
ldr sp, =(0x22000000)bl copy_uboot_to_ramb 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 valueclbss_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 endldr 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-r12ldr 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 stackadd 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 stackstr 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 stackmov 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