uboot启动 -- uboot基本启动流程

本文以Board JZ2440(SMDK2410)的启动过程进行说明,u-boot选用u-boot-2015

1 概述

1.1 概述

CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。其次,就是要根据硬件资源进行板级的初始化,代码重定向等等。最后,就是进入命令行状态,等待处理命令。
在armv7架构的uboot,主要需要做如下事情
(1)arch级的初始化

  • 关闭中断,设置svc模式
  • 禁用MMU、TLB
  • 关键寄存器的设置,包括时钟、看门狗的寄存器


(2)板级的初始化

  • 堆栈环境的设置
  • 代码重定向之前的板级初始化,包括串口、定时器、环境变量、I2C\SPI等等的初始化
  • 进行代码重定向
  • 代码重定向之后的板级初始化,包括板级代码中定义的初始化操作、emmc、nand flash、网络、中断等等的初始化。

(3)进入命令行状态,等待终端输入命令以及对命令进行处理

上述工作,也就是uboot流程的核心。


1.2 疑问


在spl的阶段中已经对arch级进行了初始化了,为什么uboot里面还要对arch再初始化一遍?
spl对于启动uboot来说并不是必须的,在某些情况下,上电之后uboot可能在ROM上或者flash上开始执行而并没有使用spl。这些都是取决于平台的启动机制。因此uboot并不会考虑spl是否已经对arch进行了初始化操作,uboot会完整的做一遍初始化动作,以保证cpu处于所要求的状态下。

和spl在启动过程的差异在哪里?
前期arch的初始化流程基本上是一致的,出现本质区别的是在board_init_f开始的。
spl的board_init_f是由board自己实现相应的功能。其主要实现了复制uboot到ddr中,并且跳转到uboot的对应位置上。一般spl在这里就可以完成自己的工作了。
uboot的board_init_f是在common下实现的,其主要实现uboot relocate前的板级初始化以及relocate的区域规划,其还需要往下走其他初始化流程。
 

1.3 编译说明

1 确定链接脚本文件:
uboot根目录下Makefile中的LDSCRIPT宏值,就是指定链接脚本(如:arch/arm/cpu/u-boot.lds)路径用的。
2 从脚本文件找入口:
在链接脚本中可以看到ENTRY()指定的入口,如:ENTRY(_start), _start就是入口(arch/arm/lib/vectors.S)


3 链接脚本简要分析:

/arch/arm/cpu/u-boot.lds

#include 

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")@指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)@指定输出可执行文件的平台为ARM
ENTRY(_start)@入口函数 ,指定输出可执行文件的起始代码段为_start
SECTIONS
{
        @指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成
        . = 0x00000000;@起始地址 从0x0位置开始

        . = ALIGN(4);@代码以4字节对齐
        .text :@文本段
        {
                *(.__image_copy_start)@变量__image_copy_start  映像文件复制起始地址
                *(.vectors)@异常向量表  .vectors标记的代码段
                CPUDIR/start.o (.text*)@启动函数
                *(.text*)@剩余的文本段
        }

        ......@安全相关
        
        . = ALIGN(4);
        .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }@ro数据段

        . = ALIGN(4);
        .data : {@RW数据段
                *(.data*)
        }

        . = ALIGN(4);

        . = .;

        . = ALIGN(4);
        .u_boot_list : {@????
                KEEP(*(SORT(.u_boot_list*)));
        }

        . = ALIGN(4);

        .image_copy_end :
        {
                *(.__image_copy_end)@变量__image_copy_end
        }

        .rel_dyn_start :
        {
                *(.__rel_dyn_start)
        }

        .rel.dyn : {
                *(.rel*)
        }

        .rel_dyn_end :
        {
                *(.__rel_dyn_end)
        }

        .end :
        {
                *(.__end)
        }
         _image_binary_end = .;

        /*
         * Deprecated: this MMU section is used by pxa at present but
         * should not be used by new boards/CPUs.
         */
        . = ALIGN(4096);
        .mmutable : {
                *(.mmutable)
        }

/*
 * Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c
 * __bss_base and __bss_limit are for linker only (overlay ordering)
 */

        .bss_start __rel_dyn_start (OVERLAY) : {
                KEEP(*(.__bss_start));
                __bss_base = .;
        }

        .bss __bss_base (OVERLAY) : {
                *(.bss*)
                 . = ALIGN(4);
                 __bss_limit = .;
        }

        .bss_end __bss_limit (OVERLAY) : {
                KEEP(*(.__bss_end));
        }

        .dynsym _image_binary_end : { *(.dynsym) }
        .dynbss : { *(.dynbss) }
        .dynstr : { *(.dynstr*) }
        .dynamic : { *(.dynamic*) }
        .plt : { *(.plt*) }
        .interp : { *(.interp*) }
        .gnu.hash : { *(.gnu.hash) }
        .gnu : { *(.gnu*) }
        .ARM.exidx : { *(.ARM.exidx*) }
        .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
                                                                               

