接下來幾行一直到while循環就是列印公司log,設定led閃爍等操作,難度不大, 不分析.
下面就直接進入bootloader菜單功能分析了. 如下圖所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbwxCdh1mcvZ2LcV2Zh1Wa9M3clN2byBXLzN3btg3Pjd0YxVTeOFGeyMlejRlT5lEVONTWq1Ee4wmT1kkeNpXWq1kdJpHT6FERNhXQq1kdR5mYsFzRhpmRHRGMGJDTwYVbiVHNHpleO1GTwhmMMZ3bENGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
Bootloader共有七個功能, 每一個功能都涉及到很多知識, 比如0号功能usb下載下傳檔案, 就涉及到usb的很多知識,如描述符,枚舉等,每一個都可以專門寫一篇文章來分析了. 我這裡隻分析加載作業系統這個功能. 以 加載wince舉例.用到的函數是void NandLoadRunW(void).
下面就一步一步跟進去這個函數去剖析作業系統的加載機制.
void NandLoadRunW(void)
{
printf("Now boot Wince/n");
ClearMemory();
InitNandFlash();
LoadRun(3);
}
ClearMemory是把wince本身運作用的記憶體空間清0, 我覺得這個操作意義不大,因為我試過不要這個操作,系統運作也沒有問題, 這段記憶體應該會被覆寫的,清空也無意義. 不知道我的說法對不對.
InitNandFlash的實作如下:
static void InitNandFlash(void)
{
U32 i;
InitNandCfg();
i = ReadChipId();
if((i==0x9873)||(i==0xec75))
NandAddr = 0;
else if(i==0xec76)
{
support=1;
NandAddr = 1;
}
else {
puts("Chip id error!!!/n");
return;
}
// printf("Nand flash status = %x/n", ReadStatus());
}
InitNandCfg初始化nandflash寄存器,它的實作非常簡單,就一行.
rNFCONF = (1<<15)|(1<<12)|(1<<11)|(7<<8)|(7<<4)|(7);
操作的結果是使能nand flash 控制器, 初始化ecc校驗.
ReadChipId讀nand flash晶片的ID值. 這塊闆子所用的nand flash型号是K9F1208U0B, 打開它的datasheet , 從下圖我們可以獲得兩個資訊, 一是讀ID的指令流, 二是晶片ID的組成.
ReadChipId的實作,可以參考K9F1208U0B,難度不大,但是比較繁瑣, 不做分析. 另外,根據上圖可知, 隻有0xec76是合法的可識别的ID号.
下面是重頭戲LoadRun函數, 該函數承擔了加載wince的主要工作, 它的實作如下(把和linux相關的代碼去掉了):
static void LoadRun(int part_sel)//part_sel = 1或3.
{
U32 i, ram_addr, buf = 0x30200000;
int size;
StartPage = NandPart[part_sel].offset>>9;
size = NandPart[part_sel].size;
ram_addr = buf;
for(i=0; size>0; )
{
if(!(i&0x1f))
{
if(CheckBadBlk(i+StartPage))
{
printf("Skipped bad block at 0x%x/n", i+StartPage);
i += 32;
size -= 32<<9;
continue;
}
}
ReadPage((i+StartPage), (U8 *)ram_addr);
i++;
size -= 512;
ram_addr += 512;
}
DsNandFlash();
rBWSCON |= 0x0000d000;
rBWSCON &= ~0x00040000;
putch('/n');
call_linux(0, 193, buf);
}
先看下面兩行
StartPage = NandPart[part_sel].offset>>9;
size = NandPart[part_sel].size;
part_sel是傳來的參數, 這裡是3. NandPart的定義如下:
static struct Partition NandPart[] = {
{0, 0x00040000, "bootloader"}, //256K
{0x00040000, 0x001c0000, "zImage"},//0.75M
{0x00200000, 0x01e00000, "cramfs"}, //30M
{0x02000000, 0x02000000, "WinCE"}, //32M.
{0, 0 , 0}
};
很明顯這裡說明, nandFlash被分成了四區,第0個分區是存bootloader, 1,2分區是linux相關的, 3區是放wince映象, 最後一組是一個結束标志. 3區的兩個0x02000000分别表示起始位址和大小(offset和size).這裡表示留給3區的大小為0x02000000(32M), 是以你編譯的wince映象檔案不能大于這個值,否則bootloader将無法加載.
現在有個問題是NandPart[3].offset為什麼要左移9位呢. 要弄清這個問題,得看這個值用在了哪裡.
這個值賦給StartPage, 然後作為參數傳進去了CheckBadBlk函數裡(參見上段LoadRun實作代碼).
CheckBadBlk函數是檢查flash晶片壞塊的, 檢視K9F1208U0B的文檔,我們知道, page的位址是A9~A25, 低9位是列位元組位置辨別, 如下圖所示:
至于CheckBadBlk的實作原理這裡不分析了,K9F1208U0B的文檔裡有詳細的流程說明, 隻說明兩點:
1 flash晶片是允許壞塊存在的, 廠家隻要保證低于某一個壞快量就可以了.
2 一個健壯的程式是應該識别所用晶片的壞塊的.
是以, 很明顯, 上面一段代碼中的for循環是讀整個nand flash的3号分區, 如果讀到壞塊就跳過目前的page繼續讀,否則就據讀到的資訊存放到ram_addr裡(ReadPage((i+StartPage), (U8 *)ram_addr)實作). 而ram_addr指向了0x30200000這個位址. 而這個位址正是wince系統運作所用的RAM的位址(你可以了解為記憶體). 這塊優龍的闆子, SDRAM的大小是64M,起始位址是0x30000000, 結束位址是0x34000000.注意我這裡說的SDRAM位址全部是實體位址, 作業系統都還沒加載,哪裡會有虛拟位址呢?
其實 bootloader存在的真正意義就兩個, 一是初始化CPU相關硬體,如cache,MMU等, 二是完成将作業系統映象檔案加載到RAM中(上段LoadRun的實作代碼中for循環就是做這個事情). 然後,它就失去了存在的價值,不起任何作用了.
繼續看loadRun的實作代碼, 下面三行:
DsNandFlash();
rBWSCON |= 0x0000d000;
rBWSCON &= ~0x00040000;
第一行使nandFlash 無效.
二三兩行的是用來的操作2410的memory data bus width的, 這裡是操作 bank3和bank4, 優龍的闆子, 兩塊SDRAM是接到bank6的, 用到bank3的是cs900(這是一個ethernet 驅動IC), 不明白為什麼要把bank3的設定放在這裡,我估計是要用到ethernet下載下傳調試系統時才會用到,因為我這裡用的是USB進行調試, 應該可以去掉這兩行.
還有一個問題,我們已經把wince的映像加載到RAM中指定的位址了, 得去執行它,否則它怎麼運作. 這就是call_linux的任務了. 它的實作代碼如下:
void call_linux(U32 a0, U32 a1, U32 a2)
{
int i, j;
void (*goto_start)(U32, U32);
cache_clean_invalidate();
tlb_invalidate();
disable_irq();
//If write-back is used,the DCache should be cleared.
for(i=0; i<64; i++)
for(j=0; j<8; j++)
MMU_CleanInvalidateDCacheIndex((i<<26)|(j<<5));
__asm {
mov r0, #0
mcr p15, 0, r0, c7, c10, 4 // drain WB
}
MMU_DisableDCache();
MMU_DisableICache();
MMU_InvalidateICache();
MMU_DisableMMU();
MMU_InvalidateTLB();
goto_start = (void (*)(U32, U32))a2;
(*goto_start)(a0, a1);
}
其實要跳轉指定的位址去執行, 隻要下面三行語句就可以了.
void (*goto_start)(U32, U32);
goto_start = (void (*)(U32, U32))a2;
(*goto_start)(a0, a1);
中間的就是一些關閉MMU和TLB等操作, 這是系統運作對硬體的要求, 這裡主要想說一下對a0, a1這兩個參數的疑問. 表面看起來,這兩個參數是傳給系統核心的, 我在CSND上看到有人問這個問題, googleman回複的文章說是沒什麼用,我寫成下面這種形式測試了一下,
void (*goto_start)(void);
goto_start = (void (*)(void))a2;
(*goto_start)();
Wince啟動也是沒有問題的.這就說明一個問題, 參數肯定不是傳給wince的. 因為我對linux不熟,不知道是不是傳給linux核心的呢?而且call_linux 在usb 的WaitDownload函數也被調用了, 這又是為什麼呢? 另外,為什麼參數是0 和193呢? 希望知道答案的朋友可以不吝賜教,小弟在這裡多謝了.