uboot分析:uboot的启动过程分析

Posted linfeng-learning

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uboot分析:uboot的启动过程分析相关的知识,希望对你有一定的参考价值。

(注:本文参考资料:朱有鹏嵌入式课程。本文为个人学习记录,如有错误,欢迎指正。)

1. U-Boot启动过程概述

  • U-Boot的启动过程分为两个阶段。

  • 第一阶段:主要是SOC内部的初始化,板级的初始化比较少,所以移植的修改量比较小。此阶段由汇编语言编写,代码主体分布在/ubootcpu/s5pc11x/start.S和/uboot/board/samsung/x210/lowlevel_init.S中。

  • 第二阶段:主要是板级的初始化,SOC内部的初始化比较少,移植的修改量主要在此。此阶段由c语言编写,代码主体分布在/uboot/lib_arm/board.c中。

  • 红色字体部分和板级关系较大,是移植的重点修改部分。

技术分享图片

 

2. U-Boot启动代码具体分析

2.1 第一阶段(/ubootcpu/s5pc11x/start.S)

(0)头文件包含

  • /uboot/include/config.h文件是在配置过程中中生成的(具体/uboot/mkconfig文件中实现),其中的内容就是“#include <configs/x210_sd.h>”,即包含开发板的配置头文件。

  • version.h文件是uboot的版本信息文件,是在编译过程中生成的。具体的U-Boot的version信息可以在/uboot/Makefile中更改

  • U-Boot中的头文件包含其实都不是真正被包含的文件,它们大多是在配置编译阶段产生的符号链接或者是具有符号链接功能的头文件(/uboot/mkconfig中创建符号链接)。总之,它们的功能都类似于符号链接,目的是让uboot的源码更具灵活性和移植性。

#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h>

(1)填充16字节的校验位

  • uboot.bin镜像在开头需要加16字节的检验头(详见 irom application note文件)。这一部分主要功能是在代码段最开始处放置16字节的填充位,即通过伪.word指令定义4个word(32位)的空间来在代码段最开始处占16字节。

  • 在此处只是占据16字节的空间,校验头的正确值在编译阶段通过mkv210image.c中计算并填充的。

#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif

(2)设置异常向量表

  • .globl是globl是把_start这个标号全局化,是编译器的操作,并不是汇编指令。_start代表程序start.S的入口。

  • 这段代码的功能是设置异常向量表。b reset 所处的位置是与异常向量表基地址偏移量为的0的地方,所以当复位异常发生时(开机也属于复位异常),CPU会自动跳转入异常表基址偏移量为0处执行复位异常程序,即跳转执行reset部分的代码。

  • 这部分代码只是构建了异常向量表,当每个异常向量指向的内容为空(除reset异常外)。因为U-Boot比较简单,只是作为引导程序,所以不需要很细致的处理各种异常。

.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

(3)禁能IRQ和FIQ,使能SVC模式

  • 开机后程序从此时正式开始运行。

  • 程序上电之初,异常向量表未初始化,故先禁能IRQ(普通中断)和FIQ(快速中断)。

  • 使能SVC模式,即超级用户模式。SVC 模式,主要用于 SWI(软件中断)和 OS(操作系统)。这个模式有额外的特权,允许你进一步控制计算机。例如,你必须进入超级用户模式来读取一个插件(podule)。这不能在用户模式下完成。

/*
 * the actual reset code
 */
reset:
/*
 * set the cpu to SVC32 mode and IRQ & FIQ disable
 */
@;mrs    r0,cpsr
@;bic    r0,r0,#0x1f
@;orr    r0,r0,#0xd3
@;msr    cpsr,r0
msr    cpsr_c, #0xd3    @ I & F disable, Mode: 0x13 - SVC

(4)初始化相关全局变量

  • TEXT_BASE是U-Boot代码的链接地址,在/uboot/board/samsung/config.mk文件中定义,该文件在/uboot/Makefile->x210_sd_config段中生成。TEXT_BASE = 0xc3e00000

_TEXT_BASE:
.word    TEXT_BASE
  • 设置U-Boot在DDR中的物理地址,即运行地址,U-Boot重定位将整个U-Boot拷贝至DDR中的_TEXT_PHY_BASE。CFG_PHY_UBOOT_BASE在开发板配置文件(/uboot/include/configs/x210_sd_config)中定义,为0x33e00000