注意几个变量:

_start:程序启动入口地址处

__image_copy_start、__image_copy_end用于u-boot搬移本身image到指定的ddr地址处;

__rel_dyn_start、__rel_dyn_end用于重定位代码

__bss_start、__bss_end是bss段的开始、结束地址

上面的这几个变量会在arch\arm\lib\sections.c中使用,定义一些全局变量用于只是各个段的起始地址

//C文件中利用这种方式把一个变量或者函数标记到指定段。
char __bss_start[0] __attribute__((section(".__bss_start")));
char __bss_end[0] __attribute__((section(".__bss_end")));
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
char __rel_dyn_start[0] __attribute__((section(".__rel_dyn_start")));
char __rel_dyn_end[0] __attribute__((section(".__rel_dyn_end")));
char __secure_start[0] __attribute__((section(".__secure_start")));
char __secure_end[0] __attribute__((section(".__secure_end")));
char _end[0] __attribute__((section(".__end")));

在文件common/board_r.c中被调用:

static int initr_reloc_global_data(void)
{
#ifdef __ARM__
monitor_flash_len = _end - __image_copy_start;

2  总体启动流程

2.1 总体流程

在arch级初始化
_start———–>reset————–>关闭中断
………………………………|
………………………………———->cpu_init_cp15———–>关闭MMU,TLB
………………………………|
………………………………———->cpu_init_crit————->lowlevel_init————->关键寄存器的配置和初始化
………………………………|
………………………………———->_main————–>进入板级初始化,具体看下面
2、板级初始化的流程

_main————–>board_init_f_alloc_reserve —————>堆栈、GD、early malloc空间的分配
…………|
…………————->board_init_f_init_reserve —————>堆栈、GD、early malloc空间的初始化
…………|
…………————->board_init_f —————>uboot relocate前的板级初始化以及relocate的区域规划
…………|
…………————->relocate_code、relocate_vectors —————>进行uboot和异常中断向量表的重定向
…………|
…………————->旧堆栈的清空
…………|
…………————->board_init_r —————>uboot relocate后的板级初始化
…………|
…………————->run_main_loop —————>进入命令行状态,等待终端输入命令以及对命令进行处理

2.2 启动流程主主干说明


1 vectors.S中第一条指令(分析文件arch/arm/lib/vectors.S)
_start: b reset
2 reset定义在文件arch/arm/cpu/arm920t/start.S中
start.S的执行流程如下图:
uboot启动 -- uboot基本启动流程_第1张图片

3 bl _main //调用c代码, _main实现在 /arch/arm/lib/crt0.S
_main执行流程图如下:
uboot启动 -- uboot基本启动流程_第2张图片
 

3 启动流程详解


3.1 中断向量表

分析文件:arch/arm/lib/vectors.S

.globl _start  /*声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl与.global相同*/
_start:
b reset  /* b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号处执行程序*/
ldr pc, _undefined_instruction   /*未定义指令异常向量,ldr的作用是,将符号_undefined_instruction指向的地址的内容加载到pc*/
ldr pc, _software_interrupt   /*软件中断向量*/
ldr pc, _prefetch_abort       /*预取指令异常向量*/
ldr pc, _data_abort           /*数据操作异常向量*/
ldr pc, _not_used             /*未使用*/
ldr pc, _irq    /*irq中断向量*/
ldr pc, _fiq    /*fiq中断向量*/
.globl _undefined_instruction
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _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
.balignl 16,0xdeadbeef /*下面的代码开始16字节对齐,即当上段的代码运行完后不是16字节对齐,就填充0xdeadbeef,直到使下段的代码开始处16字节对齐。*/
//下面是占坑操作
/* IRQ stack memory (calculated at run-time) + 8 bytes, 不能确定堆栈指针地址,所以填充无效数字,当堆栈指针确定以后,将其加8填充到这里*/
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de

/*下面是IRQ中断,放了一个 IRQ_STACK_START 标号,标号内容为: 0x0badc0de ,此内容没有意义,只是为了占一个坑。程序刚刚运行,还没有进入初始化,根本不知道堆栈指针现在应该放在什么地方,所以一开始,作者做了一个小技巧,填充一个无效的数字,等初始化以后堆栈指针建立好后,再将实际的堆栈指针写到这里。*/
#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 /* CONFIG_USE_IRQ */

3.2 关中断(FIQ IRQ),设置CPU为SVC32模式

文件位置:arch/arm/cpu/arm920t/start.S

功能:(1)设置SVC模式;(2)关看门狗,中断;(3)设置PLL

vectors.S中第一条指令
_start: b reset

arch/arm/cpu/arm920t/start.S

/*
 *************************************************************************
 *
 * Startup Code (called from the ARM reset exception vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */

	.globl	reset

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr        @将cpsr寄存器的内容传送到r0寄存器
	bic	r0, r0, #0x1f    @工作模式位清零
	orr	r0, r0, #0xd3    @设置为SVC 模式
	msr	cpsr, r0        @将r0的值赋给cpsr


#ifdef CONFIG_S3C24X0
	/* turn off the watchdog */

# if defined(CONFIG_S3C2400)
#  define pWTCON	0x15300000
#  define INTMSK	0x14400008	/* Interrupt-Controller base addresses */
#  define CLKDIVN	0x14800014	/* clock divisor register */
#else
#  define pWTCON	0x53000000
#  define INTMSK	0x4A000008	/* Interrupt-Controller base addresses */
#  define INTSUBMSK	0x4A00001C
#  define CLKDIVN	0x4C000014	/* clock divisor register */
# endif

	ldr	r0, =pWTCON
	mov	r1, #0x0
	str	r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0]
