【腾讯文档】uboot启动流程图(2016.03)​​https://docs.qq.com/flowchart/DR25oeU5KQmJydEhM​



文章目录


一、第一行代码

要分析uboot启动流程,先得找到uboot启动的第一行代码,编译uboot,查看u-boot.map文件,找到Linker script and memory map这一节:

.text           0x0000000087800000    0x3e734
*(.__image_copy_start)
.__image_copy_start
0x0000000087800000 0x0 arch/arm/lib/built-in.o
0x0000000087800000 __image_copy_start
*(.vectors)
.vectors 0x0000000087800000 0x300 arch/arm/lib/built-in.o
0x0000000087800000 _start
0x0000000087800020 _undefined_instruction
0x0000000087800024 _software_interrupt
0x0000000087800028 _prefetch_abort
0x000000008780002c _data_abort
0x0000000087800030 _not_used
0x0000000087800034 _irq
0x0000000087800038 _fiq
0x0000000087800040 IRQ_STACK_START_IN

可以看到,uboot编译出的可执行程序中,最开始放置的还是中断向量表(vectors),第一行代码为 ​​_start​​​ 函数,地址在0x87800000处,并把此地址作为起始地址赋给 ​​__image_copy_start​​ 变量用于镜像拷贝。

二、_start 函数

map文件中给出了 _start 函数在 arch/arm/lib 下面,搜索一下在​​arch/arm/lib/Vectors.S​​文件中:

/*
*************************************************************************
*
* Exception vectors as described in ARM reference manuals
*
* Uses indirect branch to allow reaching handlers anywhere in memory.
*
*************************************************************************
*/

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

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

可以看到,_start函数最先跳转到reset函数执行,不返回,接着依次执行后面的函数。

搜索一下,reset函数在​​arch/arm/cpu/armv7/start.S​​中:

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

.globl reset
.globl save_boot_params_ret

reset:
/* Allow the board to save important registers */
b save_boot_params

1. save_boot_params函数

复位时允许CPU先保存重要的寄存器,save_boot_params函数定义如下:

/*************************************************************************
*
* void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
* __attribute__((weak));
*
* Stack pointer is not yet initialized at this moment
* Don't save anything to stack even if compiled with -O0
*
*************************************************************************/
ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller
ENDPROC(save_boot_params)
.weak save_boot_params

该函数跳转到 save_boot_params_ret 去执行,不返回。

2. save_boot_params_ret函数

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

/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register

/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif

/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif

(1) ​失能中断(FIQ和IRQ)并且设置CPU为SVC32模式​,除非已经处于HYP模式。

(2)​设置中断向量表地址为_start函数的地址​,在map文件中可以看到,为0x87800000。

(3)​进行CPU初始化​,调用函数初始化CP15和CRIT。

cpu_init_cp15函数的定义很长,截取片段如下:

/*************************************************************************
*
* cpu_init_cp15
*
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*
*************************************************************************/
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

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

mov r5, lr @ Store my Caller
mrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (MIDR)
mov r3, r1, lsr #20 @ get variant field
and r3, r3, #0xf @ r3 has CPU variant
and r4, r1, #0xf @ r4 has CPU revision
mov r2, r3, lsl #4 @ shift variant field for combined value
orr r2, r4, r2 @ r2 has combined CPU variant + revision

mov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)

① 关闭 L1 I/D Cache。

② 禁用MMU和缓存。

③ 返回。

cpu_init_crit 函数的定义如下:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
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)
#endif

可见,​uboot完成CPU初始化后,调用cpu_init_crit函数,跳转到板级初始化函数 lowlevel_init。至此,Mask ROM已经完成了基础内存的初始化,到这里由 lowlevel_init 来设置pll、mux、memory,来提高时钟速度和处理唤醒条件​。

三、lowlevel_init函数