_TEXT_PHY_BASE:
.word    CFG_PHY_UBOOT_BASE
  • 在开启MMU之后,虚拟地址段0xc0000000-0xd0000000将被映射到物理地址段0x30000000-0x40000000。所以TEXT_BASE = 0xc3e00000对应的物理地址为TEXT_BASE = 0x33e00000,即U-Boot的链接地址与运行地址一致。

(5)禁用Cache和MMU

  • caches是CPU的缓冲区,它的作用是存放常用的数据和指令,提高cpu与内存之间数据与指令的传输速率。

  • MMU是CPU的内存管理单元,它的作用是转换虚拟地址与物理地址。

  • 关闭caches的原因:上电初始,DDR未初始化,当CPU从cache中取数据时,可能导致数据预取异常。另一方面,当汇编指令读取缓存数据,而实际物理地址对应的数据发生变化,导致CPU不能获取最新的数据。在C语言中无需关闭caches,因为C语言中可以使用volatile关键字避免上述情况。

  • 关闭MMU的原因:U-Boot的作用是硬件初始化和引导操作系统,纯粹的初始化阶段,开启MMU会导致这个过程更复杂。

cpu_init_crit:
    .................................................
     /*
    * disable MMU stuff and caches
    */
    mrc    p15, 0, r0, c1, c0, 0
    bic    r0, r0, #0x00002000 @ clear bits 13 (--V-)
    bic    r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
    orr    r0, r0, #0x00000002 @ set bit 1 (--A-) Align
    orr    r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
    mcr    p15, 0, r0, c1, c0, 0

(6)判断启动介质

  • 先从(PRO_ID_BASE+OMR_OFFSET)地址的寄存器读取启动介质信息,经过数据处理之后放入r2寄存器中。
  • 然后通过比较r2的值来判定启动介质,经过判断得到当前的启动介质为SD/MMC,在把BOOT_MMCSD宏写入r3中。
  • 最后将启动介质信息从寄存器r3中写入(INF_REG_BASE+INF_REG3_OFFSET)地址的寄存器中
/* Read booting information */  
      ldr    r0, =PRO_ID_BASE
      ldr    r1, [r0,#OMR_OFFSET]
      bic    r2, r1, #0xffffffc1
............................................

     /* SD/MMC BOOT */
    cmp     r2, #0xc
    moveq   r3, #BOOT_MMCSD
    .............................................
     ldr    r0, =INF_REG_BASE
    str    r3, [r0, #INF_REG3_OFFSET] 

(7)第一次初始化栈(SRAM中)

  • 第一次设置栈,由于不设置栈的话无法使用嵌套bl跳转指令,即双层函数调用,因为只有一个LR寄存器,而后面的lowlevel_init就有双层跳转,故这里开始设置SRAM中用的栈。
  • 这里栈设置的地址并没有按照s5pv210的推荐地址,将0xd0036000地址下12Bytes内存空间设为栈内存,只要保证该段内存不会被其他程序占用即可。
ldr    sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub    sp, sp, #12    /* set stack */
mov    fp, #0

(8)跳转执行lowlevel_init部分

/uboot/board/x210/lowlevel_init

1)判断复位的类型

  • 复位分为多种情况,如冷启动、休眠唤醒等。
  • 判断复位类型的意义在于:冷启动需要重新初始化DDR,而休眠唤醒不需要再次初始化DDR。
/* check reset status  */
ldr    r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr    r1, [r0]
bic    r1, r1, #0xfff6ffff
cmp    r1, #0x10000
beq    wakeup_reset_pre
cmp    r1, #0x80000
beq    wakeup_reset_from_didle

2)关看门狗

  • U-Boot主要功能是初始化硬件资源和引导OS,基本不会出现程序跑飞的情况。因此,为避免喂狗的麻烦,等U-Boot一切就绪正常运行后,再打开看门狗。
/* Disable Watchdog */
ldr    r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */
mov    r1, #0
str    r1, [r0]

3)开发板供电锁存

  • 设置开发板的供电按键锁存功能。注意是哪个gpio,移植过程可能需要修改。
/* PS_HOLD pin(GPH0_0) set to high 开发板供电上锁*/
ldr    r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr    r1, [r0]
orr    r1, r1, #0x300    
orr    r1, r1, #0x1    
str    r1, [r0]

