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

2023-11-06

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

目录

正文

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

回到顶部

1. U-Boot启动过程概述

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

  • 第一阶段:主要是SOC内部的初始化,板级的初始化比较少,所以移植的修改量比较小。此阶段由汇编语言编写,代码主体分布在/uboot/cpu/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) | (\b << 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\n");
}
#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)\n");
#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启动完成。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uboot分析:uboot的启动过程分析 的相关文章

  • MLO/uboot-spl.bin和uboot.img/uboot.bin

    前段时间使用TI的am4378芯片 xff0c 发现系统在SD卡启动的时候 xff0c 启动文件使用的是MLO和uboot img xff1b 而Norflash和eMMC启动的时候使用的是 uboot spl bin和uboot bin
  • petalinux uboot源码在哪的问题

    petalinux uboot源码在哪的问题 提出问题解决问题注意 xff1a 要知道自己的版本 1 uboot2 kernel 提出问题 petalinux 源码目录存放在哪里的问题 xff0c 也就是petalinux工程的uboot和
  • petalinux uboot源码怎么打补丁

    petalinux的源码 petalinux工程对于我来说 xff0c 就是有一点不能直接起修改源码 xff0c 你需要间接的修改源码的内容 xff1f 这个修改你还需要遵从petalinux的规章流程 当你不知道的时候你会感到无从下手 x
  • 最新uboot的Kbuild系统 3 .config的生成

    前面的工作产生了一个conf 关键点是由conf产生 config的过程 最后是通过执行 scripts kconfig conf defconfig 61 arch configs rpi defconfig Kconfig 生成的 Kc
  • U-boot引导流程分析一

    U Boot 全称 Universal Boot Loader 即通用引导程序 是遵循GPL条款的开放源码项目 它的源码目录 编译形式与Linux内核很相似 事实上 不少U Boot源码就是相应的Linux内核源程序的简化 尤其是一些设备的
  • 嵌入式开发——uboot中命令执行函数(main_loop函数)

    1 main loop 函数源码 从uboot中摘抄的部分main loop函数 为了便于理解 函数只保留了主线部分代码 一些用宏定义控制的代码被删除掉了 void main loop void static char lastcomman
  • uboot编译报错解决

    uboot编译报错 root ubuntu home gjt uboot u boot 2015 01 make scripts kconfig conf silentoldconfig Kconfig scripts kconfig co
  • 3.移植uboot-使板卡支持nor、nand

    在上一章 我们添加了nor nand启动后 uboot启动出如下图所示 上面的Flash failed 是属于uboot第二阶段函数board init r 里的代码 代码如下所示 位于arch arm lib board c 第二阶段 v
  • uboot 中内存测试,内存检测方法

    DDR内存子系统常见硬件错误及Uboot中检测流程 在 U Boot中 Denx U Boot的开发商 针对常见的DDR内存故障进行了严格的检测处理 下图描述了该检测处理过程的三个步骤 检测数据线 地址线和DDR物理存储部件 主要涉及这三个
  • IMX6ULL NXP官方原版u-boot编译烧录体验以及出现的问题

    编译 guangjie ubuntu work imx6ull uboot imx rel imx 4 1 15 2 1 0 ga xgj cat make imx6ull emmc sh bin bash make ARCH arm CR
  • 03_uboot的源码目录分析

    一 文件夹 1 api 硬件无关的功能函数的API 这些函数是uboot本身使用的 uboot移植时基本不用管 2 api examples API相关的测试事例代码 3 board board是板的意思 就是开发板 这个文件夹下放的每一个
  • GNU风格 汇编语法总结

    汇编源程序一般用于系统最基本的初始化 初始化堆栈指针 设置页表 操作 ARM的协处理器等 这些初始化工作完成后就可以跳转到C代码main函数中执行 1 GNU汇编语言语句格式 任何Linux汇编行都是如下结构
  • BootLoader介绍

    文章目录 一 BootLoader的引入 二 BootLoader的启动方式 三 BootLoader的结构和启动过程 四 自己写一个BootLoader 1 BootLoader第一阶段 2 BootLoader第二阶段 一 BootLo
  • 自定义busybox文件系统存在的问题

    1 串口终端看不到命令行入口 只能在显示器端HDMI 看到 2 内核默认无法加载除了busybox openwrt文件系统 debian ubuntu无法加载
  • 海思芯片(hi3516dv300)uboot镜像生成过程详解

    1 前言 1 本文介绍的uboot编译过程是基于海思提供SDK包里的uboot源码进行编译 具体的编译参数是根据hi3516dv300芯片来设置的 编译生成的uboot烧录镜像也是用于hi3516dv300芯片的uboot镜像 2 对于Ma
  • U-Boot顶层Makefile详解

    文章目录 一 U Boot工程目录分析 1 打包编译好的uboot 2 目录介绍 1 arch 2 board 3 configs 4 Makefile 5 config 6 README 二 VSCode工程创建 1 新建工程 2 屏蔽不
  • 如何在U-Boot和Linux内核中添加自定义的ATAG变量?

    我要添加定制atagU Boot 和 Linux 内核中的变量 我怎样才能做到这一点 有没有什么程序可以添加ATAG变量在U Boot and Linux 最新的 Linux 内核正试图废弃ATAGS with 设备树 但是 那setup
  • Linux编译 |入口点无效

    我正在编译一个linux内核使用 mipsel 工具链 一切工作正常 除了最后一点指出无效的入口点 sh 0 Can t open arch mips boot tools entry rm f arch mips boot vmlinux
  • 为什么补丁找不到这个文件?

    我想对 u boot 源代码应用补丁 但是 Linux 不允许我这么做 我拥有的 reg ubuntu NextGen trunk FW thirdparty u boot patch p1 lt u boot u boot 2013 01
  • Yocto 添加自定义 UBoot 环境变量

    我正在尝试通过 Yocto 构建过程添加两个新的 u boot 环境变量 My file u boot imx 2021 04 bbappend包含 FILESEXTRAPATHS prepend THISDIR PN SRC URI fi