1. lowlevel_init说明

  • 目的:完成执行board_init_f() 函数之前必不可少的初始化。
  • 不要使用全局变量或者BSS段
  • 没有栈
  • 不要设置SDRAM或者使用控制台
  • 仅仅完成最低限度的工作(设置栈即可)使 board_init_f 可以执行就好​。

2. lowlevel_init函数ARM v7实现

搜索一下,lowlevel_init函数的定义在 ​​arch/arm/cpu/armv7/lowlevel_init.S​​中。

ENTRY(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr}

/*
* Call the very early init function. This should do only the
* absolute bare minimum to get started. It should not:
*
* - set up DRAM
* - use global_data
* - clear BSS
* - try to start a console
*
* For boards with SPL this should be empty since SPL can do all of
* this init in the SPL board_init_f() function which is called
* immediately after this.
*/
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)

(1)设置临时堆栈,全局变量目前不可用;

(2)为仍然需要的板子设置全局变量,这段代码不久将会被移除;

(3)保存旧的lr寄存器和当前lr寄存器到栈;

(4)调用非常早期的初始化函数,它应该只做最基本的事情,不应该做:

  • 设置DRAM
  • 使用全局数据
  • 清除BSS
  • 尝试启动控制台

对于使用SPL启动的开发板,它应该为空,因为SPI可以完成所有的这些初始化在 SPL board_init_f() 函数中,该函数将在之后被立即调用。

3. 栈地址CONFIG_SYS_INIT_SP_ADDR

该宏定义表示要初始化的栈顶指针地址,在开发板BSP中的头文件​​include/configs/mx6ullatk.h​​定义:

#define CONFIG_SYS_INIT_SP_OFFSET \
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

继续找定义,可以看到,栈目前是定义在​内存RAM​的:

#define CONFIG_SYS_INIT_RAM_ADDR  IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE

找到RAM大小的定义,在​​arch/arm/include/asm/arch-mx6/imx-regs.h​​文件中:

#if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \
defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
#define IRAM_SIZE 0x00040000
#else
#define IRAM_SIZE 0x00020000
#endif

我们使用的是imx6ull,内部RAM大小为0x00020000(128KB)。

接着找IRAM_BASE_ADDR的定义,同样在该文件中,为0x00900000。

#define IRAM_BASE_ADDR      0x00900000

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_uboot

查阅参考手册的memory map,可以看到0x00900000这个地址是内部OCRAM的起始地址。

最后还有一个GENERATED_GBL_DATA_SIZE,搜索一下,在​​include/generated/generic-asm-offsets.h​​文件中,<font=“red”>​该文件是uboot编译时动态生成的​,表示

#define GENERATED_GBL_DATA_SIZE 256 /* (sizeof(struct global_data) + 15) & ~15  @ */

这样,我们就可以计算出栈顶地址了:

CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 - 0x100 = 0x1FF00
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0x1FF00 = 0x0091FF00

再回到lowlevel_init函数,对栈顶指针做了8字节对齐处理:

bic sp, sp, #7 /* 8-byte alignment for ABI compliance */

对0x0091FF00做8字节对齐,变为0x0091FE08,这就是最终设置的栈顶指针SP,栈空间大小为CONFIG_SYS_INIT_SP_OFFSET。

4. s_init函数imx6实现

经过搜索,s_init函数也是由不同的芯片厂商实现,imx6系列处理器的在​​arch/arm/cpu/armv7/mx6/soc.c​​文件中,s_init函数部分实现如下,​看上去像是要设置时钟​。

void s_init(void)
{
struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
u32 mask480;
u32 mask528;
u32 reg, periph1, periph2;

if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
return;
...
}

5. 一路返回

s_iinit函数执行完成后返回 lowlevel_init 函数,lowlevel_init 函数将之前存储的lr寄存器值恢复到pc,程序返回。

接着一路返回到 cpu_init_crit 函数被调用时候的返回地址(因为该函数调用时使用了bl指令),也就是start.S汇编文件中:

/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif

bl _main

可以看到,cpu_init_crit执行完毕后,接下来就跳转到_main函数执行。

四、__main函数