4)检测程序当前的执行位置(SRAM或DDR)

  • 本段的功能是检测当前代码的执行位置。判断是在SRAM中还是DDR中,即CPU是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR的初始化代码。
  • bic是位清除指令,其功能是:将pc中的某些位清零(r0中为1的位清零),剩下一些特殊的bit位赋值给r1。
  • 比较链接地址和当前地址的特定位,即比较r1和r2。若比较链接地址和当前地址的特定位相等,说明当前代码处于DDR中(休眠唤醒),则跳转到标号1处执行后面的代码(即跳过时钟、DDR的初始化代码);否则,继续执行后续的时钟、DDR的初始化代码。
ldr    r0, =0xff000fff
bic    r1, pc, r0    /* r0 <- current base addr of code */
ldr    r2, _TEXT_BASE    /* r1 <- original base addr in ram */
bic    r2, r2, r0    /* r0 <- current base addr of code */
cmp     r1, r2          /* compare r0, r1            */
beq     1f    /* r0 == r1 then skip sdram init  */

5)初始化时钟、DDR、串口

  • 跳转执行时钟初始化函数system_clock_init,然后返回。时钟的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改,移植过程基本不需要需改该部分代码。
  • 跳转执行DDR初始化函数mem_ctrl_asm_init,然后返回。其中,DMC0,DMC1等设置很重要,直接和板子上内存的大小和分布有关,需要注意。DDR的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改。
  • 跳转执行串口初始化函数uart_asm_init,成功执行后打印‘O’,然后返回。‘O’可作为调试的帮助。
  • 若定义了ONFIG_ONENAND,则初始化ONENAND;若定义了CONFIG_NAND,则初始化NAND。
  • 在返回start.S前打印了‘K’,与之前的‘O’组成“OK”,这是uboot的第一条打印信息,可以用来判断lowlevel_init是否正常运行。
  • 把之前保存在栈中的lr值弹出到pc中,来返回到start.S。
/* init system clock */
bl system_clock_init

/* Memory initialize */
bl mem_ctrl_asm_init
1: /* for UART */ bl uart_asm_init //初始化完成打印‘O‘ bl tzpc_init //基本没用 #if defined(CONFIG_ONENAND) bl onenandcon_init #endif #if defined(CONFIG_NAND) /* simple init for NAND */ bl nand_asm_init #endif .............................................. /* Print ‘K‘ */ ldr r0, =ELFIN_UART_CONSOLE_BASE ldr r1, =0x4b4b4b4b str r1, [r0, #UTXH_OFFSET] pop {pc}

(9)再次供电锁存

  • 从lowlevel_init中跳转回start.S中,再次进行开发板供电锁存设置(在lowlevel_init中已经设置过一次),可能为代码的冗余。注意引脚号。
ldr    r0, =0xE010E81C       /* PS_HOLD_CONTROL register */
ldr    r1, =0x00005301     /* PS_HOLD output high    */
str    r1, [r0]

(10)第二次初始化栈(DDR中)

  • 为了即将执行的c程序做准备,这里开始第二次设置栈,设置于DDR中。这里将栈设置在_TEXT_PHY_BASE,即U-Boot在DDR中的真正物理地址(U-Boot运行地址)。由于栈是满减栈,所以紧挨着uboot放置也不会冲突。
/* get ready to call C functions */
ldr    sp, _TEXT_PHY_BASE    /* setup temp stack pointer */
sub    sp, sp, #12
mov    fp, #0    /* no previous frame, so fp=0 */

(11)再次检测当前程序执行地址(SRAM或DDR)

  • 在lowlevel_init中,检测当前代码的执行位置。判断是在SRAM中还是DDR中,即cpu是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR的初始化代码。
  • 在此处再次检测当前代码的执行位置,从而来决定是否要跳过重定位代码。
ldr    r0, =0xff000fff
bic    r1, pc, r0         /* r0 <- current base addr of code */
ldr    r2, _TEXT_BASE       /* r1 <- original base addr in ram */
bic    r2, r2, r0         /* r0 <- current base addr of code */
cmp     r1, r2          /* compare r0, r1            */
beq     after_copy    /* r0 == r1 then skip flash copy  */

