Tiny4412 u-boot分析(2)u-boot啓動流程

從大方面來講,u-boot的啓動分紅兩個階段,第一個階段主要的職責是準備初始化的環境,主要有如下幾點數組

①設置異常向量表數據結構

②把CPU的工做模式設置爲SVC32模式ide

③關閉中斷、MMU和cache函數

④關閉看門狗工具

⑤初始化內存、時鐘、串口oop

⑥設置堆棧fetch

⑦代碼搬移ui

⑧清bss段this

⑨跳轉到c語言中執行(第二階段)spa

此時系統尚未進入C語言的運行階段,並無堆棧,也就不須要額外的RAM。

第二階段在上一段創建好C語言運行環境的基礎上,進行各類外設的初始化,並循環執行用戶命令。主要流程圖以下

當咱們執行make命令來構建u-boot時,它的構建過程是:首先使用交叉編譯工具將各目錄下的源文件生成目標文件(*.o),目標文件生成後,會將若干個目標文件組合成靜態庫文件(*.a),最後經過連接各個靜態庫文件生成ELF格式的可執行文件。在連接的過程當中,須要根據連接腳本(通常是各個以lds爲後綴的文本文件),肯定目標文件的各個段,連接文件一般是board/<board>/目錄中的u-boot.lds文件。通常在連接腳本中經過

ENTRY(_start)

來指定入口爲_start標號,經過文本段(.text)的第一個目標來制定u-boot入口文件。因此咱們經過這個連接腳本文件能夠肯定u-boot執行的入口。

Tiny4412 u-boot的連接腳本內容爲

//  board/samsung/tiny4412/u-boot.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000;
    . = ALIGN(4);
    .text    :
    {
        arch/arm/cpu/armv7/start.o    (.text)
        board/samsung/tiny4412/libtiny4412.o (.text)
        arch/arm/cpu/armv7/exynos/libexynos.o    (.text)
        *(.text)
    }
    . = ALIGN(4);
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
    . = ALIGN(4);
    .data : {
        *(.data)
    }
    . = ALIGN(4);
    . = .;
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;
    . = ALIGN(4);
    .rel.dyn : {
        __rel_dyn_start = .;
        *(.rel*)
        __rel_dyn_end = .;
    }
    .dynsym : {
        __dynsym_start = .;
        *(.dynsym)
    }
    .bss __rel_dyn_start (OVERLAY) : {
        __bss_start = .;
        *(.bss)
         . = ALIGN(4);
        _end = .;
    }
    /DISCARD/ : { *(.dynstr*) }
    /DISCARD/ : { *(.dynamic*) }
    /DISCARD/ : { *(.plt*) }
    /DISCARD/ : { *(.interp*) }
    /DISCARD/ : { *(.gnu*) }
}

在本連接腳本文件中,定義了起始地址爲0x00000000,每一個段使用4字節對齊(.ALIGN(4)),幾個段分別爲代碼段(.text)、只讀數據段(.rodata)、數據段(.data)其中,代碼段的第一個目標爲arch/arm/cpu/armv7/start.o,在其中定義了映像文件的入口_start。

下面來具體分析一下這個start.S。

在文件的一開始定義了映像的入口_start和中斷向量表。

.globl _start  //定義u-boot入口
_start: b       reset   //設置中斷向量表
        ldr     pc, _undefined_instruction
        ldr     pc, _software_interrupt
        ldr     pc, _prefetch_abort
        ldr     pc, _data_abort
        ldr     pc, _not_used
        ldr     pc, _irq
        ldr     pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt:    .word software_interrupt
_prefetch_abort:        .word prefetch_abort
_data_abort:            .word data_abort
_not_used:              .word not_used
_irq:                   .word irq 
_fiq:                   .word fiq 
_pad:                   .word 0x12345678 /* now 16*4=64 */

系統開機進入到u-boot運行時,首先進入到u-boot的入口_start標號處,而後經過 b  reset 跳轉到reset標號處,咱們就到reset標號一探究竟。

/*
 * the actual reset code
 */
reset:
    /*
         *設置CPU工做模式爲SVC32模式
     * set the cpu to SVC32 mode
     */
    mrs    r0, cpsr
    bic    r0, r0, #0x1f
    orr    r0, r0, #0xd3
    msr    cpsr,r0
//......
//調用 cpu_init_crit 
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_crit
#endif