# if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# endif

	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
#endif	/* CONFIG_S3C24X0 */

	/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif

	bl	_main

/*------------------------------------------------------------------------------*/

	.globl	c_runtime_cpu_setup
c_runtime_cpu_setup:

	mov	pc, lr



3.3 跳转到cpu_init_crit

文件位置:arch/arm/cpu/arm920t/start.S

功能:(1)清空I-Cache、D-Cache;(2)关闭MMU Stuff及cachei;(3)跳转到lowlevel_init;

/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */


#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0
	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache
	mcr	p15, 0, r0, c1, c0, 0

	/*
	 * before relocating, we have to setup RAM timing
	 * because memory timing is board-dependend, you will
	 * find a lowlevel_init.S in your board directory.
	 */
	mov	ip, lr

	bl	lowlevel_init

	mov	lr, ip
	mov	pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

lowlevel_init标号对应的源码:
(这个代码要初始化内存等,是和具体平台有关的,所以应该去对应平台的目录下找lowlevel_init.S文件board/samsung/smdk2410/lowlevel_init.S)

.globl lowlevel_init
lowlevel_init:
        /* memory control configuration */
        /* make r0 relative the current location so that it */
        /* reads SMRDATA out of FLASH rather than memory ! */
        ldr     r0, =SMRDATA
        ldr     r1, =CONFIG_SYS_TEXT_BASE
        sub     r0, r0, r1
        ldr     r1, =BWSCON     /* Bus Width Status Controller */
        add     r2, r0, #13*4
0:
        ldr     r3, [r0], #4
        str     r3, [r1], #4
        cmp     r2, r0
        bne     0b  

        /* everything is fine now */
        mov     pc, lr

        .ltorg
/* the literal pools origin */

SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    .word 0x32
    .word 0x30
    .word 0x30



3.4 跳转到_main

文件:arch/arm/lib/crt0.S

设置并初始化C运行环境

(1)设置SP,C运行时会用到

(2)board_init_f(0)

(3)relocate_code/relocate_vectors/c_runtime_cpu_setup

(4)清空BSS

(5)board_init_r

/*
* This file handles the target-independent stages of the U-Boot
* start-up where a C runtime environment is needed. Its entry point
* is _main and is branched into from the target's start.S file.
*
* _main execution sequence is:
*
* 1. Set up initial environment for calling board_init_f().
* This environment only provides a stack and a place to store
* the GD ('global data') structure, both located in some readily
* available RAM (SRAM, locked cache...). In this context, VARIABLE
* global data, initialized or not (BSS), are UNAVAILABLE; only
* CONSTANT initialized data are available.
*
* 2. Call board_init_f(). This function prepares the hardware for
* execution from system RAM (DRAM, DDR...) As system RAM may not
* be available yet, , board_init_f() must use the current GD to
* store any data which must be passed on to later stages. These
* data include the relocation destination, the future stack, and
* the future GD location.
*
* (the following applies only to non-SPL builds)
*
* 3. Set up intermediate environment where the stack and GD are the
* ones allocated by board_init_f() in system RAM, but BSS and
* initialized non-const data are still not available.
*
* 4. Call relocate_code(). This function relocates U-Boot from its
* current location into the relocation destination computed by
* board_init_f().
*
* 5. Set up final environment for calling board_init_r(). This
* environment has BSS (initialized to 0), initialized non-const
* data (initialized to their intended value), and stack in system
* RAM. GD has retained values set by board_init_f(). Some CPUs
* have some work left to do at this point regarding memory, so
* call c_runtime_cpu_setup.
*
* 6. Branch to board_init_r().
*/

ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
/*
这里首先为调用board_init_f准备一个临时堆栈,CONFIG_SYS_INIT_SP_ADDR 这个宏就是cpu片上内存的高地址(片上内存的 大小减去GD_SIZE)。然后将堆栈初始的地址保存在r9,所以r9就是gd的起始地址,后面需要靠r9访问gd的成员。然后将r0赋值成0,r0就是要调用的board_init_f函数的第一个参数!
CONFIG_SYS_INIT_SP_ADDR = IRAM大小 - sizeof(GD)
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
  ldr sp, =(CONFIG_SPL_STACK)
#else
  ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
  bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  sub sp, sp, #GD_SIZE /* allocate one GD above SP */
  bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  mov r9, sp /* GD is above SP */
  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.