(12)通过引脚来判断启动介质

  • 这一段区别于之前的启动介质判断,本段是通过引脚来判断启动介质的
  • 将0xD0037488地址的寄存器的值放入r0,该寄存器里的值可以判断BL1是从SD/MMC哪个通道启动的,将寄存器的值放入r1。这个寄存器的信息在irom applicationnote里有记载。
  • 将0xEB200000这个值放入r2,该值的表示是2号方式启动,并将寄存器的值和r2(0xEB200000)进行对比。如果相同则说明BL1是从SD卡通道2启动,跳转入标号mmcsd_boot处执行SD/MMC重定位相关代码(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy)。
  • 若不是从SD卡通道2启动,则读取之前存储的启动介质信息(INF_REG_BASE+INF_REG3_OFFSET地址的寄存器),该信息有上一次启动介质检测所得(见(6))。随后,跳转至启动介质相应的xxx_boot代码中执行重定位相关的代码(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy)。
#if defined(CONFIG_EVT1)

/* If BL1 was copied from SD/MMC CH2 */

ldr    r0, =0xD0037488
ldr    r1, [r0]
ldr    r2, =0xEB200000
cmp    r1, r2
beq     mmcsd_boot

#endif

ldr    r0, =INF_REG_BASE
ldr    r1, [r0, #INF_REG3_OFFSET]
cmp    r1, #BOOT_NAND    /* 0x0 => boot device is nand */
beq    nand_boot
cmp    r1, #BOOT_ONENAND    /* 0x1 => boot device is onenand */
beq    onenand_boot
cmp     r1, #BOOT_MMCSD
beq     mmcsd_boot
cmp     r1, #BOOT_NOR
beq     nor_boot
cmp     r1, #BOOT_SEC_DEV
beq     mmcsd_boot

nand_boot:
mov    r0, #0x1000
bl    copy_from_nand
b    after_copy

onenand_boot:
bl    onenand_bl2_copy
b    after_copy

mmcsd_boot:
#if DELETE
ldr     sp, _TEXT_PHY_BASE      
sub     sp, sp, #12
mov     fp, #0
#endif
bl      movi_bl2_copy
b       after_copy

nor_boot:
bl      read_hword
b       after_copy

(13)进行U-Boot重定位

  • 具体代码在/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy中。
  • 定义一个函数指针类型copy_sd_mmc_to_mem。
typedef u32(*copy_sd_mmc_to_mem) (u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);

函数返回值

u32代表函数执行成功与否

u32 channel

代表SD/MMC启动通道号(0-4)

u32 start_block

起始块地址

u16 block_size

复制的块的个数

u32 *trg

复制操作的目的地址,一般是DDR内的地址

u32 init

init一般给0,不用多管

  • 首先读取位于0xD0037488的寄存器值至变量ch中,再一次检查启动介质(SD卡的通道号)。
  • 然后0xD0037F98地址中的指针变量强制转换为copy_sd_mmc_to_mem类型,并赋值给copy_bl2。该指针变量指向一个函数,这个函数就是CPU的IROM中固化用来复制SD/mmc中的内容至任意地址的函数。
  • 最后,根据ch变量的值判断SD的通道号,并将整个U-Boot从相应的SD通道号中拷贝至DDR的指定地址(CFG_PHY_UBOOT_BASE = _TEXT_PHY_BASE = 0x33e00000)处。
  • MOVI_BL2_POS是烧录uboot时的扇区,MOVI_BL2_BLKCNT是uboot占的扇区数,具体的定义和计算都在/uboot/include/movi.h中。CFG_PHY_UBOOT_BASE为U-Boot的在DDR中的物理地址(运行地址),具体的定义和计算都在/uboot/include/configs/x210_sd.h中。
void movi_bl2_copy(void){
ulong ch;

    ch = *(volatile u32 *)(0xD003A508);

  copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008));

    u32 ret;

   if (ch == 0xEB000000) //SD卡通道0

    {
    ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);    
}   else if (ch == 0xEB200000) //SD卡通道2 { ................. } }

(14)建立虚拟地址映射表并开启MMU

  • 从movi_bl2_copy返回之后,将跳转至start.S->after_copy,建立虚拟地址映射表并开启MMU。
