uboot启动过程(一)

uboot运行有两个阶段,一个阶段是运行在SRAM中的汇编阶段,另一个是运行在DDR中的C语言阶段。我们先进行第一阶段的分析。

start.S文件

start.S文件组成了uboot的第一阶段运行内容,在链接脚本中的代码段中第一句链接的start.o就说明了这一点,我们知道C语言中main函数就是整个程序的入口,在uboot汇编阶段中,程序的入口取决链接脚本中ENTRY声明的部分,我们知道uboot中ENTRY声明的入口为_start,所以_start符号所在处的代码就是程序的起始代码,而这段代码位于cpu\s5pc11x\中的start.S文件中。

头文件包含

start.S包含了很多头文件:

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

#ifndef CONFIG_ENABLE_MMU
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE  CFG_UBOOT_BASE
#endif
#endif

config.h在配置阶段出现,在include目录下,文件内容是其实包含了x210_sd.h头文件,所以包含的其实是configs/x210_sd.h,这个文件是uboot移植时的配置文件,是由很多宏组成的。start.S后面的很多宏判断就是来自于该文件。

version.h中的内容为:

#ifndef __VERSION_H__
#define __VERSION_H__

#ifndef DO_DEPS_ONLY
#include "version_autogenerated.h"
#endif

#endif  /* __VERSION_H__ */

包含了version_autogenerated.h,这个文件我们在Makefile开头就已经见过,里面是自动生成的版本号信息。在uboot启动过程中会从串口打印出版本号。

如果定义了CONFIG_ENABLE_MMU这个宏,就会包含asm/proc/domain.h头文件,这个宏是否定义在x210_sd.h中可以找到,通过确认是定义了的,所以这个头文件会被包含进来。asm目录不是uboot原生目录,是配置时创建的符号链接,实际指向的是asm-arm,同理,proc也是一个符号链接,指向proc-armv。domain.h文件的内容为一些宏文定义。

启动校验头

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

该判断语句成立,.word是gun汇编的伪指令相当于定义了一个4字节的变量,值为后面对应的值,地址为当前地址,所以这句相当于定义了一个int类型的数据,数组里面有4个元素。其长度总共16个字节。

使用SD卡或者Nand启动镜像是需要16个字节的校验头,这16个字节就是该校验头,usb下载方式启动则不需要校验头。uboot这里放了16字节填充占位,其中的内容是不能作准的,只是保证有16字节的空间,后面需要计算校验和后重新填充(sd_fusing中)。

异常向量表

_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

_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
_pad:   ## 进行填充
    .word 0x12345678 /* now 16*4=64 */

异常向量表是硬件决定的,软件只是参照硬件设计定义了发生异常的时候跳转到哪个函数去执行,每种异常都应该被处理,防止发生异常跑飞,uboot由于程序简单并未细致处理各种异常,reset就是系统启动时执行的代码,其实每一次系统启动都是一次复位,reset的代码在下面:

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

设置CPU为svc32模式并关闭FIQ和IRQ。

坏牛肉

.global _end_vect
_end_vect:

    .balignl 16,0xdeadbeef

balignl 是一个伪指令,使内存按照16字节对齐,如果没对齐自动向后直到对齐,并依次使用0xdeadbeef来填充,可以看到填充物没有规定,使用了deadbeef这样的类单词语句。

为什么要对齐访问?有时候是效率要求,有时候是硬件的要求。

TEXT_BASE

_TEXT_BASE:
    .word   TEXT_BASE

TEXT_BASE就是Makefile配置阶段指定的TEXT_BASE,值为0xc3e00000,由此可见在源代码中和Makefile配置中很多变量是互相共用的,这个值就是从Makefile配置中传递过来的。

TEXT_PHY_BASE

/*
 * Below variable is very important because we use MMU in U-Boot.
 * Without it, we cannot run code correctly before MMU is ON.
 * by scsuh.
 */
_TEXT_PHY_BASE:
    .word   CFG_PHY_UBOOT_BASE

代表uboot早DDR内存中物理地址的基址,CFG_PHY_UBOOT_BASE是一个x210.h里面的宏,其值为”MEMORY_BASE_ADDRESS + 0x3e00000”,其中MEMORY_BASE_ADDRESS的值为0x30000000,即DDR内存的起始地址,所以CFG_PHY_UBOOT_BASE值为0x33e00000。

