2009年6月26日 星期五

vivi stage3




step 5:



    MTD設備初始化。


    關於什麼是MTD,為什麼要使用MTD,MTD技術的架構是什麼,等等,可以參考《Linux MTD原始碼分析》。


    vivi採用Linux kernel的架構,所以把Linux kernel的MTD子系統借用過來了,做了一些縮減。可以簡單地看成:Flash硬件驅動層和MTD設備層。這樣,最後統一的接口向vivi提供。


    還是以nand flash啟動這個情景為主線,對MTD初始化流程進行分析。下面先從入口開始。


ret = mtd_dev_init();


    利用source insight跟蹤,看一下此函數的接口定義部分:


/*
 * VIVI Interfaces
 */
#ifdef CONFIG_MTD
int write_to_flash(loff_t ofs, size_t len, const u_char *buf, int flag);
int mtd_dev_init(void);
#else
#define write_to_flash(a, b, c, d) (int)(1)
#define mtd_dev_init() (int)(1)
#endif



    可見,vivi在配置的時候是必須配置MTD功能部分的。如果不配置MTD,那麼CONFIG_MTD就不存在定義。由此導致寫flash的動作實際上是沒有的。也就是說,無法完成寫flash的動作。當然,在這裡可以做測試,就是使用MTD子系統的vivi把分區等都設置好。然後重新編譯一下vivi,把mtd功能去除,做簡單的修改(把bon_cmd部分從【lib/command.c】中去掉,否則編譯不通過),生成大小為35152字節。給開發板重新開機,利用老的vivi燒寫nand flash的vivi分區,完成後做一下reset,於是沒有MTD功能的vivi就跑起來了。但是,這樣的bootloader僅僅適合於最終的產品階段,不適合開發,沒什麼太大的價值。


    下面【drivers/mtd/mtdcore.c】,看看mtd_dev_init函數,核心部分就是調整mtd_init函數(【drivers/mtd/maps/s3c2410_flash.c】)。





int mtd_init(void)
{
    int ret;

#ifdef CONFIG_MTD_CFI
    ret = cfi_init();
#endif
#ifdef CONFIG_MTD_SMC
    ret = smc_init();
#endif
#ifdef CONFIG_S3C2410_AMD_BOOT
    ret = amd_init();
#endif





    可見,vivi現在支持三種類型的存儲接口,一種是CFI,也就是Intel發起的一個flash的接口標準,主要就是intel的nor flash系列;一種是smc,記憶卡系列接口,nand flash就是通過這個接口實現讀寫的;一種是AMD的flash系列。選擇什麼啟動方式,就要選擇相應的配置項。



    核心部分根據配置應該調用smc_init函數。-->【drivers/mtd/maps/s3c2410_flash.c】。這裡最為核心的就是兩個數據結構,一個是mtd_info,位於【include/mtd/mtd.h】,如下:


    mtd_info是表示MTD設備的結構,每個分區也被表示為一個mtd_info,如果有兩個MTD設備,每個設備有三個分區,那麼在系統中就一共有6個mtd_info結構。關於mtd_info,在《Linux MTD原始碼分析》中講解非常透徹,不過需要注意的是,在vivi的實現中沒有使用mtd_table,另外priv指向的是nand_info,這些都是與Linux下不同的地方,主要是為了簡化。另一個是nand_info,這個結構則包含了nand flash的所有信息。


    所謂的初始化,其實就是填充處理上述兩個數據結構的過程。填充完畢之後,後續的工作都會基於此展開。下面開始看smc_init的代碼。



mymtd = mmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip));
this = (struct nand_chip *)(&mymtd[1]);


    在這裡,第一句參考前面heap的實現代碼,重點看第二句代碼。這句代碼是有一定的技巧性,但是也存在著很大的風險。其中,mymtd是指向struct mtd_info的指針,那麼mymtd[1]實際上是等效於*(mymtd + 1)的數學計算模式,注意mymtd並非數組,這裡僅僅利用了編譯器翻譯的特點。對於指針而言,加1實際上增加的指針對應類型的值,在這裡地址實際上增加了sizeof(struct mtd_info),因為前面分配了兩塊連續的地址空間,所以&(*(mymtd + 1))實際上就是mtd_info數據結構結束的下一個地址,然後實現強制轉換,於是this就成為了nand_chip的入口指針了。但是,這裡必須要把握好,因為這個地方是不會進行內存的檢查的,也就是說,如果你使用了mymtd[2],那麼仍然按照上述公式解析,雖然可以運算,可是就是明顯的指針遺漏了,可能會出現意料不到的結果。


    瞭解清楚了,mymtd指向mtd_info的入口,this指向nand_chip的入口。



memset((char *)mymtd, 0, sizeof(struct mtd_info));
    memset((char *)this, 0, sizeof(struct nand_chip));