​__main​​​函数定义在​​arch/arm/lib/crt0.S​​文件中,这个文件处理U-Boot 启动过程中与目标无关的阶段,其中需要C运行时环境(设置好栈顶指针)。

​__main​​函数的执行流程如下。

1.设置C语言运行环境并调用board_init_f函数

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

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#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
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve

mov r0, #0
bl board_init_f

(1)设置栈顶指针

C语言运行环境其实就是堆栈,栈用于函数调用和局部变量,设置栈的过程可以分为三步。

① 设置栈顶指针SP为CONFIG_SYS_INIT_SP_ADDR,并进行8字节对齐,值之前计算过。

② 将SP的值作为函数参数,调用board_init_f_alloc_reserve函数,该函数是一个通用函数,可以被各个架构调用,用于分配预留空间,定义在​​common/init/board_init.c​​文件中:

/**
* ulong board_init_f_alloc_reserve - allocate reserved area
*
* This function is called by each architecture very early in the start-up
* code to allow the C runtime to reserve space on the stack for writable
* 'globals' such as GD and the malloc arena.
*
* @top: top of the reserve area, growing down.
* @return: bottom of reserved area
*/
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);

return top;
}

board_init_f_alloc_reserve函数的传入参数是栈顶地址,返回参数是预留空间的底部。

可以看到,预留空间分为两部分,如果开启了malloc,则为malloc预留一部分空间,大小为CONFIG_SYS_MALLOC_F_LEN;其次为GD变量(global_data结构体类型)预留空间,并且对齐到16个字节的倍数。

③ 将新的栈顶地址写入到SP中,设置完成。

(2)设置全局变量gd的地址。

在文件​​arch/arm/include/asm/global_data.h​​中,定义了使用r9寄存器作为指向全局变量gd的指针:

#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif

所以此处将r0寄存器的值写入r9寄存器,因为栈是向下增长的,所以此时的栈顶指针就是gd存储空间的基地址,也就是0x0091FA00。

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;
//代码省略
struct udevice *cur_serial_dev; /* current serial device */
struct arch_global_data arch; /* architecture-specific data */
} gd_t;

设置完gd变量的地址后,调用函数​​board_init_f_init_reserve​​​,初始化第(1)步中预留的空间,在​​common/init/board_init.c​​文件中定义:

void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;
#ifndef _USE_MEMCPY
int *ptr;
#endif

/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/

gd_ptr = (struct global_data *)base;
/* zero the area */
#ifdef _USE_MEMCPY
memset(gd_ptr, '\0', sizeof(*gd));
#else
for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
*ptr++ = 0;
#endif
/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
/* next alloc will be higher by one GD plus 16-byte alignment */
base += roundup(sizeof(struct global_data), 16);

/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/

#if defined(CONFIG_SYS_MALLOC_F)
/* go down one 'early malloc arena' */
gd->malloc_base = base;
/* next alloc will be higher by one 'early malloc arena' size */
base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}

(3)清空r0寄存器,调用board_init_f函数,board_init_f 函数非常重要,完了下一节详细讲述。

2. 设置新的sp指针和gd指针,设置中间环境,调用重定位代码

#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

board_init_f函数中会初始化开发板的许多外设,包括DRAM,并将栈顶指针和sp放置到DRAM中,所以这里主要做了三件事情:

(1)设置新的栈顶指针为​​sp = gd->start_addr_sp​

(2)设置新的gd指针为​​r9 = gd->bd​

(3)设置r0寄存器的值为 ​​gd->relocaddr​​,​跳转到重定位代码relocate_code,并且返回到here标号处​。

3. 重定位向量表

重定位代码完成后,返回到here标号处,调用relocate_vectors函数,开始进行​向量表重定位​:

here:
/*
* now relocate vectors
*/

bl relocate_vectors

4. 设置最终的环境

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

这里面主要调用函数 c_runtime_cpu_setup,然后清除BSS段,设置board_init_r函数的两个参数,​最终调用函数board_init_r​。