*/
/*
*这段代码的主要功能就是将uboot搬移到内存的高地址去执行,为kernel腾出低端空间,防止kernel解压覆盖uboot。
• adr lr, here
• ldr r0, [r9, #GD_RELOC_OFF]
• add lr, lr, r0
• 功能就是,将relocate后的here标号的地址保存到lr寄存器,这样等到relocate完成后,就可以直接跳到relocate后的here标号去执行了。
*relocate_code函数的原理及流程,是 uboot 的重要代码,下面详解!
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
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
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
here:

/*
*relocate完成后,uboot的代码被搬到了内存的顶部,所以必须重新设置异常向量表的
*地址,c_runtime_cpu_setup这个函数的主要功能就是重新设置异常向量表的地址。
*/
bl relocate_vectors

/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */


/*
*清空bss段。
*/
/*
*在relocate的过程中,并没有去搬移bss段。bss段是auto-relocated的!为什么?
*可以自己思考一下,又或许看完我后面介绍的relocate的原理后你会明白!
*/
ldr r0, =__bss_start /* this is auto-relocated! */
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 */
    strlo r2, [r0] /* clear 32-bit BSS word */
    addlo r0, r0, #4 /* move to next */
    blo clbss_l


/*
*这两行代码无视之,点灯什么的,和这里要讲的uboot的原理及过程没有半毛钱关系。
*/
bl coloured_LED_init
bl red_led_on


/*
*将relocate后的gd的地址保存到r1,然后调用board_init_r函数,进入uboot的新天地!
*/
/* 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 */
ldr pc, =board_init_r /* this is auto-relocated! */
/* we should not return here. */
#endif
ENDPROC(_main)


3.4.1 跳转到board_init_f(0)

功能:第一阶段启动
文件:common/board_f.c

void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
// 设置global_data里面的一些标志位

if (initcall_run_list(init_sequence_f))
hang();
// 调用initcall_run_list依次执行init_sequence_f函数数组里面的函数,initcall_run_list这里不深究
// 一旦init_sequence_f的函数出错,会导致initcall_run_list返回不为0,而从卡掉
}

打开DEBUG宏之后,可以通过log观察哪些init函数被调用:
uboot log中有如下log:
initcall: 23e005a4
根据u-boot.map可以发现对应
.text.print_cpuinfo
0x23e005a4 0x8 arch/arm/cpu/armv7/built-in.o
0x23e005a4 print_cpuinfo
也就是说print_cpuinfo被initcall调用了。

所以uboot relocate之前的arch级初始化的核心就是init_sequence_f中定义的函数了。
如下,这里只做简单的说明,需要的时候再具体分析:


static init_fnc_t init_sequence_f[] = {
setup_mon_len,
// 计算整个镜像的长度gd->mon_len
initf_malloc,
// early malloc的内存池的设定
initf_console_record,
// console的log的缓存
arch_cpu_init, /* basic arch cpu dependent setup */
// cpu的一些特殊的初始化
initf_dm,
arch_cpu_init_dm,
mark_bootstage, /* need timer, go after init dm */
/* TODO: can any of this go into arch_cpu_init()? */
env_init, /* initialize environment */
// 环境变量的初始化,后续会专门研究一下关于环境变量的内容
init_baud_rate, /* initialze baudrate settings */
// 波特率的初始化
serial_init, /* serial communications setup */
// 串口的初始化
console_init_f, /* stage 1 init of console */
// console的初始化
print_cpuinfo, /* display cpu info (and speed) */
// 打印CPU的信息
init_func_i2c,
init_func_spi,
// i2c和spi的初始化

dram_init, /* configure available RAM banks */
// ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size
// 如果说uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化
//========================================
setup_dest_addr,
reserve_round_4k,
reserve_trace,
setup_machine,
reserve_global_data,
reserve_fdt,
reserve_arch,
reserve_stacks,
// ==以上部分是对relocate区域的规划,具体参考《[uboot] (番外篇)uboot relocation介绍》
setup_dram_config,
show_dram_config,
display_new_sp,
reloc_fdt,
setup_reloc,
// relocation之后gd一些成员的设置
NULL,
};

注意,必须保证上述的函数都正确地返回0值,否则会导致hang。

3.4.2 跳转 relocate_code

文件:arch/arm/lib/relocate.S

几点解释:

(1)uboot image在编译链接时已经链接到固定的地址处;

(2)image首先从flash上被copy到0地址处,然后根据链接地址重新将code搬移到链接时制定的DDR地址处;

(3)code搬移完成之后,PC会跳转到下一条指令地址处(已经是搬完code之后的新的地址),继续执行;

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

3.4.3 relocate_vectors

文件:arch/arm/lib/relocate.S

说明:将uboot image开始的8个DWORD(即中断向量表)重新拷贝到0x0 或 VBAR

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

3.4.4 board_init_r

