一、uboot啟動流程簡介
與大多數BootLoader一樣,uboot的啟動過程分為BL1和BL2兩個階段。BL1階段通常是開發板的配置等設備初始化代碼,需要依賴依賴於SoC體系結構,通常用匯編語言來實現;BL2階段主要是對外部設備如網卡、Flash等的初始化以及uboot命令集等的自身實現,通常用C語言來實現。
1、BL1階段
uboot的BL1階段代碼通常放在start.s文件中,用匯編語言實現,其主要代碼功能如下:
(1) 指定uboot的入口。在鏈接腳本uboot.lds中指定uboot的入口為start.S中的_start。
(2)設置異常向量(exception vector)
(3)關閉IRQ、FIQ,設置SVC模式
(4)關閉L1 cache、設置L2 cache、關閉MMU
(5)根據OM引腳確定啟動方式
(6)在SoC內部SRAM中設置棧
(7)lowlevel_init(主要初始化系統時鍾、SDRAM初始化、串口初始化等)
(8)設置開發板供電鎖存
(9)設置SDRAM中的棧
(10)將uboot從SD卡拷貝到SDRAM中
(11)設置並開啟MMU
(12)通過對SDRAM整體使用規划,在SDRAM中合適的地方設置棧
(13)清除bss段,遠跳轉到start_armboot執行,BL1階段執行完
2、BL2階段
start_armboot函數位於lib_arm/board.c中,是C語言開始的函數,也是BL2階段代碼中C語言的主函數,同時還是整個u-boot(armboot)的主函數,BL2階段的主要功能如下:
(1)規划uboot的內存使用
(2)遍歷調用函數指針數組init_sequence中的初始化函數
(3)初始化uboot的堆管理器mem_malloc_init
(4)初始化SMDKV210開發板的SD/MMC控制器mmc_initialize
(5)環境變量重定位env_relocate
(6)將環境變量中網卡地址賦值給全局變量的開發板變量
(7)開發板硬件設備的初始化devices_init
(8)跳轉表jumptable_init
(9)控制台初始化console_init_r
(10)網卡芯片初始化eth_initialize
(11)uboot進入主循環main_loop
二、uboot程序入口分析
1、link.lds鏈接腳本文件分析
u-boot.lds文件是uboot工程的鏈接腳本文件,位於board\samsung\smdkc110目錄下,對於工程項目編譯后期的鏈接階段非常重要,決定了uboot程序的組裝。
u-boot.lds鏈接文件中的ENTRY(_start)指定了uboot程序的入口地址為_start。
2、定位uboot程序入口地址
在SourceInsight建立uboot工程,利用索引功能查找_start,在搜索結果中找到與三星smdkv210開發板相關的代碼,最終鎖定cpu\s5pc11x\start.S文件,定位到文件中的_start標識符。
三、start.S文件分析
1、頭文件分析
start.S有四個頭文件:
#include <config.h>
config.h頭文件在配置開發板時由mkconfig腳本創建的頭文件,頭文件內容即包含開發板的頭文件:#include <configs/smdkv210single.h>
#include <version.h>
version.h頭文件的內容為包含自動生成的版本頭文件,頭文件內容為:#include "version_autogenerated.h",version_autogenerated.h頭文件定義了版本宏,宏定義為:#define U_BOOT_VERSION "U-Boot 1.3.4"。版本宏的值就是Makefile中定義的版本信息。
#include <asm/proc/domain.h>
domain.h頭文件在定義了CONFIG_ENABLE_MMU宏時有效,為鏈接文件,實際指向的文件為include/asm-arm/proc-armv/domain.h。
#include <regs.h>
regs.h頭文件為鏈接文件,指向s5pc110.h頭文件,s5pc110.h文件內部使用宏定義了有關SoC內部寄存器的大量信息。
2、頭校驗信息的占位
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
定義uboot程序開頭的16字節校驗頭信息填充空間,頭校驗信息塊內的值需要在后面寫入。
3、異常向量表的構建
.globl _start
_start:
b reset
ldrpc, _undefined_instruction
ldrpc, _software_interrupt
ldrpc, _prefetch_abort
ldrpc, _data_abort
ldrpc, _not_used
ldrpc, _irq
ldrpc, _fiq
uboot程序的入口點實際是定義了異常向量表,異常向量表由SoC硬件實現,因此uboot在開機上電復位時需要跳轉到reset執行。
4、復位reset分析
SoC上電復位后運行的第一段代碼就是reset。主要包括以下幾部分:
A、關閉IRQ、FIQ,並將處理器模式設置為SVC模式
B、CPU關鍵寄存器的初始化cpu_init_crit:
關閉L2 cache
初始化L2 cache
開啟L2 cache
關閉L1 cache
關閉MMU
讀取OM啟動引腳信息
確定從啟動設備SD卡啟動
設置SRAM中的棧為調用lowlevel_init做准備(lowlevel_init內部有嵌套調用)
調用lowlevel_init(主要初始化系統時鍾、SDRAM初始化、串口初始化等)
設置開發板供電鎖存
設置SDRAM中的棧
判斷當前代碼是否運行在SDRAM中,如果當前代碼運行在SDRAM中,則跳過代碼重定位。
判斷啟動方式,選擇SD卡啟動設備,跳轉到mmcsd_boot
SD卡啟動的准備工作,從SD卡拷貝uboot到SDRAM:movi_bl2_copy
C、設置MMU,開啟MMU
D、通過對SDRAM整體使用規划,在SDRAM中合適的地方設置棧
E、清除bss段,遠跳轉到start_armboot執行,BL1階段執行完
5、lowlevel_init分析
lowlevel_init位於\board\samsung\smdkc110\lowlevel_init.S中,主要功能如下:
A、檢查復位狀態,判斷啟動的方式
根據復位狀態選擇復位啟動的方式,處於低功耗狀態時復位啟動可以跳過后續多個步驟。
B、IO狀態恢復
C、關閉看門狗
D、外部SRAM的GPIO初始化、外部SROM初始化
E、開發板供電鎖存設置
F、判斷當前代碼是否運行在SDRAM,如果當前代碼運行在SDRAM,說明目前從低功耗狀態復位,可以跳過系統時鍾初始化、串口初始化、SDRAM初始化等
G、初始化系統時鍾:system_clock_init
H、初始化SDRAM內存:mem_ctrl_asm_init
I、初始化串口,打印出’O’:uart_asm_init
J、初始化trustzone:tzpc_init
K、初始化nand或onenand
L、檢查復位狀態
M、關閉ABB
N、串口打印出‘K’
說明:”OK”是打印出的調試信息,如果打印出’O’則說明在串口初始化uart_asm_init前的所有代碼是正確的。如果打印出”OK”則說明在開發板板級初始化lowlevel_init前的所有代碼是正常工作的。
system_clock_init、uart_asm_init、tzpc_init、nand_asm_init都位於lowlevel_init.S文件內,mem_ctrl_asm_init位於cpu\s5pc11x\s5pc110\cpu_init.S文件中。
四、board.c文件分析
uboot在執行完BL1階段后遠跳轉到start_armboot函數執行BL2,start_armboot函數位於lib_arm\board.c中。
1、重要變量的說明
typedef int (init_fnc_t) (void);函數類型
init_fnc_t **init_fnc_ptr;//二級函數指針
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR定義了一個存儲在寄存器r8中的指向gd_t類型全局變量的指針gd。
全局變量結構體的定義:
typedefstructglobal_data {
bd_t*bd;//boardinfo結構體信息,存放和開發板有關的信息
unsigned longflags;//標志位
unsigned longbaudrate;//串口通信波特率
unsigned longhave_console;//控制台/* serial_init() was called */
unsigned longreloc_off;//重定位偏移量/* Relocation Offset */
unsigned longenv_addr;//環境變量結構體的地址/* Address of Environment struct */
unsigned longenv_valid;//環境變量使用標志/* Checksum of Environment valid? */
unsigned longfb_base;//fb基地址/* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned charvfd_type;///* display type */
#endif
void**jt;//跳轉表/* jump table */
} gd_t;
開發板信息結構體變量的定義:
typedef struct bd_info {
intbi_baudrate;//硬件串口波特率/* serial console baudrate */
unsigned longbi_ip_addr;//開發板IP地址/* IP Address */
unsigned charbi_enetaddr[6];//開發板網卡地址 /* Ethernet adress */
struct environment_s *bi_env;//環境變量指針
ulong bi_arch_number;//機器碼/* unique id for this board */
ulong bi_boot_params;//uboot啟動參數/* where this board expects params */
struct/* RAM configuration */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];//內存插條信息
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];//第二塊網卡的地址
#endif
} bd_t;
2、uboot的內存規划
SDRAM_BASE被MMU映射在0xC0000000,CFG_UBOOT_BASE是0xC3E00000
在BL1段運行時,uboot鏡像被拷貝到CFG_UBOOT_BASE開始的地址處。
gd的地址:
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
bd的地址:
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
3、start_armboot函數分析
start_armboot函數的主要功能如下:
(1)、遍歷調用函數指針數組init_sequence中的初始化函數
依次遍歷調用函數指針數組init_sequence中的函數,如果有函數執行出錯,則執行hang函數,打印出”### ERROR ### Please RESET the board ###”,進入死循環。
(2)、初始化uboot的堆管理器mem_malloc_init
(3)、初始化SMDKV210的SD/MMC控制器mmc_initialize
(4)、環境變量重定位env_relocate
(5)、將環境變量中網卡地址賦值給全局變量的開發板變量
(6)、開發板硬件設備的初始化devices_init
(7)、跳轉表jumptable_init
(8)、控制台初始化console_init_r
(9)、網卡芯片初始化eth_initialize
(10)、uboot進入主循環main_loop
void start_armboot (void)
{
//全局數據變量指針gd占用r8。
DECLARE_GLOBAL_DATA_PTR;
/* 給全局數據變量gd安排空間*/
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset ((void*)gd, 0, sizeof (gd_t));
/* 給板子數據變量gd->bd安排空間*/
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;//u-boot長度。
/* 順序執行init_sequence數組中的初始化函數 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* 初始化堆空間 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
/* 重新定位環境變量, */
env_relocate ();
/* 從環境變量中獲取IP地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太網接口MAC 地址 */
devices_init (); /* 設備初始化 */
jumptable_init (); //跳轉表初始化
console_init_r (); /* 完整地初始化控制台設備 */
enable_interrupts (); /* 使能中斷處理 */
/* 通過環境變量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()循環不斷執行 */
for (;;) {
main_loop (); /* 主循環函數處理執行用戶命令 -- common/main.c */
}
}
4、函數指針數組init_sequence
函數指針數組init_sequence:
init_fnc_t *init_sequence[] = {
cpu_init,//CPU架構的初始化,為空cpu\s5pc11x\cpu.c
board_init,//開發板初始化board\samsung\smdkc110\smdkc110.c
interrupt_init,//定時器timer4初始化cpu\s5pc11x\interrupts.c
env_init,//環境變量初始化common\env_movi.c
init_baudrate,//波特率設置lib_arm\board.c
serial_init,//延時函數C,沒有再次初始化串口cpu\s5pc11x\serial.c
console_init_f,//控制台第一階段初始化,控制台未初始化好common\console.c
display_banner,//用串口發送uboot版本信息lib_arm\board.c
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo,//串口打印系統時鍾信息cpu\s5pc11x\s5pc110\speed.c
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard,//打印開發板信Board:SMDKV210
//board\samsung\smdkc110\smdkc110.c
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,//SMDKV210未定義I2C,函數為空lib_arm\board.c
#endif
dram_init,//初始化gd->bd->bi_dram,開發板的SDRAM配置信息
board\samsung\smdkc110\smdkc110.c
display_dram_config,//串口打印出DRAM的大小信息,DRAM:xxxMB
lib_arm\board.c
NULL,
};
board_init函數:
dm9000_pre_init();//網卡初始化,GPIO和端口設置
gd->bd->bi_arch_number = MACH_TYPE;//開發板的機器碼,uboot的機器碼和linux的機器碼之間必須適配
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);//uboot給內核的傳參地址
display_banner函數:
打印uboot版本信息:uboot-1.3.4
print_cpuinfo函數:
打印CPU時鍾系統的時鍾信息
checkboard函數:
打印出開發板信息Board: SMDKV210
display_dram_config函數:
打印出DRAM的大小信息,DRAM:xxxMB
打印出的信息可以作為調試使用,依次遍歷調用函數指針數組init_sequence中的函數,如果有函數執行出錯,則執行hang函數,打印出”### ERROR ### Please RESET the board ###”,進入死循環。