mymtd->priv = this;



    上述原始碼首先初始化這兩個結構體,即均為0.然後利用priv把二者聯繫起來,也就是mymtd通過其成員priv指向this,那麼mymtd中的抽閒操作函數,比如read、write等,真正的是通過this來實現的。很明顯,this的實現部分屬於flash硬件驅動層,而mymtd部分則屬於MTD設備層,二者的聯繫就是通過成員priv實現的。




    接下來首先是初始化nand flash設備,這跟前面的基礎實驗一致。



    /* set NAND Flash controller */
    nfconf = NFCONF;
    /* NAND Flash controller enable */
    nfconf |= NFCONF_FCTRL_EN;

    /* Set flash memory timing */
    nfconf &= ~NFCONF_TWRPH1;     /* 0x0 */
    nfconf |= NFCONF_TWRPH0_3;    /* 0x3 */
    nfconf &= ~NFCONF_TACLS;      /* 0x0 */

    NFCONF = nfconf;


    然後填充nand flash的數據結構的一個實例this,分成了兩個部分,nand flash基本操作函數成員的初始化、其餘訊息的填寫。



    /* Set address of NAND IO lines */
    this->hwcontrol = smc_hwcontrol;
    this->write_cmd = write_cmd;
    this->write_addr = write_addr;
    this->read_data = read_data;
    this->write_data = write_data;
    this->wait_for_ready = wait_for_ready;

    /* Chip Enable -> RESET -> Wait for Ready -> Chip Disable */
    this->hwcontrol(NAND_CTL_SETNCE);
    this->write_cmd(NAND_CMD_RESET);
    this->wait_for_ready();
    this->hwcontrol(NAND_CTL_CLRNCE);

    smc_insert(this);



    上面這些都不難理解,感覺在結構體設計上還是比較出色的,把成員和相應的操作封裝起來,面向對象的一種方法。下面看smc_insert,無非還是按照結構體填寫相應的信息,細節部分就不深入探討了。




inline int
smc_insert(struct nand_chip *this) {
    /* Scan to find existance of the device */
    if (smc_scan(mymtd)) {
        return -ENXIO;
    }
    /* Allocate memory for internal data buffer */
    this->data_buf = mmalloc(sizeof(u_char) *
             (mymtd->oobblock + mymtd->oobsize));

    if (!this->data_buf) {
        printk("Unable to allocate NAND data buffer for S3C2410.\n");
        this->data_buf = NULL;
        return -ENOMEM;
    }

    return 0;
}
    第一部分掃瞄填充mymtd數據結構。後面主要用於nand flash的oob緩衝處理。具體部分可以參考《s3c2410完全開發》。


    這裡重點是學習一種結構體的構造技巧。


    首先構造一級數據結構,表示抽像實體。例如:


struct nand_flash_dev {
    char * name;
    int manufacture_id;
    int model_id;
    int chipshift;
    char page256;
    char pageadrlen;
    unsigned long erasesize;
};


    然後構造實例集合,表現形式就是一個大的數組。



static struct nand_flash_dev nand_flash_ids[] = {
    {"Toshiba TC5816BDC", NAND_MFR_TOSHIBA, 0x64, 21, 1, 2, 0x1000},    // 2Mb 5V



    ... ....



    {"Samsung K9D1G08V0M", NAND_MFR_SAMSUNG, 0x79, 27, 0, 3, 0x4000},    // 128Mb

    {NULL,}
};



    這樣修改擴展等等後續的操作就簡便多了。抽像的能力及其訓練在讀程式碼的時候是可以很好的學習的,在vivi中,多處都採用了這種設計原則,應該掌握並利用。


step 6:


    此部分的功能是把vivi可能用到的所有私有參數都放在預先規劃的內存區域,大小為48K,基地址為0x


33df0000。在內存的分配示意圖方面,《s3c2410完全開發》已經比較詳盡,就不放在這裡了。到此為止,vivi作為bootloader的三大核心任務:initialise various devices, and eventually call the Linux kernel,passing information to the kernel.,現在只是完成第一方面的工作,設備初始化基本完成,實際上step 6是為啟動Linux內核和傳遞參數做準備的,把vivi的私有信息,內核啟動參數,mtd分區信息等都放到特定的內存區域,等待後面兩個重要工作使用(在step 8完成,後面的step 7也是為step 8服務的)。這48K區域分為三個組成部分:MTD參數、vivi parameter、Linux啟動命令。每塊的具體內容框架都一致,以vivi param tlb這個情景為主線進行分析:


入口:


init_priv_data();


進入【lib/priv_data/rw.c】--init_priv_data()


