u-boot(六) - 详细启动流程

一,u-boot启动第一阶段

1,启动流程

ENTRY(_start) //arch/arm/lib/vectors.S
----b resets //arch/arm/cpu/armv7/start.S
--------b save_boot_params
------------b save_boot_params_ret //将cpu的工作模式设置为SVC32模式(即管理模式),同时将中断禁止位与快速中断禁止位都设置为1, 以此屏蔽IRQ和FIQ的中断
--------bl    cpu_init_cp15 //关闭mmu,不需要它转换地址,直接操作寄存器方便快捷
--------bl    cpu_init_crit
------------b    lowlevel_init //arch/arm/cpu/armv7/lowlevel_init.S ,与特定开发板相关的初始化函数,在这个函数里会做一些pll初始化,如果不是从内存启动,则会做内存初始化,方便后续拷贝到内存中运行。
--------bl    _main //arch/arm/lib/crt0.S //初始化c语言环境,以便调用board_init_f函数。这个环境只提供了一个堆栈和一个存储GD(全局数据)结构的地方,两者都位于一些可用的RAM中。在调用board_init_f()之前,GD应该被归零。
------------bl          board_init_f_alloc_reserve // common/init/board_init.c,该函数主要作用是保留早期malloc区域,且为GD(全局数据区)留出空间,函数返回值也是r0,r0保存着预留早期malloc区域和GD后的地址
------------bl        board_init_f_init_reserve //common/init/board_init.c 将GD区域清零,返回最初malloc区域的地址
------------bl        board_init_f //common/board_f.c
----------------initcall_run_list(init_sequence_f)
------------b    relocate_code //arch/arm/lib/relocate.S,实现u-boot自身重定位的
----------------ldr       r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
----------------ldr       r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */ 从__image_copy_start地址到__image_copy_end地址中间包含了代码段、数据段以及只读数据段,但是不包括动态链接rel_dyn部分
----------------ldr       r2, =__rel_dyn_start    /* r2 <- SRC &__rel_dyn_start */
----------------ldr       r3, =__rel_dyn_end      /* r3 <- SRC &__rel_dyn_end */ 直到修改完整个__rel_dyn段后结束,完成重定位
------------bl    relocate_vectors //主要完成的工作就是实现异常向量表的重定位,拷贝到正确的地址中去

------------bl    c_runtime_cpu_setup //关闭指令缓存I-cache,重定位后到了新的介质中运行也是要设置一下运行环境

------------ldr    r0, =__bss_start    /* this is auto-relocated! */
------------ldr    r3, =__bss_end        /* this is auto-relocated! */
------------bl    memset //清除BSS段


------------ldr    pc, =board_init_r    /* this is auto-relocated! */ 第二阶段的入口board_init_r,common/board_r.c
----------------initcall_run_list(init_sequence_r)

_main:

arch/arm/lib/crt0.S

/*
* entry point of crt0 sequence
*/

ENTRY(_main)

/*
* Set up initial C runtime environment and call board_init_f(0).
*/

#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
    ldr    r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr    r0, =(CONFIG_SPL_STACK)
#else
    ldr    r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
    bic    r0, r0, #7    /* 8-byte alignment for ABI compliance */
    mov    sp, r0
    bl    board_init_f_alloc_reserve
    mov    sp, r0
    /* set up gd here, outside any C code */
    mov    r9, r0 //gd是一个保存在ARM的r9寄存器中的gd_t结构体的指针
    bl    board_init_f_init_reserve

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

    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    r0, [r9, #GD_START_ADDR_SP]    /* sp = gd->start_addr_sp */
    bic    r0, r0, #7    /* 8-byte alignment for ABI compliance */
    mov    sp, r0
    ldr    r9, [r9, #GD_NEW_GD]        /* r9 <- gd->new_gd */

    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) || CONFIG_IS_ENABLED(FRAMEWORK)

#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

# 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

#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 CONFIG_IS_ENABLED(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)

2,global data

2.1 为什么使用global data

u-boot是一个bootloader,有些情况下,它可能位于系统的只读存储器(ROM或者flash)中,并从那里开始执行。

因此,这种情况下,在u-boot执行的前期(在将自己copy到可读写的存储器之前),它所在的存储空间,是不可写的,这会有两个问题:

    1)堆栈无法使用,无法执行函数调用,也即C环境不可用。

    2)没有data段(或者正确初始化的data段)可用,不同函数或者代码之间,无法通过全局变量的形式共享数据。

对于问题1,通常的解决方案是:

u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域,开辟一段堆栈(stack)空间。

一般来说,大部分的平台(如很多ARM平台),都有自己的SRAM,可用作堆栈空间。如果实在不行,也有可借用CPU的data cache的方法。

对于问题2,解决方案要稍微复杂一些:

首先,对于开发者来说,在u-boot被拷贝到可读写的RAM(这个动作称作relocation)之前,永远不要使用全局变量。

其次,在relocation之前,不同模块之间,确实有通过全局变量的形式传递数据的需求。怎么办?这就是global data需要解决的事情。

2.2 GD空间分配和初始化

为了在relocation前通过全局变量的形式传递数据,u-boot设计了一个巧妙的方法:

    1)定义一个struct global_data类型的数据结构,里面保存了各色各样需要传递的数据。

    2)堆栈配置好之后,在堆栈开始的位置,为struct global_data预留空间,并将开始地址(就是一个struct global_data指针)保存在一个寄存器中,后续的传递,都是通过保存在寄存器中的指针实现。

arch/arm/lib/crt0.S

ENTRY(_main)

/** Set up initial C runtime environment and call board_init_f(0).*/

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
   ldr       r0, =(CONFIG_SPL_STACK)
#else
   ldr       r0, =(CONFIG_SYS_INIT_SP_ADDR) //加载CONFIG_SYS_INIT_SP_ADDR到r0寄存器
#endif
   bic       r0, r0, #7      /* 8-byte alignment for ABI compliance */ 遵从ABI的8字节对齐
   mov       sp, r0 //将堆栈指针指向r0寄存器的值
   bl        board_init_f_alloc_reserve //主要作用是保留早期malloc区域,且为GD(全局数据区)留出空间,函数返回值也是r0,r0保存着预留早期malloc区域和GD后的地址
   mov       sp, r0
   /* set up gd here, outside any C code */
   mov       r9, r0 //定义一个寄存器全局变量指针,并指定其使用的寄存器是r9,类型为gd_t
   bl        board_init_f_init_reserve //该函数主要作用是将GD区域清零,返回最初malloc区域的地址

   mov       r0, #0 //清空r0,然后把参数r0传给board_init_f函数,并调用board_init_f
   bl        board_init_f
2.3 gt_t 和 bd_t 结构体

gd_t结构体几乎包含了u-boot中用到的所有全局变量, gd_t和bd_t都u-boot中两个重要的数据结构,在初始化操作很多都要靠这两个数据结构来保存或传递。 gd_t结构体如下所示:

include/asm-generic/global_data.h

typedef struct global_data {
    bd_t *bd;
    unsigned long flags;
    unsigned int baudrate;
    unsigned long cpu_clk;        /* CPU clock in Hz!        */
    unsigned long bus_clk;
    /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
    unsigned long pci_clk;
    unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
    unsigned long fb_base;        /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)
    unsigned long post_log_word;    /* Record POST activities */
    unsigned long post_log_res;    /* success of POST test */
    unsigned long post_init_f_time;    /* When post_init_f started */
#endif
#ifdef CONF