Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(1) Boot-Rom與UBoot.
hlchou@mail2000.com.tw by loda. Loda's Blog
本文主要針對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,<Rd>,c0,c0,5; returns CPU ID register
說明如下,
1,Cluster ID: 用以支援 Multi-MPCore架構下的Cluster識別之用 (The Cluster ID field value is set by the CLUSTERID configuration pins.)
2,CPU ID: 視處理器的個數,例如四個處理器ID依序為 0x00,0x01,0x02與0x03
多核心的開機
一般我們稱為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 http://geek43./pages/7909760?print=1 或 http://www./pub/LinuxPlatform/RealViewLink/Booting_ARM_Linux_SMP_on_MPCore.doc),對於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的處理器一開始的記憶體位址都是0x00000000,通常我們可以有兩種方式提供啟動程式碼的執行, 1,NOR Flash 2,Boot Rom 由於單位儲存成本NOR Flash較高,因此在需要大儲存空間的產品上,會選擇透過NAND Flash儲存BootLoader與作業系統,因此為了讓系統可以順利的執行開機流程,就會透過晶片上的Boot Rom定位到位址0x00000000,並在其中儲存支援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 記憶體位置 (把0x00000000位址對應到外部記憶體 或 I-TCM如果 0x00000000位址要跑中斷表的話(or 中斷表對應到0xffff0000)) 6,把第二階段的BootLoader載入到外部記憶體中 or OnChip SRAM. 7,執行第二階段的BootLoader
到這階段為止,系統會維持在低速的運作中(例如:PLL=19.2MHz)或是直接初始化PLL到最後的頻率(視所實作的SoC需求而定),外部記憶體也會進行初始化(包括要判斷記憶體的大小與初始化流程),讓第二階段的BootLoader載入到記憶體後可以直接執行.
U-Boot
在NAND或eMMC的方案中,UBoot通常會被Linux的產品定義為第二階段的BootLoader (也因為它所支援的互動命令介面彈性.).
首先,各位取得u-boot-2011.06-rc3版本的UBoot程式碼後,會看到包括如下的Source Code目錄,簡要說明如下
Uboot的維護網站在 http://www./wiki/U-Boot ,有興趣的開發者,可以自行參閱.在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 0x21000000 0xa0000 0x200000; 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編譯到以0x00000000記憶體位址為基礎的環境,然後透過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 { . = 0x00000000;
. = 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 <reset> 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 <reset>: 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 <call_board_init_f>: e08068: e59fd3d8 ldr sp, [pc, #984] ; e08448 <fiq+0x48> e0806c: e3cdd007 bic sp, sp, #7 ; 0x7 e08070: e3a00000 mov r0, #0 ; 0x0 e08074: eb0002c1 bl e08b80 <board_init_f>
在函式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, #0x00000000 /* 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平台維護的開發者,在這部份系統能力的建立,會是非常重要的一環.
有任何關於技術的討論,都歡迎與我聯繫.
|
|