NanoPC-T3 64位裸機程式設計 —— 啟動和執行狀態切換
阿新 • • 發佈:2019-05-28
參考:
https://github.com/metro94/s5p6818_spl
https://github.com/trebisky/Fire3/tree/master/Boot_NSIH
https://github.com/SamsungARTIK/bl1-artik710
https://github.com/SamsungARTIK/bl1-artik710/blob/artik/nsih-generator/PERIDOT_SYSINFO_Gen_ver03.xls
作者:彭東林
郵箱:[email protected]
Internal ROM: 0x3400_0000 ~ 0x3400_4FFF, 一共20KB Internal SRAM: 0xFFFF_0000 ~ 0xFFFF_FFFF,一共64KB
既然使用者自己的Bootcode是被固化在晶片內部的bootrom程式載入的,所以使用者自己的Bootcode在sdcard當中的存放就必須有一定的格式,否則bootrom不認,這個格式稱之為Boot Header。從上面的圖中,首先我們應該知道的是User Bootcode應該從sdcard的第1號扇區開始存放,對於sdcard來說,每個扇區的大小是512byte,其中第0號扇區保留出來給分割槽表使用,當然對於SDHCBOOT這種啟動方式,不care在sdcard的第0號扇區裡是否有分割槽表,因為bootrom是直接定位到第1號扇區開始讀取的,讀取56KB的大小,也就是112個扇區。存放位置清楚了,下面就是具體的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的哪個地址上,而執行地址的意思是,讀到Internal SRAM之後,最後執行跳轉操作時需要將PC指標設定為哪個地址),也就是0xFFFF_0000。如果是從SPI啟動的話,bootrom還會檢查CRC32(文件上說這部分校驗碼不包含Boot Header,意思是將前512B填充成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,對理解上面的啟動過程很具有參考意義。
第5行,設定復位向量基地址,也就是執行warm reset後,cluster0的core0會從這裡設定的地址上開始執行 這裡需要注意:上面寫入的是0x3FFFC080,結合暫存器,這裡設定的其實是地址的[33:2],所以最終的地址其實是(0x3FFC080<<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上: https://github.com/pengdonglin137/s5p6818_bare_metal 下面重點關注如下幾個檔案:
郵箱:[email protected]
一、系統框圖
可以看到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映象等。二、Memory Map
目前主要知道如下幾個地址範圍:Internal ROM: 0x3400_0000 ~ 0x3400_4FFF, 一共20KB Internal SRAM: 0xFFFF_0000 ~ 0xFFFF_FFFF,一共64KB
三、啟動方式
- 原理圖
- 從sdcard啟動
既然使用者自己的Bootcode是被固化在晶片內部的bootrom程式載入的,所以使用者自己的Bootcode在sdcard當中的存放就必須有一定的格式,否則bootrom不認,這個格式稱之為Boot Header。從上面的圖中,首先我們應該知道的是User Bootcode應該從sdcard的第1號扇區開始存放,對於sdcard來說,每個扇區的大小是512byte,其中第0號扇區保留出來給分割槽表使用,當然對於SDHCBOOT這種啟動方式,不care在sdcard的第0號扇區裡是否有分割槽表,因為bootrom是直接定位到第1號扇區開始讀取的,讀取56KB的大小,也就是112個扇區。存放位置清楚了,下面就是具體的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的哪個地址上,而執行地址的意思是,讀到Internal SRAM之後,最後執行跳轉操作時需要將PC指標設定為哪個地址),也就是0xFFFF_0000。如果是從SPI啟動的話,bootrom還會檢查CRC32(文件上說這部分校驗碼不包含Boot Header,意思是將前512B填充成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,對理解上面的啟動過程很具有參考意義。
四、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標籤的內容:
我們重點關注上圖中紅框裡的內容:
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) = 0x3FFC080; 6 REG32(0xC00102AC) |= 0x1; 7 wfi(); 8 while(1); 9 }
結合6818的暫存器手冊分析一下:
第4行,將0xC001113C的[15:12]寫成0xF, 表示將cluster0的四個core都設定為Aarch64,此時並沒有生效。這個暫存器的預設值是0,對應的是Aarch32,所以對於S5P6818來說,上電後,cpu預設處於Aarch32模式第5行,設定復位向量基地址,也就是執行warm reset後,cluster0的core0會從這裡設定的地址上開始執行 這裡需要注意:上面寫入的是0x3FFFC080,結合暫存器,這裡設定的其實是地址的[33:2],所以最終的地址其實是(0x3FFC080<<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上: https://github.com/pengdonglin137/s5p6818_bare_metal 下面重點關注如下幾個檔案:
- 連結指令碼spl.lds
- start.S
- boot.c
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); } }
這個裸機程式執行的效果是,板子上的兩個LED燈交替閃爍,下面是原理圖: 完。 &nbs