uboot学习之三-----uboot启动第一阶段--start.S之二

start.s启动第一阶段

----------------------------------------------------------------------------------------------------------------------------------------------------------
①设置CPU运行SVC模式,禁止IRQ和FIQ

②设置L2 L1 cache 和MMU

③读取启动信息

④读取启动方式

⑤第一次设置栈,因为要调用lowlevel_init.S,其内部需要再次调用函数
----------lowlevel_init.S----------
一:检查复位状态

二:IO状态恢复,关看门狗,SRAM和SROM相关gpio设置,供电锁存

三:来判定当前运行在SRAM(不相等)中还是DDR(相等)中,然后来决定是否跳过是时钟初始化部分和DDR 初始化

四:system_clock_init 初始化系统时钟

五:mem_ctrl_asm_init 初始化内存

六:uart_asm_init这个函数用来初始化,初始化完之后通过串口发送一个大写的O,这里是为了做阶段性的调试

七:tzpc_inittrustzone,可信任区域的初始化

八:    pop    {pc}返回前通过串口打印"K"
-----------返回start.S-----------
⑥第二次设置栈(DDR中的栈)
⑦再次判断当前地址以决定是否重定位
⑧虚拟地址映射
⑨第三次设置栈
⑩清bss
⑪ldr    pc, _start_armboot

----------------------------------------------------------------------------------------------------------------------------------------------------

start.S中真正的复位代码:

①设置CPU运行SVC模式,禁止IRQ和FIQ

/*
 * the actual reset code
 */

reset:
    /*
     * set the cpu to SVC32 mode and IRQ & FIQ disable
     */

    msr    cpsr_c, #0xd3        @ I & F disable, Mode: 0x13 - SVC

uboot学习之三-----uboot启动第一阶段--start.S之二

  msr    cpsr_c, #0xd3  代表:将CPU设置为禁止IRQ,FIQ, ARM状态,SVC模式。

 其实我们arm CPU默认的就是SVC模式的,但是这里还是用软件将其置为SVC模式,整个uboot也一直工作在SVC模式。用软件来设置只是为了不管硬件的预设。

②设置L2 L1 cache 和MMU(与CPU有关,不用去细看,了解即可,抓大放小)

  一:L2 cache

(1)bl    disable_l2cache  //禁止L2 cache

(2)bl    set_l2cache_auxctrl_cycle  //也是L2 cache的设置

(3)bl    enable_l2cache    //使能L2 cache

  二:刷新L1的icache和dcache

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

  三:关闭MMU

    为什么要关闭MMU呢?因为一开始还没有做虚拟地址映射,现在还不能操作相关的地址,等我们后面需要的时候再打开。

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

 

  ③读取启动信息

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

查询数据手册,并未查到0XE0000004,三星没有给出这方面的资料。

(1)根据这个代码表示,这个寄存器存了一个很重要的信息,就是OMPin的信息,从哪里启动是由OM5:0M0这六个引脚的高低电平决定的。实际上在210内部有一个寄存器这个寄存器的地址是0XE00000004,这个寄存器的值是硬件根据OM引脚的设置而自动设置的,这个值反映的就是OM引脚的接法,也就是真正的启动介质是啥,

(2)我们代码中可以读取这个寄存器的值,来确定我们当前选中的启动介质是nand  SD  还是inand  或者其他的。但是这个寄存器是被限制的,我们拿不到很底层的内容,只有三星的工程师才有。

(3)start.S经过上面几步,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    

例如从SD卡启动: moveq r3, #BOOT_MMCSD   这个在SD启动时,会被执行,因此执行完这一段代码后r3中存储了0xc,以后备用

⑤第一次设置栈,因为要调用lowlevel_init.S,其内部需要再次调用函数

/*
     * 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
    
    bl    lowlevel_init    /* go setup pll,mux,memory */
    /* To hold max8698 output before releasing power on switch,
     * set PS_HOLD signal to high
     */
    ldr    r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
    ldr    r1, =0x00005301     /* PS_HOLD output high    */
    str    r1, [r0]

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

 (1)这里是第一次设置栈,这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未初始化还不能用,这是只有内部SRAM可以用。这个栈的地址0xd0036000,指定的原则是这块代码只能给栈用,不会被别人占用。

(2)我们在调用函数前初始化栈,主要原因是在被调用函数的内部还要再次调用函数,而bl只会将返回地址存储到lr中,但是我们只有一个lr,所以在第二层调用函数前要先将lr入栈,否则函数返回时,第一层地址就丢了。

接下来部分的代码都是lowevel_init.S

使用sourceinsight的搜索功能找到lowlevel_init函数真正的地方在/uboot/board/samsung/x210/lowevel_init.S中,

                                                                         

    .globl lowlevel_init
