以 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)This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
/* * (C) Copyright 2002 * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de> * * (C) Copyright 2008 * Guennadi Liakhovetki, DENX Software Engineering, <lg@denx.de> * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ /* linker script 可用來將 object file (.o) 連結成一個輸出文件(例如:elf file),建議先搞懂ELF,再看這支程式 */ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) /* 設定入口函數名稱為 _start, 這是CPU執行的第一個程式, 若不指定,預設採用 .text section 的第一個Byte的位置 */ SECTIONS /* 描述如何將輸入檔案的各個section,組合成輸出檔案中所對應的section。*/ { . = 0x00000000; /* Section 的起始位置為 0x0 */ /* ”.” 是個位置計數器,紀錄當前位置在目標文件中的虛擬地址 */ /* ”.” 是個自動增加的計數器,當一個 section生成後,”.”的值會自動加上該 section 的長度 */ . = ALIGN(4); /* 程式碼以4位元組對齊 */ .text : /* 放置程式碼的位置,首先放置 start.o,接著才放置其他 .o 的程式(即 .text section) */ { arch/arm/cpu/arm1176/start.o (.text) *(.text) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } /* 唯讀 */ . = ALIGN(4); .data : { *(.data) } /* 已初始化的全局變數 */ . = ALIGN(4); .got : { *(.got) } /* Global Offset Tables,一個全域的參考表格,可用來查找 symble 與 address 的對應關係 */ . = ALIGN(4); .u_boot_list : { #include <u-boot.lst> } . = ALIGN(4); .mmudata : { *(.mmudata) } . = ALIGN(4); .rel.dyn : { __rel_dyn_start = .; /* 建立一個符號叫 __rel_dyn_start,當成此區段起始點 */ *(.rel*) /* 合併全部輸入檔的 .rel 到這各個區段 */ __rel_dyn_end = .; /* 建立一個符號叫 __rel_dyn_end,當成此區段終點 */ } .dynsym : { __dynsym_start = .; *(.dynsym) } _end = .; .bss __rel_dyn_start (OVERLAY) : { /* 未初始化的全局變數 */ __bss_start = .; *(.bss) . = ALIGN(4); __bss_end__ = .; } /DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) } /DISCARD/ : { *(.gnu*) } }
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。否則要設定週邊設備(peripheral)對應的記憶體位址
- 設定 Cache,此時應該先讓 ICache, DCache, TLB 都失效。
- 設定 MMU,包含 Endian, Address Align Check, 並通知 MMU 不要使用 ICache, DCache。
- 設定CPU內的記憶體 TCM(Tightly-coupled memory),此部分也是可分為 Instruction TCM, Data TCM
執行 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。