bl      movi_bl2_copy
b       after_copy
  • TTB(TranslationTableBase),即转换表的基地址。转换表是MMU将虚拟地址映射为物理地址的凭据,建立整个虚拟地址映射的关键就是建立转换表,此表存放在内存中,工作时不需要软件干涉。
  • 只要将转换表TTB的基地址(_mmu_table_base)放入cp15的c2寄存器,MMU就能自动使用虚拟地址映射。_mmu_table_base定义在start.S其值为标号mmu_table,mmu_table在lowlevel_init中定义。
  • 最后通过设置cp15的c1寄存器来开启mmu,以实现虚拟地址映射和内存访问权限管理等功能。
#if defined(CONFIG_ENABLE_MMU)

/*使能域访问*/
enable_mmu:    
ldr    r5, =0x0000ffff    
mcr    p15, 0, r5, c3, c0, 0    @load domain access register    

/*将转换表ttb的基地址放入cp15的c2寄存器,mmu就能自动使用虚拟地址映射*/    
ldr    r0, _mmu_table_base    ldr    r1, =CFG_PHY_UBOOT_BASE    
ldr    r2, =0xfff00000    
bic    r0, r0, r2    
orr    r1, r0, r1    
mcr    p15, 0, r1, c2, c0, 0    

/*通过设置cp15的c1寄存器来开启mmu,以实现虚拟地址映射和内存访问权限管理等功能*/    
mmu_on:    
mrc    p15, 0, r0, c1, c0, 0    
orr    r0, r0, #1    
mcr    p15, 0, r0, c1, c0, 0    
nop    
nop    
nop    
nop

#endif
  • 详细的建表步骤在/uboot/board/x210/lowlevel_init。
  • 映射表中规定了内存映射和管理是以块为单位的,在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB。真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责整个内存空间(0-4G,32位CPU)的映射。
  • 带参宏将FL_SECTION_ENTRY base,ap,d,c,b定义成一个word大小的特定值,这个特定值就是转换表的填充量。
  • 参数分析:参数base是映射出来的段地址的基地址,从第20位开始。20位的大小正好是1MB,故此映射表采用的是段式映射;ap是访问控制位,从第10位开始。d、c、b都是一些权限位。
#ifdef CONFIG_ENABLE_MMU    
#ifdef CONFIG_MCP_SINGLE    
   
.macro FL_SECTION_ENTRY base,ap,d,c,b //macro指令是汇编中宏定义
.word (base << 20) | (ap << 10) |           (d << 5) | (1<<4) | (c << 3) | ( << 2) | (1<<1)
    .endm                  //即结束宏定义


.section .mmudata, "a"
.align 14
.globl mmu_table

mmu_table:    
.set __base,0   //设置变量__base值为
/*.rept 0x100相当于for循环,一共循环0x100次,所以这一块代码创建了0x100(256)个转换表单元*/          
.rept 0x100 /*利用刚才定义的带参宏创建转换表的内容,变量__base和3,0,0,0作为参数*/ FL_SECTION_ENTRY __base,3,0,0,0 .set __base,__base+1 //相当于base=base+1 .endr //结束循环 .........................................//后续继续建表
  • 由以上代码分析,得出整张转换表的设定如下。

输入虚拟地址

输出的物理地址

长度

0-10000000

0-10000000

256MB

10000000-20000000

0

256MB

20000000-60000000

20000000-60000000

1GB

60000000-80000000

0

512MB

80000000-b0000000

80000000-b0000000

768MB

b0000000-c0000000

b0000000-c0000000

256MB

c0000000-d0000000

30000000-40000000

256MB

d-完

d-完

768MB

  • 由此可知,此表仅仅将c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存,其他的虚拟地址空间是原样映射的。所以uboot的链接地址(0xc3e00000)对应物理地址(0x33e00000),即U-Boot的链接地址和运行地址(CFG_PHY_UBOOT_BASE = 0x33e00000)一致。

(15)第三次初始化栈(DDR中)

  • 第三次设置栈,仍然设在DDR中。虽然上一次已经在DDR中设置过,但是是紧挨着uboot存放的,位置不合理。
  • 所以本次将栈设置uboot链接地址上方2MB处,这个位置合理、紧凑、安全。
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
    ldr    sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000

(16)清bss段

  • _bss_start和_bss_end是链接脚本中定义的。
  • 利用循环清零。
