1. 程式人生 > >AM335X(轉) AM335x啟動

AM335X(轉) AM335x啟動

AM335x啟動

 

參考檔案:

1、TI.Reference_Manual_1.pdf

http://pan.baidu.com/s/1c1BJNtm

2、TI_AM335X.pdf

http://pan.baidu.com/s/1geNOYI3

晶片到uboot啟動流程 :ROM → MLO(SPL)→ uboot.img。

rom為晶片內部的固化的程式,使用者不能修改。內部rom程式流程:

Booting的方式可以通過引腳SYSBOOT[15...0]來配置。SYSBoot[15...0]=LCD_DATA[15...0]引腳,這些引腳的狀態會被上電覆位後獲取。

 

AM335x 中bootloader被分成了 3 個部分:

第一級 bootloader:引導載入程式,板子上電後會自動執行這些程式碼,如選擇哪種方式啟動(NAND,SDcard,UART。。。),然後跳轉轉到第二級 bootloader。這些程式碼應該是存放在 176KB 的 ROM 中。

 

第二級 bootloader:MLO(SPL),用以硬體初始化:關閉看門狗,關閉中斷,設定 CPU 時鐘頻率、速度等操作。然後會跳轉到第三級bootloader。MLO檔案應該會被對映到 64 KB的 Internal SRAM 中。

 

第三級 bootloader:uboot.img,C程式碼的入口。

 

其中第一級 bootloader 是板子固化的,第二級和第三級是通過編譯 uboot 所得的。

u-boot-SPL編譯

也就是說spl的編譯是編譯uboot的一部分,和uboot.bin走的是兩條編譯流程,這個要重點注意。 
正常來說,會先編譯主體uboot,也就是uboot.bin.再編譯uboot-spl,也就是uboot-spl.bin,雖然編譯命令是一起的,但是編譯流程是分開的。

在uboot的頂層目錄Makefile中有:

