天天看點

2410 bootloader分析(二)

接下來幾行一直到while循環就是列印公司log,設定led閃爍等操作,難度不大, 不分析.

下面就直接進入bootloader菜單功能分析了. 如下圖所示:

2410 bootloader分析(二)

 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的組成.

2410 bootloader分析(二)

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位是列位元組位置辨別, 如下圖所示:

2410 bootloader分析(二)

至于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呢? 希望知道答案的朋友可以不吝賜教,小弟在這裡多謝了.

繼續閱讀