五、board_init_f 函数

在​​common/board_f.c​​文件中,找到 board_init_f 函数定义:

void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
/*
* For some archtectures, global data is initialized and used before
* calling this function. The data should be preserved. For others,
* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
* here to host global data until relocation.
*/
gd_t data;

gd = &data;

/*
* Clear global data before it is accessed at debug print
* in initcall_run_list. Otherwise the debug print probably
* get the wrong vaule of gd->have_console.
*/
zero_global_data();
#endif

gd->flags = boot_flags;
gd->have_console = 0;

if (initcall_run_list(init_sequence_f))
hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}

这里面的核心代码只有两行:

if (initcall_run_list(init_sequence_f))
hang();

这两行代码完成了​一系列板级外设的初始化​。

init_sequence_f 如下,初始化函数表省略其中部分代码。

static init_fnc_t init_sequence_f[] = {
setup_mon_len,
initf_malloc,
initf_console_record,
arch_cpu_init, /* basic arch cpu dependent setup */
initf_dm,
arch_cpu_init_dm,
mark_bootstage, /* need timer, go after init dm */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f,
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
defined(CONFIG_SPARC)
timer_init, /* initialize timer */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
board_postclk_init,
#endif
#if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
get_clocks,
#endif
env_init, /* initialize environment */
init_baud_rate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_options, /* say that we are here */
display_text_info, /* show debugging info if required */
print_cpuinfo, /* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
show_board_info,
#endif
INIT_FUNC_WATCHDOG_INIT
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c,
#endif
announce_dram_init,
/* TODO: unify all these dram functions? */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)
dram_init, /* configure available RAM banks */
#endif
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
/*
* Now that we have DRAM mapped and working, we can
* relocate the code and continue running from DRAM.
*
* Reserve memory at end of RAM for (top down in that order):
* - area that won't get touched by U-Boot and Linux (optional)
* - kernel log buffer
* - protected RAM
* - LCD framebuffer
* - monitor code
* - board info struct
*/
setup_dest_addr,
reserve_round_4k,
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
defined(CONFIG_ARM)
reserve_mmu,
#endif
reserve_trace,
#if !defined(CONFIG_BLACKFIN)
reserve_uboot,
#endif
#ifndef CONFIG_SPL_BUILD
reserve_malloc,
reserve_board,
#endif
setup_machine,
reserve_global_data,
reserve_fdt,
reserve_arch,
reserve_stacks,
setup_dram_config,
show_dram_config,
display_new_sp,
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
setup_reloc,
NULL,
};

这其中比较重要的一些初始化函数如下。

(1)setup_mon_len:设置gd的mon_len成员变量,也就是整个代码的长度;

(2)initf_malloc:设置gd中和malloc有关的成员变量;

(3) board_early_init_f:板子早期的一些初始化设置,imx6ull中用来初始化串口的IO配置(在​​board/freescale/mx6ullatk/mx6ullatk.c​​中实现);

int board_early_init_f(void)
{
setup_iomux_uart();

return 0;
}

(4)timer_init:初始化内核定时器,为uboot提供时钟节拍,在​​arch/arm/imx-common/timer.c​​中实现;

(5)board_postclk_init:imx6ull中用来设置VDDSOC电压,在​​arch/arm/cpu/armv7/mx6/soc.c​​中实现;

int board_postclk_init(void)
{
/* NO LDO SOC on i.MX6SLL */
if (is_cpu_type(MXC_CPU_MX6SLL))
return 0;

set_ldo_voltage(LDO_SOC, 1175); /* Set VDDSOC to 1.175V */

return 0;
}

(6)get_clocks:获取一些时钟值,imx6ull获取了SD卡外设的时钟(sdhc_clk),在​​arch/arm/imx-common/speed.c​​中实现:

gd->arch.sdhc_clk = mxc_get_clock(MXC_ESDHC2_CLK);

(7)env_init:初始化环境变量。