首先會將CPU的工做模式設置爲svc32模式,而後便調用 cpu_init_crit ,須要注意的是,這裏使用的是 bl 指令,也就是說在運行完 cpu_init_crit 標號處的代碼以後,會經過

mov    pc, lr            @ back to my caller

指令回到reset中繼續執行

bl    cpu_init_crit

下面的指令(因此這裏咱們應該使用 調用來描述更爲貼切)。下面咱們去看一下 cpu_init_crit 指令處作了哪些事

/*************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************/
cpu_init_crit:
       
        //調用 cache_init
    bl cache_init
    
    /*
         *使  L1 I/D 無效
     * 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
    /*
         * 關閉 MMU 和 cache
     * 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 12 (Z---) BTB
    mcr    p15, 0, r0, c1, c0, 0
    /*
     * 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.
     */
    mov    ip, lr            @ persevere link reg across call
        //調用 lowlevel_init
    bl    lowlevel_init        @ go setup pll,mux,memory
    mov    lr, ip            @ restore link
        //返回到 reset 標號繼續執行
    mov    pc, lr            @ back to my caller
/*

首先分析 cache_init ,它被定義在  board/samsung/tiny4412/lowlevel_init.S 文件中

    .globl cache_init
cache_init:
    mov pc, lr

能夠看出來,這是一個空函數(暫且將它叫作函數-_-!!)。

接下來咱們就要去分析 lowlevel_init 了,它也被定義在board/samsung/tiny4412/lowlevel_init.S 文件中

    .globl lowlevel_init
lowlevel_init:
    
    //初始化串口
    bl uart_asm_init
    
  //Read booting information
  //讀取啓動信息
    bl read_om
    /* when we already run in ram, we don't need to relocate U-Boot.
     * and actually, memory controller must be configured before U-Boot
     * is running in ram.
     */
    ldr    r0, =0xff000fff
    bic    r1, pc, r0        /* r0 <- current base addr of code */
    ldr    r2, _TEXT_BASE    /* r1 <- original base addr in ram */
    bic    r2, r2, r0        /* r0 <- current base addr of code */
    cmp r1, r2            /* compare r0, r1 */
    beq after_copy        /* r0 == r1 then skip sdram init and u-boot.bin loading */
   //初始化內存
    /* Memory initialize */
    bl mem_ctrl_asm_init
  //初始化系統時鐘
    /* init system clock */
    bl system_clock_init
    
  
  /*
      eg:
        1: ;A
        cmp r0, #0
        beq 1f ; r0==0那麼向前跳轉到B處執行
        bne 1b ; 不然向後跳轉到A處執行
        :1: ;B
  */
  
  // 向前跳轉到1: 標號處執行
    b  1f
1:
  //初始化 trust zone
    bl tzpc_init
    b load_uboot
after_copy:
#ifdef CONFIG_ENABLE_MMU
    bl enable_mmu
