U-boot初始化阶段流程分析

U-boot的初始化主要分为两个阶段

第一阶段:主要是SOC内部的初始化,板级的初始化比较少,所以移植的修改量比较小。此阶段由汇编语言编写,代码主体分布在start.S和lowlevel_init.S中。
其中start.S作为主干,其主要流程为:
注:加粗的比较重要,和板级有点关系
1. 填充16字节的校验位
2. 设置异常向量表
3. 设置cpu为SVC模式
4. 禁用cach和mmu
5. 判断启动介质
6. 在SRAM中设置栈
7. 跳入 lowlevel_init.S
关看门狗
设置供电锁存
判断当前执行位置
初始化DDR
初始化串口(打印‘OK’)
8. 再次供电锁存
9. 在DDR中设置栈
10. 通过引脚判断启动介质
11. 跳到movi.c
重定位
12. 跳入 lowlevel_init.S设置虚拟地址映射
13. 在DDR中合理的地方设置栈
14. 清bss段
15. 跳入board.c,到DDR中执行程序

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

  1. 初始化全局变量gd_t结构体
  2. 遍历数组执行所有的初始化函数
    cpu初始化(没做事)
    开始板级初始化,配置网卡用到的GPIO、机器码、内存传参地址
    定时器的初始化
    环境变量判定
    设置波特率
    设置串口(没做事)
    初始化控制台(没做事)
    显示uboot的logo
    打印cpu的信息
    确认开发板信息
    确认DDR信息
    打印DDR信息
  3. 初始化堆
  4. 利用了linux的设备驱动初始化mmc
  5. 环境变量重定位
  6. 往gd赋值ip地址
  7. 往gd赋值以太网地址
  8. linux设备驱动初始化
  9. 跳转表初始化(没做事)
  10. 控制台的第二部分的初始化
  11. 中断初始化(没做事)
  12. 开发板上比较晚期的初始化(没做事)
  13. 利用驱动初始化网卡
  14. 升级uboot功能(没做事)

执行完初始化的两个阶段后就进入了uboot的命令行循环部分
个人感觉U-boot的初始化相对于其他部分来说,是真正和板级相关的,所以对于移植来讲是比较重要的,毕竟上层的逻辑代码都是不用修改的…..

1.第一阶段(start.S部分)

下面是简要的代码流程节选分析

#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h> 
  • 由于在config.mk中为编译器指定了头文件搜索路径(即顶层目录下的include文件夹),故头文件将默认从顶层目录下的include文件夹内搜索
  • 这些头文件其实都不是真正被包含的文件,它们大多是在配置编译阶段产生的符号链接或者是具有符号链接功能的头文件。总之,它们的功能都类似于符号链接,目的是让uboot的源码更具灵活性和移植性
  • 比如第一句代码中的config.h,其实产生的效果是让start.S包含了根目录下的include/configs/x210_sd.h
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED) 
    .word 0x2000
    .word 0x0
    .word 0x0
    .word 0x0
#endif
  • 这一部分主要功能是在代码段最开始处放置16字节的填充位,因为sd/nand启动时需要16字节的校验头,这个校验头是在编译阶段通过mkv210image.c中计算并填充的
  • 通过伪.word指令定义4个word(32位)的空间来在代码段最开始处占16字节
  • 这段空间的地址最后将位于BL1的起始地址之前,即IROM中0xD0020000至0xD0020010,而且并不作为BL1的一部分
.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
  • 这段代码是设置异常向量表
  • .globl是把_start这个标号全局化,是编译器的操作,并不是汇编指令
  • 标号_start代表uboot的第一句代码b reset的地址值,即$(TEXT_BASE)指定的重定位后DDR内的虚拟地址即0xc3e00000,但事实上在重定位前,这句代码的实际地址是cpu规定的BL1的起始地址0xD0020010(sram内)
  • 从b reset开始其实是在设置异常向量表,这句代码所处的位置是与异常向量表基地址偏移量为的0的地方。当复位异常发生时(开机也是复位异常),cpu会自动跳转入异常表基址偏移量为0处执行复位异常程序
  • 其实uboot并没有很细致的处理各种异常,因为uboot比较简单,作为引导程序并不是OS,所以没有必要
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 
  • 开机后程序正式从此开始
  • msr cpsr_c, #0xd3 功能是将IRQ和FIQ禁能,同时把cpu设为SVC模式。具体操作是将0xd3写入cpsr的controlbits,msr是程序状态寄存器读写指令,cpsr_c是指令集中的关键字,表示cpsr的controlbits