int
init_priv_data(void)
{
    int ret_def;
#ifdef CONFIG_PARSE_PRIV_DATA
    int ret_saved;
#endif
    ret_def = get_default_priv_data();
#ifdef CONFIG_PARSE_PRIV_DATA
    ret_saved = load_saved_priv_data();
    if (ret_def && ret_saved) {
        printk("Could not found vivi parameters.\n");
        return -1;
    } else if (ret_saved && !ret_def) {
        printk("Could not found stored vivi parameters.");
        printk(" Use default vivi parameters.\n");
    } else {
        printk("Found saved vivi parameters.\n");
    }
#else
    if (ret_def) {
        printk("Could not found vivi parameters\n");
        return -1;
    } else {
        printk("Found default vivi parameters\n");
    }
#endif
    return 0;


    下面分為兩步:首先讀取預設設置到特定的內存區域,然後讀取nand flash的param區域的信息,如果讀取成功,就覆蓋掉前面的預設設置。首先看第一步,get_default_priv_data--get_default_param_tlb-->


int get_default_param_tlb(void)
{
    char *src = (char *)&default_vivi_parameters;
    char *dst = (char *)(VIVI_PRIV_RAM_BASE + PARAMETER_TLB_OFFSET);
    int num = default_nb_params;

    if (src == NULL) return -1;

    /*printk("number of vivi parameters = %d\n", num); */
    *(nb_params) = num;

    //參數表的長度不可以超過預設內存的大小



    if ((sizeof(vivi_parameter_t)*num) > PARAMETER_TLB_SIZE) {
        printk("Error: too large partition table\n");
        return -1;
    }




    //首先複製magic number
    memcpy(dst, vivi_param_magic, 8);



    //預留下8個字節作為擴展
    dst += 16;



    //複製真正的parameter
    memcpy(dst, src, (sizeof(vivi_parameter_t)*num));
    return 0;
}

    內存的入口地址為VIVI_PRIV_RAM_BASE+PARAMETER_TLB_OFFSET,開始的8個字節放magic number,這裡vivi定義為「VIVIPARA」,後面空下8個字節,留作擴展,從第17個字節開始放置真正的param。這裡用到了多處技巧,第一處就是上面剛剛介紹過的數據結構構造技巧,這裡的vivi_parameter_t就是一級數據結構:



typedef struct parameter {
    char name[MAX_PARAM_NAME];
    param_value_t value;
    void (*update_func)(param_value_t value);
} vivi_parameter_t;    

    利用其構造了預設的成員表:

vivi_parameter_t default_vivi_parameters[] = {
    { "mach_type",            MACH_TYPE,    NULL },
    { "media_type",            MT_S3C2410,    NULL },
    { "boot_mem_base",        0x30000000,    NULL },
    { "baudrate",            UART_BAUD_RATE,    NULL },
    { "xmodem_one_nak",        0,        NULL },
    { "xmodem_initial_timeout",    300000,        NULL },
    { "xmodem_timeout",        1000000,    NULL },
    { "ymodem_initial_timeout",    1500000,    NULL },
    { "boot_delay",            0x1000000,    NULL }
};


    我們這時就可以很清楚的看到param show列出的配置參數了。

    另外一個技巧就是計算數組長度。


int default_nb_params = ARRAY_SIZE(default_vivi_parameters);


    其中ARRAY_SIZE為:


#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

    這是從Linux kernel中拿來的,也是值得學習和利用的地方。

    在load階段內無非就是找到param分區,然後根據配置,找到相應的flash硬體驅動(這就是MTD層的作用所在,不過可以看出nand chip的databuf確實沒有起到作用,現在也未看出這部分究竟用在何處)。然後就是讀操作。當然,讀取出來的信息先放到臨時緩衝區,判斷前面的magic number,如果符合則說明是正確的分區信息,然後把信息從臨時緩衝區複製到對應的預設配置區,這樣就完成了真正的配置。

    其實這個地方可以改進。首先看看param分區是否有合適的分區信息,如果有,直接讀取到vivi parameter區域,不需要再讀取預設的配置訊息;如果沒有合適的分區信息,然後讀取預設的配置信息。這樣在用戶修正了分區信息時,不必再讀取預設的配置信息,這也算是一處優化。




step 7:


    調整add_command()函數,增加vivi作為終端時命令的相應處理函數。其實,這種機制還是比較簡單的,就是利用了連結表。

    整個命令處理機制及其初始化的實現是在【lib/command.c】中完成的,包括添加命令、查找命令、執行命令、解析命令行等等。具體的命令函數則在相應的模塊裡面,這樣形成了一個2層的軟件架構:頂部管理層+底部執行層。維護的核心就是一個數據結構user_command:

typedef struct user_command {
    const char *name;
    void (*cmdfunc)(int argc, const char **);
    struct user_command *next_cmd;
    const char *helpstr;
} user_command_t;
   

第一個成員是指向name字符串的指針,第二個成員就是命令的處理函數,第三個成員是指向下一個命令,第四個成員是幫助信息。如果你想添加一個命令,那麼首先需要構造一個數據結構user_command的實例,比如:




user_command_t help_cmd = {
    "help",
    command_help,
    NULL,
    "help [{cmds}] \t\t\t-- Help about help?"
};


    然後實現命令的真正處理函數command_help。





void command_help(int argc, const char **argv)
{
    user_command_t *curr;

    /* help <command>. invoke <command> with 'help' as an argument */
    if (argc == 2) {
        if (strncmp(argv[1], "help", strlen(argv[1])) == 0) {
            printk("Are you kidding?\n");
            return;
        }
        argv[0] = argv[1];
        argv[1] = "help";
        execcmd(argc, argv);
        return;
    }

    printk("Usage:\n");
    curr = head_cmd;
    while(curr != NULL) {
        printk(" %s\n", curr->helpstr);
        curr = curr->next_cmd;
    }
}




    構造好之後,需要把它加入鏈表,也就是在init_builtin_cmds中增加add_command(&help_cmd);,其中add_command的實現如下:






void add_command(user_command_t *cmd)
{
    if (head_cmd == NULL) {
        head_cmd = tail_cmd = cmd;
    } else {
        tail_cmd->next_cmd = cmd;
        tail_cmd = cmd;
    }
    /*printk("Registered '%s' command\n", cmd->name);*/
}



    這樣,自己如果增加新的程序,就按照如上的步驟添加即可。

    其餘具體命令的實現暫時不做解釋。


step 8:



    根據情況,要麼進入vivi的命令行交互界面,要麼直接啟動內核。關於此部分的流程分析,有了前面的基礎和經驗,是不難理解的。很容易通過vivi的列印信息得知進行到了第幾步,《s3c2410完全開發》在過程上講解的也很清楚。所以不打算具體分析了。現在翻閱網上資料,有一個問題實際上模模糊糊,如下:


    vivi作為bootloader的一個重要的功能就是向Linux kernel傳遞啟動參數,這個情景究竟是如何完成的呢?雖然網上討論很多,但是因為vivi具有一點特殊性,所以使得理解上有一定的困難。現在已經比較清晰了,算是回答網友的一個問題,也算是總結,就bootloader如何於kernel傳遞參數,作為一個情景進行詳盡的分析。事先需要說明的是,我們假定vivi為A,Linux kernel為B,A要傳給B東西,這就是一個通信的過程。要想通信,至少我們得有一個約定,那就是協議。現在存在的協議有兩種,一種是基於struct param_struct,不過這種因為其局限性即將作廢;一種是基於tags技術。基本的框架就是A必須按照協議設置好參數,B呢,就需要來讀取解析這些參數。它們之間必須配合好,如果配合不好,那麼,kernel是無法引導成功的。現在嵌入式系統的移植,很多時候kernel引導不起來,部分原因就直接來自於參數傳遞問題。但是設計到這個問題,不能不分析Linux kernel的引導過程。現在還不想細緻到程式碼層,只是根據部分程式碼把Linux kernel啟動至獲取引導參數的過程從整體上瞭解清楚,必要的時候輔助相應的程式碼。這部分內容的詳細分析,專門在下篇總結中完成。

2009年6月7日 星期日

S3C2440 vivi BootLoader 筆記(二) Stage 2

 
從init/main.c中的main函數開始,終於步入C語言的世界了。Main函數總共有8步(8 steps),先看看程式碼:












int main(int argc, char *argv[])

{

    int ret;

   



    GPFDAT = 0x10;
    /* NB: MMU off state */
    /*
   
/*

    * Step 1:
    *  print banner

    */

    putstr("\r\n");

    putstr(vivi_banner);   //vivi_banner是vivi執行開始的顯示資訊,vivi_banner在檔version.c中定義





    reset_handler();



     /*

      * Step 2:
      *   Board initialize
      */

    ret = board_init();
    GPFDAT = 0x20;
    if (ret) {

          putstr("Failed a board_init() procedure\r\n");

          error();

    }



    /*
     * Step 3:
     *   4G linear mapping, flash mapping
     *   MMU on
     */

    mem_map_init();

    mmu_init();

    putstr("Succeed memory mapping.\r\n");



    /*

     * Now, vivi is running on the ram. MMU is enabled.

     * 
     * Step 4:
     *   dynamic memory can be used in bootloader
     */
    /* initialize the heap area*/

    ret = heap_init();

    if (ret) {

          putstr("Failed initailizing heap region\r\n");

          error();

    }



     /* Step 5:
     *    MTD initialize
     *    read MTE partition info.
     */

    ret = mtd_dev_init();



    /* Step 6:
     *   read bootloader parameter
     */

    init_priv_data();



    /* Step 7:
     *  misc treatment
     */

    misc();





    init_builtin_cmds();



    /* Step 8:
     *   boot or vivi.
     */

    boot_or_vivi();





    return 0;

}













下面按照上面的步驟逐步來分析一下。






1、Step 1:reset_handler()
reset_handler用於將記憶體清零,代碼在lib/reset_handle.c中。






1 void

2 reset_handler(void)

3 {

4     int pressed;

5     pressed = is_pressed_pw_btn(); /*判斷是硬體重定還是軟體重定*/

6     if (pressed == PWBT_PRESS_LEVEL) {

7       DPRINTK("HARD RESET\r\n");

8       hard_reset_handle();     /*調用clear_mem對SDRAM清0*/

9     } else {

10       DPRINTK("SOFT RESET\r\n");

11       soft_reset_handle();     /*此函數為空*/

12   }

13 }




  在Power On後,reset_handler呼叫第8行的hard_reset_handle(),此函數在lib/reset_handle.c中:
[main(int argc, char *argv[]) -> reset_handler() -> hard_reset_handle()]





1 static void

2 hard_reset_handle(void)

3 {

4 #if 0


5     clear_mem((unsigned long)(DRAM_BASE + VIVI_RAM_ABS_POS), \

6     (unsigned long)(DRAM_SIZE - VIVI_RAM_ABS_POS));

7 #endif
     
/*lib/memory.c,將起始位址為USER_RAM_BASE,長度為USER_RAM_SIZE的記憶體清0*/

8   clear_mem((unsigned long)USER_RAM_BASE, (unsigned long) USER_RAM_SIZE);

9 }








2、Step 2:board_init()  






board_init呼叫2個函數用於初始化計時器和設置各GPIO引腳功能,代碼在arch/s3c2440/smdk.c中:
[main(int argc, char *argv[]) > board_init()]






1 int board_init(void)

2 {

3     init_time(); /*arch/s3c2440/proc.c*/

4     set_gpios(); /*arch/s3c2440/smdk.c */

5     return 0;

6 }




init_time() 這個函數對寄存器進行了簡單的操作:






void init_time(void)

{

    TCFG0 = (TCFG0_DZONE(0) | TCFG0_PRE1(15) | TCFG0_PRE0(0));

    /*s3c2440 data sheet 可以查到*/

    /*TCFG0 = 0 | 0xf00 | 0 */

}




寄存器TCFG0由三部分組成,prescaler0,prescaler1,deadzone和reserve四部分,前三部分分別對應
TCFG0_PRE0、TCFG0_PRE1、TCFG0_DZONE,TCFG0_PRE0(0)實際值為0x00,TCFG0_PRE1(15)實際
值為0x0f00,而TCFG0_DZONE(0)實際值為
0x000000。實際中,vivi並未使用計時器,這個函數就可以忽略。set_gpios()用於選擇GPA至GPH埠各引腳的功能及是否使用各引腳
的內部上拉電阻,並設置外部中斷源寄存器EXTINT0-2(vivi中未使用外部中斷)。






1     void set_gpios(void)

2     {

3           GPACON = vGPACON;

4           GPBCON = vGPBCON;

5           GPBUP   = vGPBUP;

6           GPCCON = vGPCCON;

7           GPCUP   = vGPCUP;

8           GPDCON = vGPDCON;

9           GPDUP   = vGPDUP;

10           GPECON = vGPECON;

11           GPEUP   = vGPEUP;

12           GPFCON = vGPFCON;

13           GPFUP   = vGPFUP;

14           GPGCON = vGPGCON;

15           GPGUP   = vGPGUP;

16           GPHCON = vGPHCON;

17           GPHUP   = vGPHUP;

18           EXTINT0 = vEXTINT0;

19           EXTINT1 = vEXTINT1;

20           EXTINT2 = vEXTINT2;

21     }




    以第三行為例,vGPACON的值為0x007fffff,查找s3c2440用戶手冊可知,該參數將GPACON的23位元全部置1。各位功能需察看s3c2440用戶手冊

3、Step 3:建立頁表和啟動MMU
      mem_map_init();
    
mmu_init();

mem_map_init函數用於建立頁表,vivi使用段式頁表,只需要一級頁表。它調用3個函數,代碼在arch/s3c2440/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void)]






