1. 程式人生 > >AM335x U-boot d程式碼分析過程3

AM335x U-boot d程式碼分析過程3

    我們繼續上一篇的程式碼,已經來到s_init()(位於arch\arm\cpu\armv7\am335x\board.c),其原始碼如下:


[cpp] view plain copy print ?
  1. …defined(CONFIG_SPL_BUILD)  
  2.     gd = &gdata;  
  3.     preloader_console_init();//wlg: uart_init(), and print the first information U-boot SPL…
      
  4. #endif  
  5. #if defined(CONFIG_SPL_AM33XX_ENABLE_RTC32K_OSC)  
  6.     /* Enable RTC32K clock */  
  7.     rtc32k_enable();  
  8. #endif  
  9. #ifdef CONFIG_SPL_BUILD  
  10.     board_early_init_f();//wlg: SPL only, initial some thing for sdram  
  11.     sdram_init();//wlg: initial the SDRAM for next stage, this function will read eeprom to decide how to initial sdram
      
  12. #endif//wlg: now, we had correctly initial sdram, we return and it’s time to copy uboot from mmc.  
  13. }  
  14. #endif  
...defined(CONFIG_SPL_BUILD)
    gd = &gdata;
    preloader_console_init();//wlg: uart_init(), and print the first information U-boot SPL...




endif

if defined(CONFIG_SPL_AM33XX_ENABLE_RTC32K_OSC)

/* Enable RTC32K clock */
rtc32k_enable();

endif

ifdef CONFIG_SPL_BUILD

board_early_init_f();//wlg: SPL only, initial some thing for sdram
sdram_init();//wlg: initial the SDRAM for next stage, this function will read eeprom to decide how to initial sdram

endif//wlg: now, we had correctly initial sdram, we return and it’s time to copy uboot from mmc.

}

endif


    這段程式碼的開始,仍是將gdata的地址賦給gd這個指向gd_t(global_data)的指標,而上文中也有提及,gd這個指標實際上就是r9.所以這一步的工作仍然是將r9指向gdata,而gdata這個資料本身就是gd_t(global_data)結構體。這裡估計也是進一步的確認吧?再往下看

    進入了preloader_console_init()函式,這個函式位於common\spl\Spl.c檔案中,將其展開


[cpp] view plain copy print ?
  1. void preloader_console_init(void)  
  2. {  
  3.     gd->bd = &bdata;  
  4.     gd->baudrate = CONFIG_BAUDRATE;  
  5.   
  6.     serial_init();      /* serial communications setup */  
  7.   
  8.     gd->have_console = 1;  
  9.   
  10.     puts(”\nU-Boot SPL ” PLAIN_VERSION “ (“ U_BOOT_DATE “ - ” \  
  11.             U_BOOT_TIME ”)\n”);//wlg: now we print our first information  
  12. #ifdef CONFIG_SPL_DISPLAY_PRINT  
  13.     spl_display_print();  
  14. #endif  
  15. }  