在​​common​​文件夹下,每个存储介质都提供了一个env_init()函数,有这么多文件:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_imx6ull_02

这个时候就要来看看这个目录下的Makefile了,果然是使用配置项来控制:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_imx6ull_03

在文件​​include/configs/mx6ullatk.h​​中找到配置定义:

/* FLASH and environment organization */
#define CONFIG_FSL_QSPI
#define CONFIG_ENV_IS_IN_MMC

(8)init_baud_rate:初始化波特率,在​​common/board_f.c​​文件中定义:

static int init_baud_rate(void)
{
gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
return 0;
}

可以看到,这里是从环境变量中寻找baudrate的值,如果没有,则默认是​​CONFIG_BAUDRATE=115200​​。

(9)serial_init:初始化串口通信设置,在文件​​drivers/serial/serial.c​​中定义:

/**
* serial_init() - Initialize currently selected serial port
*
* This function initializes the currently selected serial port. This
* usually involves setting up the registers of that particular port,
* enabling clock and such. This function uses the get_current() call
* to determine which port is selected.
*
* Returns 0 on success, negative on error.
*/
int serial_init(void)
{
gd->flags |= GD_FLG_SERIAL_READY;
return get_current()->start();
}

(10)console_init_f:初始化控制台,在文件​​common/console.c​​中定义:

/* Called before relocation - use serial functions */
int console_init_f(void)
{
gd->have_console = 1;

#ifdef CONFIG_SILENT_CONSOLE
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT;
#endif

print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);

return 0;
}

(11)display_options:打印uboot版本信息和编译信息,在​​lib/display_options.c​​文件中定义。

int display_options (void)
{
#if defined(BUILD_TAG)
printf ("\n\n%s, Build: %s\n\n", version_string, BUILD_TAG);
#else
printf ("\n\n%s\n\n", version_string);
#endif
return 0;
}

version_string在​​cmd/version.c​​文件中定义:

const char __weak version_string[] = U_BOOT_VERSION_STRING;

U_BOOT_VERSION_STRING在​​include/generated/version_autogenerated.h​​文件中定义,这个文件是构建时自动生成的,包含uboot版本信息,编译器版本信息,链接器版本信息:

#define PLAIN_VERSION "2016.03-gc37bca7-dirty"
#define U_BOOT_VERSION "U-Boot " PLAIN_VERSION

这行是uboot日志打印的第一行:

U-Boot 2016.03-g4e04879 (Jan 15 2020 - 17:28:42 +0800)

(12)display_text_info:用来打印调试信息,需要相应的宏配置开启。

(13)print_cpuinfo:用来显示CPU信息和主频,在​​arch/arm/imx-common/cpu.c​​文件中定义,函数过多不放代码了,下面三行日志都是该函数打印:

CPU:   Freescale i.MX6ULL rev1.1 69 MHz (running at 396 MHz)
CPU: Industrial temperature grade (-40C to 105C) at 31C
Reset cause: POR

(14)show_board_info:打印开发板信息,在​​common/board_info.c​​文件中定义。

/*
* If the root node of the DTB has a "model" property, show it.
* Then call checkboard().
*/
int show_board_info(void)
{
#if defined(CONFIG_OF_CONTROL) && !defined(CONFIG_CUSTOM_BOARDINFO)
DECLARE_GLOBAL_DATA_PTR;
const char *model;

model = fdt_getprop(gd->fdt_blob, 0, "model", NULL);

if (model)
printf("Model: %s\n", model);
#endif

return checkboard();
}

​checkboard​​​函数由不同开发板的bsp实现,imx6ull-atk的实现如下,在文件​​board/freescale/mx6ullatk/mx6ullatk.c​​:

int checkboard(void)
{
puts("Board: i.MX6ULL ATK ALPHA\n");

return 0;
}

(15)INIT_FUNC_WATCHDOG_RESET:初始化看门狗,imx6ull中该宏为空。