1. void mem_map_init(void)
2. {
3. #ifdef CONFIG_S3C2440_NAND_BOOT
4.     mem_map_nand_boot();
5. #else
6.     mem_map_nor();
7. #endif
8.     cache_clean_invalidate();
9.     tlb_invalidate();
10. }





第8、9行的兩個函數可以不用管它,他們做的事情在下面的mmu_init函數裏又重複了一遍。對於本開發板,在.config中定義了
CONFIG_S3C2440_NAND_BOOT。mem_map_nand_boot()函數呼叫mem_mapping_linear()函數來最
終完成建立頁表的工作。頁表存放在SDRAM物理位址0x33dfc000開始處,共16K:一個頁表項4位元組,共有4096個頁表項;每個頁表項對應
1M位址空間,共4G。mem_map_init先將4G虛擬位址映射到相同的物理位址上,NCNB(不使用cache,不使用write
buffer)——這樣,對寄存器的操作跟未啟動MMU時是一樣的;再將SDRAM對應的64M空間的頁表項修改為使用cache。
mem_mapping_linear函數的代碼在arch/s3c2440/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void) > mem_map_nand_boot( ) > mem_mapping_linear(void)]










1 static inline void mem_mapping_linear(void)

2 {

3     unsigned long pageoffset, sectionNumber;

4         putstr_hex("MMU table base address = 0x", (unsigned long)

mmu_tlb_base);

5     /* 4G 虛擬位址映射到相同的物理位址. not cacacheable, not bufferable */

6     /* mmu_tlb_base = 0x33dfc000*/

7     for (sectionNumber = 0; sectionNumber < 4096; sectionNumber++) {

8           pageoffset = (sectionNumber << 20);

9           *(mmu_tlb_base + (pageoffset >> 20)) = pageoffset |

        MMU_SECDESC;

10     }

       

11     /* make dram cacheable */

12     /* SDRAM物理位址0x3000000-0x33ffffff,

13       DRAM_BASE=0x30000000,DRAM_SIZE=64M

14     */

15     for (pageoffset = DRAM_BASE; pageoffset < (DRAM_BASE+DRAM_SIZE); \

16       pageoffset += SZ_1M) {

17       //DPRINTK(3, "Make DRAM section cacheable: 0x%08lx\n",         pageoffset);

18       *(mmu_tlb_base + (pageoffset >> 20)) = \

pageoffset | MMU_SECDESC | MMU_CACHEABLE;

19     }

20 }