lowlevel_init:
    push    {lr}

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

 

 一:检查复位状态

(1)复杂CPU允许多种复位情况,譬如直接冷上电,热启动,睡眠状态下的唤醒,这些情况都属于复位,所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。

(2)判断哪种复位的意义在于:冷上电时,DDR是需要初始化,而热启动和睡眠状态的唤醒时不需要初始化DDR的。

 

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

二:IO状态恢复

IO状态复位和检查复位状态和主线启动代码无关,

 

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

关看门狗

 

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


    /* CS0 - 16bit sram, enable nBE, Byte base address */
    ldr    r0, =ELFIN_SROM_BASE    /* 0xE8000000 */
    mov    r1, #0x1
    str    r1, [r0]

 

一些SRAM和SROM相关的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]

供电锁存的代码。

 

三:来判定当前运行在SRAM(不相等)中还是DDR(相等)中,然后来决定是否跳过是时钟初始化部分和DDR 初始化

    /* when we already run in ram, we dont 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   */

    /* init system clock */
    bl system_clock_init

    /* Memory initialize */
    bl mem_ctrl_asm_init
    
1:
    /* for UART */
    bl uart_asm_init

(1) 判断当前代码执行的位置,110-115行,判断当前的代码是否需要重定位,判断当前代码是在SRAM中还是在DDR中,

为什么需要这个判断?

    原因1:BL1(uboot的前一部分)是在SRAM中有一份,在DDR中也有一份,因此如果是冷启动,当前的代码应该是在SRAM中运行的BL1,如果是低功耗状态下的复位这时候 应该就是在DDR中运行的。

    原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在lowlevel_init.S中,判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和DDR初始化的代码。如果当前代码在SRAM中,说明是冷启动,那么时钟和DDR初始化都要执行,如果是DDR中运行,说明是热启动,就不需要执行时钟和DDR的初始化。

(2)bic r1, pc, r0   /* r0 <- current base addr of code */

将pc的值中的某些比特位清零,剩下一些特殊的比特位赋值给r1(r0中为1的那些位清零)相当于 r1 = pc & ~(ff000fff)

(3) ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */  链接地址加载到r2,然后将r2的相应位清零,剩下特定位。

(4)最后比较r1和r2

总结:读取当前运行地址和连接地址,然后处理两个地址后对比是否相等,来判定当前运行在SRAM(不相等)中还是DDR(相等)中,然后来决定是否跳过是时钟初始化部分和DDR 初始化

 

四:system_clock_init 初始化系统时钟

 搜索到在当前代码的第205行,一直到第385行。这个初始化时钟比较完整,比较详细,在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置值是多少。如果移植时需要更改CPU的时钟设置,根本不需要动代码,只是需要在x210_sd.h中去更改宏定义就好了。(待完善,时钟部分详细的分析)

 

五:mem_ctrl_asm_init 初始化内存

这个函数就是用来初始化DDR动态内存的,函数的位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S

总结:这个函数和裸机中的DDR初始化代码和这里一样。配置值是在x210_sd.h中,配置了。

配置值中其他配置值参考裸机中的解释。

有一个不一样, DMC0_MEMCONFIG_0

裸机中配置为0X20E01323;在uboot中配置为0x30F01313;关键是前面是20还是30;这个配置不同导致结果不同,在裸机中,DMC0的256MB内存地址范围是0X20000000-0X2FFFFFFF,而在uboot中内存地址范围为0X30000000-0X3FFFFFFF;

(2)之前在裸机中我们配置为2开头的地址,当时并没有讲3开头的地址,从分析九鼎移植的uboot可以看出:DMC0允许的地址范围为0X20000000-0X3FFFFFFF(一共是512MB),而我们实际直接了256MB物理内存,SOC允许我们给这256MB挑选地址范围。

总结:在uboot中,可用的物理地址范围为:0X30000000-0X4FFFFFFF;一共512MB,其中0X30000000-0X3FFFFFFF为DMC0,0X40000000-0X4FFFFFFF为DMC1

(3)我们需要的内存配置值在x210_sd.h的438行到468行之间,注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值。这样的主要目的是让不同时钟需求的客户都能找到合适自己的内存的配置值。

(4)uboot中DMC0和DMC1都工作了,所以在裸机中只要把uboot配置值和配置代码全部移植过去,应该是能够让DMC0和DMC1都工作的。

六:uart_asm_init

这个函数用来初始化,初始化完之后通过串口发送一个大写的O,这里是为了做阶段性的调试,lowlevel_init.S中的392行到432行,

