Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(1) Boot-Rom與UBoot.
hlchou@mail2000.com.tw
by loda.
本文主要針對Linux Kernel支援ARM MPCore架構下所需的多核心開機流程作一個介紹,所涉及的內容會以筆者認為值得進一步說明的內容為主,從目前市面上的產品來分析,雖然都是針對ARM MPCore的產品,然而這些流程上都還是有所出入,也因此,本文的內容主要是提供實作上的介紹與例子,實際的產品開發,請以所參與的MPCore SoC計畫為主. 其實只要能掌握好ARM處理器的行為,有關MPCore與Linux Kernel SMP的支援相信都是能夠游刃有餘的.
由於筆者時間關係,本文會分段刊登,還請見諒.
Linux Kernel對多核心的支援
Linux從Kernel 2.0開始,就已經加入對SMP (Symmetric Multi Processors)的支援,Linux Kernel會以Process或是Kernel Thread為單位來對排程,也就是說Process或Kernel Thread都有機會會被安排在一個處理器上運作.到了Kernel 2.2時,Linux SMP已經支援UltraSparc, SparcServer, Alpha 與 PowerPC相關處理器. Linux上的Thread是透過 clone的方式產生的,也就是說Thread會共享父Process的資源與記憶體空間.對多核心處理器而言,這些Thread也會有自己的Process Id與Priority,並且會一同參與多核心處理器的多工排程.
在make mnuconfig選項中,選擇 Processor type and features —>Symmetric multi-processing support 就可以開啟Kernel對於多核心的支援.
軟體識別目前所在的處理器
執行時期,軟體可以透過 CPU ID Register知道目前是MPCore中哪個處理器執行該程式碼,CPU Id儲存在CP15 c0中,長度為32bits,只能在特權等級(也就是SVC Mode下)被讀取,
讀取的範例如下程式碼所示
MRC p15,0,
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
SBZ | Cluster ID | SBZ | CPU ID |
說明如下,
1,Cluster ID: 用以支援 Multi-MPCore架構下的Cluster識別之用 (The Cluster ID field value is set by the CLUSTERID configuration pins.)
2,CPU ID: 視處理器的個數,例如四個處理器ID依序為 0×00,0×01,0×02與0×03
多核心的開機
一般我們稱為Boot Loader就是在OS前處理載入流程的動作,通常也稱為Boot Code或是Boot Monitor (ARM本身所出的Boot Loader),由於並非所有的Flash裝置都支援XIP (Execute-in-Place),因此針對像是NAND或是SD/eMMC這類裝置,就會需要有在SoC BootRom上的Boot Code支援對於Block裝置的讀取,以便順利載入第二階段的BootLoader,讓後續的流程如規劃進行.
筆者在整理本文時,有看到這篇文章"Booting ARM Linux SMP on MPCore" (in 或 ),對於Linux Kernel SMP有興趣的讀者除了本文外,建議也可以參考.
由於NAND Flash需要處理 Bad Block,且軟體在作業系統檔案系統本身,針對NAND Flash裝置也需要支援ECC,FTL(Flash Translation Layer)與 Wear-Leveling機制,藉以避免在NAND Flash上的正確性問題,也因此,附帶上述演算法的硬體實現Controller的eMMC也就被目前許多消費性產品所使用,一般而言,在低階的產品上NAND Flash還是有價格上的優勢,而在大容量儲存媒體的消費性產品中,eMMC則會有較高的性價比.
BootRom
在MPCore中,每個ARM的處理器一開始的記憶體位址都是0×00000000,通常我們可以有兩種方式提供啟動程式碼的執行,
1,NOR Flash
2,Boot Rom
由於單位儲存成本NOR Flash較高,因此在需要大儲存空間的產品上,會選擇透過NAND Flash儲存BootLoader與作業系統,因此為了讓系統可以順利的執行開機流程,就會透過晶片上的Boot Rom定位到位址0×00000000,並在其中儲存支援MPCore的程式碼.
在系統尚未啟動前,只有RTC Clock時脈為32.768KHz,而在系統啟動時,在PLL(Phase Locked Loop)起震前,只有Boot Rom或是NOR Flash這類裝置可以用來執行處理器的指令集,因此在Boot Rom或NOR Flash中的程式碼,就必須讓系統的PLL正常,以便可以達到最佳的處理器與平台效能,在系統初始化外部記憶體前,所使用的Stack或是可寫入的記憶體區塊就必須是 OnChip的SRAM,直到外部記憶體被初始化後,才可以使用外部記憶體.
以支援NAND Boot的行為來說,Boot Rom會需要執行以下的行為
1,讓CPU0執行主要開機流程,其它的處理器進入WFI. (在啟動時,每個處理器可以透過CPU ID得知自己是否為CPU0,如果不是,就進入WFI的程式碼中.)
2,初始化外部記憶體與執行系統的初始化
3,設定 Stack
4,把BootRom程式碼複製到外部記憶體中
5,重新Mapping 記憶體位置 (把0×00000000位址對應到外部記憶體 或 I-TCM如果 0×00000000位址要跑中斷表的話(or 中斷表對應到0xffff0000))
6,把第二階段的BootLoader載入到外部記憶體中 or OnChip SRAM.
7,執行第二階段的BootLoader
到這階段為止,系統會維持在低速的運作中(例如 LL=19.2MHz)或是直接初始化PLL到最後的頻率(視所實作的SoC需求而定),外部記憶體也會進行初始化(包括要判斷記憶體的大小與初始化流程),讓第二階段的BootLoader載入到記憶體後可以直接執行.
U-Boot
在NAND或eMMC的方案中,UBoot通常會被Linux的產品定義為第二階段的BootLoader (也因為它所支援的互動命令介面彈性.).
首先,各位取得u-boot-2011.06-rc3版本的UBoot程式碼後,會看到包括如下的Source Code目錄,簡要說明如下
目錄 | 說明 |
api | 提供包括Device Read/Write/Enum, Environment Get/Set/Enum,SysCall,Timers,Storage相關的介面. |
common | 主要為跟硬體與系統架構無關的檔案,包括透過Console控制命令處理與環境變數配置. |
tools | 提供包括GDB,Flash Updater..etc工具. |
lib | 提供CRC,BZLib,MD5,SHA1,軟體除法實作…etc函式庫 |
arch | 為依據不同對應處理器架構與型號相關的底層程式碼,包括處理器arm,avr32,blackfin,m68k,microblaze,mips,nios2,powerpc,sh,sparc與x86.
以arch/arm/cpu配置為例,Cortex處理器的支援是在armv7目錄下,目前支援的Cortex處理器產品包括 TI的 omap3/omap4,Samsung的 s5pc1xx/s5pc2xx,Freescale的 mx5,ST-Ericsson的 u8500 與 Nvidia的 tegra2 , 其他有關的檔案包括 u-boot.lds=>用以描述u-boot binary檔案的配置,以筆者手中的版本來說,在啟動u-boot時,最先執行的為arch/arm/cpu/armv7/start.o 對應到Source Code為 arch/arm/cpu/armv7/start.S,我們可以透過這個檔案為起點,來追蹤u-boot在Cortex下的啟動流程. cpu.c:支援在正式進入Linux前,對L1/L2 Cache的Flush與啟用.以Nvidia Tegra2處理器產品為例,有關的檔案還有 ap20.c:初始化 Cache,UART,PLL,Clock,CoreSight,Snoopy Control Unit(Cache Coherency Bus),啟動JTAG,暫停CPU1的Clock, PMU (Power Management Unit)控制,把CPU進入Reset (Tegra2為雙核), board.c: 確認在板子上的記憶體顆粒大小,支援256,512與1GB RAM Size,初始化DRAM, lowlevel_init.S:初始化I/D-Cache,SMP Mode,支援 CPU/AVP Mode的冷開機(Cold Boot). timer.c:支援 Timer與udelay相關函式. |
board | 主要為現有支援的板子,包括外部記憶體位址,硬體配置與u-boot.lds都會跟這目錄下對應的開發版硬體有關,由於支援的板子數量很多,以Nvidia為例,共支援兩款板子harmony與seaboard |
drivers | UBoot支援豐富的Driver周邊,並且也從Linux Driver中擷取有關的資源,目前共支援以下Drvers種類
(坦白說我覺得UBoot做的有點太強大了,除了沒有多工排程,完整的TCPIP,MM外,其他功能都算是頗有規模了) bios_emulator : 可用以模擬x86 Real-Mode BIOS. fpga : 用以支援 Xilinx,Altera,Virtex, Spartan, Stratix, Cyclon, Lattice的 FPGA開發環境. i2c : 這在有包括 i2c的共用程式碼,以及針對每個平台,例如 TI OMAP,Samsung,ST-Erricsson u8500..etc系列的I2C控制介面 mmc : 用以支援MMC/SD這類記憶卡周邊的控制介面,並包括ARM PL180,Atmel,PXA,OMAP,MX相關處理器的平台. pci : 用以支援PCI Bus,包括IXP,SH,Freescale,Tundra ..etc相關的PCI控制介面 qe : QE 全名為 QUICCQUICC(Quad Integrated Communications Controller) Engine 是一個Freescale 在PowerPC下的介面,QE設計目的在於讓CPU或DSP,不需要去處理通訊端的封包,透過RISC處理器的可程式化通訊協定加速引擎,只要修改微程式碼就能支援不同的通訊協定及功能(包括IP/Ethernet,ATM,QoS). spi : 用以支援 SPI介面,包括 Altera,Atmel,MX,OMAP,SH..etc. video : 用以支援Display的介面,可在Uboot啟動過程中顯示圖像在LCM/LCD上, block : 用以支援 SCSI/MGDISK/IDE/SATA..etc介面的Block儲存媒體 gpio : 用以支援GPIO介面.包括會根據平台的差異,有各自的Base Address與行為. input : 可用以支援Intel 8042/PS2介面的Keyboard與Mouse,在筆者拿到的這版Code中(u-boot-2011.06-rc3),Mouse的部份是被Ignore的. mtd : 為Memory Technology Device的介面,可用以支援包括NAND/OneNAND/SPI/Jedec/CFI(Common Flash Interface)介面的Flash裝置,讓上層不需要去處理不同儲存媒體的差異,只要統一透過MTD介面操作即可. pcmcia : 用以支援PCMCIA介面 rtc : 為 Real-Time Clock Controller 的驅動 twserial : 為一個Serial控制介面,例如 rtc4543 就須透過這個Serial介面進行Read/Write watchdog : 支援 WatchDog (如果你認為所在平台需要的話),目前有FTWDT010與Atmel AT91SAM9x上的實作 dma : 用以支援不同平台的Direct Memory Access Controller. hwmon : 主要支援硬體的感知器,像是Thermometer misc : 用以支援包括LED Service Light或PMIC..etc net :用以支援包括RealTek在內的各類網卡Driver power :用以支援包括Faraday,TI平台的電源控制 serial :用以支援各類UART Serial Port. usb : 用以支援 USB Ethernet,Host/Gadget Controller,提供包括OMAP,BlackFin,..etc相關平台..etc |
post | 全名為 power on self test,其中包括處理器,驅動與對應的Board形式 |
net | 支援NFS,DNS,TFTP,SNTP..etc網路協定 |
fs | 支援CramFS,Ext2,FAT,FDOS,JFFS2,ReiserFS,UbiFS,Yaffs2檔案系統 |
disk | 主要為支援IDE/SCSI/SATA/MGDISK/USB DISK/MMC/SD Card的儲存媒體裝置,讓上層可以透過DISK裝置例如以LBA Mode去存取相關的DISK Sector,包括DISK裝置的Partition Table讀取,或是光碟裝置的ISO檔案,都可以加以識別與存取. |
mmc_spl
onenand_ipl nand_spl |
用以支援存MMC/SD,OneNand或是Nand Flash把UBoot載入到記憶體後,執行UBoot的環境,一般而言,我們可以選擇透過Boot Rom直接載入UBoot,或是透過NAND Flash裝置最前面可以保證出廠時不是Bad Block的區塊,來存放載入UBoot的前置載入程式.可以透過在lds檔案中加入ASSERT (例如:nand_spl/board/freescale/mpc8313erdb/u-boot.lds),確保NAND Flash Bootstrap不會超過目標 NAND Flash裝置第一個區塊的大小. |
Uboot的維護網站在 ,有興趣的開發者,可以自行參閱.在Uboot的Source Code中,根目錄的 config.mk會根據所要編譯的處理器與開發版,把對應目錄的config.mk參考進來,其中像是會定義在board目錄下 config.mk的CONFIG_SYS_TEXT_BASE參數,會決定在Uboot啟動時,要把程式碼複製到哪一個記憶體位置中. 例如:TI OMAP的2420h4處理器,CONFIG_SYS_TEXT_BASE 為0x80e80000 ,定義在board目錄下的檔案board/ti/omap2420h4/config.mk中.不過也有另一種寫法,例如Nvidia Tegra2的CONFIG_SYS_TEXT_BASE為0x00E08000,則是定義在檔案”include/configs/tegra2-common.h”中.
Uboot支援多種 OS的開機, 可以參考檔案common/cmd_bootm.c ,Uboot支援包括Linux,NetBSD,LYNXKDI,RTEMS,OSE,VxWork,QnxElf,INTEGRITY多種OS起始,並支援包括ARM,AVR32,BlackFin,i386,M68K,MicroBlaze,MIPS,NIOS2,PPC,SH,Spare多種處理器.
在最終的產品時,可以透過設定 CONFIG_BOOTDELAY=0,讓啟動過程不用等待,以及設定CONFIG_BOOTCOMMAND = “tftp 8000000 pImage.metrobox;bootm 8000000” (等待來自TFTP的Image,並從記憶體開機) 或 CONFIG_BOOTCOMMAND=”nand read 0×21000000 0xa0000 0×200000; bootm” (讀取來自NAND Flash的Image,並從記憶體開機),如此只要在UBoot啟動時,沒有在Console輸入UBoot 命令(等待<= CONFIG_BOOTDELAY的時間),就會採用上述預設的命令,執行CONFIG_BOOTCOMMAND的內容.
CONFIG_BOOTDELAY設定的單位為秒,也就是說在啟動時會等待所設定的秒數,並且會在函式abortboot中每秒確認100次使用者是否有透過Console按鍵,若有就會由函式abortboot傳回1 (也就是abort =1),此時就會進入互動的介面而不會往後執行Linux Kernel Booting的流程.
會透過環境變數"bootdelay"取得CONFIG_BOOTDELAY設定的值,如果系統等待CONFIG_BOOTDELAY秒後沒有進入互動介面,就會取得環境變數"bootcmd",然後呼叫parse_string_outer執行CONFIG_BOOTCOMMAND的命令內容. (有設定CONFIG_SYS_HUSH_PARSER就會呼叫common/hush.c中的Hush Parser,支援比較有彈性的語法,包括"if…then…else…fi","&&"或"||",反之就會呼叫函式run_command.)
最後透過呼叫do_bootm_linux,載入到記憶體後,x86(in arch/x86/lib/bootm.c)會呼叫函式boot_zimage,ARM(in arch/arm/lib/bootm.c)會呼叫函式kernel_entry ,執行Linux Kernel .
如果是在使用NOR Flash的嵌入式產品中,也可以直接把UBoot編譯到以0×00000000記憶體位址為基礎的環境,然後透過ARM開機時,直接執行,並在執行過程中把Stack與Heap設定到外部或OnChip記憶體中.
common/main.c 中的main_loop為UBoot啟動後,主要處理Console端命令介面的函式,Uboot可以透過把Linux Kernel載入到記憶體後,在透過函式 do_bootm (“bootm – boot application image from image in memory”),去執行該記憶體位址,
先不考慮 Uboot 對NAND/MMC/OneNand載入的實作,我們以從BootRom把UBoot啟動的流程做分析並以Tegra2平台為例,首先我們看在連結時用的arch/arm/cpu/armv7/u-boot.lds檔案,內容如下
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0×00000000;
. = ALIGN(4);
.text :
{
arch/arm/cpu/armv7/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data)
}
. = ALIGN(4);
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__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*) }
}
可以看到在Link時,會把 arch/arm/cpu/armv7/start.o放在TEXT節區的頭,以nvidia Tegra2 seaboard 組態為例來說明,
(in arch/arm/cpu/armv7/start.S)
.globl _start
_start: b 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
我們以實際編譯出來的u-boot.bin來跟著邏輯走一次,以Nvidia Tegra2來說,UBoot會載入到記憶體0x00e08000的位址開始執行.
00e08000 <_start>:
e08000: ea000014 b e08058
e08004: e59ff014 ldr pc, [pc, #20] ; e08020 <_undefined_instruction>
e08008: e59ff014 ldr pc, [pc, #20] ; e08024 <_software_interrupt>
e0800c: e59ff014 ldr pc, [pc, #20] ; e08028 <_prefetch_abort>
e08010: e59ff014 ldr pc, [pc, #20] ; e0802c <_data_abort>
e08014: e59ff014 ldr pc, [pc, #20] ; e08030 <_not_used>
e08018: e59ff014 ldr pc, [pc, #20] ; e08034 <_irq>
e0801c: e59ff014 ldr pc, [pc, #20] ; e08038 <_fiq>
…
之後執行reset函式,並呼叫到檔案 arch/arm/lib/board.c中的函式board_init_f,
00e08058
e08058: e10f0000 mrs r0, CPSR
e0805c: e3c0001f bic r0, r0, #31 ; 0x1f
e08060: e38000d3 orr r0, r0, #211 ; 0xd3
e08064: e129f000 msr CPSR_fc, r0
00e08068
e08068: e59fd3d8 ldr sp, [pc, #984] ; e08448
e0806c: e3cdd007 bic sp, sp, #7 ; 0×7
e08070: e3a00000 mov r0, #0 ; 0×0
e08074: eb0002c1 bl e08b80
在函式board_init_f,可以看到board的啟動順序為
1,配置Global Data “struct global_data”(宣告在include/asm/global_data.h)的內容(包括,記憶體大小,ISR Stack,UBoot起始位置,Timer Clock..etc),以Trgra2為例,會參考CONFIG_SYS_INIT_SP_ADDR 把gd_t配置在記憶體位置0x02bfff80,並初始化為0 (CONFIG_SYS_INIT_SP_ADDR定義在 include/configs/tegra2-common.h).
2, init_sequence 其中包括如下流程 (按照順序,只有紅色部分為一定要的實作,其它為可透過Config參數選擇的)
a,arch_cpu_init
b,board_early_init_f
c,timer_init : 初始化Timer
d,get_clocks
e,env_init
f,init_baudrate
g,serial_init
h,console_init_f
I,display_banner,
j,print_cpuinfo
k,checkboard
l,init_func_i2c
m,dram_init : 包含設定DRAM Size到Global Data中
n,arm_pci_init
3,之後包括設定 irq_sp (給中斷用的Stack),呼叫relocate_code,設定新的Stack,並把程式碼從原本所在的位置搬到外部記憶體從高位址開始,加上FrameBuffer,TLB與相關空間後,預留一塊大小為 _bss_end_ofs (__bss_end__ – _start)的空間,然後在relocate_code函式中複製過去. (BSS為Uninitialized Data Section,沒有給予初值的全域變數就會配置在這個Section.). Relocation Code的流程如下圖所示
4,而我們在編譯階段,會把Text Base以CONFIG_SYS_TEXT_BASE值來設定,也就是說,程式碼的執行Base Address就會是以CONFIG_SYS_TEXT_BASE位址為主,因此在執行程式碼的Relocation後,由於整個程式碼的基礎位址改變了,就會需要把參考到的Symbol相關位置根據新Relocated的位置,來做修正,主要修正的方式為參考.rel.dyn Section中的內容,判斷其中Symbol相依記憶體位置的屬性,如果為
a,fixrel: 就把最後Reolcated的記憶體位址 跟 _TEXT_BASE的Offset,跟目前Symbol的位址相加,就可以修正Related的位址對應.最後修正 .rel.dyn Section中Symbol相依的位址.
b,fixabs:就把_dynsym_start_ofs跟_TEXT_BASE相加,計算出該Symbol的真實位址後,再把最後Reolcated的記憶體位址 跟 _TEXT_BASE的Offset,跟目前Symbol的位址相加.最後修正 .rel.dyn Section中Symbol相依的位址.
執行完上述流程後,就可以把 .rel.dyn Section中Symbol的參考,都對應到最後Reolcated的記憶體位址,確保程式運作的正確性. 參考如下實作程式碼 (in arch/arm/cpu/armv7/start.S)
#ifndef CONFIG_PRELOADER
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
clear_bss:
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0×00000000 /* clear
clbss_l:str r2, [r0] /* clear loop… */
add r0, r0, #4
cmp r0, r1
bne clbss_l
#endif /* #ifndef CONFIG_PRELOADER */
5,呼叫clear_bss,並取 (_bss_start_ofs + Relocated位址) 與 (_bss_end_ofs + Relocated位址)為區間,把該區段記憶體設定為0.
6,進入函式 jump_2_ram,在這會準備呼叫函式board_init_r其中第一個參數為gd_t (Global Data Struct),第二個參數為最後Relocated的記憶體位址
7,進入函式board_init_r (實作在arch/arm/lib/board.c中),
7.a,首先會設定 gd->flags |= GD_FLG_RELOC,表示Relocation 到外部記憶體的動作已經完成.
7.b,呼叫函式board_init,執行每個特定Board所需的初始化流程,
7.c,初始化UART Serial Port,Log Buffer,
7.d,初始化在函式board_init_f中預留在外部記憶體的Malloc記憶體管理空間(大小為TOTAL_MALLOC_LEN,可以參考檔案include/common.h 與 include/configs/tegra2-common.h,在tegra2中該值為CONFIG_SYS_MALLOC_LEN=(4 << 20) =4MB.).
7.e,呼叫flash_init,不過在筆者手中這版本,會對記憶體定址的Flash進行CRC32的計算,但並沒有比對CRC值的正確性與否,…..so…以開機效率而言CONFIG_SYS_FLASH_CHECKSUM選項,應該可以不用打開.
7.f,再來就會,初始化NAND/One-Nand/MMC/ATMEL DataFlash 儲存媒體,以NAND為例,會呼叫在檔案drivers/mtd/nand/nand.c中的函式,nand_init與nand_init_chip,並呼叫到對應開發版根據自己硬體配置所實作的函式board_nand_init,並把初始化完畢的NAND周邊,配置到MTD(Memory Technology Device)的裝置中,
7.g,執行env_relocate (initialize environment),drv_vfd_init ( must do this after the framebuffer is allocated ),執行gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"),取得IP Address,stdio_init ("get the devices list going."), jumptable_init,api_init (Initialize API),console_init_r ("fully init console as a device"), interrupt_init ("set up exceptions"), enable_interrupts("enable exceptions"),針對有支援的網卡進行初始化 (像是網卡SMC91111 or LAN91C96),取得相關環境變數 ”loadaddr” , “bootfile”,與呼叫函式board_late_init,bb_miiphy_init,eth_initialize,reset_phy
8,進入函式main_loop中. (在這支援互動的UBoot命令.)
多核心開機的流程與實現,可以有多種不同的方式,主要還是依據負責的Design Team與做IC架構夥伴的溝通與對於ARM與SoC平台的了解程度為主.
接下來,我們參考Tegra2的實作,了解UBoot在這方案上的多核心支援修改,首先Tegra2 包含了兩個Cortex A9 MPCore處理器,一個 AVP (Audio-Video Processor) ARM7TDMI 處理器,與其他若干處理多媒體與Graphic的處理器單元. 在開機流程中,兩個Cortex A9 與一個ARM7處理器都會有對應的流程,簡要描述如下
1,在函式 board_init_f (in arch/arm/lib/board.c) 中,執行 init_sequence中的函式board_early_init_f(in board/nvidia/common/board.c)後,會呼叫函式tegra2_start (in arch/arm/cpu/armv7/tegra2/ap20.c),之後進入函式cpu_start,由於是第一次啟動,會先進入函式cold_boot (in arch/arm/cpu/armv7/tegra2/lowlevel_init.S).
2,如果判定自己是CPU (在這就是Cortex A9)的話,就會跳到_armboot_start執行 (等於重新執行reset的流程,如果判定自己是AVP (在這就是處理Audio/Video的這顆ARM7),就會繼續往後呼叫函式startup_cpu (in arch/arm/cpu/armv7/tegra2/lowlevel_init.S).
3,會由ARM7呼叫start_cpu (in arch/arm/cpu/armv7/tegra2/ap20.c),用以設定TI PMU(Power Management Unit),並把Cortex A9設定為Reset,與Disable Cortex A9 Clock,並Enable CoreSight,如果是在cold_boot (也就是第一次啟動下),就會設定Cortex A9 CPU執行Reset Vector,之後Enable Cortex A9 CPU#0的Clock,確認是否有透過PMU供電,並讓Cortex A9 CPU#0離開Reset狀態,可以往後繼續執行. (此時,Cortex A9 CPU#1還是維持在 Disable Clock與在Reset的狀態),
4,之後ARM7會呼叫函式halt_avp (in arch/arm/cpu/armv7/tegra2/ap20.c),讓自己進入Busy Loop(for(;;))的暫停狀態中.(設定FLOW_CTLR_HALT_COP_EVENTS)
5,此時,Cortex A9 CPU#0就會重新從 start.S中的reset狀態往後執行,在函式cpu_start中,由於是在ARM7初始化後的執行,此時s_first_boot已經不為1,所以不會呼叫到函式cold_boot,在執行完cache_configure後,就會從函式tegra2_start直接返回,之後就會執行UBoot後續的流程,此時 ARM7 and Cortex A9#1 都是在停止的狀態,只有 Cortex A9#0在執行 Uboot的程式碼.
6,之後,就根據是否有設定BOOTCOMMAND與BOOT_DELAY,來進行我們之前提過的UBoot功能.
運作的概念,可以參考如下圖所示
而實作的機制除了上述Tegra2的例子外,參考ARM的文件,也可以讓除了主要初始化系統的處理器外,讓其它處理器透過WFI Loop的機制也同樣可以達到目的.(其實也相對比較單純一些.). 整體運作的概念如下圖所示
透過 NAND載入 Uboot 的NAND_SPL實作
除了eMMC外,NAND Flash會是主打中低階產品時,可以善加利用的儲存媒體,而UBoot也提供包括NAND在內的前提Boot Loader,主要目的是用以載入UBoot Image要使用Uboot的nand_spl來載入UBoot,我們可以在下載 u-boot-2011.06-rc3版本後,選擇目前有這樣子實作的Samsung SMDK6400環境,執行如下指令,就可以開始編譯流程
make smdk6400_config
make
就會在nand_spl目錄下產生u-boot-spl.bin與u-boot-spl-16k.bin,兩者差異在於後者透過arm-eabi-objcopy時,會加上 - -pad-to 選項,讓工具產生的Image可以對齊4096 bytes的大小,由於筆者所產生的Image大小為3028bytes,所以u-boot-spl.bin大小為3028bytes,而u-boot-spl-16k.bin大小為4096bytes.產生的指令如下所示
arm-eabi-objcopy –gap-fill=0xff -O binary /home/loda/u-boot-2011.06-rc3/nand_spl/u-boot-spl /home/loda/u-boot-2011.06-rc3/nand_spl/u-boot-spl.bin
arm-eabi-objcopy –gap-fill=0xff –pad-to=4096 -O binary /home/loda/u-boot-2011.06-rc3/nand_spl/u-boot-spl /home/loda/u-boot-2011.06-rc3/nand_spl/u-boot-spl-16k.bin
透過 Uboot nand_spl實現UBoot NAND Flash載入機制的運作流程為
1,CPU啟動後,由Boot Rom把NAND Flash第一個Block中的nand_spl的程式碼載入記憶體(第一個Block會保證在一定寫入次數內,都可以正確的讀出.).
2,如果BootRom有初始化外部記憶體,就可以直接載入到外部記憶體中執行,或是載入到OnChip RAM,由nand_spl進行外部記憶體的初始化.
3,跳到nand_spl中執行. 並由nand_spl初始化 處理器,外部記憶體,並會透過函式board_init_f (實作在 nand_spl/board/samsung/smdk6400/smdk6400_nand_spl.c中)把nand_spl本身拷貝到指定的外部記憶體中. (mmmmm,也就是說 Samsung smdk6400的實作本身,就假設在這ARM1176的平台上會有至少大約4kbytes的OnChip RAM).
參考如下程式碼 ( relocate_code第一個參數為addr_sp,第二個參數為addr_gd,地三個參數為 addr_destination)
void board_init_f(unsigned long bootflag)
{
relocate_code(CONFIG_SYS_TEXT_BASE – TOTAL_MALLOC_LEN, NULL,
CONFIG_SYS_TEXT_BASE);
}
在檔案nand_spl/board/samsung/smdk6400/start.S 中,
……
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
……….
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
…..
4,接下來由nand_spl把UBoot本身從NAND Flash中複製到外部記憶體的記憶體位址CONFIG_SYS_NAND_U_BOOT_DST中,並且到記憶體位址CONFIG_SYS_NAND_U_BOOT_START執行UBoot Image.
5,同樣的UBoot Image也會有自己的函式board_init_f執行,並透過 relocate_code函式,可以把自己重定位到編譯UBoot時,指定的CONFIG_SYS_TEXT_BASE 記憶體位址中.同樣的,如果CONFIG_SYS_TEXT_BASE 等於要重定位過去的記憶體位置,重定位的動作就會省略.
如下程式碼所示 (在檔案nand_spl/board/samsung/smdk6400/start.S中)
cmp r0, r6 // r0 = _start , r6=CONFIG_SYS_TEXT_BASE
beq clear_bss /* skip relocation */
此外,如果在nand_spl中已經對CPU與RAM初始化,在UBoot中就不需要重新初始化,可直接進行 bss clear 並執行函式 board_init_r
結語
本文主要著眼於根據ARM MPCore下,Boot Rom與 UBoot的流程與行為介紹,下一篇文章將會針對Linux Kernel在ARM MPCore上SMP啟動流程加以說明.
以目前Android產品來說,ARM MPCore架構幾乎會是未來的主流,除了可以在達到同樣效能的目標下,減少功耗的消耗外,對於多工(Multi-Task)作業系統的效能上,也有很顯著的加分. 對於參與Andriod平台維護的開發者,在這部份系統能力的建立,會是非常重要的一環.
有任何關於技術的討論,都歡迎與我聯繫.