mmu_init()函數用於啟動MMU,它直接調用arm920_setup()函數。arm920_setup()的代碼在arch/s3c2410/mmu.c中:
[main(int argc, char *argv[]) > mmu_init( ) > arm920_setup( )]
1 static inline void arm920_setup(void)
2 {
3             unsigned long ttb = MMU_TABLE_BASE;
/* MMU_TABLE_BASE = 0x33dfc000 */
4 __asm__(
5     /* Invalidate caches */
6     "mov   r0, #0\n"
7     "mcr   p15, 0, r0, c7, c7, 0\n"   /* invalidate I,D caches on v4 */
8     "mcr   p15, 0, r0, c7, c10, 4\n"   /* drain write buffer on v4 */
9     "mcr   p15, 0, r0, c8, c7, 0\n"   /* invalidate I,D TLBs on v4 */
      10     /* Load page table pointer */
11     "mov   r4, %0\n"
12     "mcr   p15, 0, r4, c2, c0, 0\n"   /* load page table pointer */
13     /* Write domain id (cp15_r3) */
      14     "mvn   r0, #0\n"   /* Domains 0b01 = client, 0b11=Manager*/
      15     "mcr   p15, 0, r0, c3, c0, 0\n"
/* load domain access register,write domain 15:0, 用戶手冊P548(access permissions)*/
16     /* Set control register v4 */
17     "mrc   p15, 0, r0, c1, c0, 0\n"   /* get control register v4 */
        /*數據手冊P545:read control register */
18     /* Clear out 'unwanted' bits (then put them in if we need them) */
19     /* ..VI ..RS B... .CAM */   /*這些位元的含義在資料手冊P546*/
20     "bic r0, r0, #0x3000\n" /* ..11 .... .... .... */
        /*I(bit[12])=0 = Instruction cache disabled*/
21     /*V[bit[13]](Base location of exception registers)=0 = Low addresses = 0x0000 0000*/
22     "bic r0, r0, #0x0300\n"     /* .... ..11 .... .... */
       
23     /*R(ROM protection bit[9])=0*/
            /*S(System protection bit[8])=0*/
            /*由於TTB中AP=0b11(line141),所以RS位不使用(P579)*/
24     "bic r0, r0, #0x0087\n"     /* 0x0000000010000111 */
          /*M(bit[0])=0 = MMU disabled*/
          /*A(bit[1])=0 =Data address alignment fault checking disable*/
          /*C(bit[2])=0 = Data cache disabled*/
          /*B(bit[7])=0= Little-endian operation*/
25     /* Turn on what we want */
26     /* Fault checking enabled */
27     "orr r0, r0, #0x0002\n"     /* .... .... .... ..10 */
        /*A(bit[1])=1 = Data address alignment fault checking enable*/
28             #ifdef CONFIG_CPU_D_CACHE_ON   /*is not set*/
29     "orr   r0, r0, #0x0004\n"     /* .... .... .... .100 */
        /*C(bit[2])=1 = Data cache enabled*/
30 #endif
31 #ifdef CONFIG_CPU_I_CACHE_ON   /*is not set*/
32     "orr   r0, r0, #0x1000\n" /* ...1 .... .... .... */
        /*I(bit[12])=1 = Instruction cache enabled*/
33 #endif
         
34       /* MMU enabled */
35           "orr   r0, r0, #0x0001\n"     /* .... .... .... ...1 */
        /*M(bit[0])=1 = MMU enabled*/
36           "mcr   p15, 0, r0, c1, c0, 0\n"   /* write control register */
        /*數據手冊P545*/
37           : /* no outputs */
38           : "r" (ttb) );
39 }