void preloader_console_init(void)
{
    gd->bd = &bdata;
    gd->baudrate = CONFIG_BAUDRATE;

    serial_init();      /* serial communications setup */

    gd->have_console = 1;

    puts("\nU-Boot SPL " PLAIN_VERSION " (" U_BOOT_DATE " - " \
            U_BOOT_TIME ")\n");//wlg: now we print our first information




#ifdef CONFIG_SPL_DISPLAY_PRINT spl_display_print(); #endif }


    上面的程式碼主要是對gd這個指標所指向的結構體(以下簡稱為gdata結構體)進行賦值,主要包括:

    1. 對gdata結構體中的bd進行賦值,而這個bd本身就是一個指向bd_t(board_data)結構體的指標。所以說上面的bdata其本身也是bd_t(board_data)結構體,在該Spl.c檔案中同樣由描述:Spl.c (common\spl):static bd_t bdata __attribute__ ((section(“.data”)));所以同gdata一樣,bdata也被預先儲存在了 .data段中

    2. 對gdata結構體中的baudrate 進行賦值,CONFIG_BAUDRATE實際上就是115200。這個baudrate 實際上記錄的就是uart的波特率,為115200.但是這裡僅僅只是賦值而已,並沒有對實際的uart硬體的波特率進行修改;

    3. 進入serial_init()函式(位於/drivers/serial/serial.c),該函式會利用gdata結構體中的baudrate對實際硬體進行操作!展開如下:

[cpp] view plain copy print ?
  1. int serial_init(void)  
  2. {  
  3.     gd->flags |= GD_FLG_SERIAL_READY;  
  4.     return get_current()->start();  
  5. }  
int serial_init(void)
{
    gd->flags |= GD_FLG_SERIAL_READY;
    return get_current()->start();
}
    這是個uart的初始化函式,首先是設定標誌位,這個標誌位就是在gdata結構體中的flags,這個flags是一個32位的資料,每一位都代表著某一標誌,如這裡就是將那個代表uart已經準備好的標誌位拉高(#define GD_FLG_SERIAL_READY 0x00100 /* Pre-reloc serial console ready  */)。這是為了向後傳遞各種全域性資訊;

    設定好gdata(再囉嗦一句,gd就是r9,其實際指向gdata)標誌位後,然後先執行get_current(),利用其返回再執行start()。先看get_current():

[cpp] view plain copy print ?
  1. static struct serial_device *get_current(void)  
  2. {  
  3.     struct serial_device *dev;  
  4.   
  5.     if (!(gd->flags & GD_FLG_RELOC))  
  6.         dev = default_serial_console();  
  7.     else if (!serial_current)  
  8.         dev = default_serial_console();  
  9.     else  
  10.         dev = serial_current;  
  11.   
  12.     /* We must have a console device */  
  13.     if (!dev) {  
  14. #ifdef CONFIG_SPL_BUILD  
  15.         puts(”Cannot find console\n”);  
  16.         hang();  
  17. #else  
  18.         panic(”Cannot find console\n”);  
  19. #endif  
  20.     }  
  21.   
  22.     return dev;  
  23. }  
static struct serial_device *get_current(void)
{
    struct serial_device *dev;

    if (!(gd->flags & GD_FLG_RELOC))
        dev = default_serial_console();
    else if (!serial_current)
        dev = default_serial_console();
    else
        dev = serial_current;

    /* We must have a console device */
    if (!dev) {




#ifdef CONFIG_SPL_BUILD puts("Cannot find console\n"); hang(); #else panic("Cannot find console\n"); #endif } return dev; }
    首先看到的是,這個函式的返回,是一個靜態變數,而且是一個指向serial_device結構體的指標,繼續往下看函式內部

    1. 首先是定義了一個指向serial_device結構體的區域性指標變數。

    2. 然後判斷gd->flags裡面的GD_FLG_RELOC標誌位是否有效,這個標誌位代表是否已經完成了uboot的重定位,很明顯我們目前還只是SPL程式,uboot的映象還沒有被複制到SDRAM中,更沒有重定位了。所以這個判斷無效,執行下一句

    3. 判斷serial_current(這個是全域性變數,也是一個指向serial_device結構體的指標)是否為0,如果為0的話,說明當前的serial裝置沒有初始化完成。很明顯,我們之前都沒有對這個serial_current進行操作,它實際上就是0x0000,所以這裡的判斷成立,開始執行dev = default_serial_console();(18 default_serial_console - Function in Serial_ns16550.c (drivers\serial) at line 235 (18 lines) 說實話,這個喊話在哪裡定義我還真拿不準,後期再結合Makefile來一起看,暫列如下:

[cpp] view plain copy print ?
  1. __weak struct serial_device *default_serial_console(void)  
  2. {  
  3. #if CONFIG_CONS_INDEX == 1  
  4.     return &eserial1_device;  
  5. #elif CONFIG_CONS_INDEX == 2  
  6.     return &eserial2_device;  
  7. #elif CONFIG_CONS_INDEX == 3  
  8.     return &eserial3_device;  
  9. #elif CONFIG_CONS_INDEX == 4  
  10.     return &eserial4_device;  
  11. #elif CONFIG_CONS_INDEX == 5  
  12.     return &eserial5_device;  
  13. #elif CONFIG_CONS_INDEX == 6  
  14.     return &eserial6_device;  
  15. #else  
  16. #error “Bad CONFIG_CONS_INDEX.”  
  17. #endif  
  18. }  
__weak struct serial_device *default_serial_console(void)
{




#if CONFIG_CONS_INDEX == 1 return &eserial1_device; #elif CONFIG_CONS_INDEX == 2 return &eserial2_device; #elif CONFIG_CONS_INDEX == 3 return &eserial3_device; #elif CONFIG_CONS_INDEX == 4 return &eserial4_device; #elif CONFIG_CONS_INDEX == 5 return &eserial5_device; #elif CONFIG_CONS_INDEX == 6 return &eserial6_device; #else #error "Bad CONFIG_CONS_INDEX." #endif }    可以看到,這個函式是根據巨集來條件編譯的,這裡的CONFIG_CONS_INDEX實際上在include\configs\am335x….h檔案中的定義

[cpp] view plain copy print ?
  1. /* NS16550 Configuration */  
  2. #define CONFIG_SYS_NS16550_COM1     0x44e09000  /* UART0 */  
  3. #define CONFIG_CONS_INDEX       1  
  4. #define CONFIG_BAUDRATE         115200  
/* NS16550 Configuration */




#define CONFIG_SYS_NS16550_COM1 0x44e09000 /* UART0 */ #define CONFIG_CONS_INDEX 1 #define CONFIG_BAUDRATE 115200    所以default_serial_console()實際返回的就是return &eserial1_device;,而這個由定義如下:

[cpp] view plain copy print ?
  1. DECLARE_ESERIAL_FUNCTIONS(1);  
  2. struct serial_device eserial1_device =  
  3.     INIT_ESERIAL_STRUCTURE(1, ”eserial0”);  
DECLARE_ESERIAL_FUNCTIONS(1);
struct serial_device eserial1_device =
    INIT_ESERIAL_STRUCTURE(1, "eserial0");
    這裡先執行了DECLARE_ESERIAL_FUNCTIONS(1),其功能如下:

[cpp] view plain copy print ?
  1. #define DECLARE_ESERIAL_FUNCTIONS(port) \  
  2.     static int  eserial##port##_init(void) \  
  3.     { \  
  4.         int clock_divisor; \  
  5.         clock_divisor = ns16550_calc_divisor(serial_ports[port-1], \  
  6.                 CONFIG_SYS_NS16550_CLK, gd->baudrate); \  
  7.         NS16550_init(serial_ports[port-1], clock_divisor); \  
  8.         return 0 ; \  
  9.     } \  
  10.     static void eserial##port##_setbrg(void) \  
  11.     { \  
  12.         serial_setbrg_dev(port); \  
  13.     } \  
  14.     static int  eserial##port##_getc(void) \  
  15.     { \  
  16.         return serial_getc_dev(port); \  
  17.     } \  
  18.     static int  eserial##port##_tstc(void) \  
  19.     { \  
  20.         return serial_tstc_dev(port); \  
  21.     } \  
  22.     static void eserial##port##_putc(const char c) \  
  23.     { \  
  24.         serial_putc_dev(port, c); \  
  25.     } \  
  26.     static void eserial##port##_puts(const char *s) \  
  27.     { \  
  28.         serial_puts_dev(port, s); \  
  29.     }  
#define DECLARE_ESERIAL_FUNCTIONS(port) \
    static int  eserial##port##_init(void) \
    { \
        int clock_divisor; \
        clock_divisor = ns16550_calc_divisor(serial_ports[port-1], \
                CONFIG_SYS_NS16550_CLK, gd->baudrate); \
        NS16550_init(serial_ports[port-1], clock_divisor); \
        return 0 ; \
    } \
    static void eserial##port##_setbrg(void) \
    { \
        serial_setbrg_dev(port); \
    } \
    static int  eserial##port##_getc(void) \
    { \
        return serial_getc_dev(port); \
    } \
    static int  eserial##port##_tstc(void) \
    { \
        return serial_tstc_dev(port); \
    } \
    static void eserial##port##_putc(const char c) \
    { \
        serial_putc_dev(port, c); \
    } \
    static void eserial##port##_puts(const char *s) \
    { \
        serial_puts_dev(port, s); \
    }
    然後執行INIT_ESERIAL_STRUCTURE(1, “eserial0”);


[cpp] view plain copy print ?
  1. #define INIT_ESERIAL_STRUCTURE(port, __name) {  \  
  2.     .name   = __name,           \  
  3.     .start  = eserial##port##_init,     \  
  4.     .stop   = NULL,             \  
  5.     .setbrg = eserial##port##_setbrg,   \  
  6.     .getc   = eserial##port##_getc,     \  
  7.     .tstc   = eserial##port##_tstc,     \  
  8.     .putc   = eserial##port##_putc,     \  
  9.     .puts   = eserial##port##_puts,     \  
  10. }  
#define INIT_ESERIAL_STRUCTURE(port, __name) { \ 
.name = __name, \
.start = eserial##port##_init, \
.stop = NULL, \
.setbrg = eserial##port##_setbrg, \
.getc = eserial##port##_getc, \
.tstc = eserial##port##_tstc, \
.putc = eserial##port##_putc, \
.puts = eserial##port##_puts, \
}
    簡而言之這裡就是初始化了一個serial_device結構體eserial1_device,這個結構體裡的元素都用default,也就是預先定義好的函式或者字元進行替換。而這個初始化完成的結構體最後就被返回到get_current()函式中的區域性變數dev中,在通過判斷dev是否有效來,輸出一些除錯資訊!

    最終get_current()函式的返回的就是dev。

————————–get_current()函式結束—————————

    我們繼續回到serial_init()函式,我們需要利用get_current()的返回,去執行seserial1_device結構體中的tart元素,也就是說start這個元素實際上就是一個函式指標!我們展開start來看一下,實際上就是:

     .start= eserial##port##_init,(INIT_ESERIAL_STRUCTURE中)

                static int  eserial##port##_init(void) \
        { \
        int clock_divisor; \
        clock_divisor = ns16550_calc_divisor(serial_ports[port-1], \
        CONFIG_SYS_NS16550_CLK, gd->baudrate); \
        NS16550_init(serial_ports[port-1], clock_divisor); \
        return 0 ; \
        } \

    所以執行start()就是執行了上述的static int  eserial##port##_init(void)函式,可以看到這個函式就是利用gd->baudrate對uart進行初始化!這裡不再細細展開,因為接下來的操作很多都是暫存器的操作,比較枯燥!


———-serial_init()函式執行完畢——————

    這樣我們就返回到了preloader_console_init(),再往下執行:

    4. gd->have_console = 1; 通過設定全域性變數,也就是igd所指向的gdata中的have_console元素來告訴其他函式,現在已經有console,也即是說目前已經有一個uart實現的控制檯,可以實現簡單的資料輸出和輸入!

    5. 然後我們就列印了UBOOT的資訊,包括版本等等。這個其實也就是我們再利用uart實現uboot啟動後的第一條資訊輸出,一般如下:

U-Boot SPL 2015.10-00001-g143c9ee (Nov 06 2015 - 15:27:19)


—————–    preloader_console_init()執行完畢———————

    繼續返回到s_init(),

    接下來就要開始執行非常關鍵的兩個函式:

    1.       board_early_init_f();//wlg: SPL only, initial some thing for sdram
    2.       sdram_init();//wlg: initial the SDRAM for next stage, this function will read eeprom to decide how to initial sdram

    第一個函式主要完成SDRAM的前期初始化,第二個函式進一個完成SDRAM的設定,先進入第一個函式:


[cpp] view plain copy print ?
  1. /* 
  2.  * In the case of non-SPL based booting we’ll want to call these 
  3.  * functions a tiny bit later as it will require gd to be set and cleared 
  4.  * and that’s not true in s_init in this case so we cannot do it there. 
  5.  */  
  6. int board_early_init_f(void)  
  7. {  
  8.     prcm_init();  
  9.     set_mux_conf_regs();  
  10.   
  11.     return 0;  
  12. }  
/*
 * In the case of non-SPL based booting we'll want to call these
 * functions a tiny bit later as it will require gd to be set and cleared
 * and that's not true in s_init in this case so we cannot do it there.
 */
int board_early_init_f(void)
{
    prcm_init();
    set_mux_conf_regs();

    return 0;
}

    看註釋,應該是完成引腳的而配置工作,

[cpp] view plain copy print ?
  1. void prcm_init()  
  2. {  
  3.     enable_basic_clocks();  
  4.     scale_vcores();  
  5.     setup_dplls();  
  6. }  
void prcm_init()
{
    enable_basic_clocks();
    scale_vcores();
    setup_dplls();
}

直接看第三個函式

[cpp] view plain copy print ?
  1. static void setup_dplls(void)  
  2. {  
  3.     const struct dpll_params *params;  
  4.   
  5.     params = get_dpll_core_params();  
  6.     do_setup_dpll(&dpll_core_regs, params);  
  7.   
  8.     params = get_dpll_mpu_params();  
  9.     do_setup_dpll(&dpll_mpu_regs, params);  
  10.   
  11.     params = get_dpll_per_params();  
  12.     do_setup_dpll(&dpll_per_regs, params);  
  13.     writel(0x300, &cmwkup->clkdcoldodpllper);  
  14.   
  15.     params = get_dpll_ddr_params();  
  16.     do_setup_dpll(&dpll_ddr_regs, params);  
  17. }  
static void setup_dplls(void)
{
    const struct dpll_params *params;

    params = get_dpll_core_params();
    do_setup_dpll(&dpll_core_regs, params);

    params = get_dpll_mpu_params();
    do_setup_dpll(&dpll_mpu_regs, params);

    params = get_dpll_per_params();
    do_setup_dpll(&dpll_per_regs, params);
    writel(0x300, &cmwkup->clkdcoldodpllper);

    params = get_dpll_ddr_params();
    do_setup_dpll(&dpll_ddr_regs, params);
}

直接看倒數第二行,

[cpp] view plain copy print ?
  1. const struct dpll_params *get_dpll_ddr_params(void)  
  2. {  
  3.     struct am335x_baseboard_id header;  
  4.   
  5.     enable_i2c0_pin_mux();  
  6.     i2c_init(CONFIG_SYS_OMAP24_I2C_SPEED, CONFIG_SYS_OMAP24_I2C_SLAVE);  
  7.     if (read_eeprom(&header) < 0)  
  8.         puts(”Could not get board ID.\n”);  
  9.   
  10.     if (board_is_evm_sk(&header))  
  11.         return &dpll_ddr_evm_sk;  
  12.     else if (board_is_bone_lt(&header))  
  13.         return &dpll_ddr_bone_black;  
  14.     else if (board_is_evm_15_or_later(&header))  
  15.         return &dpll_ddr_evm_sk;  
  16.     else  
  17.         return &dpll_ddr;  
  18. }  
const struct dpll_params *get_dpll_ddr_params(void)
{
    struct am335x_baseboard_id header;

    enable_i2c0_pin_mux();
    i2c_init(CONFIG_SYS_OMAP24_I2C_SPEED, CONFIG_SYS_OMAP24_I2C_SLAVE);
    if (read_eeprom(&header) < 0)
        puts("Could not get board ID.\n");

    if (board_is_evm_sk(&header))
        return &dpll_ddr_evm_sk;
    else if (board_is_bone_lt(&header))
        return &dpll_ddr_bone_black;
    else if (board_is_evm_15_or_later(&header))
        return &dpll_ddr_evm_sk;
    else
        return &dpll_ddr;
}

    這個函式的主要功能就是從EEPROM中讀取到板子的資訊,因為TI有很多開發板,每一個開發板上面的引腳配置是不一樣的,SDRAM的容量也不一樣,所以TI就把這些關鍵的board資訊放到了一塊EEPROM上,通過讀取這個EEPROM上的內容來判斷如何實現進一步配置!

    那麼首先就是EEPROM的讀取,為了讀取EEPROM,就必須先要初始化I2C介面,利用:

    1.    enable_i2c0_pin_mux();//這一步完成I2C引腳的定義!

    2.    i2c_init( CONFIG_SYS_OMAP24_I2C_SPEED, CONFIG_SYS_OMAP24_I2C_SLAVE);來完成I2C的配置,包括時鐘頻率等

    以上兩步完成以後,就可以開始真正的讀取EEPROM裡的內容了。實際上一開始就先定義一個struct am335x_baseboard_id header結構體,這個結構體是一個區域性變數,所以裡面的元素都是無效的,通過讀取EEPROM中的資料,完成這個結構體的修改,所以下一步,就是執行read_eeprom(&header) ,並將讀過來的資料儲存到header結構體中!

    我們會在第4篇開始介紹這個EEPROM的操作,敬請期待