cpu_init_crit:
    一大堆无用的代码,就不贴了
    bl  disable_l2cache

    bl  set_l2cache_auxctrl_cycle

    bl  enable_l2cache

    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

    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

  • 这段开始是和cpu有关初始化相关代码,和板级没有关系,故一般不会去改动
  • bl是带返回的跳转,此处跳转去执行禁能L2 cache 相关的代码,然后返回
  • 后面又刷新L1 cache的Icache和Dcache,再后面禁能了MMU功能
    /* Read booting information */      
    ldr r0, =PRO_ID_BASE        
    ldr r1, [r0,#OMR_OFFSET]
    bic r2, r1, #0xffffffc1

        中间一大堆就不贴了

    /* NAND BOOT */     
    cmp r2, #0x0 @ 512B 4-cycle
    moveq   r3, #BOOT_NAND

    cmp r2, #0x2 @ 2KB 5-cycle
    moveq   r3, #BOOT_NAND

    cmp r2, #0x4 @ 4KB 5-cycle 8-bit ECC
    moveq   r3, #BOOT_NAND

    cmp r2, #0x6 @ 4KB 5-cycle 16-bit ECC
    moveq   r3, #BOOT_NAND

    cmp r2, #0x8 @ OneNAND Mux
    moveq   r3, #BOOT_ONENAND

    /* SD/MMC BOOT */   
    cmp     r2, #0xc 
    moveq   r3, #BOOT_MMCSD 

    ldr r0, =INF_REG_BASE
    str r3, [r0, #INF_REG3_OFFSET] 
  • 这里比较重要,是启动介质的判断。最开始从宏内读取启动介质,然后把读到的数据经过处理存放在r2寄存器中
  • 后面都是通过比较r2的值来判断启动介质,最后判断得到当前的启动介质是SD/MMC然后把宏#BOOT_MMCSD写入寄存器r3中,宏的值为0x03
  • 再将启动介质信息再从寄存器r3中写入INF_REG3_OFFSET寄存器
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
    sub sp, sp, #12 /* set stack */
    mov fp, #0
  • 第一次设置栈,由于不设置栈的话无法使用嵌套bl跳转指令,即双层函数调用,因为只有一个LR寄存器,而后面的lowlevel_init就有双层跳转,故这里开始设置SRAM中用的栈。这里栈设置的地址并没有按照s5pv210的推荐地址,不过也无关痛痒

2.第一阶段(跳入lowlevel_init部分)

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
  • 本段功能是判断复位的类型,复位分为好多种。如冷上电,休眠唤醒等
/* Disable Watchdog */                          
    ldr r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */
    mov r1, #0
    str r1, [r0]
  • 关看门狗
/* 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]
  • 设置开发板的供电按键锁存功能,注意是哪个gpio
    ldr r0, =0xff000fff 
    bic r1, pc, r0      /* r0 <- current base addr of code */
    ldr r2, _TEXT_BASE  
    bic r2, r2, r0  
    cmp     r1, r2                
    beq     1f      
  • 本段的功能是检测当前代码的执行位置。判断是在SRAM中还是DDR中,即cpu是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR的代码
  • 一开始把一个值赋给r0寄存器,这个值其实相当于mask掩码,后面马上会用到它
  • bic指令的功能是:将pc中的某些位清零(r0中为1的位清零),剩下一些特殊的bit位赋值给r1,其实有点类似PLC中ladderlogic的maskmove功能。相当于c中的r1=pc & ~(ff000fff)
  • 将_TEXT_BASE即uboot链接初始地址赋给r2。同理,将r2中的某些位清零(r0中为1的位清零),然后去比较r1和r2的值
  • 比较r1和r2,即比较的是链接初始地址和当前地址它们的特定位,这些特定位决定了当前代码的执行位置,如果当前地址和链接初始地址的特定位相同,那么说明当前处于DDR,就调到标号1处执行后面的代码(即跳过时钟、DDR初始化代码)
    bl system_clock_init    
/* Memory initialize */
    bl mem_ctrl_asm_init
1:
    /* for UART */
    bl uart_asm_init                            
    bl tzpc_init                                    /*这个不用去管它*/

#if defined(CONFIG_ONENAND)
    bl onenandcon_init                              
#endif
#if defined(CONFIG_NAND)
    /* simple init for NAND */
    bl nand_asm_init    
#endif
  • 跳转到初始化时钟的函数,执行然后返回
  • 跳转到初始化memory(DDR)的函数,执行然后返回,其中,DMC0,DMC1等设置很重要,直接和板子上内存的大小和分布有关,需要注意一下
  • 用bl指令跳转到初始化串口的函数,执行然后返回,其中,打印了一个O,这个O可以作为调试的帮助
  • 如果定义了CONFIG_ONENAND,则初始化onenand
  • 如果定义了CONFIG_NAND,则初始化nand
/* Print 'K' */
    ldr r0, =ELFIN_UART_CONSOLE_BASE                
    ldr r1, =0x4b4b4b4b                             
    str r1, [r0, #UTXH_OFFSET]
    pop {pc}                                        
  • 在返回start.S前打印了’K’,与之前的’O’组成OK,这是uboot的第一条打印信息,可以用来判断lowlevel_init是否正常运行
  • 把之前保存在栈中的lr值弹出到pc中,来返回到start.S

3.第一阶段(跳回start.S)

    ldr r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
    ldr r1, =0x00005301  /* PS_HOLD output high */  
    str r1, [r0]
  • 又做了一遍供电按键锁存设置……已经在lowlevel_init中做过了,这里又重复了一遍
    /* 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 */
  • 为了即将执行的c程序做准备,这里开始第二次设置栈,设置于刚在lowlevel_init中初始化的DDR中
  • 这里将栈设置在_TEXT_PHY_BASE,即uboot的链接的真正物理地址。由于栈是满减栈,所以紧挨着uboot放置也不会冲突
    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 */
  • 再次检测当前代码的执行位置。判断是在SRAM中还是DDR中。原理和lowlevel_init中一模一样
  • 如果当前地址和链接初始地址的特定位相同,那么说明当前处于DDR,就跳过重定位代码
    /* If BL1 was copied from SD/MMC CH2 */
    ldr r0, =0xD0037488                             
    ldr r1, [r0]                                
    ldr r2, =0xEB200000                             
    cmp r1, r2                                      
    beq     mmcsd_boot                              
  • 这一段区别于之前那段无用的启动介质判断,本段是通过引脚来判断启动介质的
  • 将一个值放入r0,该值其实是一个寄存器的地址,该寄存器里的值可以判断BL1是从SD/MMC哪个通道启动的,将寄存器的值放入r1,这个寄存器的信息在irom applicationnote里有记载
  • 将0xEB200000这个值放入r2,该值的表示是2号方式启动,并将寄存器的值和r2(0xEB200000)进行对比
  • 如果相同则说明BL1是2号方式启动,跳转入标号mmcsd_boot处执行mmc和sd重定位相关代码
    bl      movi_bl2_copy                           
    b       after_copy                              
  • 跳转到mmc和sd的重定位函数,这个函数是c语言写的,在根目录下cpu/s5pc11x/movi.c,执行完后再返回
  • 然后跳转到后面after_copy标号处,去执行重定位完了之后需要做的一些设置

4.第一阶段之重定位(movi.c)

虽然movinand是mmc的一种,但是个人认为三星把文件名字取成movi.c还是不太妥当…

typedef u32(*copy_sd_mmc_to_mem)                
(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
  • 定义一个类型,类型的名字是copy_sd_mmc_to_mem,根据分析,首先这个copy_sd_mmc_to_mem是一个指针;其次,这个指针指向的是一个函数,故copy_sd_mmc_to_mem是一个指向函数的指针类型;其中,函数的返回值为u32,参数为(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init)
  • 可知这个函数就是IROM中固化用来复制SD/mmc中的内容至任意地址的函数,u32类型的返回值代表了函数执行成功与否;参数channel代表sd/mmc启动通道号(0-4);start_block是起始块地址;block_size是复制的块的个数;trg是复制操作的目的地址,一般是DDR内的地址;init一般给0,不用多管
void movi_bl2_copy(void)
{
    ulong ch;
#if defined(CONFIG_EVT1) 
    ch = *(volatile u32 *)(0xD0037488);             
    copy_sd_mmc_to_mem copy_bl2 =                   
        (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
  • 首先读取位于0xD0037488的寄存器值,再一次检查启动介质(启动方式)……
  • 定义一个变量copy_bl2,其类型是指向某种特定类型函数的指针类型,即开头定义的copy_sd_mmc_to_mem类型
  • 首先将数字sh0xD0037F98强制类型转换成指向一个u32数据(固化在IROM中的一个函数的首地址)的指针
  • 简而言之,就是说在cpu的0xD0037F98处(IROM内),存着一个函数的首地址,现在把这个首地址赋给一个指针,我们就能用这个指向函数的指针来调用这个函数了

    u32 ret;
    if (ch == 0xEB000000) {                         
        ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);                 
  • 判断,如果通道是通道0启动,则使用固化在cpu内部IROM中的重定位到连接在通道0上的启动介质
  • 这些参数都是宏,具体的定义和计算都在根目录下/include/movi.h中,MOVI_BL2_POS是烧录uboot时的扇区,MOVI_BL2_BLKCNT是uboot占的扇区数,CFG_PHY_UBOOT_BASE为链接的物理地址

5.第一阶段(再次跳回start.S)

#if defined(CONFIG_ENABLE_MMU) 
enable_mmu:                                         
    /* enable domain access */
    ldr r5, =0x0000ffff
    mcr p15, 0, r5, c3, c0, 0       @load domain access register 
    /* Set the TTB register */                      
    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               

    /* Enable the 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
  • 这里开始进行和虚拟地址映射相关的设置
  • TTB是TranslationTableBase即转换表的基地址,转换表是MMU将虚拟地址翻译为物理地址的凭据,建立整个虚拟地址映射的关键就是建立转换表,此表存放在内存中,工作时不需要软件干涉
  • 只要将转换表ttb的基地址放入cp15的c2寄存器,mmu就能自动使用虚拟地址映射,_mmu_table_base定义在start.S其值为标号mmu_table
  • 然后把转换表ttb的基地址放入cp15的c2寄存器
  • 最后通过设置cp15的c1寄存器来开启mmu,以实现虚拟地址映射和内存访问权限管理等功能
    详细的建表步骤(在lowlevel_init中)
    #ifdef CONFIG_MCP_SINGLE /*这里开始设置虚拟映射转换表,这里使用的是段式映射模式,即以段(1MB)为单位进行映射*/
                                                /*因此表中的一个单元只能管1MB,整个4G内存需要创建4096个表单元,所以后面采用了循环的方法来创建*/
                                            /*查表的方法是:例如虚拟地址的高12位是x,则去查第x个表单元,该表单元的高12位就是物理地址的高12位,其实这一部分只要知道一些大概的原理即可,不必深究,等用到了再去看*/
    /* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b           /*.macro指令是汇编中宏定义的意思,此带参宏将FL_SECTION_ENTRY base,ap,d,c,b定义成一个word大小的特定值*/
    .word (\base << 20) | (\ap << 10) | \       /*这个特定值就是转换表的填充量,其中,参数base是映射出来的段地址的基地址,从第20位开始。*/
          (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)/*20位的大小正好是1MB,由此可知转换表中未保留前20位,映射出来的地址之间间隔为1MB*/
                                                /*故这里采用的是段式映射;继续来分析参数,ap是访问控制位,从第10位开始。d、c、b都是一些权限位*/
.endm                                           /*.endm,即结束宏定义*/
.section .mmudata, "a"
    .align 14
    // the following alignment creates the mmu table at address 0x4000.
    .globl mmu_table
mmu_table:
    .set __base,0                               /*设置变量__base值为0*/
    // Access for iRAM
    .rept 0x100                                 /*.rept 0x100相当于for循环,一共循环0x100次,所以这一块代码创建了0x100(256)个转换表单元*/
    FL_SECTION_ENTRY __base,3,0,0,0             /*利用刚才定义的带参宏创建转换表的内容,变量__base和3,0,0,0作为参数*/
    .set __base,__base+1                        /*相当于base=base+1*/
    .endr                                       /*.endr对应.rept,表示循环体的结束*/

    // Not Allowed
    .rept 0x200 - 0x100                         /*把.rept重设为0x100,这里写成0x200 - 0x100是为了好理解,表示这里要填充第0x200到0x100的表单元*/
    .word 0x00000000                            /*把全0填充进了这256个转换表单元.....*/
    .endr

    .set __base,0x200                           /*这里开始填充第0x600 - 0x200的表单元,原理和前0x100个一模一样*/
    // should be accessed
    .rept 0x600 - 0x200
    FL_SECTION_ENTRY __base,3,0,1,1
    .set __base,__base+1
    .endr

    .rept 0x800 - 0x600                         /*把0x800 - 0x600的表单元填充全0,即禁止使用这些内存空间*/
    .word 0x00000000
    .endr

    .set __base,0x800                           /*后面的填充方法都和前面一样*/
    // should be accessed
    .rept 0xb00 - 0x800
    FL_SECTION_ENTRY __base,3,0,0,0
    .set __base,__base+1
    .endr

由此可见整张转换表的设定

输入虚拟地址 输出的物理地址 长度
0-10000000 0-10000000 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的链接地址(c3e开头的)其实是33e开头的地址

stack_setup:                                        
#if defined(CONFIG_MEMORY_UPPER_CODE) 
    ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
  • 第三次设置栈,仍然设在DDR中。虽然之前已经在DDR中设过一次,但是是紧挨着uboot存放的,这个位置不合理
  • 所以本次将栈设置uboot链接地址上方2MB处,这个位置合理、紧凑、安全
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                                     
  • 清bss段,_bss_start和_bss_end是链接脚本中定义的
  • 利用循环清零
ldr pc, _start_armboot                          
  • 从SRAM中远跳转到DDR中函数start_armboot处,start_armboot定义在根目录下/lib_arm/board.c中
  • 至此,uboot第一阶段结束,进入第二阶段,至DDR处执行

6.第二阶段

typedef int (init_fnc_t) (void);

init_fnc_t *init_sequence[] = {     
    cpu_init,       /* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
    reloc_init,     /* Set the relocation done flag, must do this AFTER cpu_init(), but as soon as possible */
#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)
#endif
    dram_init,      /* configure available RAM banks */
    display_dram_config,
    NULL,
};
  • 首先第一句定义一个返回值为int,参数为void的函数类型,命名为init_fnc_t
  • init_sequence是一个数组,元素是指向init_fnc_t类型函数的指针,这些函数都是用来初始化各个功能的函数,先将这个数组初始化
void start_armboot (void)
{
    init_fnc_t **init_fnc_ptr;
  • init_fnc_t是本文件开始定义的一个函数类型typedef int (init_fnc_t) (void),且init_fnc_ptr是一个二重指针。这种情况一般是创建了一个函数指针数组,不难想象,init_fnc_ptr指向了数组的首地址,数组中每个元素都是指向函数的指针
  • 在后面,我们将用这个指针来遍历数组,从而执行各个初始化函数
ulong gd_base;
    gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
    gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif 
    gd = (gd_t*)gd_base;
  • 本段功能是初始化全局变量结构体,gd的含义是globledata的意思,gd_t是保存全集变量的结构体
  • gd是这么被定义的:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm (“r8”),故gd是指向gd_t结构体的指针,register 表示尽量让cpu放在寄存器中,以提高其读写速度 asm (“r8”)是指定放在寄存器的r8中
  • 最后将gd_t这个结构体的地址赋给gd这个指针,以后就用指针gd来访问全局变量了

typedef struct  global_data {
    bd_t        *bd;//bd是一个指向bd_t类型链表的指针,bd_t链表的内容是和板级有关的全局变量
    unsigned long   flags;//某个标志位
    unsigned long   baudrate;//串口波特率
    unsigned long   have_console;   /* serial_init() was called */ //标志位,表示是否使用了控制台的
    unsigned long   reloc_off;  /* Relocation Offset *///重定位有关的偏移量
    unsigned long   env_addr;   /* Address of Environment struct *///环境变量结构体的偏移量
    unsigned long   env_valid;  /* Checksum of Environment valid? *///标志位,表示是否能使用内存中的环境变量
    unsigned long   fb_base;    /* base address of frame buffer *///帧缓存基地址,和显示有关
#ifdef CONFIG_VFD
    unsigned char   vfd_type;   /* display type */
#endif
#if 0
    unsigned long   cpu_clk;    /* CPU clock in Hz! */
    unsigned long   bus_clk;
    phys_size_t ram_size;   /* RAM size */
    unsigned long   reset_status;   /* reset status register at boot */
#endif
    void        **jt;       /* jump table *///跳转表,基本没用的
} gd_t;
  • 这是全局变量结构体的类型声明,在global_data.h中
  • 结构体中的bd指向的是一个和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));
  • 这段把gd_t结构体和bd_t结构体全部清零初始化
  • 并且分配了空间,把gd下面的一段空间分配给结构体bd_t(板级有关的全局变量),同时把此结构体地址赋给bd这个指针。由于sizeof的返回值是以字节为单位的纯数字,所以gd也要强制类型转换成指向字节大小空间的指针
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 
        if ((*init_fnc_ptr)() != 0) {                                       
            hang ();
        }           
    }
  • 其实这整个for循环是为了执行所有的初始化函数。上面这行利用函数指针执行遍历到的函数,并判断返回值是否为0(函数执行发生错误),如果返回值为0,则执行挂起程序,打印失败信息,然后进入死循环….
  • 由416行定义可知,init_sequence是一个数组,元素是指向init_fnc_t类型函数的指针。现在将其首地址赋给指针init_fnc_ptr。*init_fnc_ptr的值是数组元素(函数首地址),第一句的判断其实可以理解为*init_fnc_ptr!=NULL,由于这个数组定义的末尾元素就是NULL,所以*init_fnc_ptr的值可以判断这个数组是否已经遍历到了末尾
  • cpu_init,第一个函数是cpu_init,但cpu初始化之前都已经全部结束了,所以该函数为空…
  • board_init,开始板级初始化,配置网卡用到的GPIO、机器码、内存传参地址
  • interrupt_init,这个函数名字起得不太好,其实是定时器的初始化,和中断无关
  • env_init,环境变量的初始化,由于环境变量还没从启动介质中取到DDR中,故此处的初始化只是对DDR中的环境变量进行一个简单的判定,真正的初始化在start_armboot里面
  • init_baudrate,单纯波特率,并不设置串口
  • serial_init,设置串口,这个函数什么都没做,因为在汇编阶段串口已经被初始化过了
  • console_init_f,初始化控制台,名字中的_f表示这是第一阶段的初始化,由于第二阶段的初始化之前需要夹杂一些前提代码,故将在start_armboot执行
  • display_banner,用来通过串口控制台显示uboot的logo
  • print_cpuinfo,打印cpu的信息
  • checkboard, 确认开发板信息
  • dram_init,dram(DDR)的初始化,由于硬件层面已经在之前搞定了,这函数只是通过宏来获取DDR相关信息
  • display_dram_config,打印上面一个函数获得的DDR配置信息
#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
  • 配置堆内存,设置堆区的起始地址,和gd_base的设置方法相同
#if defined(CONFIG_X210)

    #if defined(CONFIG_GENERIC_MMC)
        puts ("SD/MMC: ");
        mmc_exist = mmc_initialize(gd->bd);
        if (mmc_exist != 0)
        {
            puts ("0 MB\n");
  • 定义了CONFIG_GENERIC_MMC,开始配置mmc
  • 打印SD/MMC相关的信息代码,具体容量的打印在 mmc_initialize函数中
  • 调用mmc初始化函数,它先用cpu级别的初始化函数做个引子,再利用了linux的设备驱动来正式初始化
    env_relocate ();
  • 这里开始是环境变量的重定位,将环境变量从启动介质中读到DDR内,利用了驱动来读,环境变量的位置是通过原始分区信息表中读到的
/* IP Address */
    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
  • 从重定位之后的环境变量中获取ip地址,放到bd中的全局变量内,以供使用
  • ip地址是由4个0-255的数组成,它恰好能放到一个32位的unsigned long中,这也是getenv_IPaddr的返回格式,故恰好可以放在变量bi_ip_addr内
/* MAC Address */                           //网卡的MAC(以太网)地址
    {
        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;
  • 用getenv_r获取以太网地址,并放到tmp内,i是返回值
  • 若返回值大于0,则表示成功,把tmp赋给s。若不大于0则失败,赋为NULL,由于以太网地址比较差,故要存在数组中。用循环放进去
  • 最后放到bd中的全局变量内,以供使用
然后是一些杂七杂八的函数
devices_init ();    /* get the devices list going. *///设备初始化,这里的设备指的是设备驱动,从linux中移植而来

#ifdef CONFIG_CMC_PU2
    load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */

    jumptable_init ();//跳转表初始化,其实没用到...
#if !defined(CONFIG_SMDK6442)
    console_init_r ();  /* fully init console as a device *///控制台的第二部分的初始化,有实质性的功能
#endif

#if defined(CONFIG_MISC_INIT_R)
    /* miscellaneous platform dependent initialisations */
    misc_init_r ();
#endif

    /* enable exceptions */
    enable_interrupts ();//中断初始化,其实这是个空函数,u-boot一般用不到中断
    board_late_init ();//开发板上比较晚期的初始化,实际是空函数


    eth_initialize(gd->bd);//利用驱动初始化网卡
    extern void update_all(void);//这里开始做升级uboot相关的内容....没用到...

  • 至此,U-boot初始化阶段结束,开始进入控制台主循环

你可能感兴趣的:(移植,汇编语言,SOC,u-boot)