4、Step 4:heap_init()  
第4步調用了heap_init(void)函數,並返回值。該值是函數heap_init()調用的mmalloc_init()函數的返回值。其實,這步就是申請一塊記憶體區域。
[lib/heap.c->heap_init(void)]
1     int heap_init(void)
2     {
3           return mmalloc_init((unsigned char *)(HEAP_BASE), HEAP_SIZE);    
4     }
記憶體動態分配函數mmalloc就是從heap(堆)中劃出一塊空閒記憶體。相應的mfree函數則將動態分配的某塊記憶體釋放回heap中。
heap_init函數在SDRAM中指定了一塊1M大小的記憶體作為heap(起始位址HEAP_BASE =
0x33e00000),並在heap的開頭定義了一個資料結構blockhead。事實上,heap就是使用一系列的blockhead資料結構來描述
和操作的。每個blockhead資料結構對應著一塊heap記憶體,假設一個blockhead資料結構的存放位置為A,則它對應的可分配記憶體位址為
“A + sizeof(blockhead)”到“A + sizeof(blockhead) + size -
1”。blockhead資料結構在lib/heap.c中定義:
1 typedef struct blockhead_t {
2     int32 signature;   //固定為BLOCKHEAD_SIGNATURE
3     bool allocated;     //此區域是否已經分配出去:0-N,1-Y
4     unsigned long size; //此區域大小
5     struct blockhead_t *next;   //鏈表指針
6     struct blockhead_t *prev;   //鏈表指針
7 } blockhead;
現在來看看heap是如何運作的(如果您不關心heap實現的細節,這段可以跳過)。vivi對heap的操作比較簡單,vivi中有一個總體變數
static blockhead
*gHeapBase,它是heap的鏈表頭指標,通過它可以遍曆所有blockhead資料結構。假設需要動態申請一塊sizeA大小的記憶體,則
mmalloc函數從gHeapBase開始搜索blockhead資料結構,如果發現某個blockhead滿足:
(1) allocated = 0 //表示未分配
(2) size > sizeA,則找到了合適的blockhead,
滿足上述條件後,進行如下操作:
a.allocated設為1
b.如果size – sizeA > sizeof(blockhead),則將剩下的記憶體組織成一個新的blockhead,放入鏈表中
c.返回分配的記憶體的首位址釋放記憶體的操作更簡單,直接將要釋放的記憶體對應的blockhead資料結構的allocated設為0即可。
heap_init函數直接調用mmalloc_init函數進行初始化,此函數代碼在lib/heap.c中,比較簡單,初始化gHeapBase即可:
[main(int argc, char *argv[]) > heap_init(void) > mmalloc_init(unsigned char *heap, unsigned long size)]
1       static inline int mmalloc_init(unsigned char *heap, unsigned long size)
2       {
3     if (gHeapBase != NULL) return -1;
  4   DPRINTK("malloc_init(): initialize heap area at 0x%08lx, size = 0x%08lx\n", heap, size);
5     gHeapBase = (blockhead *)(heap);
6     gHeapBase->allocated=FALSE;
7     gHeapBase->signature=BLOCKHEAD_SIGNATURE;
8     gHeapBase->next=NULL;
9     gHeapBase->prev=NULL;
10     gHeapBase->size = size - sizeof(blockhead);
11     return 0;
12       }
static blockhead *gHeapBase = NULL; 這個就是上面稱讚的總體變數了,定義在lib/heap.c中。上面就是個鏈表操作,資料結構,看來搞這個也得好好學資料結構啊,不然記憶體搞的溢出、浪費可就哭都來不及了。

