Uboot全稱Universal Boot Loader,一個遵循GPL協議的的開源項目,其做用是引導操做系統,支持引導linux、VxWorks、Solaris等操做系統;其源碼組織形式和linux源碼很類似,編譯也可參照linux源碼編譯,且包含許多linux源碼中的驅動源碼,因此uboot實際上能夠算做一個微型的操做系統,能夠作一些簡單工做。linux
本文的分析對象是u-boot-2012.10版本,如下內容將根據此版本源碼和特定的board展開分析,從設備上電運行的第一行程序開始到引導linux系統正常啓動。ios
1、uboot 目錄組織形式:api
一、../u-boot-2012.10 //一級目錄:less
├── api函數
├── archoop
├── boardpost
├── commonfetch
├── diskui
├── docspa
├── drivers
├── dts
├── examples
├── fs
├── include
├── lib
├── nand_spl
├── net
├── post
├── spl
├── test
└── tools
可見其目錄組織樣式和linux是極其類似的。
2、arch爲所支持的體系結構,內容以下:
├── arch
│ ├── arm
│ ├── avr32
│ ├── blackfin
│ ├── m68k
│ ├── microblaze
│ ├── mips
│ ├── nds32
│ ├── nios2
│ ├── openrisc
│ ├── powerpc
│ ├── sandbox
│ ├── sh
│ ├── sparc
│ └── x86
看得出來uboot支持的硬件體系是很全面的。
3、board目錄是目前已適配的板子:
├── board
│ ├── a3000
│ ├── a4m072
│ ├── actux1
│ ├── actux2
│ ├── actux3
│ ├── actux4
│ ├── adder
│ ├── afeb9260
│ ├── … //太多,此處再也不列舉
其餘目錄就不一一列舉,和linux相比,仍是熟悉的味道,仍是熟悉的配方。
好了,進入正題,uboot的啓動自己分爲兩個大的階段,第一階段是從存儲介質中讀取小部分程序到cpu中,這部分程序要完成引導linux所用的硬件的初始化,以及加載uboot其他程序到RAM中;第二階段是繼續初始化必備硬件,加載linux鏡像到RAM中,把執行權限交給linux,完成使命。
2、Uboot啓動第一階段:
主脈絡:部分硬件初始化——>加載完整uboot到RAM——>跳轉到第二階段入口開始執行
整個過程最重要的兩個文件:
start.S,彙編語言文件,涉及到特定硬件設備的讀寫寄存器操做以及特定體系結構的彙編語言;
lowlevel_init.S,設計的操做和文件名吻合,即完成底層的初始化。
1、執行流程分析:
①、中斷向量表
1 .globl _start //定義一個全局標號_star 2 _start: b reset //標號_star處的內容爲跳轉到reset標號開始執行 3 //將_undefined_instruction標號表示的內容做爲地址,加載到PC中, 4 //這種用法能夠實現執行流程的跳轉 5 ldr pc, _undefined_instruction 6 ldr pc, _software_interrupt 7 ldr pc, _prefetch_abort 8 ldr pc, _data_abort 9 ldr pc, _not_used 10 ldr pc, _irq 11 ldr pc, _fiq 12 //以上七條ldr pc,x加上b reset共八條指令組成中斷向量表 13 … 14 //_undefined_instruction標號表示定義了一個word類型的變量undefined_instruction 15 _undefined_instruction: .word undefined_instruction 16 … 17 //exception handlers //異常處理 18 .align 5 //5字節對齊 19 20 //可知undefined_instruction的真正用途是指向此處代碼,即異常處理的具體實現 21 undefined_instruction: 22 get_bad_stack 23 bad_save_user_regs 24 bl do_undefined_instruction
由以上內容可知,除第一行代碼外,其他代碼都是跳轉到特定位置去執行中斷服務子程序。
由b reset可知程序正常的流程並不會走到中斷處理流程中去(正常狀況下固然不該該執行中斷子程序,只有發生中斷時纔去執行),而是直接跳轉到reset標號處開始執行。
②、reset:
1 reset: 2 /* 3 * set the cpu to SVC32 mode 設置CPU爲SVC32模式 4 */ 5 mrs r0, cpsr //讀出 6 bic r0, r0, #0x1f //低五位清0 7 orr r0, r0, #0xd3 //與D3作與操做 8 msr cpsr,r0 //寫回
CPSR是ARM體系結構中的程序狀態寄存器,其結構以下:
M[4:0] 處理器模式 可訪問的寄存器
ob10000 user pc,r14~r0,CPSR
0b10001 FIQ PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ
0b10010 IRQ PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ
0B10011 SUPERVISOR PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC
0b10111 ABORT PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT
0b11011 UNDEFINEED PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND
0b11111 SYSTEM PC,R14-R0,CPSR(ARM V4以及更高版本)
I、F、T三位若是寫1即禁用,因此以上四句操做的結果爲設置CPU爲SUPERVISOR模式且禁用中斷,至於爲何選擇當前模式而不是其餘模式?
首先能夠排除的是,停止abt和未定義und模式,那都是不太正常的模式;
其次,對於快中斷fiq和中斷irq來講,此處uboot初始化的時候,也還沒啥中斷要處理和可以處理,並且即便是註冊了終端服務程序後,可以處理中斷,那麼這兩種模式,也是自動切換過去的,因此,此處也不該該設置爲其中任何一種模式。
至於usr模式,因爲此模式沒法直接訪問不少的硬件資源,而uboot初始化,就必需要去訪問這類資源,因此此處能夠排除,不能設置爲用戶usr模式。
而svc模式自己就屬於特權模式,自己就能夠訪問那些受控資源,並且,比sys模式還多了些本身模式下的影子寄存器,因此,相對sys模式來講,能夠訪問資源的能力相同,可是擁有更多的硬件資源。
好了,接着上面的源碼繼續向下分析:
1 #ifndef CONFIG_SKIP_LOWLEVEL_INIT 2 bl cpu_init_cp15 3 bl cpu_init_crit 4 #endif
③、cpu_init_cp15:
CP15是協處理器,uboot引導時不須要這部分功能,因此相關的寄存器都配置爲不工做狀態。
1 /************************************************************************* 2 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless 3 * CONFIG_SYS_ICACHE_OFF is defined. 4 *************************************************************************/ 5 ENTRY(cpu_init_cp15) 6 /* 7 * Invalidate L1 I/D 8 */ 9 mov r0, #0 @ set up for MCR 10 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs 11 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache 12 mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array 13 mcr p15, 0, r0, c7, c10, 4 @ DSB 14 mcr p15, 0, r0, c7, c5, 4 @ ISB 15 /* 16 * disable MMU stuff and caches 17 */ 18 mrc p15, 0, r0, c1, c0, 0 19 bic r0, r0, #0x00002000 @ clear bits 13 (--V-) 20 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) 21 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align 22 orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB 23 24 #ifdef CONFIG_SYS_ICACHE_OFF 25 bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache 26 #else 27 orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache 28 #endif 29 30 mcr p15, 0, r0, c1, c0, 0 31 mov pc, lr @ back to my caller 32 33 ENDPROC(cpu_init_cp15)
執行完這部分代碼後返回跳轉點繼續向下執行,即cpu_init_crit。
④、cpu_init_crit:
1 ENTRY(cpu_init_crit) 2 /* 3 * Jump to board specific initialization... 4 * The Mask ROM will have already initialized 5 * basic memory. Go here to bump up clock rate and handle 6 * wake up conditions. 7 */ 8 b lowlevel_init @ go setup pll,mux,memory 9 ENDPROC(cpu_init_crit)
接下來會跳轉到lowlevel_init去執行,所作工做即註釋所說起的,初始化PLL\MUX\MEM等
⑤、lowlevel_init:
1 ENTRY(lowlevel_init) 2 // Setup a temporary stack 3 ldr sp, =CONFIG_SYS_INIT_SP_ADDR 4 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 5 6 // Save the old lr(passed in ip) and the current lr to stack 7 push {ip, lr} 8 9 // go setup pll, mux, memory 10 bl s_init 11 pop {ip, pc} 12 ENDPROC(lowlevel_init)
而s_init是個C函數,因此在調用以前須要準備堆棧。在這個函數中作了一系列的初始化操做,其實就是禁用看門狗等,能夠看到不少板子的初始化操做會直接忽略這一部分。
1 void s_init(void) 2 { 3 … 4 /* Watchdog init */ 5 writew(0xA500, &rwdt0->rwtcsra0); 6 writew(0xA500, &rwdt1->rwtcsra0); 7 … 8 /* CPG */ 9 writel(0xFF800080, &cpg->rmstpcr4); 10 writel(0xFF800080, &cpg->smstpcr4); 11 … 12 }
執行完這個函數後執行流程返回到lowlevel_init,再返回到調用cpu_init_crit的地方,流程往下執行到call_board_init_f。
⑥、call_board_init_f:
1 void board_init_f(ulong bootflag) 2 { 3 bd_t *bd; 4 init_fnc_t **init_fnc_ptr; 5 gd_t *id; 6 ulong addr, addr_sp; 7 8 bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f"); 9 10 gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); 11 12 memset((void *)gd, 0, sizeof(gd_t)); 13 gd->mon_len = _bss_end_ofs; 14 ..//gd全局結構體成員複製 15 16 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 17 if ((*init_fnc_ptr)() != 0) { 18 hang (); 19 } 20 } 21 ... ..//gd全局結構體成員複製 22 gd->bd->bi_baudrate = gd->baudrate; 23 /* Ram ist board specific, so move it to board code ... */ 24 dram_init_banksize(); 25 display_dram_config(); /* and display it */ 26 ... 27 gd->relocaddr = addr; 28 gd->start_addr_sp = addr_sp; 29 gd->reloc_off = addr - _TEXT_BASE; 30 debug("relocation Offset is: %08lx\n", gd->reloc_off); 31 memcpy(id, (void *)gd, sizeof(gd_t)); 32 33 relocate_code(addr_sp, id, addr); //調用start.s中的彙編函數 34 }
以上C函數完成了gt結構體部分紅員賦值,這個結構體將會在以後的流程中用到;此函數最後調用匯編函數,執行流程再次跳轉。
⑦、relocate_code:
1 ENTRY(relocate_code) 2 mov r4, r0 /* save addr_sp */ 3 mov r5, r1 /* save addr of gd */ 4 mov r6, r2 /* save addr of destination */ 5 6 /* Set up the stack */ 7 stack_setup: 8 … //堆棧處理 9 copy_loop: 10 … //循環拷貝uboot到RAM 11 //這個過程只能用到ARM的通用寄存器,因此每次只能拷貝幾字節,循環 12 clear_bss: 13 …//bss段,因爲是未初始化數據,沒什麼用,無需拷貝,直接留出空間,並初始化爲0便可 14 jump_2_ram: //調到第二階段,即調到RAM中去執行 15 … 16 ldr r0, _board_init_r_ofs 17 adr r1, _start 18 add lr, r0, r1 19 add lr, lr, r9 20 /* 上面下面都是調用C函數的準備工做 */ 21 mov r0, r5 /* gd_t */ 22 mov r1, r6 /* dest_addr */ 23 /* jump to it ... */ 24 mov pc, lr 25 ENDPROC(relocate_code)
通過以上諸多過程,uboot已經把本身從flash中拷貝到RAM中,而且爲以後的執行準備好了各類參數,最終跳轉到第二部分的入口call_board_init_r。
⑧、call_board_init_r:
1 { 2 gd = id; 3 4 gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */ 5 bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r"); 6 board_init(); /* Setup chipselects */ 7 mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN); 8 9 env_relocate(); //環境變量初始化 10 stdio_init(); //標準IO初始化 11 jumptable_init(); 12 console_init_r(); //終端初始化 13 load_addr = getenv_ulong("loadaddr", 16, load_addr); 14 15 for (;;)
{ 16 //一切準備就緒後進入死循環,此循環用於判斷是否有用戶輸入,而且隨之作出相應 17 main_loop(); 18 } 19 }
此函數執行的操做是後一階段的各類初始化,爲引導linux kernel作好準備,接下來分析一下main_loop()的大體過程。
3、uboot流程第二階段:
①、Main_loop通過簡化後以下所示,看見,這是個很清晰很簡單的流程,檢測是否有按鍵按下,沒有則倒計時,有則開始處理命令相關的內容。
1 void main_loop (void) 2 { 3 s = getenv ("bootcmd"); //獲取引導命令 4 5 //檢測是否有按鍵按下,有則執行下面的死循環 6 if (bootdelay != -1 && s && !abortboot(bootdelay)) 7 { 8 run_command_list(s, -1, 0); 9 } 10 for (;;) 11 { 12 len = readline (CONFIG_SYS_PROMPT); 13 flag = 0; /* assume no special flags for now */ 14 if (len > 0) 15 strcpy (lastcommand, console_buffer); 16 else if (len == 0) 17 flag |= CMD_FLAG_REPEAT; 18 19 //輸入命令的的合法性驗證 20 if (len == -1) 21 puts ("<INTERRUPT>\n"); 22 else 23 rc = run_command(lastcommand, flag); //執行命令 24 25 if (rc <= 0) { 26 /* invalid command or not repeatable, forget it */ 27 lastcommand[0] = 0; 28 } 29 }
}
②、流程轉到run_command,經簡化可得:這部分是對函數的進一步封裝,這裏實際上是有兩個流程的,一個是有關哈希查找命令的,另外一個就是下面這個,簡單粗暴的。
↓
1 int run_command(const char *cmd, int flag) 2 { 3 if (builtin_run_command(cmd, flag) == -1) 4 return 1; 5 return 0; 6 }
③、流程轉到r builtin_run_command,經簡化可得:這裏所作的各類爲完整解析命令,並調用函數去進一步執行。
↓
1 static int builtin_run_command(const char *cmd, int flag) 2 { 3 //合法性校驗 4 while (*str) { 5 //特殊字符解析 6 } 7 process_macros (token, finaltoken); //宏展開,即徹底解析命令 8 9 //命令執行過程 10 if (cmd_process(flag, argc, argv, &repeatable)) 11 rc = -1; 12 return rc ? rc : repeatable; 13 }
④、流程轉到cmd_process,經簡化可得:獲得完整的命令和參數,執行命令。
↓
1 cmd_process(int flag, int argc, char * const argv[], 2 int *repeatable) 3 { 4 cmd_tbl_t *cmdtp; 5 6 cmdtp = find_cmd(argv[0]); //查找命令 7 if (cmdtp == NULL) { 8 printf("Unknown command '%s' - try 'help'\n", argv[0]); 9 return 1; 10 } 11 12 if (argc > cmdtp->maxargs) 13 rc = CMD_RET_USAGE; 14 15 /* If OK so far, then do the command */ 16 if (!rc) { 17 rc = cmd_call(cmdtp, flag, argc, argv); //真正的執行命令 18 *repeatable &= cmdtp->repeatable; 19 } 20 return rc; 21 }
至此,uboot的使命便完成了,將舞臺交給linux。