(16)init_func_i2c:在​​common/board_f.c​​文件中定义:

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
static int init_func_i2c(void)
{
puts("I2C: ");
#ifdef CONFIG_SYS_I2C
i2c_init_all();
#else
i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
#endif
puts("ready\n");
return 0;
}
#endif

i2c_init_all函数在​​drivers/i2c/i2c_core.c​​中定义:

/*
* i2c_init_all():
*
* not longer needed, will deleted. Actual init the SPD_BUS
* for compatibility.
* i2c_adap[] must be initialized beforehead with function pointers and
* data, including speed and slaveaddr.
*/
void i2c_init_all(void)
{
i2c_init_board();
i2c_set_bus_num(CONFIG_SYS_SPD_BUS_NUM);
return;
}

打印出的日志为:

I2C:   ready

(17)announce_dram_init:在​​common/board_f.c​​文件中定义:

static int announce_dram_init(void)
{
puts("DRAM: ");
return 0;
}

(18)dram_init:设置​​gd->ram_size​​的值,​并非真正初始化DRAM​,在​​board/freescale/mx6ullatk/mx6ullatk.c​​文件中定义:

int dram_init(void)
{
gd->ram_size = imx_ddr_size();

return 0;
}

(19)reserve_开头的函数,是进行代码重定位,将uboot搬运到DRAM中执行,并预留各种需要的空间。

(20)setup_machine:设置机器ID,老的linux内核中启动时会匹配machid,但新的linux内核中使用设备树,此值不使用。

static int setup_machine(void)
{
#ifdef CONFIG_MACH_TYPE
gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
#endif
return 0;
}

(21)setup_dram_config:设置DRAM配置,在文件​​common/board_f.c​​中:

static int setup_dram_config(void)
{
/* Ram is board specific, so move it to board code ... */
dram_init_banksize();

return 0;
}

imx6ull未重新实现dram_init_banksize函数。

(22)show_dram_config:打印DRAM配置信息,在文件​​common/board_f.c​​中:

static int show_dram_config(void)
{
unsigned long long size;

#ifdef CONFIG_NR_DRAM_BANKS
int i;

debug("\nRAM Configuration:\n");
for (i = size = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
size += gd->bd->bi_dram[i].size;
debug("Bank #%d: %llx ", i,
(unsigned long long)(gd->bd->bi_dram[i].start));
#ifdef DEBUG
print_size(gd->bd->bi_dram[i].size, "\n");
#endif
}
debug("\nDRAM: ");
#else
size = gd->ram_size;
#endif

print_size(size, "");
board_add_ram_info(0);
putc('\n');

return 0;
}

uboot打印的日志为:

DRAM:  512 MiB

(23)display_new_sp:打印新的sp指针,在文件​​common/board_f.c​​中,需要开启调试模式:

static int display_new_sp(void)
{
debug("New Stack Pointer is: %08lx\n", gd->start_addr_sp);

return 0;
}

(24)reloc_fdt:在文件​​common/board_f.c​​中

static int reloc_fdt(void)
{
#ifndef CONFIG_OF_EMBED
if (gd->flags & GD_FLG_SKIP_RELOC)
return 0;
if (gd->new_fdt) {
memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size);
gd->fdt_blob = gd->new_fdt;
}
#endif

return 0;
}

(25)setup_reloc:在文件​​common/board_f.c​​中。

六、relocate_code

relocate_code用于重定位代码,定义在文件​​arch/arm/lib/relocate.S​​中,代码如下:

/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an R_ARM_ABS32
* relocation record type, we never refer to linker-defined symbols directly.
* Instead, we declare literals which contain their relative location with
* respect to relocate_code, and at run time, add relocate_code back to them.
*/

ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */

copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop

/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext

/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop

relocate_done:

#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif

/* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif

ENDPROC(relocate_code)

此函数主要完成镜像拷贝和重定位,镜像地址从__image_copy_start开始,到__image_copy_end结束,拷贝的目标地址由参数传进来,也就是r0寄存器的值。重定位的原理此处不展开,值得用另外一篇文章专门讲述。

七、relocate_vectors

relocate_vectors函数用于重定位向量表,定义在文件​​arch/arm/lib/relocate.S​​中,代码如下:

/*
* Default/weak exception vectors relocation routine
*
* This routine covers the standard ARM cases: normal (0x00000000),
* high (0xffff0000) and VBAR. SoCs which do not comply with any of
* the standard cases must provide their own, strong, version.
*/

.section .text.relocate_vectors,"ax",%progbits
.weak relocate_vectors

ENTRY(relocate_vectors)

#ifdef CONFIG_CPU_V7M
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1, =V7M_SCB_BASE
str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
ands r2, r2, #(1 << 13)
ldreq r1, =0x00000000 /* If V=0 */
ldrne r1, =0xFFFF0000 /* If V=1 */
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
#endif
#endif
bx lr

ENDPROC(relocate_vectors)

该函数最终完成将uboot重定位后的首地址​​gd->relocaddr​​的,也就是新的向量表地址,写入到寄存器VBAR中,设置向量表偏移。

八、board_init_r

board_init_f函数中,会初始化一些外设和gd的成员变量,但并没有初始化所有的外设,还需要一些后续工作,这些工作就是由b

oard_init_r函数完成的,该函数定义在文件​​common/board_r.c​​中,代码如下:

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
int i;
#endif

#ifdef CONFIG_AVR32
mmu_init_r(dest_addr);
#endif

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
gd = new_gd;
#endif

#ifdef CONFIG_NEEDS_MANUAL_RELOC
for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
init_sequence_r[i] += gd->reloc_off;
#endif

if (initcall_run_list(init_sequence_r))
hang();

/* NOTREACHED - run_main_loop() does not return */
hang();
}

该函数中主要执行初始化序列 init_sequence_r,init_sequence_r是一个函数表,也定义在该文件中,部分代码如下:

/*
* Over time we hope to remove these functions with code fragments and
* stub funtcions, and instead call the relevant function directly.
*
* We also hope to remove most of the driver-related init and do it if/when
* the driver is later used.
*
* TODO: perhaps reset the watchdog in the initcall function after each call?
*/
init_fnc_t init_sequence_r[] = {
initr_trace,
initr_reloc,
initr_caches,
initr_reloc_global_data,
initr_barrier,
initr_malloc,
initr_console_record,
bootstage_relocate,
initr_bootstage,
board_init, /* Setup chipselects */
stdio_init_tables,
initr_serial,
initr_announce,
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
power_init_board,
initr_flash,
initr_nand,
initr_mmc,
initr_env,
INIT_FUNC_WATCHDOG_RESET
initr_secondary_cpu,
INIT_FUNC_WATCHDOG_RESET
stdio_add_devices,
initr_jumptable,
console_init_r, /* fully init console as a device */
INIT_FUNC_WATCHDOG_RESET
interrupt_init,
initr_enable_interrupts,
initr_ethaddr,
board_late_init,
INIT_FUNC_WATCHDOG_RESET
initr_net,
run_main_loop,
};

其中比较重要的有。

(1)initr_caches:初始化cache,使能cache。

(2)board_init:板级初始化,包括74XX芯片、I2C、FEC、USB、QSPI等,在​​board/freescale/mx6ullatk/mx6ullatk.c​​中定义:

int board_init(void)
{
/* Address of boot parameters */
gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;

#ifdef CONFIG_SYS_I2C_MXC
setup_i2c(0, CONFIG_SYS_I2C_SPEED, 0x7f, &i2c_pad_info1);
#endif

#ifdef CONFIG_FEC_MXC
setup_fec(CONFIG_FEC_ENET_DEV);
#endif

#ifdef CONFIG_USB_EHCI_MX6
setup_usb();
#endif

#ifdef CONFIG_FSL_QSPI
board_qspi_init();
#endif

#ifdef CONFIG_NAND_MXS
setup_gpmi_nand();
#endif

return 0;
}

