u-boot分为两个阶段stage1和stage2,stage1只负责一些与特定CPU体系结构有关的代码,完成简单的初始化工作,使用汇编语言实现。stage2使用C语言实现以完成更多功能。stage1执行完之后会跳转到stage2执行。本文只做启动过程分析,不对代码实现做详细分析,具体u-boot第一阶段如何实现初始化工作、代码搬运以及第二阶段如何实现命令处理、系统启动等等的功能以后有机会再分析。

第一阶段

首先来看一下u-boot的链接脚本文件,简单来说 链接脚本文件就是控制所有(通过编译程序源文件生成的)“.o”文件排布组合成想要的可执行文件。链接脚本文件中指定好了什么地址存放什么文件,编译完成之后使用“ld”链接工具根据链接脚本文件将所有“.o”文件都组合起来生成目标文件。下面是Hi3519的链接脚本文件u-boot.lds:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000; 把定位器符号置为0x00000000

    . = ALIGN(4); 4字节对齐
    .text   :
    {
        __text_start = .;
        arch/arm/cpu/hi3519v101/start.o (.text)
        drivers/ddr/ddr_training_impl.o (.text)
        drivers/ddr/ddr_training_ctl.o (.text)
        drivers/ddr/ddr_training_boot.o (.text)
        drivers/ddr/ddr_training_custom.o (.text)
        __init_end = .;
        ASSERT(((__init_end - __text_start) < 0x16000), "init sections too big!");
        *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

    . = ALIGN(4);
    .data : { *(.data) }

    . = ALIGN(4);
    .got : { *(.got) }

    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}

详细的lds文件语法不多做介绍,因为我也不会,但是可以明显看出来几点:
①、程序从0x00000000处开始执行
②、arch/arm/cpu/hi3519v101/start.o放在了输出文件的最开始部分,start.o由start.S编译得到,所以其为start.S最开始运行的代码。
③、start.o之后还有很多ddr相关的文件被链接进来,应该是DRAM初始化部分的程序。

从上面得到的最关键的信息是start.S为最先执行的程序文件,那么下面分析一下start.S的汇编程序,做了一些简单的注释:

#include <config.h>
#include <version.h>
/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


.globl _start
下面是中断向量表
_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 */
__blank_zone_start:
.fill 1024*5,1,0
__blank_zone_end:

.globl _blank_zone_start
_blank_zone_start:
.word __blank_zone_start


.globl _blank_zone_end
_blank_zone_end:
.word __blank_zone_end

    .balignl 16,0xdeadbeef
/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * setup Memory and board specific bits prior to relocation.
 * relocate armboot to ram
 * setup stack
 *
 *************************************************************************/

_TEXT_BASE:
    .word   TEXT_BASE

.globl _armboot_start
_armboot_start:
    .word _start

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
    .word __bss_start

.globl _bss_end
_bss_end:
    .word _end

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
    .word   0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
    .word 0x0badc0de
#endif

_clr_remap_fmc_entry:
    .word   FMC_TEXT_ADRS + do_clr_remap - TEXT_BASE
_clr_remap_ddr_entry:
    .word   MEM_BASE_DDR + do_clr_remap - TEXT_BASE
_clr_remap_ram_entry:
    .word   RAM_START_ADRS + do_clr_remap - TEXT_BASE

_copy_abort_code:
    .word  copy_abort_code
/*
 * the actual reset code
 */