第二阶段启动
文件:common/board_r.c

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
if (initcall_run_list(init_sequence_r))
hang();
// 调用initcall_run_list依次执行init_sequence_r函数数组里面的函数,initcall_run_list这里不深究
// 一旦init_sequence_r的函数出错,会导致initcall_run_list返回不为0,而从卡掉

/* NOTREACHED - run_main_loop() does not return */
hang();
// uboot要求在这个函数里面终止一切工作,或者进入死循环,一旦试图返回,则直接hang。
}

所以uboot relocate之后的板级初始化的核心就是init_sequence_r中定义的函数了。
如下,这里只做简单的说明,需要的时候再具体分析:
common/board_r.c

init_fnc_t init_sequence_r[] = {
initr_trace,
// trace相关的初始化
initr_reloc,
// gd中一些关于relocate的标识的设置
initr_reloc_global_data,
// relocate之后,gd中一些的成员的重新设置
initr_malloc,
// malloc内存池的设置
initr_console_record,
bootstage_relocate,
initr_bootstage,
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
board_init, /* Setup chipselects */
// 板级自己需要的特殊的初始化函数,如board/samsung/tiny210/board.c中定义了board_init这个函数
#endif
stdio_init_tables,
initr_serial,
// 串口初始化
initr_announce,
// 打印uboot运行位置的log
initr_logbuffer,
// logbuffer的初始化
power_init_board,
#ifdef CONFIG_CMD_NAND
initr_nand,
// 如果使用nand flash,那么这里需要对nand进行初始化
#endif
#ifdef CONFIG_GENERIC_MMC
initr_mmc,
// 如果使用emmc,那么这里需要对nand进行初始化
#endif
initr_env,
// 初始化环境变量
initr_secondary_cpu,
stdio_add_devices,
initr_jumptable,
console_init_r, /* fully init console as a device */
interrupt_init,
// 初始化中断
#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
initr_enable_interrupts,
// 使能中断
#endif
run_main_loop,
// 进入一个死循环,在死循环里面处理终端命令。
};

 

最终,uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。
cli---command line interface 命令行界面 命令行界面

4 run_main_loop函数

static int run_main_loop(void)
{
/* initialize uboot log */
init_write_log();

/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}

main_loop函数基本框架

main_loop()
    >>>s = bootdelay_process();
           >>>s = getenv("bootcmd");
                  >>>from GD
     >>>autoboot_command(s)
            >>>run_command_list(s, -1, 0);
                    >>>cli_simple_run_command_list()
                           while (*next) {
                                     >>>cli_simple_run_command()
                                     >>>cmd_process()
                                     >>>cmd_call()
                                     >>>result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
                                              cmd_tbl_t/U_BOOT_CMD -----> do_cboot()
                             }
              >>>cli_loop(); #进入命令行模式
              >>> cli_simple_loop()
              >>>run_command_repeatable()
              >>>cli_simple_run_command()
              >>>cmd_process()


到此,uboot流程也就完成了,main_loop函数的主要功能是实现命令的处理。

5 命令行的处理

5.1 概述

(1)uboot在执行完所有初始化程序之后,调用run_main_loop进入主循环,通过主循环进入了命令行模式。

(2)uboot把所有命令的数据结构都放在一个表格中,我们后续称之为命令表。表中的每一项代表着一个命令,其项的类型是cmd_tbl_t,这些命令在链接时会被链接到指定的段中,命令表是被定义在这个段中。

(3)命令行模式有两种简单的方式。正常模式是简单地获取串口数据、解析和处理命令。hush模式则是指命令的接收和解析使用busybox的hush工具,对应代码是hush.c

 

5.2 定义一个命令

文件:common/cmd_bootm.c

U_BOOT_CMD(
        bootm,  CONFIG_SYS_MAXARGS,     1,      do_bootm,                                                                                                                                                    
        "boot application image from memory", bootm_help_text
);
// bootm就是我们的命令字符串
// 在smdk2410.h中定义了最大参数数量是64
// #define CONFIG_SYS_MAXARGS 64 /* max number of command args */
// 1表示重复一次
// 对应命令处理函数是do_bootm
// usage字符串是对该命令的简短说明
// help字符串是bootm_help_text定义的字符串。

5.2.1 U_BOOT_CMD实现

功能:定义一个cmd_tbl_t类型的变量,并在程序编译时链接到指定的段中。

文件:include/command.h

/*
 * Command Flags:
 */
#define CMD_FLAG_REPEAT         0x0001  /* repeat last command          */
#define CMD_FLAG_BOOTD          0x0002  /* command is from bootd        */
#define CMD_FLAG_ENV            0x0004  /* command is from the environment */

#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,          \
                                _usage, _help, _comp)                   \
                { #_name, _maxargs, _rep, _cmd, _usage,                 \
                        _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

#define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help)    \
        U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,          \
                                        _usage, _help, NULL)

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
        ll_entry_declare(cmd_tbl_t, _name, cmd) =                       \                                                                                                                                    
                U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,  \
                                                _usage, _help, _comp);
    
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)          \
        U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

