參考:
做者:彭東林
郵箱:pengdonglin137@163.com
1、系統框圖
能夠看到S5P6818一共有兩個cluster,每一個cluster各有4個Cortex-A53架構的core。從官方手冊中說,每一個core都工做在不小於1.4GHz的頻率上,每一個core都有屬於本身的L1 Cache,其中I-Cache和D-Cache各32KB,每一個cluster內部的4個core共享一個大小爲512KB的L2 Cache,外部的CCI-400用於Cache一致性。此外,SoC內部還有一個64KB可讀寫的Internal SRAM和一個20KB只讀的Internal ROM,其中Internal ROM內部固化有芯片廠家的bootrom代碼,Internal SRAM一部分給運行時的bootrom存放.data/.bss/stack,另外一部分給留給第二級bootloader使用,第二級bootloader用於初始化DDR以及從Flash讀取其餘鏡像到DDR中,好比uboot以及ATF鏡像等。
2、Memory Map
目前主要知道以下幾個地址範圍:
Internal ROM: 0x3400_0000 ~ 0x3400_4FFF, 一共20KB
Internal SRAM: 0xFFFF_0000 ~ 0xFFFF_FFFF,一共64KB
3、啓動方式
上圖是S5P6818的啓動源,目前NanoPC T3上支持eMMC、sdcard的啓動方式,若是啓動失敗的話,會從USB啓動,對應的就是上面的 "5=SDMMC",而後經過SD3和VID1[3]來控制使用哪一個port,一個port對應的就是一個SDMMC控制器,這款SoC一共有3個SDMMC控制器,eMMC接在SDMMC2上,sdcard接在SDMMC0上,wifi和bt接在SDMMC1上,使用的是SDIO接口。在原理圖上:
圖中,CAM1_D3就是VID1[3]引腳,當按下圖中的BOOT按鍵是,就是從sdcard啓動,擡起就是從eMMC啓動。
若是從sdcard啓動的話,上電後首先執行的是Internal ROM中的程序(稱之爲iROMBOOT),硬件會自動把Internal ROM從新映射到物理地址0x0000_0000上,而後bootrom中的程序經過檢查bootconfig的配置得知是從SDMMC0啓動,而後將用戶本身的bootcode(第二級bootloader)從sdcard當中讀取出來放入Internal SRAM中相應的位置(0xFFFF_0000)執行,稱之爲SDHCBOOT。
既然用戶本身的Bootcode是被固化在芯片內部的bootrom程序加載的,因此用戶本身的Bootcode在sdcard當中的存放就必須有必定的格式,不然bootrom不認,這個格式稱之爲Boot Header。從上面的圖中,首先咱們應該知道的是User Bootcode應該從sdcard的第1號扇區開始存放,對於sdcard來講,每一個扇區的大小是512byte,其中第0號扇區保留出來給分區表使用,固然對於SDHCBOOT這種啓動方式,不care在sdcard的第0號扇區裏是否有分區表,由於bootrom會直接定位到第1號扇區開始讀取的,讀取最大56KB的大小(實際大小應該是Boot Header中的LOADSIZE,須要後續驗證)到Internal SRAM中。存放位置清楚了,下面就是具體的Boot Header的數據結構,具體請參考S5P6818的芯片手冊的3.4.9 Additional Information。
下面是個人理解:上面是關於Boot Header的說明:若是不是從uart啓動的話,那麼bootrom會檢查第二級bootloader(也就是user boot code)的前512字節(即Boot Header),bootrom會將第二級bootloader的前512字節(即Boot Header)存放到0xFFFF_0000地址上,這個是Internal SRAM的起始地址,而後檢查signature是否爲"NSIH",若是不是的話,就會嘗試下一個啓動源。在Boot Header中LOADSIZE、LOADADDR以及LAUNCHADDR必須有效(16字節對齊),LOADSIZE表示第二級bootloader的大小(給bootrom看的),後兩個分別表示第二級bootloader的加載地址和運行地址(加載地址表示bootrom把第二級bootloader從sdcard讀取出來後,從Internal SRAM的哪一個地址開始存放,而運行地址的意思是,將第二級bootloader所有讀到Internal SRAM後,最後跳轉執行第二級bootloader時須要將PC指針設置爲哪一個地址開始執行),這裏是0xFFFF_0000。若是是從SPI啓動的話,bootrom還會檢查CRC32(文檔上說這部分校驗碼不包含Boot Header,意思是將前512B填充成0,而後計算CRC32,計算結果填充到對應的位置,前面填充0不影響CRC32的校驗結果)。最後PC指針就會跳轉到LAUNCHADDR表示的地址處開始執行,也就是0xFFFF_0000,下面是從sdcard啓動時的Boot Header的格式:
上面是Boot Header的基本格式,其中vector能夠用於存放異常向量表(固然也能夠不這麼幹),文檔中給的例子看,異常向量表是按Aarch32組織的,說明S5P6818這款SoC的上電後bootrom運行在Aarch32狀態。Device Addr表示第二級bootloader從sdcard的哪一個地址(以字節爲單位)上去讀取第三級bootloder。從0x44~0x4C分別表示第二級bootloader的大小,加載地址和運行地址(這兩個地址固定爲0xFFFF_0000),這三個是給bootrom看的。Port Num表示第二級bootloader經過哪一個sdhc port將第三級bootloader讀取進來,CRC32是user bootcode的校驗碼(文檔上說這部分校驗碼不包含Boot Header,意思是將前512B填充成0,而後計算CRC32,計算結果填充到對應的位置)。Stub區域也是留給第二級bootloader本身使用的,下面的excel表格只是一種用法,其中存放了一些時鐘配置和ddr時序配置參數,在第二級bootloader裏會解析這部分,這樣的好處是,不須要修改代碼,若是換了硬件,只須要修改一下Boot Header就好了。最後的signature很是重要。能夠參考https://github.com/SamsungARTIK/bl1-artik710,這份代碼實現了一個第二級bootloader,對理解上面的啓動過程很具備參考意義。
4、64位裸機程序
首先須要認識一下nsih.bin文件,也就是上面說的Boot Header,它佔一個扇區(512B)大小。能夠參考https://github.com/SamsungARTIK/bl1-artik710/blob/artik/nsih-generator/PERIDOT_SYSINFO_Gen_ver03.xls,這個文件用excel表格的方式表示了Boot Header,因爲咱們這裏要折騰的是64位裸機程序,因此在nsih.bin裏須要實現對處理器運行狀態的切換操做,好在前面的excel表格裏已經有這部分操做了,下圖是這個excel表格的DDR3 NSIH64標籤的內容:linux