clear_bss:
ldr    r0, _bss_start         /* find start of bss segment     */
ldr    r1, _bss_end    /* stop here                 */
mov     r2, #0x00000000    /* clear                   */


clbss_l:
str    r2, [r0]          /* clear loop...               */
add    r0, r0, #4
cmp    r0, r1
ble    clbss_l

(17)远跳转至start_armboot(U-Boot第二阶段)

  • 从SRAM中远跳转到DDR中函数start_armboot处,start_armboot定义在根目录下/uboot/lib_arm/board.c中。
  • 至此,uboot第一阶段结束,进入第二阶段,至DDR处执行。
ldr    pc, _start_armboot
_start_armboot:
    .word start_armboot

2.2 第二阶段(/uboot/lib_arm/board.c->start_armboot)

(1)初始化全局变量

1)定义一个函数指针类型。

typedef int (init_fnc_t) (void);

2)定义并初始化init_sequence数组

定义并初始化一个全局数组init_sequence,其元素是指向init_fnc_t类型函数的指针,这些函数都是用来初始化各个功能的函数。这些函数的具体功能如下:

  • cpu_init:CPU初始化函数,但cpu初始化工作已在U-Boot的第一阶段完成,所以该函数为空。
  • board_init:开始板级初始化函数,配置网卡用到的GPIO、机器码、内存传参地址,并填充至gd结构体。
  • interrupt_init:定时器的初始化函数,和中断无关(应用,如bootdelay)。
  • env_init:环境变量的初始化函数,由于环境变量还没从启动介质中取到DDR中,故此处的初始化只是对DDR中的环境变量(U-Boot自带的环境变量)进行一个简单的判定,检测其是否可用。真正的初始化在start_armboot里面。U-Boot支持多种不同的启动介质(如norflash、nandflash、sd/mmc),而各种介质存取操作env的方法都是不同的,故U-Boot中包含在多个文件中包含了env_init函数。而此U-Boot的启动介质为SD/MMC,故当前的env_init函数在/uboot/common/Env_movi.c文件中。
  • init_baudrate:串口波特率初始化函数,设置串口波特率,并填充至gd结构体。具体的串口初始化工作已在U-Boot的第一阶段完成(lowlevel_init)。
  • serial_init:串口初始化函数,但具体的串口初始化工作已在U-Boot的第一阶段完成(lowlevel_init),所以该函数为空。
  • console_init_f:控制台初始化函数,名字中的_f表示这是第一阶段的初始化,由于第二阶段的初始化之前需要夹杂一些前提代码,故将在start_armboot执行。
  • display_banner:用来通过串口控制台显示U-Boot的版本信息(logo)。
  • print_cpuinfo:打印CPU的信息。
  • checkboard:确认开发板信息。即,打印出当前开发板的名称信息。
  • init_func_i2c:I2C初始化函数,可在开发板配置文件(/uboot/include/configs/x210_sd.h)中设置U-Boot是否使用I2C。
  • dram_init:DDR初始化函数,由于DDR硬件层面的初始化已在第一阶段完成,故此函数只是通过宏来获取DDR相关信息,并填充至gd结构体。DDR信息相关的宏在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改。
  • display_dram_config:打印bd中的DDR配置信息(由dram_init获取)
init_fnc_t *init_sequence[] = {    
cpu_init,    /* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
reloc_init,   /* Set the relocation done flag, mustdo this AFTER cpu_init()*/
#endif    
board_init,    /* basic board dependent setup */    
interrupt_init,    /* set up exceptions */    
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 */    
display_dram_config,    
NULL,
};

3)初始化全局变量结构体gd_t、bd_t

  • 本段功能是初始化全局变量结构体。
  • gd_t是保存全集变量的结构体类型,在/uboot/include/asm-arm/Global_data.h文件中定义。