其中最关键的函数ll_entry_declare  数据结构体类型:cmd_tbl_t

#define ll_entry_declare(_type, _name, _list)                           \                                                                                                                                    
        _type _u_boot_list_2_##_list##_2_##_name __aligned(4)           \
                        __attribute__((unused,                          \
                        section(".u_boot_list_2_"#_list"_2_"#_name)))
文件:include/command.h
/*
 * Monitor Command Table
 */

struct cmd_tbl_s {
        char            *name;          /* Command Name                 */
        int             maxargs;        /* maximum number of arguments  */
        int             repeatable;     /* autorepeat allowed?          */
                                        /* Implementation function      */
        int             (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
        char            *usage;         /* Usage message        (short) */
#ifdef  CONFIG_SYS_LONGHELP
        char            *help;          /* Help  message        (long)  */
#endif
#ifdef CONFIG_AUTO_COMPLETE
        /* do auto completion on the arguments */
        int             (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

typedef struct cmd_tbl_s        cmd_tbl_t;   

展开后的变量定义如下:

ll_entry_declare(cmd_tbl_t, _name, cmd)
 以bootm为例
 _type=cmd_tbl_t
 _name=bootm
 _list=cmd
 这里最终会转化为如下数据结构

 cmd_tbl_t _u_boot_list_2_cmd_2_bootm=
 {
 _name=bootm,
 _maxargs=CONFIG_SYS_MAXARGS,
 _rep=1,
 _cmd=do_bootm,
 _usage="boot application image from memory",
 _help=bootm_help_text,
 _comp=NULL,
 }
 并且这个数据结构给存放到了 .u_boot_list_2_cmd_2_bootm段中!!!和我们上述的完全一致。

 

 

5.2.2 do_bootm

具体解析见下节uboot boot kernel

 

5.3 命令的处理

5.3.1 基本流程

假设传进来的命令是cmd_name。
(1)获取命令表;
(2)从命令表中搜索和cmd_name匹配的项;
(3)执行对应项中的命令;

5.3.2 代码详解

通过find_cmd可以获取命令对应的命令表项cmd_tbl_t 。在命令表中(对应的段中)寻找与cmd_name相匹配的命令表项;

文件:common/command.c

enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable, ulong *ticks)
{
    enum command_ret_t rc = CMD_RET_SUCCESS;
    cmd_tbl_t *cmdtp;

    /* Look up command in command table */
    cmdtp = find_cmd(argv[0]); // 第一个参数argv[0]表示命令,调用find_cmd获取命令对应的表项cmd_tbl_t。
    if (cmdtp == NULL) {
        printf("Unknown command '%s' - try 'help'\n", argv[0]);
        return 1;
    }

    /* found - check max args */
    if (argc > cmdtp->maxargs)
        rc = CMD_RET_USAGE;   // 检测参数是否正常

    /* If OK so far, then do the command */
    if (!rc) {
        if (ticks)
            *ticks = get_timer(0);
        rc = cmd_call(cmdtp, flag, argc, argv);   // 调用cmd_call执行命令表项中的命令,成功的话需要返回0值
        if (ticks)
            *ticks = get_timer(*ticks);            // 判断命令执行的时间
        *repeatable &= cmdtp->repeatable;        // 这个命令执行的重复次数存放在repeatable中的
    }
    if (rc == CMD_RET_USAGE)
        rc = cmd_usage(cmdtp);    // 命令格式有问题,打印帮助信息
    return rc;
}


返回0表示执行成功,返回非0值表示执行失败。
后续需要执行一个命令的时候,直接调用cmd_process即可。

后续我们我们分成“查找cmd对应的表项”、“执行对应表项中的命令”两部分进行说明.

 

 1 查找cmd对应的表项 --- find_cmd

文件:common/command.c
cmd_tbl_t *find_cmd(const char *cmd)
{
    cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
    // 获取命令表的地址,start表示指向命令表的指针,具体实现看后面
    const int len = ll_entry_count(cmd_tbl_t, cmd);
    // 获取命令表的长度,具体实现看后面
    return find_cmd_tbl(cmd, start, len);
    // 以命令表的指针和命令表的长度为参数,查找和cmd匹配的表项,也就是cmd_tbl_t结构,并返回给调用者。
}

文件:include/linker_lists.h
// 获取命令表的起始地址,也就是.u_boot_list_2_cmd_1的地址,
#define ll_entry_start(_type, _list) \
        ({ \
            static char start[0] __aligned(4) __attribute__((unused, \
            section(".u_boot_list_2_"#_list"_1"))); \
            (_type *)&start; \
        })
        

// 获取命令表的结束地址,也就是.u_boot_list_2_cmd_3的地址,
#define ll_entry_end(_type, _list) \
        ({ \
            static char end[0] __aligned(4) __attribute__((unused, \
            section(".u_boot_list_2_"#_list"_3"))); \
            (_type *)&end; \
        })
        

// 计算命令表的长度
#define ll_entry_count(_type, _list) \
        ({ \
            _type *start = ll_entry_start(_type, _list); \
            _type *end = ll_entry_end(_type, _list); \
            unsigned int _ll_result = end - start; \
            _ll_result; \
        })


/* find command table entry for a command */
cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len)
{
    #ifdef CONFIG_CMDLINE
    cmd_tbl_t *cmdtp;
    cmd_tbl_t *cmdtp_temp = table; /* Init value */
    const char *p;
    int len;
    int n_found = 0;

    if (!cmd)
        return NULL;
    /*
    * Some commands allow length modifiers (like "cp.b");
    * compare command name only until first dot.
    */
    len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);

    for (cmdtp = table; cmdtp != table + table_len; cmdtp++) {
        // 通过指针递增的方式,查找table中的每一个cmd_tbl_t
        if (strncmp(cmd, cmdtp->name, len) == 0) {
            if (len == strlen(cmdtp->name))
                return cmdtp; /* full match */
            // 如果是命令字符串和表项中的name完全匹配,包括长度一致的,则直接返回

            cmdtp_temp = cmdtp; /* abbreviated command ? */
            n_found++;
            // 如果命令字符串和表项中的name的前面部分匹配的话(我们称为部分匹配),暂时保存下来,并且记录有几个这样的表项,主要是为了支持自动补全的功能
        }
    }
    if (n_found == 1) { /* exactly one match */
        return cmdtp_temp;
    // 如果部分匹配的表项是唯一的话,则可以将这个表项返回,主要是为了支持自动补全的功能
    }
#endif /* CONFIG_CMDLINE */

    return NULL; /* not found or ambiguous command */
}


2 执行对应表项中的命令——cmd_call

通过调用cmd_call可以执行命令表项cmd_tbl_t 中的命令
文件:common/command.c
 

static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    int result;

    result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
    // 直接执行命令表项cmd_tbl_t 中的cmd命令处理函数
    if (result)
        debug("Command failed, result=%d\n", result);
        // 命令返回非0值时,报错
    return result;
}

 

 

6 uboot boot kernel

文件:common/cmd_bootm.c

U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);

do_bootm参数说明:
当执行‘bootm 0x20008000 0x21000000 0x22000000’命令时,do_bootm会被调用
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
cmdtp:传递的是bootm的命令表项指针,也就是_u_boot_list_2_cmd_2_bootm的指针
argc=4,argv[0]="bootm", argv[1]=0x20008000, arv[2]=0x21000000, argv[3]=0x22000000
 

/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    /* determine if we have a sub command */
    argc--; argv++;
    if (argc > 0) {
    char *endp;

    simple_strtoul(argv[0], &endp, 16);
    /* endp pointing to NULL means that argv[0] was just a
    * valid number, pass it along to the normal bootm processing
    *
    * If endp is ':' or '#' assume a FIT identifier so pass
    * along for normal processing.
    *
    * Right now we assume the first arg should never be '-'
    */
    if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
        return do_bootm_subcommand(cmdtp, flag, argc, argv);
    }
    // 以上会判断是否有子命令,这里我们不管
    // 到这里,参数中的bootm参数会被去掉,
    // 也就是当'bootm 0x20008000 0x21000000 0x22000000'时
    // argc=3, argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000
    // 当‘bootm 0x30000000’时
    // argc=1, argv[0]=0x30000000
    
    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
    BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
    BOOTM_STATE_LOADOS |
    BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
    BOOTM_STATE_OS_GO, &images, 1);
    // 最终对调用到do_bootm_states,在do_bootm_states中执行的操作如states标识所示:
    // BOOTM_STATE_START
    // BOOTM_STATE_FINDOS
    // BOOTM_STATE_FINDOTHER
    // BOOTM_STATE_LOADOS
    // BOOTM_STATE_OS_PREP
    // BOOTM_STATE_OS_FAKE_GO
    // BOOTM_STATE_OS_GO
}
所以bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数。

6.1 do_bootm_states函数

do_bootm_states
    >>>bootm_start(cmdtp, flag, argc, argv)//填充image中的verify和lmb
    >>>bootm_find_os(cmdtp, flag, argc, argv)//填充image中的os和ep,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
    >>>bootm_find_other(cmdtp, flag, argc, argv)//实现rd_start, rd_end,ft_addr和initrd_end,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员

    >>>ret = bootm_load_os(images, &load_end, 0);

    >>>bootm_os_get_boot_func()//用于获取到对应操作系统的启动函数,被存储到boot_fn 中
    >>>boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

    >>>boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);

下面对着几个函数的功能做一个简要的说明:

(1)bootm_start & bootm_find_os & bootm_find_other
主要负责解析环境变量、参数、uImage,来填充bootm_headers_t images这个数据结构。

typedef struct bootm_headers {
    image_info_t os; /* os image info */
    ulong ep; /* entry point of OS */

    ulong rd_start, rd_end;/* ramdisk start/end */

    char *ft_addr; /* flat dev tree address */
    ulong ft_len; /* length of flat device tree */
    
    ulong initrd_start;
    ulong initrd_end;
    ulong cmdline_start;
    ulong cmdline_end;
    bd_t *kbd;
    int verify; /* getenv("verify")[0] != 'n' */
    #ifdef CONFIG_LMB
    struct lmb lmb; /* for memory mgmt */
    #endif
}

(2)bootm_load_os

在bootm_load_os中,会对kernel镜像进行load到对应的位置上,并且如果kernel镜像是被mkimage压缩过的,那么会先经过解压之后再进行load。(这里要注意,这里的压缩和Image压缩成zImage并不是同一个,而是uboot在Image或者zImage的基础上进行的压缩!!!)

static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
int boot_progress)
{
    image_info_t os = images->os;
    ulong load = os.load; // kernel要加载的地址
    ulong blob_start = os.start;
    ulong blob_end = os.end;
    ulong image_start = os.image_start; // kernel实际存在的位置
    ulong image_len = os.image_len; // kernel的长度
    bool no_overlap;
    void *load_buf, *image_buf;
    int err;

    load_buf = map_sysmem(load, 0);
    image_buf = map_sysmem(os.image_start, image_len);

    // 调用bootm_decomp_image,对image_buf的镜像进行解压缩,并load到load_buf上
    err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
    load_buf, image_buf, image_len,
    CONFIG_SYS_BOOTM_LEN, load_end);
    。。。
}

结果上述步骤之后,kernel镜像就被load到对应位置上了。

(3)bootm_os_get_boot_func

bootm_os_get_boot_func用于获取到对应操作系统的启动函数,被存储到boot_fn 中。

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
    ...
    boot_fn = bootm_os_get_boot_func(images->os.os);
    ...
}

boot_os_fn *bootm_os_get_boot_func(int os)
{
    return boot_os[os];
    // 根据操作系统类型获得到对应的操作函数
}

static boot_os_fn *boot_os[] = {
    ...
#ifdef CONFIG_BOOTM_LINUX
    [IH_OS_LINUX] = do_bootm_linux,
#endif
}

可以看出最终启动linux的核心函数是do_bootm_linux。

(4)boot_selected_os及其它

另外几个函数最终也是调用到boot_fn,对应linux也就是do_bootm_linux,所以这里不在说明了。

6.2 do_bootm_linux函数

文件:arch/arm/lib/bootm.c

int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
    /* No need for those on ARM */
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
    return -1;

    // 当flag为BOOTM_STATE_OS_PREP,则说明只需要做准备动作boot_prep_linux
    if (flag & BOOTM_STATE_OS_PREP) {
    boot_prep_linux(images);
    return 0;
    }

    // 当flag为BOOTM_STATE_OS_GO ,则说明只需要做跳转动作
    if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
    boot_jump_linux(images, flag);
    return 0;
    }

    boot_prep_linux(images); // 以全局变量bootm_headers_t images为参数传递给boot_prep_linux
    boot_jump_linux(images, flag);// 以全局变量bootm_headers_t images为参数传递给    boot_jump_linux
    return 0;
}