随机推荐

  • php安装swoole扩展(linux)

    在Linux中php安装swoole扩展 相关环境 LAMP或LNMP开发环境 L 指linux A 指Apache M 指MySQL P 指PHP N 指Nginx 下载swoole wget c https github com swo
  • 33 KVM管理设备-配置虚拟机PCIe控制器

    文章目录 33 KVM管理设备 配置虚拟机PCIe控制器 33 1 概述 33 2 配置PCIe Root PCIe Root Port和PCIe PCI Bridge 33 2 1 简化配置方法 33 2 1完整配制方法 33 KVM管理
  • 提供许可证到期新通知!标签管理软件BarTender v2019 R5上线!

    BarTender在150多个国家 地区拥有成千上百的用户 在标签 条形码 证卡和 RFID 标记的设计和打印领域是全球首屈一指的软件 BarTender既可以单独运行 也可以与任何其他程序集成 几乎是所有按需打印或打标应用的完美解决方案
  • 【网络编程】---C++实现原始套接字捕获数据包

    C 实现原始套接字捕获数据包 引言 原始套接字与TCP套接字和UDP套接字的区别 原始套接字编程使用的场合 原始套接字的通信过程 1 基于原始套接字的数据发送过程 2 基于原始套接字的数据接收过程 创建原始套接字 常用协议定义列表 使用原始
  • 显著性检测的四种经典方法

    最近闲来蛋痛 看了一些显著性检测的文章 只是简单的看看 并没有深入的研究 以下将研究的一些收获和经验共享 先从最简单的最容易实现的算法说起吧 1 LC算法 参考论文 Visual Attention Detection in Video S
  • angualr学习笔记3-angular模型(model)

    AngularJS ng model 指令 ng model 指令用于绑定应用程序数据到 HTML 控制器 input select textarea 的值 ng model 指令 ng model 指令可以将输入域的值与 AngularJ
  • 执行yum软件包索引步骤报错

    解决 进入目录 cd etc yum repos d 执行rm rf删除所有 rm rf 然后 yum update 重新设置yum源 curl o etc yum repos d CentOS Base repo http mirrors
  • 生活总是不经意给你开个玩笑_你在开玩笑的故事吗

    生活总是不经意给你开个玩笑 In 1994 Vincent Connare who had previously designed type at Apple and Agfa Compugraphic was working at Mic
  • js 异步执行_webpack模块化原理-异步加载模块

    在上篇文章中 我们介绍了 webpack 同步加载模块的原理 这篇文章 我们来介绍一下 webpack 异步加载模块 异步加载模块 还是先做一些准备工作 首先定义一个依赖模块 math js math js 采用 ES6 module 导出
  • 人工智能交互革命:探索ChatGPT的无限可能 第4章 ChatGPT-智能客服

    第4章ChatGPT 智能客服 4 1智能客服的定义与发展 智能客服是一种利用人工智能技术 为客户提供在线服务和支持的解决方案 它能够通过自然语言处理 机器学习等技术 识别和理解客户的问题 并提供针对性的解决方案 智能客服可以通过多种渠道提
  • Ajax、fetch、axios的区别与优缺点;axios跨域问题

    背景 前端的技术发展速度非常的快 异步请求也是其重要的体现之一 从最早的原生XHR 再到JqueryAjax的统治时代 再到近来 fetch axios等技术也开始出现并大量投入使用 原生Ajax Ajax是指一种创建交互式网页应用的网页开
  • 【软件工程】概要设计说明书

    概要设计说明书 1引言 1 1编写目的 这篇文章的编写目的主要是为了开发此系统为系统做一个总体的结构设计 经评审后进一步细化 分别对每一模块进行详细细化的解决方案 接口和数据库等方面的设计 明确描述所有输入输出参数 类型逻辑算法以及调用关系
  • DS二叉排序树之查找

    题目描述 给出一个数据序列 建立二叉排序树 并实现查找功能 对二叉排序树进行中序遍历 可以得到有序的数据序列 输入 第一行输入t 表示有t个数据序列 第二行输入n 表示首个序列包含n个数据 第三行输入n个数据 都是自然数且互不相同 数据之间
  • u-boot常用命令

    u boot常用命令 查看u boot所支持的命令 查询命令 u boot版本 环境变量 板子相关信息 环境变量操作 内存操作 网络操作 EMMC和 SD卡操作 FAT 格式文件系统操作 EXT格式文件系统操作 ubi格式文件系统操作 bo
  • 汇编JMP语句 IP值和偏移量的问题。问题如下,我想知道IP值是怎么变化的。还有8086一条指令占个几字节啊

    汇编JMP语句 IP值和偏移量的问题 问题如下 我想知道IP值是怎么变化的 还有8086一条指令占个几字节啊 2011 10 16 22 26 干物虫子 分类 汇编语言 浏览404次 1 在0624单元内忧一条二字节JMP SHORT OB
  • Linux下连接Oracle数据库并进行一系列操作

    Linux下操作Oracle数据库 操作Oracle要确保服务器上已经安装了Oracle数据库 1 连接到有Oracle数据库的服务器 ssh 172 16 100 201 服务器IP 如图 2 根据提示 输入服务器root密码 输入无误即
  • 游戏服务端框架之配置与玩家数据库设计

    目录 背景 策划数据库的概念 用户数据库的概念 数据库ORM方案 配置数据库的设计
  • 虎牙SRE谈可观测:如何做到比用户和老板更早发现业务异常?

    一分钟精华速览 可观测能力是指在复杂的软件系统中能及时 准确感知到服务状态 特别是异常或故障的发生 确定异常的影响范围 异常部位边界 判定异常点位 并由相关人员或软件做出准确决策的能力 本文作者结合虎牙SRE实践及20余年架构 研发 运维经
  • C# 指针的使用 ref byte 转 byte 或 byte [] ref 与指针

    C 不推荐用指针 但可以使用指针 同时在某些情况下又不得不使用指针 比如C 调用了C 的DLL 而经常会用到指针 下在介绍C 代码中使用指针 1 首先要使用指针 先在要C 工程属性中设置 右键工程名 属性 生成 允许不安全代码 2 添加 引
  • uboot分析:uboot的启动过程分析

    uboot分析 uboot的启动过程分析 目录 1 U Boot启动过程概述 2 U Boot启动代码具体分析 2 1 第一阶段 ubootcpu s5pc11x start S 2 2 第二阶段 uboot lib arm board c