uart_asm_init:

    /* set GPIO(GPA) to enable UART */
    @ GPIO setting for UART
    ldr    r0, =ELFIN_GPIO_BASE
    ldr    r1, =0x22222222
    str       r1, [r0, #GPA0CON_OFFSET]

    ldr     r1, =0x2222
    str     r1, [r0, #GPA1CON_OFFSET]

    // HP V210 use. SMDK not use.
#if defined(CONFIG_VOGUES)
    ldr    r1, =0x100
    str    r1, [r0, #GPC0CON_OFFSET]

    ldr    r1, =0x4
    str    r1, [r0, #GPC0DAT_OFFSET]
#endif

    ldr    r0, =ELFIN_UART_CONSOLE_BASE        @0xEC000000
    mov    r1, #0x0
    str    r1, [r0, #UFCON_OFFSET]
    str    r1, [r0, #UMCON_OFFSET]

    mov    r1, #0x3
    str    r1, [r0, #ULCON_OFFSET]

    ldr    r1, =0x3c5
    str    r1, [r0, #UCON_OFFSET]

    ldr    r1, =UART_UBRDIV_VAL
    str    r1, [r0, #UBRDIV_OFFSET]

    ldr    r1, =UART_UDIVSLOT_VAL
    str    r1, [r0, #UDIVSLOT_OFFSET]

    ldr    r1, =0x4f4f4f4f
    str    r1, [r0, #UTXH_OFFSET]        @'O'

    mov    pc, lr

 

七:tzpc_init

trustzone,可信任区域的初始化,可以搜索什么叫trustzone,ARM在2003年推出了trustzone;

 

八:    pop    {pc}

返回前通过串口打印"K"

如果lowlevel_init.S执行完了如果没错那么就会打印OK字样,这应该是uboot中输出的最早的字样,

 

 

大总结:lowlevel_init.S总共做了哪些事情?

lowlevel_init.S做了哪些事情:

检查复位状态,IO恢复,关看门狗,开发板供电锁存,判断代码运行地址,时钟初始化,DDR初始化, 串口初始化并打印O,tzpc初始化,打印'K'的字样,

这里面值得关注的:关看门狗,开发板供电锁存,判断代码运行地址,时钟初始化,DDR初始化,打印'K'的字样。

从这里回到start.S

 

⑥第二次设置栈(DDR中的栈)

 (1)再一次开发板供电锁存(start.S中第289-294)

    /* To hold max8698 output before releasing power on switch,
     * set PS_HOLD signal to high
     */
    ldr    r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
    ldr    r1, =0x00005301     /* PS_HOLD output high    */
    str    r1, [r0]

这一次设置供电锁存无意义。但是没错。

(2)第二次设置栈; 第一次设置栈的时候是在上面第⑤(284-287行)点,那时候DDR尚未初始化,程序是在SRAM中运行的,所以在SRAM中分配了一部分内存作为栈,这一次因为DDR已经初始化了(在lowlevel_init分析的:  五:mem_ctrl_asm_init 初始化内存这个函数就是用来初始化DDR动态内存的,函数的位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S),因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次设置栈了(在start.S中296-299行)

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

 

_TEXT_PHY_BASE 实际值 0x33e00000,这里栈的地址是刚好在uboot的下面紧挨着,

uboot学习之三-----uboot启动第一阶段--start.S之二

 

(3)为什么要第二次设置栈?因为DDR已经初始化了,已经有一大片的内存已经初始化了,没必要再把栈放在SRAM中,SRAM中的栈比较小,内存空间有限,栈放在这里,不能使用过多的栈,否则会溢出,SRAM外部什么都没有,溢出后果不堪设想,所以将栈迁移到DDR来,为了避免使用栈的时候会溢出。

⑦再次判断当前地址以决定是否重定位

在start.S中的301-310行

    /* when we already run in ram, we dont 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   */

 

(1)再次用相同的代码判断运行地址是在SRAM中还是在DDR中,不过本次判断的目的不同(上次判断是为了决定是否要初始化时钟和DDR,也就是上面lowlevel_init.S的第三点)这次判断是为了决定uboot是否需要重定位

(2)冷启动时,uboot的前一部分是16kb或者8kb,开机自动从SD卡加载到SRAM中,正在运行,uboot第二部分(整个uboot)还在SD卡中的某个扇区开头的N个扇区中,本次第一阶段已经差不多结束了,uboot第一阶段的事基本做完了,结束之前,要把第二部分加载到DDR中链接地址处也就是0x33e00000,整个加载的过程就叫---重定位。

 

如果需要重定位怎么重定位?

 start.S第312-354行:

#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

 

(1)0xD0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的,硬件根据我们实际电路中,SD卡在哪个通道中会将这个地址中的值设置为相应的数字,譬如我们从sd0启动时,这个值为EB000000,如果是从sd2启动时,这个值为EB200000;如果相等就是从sd2来启动的;

(2)我们在start.S的第260行确定了从MMCSD启动(也是上面start.S分析中的第④点),将其放在r3里,然后又在278行将#BOOT_MMCSD写入了INF_REG3寄存器中存储着,然后又在第322将其读出来,再和#BOOT_MMCSD比较,确定是从MMCSD启动,最终跳转到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这个函数,这个函数是一个C语言的函数,在uboot/cpu/s5pc11x/Movi.c中

typedef u32(*copy_sd_mmc_to_mem)
(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);


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

这个函数上来一开始是读出0xD0037488里的值,这里面存的是选择通道(irom application note),

然后将copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));这里将sdmmc拷贝到ddr中去,调用了封装好的函数,然后后面又判断ch == 0xEB200000时, ret = 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的起始地址(33E00000)

 

uboot学习之三-----uboot启动第一阶段--start.S之二

uboot学习之三-----uboot启动第一阶段--start.S之二

 

⑧虚拟地址映射

在上面movi_bl2_copy重定位完了以后,跳转到after_copy,这里后面就是虚拟地址映射部分

after_copy:

#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
    /* enable domain access */
    ldr    r5, =0x0000ffff
    mcr    p15, 0, r5, c3, c0, 0        @load domain access register

(1)使能域访问(cp15的c3寄存器)

cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用,可以通过mcr和mrc来访问,c3寄存器在MMU中的作用是控制域访问,

(2)设置TTB(cp15的c2寄存器)

/* 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就是translation table base,就是转换表基地址,转换表是虚拟地址对应到物理地址的那张表,转换表分两部分,表索引和表项,表索引对应虚拟地址,表项对应物理地址;一对表索引和表项组成一个转换表单元,能够对一个内存块进行虚拟地址转换(映射中基本规定中规定了内存映射和管理单元是以块为单位的,至于有多大,要看mmu的支持和用户自己的选择,在arm中,支持3种块大小,分别是细表1KB 粗表4KB  段1MB)。真正的转换表就是由若干个转换表的单元构成的,每个单元负责一个内存块,最后总体的转换表负责整个内存空间的映射,0-4G的范围。整个建立虚拟地址映射的主要工作就是建立这张转换表,转换表放置在内存中,放置时要求起始地址在内存中要xx位对齐,转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时,会自动去查表。

(3)使能MMU单元(cp15的c1寄存器)

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

cp15的c1寄存器的bit0控制MMU开关,只要将这一个bit置1即可开启MMU。开启MMU之后,上层软件层的地址就必须经过TTB的一个转换,才能发送到下层的物理层去

(4)转换表分析

通过符号查找到,确定转换表放在lowlevel_init.S的593行。

 宏观理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个表索引和表项的单元,数组中的元素值就是表项,数组的下标就是表索引

ARM的段式映射中长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G的范围需要 4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候,并没有依次单个处理这4096个单元,而是把4096个分成了几部分,然后每部分用for循环做相同的处理

VA                    PA                    length
0-10000000            0-10000000            256MB
10000000-20000000    0                    256MB
20000000-60000000    20000000-60000000    1GB        512-1.5G
60000000-80000000    0                    512MB    1.5G-2G
80000000-b0000000    80000000-b0000000    768MB    2G-2.75G
b0000000-c0000000    b0000000-c0000000    256MB    2.75G-3G
c0000000-d0000000    30000000-40000000    256MB    3G-3.25G
d-完                d-完                768MB    3.25G-4G



DRAM有效范围:
DMC0:     0x30000000-0x3FFFFFFF
DMC1:    0x40000000-0x4FFFFFFF

结论:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。

思考:为什么配置时将链接地址设置为c3e00000,因为这个地址将来会被映射到33e00000这个物理地址。

⑨第三次设置栈

这一次设置栈是在DDR中,之前虽然已经在DDR中设置过一次栈,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方,我们实际将栈设置在uboot起始地址上方2MB处,这样我们安全的栈空间-uboot大小-0x10000 ,大概1.8MB左右,这个空间既没有浪费内存,又很安全,

⑩清bss

清理bss段代码,注意bss段开头和结尾地址的符号,是从链接脚本,uboot.lds得来的。

⑪ldr    pc, _start_armboot
start_armboot这个函数在uboot/lib_arm/board.c中,这是一个C语言的函数 ,这个函数就是uboot的第二阶段,这句代码就是将uboot第二阶段的函数地址传给pc,实际上就是使用了远跳转,直接跳转到DDR中的第二阶段开始地址处。

远跳转的意思是:这段代码加载的地址和当前的运行地址无关,而和链接地址有关,因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR的第二阶段。这个跳转就是第一阶段和第二阶段的分界线