typedef    struct    global_data {   
bd_t *bd;//该指针指向bd_t结构体,其具体内容涉与板级硬件资源信息相关的全局变量   
unsigned long    flags;  
unsigned long    baudrate;    //串口波特率   
unsigned long    have_console;//标志位,是否使用控制台console   
unsigned long    reloc_off;   //重定位有关偏移量   
unsigned long    env_addr;    //环境变量结构体的偏移量   
usigned long    env_valid;   //标志位,表示内存中的环境变量能否使用   
unsigned long    fb_base;     //帧缓存基地址,和显示有关
#ifdef CONFIG_VFD   
unsigned char    vfd_type;    /* display type */
#endif   
void    **jt;    /* jump table *///基本无用
} gd_t;
  • gd_t结构体中的bd指向一个bd_t结构体,bd_t结构体存放的是和开发板硬件相关的全局变量,在/uboot/include/asm-arm/U-Boot.h文件中定义。
typedef struct bd_info {    
int    bi_baudrate;         //串口波特率    
unsigned long    bi_ip_addr;    //IP地址    
unsigned char    bi_enetaddr[6]; //MAC地址    
struct environment_s    *bi_env;    
ulong bi_arch_number;    //机器码    
ulong    bi_boot_params;    //U-Boot给内核传参的地址    
struct    //DDR相关信息    
{            
ulong start;        
ulong size;     
} 
bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1    
unsigned char bi_enet1addr[6];
#endif
} bd_t;
  • gd(globle data)是指向gd_t结构体的指针,在/uboot/include/asm-arm/Global_data.h文件中定义。
/*register表示尽量让cpu放在寄存器中,以提高其读写速度;asm (“r8”)是指定放在寄存器的r8*/

#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
  • 给gd结构体指针分配内存地址,以后可通过gd指针访问全局变量结构体gd_t。
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */  

ulong gd_base;  

gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);

...............  

gd = (gd_t*)gd_base;

#endif
  • 为bd_t结构体分配内存空间,将gd下的一段内存空间分配给bd_t。
  • 清空gd_t、bd_t结构体。
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t))

(2)运行init_sequence数组中所有的初始化函数

  • 利用for循环遍历init_sequence数组,并执行初始化函数。若函数返回值为0,则说明初始化函数执行错误,挂起程序,进入死循环。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) 
{   
if ((*init_fnc_ptr)() != 0)
  {     hang ();   } }

(3)初始化堆内存

  • 配置堆内存的起止地址、终止地址。
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
    mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else
    mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif

(4)初始化外部存储设备

  • 若在开发板配置文件中配置过外部存储设备,则进行相应的初始化。
#if defined(CONFIG_SMDKC110)

#if defined(CONFIG_GENERIC_MMC)
puts ("SD/MMC:  ");
mmc_exist = mmc_initialize(gd->bd);
if (mmc_exist != 0)
{
puts ("0 MB
");
}
#endif

#if defined(CONFIG_MTD_ONENAND)
puts("OneNAND: ");
onenand_init();
/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
#else
//puts("OneNAND: (FSR layer enabled)
");
#endif

#if defined(CONFIG_CMD_NAND)
puts("NAND:    ");
nand_init();
#endif

#endif /* CONFIG_SMDKC110 */

(5)环境变量的重定位

  • 环境变量的重定位,将环境变量从启动介质中读到DDR内,环境变量的位置是通过原始分区信息表中读到的。
/* initialize environment */
env_relocate ();

(6)获取IP地址和MAC地址

  • 从重定位之后的环境变量中获取IP地址和MAC地址(以太网地址),放到bd中的全局变量内,以供使用。
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];

i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;


for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}

(7)其他函数

devices_init ();     /* get the devices list going. *///设备驱动初始化,从linux中移植而来

jumptable_init ();//跳转表初始化,其实没用

console_init_r (); //控制台的第二部分的初始化,有实质性的功能
enable_interrupts ();//开启中断,实质是一个空函数,U-Boot中并没有使用中断

(8)进入main_loop循环

  • 至此,U-Boot启动第二阶段结束。即整个U-Boot启动完成,进入main_loop循环。若控制台有任意输入,则进入控制台命令解析-执行的循环。若无,则U-Boot将启动内核。

 

for (;;) 
{
    main_loop ();
}

至此,U-Boot启动完成。


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 









以上是关于uboot分析:uboot的启动过程分析的主要内容,如果未能解决你的问题,请参考以下文章

海思芯片(hi3516dv300)uboot启动过程分析

新版本uboot启动流程分析

BSP开发之ubootuboot常用命令以及代码分析

为什么要有uboot?带你全面分析嵌入式linux系统启动过程中uboot的作用

uboot启动过程

uboot启动过程