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