(3)initr_mmc:初始化emmc,在​​common/board_r.c​​中定义:

#ifdef CONFIG_GENERIC_MMC
static int initr_mmc(void)
{
puts("MMC: ");
mmc_initialize(gd->bd);
return 0;
}
#endif

对应在uboot启动日志中打印这行:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_初始化_04

(4)initr_env:初始化环境变量。

(5)stdio_add_devices:各种输入输出的设备初始还,比如LCD drvier,在文件​​common/stdio.c​​中定义,其中会调用到drv_video_init,在该函数中初始化LCD,打印日志:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_初始化_05

(6)console_init_r,控制台初始化,在文件​​common/console.c​​中定义,该函数的最后会调用 print_pre_console_buffer函数来打印当前的控制台设备,对应的日志如下:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_初始化_06

(7)interrupt_init和initr_enable_interrupts,初始化中断并使能中断。

(8)initr_ethaddr:初始化网络地址,获取MAC地址,读取环境变量​​ethaddr​​的值。

(9)board_late_init:板子后续初始化,此函数定义在文件 ​​board/freescale/mx6ullatk/mx6ullatk.c​​中,代码如下:

int board_late_init(void)
{
#ifdef CONFIG_CMD_BMODE
add_board_boot_modes(board_boot_modes);
#endif

#ifdef CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG
setenv("board_name", "EVK");

if (is_mx6ull_9x9_evk())
setenv("board_rev", "9X9");
else
setenv("board_rev", "14X14");
#endif

#ifdef CONFIG_ENV_IS_IN_MMC
board_late_mmc_env_init();
#endif

set_wdog_reset((struct wdog_regs *)WDOG1_BASE_ADDR);

return 0;
}

其中,如果环境变量定义在MMC中,board_late_mmc_env_init会初始化MMC,切换到正在使用的emmc设备,如下,在​​board/freescale/common/mmc.c​​文件中定义:

void board_late_mmc_env_init(void)
{
char cmd[32];
char mmcblk[32];
u32 dev_no = mmc_get_env_dev();

if (!check_mmc_autodetect())
return;

setenv_ulong("mmcdev", dev_no);

/* Set mmcblk env */
sprintf(mmcblk, "/dev/mmcblk%dp2 rootwait rw",
mmc_map_to_kernel_blk(dev_no));
setenv("mmcroot", mmcblk);

sprintf(cmd, "mmc dev %d", dev_no);
run_command(cmd, 0);
}

对应uboot的启动日志为:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_#endif_07

(10)initr_net:初始化网络设备,该函数定义在​​common/board_r.c​​文件中,代码如下:

#ifdef CONFIG_CMD_NET
static int initr_net(void)
{
puts("Net: ");
eth_initialize();
#if defined(CONFIG_RESET_PHY_R)
debug("Reset Ethernet PHY\n");
reset_phy();
#endif
return 0;
}
#endif

其中 eth_initialize函数会调用到 board_eth_init,定义在文件​​board/freescale/mx6ullatk/mx6ullatk.c​​中,代码如下:

int board_eth_init(bd_t *bis)
{
setup_iomux_fec(CONFIG_FEC_ENET_DEV);
board_eth_hard_reset(CONFIG_FEC_ENET_DEV);

return fecmxc_initialize_multi(bis, CONFIG_FEC_ENET_DEV,
CONFIG_FEC_MXC_PHYADDR, IMX_FEC_BASE);
}

对应uboot的启动日志如下:

uboot研读笔记 | 14 - uboot启动流程分析(2016.03版本)_初始化_08

最后,一切就绪,调用run_main_loop,uboot启动!

九、一切就绪,uboot启动!

uboot启动后会自动倒计时,如果按下回车键则进入命令行模式,如果未按下则自动启动Linux内核,该功能是由run_main_loop完成的。

run_main_loop函数定义在文件​​common/board_r.c​​中,代码如下:

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}

至于main_loop如何处理各种命令,我们下篇文章接着讲述。