设置CPU为SVC模式

/*
 * 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局代码被注释掉了,我们只关心后一句代码,这一句向cpsr的c位写入0xd3,我们知道cpsr是程序状态寄存器,c位是CPU的模式位,写入0xd3之后状态位的值为10011,也就是0x13,该值代表SVC模式。第5位是0,代表为arm模式6,7位是1,代表禁止IRQ和FIQ。其实arm的cpu在复位的时候,默认就会进入SVC模式,而且整个uboot程序运行时CPU都出于SVC模式,这里使用代码设置,多一层保险。

设置L1,L2 Cache和MMU

    bl  disable_l2cache

    bl  set_l2cache_auxctrl_cycle

    bl  enable_l2cache

       /*
        * Invalidate L1 I/D
        */
        mov r0, #0                  @ set up for MCR
        mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
        mcr p15, 0, r0, c7, c5, 0   @ invalidate icache

       /*
        * 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
  1. 禁止了L2 Cache,设置L2 Cache参数,再启动L2 Cache
  2. 刷新L1 Cache的icache指令缓存和dcache数据缓存
  3. 关闭MMU和相关cache

选择启动介质



        /* Read booting information */
        ldr r0, =PRO_ID_BASE
        ldr r1, [r0,#OMR_OFFSET]
        bic r2, r1, #0xffffffc1

读取启动信息,从PRO_ID_BASE(0xE0000000)中读取数据到r0,在加上OMR_OFFSET(0x04)的偏移量,所以值为0xE0000004,这个寄存器的值是根据OM引脚自动设置的,指定了从什么地方启动,我们在代码中可以读取该值来判断来选择什么启动介质来启动。最终r2就存储了启动信息。

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

    /* NOR BOOT */
    cmp     r2, #0x14
    moveq   r3, #BOOT_NOR   

判断r2的值:

  1. 0代表512字节4cycle的nand
  2. 2代表2KB,5cycle的nand
  3. 4代表4KB,5cycle,8bitECC的nand
  4. 6代表4KB,5cycle,16bitECC的nand
  5. 8代表one nand
  6. c代表从SD/MMC启动
  7. 14代表从NOR Flash启动

这个启动信息将会放置到r3寄存器中。

设置栈

    /*
     * Go setup Memory and board specific bits prior to relocation.
     */

    ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
    sub sp, sp, #12 /* set stack */
    mov fp, #0

设置sp寄存器值为0xd0036000,这个寄存器的位置是在sram中用于放置栈的,为调用函数创建环境。因为BL调用只会将返回地址存储到lr中,但是我们只有一个lr,所以第二层函数调用前要先将lr入栈,否则函数返回时第一层函数的返回地址就丢了。之所以不在DDR中设置栈,因为当前代码还在sram中运行,DDR还没有被初始化,不能使用。

低层初始化

    bl  lowlevel_init   /* go setup pll,mux,memory */

lowlevel_init这个函数位于board/samsung/x210/lowlevel_init .S文件中。

压栈

使用语句push {lr}将lr寄存器压栈,以便于后面再进行bl调用。

检查复位状态

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

    /* IO Retention release */
    ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
    ldr r1, [r0]
    ldr r2, =IO_RET_REL
    orr r1, r1, r2
    str r1, [r0]

现代CPU在复位的时候有好几种情况,冷启动,热启动,睡眠唤醒,低功耗唤醒等,这里检测复位状态来判断属于哪种情况,例如冷上电时DDR需要初始化再使用,而其他一些情况则不需要。IO状态恢复检查也是一样的道理。

关闭看门狗


    /* Disable Watchdog */
    ldr r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */
    mov r1, #0
    str r1, [r0]

SRAM,SROM的GPIO设置

    /* SRAM(2MB) init for SMDKC110 */
    /* GPJ1 SROM_ADDR_16to21 */
    ldr r0, =ELFIN_GPIO_BASE

    ldr r1, [r0, #GPJ1CON_OFFSET]
    bic r1, r1, #0xFFFFFF
    ldr r2, =0x444444
    orr r1, r1, r2
    str r1, [r0, #GPJ1CON_OFFSET]

    ldr r1, [r0, #GPJ1PUD_OFFSET]
    ldr r2, =0x3ff
    bic r1, r1, r2
    str r1, [r0, #GPJ1PUD_OFFSET]

    /* GPJ4 SROM_ADDR_16to21 */
    ldr r1, [r0, #GPJ4CON_OFFSET]
    bic r1, r1, #(0xf<<16)
    ldr r2, =(0x4<<16)
    orr r1, r1, r2
    str r1, [r0, #GPJ4CON_OFFSET]

    ldr r1, [r0, #GPJ4PUD_OFFSET]
    ldr r2, =(0x3<<8)
    bic r1, r1, r2
    str r1, [r0, #GPJ4PUD_OFFSET]

这里指的是使用SROM_BANK进行外部扩展的SRAM以及SROM。

供电锁存

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

判断当前代码位置

/* when we already run in ram, we don't need to relocate U-Boot.
     * and actually, memory controller must be configured before U-Boot
     * is running in ram.
     */
    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   */

判断当前的代码是在哪里运行的,在SRAM中还是DDR中。由于uboot分成两部分,前一部分BL1在SRAM中,整个UBOOT在DDR中,所以这段代码有可能在SRAM中或者是DDR中运行,低功耗或者热启动时也会在DDR中,获取当前代码运行的地址也可以指导后面代码的运行,用于确定要不要初始化DDR以及初始化时钟。

  1. 向r0中加载一个常数0xff000fff
  2. bic r1, pc, r0,将pc的值中在r0中为1的bit清零,剩下的bit位赋值给r1。
  3. 将DRAM连接地址的基地址TEXT_BASE(0xc3e00000)加载到r2。
  4. bic r2, r2, r0,将r2的想赢bit也清零,剩下特定的bit位。
  5. 比较r1和r2,如果一致代表运行在DDR,不一致就是运行在SRAM中。

初始化系统时钟

    /* init system clock */
    bl system_clock_init

system_clock_init函数位于当前文件的205行,内容如下:

/*
 * system_clock_init: Initialize core clock and bus clock.
 * void system_clock_init(void)
 */
system_clock_init:

    ldr r0, =ELFIN_CLOCK_POWER_BASE @0xe0100000

    /* Set Mux to FIN */
    ldr r1, =0x0
    str r1, [r0, #CLK_SRC0_OFFSET]

    ldr r1, =APLL_LOCKTIME_VAL
    str r1, [r0, #APLL_LOCK_OFFSET]

    /********lxg added*********************/
    ldr r0, =ELFIN_CLOCK_POWER_BASE @0xe0100000

    ldr r1, =MPLL_LOCKTIME_VAL
    str r1, [r0, #MPLL_LOCK_OFFSET]
    /********end*********************/

    /* Disable PLL */
#if defined(CONFIG_CHECK_MPLL_LOCK)
retryloop:
#endif
    ldr r1, =0x0
    str r1, [r0, #APLL_CON0_OFFSET]
    ldr r1, =0x0
    str r1, [r0, #MPLL_CON_OFFSET]

    ldr r1, =0x0
    str r1, [r0, #MPLL_CON_OFFSET]

    ldr     r1, [r0, #CLK_DIV0_OFFSET]
    ldr r2, =CLK_DIV0_MASK
    bic r1, r1, r2

    ldr r2, =CLK_DIV0_VAL
    orr r1, r1, r2
    str r1, [r0, #CLK_DIV0_OFFSET]

    ldr r1, =APLL_VAL
    str r1, [r0, #APLL_CON0_OFFSET]

    ldr r1, =MPLL_VAL
    str r1, [r0, #MPLL_CON_OFFSET]

    ldr r1, =VPLL_VAL
    str r1, [r0, #VPLL_CON_OFFSET]

    /*******lxg added***********************/
    ldr r1, =EPLL_VAL
    str r1, [r0, #EPLL_CON_OFFSET]

    /*******lxg added***********************/
    ldr     r1, [r0, #CLK_DIV1_OFFSET]
    ldr r2, =CLK_DIV1_MASK
    bic r1, r1, r2

    ldr r2, =CLK_DIV1_VAL
    orr r1, r1, r2
    str r1, [r0, #CLK_DIV1_OFFSET]

    ldr     r1, [r0, #CLK_DIV2_OFFSET]
    ldr r2, =CLK_DIV2_MASK
    bic r1, r1, r2

    ldr r2, =CLK_DIV2_VAL
    orr r1, r1, r2
    str r1, [r0, #CLK_DIV2_OFFSET]

    ldr     r1, [r0, #CLK_DIV4_OFFSET]
    ldr r2, =CLK_DIV4_MASK
    bic r1, r1, r2

    ldr r2, =CLK_DIV4_VAL
    orr r1, r1, r2
    str r1, [r0, #CLK_DIV4_OFFSET]

    ldr     r1, [r0, #CLK_DIV6_OFFSET]
    ldr r2, =CLK_DIV6_MASK
    bic r1, r1, r2

    ldr r2, =CLK_DIV6_VAL
    orr r1, r1, r2
    str r1, [r0, #CLK_DIV6_OFFSET]
    /*******end*****************/
    /*******end*****************/
#if defined(CONFIG_EVT1)
    ldr r1, =AFC_ON
    str r1, [r0, #APLL_CON1_OFFSET]
#endif
    mov r1, #0x10000
1:  subs    r1, r1, #1
    bne 1b

#if defined(CONFIG_CHECK_MPLL_LOCK)
    /* MPLL software workaround */
    ldr r1, [r0, #MPLL_CON_OFFSET]
    orr     r1, r1, #(1<<28)
    str r1, [r0, #MPLL_CON_OFFSET]

    mov r1, #0x100
1:  subs    r1, r1, #1
    bne 1b

    ldr r1, [r0, #MPLL_CON_OFFSET]
    and r1, r1, #(1<<29)
    cmp r1, #(1<<29)
    bne     retryloop

    /* H/W lock detect disable */
    ldr r1, [r0, #MPLL_CON_OFFSET]
    bic     r1, r1, #(1<<28)
    str r1, [r0, #MPLL_CON_OFFSET]
#endif

    ldr r1, [r0, #CLK_SRC0_OFFSET]
    //ldr   r2, =0x10001111 //lxg changed.
    ldr r2, =0x00000111
    orr r1, r1, r2
    str r1, [r0, #CLK_SRC0_OFFSET]

    // added by terry 2012.12.4 for camera 
    ldr r1, [r0, #CLK_SRC1_OFFSET]
    bic r1, r1, #(0xf<<12)
    orr r1, r1, #(0x1<<12) //0001 XusbXTI
    str r1, [r0, #CLK_SRC1_OFFSET]

#if defined(CONFIG_MCP_AC)

    /* CLK_SRC6[25:24] -> OneDRAM clock sel = MPLL */
    ldr r1, [r0, #CLK_SRC6_OFFSET]
    bic r1, r1, #(0x3<<24)
    orr r1, r1, #0x01000000
    str r1, [r0, #CLK_SRC6_OFFSET]

    /* CLK_DIV6[31:28] -> 4=1/5, 3=1/4(166MHZ@667MHz), 2=1/3 */
    ldr r1, [r0, #CLK_DIV6_OFFSET]
    bic r1, r1, #(0xF<<28)
    bic r1, r1, #(0x7<<12)  @; ONENAND_RATIO: 0
    orr r1, r1, #0x30000000
    str r1, [r0, #CLK_DIV6_OFFSET]

#elif defined (CONFIG_MCP_H)

    /* CLK_SRC6[25:24] -> OneDRAM clock sel = 00:SCLKA2M, 01:SCLKMPLL */
    ldr r1, [r0, #CLK_SRC6_OFFSET]
    bic r1, r1, #(0x3<<24)
    orr r1, r1, #0x00000000
    str r1, [r0, #CLK_SRC6_OFFSET]

    /* CLK_DIV6[31:28] -> 4=1/5, 3=1/4(166MHZ@667MHz), 2=1/3 */
    ldr r1, [r0, #CLK_DIV6_OFFSET]
    bic r1, r1, #(0xF<<28)
    bic r1, r1, #(0x7<<12)  @; ONENAND_RATIO: 0
    orr r1, r1, #0x00000000
    str r1, [r0, #CLK_DIV6_OFFSET]  

#elif defined (CONFIG_MCP_B) || defined (CONFIG_MCP_D)

    /* CLK_SRC6[25:24] -> OneDRAM clock sel = 00:SCLKA2M, 01:SCLKMPLL */
    ldr r1, [r0, #CLK_SRC6_OFFSET]
    bic r1, r1, #(0x3<<24)
    orr r1, r1, #0x01000000
    str r1, [r0, #CLK_SRC6_OFFSET]

    /* CLK_DIV6[31:28] -> 4=1/5, 3=1/4(166MHZ@667MHz), 2=1/3 */
    ldr r1, [r0, #CLK_DIV6_OFFSET]
    bic r1, r1, #(0xF<<28)
    bic r1, r1, #(0x7<<12)  @; ONENAND_RATIO: 0
    orr r1, r1, #0x30000000
    str r1, [r0, #CLK_DIV6_OFFSET]

#elif defined (CONFIG_MCP_SINGLE)

    /* CLK_DIV6 */
    /*ldr   r1, [r0, #CLK_DIV6_OFFSET]
    bic r1, r1, #(0x7<<12)  @; ONENAND_RATIO: 0
    str r1, [r0, #CLK_DIV6_OFFSET]*/ //lxg mask

#endif  

    mov pc, lr

时钟初始化的过程和裸机程序中初始化是一样的,最后使用mov pc lr代码返回到调用函数,完成时钟的初始化。

初始化DRAM

    /* Memory initialize */
    bl mem_ctrl_asm_init

mem_ctrl_asm_init函数中初始化DDR的操作和裸机程序初始化DDR的操作是一样的。

串口初始化

    /* for UART */
    bl uart_asm_init

这个函数初始化串口的方式和裸机程序中是一样的。串口初始化完毕后通过串口发送了一个大写的“O”,用于检测初始化是否成功。

初始化TrustZone

    bl tzpc_init

初始化TrustZone,以便使用TEE环境。
至此,lowlevel_init执行完毕,最后使用pop {pc}进行函数的返回,如果初始化成功,则会打印“OK”字样。

栈设置

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

之前已经设置过栈了,这里再次设置栈,是因为之前程序的执行都是在SRAM中,SRAM空间有限,不能过多的使用进行函数调用,否则会造成内存溢出,但是在这里DDR已经被初始化了,有大片的内存空间可用,因此要把栈移到DDR中,所以在这里要重新设置栈。

判断运行地址决定是否重定位

    /* when we already run in ram, we don't need to relocate U-Boot.
     * and actually, memory controller must be configured before U-Boot
     * is running in ram.
     */
    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   */

这里的判断方法和之前是一样的,用于决定是否进行relocate,uboot第第一部分即将结束之前,要把第二部分加载到DDR的链接地址上(0x33e00000),这个过程就是重定位,这里判断如果相等则跳过下面的代码直接运行after_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

0xD0037488的值是硬件自动设置的,硬件根据实际电路中SD卡在哪个通道中,会将它的值设置为相应数据。用于决定是从外部的SD卡启动还是从内部SD卡(iNand)启动。

接下来的代码判断是从哪个nand中进行启动,因为我们是从外部SD卡启动则会执行beq mmcsd_boot,调用mmcsd_boot函数。函数内容如下:

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

可以看到主要调用的是movi_bl2_copy这个函数,用于将第二部分拷贝到DDR中,该函数是一个C语言函数:

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));

    #if defined(CONFIG_SECURE_BOOT)
    ulong rv;
    #endif
#else
    ch = *(volatile u32 *)(0xD003A508);
    copy_sd_mmc_to_mem copy_bl2 =
        (copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008));
#endif
    u32 ret;
    if (ch == 0xEB000000) {
        ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);

#if defined(CONFIG_SECURE_BOOT)
        /* do security check */
        rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
                      (unsigned char *)CFG_PHY_UBOOT_BASE, (1024*512-128),
                          (unsigned char *)(CFG_PHY_UBOOT_BASE+(1024*512-128)), 128 );
        if (rv != 0){
                while(1);
            }
#endif
    }
    else if (ch == 0xEB200000) {
        ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);

#if defined(CONFIG_SECURE_BOOT)
        /* do security check */
        rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
                      (unsigned char *)CFG_PHY_UBOOT_BASE, (1024*512-128),
                          (unsigned char *)(CFG_PHY_UBOOT_BASE+(1024*512-128)), 128 );
        if (rv != 0) {
            while(1);
        }
#endif
    }
    else
        return;

    if (ret == 0)
        while (1)
            ;
    else
        return;
}

关键部分在于

copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);

这里的调用

  • 2:表示通道二
  • MOVI_BL2_POS:代表uboot第二部分在SD卡中的开始的扇区号,必须和uboot烧录时的烧录位置相同。
  • MOVI_BL2_BLKCNT:代表uboot占用的扇区数
  • CFG_PHY_UBOOT_BASE:代表重定位时将uboot的第二部分复制到DDR中的起始位置

虚拟地址映射

#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

虚拟地址和物理地址

我们常接触到的是物理地址,物理地址就是设备设计生产时赋予的地址,例如裸机程序中的寄存器的地址,物理地址在设计和生产时确定好的,一旦确定好就不能修改了。

虚拟地址是在软件的操作和被操作硬件之间,增加一个虚拟地址映射层,负责将虚拟地址映射为物理地址,映射层有一张虚拟地址到物理地址的转换表,软件开发的时候使用的地址都是虚拟地址,不需要对硬件的物理地址有任何的了解,这些对虚拟地址的操作,都会通过映射层映射为物理地址,从而操纵硬件工作。

MMU(Memory Management Unit)内存管理单元,是Soc中的一个内部外设,主要功能就是实现虚拟地址到物理地址的映射,操作MMU主要是通过CP15协处理器进行,将虚拟地址映射成物理地址,主要是操作对CP15的寄存器进行编程控制。

虚拟地址映射可以实现访问控制,方法是我们在管理上对内存进行分块,每个块进行独立的虚拟地址映射,在每个块的映射关系上添加读,写,是否可以访问等权限,这样就建立起来了基于内存块的访问控制机制。

虚拟地址映射还可以实现cache功能,cache主要是在CPU和DDR内存之间实现一个缓冲区域,平衡CPU和DDR的读写速度差异,根据cache的读取差别,有的CPU给Cahce分成L1,L2等不同的等级。

使能域访问

    /* enable domain access */
    ldr r5, =0x0000ffff
    mcr p15, 0, r5, c3, c0, 0       @load domain access register

mcr是用来写CP15协处理器的寄存器指令,c3寄存器就是CP15中处理域访问的寄存器,域访问用于管理MMU的访问控制权限。

TTB设置

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

TTB,转换表基地址,转换表就是虚拟地址映射表,转换表有表索引和表项,表索引对应虚拟地址,表项对应物理地址。转换表中的每个单元都由一对表索引和表项组成,对应一个内存块,若干个单元组成了一整个转换表,完成所有内存的转换。内存的映射和管理是以块为单位的,在ARM中支持三种块大小,分别为1KB,4KB,1MB。转换表位于内存空间中,转换表的起始地址要求对齐,一般是4字节或者8字节对齐。转换表使用时不需要软件干涉,只需要将基地址TTB赋值到CP15的c2寄存器中,MMU工作时自动会根据c2寄存器中的值找到转换表,自动查表完成地址转换。

开启MMU映射

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

将c1寄存器的bit0值设置为1,表示使能MMU单元。MMU开启之后,软件执行时操作的地址都会经过TTB的转换,得到物理地址之后再去执行。

TTB转换表

TTB转换表中其实是由很多个数字组成的数组,这些都是表项,索引就是这些数组的下标,具体有多少个数组,是由所使用的映射块大小模式所决定,uboot中使用段式映射,大小为1MB,32bit的CPU内存范围为4GB,所以数组的大小为4096。理论上要依次设置这4096个数组的值,但在实际上是将这4096个数组分成几部分,每部分使用循环做相同的处理:

mmu_table:
    .set __base,0
    // Access for iRAM
    .rept 0x100
    FL_SECTION_ENTRY __base,3,0,0,0
    .set __base,__base+1
    .endr

    // Not Allowed
    .rept 0x200 - 0x100
    .word 0x00000000
    .endr

    .set __base,0x200
    // should be accessed
    .rept 0x600 - 0x200
    FL_SECTION_ENTRY __base,3,0,1,1
    .set __base,__base+1
    .endr

    .rept 0x800 - 0x600
    .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

/*  .rept 0xc00 - 0xb00
    .word 0x00000000
    .endr */

    .set __base,0xB00
    .rept 0xc00 - 0xb00
    FL_SECTION_ENTRY __base,3,0,0,0
    .set __base,__base+1
    .endr

    // 0xC000_0000映射到0x2000_0000
    .set __base,0x300
    //.set __base,0x200
    // 256MB for SDRAM with cacheable
    .rept 0xD00 - 0xC00
    FL_SECTION_ENTRY __base,3,0,1,1
    .set __base,__base+1
    .endr

    // access is not allowed.
    @.rept 0xD00 - 0xC80
    @.word 0x00000000
    @.endr

    .set __base,0xD00
    // 1:1 mapping for debugging with non-cacheable
    .rept 0x1000 - 0xD00
    FL_SECTION_ENTRY __base,3,0,0,0
    .set __base,__base+1
    .endr   

最终映射出来的效果如下:

虚拟地址物理地址映射大小虚拟地址分布范围
00000000~1000000000000000~10000000256MB0~256MB
10000000~200000000256MB256MB~512MB
20000000~6000000020000000~600000001GB512MB~1.5GB
60000000~800000000512MB1.5GB~2GB
80000000~B000000080000000~B0000000768MB2GB~2.75GB
B0000000~C0000000B0000000~C0000000256MB2.75GB~3GB
C0000000~D000000030000000~40000000256MB3GB~3.25GB
D0000000~ENDD0000000~END768MB3.25GB~4GB

再来对比一下我们开发板上的DMC0和DMC1的有效地址范围:
DMC0:0x30000000~0x3FFFFFFF
DMC1:0x40000000~0x4FFFFFFF

可以看出,虚拟地址C0000000~D0000000映射到了0x30000000~0x3FFFFFFF,其他的虚拟地址和物理地址还是没动的。这也可以理解在uboot配置的时候为什么将链接地址设置为0xC3E00000,因为这个地址将会被映射到物理地址的0x33E00000上。

栈设置

    /* Set up the stack                         */
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
    ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
    ldr r0, _TEXT_BASE      /* upper 128 KiB: relocated uboot   */
    sub r0, r0, #CFG_MALLOC_LEN /* malloc area                      */
    sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#if defined(CONFIG_USE_IRQ)
    sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
    sub sp, r0, #12     /* leave 3 words for abort-stack    */

#endif

第一个宏满足,所以会执行ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000),CFG_UBOOT_BASE的值为0x33E00000,CFG_UBOOT_SIZE的值为2MB,再减去0x1000,最终值为2MB-200K-0x1000(4KB)左右,之所以再次设置栈,是为了将栈放在一个比较合理的位置上,这个位置比较安全,不会被随意覆写,位置紧凑并且不浪费内存。

清bss

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

    ldr pc, _start_armboot 

其中_bss_start和_bss_end的值来自于链接脚本。

开启uboot第二阶段

_start_armboot:
    .word start_armboot

start_armboot函数用于开启uboot第二阶段的运行,清bss的时候将这个函数的地址赋值到pc寄存器,实际上就是使用一个长跳转让程序从这个函数开始执行,从DDR中运行这个函数,ldr pc, _start_armboot加载的地址和当前的运行地址无关,使用的是链接地址,可以实现从SRAM跳转执行DDR中函数,也就实现了第二阶段的运行。

u-boot启动过程中,有以下详细步骤: 1. u-boot被加载到内存中并运行。 2. u-boot执行do_bootm_linux函数,该函数用于启动Linux内核。 3. u-boot根据参数信息设置启动参数,这些参数由uboot传递给内核,用于配置内核的运行环境。 4. u-boot执行boot命令来启动Linux内核,常用的boot命令有bootz、bootm和boot。 5. 在bootm命令中,u-boot会执行bootcmd参数中定义的一系列命令。这些命令可以用于执行一些预定义的操作,比如显示logo信息、从存储设备中读取内核映像到内存等。 6. 最后,u-boot会将内核映像加载到内存中,并启动内核。 总结起来,uboot启动过程的详细步骤包括加载u-boot到内存中、设置启动参数、执行boot命令启动Linux内核,并根据bootcmd参数执行一些额外的操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Uboot启动过程详解](https://blog.csdn.net/weixin_45566765/article/details/119082331)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [linux-uboot 移植三 uboot启动内核过程](https://blog.csdn.net/u010681589/article/details/125195077)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值