Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(1) Boot-Rom與UBoot.

本文主要針對Linux Kernel支援ARM MPCore架構下所需的多核心開機流程作一個介紹,所涉及的內容會以筆者認為值得進一步說明的內容為主,從目前市面上的產品來分析,雖然都是針對ARM MPCore的產品,然而這些流程上都還是有所出入,也因此,本文的內容主要是提供實作上的介紹與例子,實際的產品開發,請以所參與的MPCore SoC計畫為主. 其實只要能掌握好ARM處理器的行為,有關MPCoreLinux Kernel SMP的支援相信都是能夠遊刃有餘的.

由於筆者時間關係,本文會分段刊登,還請見諒.

Linux Kernel對多核心的支援

LinuxKernel 2.0開始,就已經加入對SMP (Symmetric Multi Processors)的支援,Linux Kernel會以Process或是Kernel Thread為單位來對排程,也就是說ProcessKernel Thread都有機會會被安排在一個處理器上運作.到了Kernel 2.2,Linux SMP已經支援UltraSparc, SparcServer, Alpha 與 PowerPC相關處理器. Linux上的Thread是透過 clone的方式產生的,也就是說Thread會共享父Process的資源與記憶體空間.對多核心處理器而言,這些Thread也會有自己的Process IdPriority,並且會一同參與多核心處理器的多工排程.

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

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×020×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 http://geek43.springnote.com/pages/7909760?print=1 http://www.linux-arm.org/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上的正確性問題,也因此,附帶上述演算法的硬體實現ControllereMMC也就被目前許多消費性產品所使用,一般而言,在低階的產品上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 RomNOR Flash中的程式碼,就必須讓系統的PLL正常,以便可以達到最佳的處理器與平臺效能,在系統初始化外部記憶體前,所使用的Stack或是可寫入的記憶體區塊就必須是 OnChipSRAM,直到外部記憶體被初始化後,纔可以使用外部記憶體.

以支援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

到這階段為止,系統會維持在低速的運作中(例如 :P LL=19.2MHz)或是直接初始化PLL到最後的頻率(視所實作的SoC需求而定),外部記憶體也會進行初始化(包括要判斷記憶體的大小與初始化流程),讓第二階段的BootLoader載入到記憶體後可以直接執行.

U-Boot

NANDeMMC的方案中,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,sparcx86.

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-bootCortex下的啟動流程.

cpu.c:支援在正式進入Linux,L1/L2 CacheFlush與啟用.Nvidia Tegra2處理器產品為例,有關的檔案還有

ap20.c:初始化 Cache,UART,PLL,Clock,CoreSight,Snoopy Control Unit(Cache Coherency Bus),啟動JTAG,暫停CPU1Clock, PMU (Power Management Unit)控制,CPU進入Reset (Tegra2為雙核),

board.c: 確認在板子上的記憶體顆粒大小,支援256,5121GB RAM Size,初始化DRAM,

lowlevel_init.S:初始化I/D-Cache,SMP Mode,支援 CPU/AVP Mode的冷開機(Cold Boot).

timer.c:支援 Timerudelay相關函式.

board 主要為現有支援的板子,包括外部記憶體位址,硬體配置與u-boot.lds都會跟這目錄下對應的開發版硬體有關,由於支援的板子數量很多,Nvidia為例,共支援兩款板子harmonyseaboard
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設計目的在於讓CPUDSP,不需要去處理通訊端的封包,透過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介面的KeyboardMouse,在筆者拿到的這版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 (如果你認為所在平臺需要的話),目前有FTWDT010Atmel AT91SAM9x上的實作

dma : 用以支援不同平臺的Direct Memory Access Controller.

hwmon : 主要支援硬體的感知器,像是Thermometer

misc : 用以支援包括LED Service LightPMIC..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 FlashUBoot載入到記憶體後,執行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的維護網站在 http://www.denx.de/wiki/U-Boot ,有興趣的開發者,可以自行參閱.UbootSource Code,根目錄的 config.mk會根據所要編譯的處理器與開發版,把對應目錄的config.mk參考進來,其中像是會定義在board目錄下 config.mkCONFIG_SYS_TEXT_BASE參數,會決定在Uboot啟動時,要把程式碼複製到哪一個記憶體位置中. 例如:TI OMAP2420h4處理器,CONFIG_SYS_TEXT_BASE 0x80e80000 ,定義在board目錄下的檔案board/ti/omap2420h4/config.mk.不過也有另一種寫法,例如Nvidia Tegra2CONFIG_SYS_TEXT_BASE0x00E08000,則是定義在檔案”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” (等待來自TFTPImage,並從記憶體開機) 或 CONFIG_BOOTCOMMAND=”nand read 0×21000000 0xa0000 0×200000; bootm” (讀取來自NAND FlashImage,並從記憶體開機),如此只要在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開機時,直接執行,並在執行過程中把StackHeap設定到外部或OnChip記憶體中.