5、Step 5:mtd_dev_init()  
所謂MTD(Memory Technology
Device)相關的技術。在linux系統中,我們通常會用到不同的存儲設備,特別是FLASH設備。為了在使用新的存儲設備時,我們能更簡便地提供它
的驅動程式,在上層應用和硬體驅動的中間,抽象出MTD設備層。驅動層不必關心存儲的資料格式如何,比如是FAT32、ETX2還是FFS2或其他。它僅
僅提供一些簡單的介面,比如讀寫、擦除及查詢。如何組織資料,則是上層應用的事情。MTD層將驅動層提供的函數封裝起來,向上層提供統一的介面。這樣,上
層即可專注於檔系統的實現,而不必關心存儲設備的具體操作。這段亂七八糟的話也許比較讓人暈,也可以這樣理解在設備驅動(此處指存儲設備)和上層應用之間
還存在著一層,共三層,這個中間層就是MTD技術的產物。通常可以將它視為驅動的一部分,叫做上層驅動,而那些實現設備的讀、寫操作的驅動稱為下層驅動,
上層驅動將下層驅動封裝,並且留給其上層應用一些更加容易簡單的介面。
在我們即將看到的代碼中,使用mtd_info資料結構表示一個MTD設備,使用nand_chip資料結構表示一個nand
flash晶片。在mtd_info結構中,對nand_flash結構作了封裝,向上層提供統一的介面。比如,它根據nand_flash提供的
read_data(讀一個位元組)、read_addr(發送要讀的磁區的位址)等函數,構造了一個通用的讀函數read,將此函數的指標作為自己的一
個成員。而上層要讀寫flash時,執行mtd_info中的read、write函數即可。
mtd_dev_init()用來掃描所使用的NAND
Flash的型號,構造MTD設備,即構造一個mtd_info的資料結構。對於S3C2410來說,它直接調用mtd_init(),mtd_init
又調用smc_init(),此函數在drivers/mtd/maps/s3c2410_flash.c中:
[main(int argc,char *argv[])>mtd_dev_init()>mtd_init()]
1     int mtd_init(void)
2 {
3 int ret;

4 #ifdef CONFIG_MTD_CFI           /*is not set*/
5 ret = cfi_init();
6     #endif
7     #ifdef CONFIG_MTD_SMC9           /* =y */
8           ret = smc_init();
9     #endif
10     #ifdef CONFIG_S3C2410_AMD_BOOT     /*is not set*/
11           ret = amd_init();
12     #endif

13     if (ret) {
14           mymtd = NULL;
15           return ret;
16           }
17     return 0;
18     }
顯而易見,該函數應取第二項,這項在autoconf.h中定義了。
[main(int argc, char *argv[]) > mtd_dev_init() > mtd_init() > smc_init()]
1 static int
2 smc_init(void)
3 {
/*struct mtd_info *mymtd,資料類型在include/mtd/mtd.h*/
        /*strcut nand_chip在include/mtd/nand.h中定義*/
4     struct nand_chip *this;
5     u_int16_t nfconf;
        /* Allocate memory for MTD device structure and private data */
6     mymtd = mmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip));
7     if (!mymtd) {
8       printk("Unable to allocate S3C2410 NAND MTD device structure.\n");
9       return -ENOMEM;
10     }
        /* Get pointer to private data */
