U-Boot啟動過程原始碼分析(2)-第二階段
先總述:第一階段cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S進行初始化,再跳到第二階段的入口點lib_arm/board.c中的start_armboot函式。
第二階段start_armboot函式需要用到include/asm-arm/global_data.h中結構體gd_t; include/asm-arm.u/u-boot.h中結構體bd_t。gd_t,bd_t結構體中的資訊來設定標記列表,U-Boot啟動核心時給核心傳遞引數。逐個呼叫init_sequence陣列中的初始化函式。最後執行 common/main.c中的 main_loop函式
U-Boot啟動第二階段程式碼分析
重要資料結構
(1)gd_t結構體
U-Boot使用了一個結構體gd_t來儲存全域性資料區的資料,這個結構體在include/asm-arm/global_data.h中定義如下:
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
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 */
void **jt; /* jump table */
} gd_t;
U-Boot使用了一個儲存在暫存器中的指標gd來記錄全域性資料區的地址:
define DECLARE_GLOBAL_DATA_PTR (register volatile gd_t *)gd asm (“r8”)
DECLARE_GLOBAL_DATA_PTR定義一個gd_t全域性資料結構的指標,這個指標存放在指定的暫存器r8中。這個宣告也避免編譯器把r8分配給其它的變數。任何想要訪問全域性資料區的程式碼,只要程式碼開頭加入“DECLARE_GLOBAL_DATA_PTR”一行程式碼,然後就可以使用gd指標來訪問全域性資料區了。
根據U-Boot記憶體使用圖中可以計算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
(2)bd_t結構體
bd_t在include/asm-arm.u/u-boot.h中定義如下:
typedef struct bd_info {
int bi_baudrate; /* 串列埠通訊波特率 */
unsigned long bi_ip_addr; /* IP 地址*/
struct environment_s *bi_env; /* 環境變數開始地址 */
ulong bi_arch_number; /* 開發板的機器碼 */
ulong bi_boot_params; /* 核心引數的開始地址 */
struct /* RAM配置資訊 */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
U-Boot啟動核心時要給核心傳遞引數,這時就要使用gd_t,bd_t結構體中的資訊來設定標記列表。第一階段呼叫start_armboot指向C語言執行程式碼區,首先它要從記憶體上的重定位資料獲得不完全配置的全域性資料表格和板級資訊表格,即獲得gd_t和bd_t,這兩個型別變數記錄了剛啟動時的資訊,並將要記錄作為引導核心和檔案系統的引數,如bootargs等等,並且將來還會在啟動核心時,由uboot交由kernel時會有所用。
(3)init_sequence陣列
U-Boot使用一個數組init_sequence來儲存對於大多數開發板都要執行的初始化函式的函式指標。init_sequence陣列中有較多的編譯選項,去掉編譯選項後init_sequence陣列如下所示:
init_fnc_t *init_sequence[] = {
board_init, /*開發板相關的配置--board/samsung/mini2440/mini2440.c */
timer_init, /* 時鐘初始化-- cpu/arm920t/s3c24x0/timer.c */
env_init, /*初始化環境變數--common/env_flash.c 或common/env_nand.c*/
init_baudrate, /*初始化波特率-- lib_arm/board.c */
serial_init, /* 串列埠初始化-- drivers/serial/serial_s3c24x0.c */
console_init_f, /* 控制通訊臺初始化階段1-- common/console.c */
display_banner, /*列印U-Boot版本、編譯的時間-- gedit lib_arm/board.c */
dram_init, /*配置可用的RAM-- board/samsung/mini2440/mini2440.c */
display_dram_config, /* 顯示RAM大小-- lib_arm/board.c */
NULL,
};
其中的board_init函式在board/smdk2410/smdk2410.c中定義,該函式設定了MPLLCOM,UPLLCON,以及一些GPIO暫存器的值,還設定了U-Boot機器碼和核心啟動引數地址 :
/* smdk2410開發板的機器碼 */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
/* 核心啟動引數地址 */
gd->bd->bi_boot_params = 0x30000100;
dram_init函式在board/smdk2410/smdk2410.c中定義int
檢測系統記憶體對映,特定的開發板其記憶體對映是明確的,這些引數在後面傳遞給核心。
dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
return 0;
}
分析完上述結構,下析start_armboot函式:
void start_armboot (void)
{
/* 計算全域性資料結構的地址gd */
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
… …
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
gd->flags |= GD_FLG_RELOC;
monitor_flash_len = _bss_start - _armboot_start;
/* 逐個呼叫init_sequence陣列中的初始化函式 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* armboot_start 在cpu/arm920t/start.S 中被初始化為u-boot.lds連線指令碼中的_start */
mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
CONFIG_SYS_MALLOC_LEN);
/* NOR Flash初始化 */
#ifndef CONFIG_SYS_NO_FLASH
/* configure available FLASH banks */
display_flash_config (flash_init ());
#endif /* CONFIG_SYS_NO_FLASH */
… …
/* NAND Flash 初始化*/
#if defined(CONFIG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
… …
/*配置環境變數,重新定位 */
env_relocate ();
… …
/* 從環境變數中獲取IP地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
stdio_init (); /* get the devices list going. */
jumptable_init ();
… …
console_init_r (); /* fully init console as a device */
… …
/* enable exceptions */
enable_interrupts ();
#ifdef CONFIG_USB_DEVICE
usb_init_slave();
#endif
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
… …
/* 網絡卡初始化 */
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts ("Net: ");
#endif
eth_initialize(gd->bd);
… …
#endif
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();//main_loop!!
}
/* NOTREACHED - no way out of command loop except booting */
}
main_loop函式在common/main.c中定義。