咱們重點關注上圖中紅框裏的內容:
git
圖中第一列是機器碼,第二列表示的是偏移地址,最後是對應的反彙編代碼,這段反彙編實現了從Aarch32切到Aarch64。根據上面的內容我手動填寫了一個可用的nsih64.bin文件,內容以下:
而後使用下面的命令對其進行反彙編:
arm-none-linux-gnueabi-objdump -D -b binary -m arm nsih64.bin > nsih64.S
nsih64.bin: file format binary
Disassembly of section .data:
00000000 <.data>:
0: e3a00103 mov r0, #-1073741824 ; 0xc0000000
4: e3800a11 orr r0, r0, #69632 ; 0x11000
8: e590113c ldr r1, [r0, #316] ; 0x13c
c: e3811a0f orr r1, r1, #61440 ; 0xf000
10: e580013c str r0, [r0, #316] ; 0x13c
14: e3a025ff mov r2, #1069547520 ; 0x3fc00000
18: e38229ff orr r2, r2, #4177920 ; 0x3fc000
1c: e3822080 orr r2, r2, #128 ; 0x80
20: e5802140 str r2, [r0, #320] ; 0x140
24: e3a08103 mov r8, #-1073741824 ; 0xc0000000
28: e3888801 orr r8, r8, #65536 ; 0x10000
2c: e59892ac ldr r9, [r8, #684] ; 0x2ac
30: e3899001 orr r9, r9, #1
34: e58892ac str r9, [r8, #684] ; 0x2ac
38: e320f003 wfi
3c: eafffffe b 0x3c
...
48: ffff0000 ; <UNDEFINED> instruction: 0xffff0000
4c: ffff0000 ; <UNDEFINED> instruction: 0xffff0000
...
1fc: 4849534e stmdami r9, {r1, r2, r3, r6, r8, r9, ip, lr}^
將上面的代碼轉成C語言就容易理解了:
1 {
2 #define REG32(addr) (*((volatile uint32 *)addr))
3
4 REG32(0xC001113c) |= 0xF000;
5 REG32(0xC0011140) = 0x3FFFC080;
6 REG32(0xC00102AC) |= 0x1;
7 wfi();
8 while(1);
9 }
結合6818的寄存器手冊分析一下:github
第4行,將0xC001113C的[15:12]寫成0xF, 表示將cluster0的四個core都設置爲Aarch64,此時並無生效。這個寄存器的默認值是0,對應的是Aarch32,因此對於S5P6818來講,上電後,cpu默認處於Aarch32模式
第5行,設置復位向量基地址,也就是執行warm reset後,cluster0的core0會從這裏設置的地址上開始運行
這裏須要注意:上面寫入的是0x3FFFC080,結合寄存器,這裏設置的實際上是地址的[33:2],因此最終的地址實際上是(0x3FFFC080<<2) = 0xFFFF0200。
第6行,0xC00102AC寄存器在手冊裏描述的是Reserved,這個寄存器的做用應該是設置warm reset標誌,此時並無執行reset操做
第7行,執行wfi操做,當執行完這條指令後,發現前面設置了warm reset標誌,此時纔會執行真正的warm reset操做。執行warm reset後,cluster的core0就會從0xFFFF0200地址上開始運行,而且此時的運行狀態是Aarch64,這樣就完成了對處理器運行狀態的切換。
這裏爲何不採用eret的方式進行處理器運行狀態切換呢? 由於目前運行在Aarch32,而eret是Aarch64指令,因此只能經過warm reset的方式。
關於處理器執行狀態的切換這部分,能夠參考ARMv8參考手冊D1.20:
關於warm reset能夠參考ARMv8參考手冊D1.9:
至此,咱們已經知道了,在nsih64.bin的開始階段完成了對處理器運行狀態的切換,並且切換後會從0xFFFF0200開始運行。因此咱們須要將裸機程序的入口放到這個地址上。
這裏用到的裸機程序已經上傳到了github上:
下面重點關注以下幾個文件:
連接地址設置的是0xFFFF0000。
上面第23行,表示跳過前512字節,也就是將最終可執行程序的前512字節填充爲0,未來這部分會用nsih64.bin填充,並更新LOADSIZE和CRC32字段(前面填充0不會影響CRC32的校驗值)。這樣的話,第27行的b reset指令正好就位於0xFFFF0200.
void boot_master(void)
{
int i, d = 0;
clrsetbits32(0xc001b020, 3 << 24, 2 << 24);
setbits32(0xc001b004, 1 << 12);
clrsetbits32(0xc001b020, 3 << 22, 2 << 22);
setbits32(0xc001b004, 1 << 11);
tglbits32(0xc001b000, 1 << 11);
while (1) {
for (i = 0; i < 200000; ++i)
d ^= i;
tglbits32(0xc001b000, 1 << 12);
tglbits32(0xc001b000, 1 << 11);
}
}
從github上下載後,進入工程目錄執行make,就會在out目錄下生成以下幾個文件:sass

能夠閱讀Makefile看看這幾個鏡像都是怎麼來的。 這裏大概說明以下:NanoPC-T3.elf文件表示最後編譯生成的elf格式的可執行文件,NanoPC-T3.map文件是NanoPC-T3.elf的地址空間map表,對於分析連接腳本以及每一個成員的空間佔用狀況頗有幫助,NanoPC-T3_nonsih.img是將NanoPC-T3.elf文件用objcopy處理獲得的bin文件,NanoPC-T3.img是用build工具將nsih64.bin跟NanoPC-T3_nonsih.img組裝起來的,同時會更新LOADSIZE和CRC32字段(能夠用beyondcompare比較一下):數據結構

其中,NanoPi_M3.img就是咱們須要燒寫到sdcard中的,燒寫命令以下:架構
dd if=./out/NanoPC-T3.img of=/dev/sdh bs=512 seek=1 conv=fdatasync
注意: 上面的/dev/sdh對應的就是sdcard的節點,sdh後面不太任何數字,表示的是整塊sdcard,從0扇區開始。「bs=512 seek=1」表示跳過第一個512字節,也就是跳過第0個扇區,從第1個扇區開始燒寫。async
燒寫完畢後,在板子上電或者reset時按住BOOT按鍵,此時就會從sdcard啓動,這個裸機程序運行的效果是,板子上的兩個LED燈交替閃爍,下面是原理圖:工具
完。