【Bootloader】探究bootloader,分析u-boot源码
Preface
? ?之前也發表過關于《Bootloader啟動過程分析》的文章,但是內容表達得比較抽象,大多是文字敘述,所以這里從系統和代碼的角度來深入分析bootloader的啟動過程。
? ?工具:Source Insight
? ?目標:U-Boot-1.1.6
? ?僅留此分析過程,日后再作補充(純手打也不容易啊,嘿嘿)。
?
U-Boot工程結構
? ?學習一個軟件,尤其是開源軟件,首先應該從分析軟件的工程結構開始。一個好的軟件有良好的工程結構,對于讀者學習和理解軟件的架構以及工作流程都有很好的幫助。
? ?U-Boot的源代碼布局和Linux類似,使用了按照模塊劃分的結構,并且充分考慮了體系結構和跨平臺問題。
U-Boot源代碼目錄結構
| 子目錄名 | 作用 |
| board | 開發板相關的定義和結構 |
| common | 包含U-Boot用到的各種處理函數 |
| cpu | 各種不同類型的處理器相關代碼 |
| doc | U-Boot文檔 |
| drivers | 常用外部設備驅動程序 |
| examples | 存放U-Boot開發代碼樣例 |
| fs | 文件系統有關的代碼,包括cramfs、ext2、fat等常見文件系統 |
| include | U-Boot用到的頭文件 |
| lib_arm | ARM體系結構有關的數據定義和操作 |
| lib_generic | U-Boot通用的操作函數 |
| net | 常用的網絡協議,包括bootp、rarp、arp、tftp等 |
| post | 上電自檢相關代碼 |
| rtc | 實時鐘有關操作 |
| tools | U-Boot有關的數據代碼 |
?
U-Boot總體工作流程
? ?與大多數Bootloader類似,U-Boot的啟動分成stage1和stage2兩個階段。
? ?stage1使用匯編語言編寫,通常與CPU體系緊密相關,如處理器初始化和設備初始化代碼等,該階段在start.S文件中實現。
? ?上圖是U-Boot中Stage1工作流程。Stage1的代碼都是與平臺相關的,使用匯編語言編寫占用空間小而且執行速度快。
? ?Stage1負責建立Stage1階段使用堆棧和代碼段,然后復制Stage2階段的代碼到內存。
? ?Stage2階段一般包括:初始化Flash器件、swim 系統內存映射、初始化網絡設備、進入命令循環,接收用戶從串口發送的命令然后進行相應的處理。
? ?Stage2使用C語言編寫,用于加載操作系統內核,該階段主要是board.c中是start_armboot()函數實現。下圖為U-Boot的Stage1和Stage2在Flash和RAM中的分配。
? ?從上圖中可以看出,U-Boot在加載到內存后,使用了操作系統空余的內存空間。
?
U-Boot啟動流程分析
?
?
? ?從圖中可以看出U-Boot的啟動代碼分布在start.S、low_level_init.S、board.c和main.c文件中
? ?Start.S是U-Boot整個程序的入口,該文件使用匯編語言編寫,不同體系結構的啟動代碼不同
? ?low_level_init.S是特定開發板的設置代碼;
? ?board.c包含開發板底層設備驅動;
? ?main.c是一個與平臺無關的代碼,U-Boot應用程序的入口在此文件中。
?
①_start標號
? ?在U-Boot工程中,每種處理器目錄下都有一個start.S文件,該文件中有一個_start標號,是整個U-Boot代碼的入口點。
/**************************************************************************** Jump vector table as in table 3.1 in [1]***************************************************************************/ .globl _start _start: b reset //復位向量:無條件跳轉到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 //快速中斷向量 _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.balignl 16,0xdeadbeef /**************************************************************************** Startup Code (reset vector)** do important init only if we don't start from memory!* relocate armboot to ram* setup stack* jump to second stage***************************************************************************/ _TEXT_BASE:.word TEXT_BASE //定義整個錟-Boot鏡像文件在內存加載的地址 .globl _armboot_start _armboot_start:.word _start /** These are defined in the board-specific linker script.*/ .globl _bss_start _bss_start:.word __bss_start //定義代碼段起始 .globl _bss_end _bss_end:.word _end //定義代碼段結束地址 #ifdef CONFIG_USE_IRQ /* IRQ stack memory (calculated at run-time) */ .globl IRQ_STACK_START //定義IRQ的堆棧地址 IRQ_STACK_START:.word 0x0badc0de /* IRQ stack memory (calculated at run-time) */ .globl FIQ_STACK_START //定義FIQ的堆棧地址 FIQ_STACK_START:.word 0x0badc0de #endif?
? ?_start標號下面的代碼主要是一些偽指令,設置全局變量,供啟動程序把U-Boot映像從Flash存儲器復制到內存中。
? ?其中比較重要的變量是TEXT_BASE,該變量是通過連接腳本得到的。TEXT_BASE變量需要根據開發板的情況自己修改,具體地址需要根據硬件設計確定。
? ?_start標號一開始定義了ARM處理器7個中斷向量的向量表,對應ARM處理器的7種模式。
由于上電一開始處理器會從0地址執行指令,因此第一個指令直接跳轉到reset標號。
? ?reset執行機器初始化的一些操作,此處的跳轉指令,無論是冷啟動還是熱啟動開發板都會執行reset標號的代碼。
? ?reset也屬于一種異常模式,并且該模式的代碼不需要返回。
?
②reset標號
? ?reset標號的代碼在處理器啟動的時候最先被執行。
/** the actual reset code*/ reset:/** set the cpu to SVC32 mode*/mrs r0,cpsr //保存CPSR寄存器的值到r0寄存器bic r0,r0,#0x1f //清除中斷orr r0,r0,#0xd3msr cpsr,r0 //設置CPSR為超級保護模式 /* turn off the watchdog */ //關閉看門狗 #if defined(CONFIG_S3C2400) # define pWTCON 0x15300000 //看門狗地址 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */ //中斷控制器基址 # define CLKDIVN 0x14800014 /* clock divisor register */ #elif defined(CONFIG_S3C2410) # define pWTCON 0x53000000 # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */ # define INTSUBMSK 0x4A00001C # define CLKDIVN 0x4C000014 /* clock divisor register */ #endif #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)ldr r0, =pWTCON //取出當前看門狗控制寄存器的地址到r0mov r1, #0x0 //設置r1寄存器的值為0str r1, [r0] //寫入看門狗控制寄存器/** mask all IRQs by setting all bits in the INTMR - default*/mov r1, #0xffffffff //設置r1ldr r0, =INTMSK //取出中斷屏蔽寄存器地址到r0str r1, [r0] //r1的值寫入中斷屏蔽寄存器 # if defined(CONFIG_S3C2410)ldr r1, =0x3ffldr r0, =INTSUBMSKstr r1, [r0] # endif/* FCLK:HCLK:PCLK = 1:2:4 *//* default FCLK is 120 MHz ! */ldr r0, =CLKDIVN //取出時鐘寄存器地址到r0mov r1, #3 //設置r1的值str r1, [r0] //寫入時鐘配置 #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 *//** we do sys-critical inits only at reboot,* not when booting from ram!*/ #ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_crit //跳轉到開發板相關初始化代碼 #endif?
? ?注意,最后根據CONFIG_SKIP_LOWLEVEL_INIT宏的值是否跳到cpu_init_crit標號執行。
? ?請注意這里使用的是bl指令,在執行完cpu_init_crit標號的代碼后會返回。
?
?
③cpu_init_crit標號
? ?cpu_init_crit標號處的代碼初始化ARM處理器關鍵的寄存器。
/**************************************************************************** CPU_init_critical registers** setup important registers* setup memory timing***************************************************************************/ #ifndef CONFIG_SKIP_LOWLEVEL_INIT cpu_init_crit:/** flush v4 I/D caches*/mov r0, #0mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ //刷新cachemcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ //刷新TLB/** disable MMU stuff and caches //關閉MMU*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)orr r0, r0, #0x00000002 @ set bit 2 (A) Alignorr r0, r0, #0x00001000 @ set bit 12 (I) I-Cachemcr p15, 0, r0, c1, c0, 0/** before relocating, we have to setup RAM timing* because memory timing is board-dependend, you will* find a lowlevel_init.S in your board directory.*/mov ip, lrbl lowlevel_init //跳轉到lowlevel_initmov lr, ipmov pc, lr #endif /* CONFIG_SKIP_LOWLEVEL_INIT */?
? ?注意刷新cache和TLB。
? ?cache是一種高速緩存存儲器,用于保存CPU頻繁使用的數據,在使用Cache技術的處理器上,當一條指令要訪問內存的數據時,首先查詢cache緩存中是否有數據以及數據是否過期,如果數據未過期則從cache讀出數據,處理器會定期回寫cache中的數據到內存。根據程序的局部性原理,使用cache后可以大大加快處理器訪問內存數據的速度。
? ?TLB的作秀是在處理器訪問內存數據的時候做地址轉換。TLB的全稱是Translation Lookaside Buffer,可以翻譯做旁路緩沖。TLB中存放了一些頁表文件,文件中記錄了虛擬地址和物理地址的映射關系。當應用程序訪問一個虛擬地址的時候,會從TLB中查詢出對就的物理地址,然后訪問物理地址。TLB通常是一個分層結構,使用與cache類似的原理。處理器使用一定的算法把最常用的頁表放在最先訪問的層次。
? ?MMU是內存管理單元(Memory Management Unit)的縮寫,在現代計算機體系結構上,MMU被廣泛應用。使用MMU技術可以向應用程序提供一個巨大的虛擬地址空間。在U-Boot初始化的時候,程序看到的地址都是物理地址,無須使用MMU。
?
④lowlevel_init標號
? ?lowlevel_init標號,執行與開發板相關的初始化配置。
.globl lowlevel_init lowlevel_init:/* memory control configuration *//* make r0 relative the current location so that it *//* reads SMRDATA out of FLASH rather than memory ! */ldr r0, =SMRDATA //讀取SMRDATA變量地址ldr r1, _TEXT_BASE //讀取_TEXT_BASE變量地址sub r0, r0, r1ldr r1, =BWSCON /* Bus Width Status Controller */ //讀取總線寬度寄存器add r2, r0, #13*4 //得到SMRDATA占用的大小 0:ldr r3, [r0], #4 //加載SMRDATA到內存str r3, [r1], #4cmp r2, r0bne 0b/* everything is fine now */mov pc, lr.ltorg /* the literal pools origin */ SMRDATA: //定義SMRDATA的值.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)).word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)).word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)).word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)).word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)).word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)).word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)).word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)).word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)).word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT).word 0x32.word 0x30.word 0x30?
? ?程序中需要計算SMRDATA需要加載的內存地址和大小。
? ?首先讀取SMRDATA的變量地址,之后計算存放的內存地址并且記錄在r0寄存器,然后根據總線寬度計算需要加載的SMRDATA大小,并且把加載結束的地址存放在r2寄存器。
? ?最后復制SMRDATA到內存。SMRDATA是開發板上內存映射的配置。
?
⑤relocate標號
? ?relocate部分的代碼負責把U-Boot Stage2的代碼從Flash存儲器加載到內存。
#ifndef CONFIG_SKIP_RELOCATE_UBOOT relocate: /* relocate U-Boot to RAM */adr r0, _start /* r0 <- current position of code *///獲取當前代碼存放地址ldr r1, _TEXT_BASE /* test if we run from flash or RAM *///獲取內存存放代碼地址cmp r0, r1 /* don't reloc during debug *///檢查是否需要加載beq stack_setupldr r2, _armboot_start //獲取stage2代碼存放地址ldr r3, _bss_start //獲取內存代碼段起始地址sub r2, r3, r2 /* r2 <- size of armboot */ //計算stage2代碼長度add r2, r0, r2 /* r2 <- source end address */ //計算stage2代碼結束地址 copy_loop:ldmia r0!, {r3-r10} /* copy from source address [r0] *///從Flash復制代碼到內存stmia r1!, {r3-r10} /* copy to target address [r1] */cmp r0, r2 /* until source end addreee [r2] */ble copy_loop #endif /* CONFIG_SKIP_RELOCATE_UBOOT *//* Set up the stack */ stack_setup: //在內存中建立堆棧ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ //分配內存區域sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */ #ifdef CONFIG_USE_IRQsub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endifsub sp, r0, #12 /* leave 3 words for abort-stack */ clear_bss: //初始化內存bss段內容為0ldr r0, _bss_start /* find start of bss segment *///查找bss段起始地址ldr r1, _bss_end /* stop here *///查找bss段結束地址mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... */add r0, r0, #4cmp r0, r1ble clbss_l #if 0/* try doing this stuff after the relocation */ldr r0, =pWTCONmov r1, #0x0str r1, [r0]/** mask all IRQs by setting all bits in the INTMR - default*/mov r1, #0xffffffffldr r0, =INTMRstr r1, [r0]/* FCLK:HCLK:PCLK = 1:2:4 *//* default FCLK is 120 MHz ! */ldr r0, =CLKDIVNmov r1, #3str r1, [r0]/* END stuff after relocation */ #endifldr pc, _start_armboot //設置程序指針為start_armboot()函數地址 _start_armboot: .word start_armboot?
? ?程序首先檢查當前是否在內存中執行代碼,根據結果決定是否需要從Flash存儲器加載代碼。
程序通過獲取_start和_TEXT_BASE所在的地址比較,如果地址相同說明程序已經在內存中,無須加載。
? ?然后計算要加載的stage2代碼起始地址和長度,然后在循環復制Flash的數據到內存,每次可以復制8個字長的數據。stage2程序復制完成后,程序設置系統堆棧,最后清空內存bss段內容。
? ?relocate程序最后在設置程序指針寄存器為start_armboot()函數地址,程序跳轉到stage2部分執行,注意最后的定義,_start_armboot全局變量的值是C語言函數start_armboot()函數的地址,使用這種方式可以在匯編中調用C語言編寫的函數。
? ?另外,有一種NOR類型Flash存儲器,可以像使用內存一樣直接執行程序,NOR Flash被映射到地址0開始的內存空間。
? ?注意,程序中第12行的_armboot_start即標號⑥_armboot_start
?
⑦start_armboot()函數
? ?start_armboot()函數主要初始化ARM系統的硬件和環境變量,包括Flash存儲器、FrameBuffer、網卡等,最后進入U-Boot應用程序主循環。
void start_armboot (void) {init_fnc_t **init_fnc_ptr;char *s; #ifndef CFG_NO_FLASHulong size; #endif #if defined(CONFIG_VFD) || defined(CONFIG_LCD)unsigned long addr; #endif/* Pointer is writable since we allocated a register for it */gd = (gd_t*)(_armboot_start - CFG_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));monitor_flash_len = _bss_start - _armboot_start;for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang ();}} #ifndef CFG_NO_FLASH/* configure available FLASH banks */size = flash_init (); //初始化Flash存儲器配置display_flash_config (size); //顯示Flash存儲器配置 #endif /* CFG_NO_FLASH */ #ifdef CONFIG_VFD # ifndef PAGE_SIZE # define PAGE_SIZE 4096 # endif/** reserve memory for VFD display (always full pages)*//* bss_end is defined in the board-specific linker script */addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); //計算FrameBuffer內存地址size = vfd_setmem (addr); //計算FrameBuffer占用內存大小gd->fb_base = addr; //設置FrameBuffer內存起始地址 #endif /* CONFIG_VFD */ #ifdef CONFIG_LCD # ifndef PAGE_SIZE # define PAGE_SIZE 4096 # endif/** reserve memory for LCD display (always full pages)*//* bss_end is defined in the board-specific linker script */addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); //計算rameBuffer內存地址size = lcd_setmem (addr); //計算FrameBuffer占用內存大小gd->fb_base = addr; //設置FrameBuffer內存起始地址 #endif /* CONFIG_LCD *//* armboot_start is defined in the board-specific linker script */mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); #if (CONFIG_COMMANDS & CFG_CMD_NAND)puts ("NAND: ");nand_init(); /* go init the NAND */ //初始化NAND Flash存儲器 #endif #ifdef CONFIG_HAS_DATAFLASHAT91F_DataflashInit(); //初始化Hash表dataflash_print_info(); #endif/* initialize environment */env_relocate (); //重新設置環境變量 #ifdef CONFIG_VFD/* must do this after the framebuffer is allocated */drv_vfd_init(); //初始化虛擬顯示設置 #endif /* CONFIG_VFD *//* IP Address */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); //設置網卡的IP地址/* MAC Address */{int i;ulong reg;char *s, *e;char tmp[64];i = getenv_r ("ethaddr", tmp, sizeof (tmp)); //從網卡寄存器讀取MAC地址s = (i > 0) ? tmp : NULL;for (reg = 0; reg < 6; ++reg) {gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;if (s)s = (*e) ? e + 1 : e;} #ifdef CONFIG_HAS_ETH1i = getenv_r ("eth1addr", tmp, sizeof (tmp)); //讀取Hash值s = (i > 0) ? tmp : NULL;for (reg = 0; reg < 6; ++reg) {gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;if (s)s = (*e) ? e + 1 : e;} #endif}devices_init (); /* get the devices list going. */ //初始化開發板上的設備 #ifdef CONFIG_CMC_PU2load_sernum_ethaddr (); #endif /* CONFIG_CMC_PU2 */jumptable_init (); //初始化跳轉表console_init_r (); /* fully init console as a device */ //初始化控制臺 #if defined(CONFIG_MISC_INIT_R)/* miscellaneous platform dependent initialisations */misc_init_r (); //初始化其他設備 #endif/* enable exceptions */enable_interrupts (); //打開中斷/* Perform network card initialisation if necessary */ #ifdef CONFIG_DRIVER_CS8900cs8900_get_enetaddr (gd->bd->bi_enetaddr); //獲取CS8900網卡MAC地址 #endif #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)if (getenv ("ethaddr")) {smc_set_mac_addr(gd->bd->bi_enetaddr); //設置SMC網卡MAC地址} #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 *//* Initialize from environment */if ((s = getenv ("loadaddr")) != NULL) {load_addr = simple_strtoul (s, NULL, 16);} #if (CONFIG_COMMANDS & CFG_CMD_NET)if ((s = getenv ("bootfile")) != NULL) {copy_filename (BootFile, s, sizeof (BootFile)); //保存FrameBuffer} #endif /* CFG_CMD_NET */ #ifdef BOARD_LATE_INITboard_late_init (); //開發板相關設備初始化 #endif #if (CONFIG_COMMANDS & CFG_CMD_NET) #if defined(CONFIG_NET_MULTI)puts ("Net: "); #endifeth_initialize(gd->bd); #endif/* main_loop() can return to retry autoboot, if so just run it again. */for (;;) {main_loop (); //進入主循環}/* NOTREACHED - no way out of command loop except booting */ } void hang (void) {puts ("### ERROR ### Please RESET the board ###\n");for (;;); }?
? ?start_armboot()函數代碼里有許多的宏相關,這個根據開發板的情況進行配置。函數里面的board_late_init()函數,該函數是開發板提供的,供不同的開發板做一些特有的初始化工作。
? ?在start_armboot()函數中,使用宏開關括起來的代碼是在各種開發板是最常用的功能,如CS8900網卡配置。整個函數配置完畢后,進入一個for死循環,調用main_loop()函數。這里需要注意,在main_loop()函數中也有一個for死循環。
? ?start_armboot()函數使用死循環調用main_loop()函數,作用是防止main_loop()函數開始的初始化代碼如果調用失敗后重新執行初始化操作,保證程序能進入到U-Boot的命令行。
?
?
⑧main_loop()函數
? ?main_loop()函數做的都是與具體平臺無關的工作,主要包括初始化啟動次數限制機制、設置軟件版本號、打印啟動信息、解析命令等。
? ??設置啟動次數有關參數。在進入main_loop()函數后,首先是根據配置加載已經保留的啟動次數,并且根據配置判斷是否超過啟動次數。
void main_loop (void) { #ifndef CFG_HUSH_PARSERstatic char lastcommand[CFG_CBSIZE] = { 0, };int len;int rc = 1;int flag; #endif #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)char *s;int bootdelay; #endif #ifdef CONFIG_PREBOOTchar *p; #endif #ifdef CONFIG_BOOTCOUNT_LIMITunsigned long bootcount = 0;unsigned long bootlimit = 0;char *bcs;char bcs_set[16]; #endif /* CONFIG_BOOTCOUNT_LIMIT */ #if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)ulong bmp = 0; /* default bitmap */extern int trab_vfd (ulong bitmap); #ifdef CONFIG_MODEM_SUPPORTif (do_mdm_init)bmp = 1; /* alternate bitmap */ #endiftrab_vfd (bmp); #endif /* CONFIG_VFD && VFD_TEST_LOGO */ #ifdef CONFIG_BOOTCOUNT_LIMITbootcount = bootcount_load(); //加載保存的啟動次數bootcount++; //啟動次數加1bootcount_store (bootcount); //更新啟動次數sprintf (bcs_set, "%lu", bootcount); //打印啟動次數setenv ("bootcount", bcs_set);bcs = getenv ("bootlimit");bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0; //轉換啟動次數字符串為UINT類型 #endif /* CONFIG_BOOTCOUNT_LIMIT */?
? ?函數啟動次數限制功能,啟動次數限制可以被用戶設置一個啟動次數,然后保存在Flash存儲器的特定位置,當到達啟動次數后,U-Boot無法啟動,該功能適合一些商業產品,通過配置不同的License限制用戶重新啟動系統。
? ??接下來是Modem功能。如果系統中有Modem,打開該功能可以接受其他用戶通過電話網絡的撥號請求。Modem功能通常供一些遠程控制的系統使用
#ifdef CONFIG_MODEM_SUPPORTdebug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);if (do_mdm_init) { //判斷是否需要初始化Modemchar *str = strdup(getenv("mdm_cmd")); //獲取Modem參數setenv ("preboot", str); /* set or delete definition */if (str != NULL)free (str);mdm_init(); /* wait for modem connection */ //初始化Modem} #endif /* CONFIG_MODEM_SUPPORT */?
? ??然后設置U-Boot版本號,初始化命令自動完成功能等。
#ifdef CONFIG_VERSION_VARIABLE{extern char version_string[];setenv ("ver", version_string); /* set version variable */ //設置版本號} #endif /* CONFIG_VERSION_VARIABLE */ #ifdef CFG_HUSH_PARSERu_boot_hush_start (); //初始化Hash功能 #endif #ifdef CONFIG_AUTO_COMPLETEinstall_auto_complete(); //初始化命令自動完成功能 #endif #ifdef CONFIG_PREBOOTif ((p = getenv ("preboot")) != NULL) { # ifdef CONFIG_AUTOBOOT_KEYEDint prev = disable_ctrlc(1); /* disable Control C checking *///關閉Crtl+C組合鍵 # endif # ifndef CFG_HUSH_PARSERrun_command (p, 0); //運行Boot參數 # elseparse_string_outer(p, FLAG_PARSE_SEMICOLON |FLAG_EXIT_FROM_LOOP); # endif # ifdef CONFIG_AUTOBOOT_KEYEDdisable_ctrlc(prev); /* restore Control C checking *///恢復Ctrl+C組合鍵 # endif} #endif /* CONFIG_PREBOOT */?
? ?程序開始是動態版本號功能支持代碼,version_string變量是在其他文件定義的一個字符串變量,當用戶改變U-Boot版本的時候會更新該變量。打開動態版本支持功能后,U-Boot在啟動的時候會顯示最新的版本號。
? ?install_auto_comlpete()函數設置命令行自動完成功能,該功能與linux的shell類似,當用戶輸入一個部分命令后,可以通過按下鍵盤上的Tab鍵補全命令的剩余部分,main_loop()函數不同的功能使用宏開關控制不僅能提高代碼模塊化,理主要的是針對嵌入式系統Flash存儲器大小設計的。在嵌入式系統上,不同的系統Flash存儲空間不同。對于一些Flash空間比較緊張的設備來說,通過宏開關關閉一些不是特別必要的功能如命令行自動完成,可以減小U-Boot編譯后的文件大小。
? ??在進入主循環之前,如果配置了啟動延遲功能,需要等待用戶從串口或者網絡接口輸入。如果用戶按下任意鍵打斷,啟動流程,會向終端打印出一個啟動菜單。
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)s = getenv ("bootdelay");bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; //啟動延遲debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay); # ifdef CONFIG_BOOT_RETRY_TIMEinit_cmd_timeout (); //初始化命令行超時機制 # endif /* CONFIG_BOOT_RETRY_TIME */ #ifdef CONFIG_BOOTCOUNT_LIMITif (bootlimit && (bootcount > bootlimit)) { //檢查是否超出啟動次數限制printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",(unsigned)bootlimit);s = getenv ("altbootcmd");}else #endif /* CONFIG_BOOTCOUNT_LIMIT */s = getenv ("bootcmd"); //獲取啟動命令參數debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");if (bootdelay >= 0 && s && !abortboot (bootdelay)) { //檢查是否支持啟動延遲功能 # ifdef CONFIG_AUTOBOOT_KEYEDint prev = disable_ctrlc(1); /* disable Control C checking *///關閉Ctrl+C組合鍵 # endif # ifndef CFG_HUSH_PARSERrun_command (s, 0); //運行啟動命令行 # elseparse_string_outer(s, FLAG_PARSE_SEMICOLON |FLAG_EXIT_FROM_LOOP); # endif # ifdef CONFIG_AUTOBOOT_KEYEDdisable_ctrlc(prev); /* restore Control C checking *///打開Ctrl+C組合鍵 # endif} # ifdef CONFIG_MENUKEYif (menukey == CONFIG_MENUKEY) { //檢查是否支持菜單鍵s = getenv("menucmd");if (s) { # ifndef CFG_HUSH_PARSERrun_command (s, 0); # elseparse_string_outer(s, FLAG_PARSE_SEMICOLON |FLAG_EXIT_FROM_LOOP); # endif}} #endif /* CONFIG_MENUKEY */ #endif /* CONFIG_BOOTDELAY */ #ifdef CONFIG_AMIGAONEG3SE{extern void video_banner(void);video_banner(); //打印啟動圖標} #endif?
? ??在各功能設置完畢后,程序進入一個for死循環,該循環不斷使用readline()函數從控制臺(一般是串口)讀取用戶的輸入,然后解析,有關如何解析命令則可以參考U-Boot代碼中run_command()函數的定義。
/** Main Loop for Monitor Command Processing*/ #ifdef CFG_HUSH_PARSERparse_file_outer();/* This point is never reached */for (;;); #elsefor (;;) { //進入命令行超時 #ifdef CONFIG_BOOT_RETRY_TIMEif (rc >= 0) {/* Saw enough of a valid command to* restart the timeout.*/reset_cmd_timeout(); //設置命令行超時} #endiflen = readline (CFG_PROMPT); //讀取命令flag = 0; /* assume no special flags for now */if (len > 0)strcpy (lastcommand, console_buffer);else if (len == 0)flag |= CMD_FLAG_REPEAT; #ifdef CONFIG_BOOT_RETRY_TIMEelse if (len == -2) {/* -2 means timed out, retry autoboot*/puts ("\nTimed out waiting for command\n"); # ifdef CONFIG_RESET_TO_RETRY/* Reinit board to run initialization code again */do_reset (NULL, 0, 0, NULL); # elsereturn; /* retry autoboot */ # endif} #endifif (len == -1)puts ("<INTERRUPT>\n");elserc = run_command (lastcommand, flag); //運行命令if (rc <= 0) {/* invalid command or not repeatable, forget it */lastcommand[0] = 0;}} #endif /*CFG_HUSH_PARSER*/ }?
結束語
? ?整個U-Boot的啟動流程代碼,最關鍵的就是這些了,其中主要語句都作了相應注釋,另外我把自己注釋后的四個源文件上傳到附件,以備查看。
? ?如果有人覺得哪里注釋沒對,歡迎留言探討。
?
?
本文出自 “成鵬致遠” 博客,請務必保留此出處http://infohacker.blog.51cto.com/6751239/1202976
轉載于:https://www.cnblogs.com/lcw/p/3159399.html
總結
以上是生活随笔為你收集整理的【Bootloader】探究bootloader,分析u-boot源码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VC++中操作XML(MFC、SDK)
- 下一篇: Hadoop4.2HDFS测试报告之四