首页 > 代码库 > Tiny4412 u-boot分析(2)u-boot启动流程

Tiny4412 u-boot分析(2)u-boot启动流程

从大方面来说,u-boot的启动分成两个阶段,第一个阶段主要的职责是准备初始化的环境,主要有以下几点

①设置异常向量表

②把CPU的工作模式设置为SVC32模式

③关闭中断、MMU和cache

④关闭看门狗

⑤初始化内存、时钟、串口

⑥设置堆栈

⑦代码搬移

⑧清bss段

⑨跳转到c语言中执行(第二阶段)

此时系统还没有进入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 dont 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分析

②启动内核过程

。。。。。。

Tiny4412 u-boot分析(2)u-boot启动流程