bl lowlevel_init
ldr r0, =0xE010E81C
ldr r1, =0x00005301
str r1, [r0]
再次開發闆電源置鎖。再次開發闆供電鎖存。第一,做2次是不會錯的;第二,做2次則第2次無意義;做代碼移植時有一個古怪謹慎保守政策就是盡量添加代碼而不要删除代碼。
ldr sp, _TEXT_PHY_BASE
sub sp, sp, #12
mov fp, #0
1)之前在調用lowlevel_init程式前設定過1次棧(start.S 284-287行),那時候因為DDR尚未初始化,是以程式執行都是在SRAM中,是以在SRAM中配置設定了一部分記憶體作為棧。本次因為DDR已經被初始化了,是以要把棧挪移到DDR中,是以要重新設定棧,這是第二次(start.S 297-299行);這裡實際設定的棧的位址是33E00000,剛好在uboot的代碼段的下面緊挨着。
2)為什麼要再次設定棧?DDR已經初始化了,已經有大片記憶體可以用了,沒必要再把棧放在SRAM中可憐兮兮的了;原來SRAM中記憶體大小空間有限,棧放在那裡要注意不能使用過多的棧否則棧會溢出,我們及時将棧遷移到DDR中也是為了盡可能避免棧使用時候的小心翼翼。
感慨:uboot的啟動階段主要技巧就在于小範圍内有限條件下的輾轉騰挪。
ldr r0, =0xff000fff
bic r1, pc, r0
ldr r2, _TEXT_BASE
bic r2, r2, r0
cmp r1, r2
beq after_copy
1)再次用相同的代碼判斷運作位址是在SRAM中還是DDR中,不過本次判斷的目的不同(上次判斷是為了決定是否要執行初始化時鐘和DDR的代碼)這次判斷是為了決定是否進行uboot的relocate。
2)冷啟動時目前情況是uboot的前一部分(16kb或者8kb)開機自動從SD卡加載到SRAM中正在運作,uboot的第二部分(其實第二部分是整個uboot)還躺在SD卡的某個扇區開頭的N個扇區中。此時uboot的第一階段已經即将結束了(第一階段該做的事基本做完了),結束之前要把第二部分加載到DDR中連結位址處(33e00000),這個加載過程就叫重定位。
這裡我們是冷啟動。不跳轉,繼續看。
#if defined(CONFIG_EVT1)
我們在x210_sd.h中定義了。
ldr r0, =0xD0037488
1)D0037488這個記憶體位址在SRAM中,這個位址中的值是被硬體自動設定的。硬體根據我們實際電路中SD卡在哪個通道中,會将這個位址中的值設定為相應的數字。譬如我們從SD0通道啟動時,這個值為EB000000;從SD2通道啟動時,這個值為EB200000
ldr r1, [r0]
ldr r2, =0xEB200000
cmp r1, r2
beq mmcsd_boot
#endif
2)我們在start.S的260行确定了從MMCSD啟動,然後又在278行将#BOOT_MMCSD寫入了INF_REG3寄存器中存儲着。然後又在322行讀出來,再和#BOOT_MMCSD去比較,确定是從MMCSD啟動。最終跳轉到mmcsd_boot函數中去執行重定位動作。
3)真正的重定位是通過調用movi_bl2_copy函數完成的,在uboot/cpu/s5pc11x/movi.c中。是一個C語言的函數
4)copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
分析參數:2表示通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的開始扇區,這個扇區數字必須和燒錄uboot時燒錄的位置相同;MOVI_BL2_BLKCNT是uboot的長度占用的扇區數;CFG_PHY_UBOOT_BASE是重定位時将uboot的第二部分複制到DDR中的起始位址(33E00000).
ldr r0, =INF_REG_BASE
ldr r1, [r0, #INF_REG3_OFFSET]
cmp r1, #BOOT_NAND
beq nand_boot
cmp r1, #BOOT_ONENAND
beq onenand_boot
cmp r1, #BOOT_MMCSD
beq mmcsd_boot
cmp r1, #BOOT_NOR
beq nor_boot
cmp r1, #BOOT_SEC_DEV
beq mmcsd_boot
nand_boot:
mov r0, #0x1000
bl copy_from_nand
b after_copy
onenand_boot:
bl onenand_bl2_copy
b after_copy
mmcsd_boot:
#if DELETE
ldr sp, _TEXT_PHY_BASE
sub sp, sp, #12
mov fp, #0
#endif
bl movi_bl2_copy
1)210啟動首先執行内部的iROM(也就是BL0),BL0會判斷OMpin來決定從哪個裝置啟動,如果啟動裝置是SD卡,則BL0會從SD卡讀取前16KB(不一定是16,反正16是工作的)到SRAM中去啟動執行(這部分就是BL1,這就是steppingstone技術)
2)BL1執行之後剩下的就是軟體的事情了,SoC就不用再去操心了。
SD卡啟動流程(bin檔案小于16KB時和大于16KB時)
1)啟動的第一種情況是整個鏡像大小小于16KB。這時候相當于我的整個鏡像作為BL1被steppingstone直接硬體加載執行了而已。
2)啟動的第二種情況就是整個鏡像大小大于16KB。(隻要大于16KB,哪怕是17KB,或者是700MB都是一樣的)這時候就要把整個鏡像分為2部分:第一部分16KB大小,第二部分是剩下的大小。然後第一部分作為BL1啟動,負責去初始化DRAM并且将第二部分加載到DRAM中去執行(uboot就是這樣做的)。
最重要的但是卻隐含未講的東西
1)問題:iROM究竟是怎樣讀取SD卡/NandFlash的?
2)三星在iROM中事先内置了一些代碼去初始化外部SD卡/NandFlash,并且内置了讀取各種SD卡/NandFlash的代碼在iROM中。BL0執行時就是通過調用這些device copy function來讀取外部SD卡/NandFlash中的BL1的。
SoC支援SD卡啟動的秘密(iROM代碼)
三星系列SoC支援SD卡/NandFlash啟動,主要是依靠SteppingStone技術,具體在S5PV210中支援steppingstone技術的是内部的iROM代碼。
扇區和塊的概念
1)早期的塊裝置就是軟碟硬碟這類磁儲存設備,這種裝置的存儲單元不是以位元組為機關,而是以扇區為機關。磁儲存設備讀寫的最小單元就是扇區,不能隻讀取或寫部分扇區。這個限制是磁儲存設備本身實體方面的原因造成的,也成為了我們程式設計時必須遵守的規律。
2)一個扇區有好多個位元組(一般是512個位元組)。早期的磁盤扇區是512位元組,實際上後來的磁盤扇區可以做的比較大(譬如1024位元組,譬如2048位元組,譬如4096位元組),但是因為原來最早是512位元組,很多的軟體(包括作業系統和檔案系統)已經預設了512這個數字,是以後來的硬體雖然實體上可能支援更大的扇區,但是實際上一般還是相容512位元組扇區這種操作方法。
3)一個扇區可以看成是一個塊block(塊的概念就是:不是一個位元組,是多個位元組組成一個共同的操作單元塊),是以就把這一類的裝置稱為塊裝置。常見的塊裝置有:磁儲存設備硬碟、軟碟、DVD和Flash裝置(U盤、SSD、SD卡、NandFlash、Norflash、eMMC、iNand)
4)linux裡有個mtd驅動,就是用來管理這類塊裝置的。
5)磁盤和Flash以塊為機關來讀寫,就決定了我們啟動時device copy function隻能以整塊為機關來讀取SD卡。
用函數指針方式調用device copy function
1)第一種方法:宏定義方式來調用。好處是簡單友善,壞處是編譯器不能幫我們做參數的靜态類型檢查。
#define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned
int *)0xD0037F98)))(z,a,b,c,e))
2)第二種方法:用函數指針方式來調用。
---------------------------------------- movi_bl2_copy函數講解 ----------------------------------------------------------
typedef u32(*copy_sd_mmc_to_mem) (u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
使用typedef定義了一個函數指針類型的資料類型,資料類型的名字為指針:copy_sd_mmc_to_mem,函數的參數清單為:u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init。函數傳回一個整型。具體的函數是三星官網提供的。
void movi_bl2_copy(void)
{
ulong ch;
#if defined(CONFIG_EVT1)
我們在x210_sd.h中定義了。
ch = *(volatile u32 *)(0xD0037488);
将0xD0037488強制類型轉換成位址,然後取出此位址中的内容,指派給ch。取出的結果是EB200000指派給ch。
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
定義一個函數指針變量copy_bl2,變量的值為(*(u32 *) (0xD0037F98)),為什麼要寫成(*(u32 *) (0xD0037F98))。
我們來分析一下,首先将0Xd0037F98強制類型轉換成u32類型的指針。既然已經是指針為什麼還要加*呢?我們在C語言的函數指針一節中講過,函數名是一個指向自己的指針,函數名就是函數指針,它指向自己。我們定義了函數copy_bl2的位址進行強制指派,我們對函數指針的解引用是(* copy_bl2)(參數清單)。但是我們如果想使用(opy_bl2)(參數清單),把這個*去掉,那就要在定義的時候,放在右值中,這是一種經典的做法。
#if defined(CONFIG_SECURE_BOOT)
ulong rv;
#endif
#else
ch = *(volatile u32 *)(0xD003A508);
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008));
#endif
上面主要是對copy_bl2這個函數指針進行指派,也就是函數位址。
u32 ret;
if (ch == 0xEB000000) {
ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
0xEB000000對應的是0通道。
#define MOVI_BL2_POS ((eFUSE_SIZE / MOVI_BLKSIZE) + MOVI_BL1_BLKCNT + MOVI_ENV_BLKCNT)
#define eFUSE_SIZE (1 * 1024) // 0.5k eFuse, 0.5k reserved
#define MOVI_BLKSIZE (1<<9)
#define MOVI_BL1_BLKCNT (SS_SIZE / MOVI_BLKSIZE)
#define SS_SIZE (8 * 1024)
#define MOVI_ENV_BLKCNT (CFG_ENV_SIZE / MOVI_BLKSIZE)
#define CFG_ENV_SIZE 0x4000
計算一下:MOVI_BL2_POS :2+16+32=50
#define MOVI_BL2_BLKCNT (PART_SIZE_BL / MOVI_BLKSIZE)
#define PART_SIZE_BL (512 * 1024)
MOVI_BL2_BLKCNT:1024
CFG_PHY_UBOOT_BASE:0x33e00000
#if defined(CONFIG_SECURE_BOOT)
沒定義,這個是安全簽名,主要用在安全啟動中。就像windows裡面的數字簽名一樣。
rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
(unsigned char *)CFG_PHY_UBOOT_BASE, (1024*512-128),
(unsigned char *)(CFG_PHY_UBOOT_BASE+(1024*512-128)), 128 );
if (rv != 0){
while(1);
}
#endif
}
else if (ch == 0xEB200000) {
ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
我們使用的2通道,SD卡啟動。
2表示通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的開始扇區,這個扇區數字必須和燒錄uboot時燒錄的位置相同;MOVI_BL2_BLKCNT是uboot的長度占用的扇區數;CFG_PHY_UBOOT_BASE是重定位時将uboot的第二部分複制到DDR中的起始位址(33E00000).
#if defined(CONFIG_SECURE_BOOT)
rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
(unsigned char *)CFG_PHY_UBOOT_BASE, (1024*512-128),
(unsigned char *)(CFG_PHY_UBOOT_BASE+(1024*512-128)), 128 );
if (rv != 0) {
while(1);
}
#endif
}
else
return;
if (ret == 0)
while (1)
判斷下是否搬移完成,如果==0就等待,直至ret = 1;
;
else
return;
}
----------------------------------------------------------------------------------------------------------------------------------
函數執行完成之後,因為使用的是bl,是以傳回位址還是這個copy函數,繼續執行後面的。
b after_copy
使用的是b,這個after_copy函數是空函數,不用管。
nor_boot:
bl read_hword
b after_copy
after_copy: