2016年7月20日 星期三

[Embedded] U-Boot的開機流程


以 U-Boot source code 為例,整理 embedded system 的開機流程

1.  開機流程
CPU上電後,會到固定位置讀取指令並且執行,不同架構其位置不同,比方說 ARM 結構的 CPU 會從位址 0x00000000 開始執行,MIPS結構的 CPU 會從位址 0xBFC00000 開始,。 
若要手動搞定一個嵌入式系統,首先要作的便是安排系統記憶體的編排方式,透過編寫 linker script,可以控制 CPU 上電後會先開始執行的指令,因而讓開機時便開始執行 U-Boot。 
以下是一個 linker script 例子,此例中,會將 start.o 擺放在 0x00000000 的位置。系統開機時便會在從此處開始取出指令開始執行。一般作法在 start.o 會做些硬體的初始化,然後再執行 u-boot.  (此例子取自 u-boot-2013.04-rc1.tar.bz2 ,參考\board\samsung\smdk6400\u-boot-nand.ld)

2.  U-Boot 有兩種工作模式
(1)啟動載入(Boot loading)模式。
上電後,Bootloader 從板子上的某個固態存放裝置上將作業系統載入到RAM中運行
(2)下載(Downloading)模式。
在這種模式下,開發人員可以使用各種命令,(xmodem, tftp, usb and etc...)通過串口連接或網路連接等通信手段從主機(Host)下載檔案(比如內核映射、檔案系統映射),將它們直接放在記憶體運行或是燒入Flash類固態存放裝置中。

3. U-Boot啟動過程分為兩個階段
a. Phase 1,
硬體設備初始化, 準備 RAM 空間(對DDR晶片初始化), 設置 CPU 工作模式為管理模式(SVC)複製 U-Boot程式至 RAM, 啟動 MMU, 設置堆疊,  跳轉到 phase2的程式碼進入點. 
b. Phase 2,
初始化要使用的硬體設備, 檢測系統記憶體映象 (memory map), 指定初始化函數清單(如:cpu_init, interrupt_init),為核心設定啟動參數, 進入 U-Boot 命令列 (無窮迴圈)

4.從 U-Boot 程式碼分析其流程

start.s (程式碼及註解請看這裡)
禁止 FIQ, IRQ中斷,並將 ARM CPU 設定為 SVC(Supervisor Calls) 模式 
設定 Cache和 MMU
若是從 nand boot,則應該已經做了硬體的Reset,因此不需清除Cache與暫停MMU。否則要 
  • 設定 Cache,此時應該先讓 ICache, DCache, TLB 都失效。
  • 設定 MMU,包含 Endian, Address Align Check, 並通知 MMU 不要使用 ICache, DCache。
  • 設定CPU內的記憶體 TCM(Tightly-coupled memory),此部分也是可分為 Instruction TCM, Data TCM
設定週邊設備(peripheral)對應的記憶體位址 
執行 lowlevel_init()
此函數用來初始化板子相關設定,程式碼會放在 board 目錄下,例如:board/samsung/smdk6400/lowlevel_init.s,此函數會設定LED,WatchDog,Interrupt,System Clock,Serial Port,NAND Flash,DRAM。建置一個小的 Stack,清除BSS段。 
執行 _main() 
此函數用來建立C語言的執行環境,執行結束之後會返回 start.s。實作可參考 crt0.s。
relocate code
將 ELF 對應的各個區塊,複製到記憶體內。然後啟動MMU,致能TLB。此時還不需要 ICache, DCache,因此不用變更設定。 
最後設定 exception handler,處理異常事件

crt0.s (C-runtime startup Code,編譯成 Thumb code),程式碼位置在 arch/arm/lib/crt0.s,主要動作如下
配置一小塊堆疊,用來放置 160bytes 的 Global Data,將此記憶體位置紀錄在 r8,供後續使用。 
呼叫 board_init_f(ulong bootflag),此函數是U-Boot執行的第一個 C 函數,程式碼在arch\arm\lib\board.c,此函數會清空 Global Data,將 init_sequence[] 內定義的所有函數都執行一遍,其初始化工作可能包含 serial, baudrate, consol, system information, i2c 跟 dram,並規劃SDRAM空間配置,由位址最大處往前,分別分配 TLB, .Bss, .Data, .Text等區段,接著再分配 malloc空間,global data, abort_stack。

如果是 non-SPL build,會繼續下列流程
呼叫 relocate_code(addr_sp, gd, addr_moni),跳回到 start.S中,此時會將 u-boot 程式碼複製到 SDRAM中。複製成功後,執行 bx lr,跳回 crt0.S。 
呼叫 c_runtime_cpu_setup,此時一樣會跳至 start.S,然後立刻透過 bx lr 返回 crt0.S。 
清空BSS,控制LED閃爍。
呼叫 board_init_r(gd_t *id, ulong dest_addr),此函數是 U-Boot 執行的第二個 C函數,也是做些初始化動作,注意的是許多外接設備都在此時才初始化,如 :Ethernet, I2Cfast, LCD, KEYBOARD, USB, SCSI, KGDB等, 最後會進入一個無窮迴圈裡,持續的執行 main_loop(), 
main_loop()的主要作用就是 U-Boot 啟動管理,此時可能會 delay 數秒 (CONFIG_BOOTDELAY),等待使用者輸入指令。main_loop()的實作可參考 common/main.c。此函數會取得環境參數中的 bootcmd,執行 bootcmd 載入linux kernel,後續任務便由 kernel 接手。 
可透過 bootm 命令載入 kernel,例如:"bootm 0x80700000"。
cmd_bootm.c
設定 kernel 的 entry point 
取出 zImage 所儲存的 64 bytes header,判斷 magic number是否正確? 
設定 ramdisk 
取得 fdt (flattened device tree),設定各項周邊設備。 
執行 kernel,函數宣告為 void (*theKernel) (int, char **, char **, int *);,此函數內容還需要再研究。

以ARM開機為例,做個簡單總結,CPU開機時會取出 0x00000000 處的程式碼 (start.s),初始化相關設備,並準備好C語言執行環境,將 u-boot 程式 relocate 至 SDRAM,執行 u-boot 程式,初始化所有設備,最後由 u-boot 傳入各項參數,啟動 kernel。