11     this = (struct nand_chip *)(&mymtd[1]);
        /* Initialize structures */
12     memset((char *)mymtd, 0, sizeof(struct mtd_info));
13     memset((char *)this, 0, sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
14     mymtd->priv = this;
/* set NAND Flash controller */
15     nfconf = NFCONF;
/* NAND Flash controller enable */
16     nfconf |= NFCONF_FCTRL_EN;
/* Set flash memory timing */
17     nfconf &= ~NFCONF_TWRPH1;   /* 0x0 */
  18     nfconf |= NFCONF_TWRPH0_3; /* 0x3 */
  19     nfconf &= ~NFCONF_TACLS; /* 0x0 */
       
  20     NFCONF = nfconf;
       
        /* Set address of NAND IO lines */
  21     this->hwcontrol = smc_hwcontrol;
  22     this->write_cmd = write_cmd;
23     this->write_addr = write_addr;
24     this->read_data = read_data;
25     this->write_data = write_data;
26     this->wait_for_ready = wait_for_ready;
       
/* Chip Enable -> RESET -> Wait for Ready -> Chip Disable */
27     this->hwcontrol(NAND_CTL_SETNCE);
28     this->write_cmd(NAND_CMD_RESET);
29     this->wait_for_ready();
30     this->hwcontrol(NAND_CTL_CLRNCE);
       
31     smc_insert(this);
       
32     return 0;
33 }
6-14行構造了一個mtd_info結構和nand_flash結構,前者對應MTD設備,後者對應nand
flash晶片(如果您用的是其他類型的記憶體件,比如nor
flash,這裏的nand_flash結構應該換為其他類型的資料結構)。MTD設備是具體記憶體件的抽象,那麼在這些代碼中這種關係如何體現呢——第
14行的代碼把兩者連結在一起了。事實上,mtd_info結構中各成員的實現(比如read、write函數),正是由priv變數所指向的
nand_flash的各類操作函數(比如read_addr、read_data等)來實現的。
15-20行是初始化S3C2410上的NAND
FLASH控制器。前面分配的nand_flash結構還是空的,現在當然就是填滿它的各類成員了,這正是21-26行做的事情。27-30行對這塊
nand flash作了一下復位操作。最後,也是最複雜的部分,根據剛才填充的nand_flash結構,構造mtd_info結構,這由31行的
smc_insert函數調用smc_scan完成。

這才是VIVI啟動的第5步,還有三步就完成了啟動了,同時我的這篇閱讀筆記也就O了。