本文主要針對Linux Kernel支援ARM MPCore架構下所需的多核心開機流程作一個介紹,所涉及的內容會以筆者認為值得進一步說明的內容為主,從目前市面上的產品來分析,雖然都是針對ARM MPCore的產品,然而這些流程上都還是有所出入,也因此,本文的內容主要是提供實作上的介紹與例子,實際的產品開發,請以所參與的MPCore SoC計畫為主.
由於筆者時間關係,本文會分段刊登,還請見諒.
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
在make mnuconfig選項中,選擇
軟體識別目前所在的處理器
執行時期,軟體可以透過
讀取的範例如下程式碼所示
MRC p15,0,,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:
2,CPU ID:
多核心的開機
一般我們稱為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
由於NAND Flash需要處理
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或是可寫入的記憶體區塊就必須是
以支援NAND Boot的行為來說,Boot Rom會需要執行以下的行為
1,讓CPU0執行主要開機流程,其它的處理器進入WFI. (在啟動時,每個處理器可以透過CPU ID得知自己是否為CPU0,如果不是,就進入WFI的程式碼中.)
2,初始化外部記憶體與執行系統的初始化
3,設定
4,把BootRom程式碼複製到外部記憶體中
5,重新Mapping
6,把第二階段的BootLoader載入到外部記憶體中
7,執行第二階段的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處理器產品包括 其他有關的檔案包括 u-boot.lds=>用以描述u-boot binary檔案的配置,以筆者手中的版本來說,在啟動u-boot時,最先執行的為arch/arm/cpu/armv7/start.o 對應到Source Code為 cpu.c:支援在正式進入Linux前,對L1/L2 Cache的Flush與啟用.以Nvidia Tegra2處理器產品為例,有關的檔案還有 ap20.c:初始化 board.c: lowlevel_init.S:初始化I/D-Cache,SMP Mode,支援 timer.c:支援 |
board | 主要為現有支援的板子,包括外部記憶體位址,硬體配置與u-boot.lds都會跟這目錄下對應的開發版硬體有關,由於支援的板子數量很多,以Nvidia為例,共支援兩款板子harmony與seaboard |
drivers | UBoot支援豐富的Driver周邊,並且也從Linux Driver中擷取有關的資源,目前共支援以下Drvers種類 (坦白說我覺得UBoot做的有點太強大了,除了沒有多工排程,完整的TCPIP,MM外,其他功能都算是頗有規模了) bios_emulator : fpga : i2c : mmc : pci : qe : QE spi : video : block : gpio : input : mtd : pcmcia : rtc : twserial : watchdog : dma : hwmon : misc : net :用以支援包括RealTek在內的各類網卡Driver power :用以支援包括Faraday,TI平台的電源控制 serial :用以支援各類UART Serial Port. usb : |
post | 全名為 |
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不會超過目標 |
Uboot的維護網站在
Uboot支援多種
在最終的產品時,可以透過設定
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
先不考慮
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時,會把
(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函式,並呼叫到檔案
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
2, init_sequence
a,arch_cpu_init
b,board_early_init_f
c,timer_init
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
n,arm_pci_init
3,之後包括設定
4,而我們在編譯階段,會把Text Base以CONFIG_SYS_TEXT_BASE值來設定,也就是說,程式碼的執行Base Address就會是以CONFIG_SYS_TEXT_BASE位址為主,因此在執行程式碼的Relocation後,由於整個程式碼的基礎位址改變了,就會需要把參考到的Symbol相關位置根據新Relocated的位置,來做修正,主要修正的方式為參考.rel.dyn Section中的內容,判斷其中Symbol相依記憶體位置的屬性,如果為
a,fixrel:
b,fixabs:就把_dynsym_start_ofs跟_TEXT_BASE相加,計算出該Symbol的真實位址後,再把最後Reolcated的記憶體位址 跟
執行完上述流程後,就可以把
#ifndef CONFIG_PRELOADER
ldr r0, _TEXT_BASE
sub r9, r6, r0
ldr r10, _dynsym_start_ofs
add r10, r10, r0
ldr r2, _rel_dyn_start_ofs
add r2, r2, r0
ldr r3, _rel_dyn_end_ofs
add r3, r3, r0
fixloop:
ldr r0, [r2]
add r0, r0, r9
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23
beq fixrel
cmp r7, #2
beq fixabs
b fixnext
fixabs:
mov r1, r1, LSR #4
add r1, r10, r1
ldr r1, [r1, #4]
add r1, r1, r9
b fixnext
fixrel:
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8
cmp r2, r3
blo fixloop
clear_bss:
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
mov r4, r6
add r0, r0, r4
add r1, r1, r4
mov r2, #0×00000000
add r0, r0, #4
cmp r0, r1
bne clbss_l
#endif
5,呼叫clear_bss,並取
6,進入函式
7,進入函式board_init_r (實作在arch/arm/lib/board.c中),
7.a,首先會設定
7.b,呼叫函式board_init,執行每個特定Board所需的初始化流程,
7.c,初始化UART Serial Port,Log Buffer,
7.d,初始化在函式board_init_f中預留在外部記憶體的Malloc記憶體管理空間(大小為TOTAL_MALLOC_LEN,可以參考檔案include/common.h與
7.e,呼叫flash_init,不過在筆者手中這版本,會對記憶體定址的Flash進行CRC32的計算,但並沒有比對CRC值的正確性與否,…..so…以開機效率而言CONFIG_SYS_FLASH_CHECKSUM選項,應該可以不用打開.
7.f,再來就會,初始化NAND/One-Nand/MMC/ATMEL DataFlash
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"),針對有支援的網卡進行初始化
8,進入函式main_loop中. (在這支援互動的UBoot命令.)
多核心開機的流程與實現,可以有多種不同的方式,主要還是依據負責的Design Team與做IC架構夥伴的溝通與對於ARM與SoC平台的了解程度為主.
接下來,我們參考Tegra2的實作,了解UBoot在這方案上的多核心支援修改,首先Tegra2
1,在函式
2,如果判定自己是CPU (在這就是Cortex A9)的話,就會跳到_armboot_start執行
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還是維持在
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就會重新從
6,之後,就根據是否有設定BOOTCOMMAND與BOOT_DELAY,來進行我們之前提過的UBoot功能.
運作的概念,可以參考如下圖所示
而實作的機制除了上述Tegra2的例子外,參考ARM的文件,也可以讓除了主要初始化系統的處理器外,讓其它處理器透過WFI Loop的機制也同樣可以達到目的.(其實也相對比較單純一些.).
透過
除了eMMC外,NAND Flash會是主打中低階產品時,可以善加利用的儲存媒體,而UBoot也提供包括NAND在內的前提Boot Loader,主要目的是用以載入UBoot Image要使用Uboot的nand_spl來載入UBoot,我們可以在下載
make smdk6400_config
make
就會在nand_spl目錄下產生u-boot-spl.bin與u-boot-spl-16k.bin,兩者差異在於後者透過arm-eabi-objcopy時,會加上
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
透過
1,CPU啟動後,由Boot Rom把NAND Flash第一個Block中的nand_spl的程式碼載入記憶體(第一個Block會保證在一定寫入次數內,都可以正確的讀出.).
2,如果BootRom有初始化外部記憶體,就可以直接載入到外部記憶體中執行,或是載入到OnChip RAM,由nand_spl進行外部記憶體的初始化.
3,跳到nand_spl中執行.
參考如下程式碼
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
mov r5, r1
mov r6, r2
……….
copy_loop:
ldmia r0!, {r9-r10}
stmia r1!, {r9-r10}
cmp r0, 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執行,並透過
如下程式碼所示
cmp r0, r6 // r0 = _start , r6=CONFIG_SYS_TEXT_BASE
beq clear_bss
此外,如果在nand_spl中已經對CPU與RAM初始化,在UBoot中就不需要重新初始化,可直接進行
結語
本文主要著眼於根據ARM MPCore下,Boot Rom與
以目前Android產品來說,ARM MPCore架構幾乎會是未來的主流,除了可以在達到同樣效能的目標下,減少功耗的消耗外,對於多工(Multi-Task)作業系統的效能上,也有很顯著的加分.
有任何關於技術的討論,都歡迎與我聯繫.