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


hlchou@mail2000.com.tw

by loda.


Loda's Blog

App BizOrz


Android/Linux Source Code Tags
App BizOrz
BizOrz.COM
BizOrz Blog


本文主要針對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, AlphaPowerPC相關處理器. 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依序為0x00,0x01,0x020x03


多核心的開機


一般我們稱為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" (inhttp://geek43.springnote.com/pages/7909760?print=1http://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的處理器一開始的記憶體位址都是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 RomNOR Flash中的程式碼,就必須讓系統的PLL正常,以便可以達到最佳的處理器與平台效能,在系統初始化外部記憶體前,所使用的Stack或是可寫入的記憶體區塊就必須是OnChipSRAM,直到外部記憶體被初始化後,才可以使用外部記憶體.


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


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處理器產品包括TIomap3/omap4,Samsungs5pc1xx/s5pc2xx,Freescalemx5,ST-Ericssonu8500Nvidiategra2 ,

其他有關的檔案包括

u-boot.lds=>用以描述u-boot binary檔案的配置,以筆者手中的版本來說,在啟動u-boot,最先執行的為arch/arm/cpu/armv7/start.o

對應到Source Codearch/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, LatticeFPGA開發環境.

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是一個FreescalePowerPC下的介面,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_BASE0x80e80000 ,定義在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 0x21000000 0xa0000 0x200000; 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編譯到以0x00000000記憶體位址為基礎的環境,然後透過ARM開機時,直接執行,並在執行過程中把StackHeap設定到外部或OnChip記憶體中.


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


先不考慮UbootNAND/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

{

. = 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_ADDRgd_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, #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.hinclude/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載入UbootNAND_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 RomUBoot的流程與行為介紹,下一篇文章將會針對Linux KernelARM MPCoreSMP啟動流程加以說明.


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


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


更多相关文章

  1. what is Android?
  2. Android(安卓)TextToSpeech语音播放文本
  3. [cocos2d-x 学习] Scene(场景)学习
  4. Android学习--Android带删除按钮的ListView
  5. 说说在 Android(安卓)如何在全局获取 Context
  6. Android中添加布局和初始化布局总结
  7. ActivityGroup + ViewPager 实现可滑动的底部Tab
  8. Android初始化语言 (init.*.rc、init.conf文件格式)
  9. android appwidget service的初始化

随机推荐

  1. Android中Display及DisplayMetrics理解
  2. 系出名门Android(8) - 控件(View)之TextS
  3. ImageView的属性android:scaleType作用
  4. Android--Android主流网络框架
  5. 高手速成android开源项目【developer篇】
  6. Android(安卓)apk的安装、卸载、更新升级
  7. android-HandlerThread、IntentServer
  8. android 扫雷游戏
  9. 为Android加入busybox工具
  10. [置顶] 我的Android进阶之旅------>Ubunt