#endif
    /* store second boot information in u-boot C level variable */
    ldr r0, =CONFIG_PHY_UBOOT_BASE
    sub r0, r0, #8
    ldr r1, [r0]
    ldr r0, _second_boot_info
    str r1, [r0]
    /* Print 'K' */
    ldr    r0, =S5PV310_UART_CONSOLE_BASE
    ldr    r1, =0x4b4b4b4b
    str    r1, [r0, #UTXH_OFFSET]
  //第二階段入口,調用C語言函數:board_init_f
    ldr r0, _board_init_f
    mov pc, r0
    
_board_init_f:
    .word board_init_f
load_uboot:
    ldr    r0, =INF_REG_BASE
    ldr    r1, [r0, #INF_REG3_OFFSET]
    cmp    r1, #BOOT_NAND
    beq    nand_boot
    cmp    r1, #BOOT_ONENAND
    beq    onenand_boot
    cmp     r1, #BOOT_MMCSD
    beq     mmcsd_boot
    cmp    r1, #BOOT_EMMC
    beq    emmc_boot
    cmp    r1, #BOOT_EMMC_4_4
    beq    emmc_boot_4_4
    cmp     r1, #BOOT_NOR
    beq     nor_boot
    cmp     r1, #BOOT_SEC_DEV
    beq     mmcsd_boot
nand_boot:
    mov    r0, #0x1000
    bl    copy_uboot_to_ram
    b    after_copy
onenand_boot:
    bl    onenand_bl2_copy /*goto 0x1010*/
    b    after_copy
mmcsd_boot:
#ifdef CONFIG_SMDKC220
//#ifdef CONFIG_CLK_BUS_DMC_200_400
    ldr    r0, =ELFIN_CLOCK_BASE
    ldr    r2, =CLK_DIV_FSYS2_OFFSET
    ldr    r1, [r0, r2]
    orr r1, r1, #0xf
    str r1, [r0, r2]
//#endif
#else
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
    ldr    r0, =ELFIN_CLOCK_BASE
    ldr    r2, =CLK_DIV_FSYS2_OFFSET
    ldr    r1, [r0, r2]
    orr r1, r1, #0xf
    str r1, [r0, r2]
#endif
#endif
    bl      movi_uboot_copy
    b       after_copy
emmc_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
    ldr    r0, =ELFIN_CLOCK_BASE
    ldr    r2, =CLK_DIV_FSYS1_OFFSET
    ldr    r1, [r0, r2]
    orr     r1, r1, #0xf
    str     r1, [r0, r2]
#endif
    bl    emmc_uboot_copy
    b    after_copy
emmc_boot_4_4:
    /* read TCBCNT to get Transferred CIU card byte count */
    ldr r0, =0x1255005c
    ldr r1, [r0]
    ldr r2, =0x6000
    cmp r1, r2
    /* store second boot information in DRAM */
    ldr r0, =CONFIG_PHY_UBOOT_BASE
    sub r0, r0, #8
    mov r3, #0
    movlo r3, #1
    str r3, [r0]
    /* if transferred CIU card byte count >= 0x6000 (24 KB)  */
    /* BL1 and BL2 are loaded from emmc 4.4                  */
    /* Otherwise BL1 and BL2 are loaded from sdmmc ch2.      */
    blo mmcsd_boot
    /* mmc ch4 devider value change */
    bl    mmc_ch4_devider_change
    /* u-boot image copy from boot partition to DRAM. */
    bl    emmc_4_4_uboot_copy
    /* Exit Boot mood */
    bl    emmc_4_4_endbootOp_eMMC
    b    after_copy

在 lowlevel_init 中,主要作了一些初始化工做,好比系統時鐘、內存、串口等的初始化工做,而後初始化堆棧、清bss段,並進行了代碼搬移,爲第二階段C語言程序運行提供保障。最後經過

ldr r0, _board_init_f
    mov pc, r0

指令跳轉到第二階段C語言函數 board_init_f 函數處。接着咱們就去分析一下這個函數。

在分析board_init_f函數以前,先來了解如下gd_t數據結構

// arch/arm/include/asm/global_data.h
typedef    struct    global_data {
    bd_t        *bd;
    unsigned long    flags;
    unsigned long    baudrate;
    unsigned long    have_console;    /* serial_init() was called */
    unsigned long    env_addr;    /* Address  of Environment struct */
    unsigned long    env_valid;    /* Checksum of Environment valid? */
    unsigned long    fb_base;    /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char    vfd_type;    /* display type */
#endif
#ifdef CONFIG_FSL_ESDHC
    unsigned long    sdhc_clk;
#endif
#ifdef CONFIG_AT91FAMILY
    /* "static data" needed by at91's clock.c */
    unsigned long    cpu_clk_rate_hz;
    unsigned long    main_clk_rate_hz;
    unsigned long    mck_rate_hz;
    unsigned long    plla_rate_hz;
    unsigned long    pllb_rate_hz;
    unsigned long    at91_pllb_usb_init;
#endif
#ifdef CONFIG_ARM
    /* "static data" needed by most of timer.c on ARM platforms */
    unsigned long    timer_rate_hz;
    unsigned long    tbl;
    unsigned long    tbu;
    unsigned long long    timer_reset_value;
    unsigned long    lastinc;
#endif
    unsigned long    relocaddr;    /* Start address of U-Boot in RAM */
    phys_size_t    ram_size;    /* RAM size */
    unsigned long    mon_len;    /* monitor len */
    unsigned long    irq_sp;        /* irq stack pointer */
    unsigned long    start_addr_sp;    /* start_addr_stackpointer */
    unsigned long    reloc_off;
#if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
    unsigned long    tlb_addr;
#endif
    void        **jt;        /* jump table */
    char        env_buf[32];    /* buffer for getenv() before reloc. */
} gd_t;
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

u-boot中使用一個結構體gd_t來存儲全局區的數據,使用一個存儲在寄存器中的指針gd來記錄全局數據區的地址。

DECLARE_GLOBAL_DATA_PTR

在board/samsung/tiny4412/tiny4412.c被聲明。

u-boot 中還有一個數據結構 bd_t用來存放板級相關的全局數據,是gd_t中結構體指針成員bd的結構體類型。

//   arch/arm/include/asm/u-boot.h
typedef struct bd_info {
    int            bi_baudrate;    /* serial console baudrate */
    unsigned long    bi_ip_addr;    /* IP Address */
    ulong            bi_arch_number;    /* unique id for this board */
    ulong            bi_boot_params;    /* where this board expects params */
    struct                /* RAM configuration */
    {
    ulong start;
    ulong size;
    }            bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;

u-boot啓動內核時要給內核傳遞參數,這時須要使用gd_t、bd_t結構體中的信息來設置標記列表。瞭解了這兩個數據結構咱們就去分析一下board_init_f函數

//   arch/arm/lib/board.c
void board_init_f(ulong bootflag)
{
    bd_t *bd;
    init_fnc_t **init_fnc_ptr;
    gd_t *id;
    ulong addr, addr_sp;
    //計算全局數據結構的地址,保存在gd指針中
    /* Pointer is writable since we allocated a register for it */
    gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
    /* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");
    
    memset((void*)gd, 0, sizeof (gd_t));
    gd->mon_len = _bss_end_ofs;
    逐個調用init_sequence數組的初始化函數
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang();
        }
    }
    debug ("monitor len: %08lX\n", gd->mon_len);
    /*
     * Ram is setup, size stored in gd !!
     */
    debug ("ramsize: %08lX\n", gd->ram_size);
    //填充gd數據結構
    gd->bd->bi_baudrate = gd->baudrate;
    /* Ram ist board specific, so move it to board code ... */
    dram_init_banksize();
    display_dram_config();    /* and display it */
    gd->relocaddr = addr;
    gd->start_addr_sp = addr_sp;
    gd->reloc_off = addr - _TEXT_BASE;
    debug ("relocation Offset is: %08lx\n", gd->reloc_off);
    memcpy(id, (void *)gd, sizeof (gd_t));
    //調用arch/arm/cpu/armv7/start.S  relocate_code
    relocate_code(addr_sp, id, addr);
    /* NOTREACHED - relocate_code() does not return */
}

u-boot使用一個init_sequence數組來存儲大多數開發板都要執行的初始化函數的函數指針

// arch/arm/lib/board.c
typedef int (init_fnc_t)(void);
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
    arch_cpu_init,        /* basic arch cpu dependent setup */
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,
#endif
    timer_init,        /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
    get_clocks,
#endif
    env_init,        /* initialize environment */
#if defined(CONFIG_S5P6450) && !defined(CONFIG_S5P6460_IP_TEST)
    init_baudrate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
#endif
    console_init_f,        /* stage 1 init of console */
    display_banner,        /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,        /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,        /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
#endif
    dram_init,        /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI)
    arm_pci_init,
#endif
    NULL,
};

board_init_f函數在調用完初始化函數指針、填充完gd結構以後,調用了arch/arm/cpu/armv7/start.S中的relocate_code去看一下relocate_code作了什麼

    .globl    relocate_code
relocate_code:
    mov    r4, r0    /* save addr_sp */
    mov    r5, r1    /* save addr of gd */
    mov    r6, r2    /* save addr of destination */
    /* Set up the stack                            */
stack_setup:
    mov    sp, r4
    adr    r0, _start
#if defined(CONFIG_S5PC110) && defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
    sub    r0, r0, #16
#endif
#ifndef CONFIG_PRELOADER
    cmp    r0, r6
    beq    clear_bss        /* skip relocation */
#endif
    mov    r1, r6            /* r1 <- scratch for copy_loop */
    ldr    r2, _TEXT_BASE
    ldr    r3, _bss_start_ofs
    add    r2, r0, r3        /* r2 <- source end address        */
copy_loop:
    ldmia    r0!, {r9-r10}        /* copy from source address [r0]    */
    stmia    r1!, {r9-r10}        /* copy to   target address [r1]    */
    cmp    r0, r2            /* until source end address [r2]    */
    blo    copy_loop
#ifndef CONFIG_PRELOADER
    /*
     * fix .rel.dyn relocations
     */
    ldr    r0, _TEXT_BASE        /* r0 <- Text base */
    sub    r9, r6, r0        /* r9 <- relocation offset */
    ldr    r10, _dynsym_start_ofs    /* r10 <- sym table ofs */
    add    r10, r10, r0        /* r10 <- sym table in FLASH */
    ldr    r2, _rel_dyn_start_ofs    /* r2 <- rel dyn start ofs */
    add    r2, r2, r0        /* r2 <- rel dyn start in FLASH */
    ldr    r3, _rel_dyn_end_ofs    /* r3 <- rel dyn end ofs */
    add    r3, r3, r0        /* r3 <- rel dyn end in FLASH */
fixloop:
    ldr    r0, [r2]        /* r0 <- location to fix up, IN FLASH! */
    add    r0, r0, r9        /* r0 <- location to fix up in RAM */
    ldr    r1, [r2, #4]
    and    r7, r1, #0xff
    cmp    r7, #23            /* relative fixup? */
    beq    fixrel
    cmp    r7, #2            /* absolute fixup? */
    beq    fixabs
    /* ignore unknown type of fixup */
    b    fixnext
fixabs:
    /* absolute fix: set location to (offset) symbol value */
    mov    r1, r1, LSR #4        /* r1 <- symbol index in .dynsym */
    add    r1, r10, r1        /* r1 <- address of symbol in table */
    ldr    r1, [r1, #4]        /* r1 <- symbol value */
    add    r1, r1, r9        /* r1 <- relocated sym addr */
    b    fixnext
fixrel:
    /* relative fix: increase location by offset */
    ldr    r1, [r0]
    add    r1, r1, r9
fixnext:
    str    r1, [r0]
    add    r2, r2, #8        /* each rel.dyn entry is 8 bytes */
    cmp    r2, r3
    blo    fixloop
clear_bss:
    ldr    r0, _bss_start_ofs
    ldr    r1, _bss_end_ofs
    ldr    r3, _TEXT_BASE        /* Text base */
    mov    r4, r6            /* reloc addr */
    add    r0, r0, r4
    add    r1, r1, r4
    mov    r2, #0x00000000        /* clear                */
clbss_l:str    r2, [r0]        /* clear loop...            */
    add    r0, r0, #4
    cmp    r0, r1
    bne    clbss_l
#endif    /* #ifndef CONFIG_PRELOADER */
/*
 * We are done. Do not return, instead branch to second part of board
 * initialization, now running from RAM.
 */
 
//調用board_init_r
jump_2_ram:
    ldr    r0, _board_init_r_ofs
    adr    r1, _start
    add    lr, r0, r1
@    add    lr, lr, r9
    /* setup parameters for board_init_r */
    mov    r0, r5        /* gd_t */
    mov    r1, r6        /* dest_addr */
    /* jump to it ... */
    mov    pc, lr
_board_init_r_ofs:
    .word board_init_r - _start

可見,最後調用了board_init_r函數

//   arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
    char *s;
    bd_t *bd;
    ulong malloc_start;
    gd = id;
    bd = gd->bd;
    gd->flags |= GD_FLG_RELOC;    /* tell others: relocation done */
    monitor_flash_len = _bss_start_ofs;
    debug ("monitor flash len: %08lX\n", monitor_flash_len);
    board_init();    /* Setup chipselects */
    debug ("Now running in RAM - U-Boot at: %08lx\n", dest_addr);
    /* The Malloc area is immediately below the monitor copy in DRAM */
    malloc_start = dest_addr - TOTAL_MALLOC_LEN;
    mem_malloc_init(malloc_start, TOTAL_MALLOC_LEN);
    //初始化MMC
#ifdef CONFIG_GENERIC_MMC
    mmc_initialize(bd);
#endif
     //初始化環境變量
    /* initialize environment */
    env_relocate();
     //將環境變量中的IP填充到gd結構體
    /* IP Address */
    gd->bd->bi_ip_addr = getenv_IPaddr("ipaddr");
    stdio_init();    /* get the devices list going. */
    jumptable_init();
      //初始化並使能中斷
     /* set up exceptions */
    interrupt_init();
    /* enable exceptions */
    enable_interrupts();
    /* Initialize from environment */
    if ((s = getenv("loadaddr")) != NULL) {
        load_addr = simple_strtoul(s, NULL, 16);
    }
     //進入到main_loop
    /* main_loop() can return to retry autoboot, if so just run it again. */
    for (;;) {
        main_loop();
    }
    /* NOTREACHED - no way out of command loop except booting */
}

最後調用了main_loop

待完成:

①main_loop分析

②啓動內核過程

。。。。。。

相關文章
相關標籤/搜索