最近在做展訊Android手機驅動的開發,需要能清楚U-Boot啟動過程,然後書寫相關的驅動代碼。而像lcd驅動一般會在u-boot啟動的過程中便會選擇載入某一特定的驅動,而且u-boot和kernel中的代碼大致相同,因為Android開機過程便會顯示開機logo,這涉及到顯示的部分驅動。u-boot啟動過程可以分為兩個階段,兩個階段的功能如下:
StageI:第一階段的功能(展訊為16k的彙編代碼) 硬體裝置初始化 載入U-Boot第二階段代碼到RAM空間 設定好棧 跳轉到第二階段代碼入口
StageII:第二階段的功能
初始化本階段使用的硬體裝置 檢測系統記憶體映射 將核心從Flash讀取到RAM中 為核心設定啟動參數 調用核心1.1.1 U-Boot啟動第一階段程式碼分析
第一階段對應的檔案是lowlevel_init.S。 U-Boot啟動第一階段流程如下:1. 硬體裝置初始化(1)設定異常向量 其中複位異常向量的指令“b start_code”決定了U-Boot啟動後將自動跳轉到標號“start_code”處執行。(2)設定控制寄存器地址(3)關閉看門狗(4)關閉MMU,cache(5)初始化RAM控制寄存器 其中的lowlevel_init就完成了記憶體初始化的工作,由於記憶體初始化是依賴於開發板的,因此lowlevel_init的代碼一般放在board下面相應的目錄中。
board/samsung/mini2440/lowlevel_init.o將被連結到cpu/arm920t/start.o後面,因此board/samsung/mini2440/lowlevel_init.o也在U-Boot的前4KB的代碼中。 U-Boot在NAND Flash啟動時,lowlevel_init.o將自動被讀取到CPU內部4KB的內部RAM中。因此第137~146行的代碼將從CPU內部RAM中複製寄存器的值到相應的寄存器中。 對於U-Boot在NOR Flash啟動的情況,由於U-Boot串連時確定的地址是U-Boot在記憶體中的地址,而此時U-Boot還在NOR Flash中,因此還需要在NOR Flash中讀取資料到RAM中。 由於NOR Flash的開始地址是0,而U-Boot的載入到記憶體的起始地址是TEXT_BASE,SMRDATA標號在Flash的地址就是SMRDATA-TEXT_BASE。 綜上所述,lowlevel_init的作用就是將SMRDATA開始的13個值複製給開始地址[BWSCON]的13個寄存器,從而完成了儲存控制器的設定。(6)複製U-Boot第二階段代碼到RAM cpu/arm920t/start.S原來的代碼是只支援從NOR Flash啟動的,經過修改現在U-Boot在NOR Flash和NAND Flash上都能啟動了,實現的思路是這樣的: bl bBootFrmNORFlash /* 判斷U-Boot是在NAND Flash還是NOR Flash啟動 */ cmp r0, #0 /* r0存放bBootFrmNORFlash函數傳回值,若返回0表示NAND Flash啟動,否則表示在NOR Flash啟動 */ beq nand_boot /* 跳轉到NAND Flash啟動代碼 */ /* NOR Flash啟動的代碼 */ b stack_setup /* 跳過NAND Flash啟動的代碼 */ nand_boot:/* NAND Flash啟動的代碼 */ stack_setup: /* 其他代碼 */ 其中bBootFrmNORFlash函數作用是判斷U-Boot是在NAND Flash啟動還是NOR Flash啟動,若在NOR Flash啟動則返回1,否則返回0。根據ATPCS規則,函數傳回值會被存放在r0寄存器中。bBootFrmNORFlash函數後根據r0的值就可以判斷U-Boot在NAND Flash啟動還是NOR Flash啟動。bBootFrmNORFlash函數在board/samsung/mini2440/nand_read.c中定義如下:
int bBootFrmNORFlash(void){ volatile unsigned int *pdw = (volatile unsigned int *)0; unsigned int dwVal; dwVal = *pdw; /* 先記錄下原來的資料 */ *pdw = 0x12345678; if (*pdw != 0x12345678) /* 寫入失敗,說明是在NOR Flash啟動 */ { return 1; } else /* 寫入成功,說明是在NAND Flash啟動 */ { *pdw = dwVal; /* 恢複原來的資料 */ return 0; }}
u-boot可以選擇進入兩種不同的nand flash模式,一種是傳統的normal_nand_mode,另一種是normal_emmc_mode,這種flash標準可以將flash做成很大的儲存量。
(6)跳轉到第二階段代碼入口 ldr pc, _start_armboot _start_armboot: .word start_armboot 跳轉到第二階段代碼入口start_armboot處。1.1.2 U-Boot啟動第二階段程式碼分析 start_armboot函數在lib_arm/board.c中定義,是U-Boot第二階段代碼的入口。U-Boot啟動第二階段流程如下:
選擇一個進入的模式:展訊啟動模式有fast_mode,specilal_mode,normal_mode,panic_reboot_mode等,一般啟動會進入到normal模式中。
在分析start_armboot函數前先來看看一些重要的資料結構:(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結構體中的資訊來設定標記列表。(3)init_sequence數組 U-Boot使用一個數組init_sequence來儲存對於大多數開發板都要執行的初始化函數的函數指標。init_sequence數組中有較多的編譯選項,去掉編譯選項後init_sequence數組如下所示:typedef int (init_fnc_t) (void); 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,};gd->bd->bi_arch_number = MACH_TYPE_MINI2440; /* 核心啟動參數地址 */gd->bd->bi_boot_params= 0x30000100;
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){ init_fnc_t **init_fnc_ptr; char *s; … … /* 計算全域資料結構的地址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 … … main_loop函數在common/main.c中定義。一般情況下,進入main_loop函數若干秒內沒有啟動Linux過程 U-Boot使用標記列表(tagged list)的方式向Linux傳遞參數。標記的資料結構式是tag,在U-Boot原始碼目錄include/asm-arm/setup.h中定義如下:struct tag_header { u32 size; /* 表示tag資料結構的聯合u實質存放的資料的大小*/ u32 tag; /* 表示標記的類型 */}; struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; } u;}; U-Boot使用命令bootm來啟動已經載入到記憶體中的核心。而bootm命令實際上調用的是do_bootm函數。對於Linux核心,do_bootm函數會調用do_bootm_linux函數來設定標記列表和啟動核心。do_bootm_linux函數在lib_arm/bootm.c 中定義如下: int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images) { bd_t *bd = gd->bd; int machid = bd->bi_arch_number; void (*theKernel)(int zero, int arch, uint params); #ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); /* U-Boot環境變數bootargs */ #endif … … theKernel = (void (*)(int, int, uint))images->ep; /* 擷取核心入口地址 */ … …} 標記列表必須以ATAG_CORE開始,setup_start_tag函數在核心的參數的開始地址設定了一個ATAG_CORE標記。 (4) 使資料Cache與指令Cache失效 由於U-Boot啟動以來就一直工作在SVC模式,因此CPU的工作模式就無需設定了。do_bootm_linux中:64 void (*theKernel)(int zero, int arch, uint params);… …73 theKernel = (void (*)(int, int, uint))images->ep;… …128 theKernel (0, machid, bd->bi_boot_params); 第73行代碼將核心的入口地址“images->ep”強制類型轉換為函數指標。根據ATPCS規則,函數的參數個數不超過4個時,使用r0~r3這4個寄存器來傳遞參數。因此第128行的函數調用則會將0放入,r0,機器碼machid放入r1,核心參數地址bd->bi_boot_params放入r2,從而完成了寄存器的設定,最後轉到核心的入口地址。
到這裡,U-Boot的工作就結束了,系統跳轉到Linux核心代碼執行。