common/main.c 中的main_loopUBoot啟動後,主要處理Console端命令介面的函式,Uboot可以透過把Linux Kernel載入到記憶體後,在透過函式 do_bootm (“bootm – boot application image from image in memory”),去執行該記憶體位址,

先不考慮 Uboot NAND/MMC/OneNand載入的實作,我們以從BootRomUBoot啟動的流程做分析並以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 <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+0×48>

e0806c: e3cdd007 bic sp, sp, #7 ; 0×7

e08070: e3a00000 mov r0, #0 ; 0×0

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 SizeGlobal Data

n,arm_pci_init

3,之後包括設定 irq_sp (給中斷用的Stack),呼叫relocate_code,設定新的Stack,並把程式碼從原本所在的位置搬到外部記憶體從高位址開始,加上FrameBuffer,TLB與相關空間後,預留一塊大小為 _bss_end_ofs (__bss_end__ – _start)的空間,然後在relocate_code函式中複製過去. (BSSUninitialized Data Section,沒有給予初值的全域變數就會配置在這個Section.). Relocation Code的流程如下圖所示

4,而我們在編譯階段,會把Text BaseCONFIG_SYS_TEXT_BASE值來設定,也就是說,程式碼的執行Base Address就會是以CONFIG_SYS_TEXT_BASE位址為主,因此在執行程式碼的Relocation,由於整個程式碼的基礎位址改變了,就會需要把參考到的Symbol相關位置根據新Relocated的位置,來做修正,主要修正的方式為參考.rel.dyn Section中的內容,判斷其中Symbol相依記憶體位置的屬性,如果為

a,fixrel: 就把最後Reolcated的記憶體位址 跟 _TEXT_BASEOffset,跟目前Symbol的位址相加,就可以修正Related的位址對應.最後修正 .rel.dyn SectionSymbol相依的位址.

b,fixabs:就把_dynsym_start_ofs_TEXT_BASE相加,計算出該Symbol的真實位址後,再把最後Reolcated的記憶體位址 跟 _TEXT_BASEOffset,跟目前Symbol的位址相加.最後修正 .rel.dyn SectionSymbol相依的位址.

執行完上述流程後,就可以把 .rel.dyn SectionSymbol的參考,都對應到最後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_initnand_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架構夥伴的溝通與對於ARMSoC平臺的瞭解程度為主.

接下來,我們參考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#0Clock,確認是否有透過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,之後,就根據是否有設定BOOTCOMMANDBOOT_DELAY,來進行我們之前提過的UBoot功能.

運作的概念,可以參考如下圖所示

而實作的機制除了上述Tegra2的例子外,參考ARM的文件,也可以讓除了主要初始化系統的處理器外,讓其它處理器透過WFI Loop的機制也同樣可以達到目的.(其實也相對比較單純一些.). 整體運作的概念如下圖所示

透過 NAND載入 Uboot NAND_SPL實作

除了eMMC,NAND Flash會是主打中低階產品時,可以善加利用的儲存媒體,UBoot也提供包括NAND在內的前提Boot Loader,主要目的是用以載入UBoot Image要使用Ubootnand_spl來載入UBoot,我們可以在下載 u-boot-2011.06-rc3版本後,選擇目前有這樣子實作的Samsung SMDK6400環境,執行如下指令,就可以開始編譯流程

make smdk6400_config

make

就會在nand_spl目錄下產生u-boot-spl.binu-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 RomNAND 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的平臺上會有至少大約4kbytesOnChip 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_splUBoot本身從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中已經對CPURAM初始化,UBoot中就不需要重新初始化,可直接進行 bss clear 並執行函式 board_init_r

結語

本文主要著眼於根據ARM MPCore,Boot Rom與 UBoot的流程與行為介紹,下一篇文章將會針對Linux KernelARM MPCoreSMP啟動流程加以說明.

以目前Android產品來說,ARM MPCore架構幾乎會是未來的主流,除了可以在達到同樣效能的目標下,減少功耗的消耗外,對於多工(Multi-Task)作業系統的效能上,也有很顯著的加分. 對於參與Andriod平臺維護的開發者,在這部份系統能力的建立,會是非常重要的一環.

有任何關於技術的討論,都歡迎與我聯繫.

People who looked at this item also looked at… Related items
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章