
對于學習嵌入式的夥伴對于uboot都不陌生,今天我們就來了解一些關于uboot啟動流程的一些相關知識,老樣子本文結尾會有uboot啟動流程的視訊資料。
uboot啟動流程如下:
1)設定CPU為管理模式
2)關看門狗
3)關中斷
4)設定時鐘頻率
5)關mmu,初始化各個bank
6)進入board_init_f()函數 (初始化定時器,GPIO,序列槽等,劃分記憶體區域)
7)重定位 複制uboot,然後修改SDRAM上的uboot連結位址)
8)清bss
9)跳轉到board_init_r()函數,啟動流程結束
-----------------------------------------------------------------------------------------------
1.首先來安裝arm-linux-gcc-4.3.2交叉編譯器
mkdir arm-linux-gcc-4.3.2 //建立目錄
tar -xjf arm-linux-gcc-4.3.2.tar.bz2 -C arm-linux-gcc-4.3.2/ //解壓到arm-linux-gcc-4.3.2目錄下
然後添加環境變量:
有兩種方法,第一種隻是臨時修改,重新開機虛拟機便會複位:
export PATH=/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin:/usr/sbin:/usr/bin... ...
//将arm-linux-gcc-4.3.2添加到環境變量
第二種,重新開機不複位:
vi /etc/environment
添加:
PATH=/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin:/usr/sbin:/usr/bin... ...
//将arm-linux-gcc-4.3.2添加到環境變量
2.然後進入ftp://ftp.denx.de/pub/u-boot/來下載下傳u-boot-2012.04.01
2.1.建立source insight工程,來看代碼
1)在board 目錄下隻添加:
board/samsung/smdk2410/ // (2410單闆檔案)
2)在arch 目錄下隻添加:
arch/arm/cpu/arm920t/ //(隻添加這個目錄下的*.c,*.S公用檔案)
arch/arm/cpu/arm920t/s3c24x0/ //(24x0架構所有檔案)
arch/arm/include/asm/ //(隻添加這個目錄下的*.h公用頭檔案)
arch/arm/include/asm/proc-armv/ //(arm架構的檔案)
arch/arm/include/asm/arch-s3c24x0/ //(24x0架構頭檔案)
arch/arm/lib/ //(與arm相關的庫檔案)
3)在include/configs目錄下隻添加:
include/configs/smdk2410.h // (用來配置2410單闆的頭檔案)
2.2編譯燒寫:
tar xjf u-boot-2012.04.01.tar.bz2
cd u-boot-2012.04.01 //進入解壓後檔案目錄
make smdk2410_config //由于該uboot不支援2440闆卡,是以隻有配置2410闆卡
make //編譯,生成u-boot.bin
3.最後燒寫u-boot.bin,發現無法啟動,接下來便來分析uboot的啟動流程
4.首先檢視arch/arm/cpu/u-boot.lds連結腳本
如下圖所示,看到uboot最開始會進入_start:
5. _start位于arch/arm/cpu/arm920t/start.S
是以,我們從start.S開始分析uboot啟動流程:
.globl _start //聲明_start全局符号,這個符号會被lds連結腳本用到
_start:
b start_code //跳轉到start_code符号處,0x00
ldr pc, _undefined_instruction //0x04
ldr pc, _software_interrupt //0x08
ldr pc, _prefetch_abort //0x0c
ldr pc, _data_abort //0x10
ldr pc, _not_used //0x14
ldr pc, _irq //0x18
ldr pc, _fiq //0x20
_undefined_instruction: .word undefined_instruction
//定義_undefined_instruction指向undefined_instruction(32位位址)
_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 //balignl使用,參考http://www.cnblogs.com/lifexy/p/7171507.html
其中符号儲存的位址都在頂層目錄/system.map中列出來了
6. 從上面看到, _start會跳轉到start_code處
start_code:
/*設定CPSR寄存器,讓CPU進入管理模式*/
mrs r0, cpsr //讀出cpsr的值
bic r0, r0, #0x1f //清位
orr r0, r0, #0xd3 //位或
msr cpsr, r0 //寫入cpsr
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
ldr r0, =_start
ldr r1, =0x0 //r1等于異常向量基位址
mov r2, #16
copyex:
subs r2, r2, #1 //減16次,s表示每次減都要更新條件标志位
ldr r3, [r0], #4
str r3, [r1], #4 //将_start标号後的16個符号存到異常向量基位址0x0~0x3c處
bne copyex //直到r2減為0
#endif
#ifdef CONFIG_S3C24X0
/* 關看門狗*/
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //關看門狗,使WTCON寄存器=0
/*關中斷*/
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預設為120Mhz*/
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //關閉mmu,并初始化各個bank
#endif
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80
bic sp, sp, #7 //sp=0x30000f80
ldr r0,=0x00000000
bl board_init_f
上面的CONFIG_SYS_INIT_SP_ADDR =0x30000f80,是通過arm-linux-objdump -D u-boot>u-boot.dis生成反彙編,然後從u-boot.dis得到的,如下圖所示:
7.然後進入第一個C函數:board_init_f()
該函數主要工作是:
清空gd指向的結構體、通過init_sequence函數數組,來初始化各個函數以及逐漸填充gd結構體,最後劃分記憶體區域,将資料儲存在gd裡,然後調用relocate_code()對uboot重定位
(gd是用來傳遞給核心的參數)
1)具體代碼如下所示:
void board_init_f(ulong bootflag) // bootflag=0x00000000
{
bd_t *bd;
init_fnc_t **init_fnc_ptr; //函數指針
gd_t *id;
ulong addr, addr_sp;
#ifdef CONFIG_PRAM
ulong reg;
#endif
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
/* Pointer is writable since we allocated a register for it */
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
其中gd是一個全局變量,用來傳遞給核心的參數用的,如下圖所示,在arch/arn/include/asm/global_data.h中定義,*gd指向r8寄存器,是以r8專門提供給gd使用
而CONFIG_SYS_INIT_SP_ADDR,在6節裡得到=0x30000f80,是以gd=0x30000f80
2)繼續來看board_init_f():
__asm__ __volatile__("": : :"memory"); //memory:讓cpu重新讀取記憶體的資料
memset((void *)gd, 0, sizeof(gd_t)); //将0x30000f80位址上的gd_t結構體清0
gd->mon_len = _bss_end_ofs;
// _bss_end_ofs =__bss_end__ - _start,在反彙編找到等于0xae4e0,是以mon_len等于uboot的資料長度
gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob);
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
//調用init_sequence[]數組裡的各個函數
{
if ((*init_fnc_ptr)() != 0) //執行函數,若函數執行出錯,則進入hang()
{ hang (); //列印錯誤資訊,然後一直while
}
}
上面的init_sequence[]數組裡存了各個函數,比如有:
board_early_init_f():設定系統時鐘,設定各個GPIO引腳
timer_init():初始化定時器
env_init():設定gd的成員變量
init_baudrate():設定波特率
dram_init():設定gd->ram_size= 0x04000000(64MB)
3)繼續來看board_init_f():
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; // addr=0x34000000
// CONFIG_SYS_SDRAM_BASE: SDRAM基位址,為0X30000000
// gd->ram_size: 等于0x04000000
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
addr -= (4096 * 4); //addr=33FFC000
addr &= ~(0x10000 - 1); // addr=33FF0000,
gd->tlb_addr = addr; //将64kB配置設定給TLB,是以TLB位址為33FF0000~33FFFFFF
#endif
/* round down to next 4 kB limit */
addr &= ~(4096 - 1); //4kb對齊, addr=33FF0000
debug("Top of RAM usable for U-Boot at: %08lxn", addr);
/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len; // 在前面分析過gd->mon_len=0xae4e0,
//是以addr=33FF0000 -0xae4e0=33F41B20,
addr &= ~(4096 - 1); //4095=0xfff,4kb對齊, addr=33F41000
//是以配置設定給uboot各個段的重定位位址為33F41000~33FFFFFF
debug("Reserving %ldk for U-Boot at: %08lxn", gd->mon_len >> 10, addr);
#ifndef CONFIG_SPL_BUILD
addr_sp = addr - TOTAL_MALLOC_LEN; //配置設定一段malloc空間給addr_sp
//TOTAL_MALLOC_LEN=1024*1024*4,是以malloc空間為33BF1000~33F40FFF
addr_sp -= sizeof (bd_t); //配置設定一段bd_t結構體大的空間
bd = (bd_t *) addr_sp; //bd指向剛剛配置設定出來的bd_t結構體
gd->bd = bd; // 0x30000f80處的gd變量的成員bd等于bd_t基位址
addr_sp -= sizeof (gd_t); //配置設定一個gd_t結構體大的空間
id = (gd_t *) addr_sp; //id指向剛剛配置設定的gd_t結構體
gd->irq_sp = addr_sp; //0x30000f80處的gd變量的成員irq_sp等于gd_t基位址
addr_sp -= 12;
addr_sp &= ~0x07;
... ...
relocate_code(addr_sp, id, addr); //進入relocate_code()函數,重定位代碼,以及各個符号
// addr_sp: 棧頂,該棧頂向上的位置用來存放gd->irq_sp、id 、gd->bd、malloc、uboot、TLB(64kb),
//id: 存放 gd_t結構體的首位址
// addr: 等于存放uboot重定位位址33F41000
}
執行完board_init_f()後,最終記憶體會劃分如下圖所示:
其實此時uboot還在flash中運作,然後會進入start.S的relocate_code()裡進行uboot重定位
8.接下來進入重定位
1)start.S的relocate_code()代碼如下所示
relocate_code:
mov r4, r0 /* save addr_sp */ // addr_sp棧頂值
mov r5, r1 /* save addr of gd */ // id值
mov r6, r2 /* save addr of destination */ // addr值:uboot重定位位址
/* Set up the stack */
stack_setup:
mov sp, r4 //設定棧addr_sp
adr r0, _start //在頂層目錄下system.map符号檔案中找到_start =0,是以r0=0
cmp r0, r6 //判斷_start(uboot重定位之前的位址)和addr(重定位位址)是否一樣
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */ //r1= addr(重定位位址)
ldr r3, _bss_start_ofs //_bss_start_ofs=__bss_start - _start(uboot代碼大小)
add r2, r0, r3 /* r2 <- source end address*/ //r2= uboot重定位之前的結束位址
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
//将r0處的兩個32位資料拷到r9-r10中,然後r0+=8
stmia r1!, {r9-r10} /* copy to target address [r1]*/
//将拷出來的兩個資料放入r1(重定位位址)處,然後r1+=8
cmp r0, r2 /* until source end address [r2]*/ //判斷拷貝的資料是否到結束位址
blo copy_loop
上面隻是把代碼複制到SDRAM上,而連結位址内容卻沒有改變,比如異常向量0x04的代碼内容還是0x1e0,
我們以異常向量0x04為例,來看它的反彙編:
如上圖所示,即使uboot在SDRAM運作,由于代碼沒修改,PC也會跳到0x1e0(flash位址)上
和之前老的uboot有很大差別,以前老的uboot直接是使用的SDRAM連結位址,如下圖所示:
是以,新的uboot采用了動态連結位址的方法,在連結腳本uboot.lds中,可以看到這兩個段(.rel.dyn、.dynsym):
該兩個段裡,便是儲存了各個檔案的相對動态資訊(.rel.dyn)、動态連結位址的符号(.dynsym)
以上圖的.rel.dyn段為例來分析,找到__rel_dyn_start符号處:
如上圖所示,其中0x17表示的是符号的結束标志位,我們以0x20為例來講解:
在之前,我們講過0x20裡面儲存的是異常向量0x04跳轉的位址(0x1e0),如下圖所示:
是以,接下來的代碼,便會根據0x20裡的值0x1e0(flash位址),将SDRAM的33F41000+0x20的内容改為33F41000+0x1e0(SDRAM位址),來改變uboot的連結位址
2)重定位的剩餘代碼,如下所示:
#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */ //r0=text段基位址=0
sub r9, r6, r0 /* r9 <- relocation offset */ //r9= 重定位後的偏移值=33F41000
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
//_dynsym_start_ofs =__dynsym_start - _start=0x73608
//是以r10=動态符号表的起始偏移值=0x73608
add r10, r10, r0 /* r10 <- sym table in FLASH */
//r10=flash上的動态符号表基位址=0x73608
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
//r2=__rel_dyn_start - _start=0x6b568
//是以r2=相對動态資訊的起始偏移值=0x6b568
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
//r2=flash上的相對動态資訊基位址=0x6b568
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
// _rel_dyn_end_ofs=__rel_dyn_end - _start=00073608
//是以r3=相對動态資訊的結束偏移值=00073608
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
//r3=flash上的相對動态資訊結束位址=0x6b568
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
//以0x20為例,r0=0x6b568位址處的内容= 0x20
add r0, r0, r9 /* r0 <- location to fix up in RAM */
//r0=33F41000+0x20=33F41020
ldr r1, [r2, #4] //r1= 33F41024位址處的内容=0x17
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */ //0x17=23,是以相等
beq fixrel //跳到:fixerl
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0] //r1=33F41020位址處的内容=0x1e0
add r1, r1, r9 //r1=0x1e0+33F41000= 33F411e0
fixnext:
str r1, [r0] //改變連結位址裡的内容, 33F41020=33F411e0 (之前為0x1e0)
add r2, r2, #8 //r2等于下一個相對動态資訊(0x24)的位址
cmp r2, r3 //若沒到尾部__rel_dyn_end,便繼續執行: fixloop
blo fixloop
#endif
9.清除bss段
/*重定位完成後,清除bss段*/
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldr r0, _bss_start_ofs //擷取flash上的bss段起始位置
ldr r1, _bss_end_ofs //擷取flash上的bss段結束位置
mov r4, r6 /* reloc addr */ //擷取r6(SDRAM上的uboot基位址)
add r0, r0, r4 //加上重定位偏移值,得到SDRAM上的bss段起始位置
add r1, r1, r4 //得到SDRAM上的bss段結束位置
mov r2, #0x00000000 /* clear*/
clbss_l:
str r2, [r0] /* clear loop... */ //開始清除SDRAM上的bss段
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_led_on
#endif
9.1繼續往下分析
#ifdef CONFIG_NAND_SPL //未定義,是以不執行
... ...
#else //執行else
ldr r0, _board_init_r_ofs //r0=flash上的board_init_r()函數位址偏移值
adr r1, _start //0
add lr, r0, r1 //傳回位址lr=flash上的board_init_r()函數
add lr, lr, r9 //加上重定位偏移值(r9)後,lr=SDRAM上的board_init_r()函數
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */ //r0=id值
mov r1, r6 /* dest_addr */ //r1=uboot重定位位址
/* jump to it ... */
mov pc, lr //跳轉: board_init_r()函數
_board_init_r_ofs:
.word board_init_r - _start //擷取在flash上的board_init_r()函數位址偏移值
#endif
從上面代碼看出, 接下來便會進入uboot的board_init_r()函數,該函數會對各個外設初始化、環境變量初始化等.
uboot的啟動過程到此便結束了.
uboot視訊資料:
Uboot啟劢流程分析