boot_prep_linux用于实现跳转到linux前的准备动作;boot_jump_linux用于跳转到linux中。
都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。


6.2.1 boot_prep_linux函数


首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。
boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中。
实现如下:

static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
#ifdef CONFIG_OF_LIBFDT
    debug("using: FDT\n");
    if (image_setup_linux(images)) {
        printf("FDT creation failed! hanging...");
        hang();
    }
#endif
}

这里没有深入学习image_setup_linux,等后续有需要的话再进行深入。


6.2.2 boot_jump_linux函数


文件:arch/arm/lib/bootm.c

static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number; // 从bd中获取machine-id,
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params); // kernel入口函数,也就是kernel的入口地址,对应kernel的_start地址。
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO); // 伪跳转,并不真正地跳转到kernel中

    kernel_entry = (void (*)(int, int, uint))images->ep;
    // 将kernel_entry设置为images中的ep(kernel的入口地址),后面直接执行kernel_entry也就跳转到了kernel中了
    // 这里要注意这种跳转的方法

    debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    announce_and_cleanup(fake);

    // 把images->ft_addr(fdt的地址)放在r2寄存器中
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;

    if (!fake) {
        kernel_entry(0, machid, r2);
        // 这里通过调用kernel_entry,就跳转到了images->ep中了,也就是跳转到kernel中了,具体则是kernel的_start符号的地址。
        // 参数0则传入到r0寄存器中,参数machid传入到r1寄存器中,把images->ft_addr(fdt的地址)放在r2寄存器中
        // 满足了kernel启动的硬件要求
    }
}


到这里,经过kernel_entry之后就跳转到kernel环境中了。

 

 

 

你可能感兴趣的:(UBoot)