簡介
在前面我們已經分析了4412的啓動流程以及編譯4412的Uboot的流程,這兩個流程其實是並不適用於所有芯片的,可以說是4412的特有流程,別的芯片可能類似但是差別不會太小,這次我們分析Uboot的啓動過程,這是從一上電到執行main_loop之前的操作,這個部分會有一部分的彙編代碼,因爲剛剛上電時系統並未準備好C語言的運行環境,需要使用匯編代碼來搭建C語言的運行環境,搬移C代碼,然後跳轉到C語言中執行,所以說這個啓動過程可以分爲兩個部分
- 第一階段:初始化環境
- 第二階段:跳轉到C語言運行環境,進行各種外設初始化,循環執行用戶命令
主要流程圖如下:
接下來我們做進一步的分析
鏈接腳本
當我們執行make命令來構建u-boot時,它的構建過程是:首先使用交叉編譯工具將各目錄下的源文件生成目標文件(.o),目標文件生成後,會將若干個目標文件組合成靜態庫文件(.a),最後通過鏈接各個靜態庫文件生成ELF格式的可執行文件。在鏈接的過程中,需要根據鏈接腳本(一般是各個以lds爲後綴的文本文件),確定目標文件的各個段,鏈接文件是在uboot目錄中的u-boot.lds文件。一般在鏈接腳本中通過
ENTRY(_start)
來指定入口爲_start標號,通過文本段(.text)的第一個目標來指定Uboot入口文件。所以我們通過這個鏈接腳本文件可以確Uboot執行的入口
iTop-4412 Uboot的連接腳本內容爲
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm_cortexa9/start.o (.text)
cpu/arm_cortexa9/s5pc210/cpu_init.o (.text)
board/samsung/smdkc210/lowlevel_init.o (.text)
common/ace_sha1.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.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,每個段使用4字節對齊(.ALIGN(4)),幾個段分別爲代碼段(.text)、只讀數據段(.rodata)、數據段(.data)其中,代碼段的第一個目標爲cpu/arm_cortexa9/start.o,在其中定義了映像文件的入口_start
接下來我們分析start.s文件
start.s分析
1、首先進行了中斷向量的設置,並且跳轉到了reset
.globl _start //程序的全局入口,u-boot.lds中設置此入口地址爲0x0000_0000
_start: b reset
ldr pc, _undefined_instruction //未定義指令異常
ldr pc, _software_interrupt //軟中斷異常
ldr pc, _prefetch_abort //內存操作異常
ldr pc, _data_abort //數據異常
ldr pc, _not_used //未使用
ldr pc, _irq //慢速中斷異常
ldr pc, _fiq //快速中斷異常
//word的意思是將後邊的符號所對應的32bit值賦予前面的符號
//下面的七條語句後面的符號正好是對應的中斷異常服務程序的入口地址
//七個中斷服務程序位於\cpu\arm_cortexa9\s5pc210\interrupts.c
_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 //16bytes對齊,並且使用0xdeadbeef填充
/*.balignl是.balign的變體
.align僞操作用於表示對齊方式:通過添加填充字節使當前位置滿足一定的對齊方式。
.balign的作用同.align。
.align {alignment} {,fill} {,max}
其中:
alignment用於指定對齊方式,可能的取值爲2的次冪,缺省爲4。
fill是填充內容,缺省用0填充。
max是填充字節數最大值,如果填充字節數超過max,就不進行對齊*/
2、接下來我們分析reset,reset中首先設置CPU進入SVC32模式,然後關閉了IRQ和FIQ;接下來初始化了cache,然後通過以下的方式獲取OM PIN的狀態,以此確定CPU的啓動模式
/* Read booting information */
ldr r0, =POWER_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
3、接下來根據r2寄存器的值來選取啓動模式,我們的是從emmc啓動所以如下,比較r2和0x28,如果相等將r3置爲BOOT_EMMC441
/* eMMC441 BOOT */
cmp r2, #0x28
moveq r3, #BOOT_EMMC441
4、緊接着,我們的程序會跳轉到lowlevel_init,去初始化PLL、MUX、MEM
bl lowlevel_init /* go setup pll,mux,memory */
5、我們看一下lowlevel_init 的代碼
在lowlevel_init 的代碼中調用了system_clock_init_scp來初始化系統的時鐘,調用了mem_ctrl_asm_init_ddr3來初始化內存,又調用了tzpc_init來初始化TrustZone Protection Controller,
/* init system clock */
bl system_clock_init_scp
/* Memory initialize */
bl mem_ctrl_asm_init_ddr3
bl tzpc_init
接下來初始化了串口,方便調試,最後POP PC寄存器,回到start.s中
/* for UART */
bl uart_asm_init
pop {pc}
6、回到start.s後,配置寄存器使PS_HOLD輸出高電平
ldr r0, =0x1002330C /* PS_HOLD_CONTROL register */
ldr r1, =0x00005300 /* PS_HOLD output high */
str r1, [r0]
7、配置棧指針,準備進入C環境
/* get ready to call C functions */
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
8、接下來會進行一個延時操作
/* wait ?us */
mov r1, #0x10000
9: subs r1, r1, #1
bne 9b
9、然後跳轉到emmc441_boot,進行代碼從emmc到內存的複製,當複製完成後初始化MMU
cmp r1, #BOOT_EMMC441
beq emmc441_boot
emmc441_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
ldr r0, =CMU_BASE
ldr r2, =CLK_DIV_FSYS3_OFFSET
ldr r1, [r0, r2]
orr r1, r1, #0x3
str r1, [r0, r2]
#endif
bl emmc441_uboot_copy
//ly 20110824
ldr r0, =0x43e00000
ldr r1, [r0]
ldr r2, =0x2000
cmp r1, r2
bne second_mmcsd_boot
b after_copy
after_copy:
#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @load domain access register
/* Set the TTB register */
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0
/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0
nop
nop
nop
nop
#endif
10、這時初始化的操作已經基本完成,配置棧,清除bss段,準備進入C語言環境
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#if defined(CONFIG_USE_IRQ)
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
#endif
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
11、最後設置PC指針爲_start_armboot,進入start_armboot函數,這就進入了C語言環境
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
start_armboot分析
start_armboot位於\lib_arm\board.c中
首先我們先看兩個比較重要的結構體gd_t和bd_t,在初始化操作很多都要靠這兩個數據結構來保存和傳遞
gd_t
gd_t定義在\include\asm-arm\global_data.h中,其成員主要是一些全局的系統初始化參數。當使用gd_t時需用宏定義進行聲明:DECLARE_GLOBAL_DATA_PTR,指定佔用寄存器R8。
這個結構體其實可以有很多的成員變量,大部分是根據配置的宏來確定是否編譯的,我刪除了其中沒有被定義的部分,剩下的就是我們會使用到的成員變量
typedef struct global_data {
volatile bd_t *bd; //與板子相關的結構
volatile unsigned long flags;//只是標誌,如設備已經初始化標誌等
volatile unsigned long baudrate;//串口波特率
volatile unsigned long have_console; /* serial_init() was called 串口初始化標誌*/
volatile unsigned long env_addr; /* Address of Environment struct 環境變量參數地址*/
volatile unsigned long env_valid; /* Checksum of Environment valid? 檢測環境變量參數是否有效*/
volatile unsigned long fb_base; /*frame buffer的基地址*/
#ifdef CONFIG_VFD
volatile unsigned char vfd_type; /* 顯示類型 */
#endif
#ifdef CONFIG_FSL_ESDHC
volatile unsigned long sdhc_clk;//
#endif
volatile void **jt; /* jump table */
} gd_t;
gd_t
gd_t定義在\include\asm-arm\u-boot.h
board info數據結構定義,主要是用來保存板子參數。
typedef struct bd_info {
int bi_baudrate; /* 串口終端的波特率*/
unsigned long bi_ip_addr; /* IP地址 */
struct environment_s *bi_env; //環境變量開始地址
//開發板ID 該變量標識每一種開發板相關的ID,該值將傳遞給內核,如果這個參數與內核配置的不相同,那麼內核啓動解壓縮完成後將出現“Error:a”錯誤,開發板ID可以在內核arch/arm/tools/mach-types中查看*/
ulong bi_arch_number; /* unique id for this board */
//傳遞給內核的參數保存地址
ulong bi_boot_params; /* where this board expects params */
//內存的起始地址及大小
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
start_armboot代碼分析
這是我第一次閱讀分析Uboot的源碼,這次着重分析的是整個的流程,而不是內部的各種細節,所以說對於start_armboot的分析我會比較浮於表面,不會去分析具體某個模塊的初始化
1、首先定義了一些變量
init_fnc_t **init_fnc_ptr;
char *s;
int mmc_exist = 0;
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
2、接下來是給gd_t結構體的空間分配
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(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));
memset (gd->bd, 0, sizeof (bd_t));
這裏面有幾個要點
(1)gd這個指針事怎麼來的,沒有malloc,我們也無法轉到定義,這個gd就好像是憑空出現。通過對源碼的分析我們可以得到,在\lib_arm\board.c的73行有一行代碼
DECLARE_GLOBAL_DATA_PTR;
轉到定義後,到了\include\asm-arm\global_data.h,它是這樣定義的
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
這個聲明告訴編譯器使用寄存器r8來存儲gd_t類型的指針gd,即這個定義聲明瞭一個指針,並且指明瞭它的存儲位置。
register表示變量放在機器的寄存器
volatile用於指定變量的值可以由外部過程異步修改。
並且在上面start_armboot的代碼中gd指針被初始化,指向了一個可寫的地址,這樣的gd就可以使用了
(2)在初始化gd後還有一行內聯彙編比較難懂
__asm__ __volatile__("": : :"memory");
這裏是一個特殊的用法:
- asm用於指示編譯器在此插入彙編語句。
- volatile用於告訴編譯器,嚴禁將此處的彙編語句與其它的語句重組合優化。即:原原本本按原來的樣子處理這這裏的彙編。
- memory強制gcc編譯器假設RAM所有內存單元均被彙編指令修改,這樣cpu中的registers和cache中已緩存的內存單元中的數據將作廢。cpu將不得不在需要的時候重新讀取內存中的數據。這就阻止了cpu又將registers,cache中的數據用於去優化指令,而避免去訪問內存。
- “”:::表示這是個空指令。
3、接下來是循環調用一個初始化函數序列
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
// init_fnc_t 定義如下
init_fnc_t *init_fnc_t [] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
board_init, /* basic board dependent setup */
//#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
//#endif
//timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
//get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
off_charge, // xiebin.wang @ 20110531,for charger&power off device.
display_banner, /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
//init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
//arm_pci_init,
#endif
display_dram_config,
NULL,
};
可以看出這個數組存放的就是一下函數指針,在for循環中會被循環調用,這裏會初始化板級硬件接口,串口就可以使用了
4、接下來還有幾個比較重要的初始化
env_relocate ();//初始化環境
jumptable_init ();//安裝系統函數指針
console_init_r ();//完全初始化中斷爲一個設備
enable_interrupts ();//使能中斷
5、接下來就進入了main_loop
for (;;) {
main_loop ();
}
main_loop中將會處理用戶輸入的命令以及完成對操作系統的引導,我們下篇博客將分析Uboot引導內核的流程