spl/u-boot-spl.bin: spl/u-boot-spl
@:
spl/u-boot-spl: tools prepare (if(if(CONFIG_OF_SEPARATE),dts/dt.dtb)
(Q)(Q)(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all

指出編譯u-boot-spl.bin的連結是在u-boot的頂層目錄下的scripts/make.spl裡定義的,且要編譯u-boot-spl.bin首先要定義CONFIG_SPL(在u-boot的頂層目錄下的configs目錄對應的AM335X_evm_defconfig有定義)

在檔案scripts/makedile.spl中:

spl/u-boot-spl.bin依賴如下

           ||

spl/u-boot-spl-nodtb.bin

           ||

spl/u-boot-spl

           ||

u-boot-spl-init,  u-boot-spl-main, spl/u-boot-spl.ld 最後通過cmd_u-boot-spl來生成spl/u-boot-spl。

重點定義:

  1、CONFIG_SPL:configs/AM335x_evm_defconfig,用於指定是否需要編譯SPL,也就是是否需要編譯出uboot-spl.bin檔案

  2、CONFIG_SPL_TEXT_BASE   定義在板子的對應的config檔案中

  3、CONFIG_SPL_BUILD 

  • 在編譯spl過程中,會配置 
    u-boot-2016.03/scripts/Makefile.spl中定義瞭如下 KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD 也就是說在編譯uboot-spl.bin的過程中,CONFIG_SPL_BUILD這個巨集是被定義的。                                                                                                                                                                                                                                                                                                                                                                                                                    更加具體參考:

  • http://www.cnblogs.com/leaven/p/6296140.html。

U-boot-spl程式碼流程

通過u-boot-spl編譯的指令碼(Makefile.spl)知u-boot-spl入口:u-boot-2016.03/arch/arm/cpu/armv7/u-boot-spl.lds

ENTRY(_start)

所以uboot-spl的程式碼入口函式是_start 
對應於路徑project-X/u-boot/arch/arm/lib/vector.S的_start,後續就是從這個函式開始分析。

uboot-spl需要做的事情

CPU初始剛上電的狀態。需要小心的設定好很多狀態,包括cpu狀態、中斷狀態、MMU狀態等等。 
在armv7架構的uboot-spl,主要需要做如下事情

  • 關閉中斷,svc模式
  • 禁用MMU、TLB
  • 晶片級、板級的一些初始化操作 
    • IO初始化
    • 時鐘
    • 記憶體
    • 選項,串列埠初始化
    • 選項,nand flash初始化
    • 其他額外的操作
  • 載入u-boot.img,跳轉到u-boot.img.

上述工作,也就是uboot-spl程式碼流程的核心。

程式碼流程

1、程式碼整體流程

程式碼整體流程如下,以下列出來的就是spl核心函式。 
_start———–>reset————–>關閉中斷 
………………………………| 
………………………………———->cpu_init_cp15———–>關閉MMU,TLB 
………………………………| 
………………………………———->cpu_init_crit————->lowlevel_init————->CPU級初始化 
………………………………| 
………………………………———->_main————–>board_init_f_alloc_reserve & board_init_f_init_reserve & board_init_f———->board_init_r載入u-boot.img,跳轉到u-boot.img. 
board_init_f,board_init_r執行時已經是C語言環境了。在這裡需要結束掉SPL的工作,跳轉到u-boot.img中。

2、_start

上述已經說明了_start是整個spl的入口,其程式碼如下: 
arch/arm/lib/vector.S

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    b   reset

會跳轉到reset中。 
注意,spl的流程在reset中就應該被結束,也就是說在reset中,就應該轉到到BL2,也就是uboot中了。 
後面看reset的實現。

3、reset

建議先參考[kernel 啟動流程] (第二章)第一階段之——設定SVC、關閉中斷,瞭解一下為什麼要設定SVC、關閉中斷以及如何操作。目的:svc模式主要用於軟體中斷和OS作業系統。若是中斷未關閉,CPU在初始化階段有可能產生中斷,但是中斷處理函式還未就緒(未對中斷進行處理),容易使CPU halt停止工作。

程式碼如下: 
arch/arm/cpu/armv7/start.S

    .globl  reset
    .globl  save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b   save_boot_params
save_boot_params_ret:
    /*
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
     * except if in HYP mode already
     */
    mrs r0, cpsr
    and r1, r0, #0x1f       @ mask mode bits
    teq r1, #0x1a       @ test for HYP mode
    bicne   r0, r0, #0x1f @ clear all mode bits orrne r0, r0, #0x13 @ set SVC mode orr r0, r0, #0xc0 @ disable FIQ and IRQ msr cpsr,r0 @@ 以上通過設定CPSR暫存器裡設定CPU為SVC模式,禁止中斷 @@ 具體操作可以參考《[kernel 啟動流程] (第二章)第一階段之——設定SVC、關閉中斷》的分析 /* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_cp15 @@ 呼叫cpu_init_cp15,初始化協處理器CP15,從而禁用MMU和TLB。 @@ 後面會有一小節進行分析 bl cpu_init_crit @@ 呼叫cpu_init_crit,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化 @@ 後面會有一小節進行分析 #endif bl _main @@ 跳轉到主函式,也就是要載入BL2以及跳轉到BL2的主體部分

 

4、cpu_init_cp15

建議先參考[kernel 啟動流程] (第六章)第一階段之——開啟MMU兩篇文章的分析。 
cpu_init_cp15主要用於對cp15協處理器進行初始化,其主要目的就是關閉其MMU和TLB。 
程式碼如下(去掉無關部分的程式碼): 
arch/arm/cpu/armv7/start.S

ENTRY(cpu_init_cp15)
    /*
     * Invalidate L1 I/D
     */
    mov r0, #0          @ set up for MCR
    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
    mcr p15, 0, r0, c7, c5, 0 @ invalidate icache mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array mcr p15, 0, r0, c7, c10, 4 @ DSB mcr p15, 0, r0, c7, c5, 4 @ ISB @@ 這裡只需要知道是對CP15處理器的部分暫存器清零即可。 @@ 將協處理器的c7\c8清零等等,各個暫存器的含義請參考《ARM的CP15協處理器的暫存器》 /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 @ clear bits 13 (--V-) bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB #ifdef CONFIG_SYS_ICACHE_OFF bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache #else orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache #endif mcr p15, 0, r0, c1, c0, 0 @@ 通過上述的文章的介紹,我們可以知道cp15的c1暫存器就是MMU控制器 @@ 上述對MMU的一些位進行清零和置位,達到關閉MMU和cache的目的,具體的話去看一下上述文章吧。 ENDPROC(cpu_init_cp15)

5、cpu_init_crit

cpu_init_crit,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化。其程式碼核心就是lowlevel_init,如下 
arch/arm/cpu/armv7/start.S

ENTRY(cpu_init_crit)
    /*
     * Jump to board specific initialization...
     * The Mask ROM will have already initialized
     * basic memory. Go here to bump up clock rate and handle
     * wake up conditions.
     */
    b   lowlevel_init       @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

所以說lowlevel_init就是這個函式的核心。 
lowlevel_init一般是由板級程式碼自己實現的。但是對於某些平臺來說,也可以使用通用的lowlevel_init,其定義在arch/arm/cpu/armv7/lowlevel_init.S中 
以Am335x為例,在移植過程中,就需要在lowlevel_init.S里加入一些簡單的板級初始化,例如在lowlevle_init.s------->s_init中:

arch/arm/cpu/armv7/am33xx/board.c

複製程式碼
void s_init(void)
{
/*
* The ROM will only have set up sufficient pinmux to allow for the
* first 4KiB NOR to be read, we must finish doing what we know of
* the NOR mux in this space in order to continue.
*/
#ifdef CONFIG_NOR_BOOT
enable_norboot_pin_mux();
#endif
watchdog_disable();               //arch/arm/cpu/armv7/am33xx/board.c

set_uart_mux_conf();            // xx/board/ti/am335x/board.c
setup_clocks_for_console();   // arch/arm/cpu/armv7/am335x/Clock_am33xx.c
uart_soft_reset();                 // arch/arm/cpu/armv7/am33xx/board.c
#if defined(CONFIG_SPL_AM33XX_ENABLE_RTC32K_OSC)  // include/configs/ti_am335x_common.h
/* Enable RTC32K clock */
rtc32k_enable();                  // arch/arm/cpu/armv7/am33xx/board.c
#endif
}
複製程式碼

 

(其實只要實現了lowlevel_init了就好,沒必要說在哪裡是實現,但是通常規範都是建立了lowlevel_init.S來專門實現lowlevel_init函式)。

6、_main

spl的main的主要目標是呼叫board_init_f進行先前的板級初始化動作,板級初始化之後呼叫board_init_r主要設計為,載入u-boot.img到DDR上並且跳轉到u-boot.img中。DDR在板級初始化中完成--board_init_f。 
由於board_init_f是以C語言的方式實現,所以需要先構造C語言環境。 
注意:uboot-spl和uboot的程式碼是通用的,其區別就是通過CONFIG_SPL_BUILD巨集來進行區分的。 
所以以下程式碼中,我們只列出spl相關的部分,也就是被CONFIG_SPL_BUILD包含的部分。 
arch/arm/lib/crt0.S

複製程式碼
ENTRY(_main)

/*
* Set up initial C runtime environment and call board_init_f(0). 因為後面是C語言環境,首先是設定堆疊
*/

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr    sp, =(CONFIG_SPL_STACK)                                                   //設定堆疊為後邊呼叫board_init_f做準備
#else
ldr    sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M)    /* v7M forbids using SP as BIC destination */
mov    r3, sp
bic    r3, r3, #7
mov    sp, r3
#else
bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
mov    r0, sp
bl    board_init_f_alloc_reserve                                     //2. 為gd_t結構體保留空間理論上有些函式可以用了,例如:preloader_console_init()初始化串列埠,common/init/board_init.c


mov    sp, r0
/* set up gd here, outside any C code */
mov    r9, r0
bl    board_init_f_init_reserve                      //3. 初始化gd_t(清零)  common/init/board_init.c

                    //gd_t的地址存在r9暫存器中,結構體中存放的是全域性引數                 
mov r0, #0
bl    board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/

ldr    sp, [r9, #GD_START_ADDR_SP]    /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M)    /* v7M forbids using SP as BIC destination */
mov    r3, sp
bic    r3, r3, #7
mov    sp, r3
#else
bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
ldr    r9, [r9, #GD_BD]    /* r9 = gd->bd */
sub    r9, r9, #GD_SIZE    /* new GD is below bd */

adr    lr, here
ldr    r0, [r9, #GD_RELOC_OFF]    /* r0 = gd->reloc_off */
add    lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr    lr, #1    /* As required by Thumb-only */
#endif
ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */
b    relocate_code
here:
/*
* now relocate vectors
*/

bl    relocate_vectors

/* Set up final (full) environment */

bl    c_runtime_cpu_setup    /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl    spl_relocate_stack_gd
cmp    r0, #0
movne    sp, r0
movne    r9, r0
# endif
ldr    r0, =__bss_start    /* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
ldr    r3, =__bss_end    /* this is auto-relocated! */
mov    r1, #0x00000000    /* prepare zero to clear BSS */

subs    r2, r3, r0    /* r2 = memset len */
bl    memset
#else
ldr    r1, =__bss_end    /* this is auto-relocated! */
mov    r2, #0x00000000    /* prepare zero to clear BSS */

clbss_l:cmp    r0, r1    /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt    lo
#endif
strlo    r2, [r0]    /* clear 32-bit BSS word */
addlo    r0, r0, #4    /* move to next */
blo    clbss_l
#endif

#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr    r1, [r9, #GD_RELOCADDR]    /* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr    lr, =board_init_r    /* this is auto-relocated! */
bx    lr
#else
ldr    pc, =board_init_r    /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif

ENDPROC(_main)
複製程式碼

 

程式碼拆分如下: 
(1)因為後面是C語言環境,首先是設定堆疊

    ldr sp, =(CONFIG_SPL_STACK)
@@ 設定堆疊為CONFIG_SPL_STACK

    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
@@ 堆疊是8位元組對齊,2^7bit=2^3byte=8byte mov r0, sp @@ 把堆疊地址存放到r0暫存器中

 

關於CONFIG_SPL_STACK,我們通過前面的文章
我們已經知道am335x的BL1(spl)是執行在RAM的,並且RAM的地址空間是0x402F0400-0x402FFFFF,RAM前面的部分放的是BL1的程式碼部分,所以把RAM最後的空間用來當作堆疊。 
所以CONFIG_SPL_STACK定義如下: 
include/configs/ti_am335x_common.h

                     --->ti_armv7_common.h

                    ---->ti_armv7_keystone2.h

#define CONFIG_SPL_STACK    (0x402F0400+32*1024+32*1024+8*1024-4)//自己計算結果可能不正確
  • 1
  • 1

注意:上述還不是最終的堆疊地址,只是暫時的堆疊地址!!!

(2)為GD分配空間

    bl  board_init_f_alloc_reserve
@@ 把堆疊的前面一部分空間分配給GD使用

    mov sp, r0
@@ 重新設定堆疊指標SP

    /* set up gd here, outside any C code */
    mov r9, r0
@@ 儲存GD的地址到r9暫存器中

注意:雖然sp的地址和GD的地址是一樣的,但是堆疊是向下增長的,而GD則是佔用該地址後面的部分,所以不會有衝突的問題。 
關於GD,也就是struct global_data,可以簡單的理解為uboot的全域性變數都放在了這裡,比較重要,所以後續有會寫篇文章說明一下global_data。這裡只需要知道在開始C語言環境的時候需要先為這個結構體分配空間。 
board_init_f_alloc_reserve實現如下 
common/init/board_init.c

ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
    /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
    top = rounddown(top-sizeof(struct global_data), 16); // 現將top(也就是r0暫存器,前面說過存放了暫時的指標地址),減去sizeof(struct global_data),也就是預留出一部分空間給sizeof(struct global_data)使用。 // rounddown表示向下16個位元組對其 return top; // 到這裡,top就存放了GD的地址,也是SP的地址 //把top返回,注意,返回後,其實還是存放在了r0暫存器中。 }

還有一點,其實GD在spl中沒什麼使用,主要是用在uboot中,但在uboot中的時候還需要另外分配空間,在講述uboot流程的時候會說明。

(3)初始化GD空間 
前面說了,此時r0暫存器存放了GD的地址。

    bl  board_init_f_init_reserve

 

board_init_f_init_reserve實現如下 
common/init/board_init.c 
編譯SPL的時候_USE_MEMCPY巨集沒有開啟,所以我們去掉了_USE_MEMCPY的無關部分。

void board_init_f_init_reserve(ulong base)
{
    struct global_data *gd_ptr;
    int *ptr;
    /*
     * clear GD entirely and set it up.
     * Use gd_ptr, as gd may not be properly set yet.
     */

    gd_ptr = (struct global_data *)base; // 從r0獲取GD的地址 /* zero the area */ for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); ) *ptr++ = 0; // 對GD的空間進行清零 }

 

(4)跳轉到板級前期的初始化函式中 

u-boot-2016.03/arch/arm/cpu/armv7/Am33xx/board.c
如下程式碼

    bl  board_init_f

 

board_init_f需要由板級程式碼自己實現。

u-boot-2016.03/arch/arm/cpu/armv7/Am33xx/board.c

1 void board_init_f(ulong dummy)
2 {
3 board_early_init_f();
4 preloader_console_init();  //初始化uart,使用Puts函式
5 sdram_init();
6 }

 

u-boot-2016.03/arch/arm/cpu/armv7/Am33xx/board.c

1 int board_early_init_f(void)
2 {
3     prcm_init();    //系統外設時鐘初始化使能,SPI,I2C,uart,gpio.....
4     set_mux_conf_regs();  //系統外設引功能配置,spi,I2C,uart,gpio.
5     return 0;
6 }

 

u-boot-2016.03/arch/arm/cpu/armv7/Am33xx/clock.c

1 void prcm_init()
2 {
3     enable_basic_clocks();
4     scale_vcores();
5     setup_dplls();
6 }

 

u-boot-2016.03/board/ti/am335x/board.c

複製程式碼
void set_mux_conf_regs(void)
{
    
    __maybe_unused struct am335x_baseboard_id header;

    if (read_eeprom(&header) < 0)
        puts("Could not get board ID.\n");
    
    enable_board_pin_mux(&header);
        puts("enable_board_pin.\n");
}
複製程式碼

u-boot-2016.03/board/ti/am335x/MUX.c

複製程式碼
void enable_board_pin_mux(struct am335x_baseboard_id *header)
{
    /* Do board-specific muxes. */
    if (board_is_bone(header)) {
        /* Beaglebone pinmux */
        configure_module_pin_mux(mii1_pin_mux);
        configure_module_pin_mux(mmc0_pin_mux);
#if defined(CONFIG_NAND)
        configure_module_pin_mux(nand_pin_mux);
#elif defined(CONFIG_NOR)
        configure_module_pin_mux(bone_norcape_pin_mux);
#else
        configure_module_pin_mux(mmc1_pin_mux);
#endif
    } else if (board_is_gp_evm(header)) {
        /* General Purpose EVM */
        unsigned short profile = detect_daughter_board_profile();
        configure_module_pin_mux(rgmii1_pin_mux);
        configure_module_pin_mux(mmc0_pin_mux);
        /* In profile #2 i2c1 and spi0 conflict. */
        if (profile & ~PROFILE_2)
            configure_module_pin_mux(i2c1_pin_mux);
        /* Profiles 2 & 3 don't have NAND */
#ifdef CONFIG_NAND
        if (profile & ~(PROFILE_2 | PROFILE_3))
            configure_module_pin_mux(nand_pin_mux);
#endif
        else if (profile == PROFILE_2) {
            configure_module_pin_mux(mmc1_pin_mux);
            configure_module_pin_mux(spi0_pin_mux);
        }
    } else if (board_is_idk(header)) {
        /* Industrial Motor Control (IDK) */
        configure_module_pin_mux(mii1_pin_mux);
        configure_module_pin_mux(mmc0_no_cd_pin_mux);
    } else if (board_is_evm_sk(header)) {
        /* Starter Kit EVM */
        configure_module_pin_mux(i2c1_pin_mux); // 配置I2C1對應的引腳為功能引腳,u-boot-2016.03/board/ti/am335x/mux.c
        configure_module_pin_mux(gpio0_7_pin_mux);
        configure_module_pin_mux(rgmii1_pin_mux);
        configure_module_pin_mux(mmc0_pin_mux_sk_evm);
    } else if (board_is_bone_lt(header)) {
        /* Beaglebone LT pinmux */
        configure_module_pin_mux(mii1_pin_mux);
        configure_module_pin_mux(mmc0_pin_mux);
#if defined(CONFIG_NAND) && defined(CONFIG_EMMC_BOOT)
        configure_module_pin_mux(nand_pin_mux);
#elif defined(CONFIG_NOR) && defined(CONFIG_EMMC_BOOT)
        configure_module_pin_mux(bone_norcape_pin_mux);
#else
        configure_module_pin_mux(mmc1_pin_mux);
#endif
    } else {
        puts("Unknown board, cannot configure pinmux.");
        hang();
    }
}
複製程式碼

 

也就是說

void board_init_f(ulong dummy)
{
  1、board_early_init_f------>prmc_init(u-boot-2016.03/arch/arm/cpu/armv7/Am33xx/clock.c)------->enable_basic_clocks(u-boot-                                                                             使能CPU各外設包括gpio時鐘                                                 2016.03/arch/arm/cpu/armv7/Am33xx/clock_am3xx.c)

                                    ------>set_mux_conf_regs(u-boot-2016.03/board/ti/am335x/board.c)------>enable_board_pin_mux(&header)配置板級各外設引腳功能(UART,MMC,SPI,)u-boot-2016.03/board/ti/am335x/Mux.c

  2、sdram_init()--------------》(u-boot-2016.03/board/ti/am335x/board.c)

          --------->未完待續
}

 

SDRAM初始化,只有SDRAM初始化完成了,才能為下一步的u-boot執行提供執行環境,這一部分也是要根據板上的SDRAM晶片型號來初始化的。

u-boot-2016.03/board/ti/am335x/board.c

複製程式碼
void sdram_init(void)
{
    __maybe_unused struct am335x_baseboard_id header;

    if (read_eeprom(&header) < 0)
        puts("Could not get board ID.\n");

    if (board_is_evm_sk(&header)) {
        /*
         * EVM SK 1.2A and later use gpio0_7 to enable DDR3.
         * This is safe enough to do on older revs.
         */
        gpio_request(GPIO_DDR_VTT_EN, "ddr_vtt_en"); //請求GPIO0_7腳是否有效,AM335x共有GPIO=32*4.如果GPIO_DDR_VTT_EN的數值在0~127之間,返回0
        gpio_direction_output(GPIO_DDR_VTT_EN, 1);//使GPIO的第8腳,GPIO0_7輸出1,使能SDRAM晶片的電源
    }

    if (board_is_evm_sk(&header))
        config_ddr(303, &ioregs_evmsk, &ddr3_data,
               &ddr3_cmd_ctrl_data, &ddr3_emif_reg_data, 0);
    else if (board_is_bone_lt(&header))
        config_ddr(400, &ioregs_bonelt,
               &ddr3_beagleblack_data,
               &ddr3_beagleblack_cmd_ctrl_data,
               &ddr3_beagleblack_emif_reg_data, 0);
    else if (board_is_evm_15_or_later(&header))
        config_ddr(303, &ioregs_evm15, &ddr3_evm_data,
               &ddr3_evm_cmd_ctrl_data, &ddr3_evm_emif_reg_data, 0);
    else
        config_ddr(266, &ioregs, &ddr2_data,
               &ddr2_cmd_ctrl_data, &ddr2_emif_reg_data, 0);
               debug(">>spl:SDRAM_init()\n");
               puts(">>spl:SDRAM_init()\n");

}
複製程式碼

 板級的初級初始化之後board_init_f()會為後續的呼叫board_init_r()函式提供更多的堆疊的空間(放在了SDRAM中)

bl spl_relocate_stack_gd

u-boot-2016.03/common/spl/spl.c

複製程式碼
ulong spl_relocate_stack_gd(void)
{
#ifdef CONFIG_SPL_STACK_R
    gd_t *new_gd;
    ulong ptr = CONFIG_SPL_STACK_R_ADDR;

#ifdef CONFIG_SPL_SYS_MALLOC_SIMPLE
    if (CONFIG_SPL_STACK_R_MALLOC_SIMPLE_LEN) {
        if (!(gd->flags & GD_FLG_SPL_INIT))
            panic_str("spl_init must be called before heap reloc");

        ptr -= CONFIG_SPL_STACK_R_MALLOC_SIMPLE_LEN;
        gd->malloc_base = ptr;
        gd->malloc_limit = CONFIG_SPL_STACK_R_MALLOC_SIMPLE_LEN;
        gd->malloc_ptr = 0;
    }
#endif
    /* Get stack position: use 8-byte alignment for ABI compliance */
    ptr = CONFIG_SPL_STACK_R_ADDR - roundup(sizeof(gd_t),16);
    new_gd = (gd_t *)ptr;
    memcpy(new_gd, (void *)gd, sizeof(gd_t));
#if !defined(CONFIG_ARM)
    gd = new_gd;
#endif
    return ptr;
#else
    return 0;
#endif
}
複製程式碼

堆疊空間分配好之後就可以用board_init_r了。

u-boot-2016.03/common/spl/spl.c

複製程式碼
board_init_r
{
     1、經行memory的malloc池的初始化
      2、timer_init() 初始化時鐘 arch/arm/cpu/armv7/omap_common
      3、spl_board_init() arch/arm/cpu/armv7/omap_common/boot-common.c
      4、jump_to_image_no_args(&spl_image) // 跳轉u-boot入口地址 entry_point,entry_point是由u-boot.img頭部資訊提供的
5、SPL結束其生命,將控制權交給u-boot/Linux
}
複製程式碼
arch/arm/cpu/armv7/omap_common/boot-common.c
複製程式碼
void spl_board_init(void)
{
    /*
     * Save the boot parameters passed from romcode.
     * We cannot delay the saving further than this,
     * to prevent overwrites.
     */
    save_omap_boot_params(); // 這裡講boot_device的引數傳遞給spl_boot_list(自己理解),確定以什麼方式載入u-boot.img(uart or spi or mmc or nand....)

    /* Prepare console output */
   // preloader_console_init();

#if defined(CONFIG_SPL_NAND_SUPPORT) || defined(CONFIG_SPL_ONENAND_SUPPORT)
    gpmc_init();
#endif
#ifdef CONFIG_SPL_I2C_SUPPORT
    i2c_init(CONFIG_SYS_OMAP24_I2C_SPEED, CONFIG_SYS_OMAP24_I2C_SLAVE);
#endif
#if defined(CONFIG_AM33XX) && defined(CONFIG_SPL_MUSB_NEW_SUPPORT)
    arch_misc_init();
#endif
#if defined(CONFIG_HW_WATCHDOG)
    hw_watchdog_init();
#endif
#ifdef CONFIG_AM33XX
    am33xx_spl_board_init();
#endif
}
複製程式碼

 更加詳細的啟動順序,及相關記憶體地址分配參考:

連結: https://pan.baidu.com/s/1hseK4cO 密碼: m9ci

參考檔案:

1、TI.Reference_Manual_1.pdf

http://pan.baidu.com/s/1c1BJNtm

2、TI_AM335X.pdf

http://pan.baidu.com/s/1geNOYI3

晶片到uboot啟動流程 :ROM → MLO(SPL)→ uboot.img。

rom為晶片內部的固化的程式,使用者不能修改。內部rom程式流程:

Booting的方式可以通過引腳SYSBOOT[15...0]來配置。SYSBoot[15...0]=LCD_DATA[15...0]引腳,這些引腳的狀態會被上電覆位後獲取。

 

AM335x 中bootloader被分成了 3 個部分:

第一級 bootloader:引導載入程式,板子上電後會自動執行這些程式碼,如選擇哪種方式啟動(NAND,SDcard,UART。。。),然後跳轉轉到第二級 bootloader。這些程式碼應該是存放在 176KB 的 ROM 中。

 

第二級 bootloader:MLO(SPL),用以硬體初始化:關閉看門狗,關閉中斷,設定 CPU 時鐘頻率、速度等操作。然後會跳轉到第三級bootloader。MLO檔案應該會被對映到 64 KB的 Internal SRAM 中。

 

第三級 bootloader:uboot.img,C程式碼的入口。

 

其中第一級 bootloader 是板子固化的,第二級和第三級是通過編譯 uboot 所得的。

u-boot-SPL編譯

也就是說spl的編譯是編譯uboot的一部分,和uboot.bin走的是兩條編譯流程,這個要重點注意。 
正常來說,會先編譯主體uboot,也就是uboot.bin.再編譯uboot-spl,也就是uboot-spl.bin,雖然編譯命令是一起的,但是編譯流程是分開的。

在uboot的頂層目錄Makefile中有:

spl/u-boot-spl.bin: spl/u-boot-spl
@:
spl/u-boot-spl: tools prepare (if(if(CONFIG_OF_SEPARATE),dts/dt.dtb)
(Q)(Q)(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all

指出編譯u-boot-spl.bin的連結是在u-boot的頂層目錄下的scripts/make.spl裡定義的,且要編譯u-boot-spl.bin首先要定義CONFIG_SPL(在u-boot的頂層目錄下的configs目錄對應的AM335X_evm_defconfig有定義)

在檔案scripts/makedile.spl中:

spl/u-boot-spl.bin依賴如下

           ||

spl/u-boot-spl-nodtb.bin

           ||

spl/u-boot-spl

           ||

u-boot-spl-init,  u-boot-spl-main, spl/u-boot-spl.ld 最後通過cmd_u-boot-spl來生成spl/u-boot-spl。

重點定義:

  1、CONFIG_SPL:configs/AM335x_evm_defconfig,用於指定是否需要編譯SPL,也就是是否需要編譯出uboot-spl.bin檔案

  2、CONFIG_SPL_TEXT_BASE   定義在板子的對應的config檔案中

  3、CONFIG_SPL_BUILD 

  • 在編譯spl過程中,會配置 
    u-boot-2016.03/scripts/Makefile.spl中定義瞭如下 KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD 也就是說在編譯uboot-spl.bin的過程中,CONFIG_SPL_BUILD這個巨集是被定義的。                                                                                                                                                                                                                                                                                                                                                                                                                    更加具體參考:

  • http://www.cnblogs.com/leaven/p/6296140.html。

U-boot-spl程式碼流程

通過u-boot-spl編譯的指令碼(Makefile.spl)知u-boot-spl入口:u-boot-2016.03/arch/arm/cpu/armv7/u-boot-spl.lds

ENTRY(_start)

所以uboot-spl的程式碼入口函式是_start 
對應於路徑project-X/u-boot/arch/arm/lib/vector.S的_start,後續就是從這個函式開始分析。

uboot-spl需要做的事情

CPU初始剛上電的狀態。需要小心的設定好很多狀態,包括cpu狀態、中斷狀態、MMU狀態等等。 
在armv7架構的uboot-spl,主要需要做如下事情

  • 關閉中斷,svc模式
  • 禁用MMU、TLB
  • 晶片級、板級的一些初始化操作 
    • IO初始化
    • 時鐘
    • 記憶體
    • 選項,串列埠初始化
    • 選項,nand flash初始化
    • 其他額外的操作
  • 載入u-boot.img,跳轉到u-boot.img.

上述工作,也就是uboot-spl程式碼流程的核心。

程式碼流程

1、程式碼整體流程

程式碼整體流程如下,以下列出來的就是spl核心函式。 
_start———–>reset————–>關閉中斷 
………………………………| 
………………………………———->cpu_init_cp15———–>關閉MMU,TLB 
………………………………| 
………………………………———->cpu_init_crit————->lowlevel_init————->CPU級初始化 
………………………………| 
………………………………———->_main————–>board_init_f_alloc_reserve & board_init_f_init_reserve & board_init_f———->board_init_r載入u-boot.img,跳轉到u-boot.img. 
board_init_f,board_init_r執行時已經是C語言環境了。在這裡需要結束掉SPL的工作,跳轉到u-boot.img中。

2、_start

上述已經說明了_start是整個spl的入口,其程式碼如下: 
arch/arm/lib/vector.S

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    b   reset

會跳轉到reset中。 
注意,spl的流程在reset中就應該被結束,也就是說在reset中,就應該轉到到BL2,也就是uboot中了。 
後面看reset的實現。

3、reset

建議先參考[kernel 啟動流程] (第二章)第一階段之——設定SVC、關閉中斷,瞭解一下為什麼要設定SVC、關閉中斷以及如何操作。目的:svc模式主要用於軟體中斷和OS作業系統。若是中斷未關閉,CPU在初始化階段有可能產生中斷,但是中斷處理函式還未就緒(未對中斷進行處理),容易使CPU halt停止工作。

程式碼如下: 
arch/arm/cpu/armv7/start.S

    .globl  reset
    .globl  save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b   save_boot_params
save_boot_params_ret:
    /*
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
     * except if in HYP mode already
     */
    mrs r0, cpsr
    and r1, r0, #0x1f       @ mask mode bits
    teq r1, #0x1a       @ test for HYP mode
    bicne   r0, r0, #0x1f @ clear all mode bits orrne r0, r0, #0x13 @ set SVC mode orr r0, r0, #0xc0 @ disable FIQ and IRQ msr cpsr,r0 @@ 以上通過設定CPSR暫存器裡設定CPU為SVC模式,禁止中斷 @@ 具體操作可以參考《[kernel 啟動流程] (第二章)第一階段之——設定SVC、關閉中斷》的分析 /* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_cp15 @@ 呼叫cpu_init_cp15,初始化協處理器CP15,從而禁用MMU和TLB。 @@ 後面會有一小節進行分析 bl cpu_init_crit @@ 呼叫cpu_init_crit,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化 @@ 後面會有一小節進行分析 #endif bl _main @@ 跳轉到主函式,也就是要載入BL2以及跳轉到BL2的主體部分

 

4、cpu_init_cp15

建議先參考[kernel 啟動流程] (第六章)第一階段之——開啟MMU兩篇文章的分析。 
cpu_init_cp15主要用於對cp15協處理器進行初始化,其主要目的就是關閉其MMU和TLB。 
程式碼如下(去掉無關部分的程式碼): 
arch/arm/cpu/armv7/start.S

ENTRY(cpu_init_cp15)
    /*
     * Invalidate L1 I/D
     */
    mov r0, #0          @ set up for MCR
    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
    mcr p15, 0, r0, c7, c5, 0 @ invalidate icache mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array mcr p15, 0, r0, c7, c10, 4 @ DSB mcr p15, 0, r0, c7, c5, 4 @ ISB @@ 這裡只需要知道是對CP15處理器的部分暫存器清零即可。 @@ 將協處理器的c7\c8清零等等,各個暫存器的含義請參考《ARM的CP15協處理器的暫存器》 /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 @ clear bits 13 (--V-) bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB #ifdef CONFIG_SYS_ICACHE_OFF bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache #else orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache #endif mcr p15, 0, r0, c1, c0, 0 @@ 通過上述的文章的介紹,我們可以知道cp15的c1暫存器就是MMU控制器 @@ 上述對MMU的一些位進行清零和置位,達到關閉MMU和cache的目的,具體的話去看一下上述文章吧。 ENDPROC(cpu_init_cp15)

5、cpu_init_crit

cpu_init_crit,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化。其程式碼核心就是lowlevel_init,如下 
arch/arm/cpu/armv7/start.S

ENTRY(cpu_init_crit)
    /*
     * Jump to board specific initialization...
     * The Mask ROM will have already initialized
     * basic memory. Go here to bump up clock rate and handle
     * wake up conditions.
     */
    b   lowlevel_init       @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

所以說lowlevel_init就是這個函式的核心。 
lowlevel_init一般是由板級程式碼自己實現的。但是對於某些平臺來說,也可以使用通用的lowlevel_init,其定義在arch/arm/cpu/armv7/lowlevel_init.S中 
以Am335x為例,在移植過程中,就需要在lowlevel_init.S里加入一些簡單的板級初始化,例如在lowlevle_init.s------->s_init中:

arch/arm/cpu/armv7/am33xx/board.c

複製程式碼
void s_init(void)
{
/*
* The ROM will only have set up sufficient pinmux to allow for the
* first 4KiB NOR to be read, we must finish doing what we know of
* the NOR mux in this space in order to continue.
*/
#ifdef CONFIG_NOR_BOOT
enable_norboot_pin_mux();
#endif
watchdog_disable();               //arch/arm/cpu/armv7/am33xx/board.c

set_uart_mux_conf();            // xx/board/ti/am335x/board.c
setup_clocks_for_console();   // arch/arm/cpu/armv7/am335x/Clock_am33xx.c
uart_soft_reset();                 // arch/arm/cpu/armv7/am33xx/board.c
#if defined(CONFIG_SPL_AM33XX_ENABLE_RTC32K_OSC)  // include/configs/ti_am335x_common.h
/* Enable RTC32K clock */
rtc32k_enable();                  // arch/arm/cpu/armv7/am33xx/board.c
#endif
}
複製程式碼

 

(其實只要實現了lowlevel_init了就好,沒必要說在哪裡是實現,但是通常規範都是建立了lowlevel_init.S來專門實現lowlevel_init函式)。

6、_main

spl的main的主要目標是呼叫board_init_f進行先前的板級初始化動作,板級初始化之後呼叫board_init_r主要設計為,載入u-boot.img到DDR上並且跳轉到u-boot.img中。DDR在板級初始化中完成--board_init_f。 
由於board_init_f是以C語言的方式實現,所以需要先構造C語言環境。 
注意:uboot-spl和uboot的程式碼是通用的,其區別就是通過CONFIG_SPL_BUILD巨集來進行區分的。 
所以以下程式碼中,我們只列出spl相關的部分,也就是被CONFIG_SPL_BUILD包含的部分。 
arch/arm/lib/crt0.S

複製程式碼
ENTRY(_main)

/*
* Set up initial C runtime environment and call board_init_f(0). 因為後面是C語言環境,首先是設定堆疊
*/

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr    sp, =(CONFIG_SPL_STACK)                                                   //設定堆疊為後邊呼叫board_init_f做準備
#else
ldr    sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M)    /* v7M forbids using SP as BIC destination */
mov    r3, sp
bic    r3, r3, #7
mov    sp, r3
#else
bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
mov    r0, sp
bl    board_init_f_alloc_reserve                                     //2. 為gd_t結構體保留空間理論上有些函式可以用了,例如:preloader_console_init()初始化串列埠,common/init/board_init.c


mov    sp, r0
/* set up gd here, outside any C code */
mov    r9, r0
bl    board_init_f_init_reserve                      //3. 初始化gd_t(清零)  common/init/board_init.c

                    //gd_t的地址存在r9暫存器中,結構體中存放的是全域性引數                 
mov r0, #0
bl    board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/

ldr    sp, [r9, #GD_START_ADDR_SP]    /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M)    /* v7M forbids using SP as BIC destination */
mov    r3, sp
bic    r3, r3, #7
mov    sp, r3
#else
bic    sp, sp, #7    /* 8-byte alignment for ABI compliance */
#endif
ldr    r9, [r9, #GD_BD]    /* r9 = gd->bd */
sub    r9, r9, #GD_SIZE    /* new GD is below bd */

adr    lr, here
ldr    r0, [r9, #GD_RELOC_OFF]    /* r0 = gd->reloc_off */
add    lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr    lr, #1    /* As required by Thumb-only */
#endif
ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */
b    relocate_code
here:
/*
* now relocate vectors
*/

bl    relocate_vectors

/* Set up final (full) environment */

bl    c_runtime_cpu_setup    /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl    spl_relocate_stack_gd
cmp    r0, #0
movne    sp, r0
movne    r9, r0
# endif
ldr    r0, =__bss_start    /* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
ldr    r3, =__bss_end    /* this is auto-relocated! */
mov    r1, #0x00000000    /* prepare zero to clear BSS */

subs    r2, r3, r0    /* r2 = memset len */
bl    memset
#else
ldr    r1, =__bss_end    /* this is auto-relocated! */
mov    r2, #0x00000000    /* prepare zero to clear BSS */

clbss_l:cmp    r0, r1    /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt    lo
#endif
strlo    r2, [r0]    /* clear 32-bit BSS word */
addlo    r0, r0, #4    /* move to next */
blo    clbss_l
#endif

#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr    r1, [r9, #GD_RELOCADDR]    /* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr    lr, =board_init_r    /* this is auto-relocated! */
bx    lr
#else
ldr    pc, =board_init_r    /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif

ENDPROC(_main)
複製程式碼

 

程式碼拆分如下: 
(1)因為後面是C語言環境,首先是設定堆疊

    ldr sp, =(CONFIG_SPL_STACK)
@@ 設定堆疊為CONFIG_SPL_STACK

    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
@@ 堆疊是8位元組對齊,2^7bit=2^3byte=8byte mov r0, sp @@ 把堆疊地址存放到r0暫存器中

 

關於CONFIG_SPL_STACK,我們通過前面的文章
我們已經知道am335x的BL1(spl)是執行在RAM的,並且RAM的地址空間是0x402F0400-0x402FFFFF,RAM前面的部分放的是BL1的程式碼部分,所以把RAM最後的空間用來當作堆疊。 
所以CONFIG_SPL_STACK定義如下: 
include/configs/ti_am335x_common.h

                     --->ti_armv7_common.h

                    ---->ti_armv7_keystone2.h

#define CONFIG_SPL_STACK    (0x402F0400+32*1024+32*1024+8*1024-4)//自己計算結果可能不正確
  • 1
  • 1

注意:上述還不是最終的堆疊地址,只是暫時的堆疊地址!!!

(2)為GD分配空間

    bl  board_init_f_alloc_reserve
@@ 把堆疊的前面一部分空間分配給GD使用

    mov sp, r0
@@ 重新設定堆疊指標SP

    /* set up gd here, outside any C code */
    mov r9, r0
@@ 儲存GD的地址到r9暫存器中

注意:雖然sp的地址和GD的地址是一樣的,但是堆疊是向下增長的,而GD則是佔用該地址後面的部分,所以不會有衝突的問題。 
關於GD,也就是struct global_data,可以簡單的理解為uboot的全域性變數都放在了這裡,比較重要,所以後續有會寫篇文章說明一下global_data。這裡只需要知道在開始C語言環境的時候需要先為這個結構體分配空間。 
board_init_f_alloc_reserve實現如下 
common/init/board_init.c

ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
    /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
    top = rounddown(top-sizeof(struct global_data), 16); // 現將top(也就是r0暫存器,前面說過存放了暫時的指標地址),減去sizeof(struct global_data),也就是預留出一部分空間給sizeof(struct global_data)使用。 // rounddown表示向下16個位元組對其 return top; // 到這裡,top就存放了GD的地址,也是SP的地址 //把top返回,注意,返回後,其實還是存放在了r0暫存器中。 }

還有一點,其實GD在spl中沒什麼使用,主要是用在uboot中,但在uboot中的時候還需要另外分配空間,在