基于S3C2440的U-BOOT的start.S分析
在了解了ARM相關(guān)的匯編指令后,同時(shí)結(jié)合網(wǎng)上各位大蝦的提點(diǎn)開始閱讀u-boot的啟動代碼,現(xiàn)將分析過程記錄如下
可執(zhí)行文件及內(nèi)存映射
我們可以把可執(zhí)行文件分為2種情況:存放態(tài)和運(yùn)行態(tài)
1.存放態(tài):可執(zhí)行文件經(jīng)過燒到存儲介質(zhì)上(flash或磁盤)的分布,此時(shí)可執(zhí)行文件通常有2部分組成,代碼段和數(shù)據(jù)段,代碼段又分為可執(zhí)行代碼段 (.text)和只讀數(shù)據(jù)段(.rodata),數(shù)據(jù)段可以分為初始化數(shù)據(jù)段(.data)和未初始化代碼段(.bss),如下:
+-------------+-----------
| .bss | (ZI)
+-------------+-- 數(shù)據(jù)段
| .data | (RW)
+-------------+-----------
| .rodata |
|_____________| 代碼段(RO)
| .text |
+-------------+-----------
2.運(yùn)行態(tài):可執(zhí)行文件經(jīng)過裝載后就變成為運(yùn)行態(tài),
當(dāng)可執(zhí)行文件裝載后, 在RAM中的分布如下:
| ... |
+-------------+-- ZI段結(jié)束地址
| ZI 段 |
+-------------+-- ZI段起始地址
| 保留區(qū)2 |
+-------------+-- RW段結(jié)束地址
| RW 段 |
+-------------+-- RW段起始地址
| 保留區(qū)1 |
+-------------+-- RO段結(jié)束地址
| RO 段 |
+-------------+-- RO段起始地址
所以裝載過程必須完成把可執(zhí)行文件的各個(gè)段搬移到RAM的指定位置,這個(gè)裝載過程則是由啟動程序來完成的。而可執(zhí)行代碼在RAM中的地址則是由鏈接腳本來指定的。
一個(gè)可執(zhí)行的image必須有一個(gè)入口點(diǎn),并且只能有一個(gè)全局入口點(diǎn),所以要通知編譯器這個(gè)入口在哪里。這個(gè)是有鏈接腳本來實(shí)現(xiàn)的,由此我們可以找到程序 的入口點(diǎn)是在 /board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)說明程序從_start開始運(yùn)行,而他指向的是cpu /arm7tdmi/start.o文件。因?yàn)槲覀冇玫氖茿RM7TDMI的cpu架構(gòu),在復(fù)位后從地址0x00000000取它的第一條指令,所以我們 將Flash映射到這個(gè)地址上,這樣在系統(tǒng)加電后,cpu將首先執(zhí)行u-boot程序。
ARM在CPU加電復(fù)位后是從0x0000地址開始取指,因此在零地址需要放置第一條啟動代碼。默認(rèn)情況下,程序的鏈接器是把0x8000作為映像的入口 點(diǎn)(取指的第一條指令的位置),因此 需要對映像鏈接定位,即重定位映像段的存放,包括代碼段、數(shù)據(jù)段、零區(qū)等,對整個(gè)系統(tǒng)的代碼做正確的定位,這些規(guī)則通常寫成鏈接腳本。鏈接腳本就是提供了 一種把代碼段和數(shù)據(jù)段放在不同存儲器定位。
我們的只讀代碼和數(shù)據(jù)是固化在ROM中(通常在0x0000),但是在執(zhí)行的時(shí)候想在RAM區(qū)運(yùn)行(優(yōu)化系統(tǒng),使性能發(fā)揮最大),就需要鏈接定位。鏈接器告訴了隨機(jī)存儲器從哪里開始。
Load View:代碼編譯鏈接的一個(gè)組織情況
Execute View:代碼正確執(zhí)行的空間組織
啟動過程的C部分
1. 初始化MMU
2.初始化外部端口
3. 中斷處理程序表初始化
4. 串口初始化
5. 其它部分初始化(可選)
6. 主程序循環(huán)
于是我們可以在鏈接腳本中找到映像的加載地址,也即程序的入口點(diǎn)。/board/s3c2410/U-boot.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; /*映像的入口點(diǎn),通常鏈接器將此地址定位到ROM的0x0地址,必須使編譯器知道這
個(gè)地址*/
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
從上面可以看出,鏈接腳本指定了代碼段從0x00000000開始,而代碼段最開始鏈接的就是cpu/arm920t/start.o。于是可以知道在CPU加電復(fù)位后程序首先是從cpu/arm920t/start.S開始的。
1.Stage1:cpu/arm920t/start.S
當(dāng)系統(tǒng)啟動時(shí), ARM CPU會跳到0x00000000去執(zhí)行。一般BootLoader都包括如下幾個(gè)部分:
1. 建立中斷向量異常表
2. 顯示的切換到SVC且32指令模式
3. 關(guān)閉S3C2410的內(nèi)部看門狗
4. 禁止所有的中斷
5. 配置系統(tǒng)時(shí)鐘頻率和總線頻率
6. 設(shè)置內(nèi)存區(qū)的控制寄存器
7. 初始化中斷
8. 安裝中斷向表量
9. 把可執(zhí)行文件的各個(gè)段搬到運(yùn)行態(tài)的各個(gè)位置
10. 跳到C代碼部分執(zhí)行
具體分析如下:
/*復(fù)位時(shí)0地址是ROM區(qū),從0x0到0x20分配了ARM的中斷向量表*/
.globl _start
_start: b reset /*0x0,正常情況下,系統(tǒng)reset后進(jìn)入的入口,駐留于0x0地址,機(jī)器碼為EA0000XX*/
ldr pc, _undefined_instruction /*0x4,未定義指令,系統(tǒng)出錯(cuò)處理的入口*/
ldr pc, _software_interrupt /*0x8,軟中斷,monitor程序的入口*/
ldr pc, _prefetch_abort /*0x0c,預(yù)取失敗錯(cuò)誤*/
ldr pc, _data_abort /*0x10,取數(shù)據(jù)失敗錯(cuò)誤(通常是保護(hù)現(xiàn)場,然后do nothing)*/
ldr pc, _not_used /*0x14保留*/
ldr pc, _irq /*0x18,快速中斷請求 */
ldr pc, _fiq /*0x1c,處理原理與irq相同,所有的硬件中斷源共用一個(gè)通道來進(jìn)行IRQ或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的倍數(shù),如果PC跳過4字節(jié)才是16的倍數(shù),則用0xdeadbeef填充,如果只跳過了1,2,3個(gè)字節(jié)則填充不確定,如果PC是16的倍數(shù),則什么也不做*/
***************************************************************
* 當(dāng)一個(gè)異常出現(xiàn)以后,ARM會自動執(zhí)行以下幾個(gè)步驟:
* (1) 把下一條指令的地址放到連接寄存器LR(通常是R14),這樣就能夠在處理異常返回時(shí)從正確的位置繼續(xù)執(zhí)行。
* (2) 將相應(yīng)的CPSR(當(dāng)前程序狀態(tài)寄存器)復(fù)制到SPSR(備份的程序狀態(tài)寄存器)中。從異常退出的時(shí)候,就可以由SPSR來恢復(fù)CPSR。
* (3) 根據(jù)異常類型,強(qiáng)制設(shè)置CPSR的運(yùn)行模式位。
* (4) PC(程序計(jì)數(shù)器)被強(qiáng)制成相關(guān)異常向量處理函數(shù)地址,從而跳轉(zhuǎn)到相應(yīng)的異常處理程序中。
*
* 當(dāng)異常處理完畢后,ARM會執(zhí)行以下幾步操作從異常返回:
* (1) 將連接寄存器LR的值減去相應(yīng)的偏移量后送到PC中
* (2) 將SPSR復(fù)制回CPSR中
* (3) 若在進(jìn)入異常處理時(shí)設(shè)置了中斷禁止位,要在此清除
上述代碼即碰到異常時(shí),PC會被強(qiáng)制設(shè)置為對應(yīng)的異常向量,從而跳轉(zhuǎn)到
相應(yīng)的處理程序,然后再返回到主程序繼續(xù)執(zhí)行。
******************************************************************
/*
*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************************
*/
/*保存變量的數(shù)據(jù)區(qū)*/
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#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
/*****************************************************/
上述代碼主要是用于保存一些全局變量,用于啟動程序?qū)⒋a從flash
拷貝到RAM或其他使用。有一些變量的值是通過鏈接腳本得到的,如
TEXT_BASE位于/u-boot-1.1.6/board/xxx(開發(fā)板目錄名稱)/config.mk
* 文件里。__bss_start、_end位于/u-boot-1.1.6/board/xxx(開發(fā)板目錄名稱)
/u-boot.lds文件里,具體值是由編譯器算出來的。
/********************************************************/
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode ,在進(jìn)入時(shí)將CPSR設(shè)置為監(jiān)控模式,退出后改為用戶模式
* 運(yùn)行模式位為:10011(svc mode)
*/
mrs r0,cpsr
bic r0,r0,#0x1f //r0=r0 AND (!0x1f),屏蔽所有中斷,為中斷提供服務(wù)通常是OS的設(shè)備驅(qū)動的責(zé)任,在bootloader執(zhí)行中不需要中斷
orr r0,r0,#0xd3 //邏輯或
msr cpsr,r0 //svc mode
/**************************************************************************/
*設(shè)置cpu運(yùn)行在SVC32模式。ARM共有7種模式:
* 用戶模式(usr): arm處理器正常的程序執(zhí)行狀態(tài)
* 快速中斷模式(fiq): 用于高速數(shù)據(jù)傳輸或通道處理
* 外部中斷模式(irq): 用于通用的中斷處理
* 超級保護(hù)模式(svc): 操作系統(tǒng)使用的保護(hù)模式
* 數(shù)據(jù)訪問終止模式(abt): 當(dāng)數(shù)據(jù)或指令預(yù)取終止時(shí)進(jìn)入該模式,可用于虛擬存儲及存儲保護(hù)
* 系統(tǒng)模式(sys): 運(yùn)行具有特權(quán)的操作系統(tǒng)任務(wù)
* 未定義指令中止模式(und): 當(dāng)未定義的指令執(zhí)行時(shí)進(jìn)入該模式,可用于支持硬件協(xié)處理器的軟件仿真
* 通過設(shè)置ARM的CPSR寄存器,讓CPU運(yùn)行在操作系統(tǒng)保護(hù)模式,為后面進(jìn)行其它操作作好準(zhǔn)備了。
*************************************************************************/
/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //各個(gè)硬件還未就緒,關(guān)閉看門狗
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
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用于CPU,HCLK用于AHB,PCLK用于APB
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*初始化代碼在系統(tǒng)重啟的時(shí)候調(diào)用,運(yùn)行時(shí)熱復(fù)位從RAM中啟動不執(zhí)行
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //初始化CPU
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* 重定位 U-Boot 到 RAM */
adr r0, _start /* r0
/* 初始化堆棧 */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
/*得到最終sp的值*/
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
/**********************************************************************/
* 已經(jīng)準(zhǔn)備好了堆棧,就可跳到C寫的代碼里了,也就是
* 跳到內(nèi)存中的/u-boot-1.1.4/board.c --> start_armboot中運(yùn)行了
* 把_start_armboot地址處的值也就是start_armboot絕對地址值移到pc
* 于是跳到C代碼。
/*********************************************************************/
ldr pc, _start_armboot
_start_armboot: .word start_armboot
/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************
*/
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
/*使I/D cache失效: 協(xié)處理寄存器操作,將r0中的數(shù)據(jù)寫入到協(xié)處理器p15的c7中,c7對應(yīng)cp15的cache控制寄存器*/
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*使TLB操作寄存器失效:將r0數(shù)據(jù)送到cp15的c8、c7中。C8對應(yīng)TLB操作寄存器*/
/******************************************************************************************************
* MCR 指令用于將ARM 處理器寄存器中的數(shù)據(jù)傳送到協(xié)處理器寄存器中,格式為:
* MCR 協(xié)處理器編碼,協(xié)處理器操作碼1,源寄存器,目的寄存器1,目的寄存器2,協(xié)處理器操作碼2。
* 其中協(xié)處理器操作碼1 和協(xié)處理器操作碼2 為協(xié)處理器將要執(zhí)行的操作,
* 源寄存器為ARM 處理器的寄存器,目的寄存器1 和目的寄存器2 均為協(xié)處理器的寄存器。
******************************************************************************************************/
/*
* disable MMU stuff and caches,禁止MMU和caches
*/
mrc p15, 0, r0, c1, c0, 0 //將c1、c0的值寫入到r0中
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 //將設(shè)置好的r0值寫入到協(xié)處理器p15的c1、c0中
/*
* 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 //保存前一個(gè)跳轉(zhuǎn)地址,防止下一個(gè)跳轉(zhuǎn)將前一個(gè)lr地址覆蓋
bl lowlevel_init //board/smdk2410/lowlevel_init.S:用于完成芯片存儲器的初始化
mov lr, ip
mov pc, lr //返回cpu_init_crit函數(shù)
2.Stage2:lib_arm/board.c
此文件是u-boot Stage2部分,入口為Stage1最后調(diào)用的start_armboot函數(shù)。注意上面最后ldr到pc的是_start_armboot這個(gè)地址,而非start_armboot變量。
start_armboot是U-Boot執(zhí)行的第一個(gè)C語言函數(shù),完成如下工作:
1. 初始化MMU
2.初始化外部端口
3. 中斷處理程序表初始化
4. 串口初始化
5. 其它部分初始化(可選)
6. 主程序循環(huán)
void start_armboot (void)
{
DECLARE_GLOBAL_DATA_PTR;
//此宏定義了一個(gè)gd_t類型的指針 *gd,并指名用r8寄存器來存儲:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
ulong size;
init_fnc_t **init_fnc_ptr;
char *s;
/* Pointer is writable since we allocated a register for it 上面那個(gè)宏的作用*/
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
//
此C語句引用的是start.S中的地址標(biāo)號_armboot_start,但是得到的卻是其中所指的變量_start的值(在RAM中,_start
= 0x33F80000)。 Ps: _armboot_start: .word _start
//gd是全局變量,位置在堆棧區(qū)以下(低地址):
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); //得到bd的起點(diǎn)
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;
/* 順序執(zhí)行init_sequence數(shù)組中的初始化函數(shù) */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/*配置可用的Flash */
size = flash_init (); //初始化Nor flash的函數(shù),函數(shù)實(shí)現(xiàn)在下面
display_flash_config (size); //打印到控制臺:Flash: 512 kB
/* _armboot_start 在u-boot.lds鏈接腳本中定義 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //將CFG_MALLOC_LEN區(qū)域用memset函數(shù)清零(直接往目的地址寫0)
/* 配置環(huán)境變量,重新定位 */
env_relocate (); //剛才的初始化函數(shù)中有一個(gè)是env_init(),根據(jù)CRC校驗(yàn)來初始化gd->env_addr變量(自己設(shè)定的還是初始值),此函 數(shù)是作用是將環(huán)境變量值從某個(gè)flash和RAM之間的拷貝。下圖描述了ENV的初始化過程:
/* 從環(huán)境變量中獲取IP地址,放到全局變量gd中 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太網(wǎng)接口MAC 地址,放到全局變量gd中*/
{
int i;
ulong reg;
char *s, *e;
uchar tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
devices_init (); /* 獲取列表中的設(shè)備 */
jumptable_init ();
console_init_r (); /* 完整地初始化控制臺設(shè)備 */
enable_interrupts (); /* 使能例外處理 */
/* 通過環(huán)境變量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()總是試圖自動啟動,循環(huán)不斷執(zhí)行 */
for (;;) {
main_loop (); /* 主循環(huán)函數(shù)處理執(zhí)行用戶命令 -- common/main.c */
}
/* NOTREACHED - no way out of command loop except booting */
}