reset:  ----复位函数/入口函数
    /*
     * set the cpu to SVC32 mode  ----设置CPU进入SVC32模式
     */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0xd3
    msr cpsr,r0

    /*
     * 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

    /*
     * disable MMU stuff and caches  ----关闭MMU和cache缓冲
     */
    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
    mcr p15, 0, r0, c1, c0, 0

    /*
     *  read system register REG_SC_GEN2  ----读取REG_SC_GEN2寄存器并判断ziju标志位
         *  check if ziju flag
     */
    ldr r0, =SYS_CTRL_REG_BASE
    ldr r1, [r0, #REG_SC_GEN2]
    ldr r2, =0x7a696a75          /* magic for "ziju" */
    ldr r3, [r0, #REG_SC_GEN20]  /* pcie slave start up flag*/
    cmp r3, r2
    beq after_ziju
    cmp r1, r2
    bne normal_start_flow
    mov r1, sp                   /* save sp */
    str r1, [r0, #REG_SC_GEN2]  /* clear ziju flag */
after_ziju:
    /* init PLL/DDRC/pin mux/... */  ----初始化PLL时钟、DDR控制器、引脚复用等功能
    ldr r0, _blank_zone_start
    ldr r1, _TEXT_BASE
    sub r0, r0, r1
    cmp r3, r2
    beq pcie_slave_addr
    ldr r1, =RAM_START_ADRS
    ldr sp, =STACK_TRAINING
    b   ziju_ddr_init
pcie_slave_addr:
    ldr r1, =0x0
    ldr sp, =PCIE_SLV_STACK
    ldr r4, =SYS_CTRL_REG_BASE
    str r2, [r4, #HI3519V101_SYSBOOT10]
ziju_ddr_init:
    add r0, r0, r1
    mov r1, #0x0                 /* flags: 0->normal 1->pm */
    bl  init_registers           /* init PLL/DDRC/... */  ----跳转到init_registers函数初始化PLL和DDR控制器

    /* after ziju, we need ddr traning */
/*#ifdef CONFIG_DDR_TRAINING_V2*/
    ldr r0, =REG_BASE_SCTL
    bl  start_ddr_training       /* DDR training */  ----ziju(?)之后进行DDR测试
/*#endif*/

    ldr r0, =SYS_CTRL_REG_BASE
    ldr r2, =PCIE_SLV_DDR_INIT_FLG
    str r2, [r0, #HI3519V101_SYSBOOT9]
    ldr     r2, =0x7a696a75
    ldr r1, [r0, #HI3519V101_SYSBOOT10]
    cmp r1, r2
    beq pcie_slave_hold
    ldr r1, [r0, #REG_SC_GEN2]
    mov sp, r1               /* restore sp */
    ldr r1, [r0, #REG_SC_GEN3]
    mov pc, r1                   /* return to bootrom */
pcie_slave_hold:
    ldr r2, [r0, #HI3519V101_SYSBOOT9]
    ldr r1, =0x7964616f     /* complete flag */
    cmp r1, r2
    bne pcie_slave_hold
    ldr r1, =0x0
    str     r1, [r0, #HI3519V101_SYSBOOT9]
    mov pc, #0x0
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    b   .                        /* bug here */  ----死循环

normal_start_flow:  ----正常启动流程
    /* init serial and printf a string. */  ----初始化串口打印功能并使用printf打印一串字符
    ldr     sp, =STACK_TRAINING
    bl      uart_early_init  ----跳转到uart_early_init串口初始化函数(函数在uart.S文件中)
    bl      msg_main_cpu_startup  ----打印主CPU启动的信息到uart

    /*
     *  enable cci snoop for GSF and VDMA
     */
    ldr     r0, =CCI_PORT_CTRL_0
    mov     r3, #CCI_ENABLE_REQ
    str     r3, [r0]

4:      ldr     r0, =CCI_CTRL_STATUS
    ldr     r1, [r0]
    tst     r1, #1
    bne     4b

    /*
     *  enable cci snoop for core 0
     */
    ldr     r0, =CCI_PORT_CTRL_1
    mov     r3, #CCI_ENABLE_REQ
    str     r3, [r0]

5:      ldr     r0, =CCI_CTRL_STATUS
    ldr     r1, [r0]
    tst     r1, #1
    bne     5b

    @if running not boot from nand/spi/emmc,
    @we skipping boot_type checking.
    mov    r0, pc, lsr#24
    cmp    r0, #0x0
    bne    check_bootrom_type

check_boot_type:
        ldr     r0, =SYS_CTRL_REG_BASE
        ldr     r0, [r0, #REG_SYSSTAT]
        mov     r6, r0, lsr#5
    and     r6, #0x1
        cmp     r6, #0          @ [5]=0 fmc /* SPI Nor/Nand and Nand */
        ldreq   pc, _clr_remap_fmc_entry

    @otherwise, [31]=1 means boot from bootrom, err
    beq bug
check_bootrom_type:
    cmp    r0, #0x4
    /*boot from bootrom,we copy the uboot.bin to ram (0x4010500)*/
    ldreq  pc, _clr_remap_ram_entry

do_clr_remap:
        /* do clear remap */
    ldr     r4, =SYS_CTRL_REG_BASE
    ldr r0, [r4, #REG_SC_CTRL]

    @Set clear remap bit.
    orr r0, #(1<<8)
    str r0, [r4, #REG_SC_CTRL]

    /*
     * Set ACTLR.SMP to 1
     * This is a bug on Cortex-A7 MPCORE. see buglist of Cortex-A7
     * The D-caches are disabled when ACTLR.SMP is set to 0 regardless
     * of the value of the cache enable bit. so we must set SMP bit of
     * ACTLR register before enable D-cache
     */
    mrc     p15, 0, r0, c1, c0, 1
    orr     r0, #(1 << 6)
    mcr     p15, 0, r0, c1, c0, 1

    @enable I-Cache now
    mrc p15, 0, r0, c1, c0, 0
        orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
        mcr p15, 0, r0, c1, c0, 0

    @Check wether I'm running in dynamic mem bank
    mov r0, pc, lsr#28
    cmp r0, #8
    blo ddr_init

no_ddr_init:
    adrl    r0, _start
    b       copy_to_ddr

ddr_init:
    ldr     r0, _blank_zone_start
    ldr     r1, _TEXT_BASE
    sub     r0, r0, r1
    adrl    r1, _start
    add     r0, r0, r1
    mov     r1, #0          /* flags: 0->normal 1->pm */
    bl      init_registers

/*#ifdef CONFIG_DDR_TRAINING_V2*/
    ldr sp, =STACK_TRAINING
    ldr r0, =REG_BASE_SCTL
    bl  start_ddr_training       /* DDR training */
/*#endif*/
check_boot_mode:  ----检测boot模式
        ldr     r0, =SYS_CTRL_REG_BASE
        ldr     r0, [r0, #REG_SYSSTAT]
        mov     r6, r0, lsr#4
    and r6, #0x3
    cmp     r6, #BOOT_FROM_EMMC
    bne     copy_flash_to_ddr
#ifdef CONFIG_HIMCI_V200
emmc_boot:  ----从emmc中boot
    ldr     r0, _TEXT_BASE
    ldr     r1, _armboot_start
    ldr     r2, _bss_start
    sub     r1, r2, r1
    bl      emmc_boot_read
    b       jump_to_ddr
#endif

copy_flash_to_ddr:  ----从flash中读取到ddr中
    /* relocate SPI nor/nand Boot to DDR  */
    cmp r6, #BOOT_FROM_DDR
    beq copy_to_ddr
    ldr r0, =FMC_TEXT_ADRS      /* 0x1400_0000 */

copy_to_ddr:
    /* now, r0 stores __reset offset from where we get started */
    ldr     r1, _TEXT_BASE   /* r1 stores where we will copy uboot to */

    /* compare source and target address, *
     *if equal no copy to target address */
    cmp     r0, r1
    beq     copy_abort_code

    ldr     r2, _armboot_start
    ldr     r3, _bss_start
    sub     r2, r3, r2      /* r2 <- size of armboot */
    /* memcpy(r1, r0, r2) */
    bl      memcpy

jump_to_ddr:  ----跳转到DDR中执行程序
    ldr     r0, _TEXT_BASE
    ldr     pc, _copy_abort_code

copy_abort_code:
    ldr     r1, =0x00000000
        mov     r2, #0x4000
    /* memcpy(r1, r0, r2) */
    bl      memcpy

    /* Set up the stack */  ----初始化栈
stack_setup:
    ldr r0, _TEXT_BASE      @ upper 128 KiB: relocated uboot
    sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area
    sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo
#ifdef CONFIG_USE_IRQ
    sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ)
#endif
    sub sp, r0, #12     @ leave 3 words for abort-stack
    and sp, sp, #~7        @ 8 byte alinged for (ldr/str)d

    /* Clear BSS (if any). Is below tx (watch load addr - need space) */
clear_bss:  ----清零BSS段
    ldr r0, _bss_start      @ find start of bss segment
    ldr r1, _bss_end        @ stop here
    mov r2, #0x0        @ clear value
clbss_l:
    str r2, [r0]        @ clear BSS location
    cmp r0, r1          @ are we at the end yet
    add r0, r0, #4      @ increment clear index pointer
    bne clbss_l         @ keep clearing till at end

    ldr pc, _start_armboot  @ jump to C code  ----跳转到stage2阶段的start_armboot函数

_start_armboot: .word start_armboot

......

简单看完start.S文件中的程序之后可以发现start.S就是uboot的stage1阶段,主要完成了一些CPU相关的初始化包括时钟系统、DDR控制器、串调试口等,同时将代码从emmc或者flash中搬运到DDR RAM中,最后完成向start_armboot函数的跳转,进入到stage2阶段运行。

第二阶段

在stage1汇编代码中调用stage2的入口函数start_armboot进入第二阶段,这个阶段的程序使用C语言实现,start_armboot函数在arch\arm\lib\board.c文件中:

void start_armboot (void)
{
    init_fnc_t **init_fnc_ptr;
    char *s;
#ifdef CONFIG_HAS_SLAVE
    char *e;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
    unsigned long addr;
#endif

    /* Pointer is writable since we allocated a register for it */
    gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
    /* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");

    memset ((void*)gd, 0, sizeof (gd_t));
    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    memset (gd->bd, 0, sizeof (bd_t));

    gd->flags |= GD_FLG_RELOC;

    monitor_flash_len = _bss_start - _armboot_start;

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }

......  

    /* 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 */
}

这个函数很长很长,中间的我不予理睬,前面有一段代码:

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }

这个是调用一个函数指针数组init_sequence中的所有函数,这个写函数都是初始化函数,在同文件前面有定义:

init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
    arch_cpu_init,      /* basic arch cpu dependent setup */
#endif
    timer_init,     /* initialize timer before usb init */
    board_init,     /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
    interrupt_init,     /* set up exceptions */
#endif
//  timer_init,     /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
    get_clocks,
#endif
    env_init,       /* initialize environment */
    init_baudrate,      /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    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
/*  display_dram_config */
    NULL,
};

都是将初始化函数填到数组中,这样的做法是为了方便自己进行代码修改,执行自己需要的初始化函数。start_armboot 函数的最后面的死循环调用main_loop也是很重要的线索,这个main_loop 函数就是u-boot第二阶段的主循环,定义在common/main.c文件中,这个函数也是很长很长,主要就是实现u-boot的命令行的功能以及实现延时自动boot功能等。

void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
    static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
    int len;
#ifdef CONFIG_BOOT_RETRY_TIME
    int rc = 1;
#endif
    int flag;
#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    char *s;
    int bootdelay;
#endif
#ifdef CONFIG_PREBOOT
    char *p;
#endif
#ifdef CONFIG_BOOTCOUNT_LIMIT
    unsigned long bootcount = 0;
    unsigned long bootlimit = 0;
    char *bcs;
    char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)
    ulong bmp = 0;      /* default bitmap */
    extern int trab_vfd (ulong bitmap);

#ifdef CONFIG_MODEM_SUPPORT
    if (do_mdm_init)
        bmp = 1;    /* alternate bitmap */
#endif
    trab_vfd (bmp);
#endif  /* CONFIG_VFD && VFD_TEST_LOGO */

#ifdef CONFIG_BOOTCOUNT_LIMIT
    bootcount = bootcount_load();
    bootcount++;
    bootcount_store (bootcount);
    sprintf (bcs_set, "%lu", bootcount);
    setenv ("bootcount", bcs_set);
    bcs = getenv ("bootlimit");
    bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */

......

好,至此u-boot以及启动到了该去的主循环中,接下来可以进行linux内核的启动工作或者接收来自用户的命令并处理这些命令,引用网上一张图片来大致解释一下u-boot的执行过程:


U-BOOT启动过程_linux