A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

1. 设置 CPU 为 SVC 模式

代码:141 ~ 149 行



  • 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


CPSR寄存器:

(1)msr cpsr_c, #0xd3 —— 将 CPU 设置为禁止 FIQ 、IRQ,ARM 状态,SVC 模式。

(2)其实 ARM CPU 在复位时默认是 SVC 模式,但是这里还是用软件的方式将其置位 SVC 模式,只是为了不管硬件的预设。

(3)整个 uboot 工作在 SVC 模式。

2. 设置 L1cache、L2cache 和 MMU

注1:CONFIG_EVT1 有定义,因此代码 168 ~ 198 行不执行。

注2:与 CPU 相关,不用细看,了解即可。

代码:200 ~ 204 行



  •         bl        disable_l2cache







  •         bl        set_l2cache_auxctrl_cycle







  •         bl        enable_l2cache


(1)bl disable_l2cache —— 禁止 L2cache

(2)bl set_l2cache_auxctrl_cycle —— 配置L2cache

(3)bl enable_l2cache —— 使能L2cache

代码:206 ~ 211 行



  •        /*



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


(1)刷新 L1 的 icache 和 dcache

代码:213 ~ 211 行



  •        /*



  •         * 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)关闭 MMU

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

3. 读取启动信息

代码:224 ~ 227  行



  •         /* Read booting information */



  •         ldr        r0, =PRO_ID_BASE                /* PRO_ID_BASE = 0xE0000000 */



  •         ldr        r1, [r0,#OMR_OFFSET]            /* OMR_OFFSET = 0X4 */



  •         bic        r2, r1, #0xffffffc1


(1)通过上面的代码可知:启动信息是通过读取地址为 0xE0000004 的寄存器获得的,而我们是通过配置 SoC 的 OM5 : OM0 这 6 个引脚来设置启动项的,因此可以知道 SoC 的 OM5 : OM0 这 6 个引脚的值会映射到地址为 0xE0000004 的寄存器中。

(2)地址为 0xE0000004 的寄存器在数据手册上查不到,表明三星没有给出这方面的资料,因此不去详细分析这个寄存器,我们只需要知道可以在代码中读取这个寄存器的值,来判断当前选中的启动介质是 SD 卡还是 iNand 或者其他的。

(3)经过上面几步,r2 中记录了一个数,这个数等于某个特定值时,就表示从哪种介质启动。

4. 设置启动方式

代码:242 ~ 278 行



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







  • #if 0        /* Android C110 BSP uses OneNAND booting! */



  •         /* For second device booting */



  •         /* OneNAND BOOTONG failed */



  •         cmp     r2, #0x8



  •         moveq   r3, #BOOT_SEC_DEV



  • #endif







  •         /* Uart BOOTONG failed */



  •         cmp     r2, #(0x1<<4)



  •         moveq   r3, #BOOT_SEC_DEV







  •         ldr        r0, =INF_REG_BASE



  •         str        r3, [r0, #INF_REG3_OFFSET]


(1)根据 r2 中值判断是从哪种启动介质启动,并将启动介质的信息放到 r3 中。

(2)然后存储到地址为 INF_REG_BASE + INF_REG3_OFFSET 的寄存器中。(这一步了解就可以)

5. 设置栈并调用 lowlevel_init

代码:280 ~ 288 行



  •         /*



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


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

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

(3)执行跳转指令 bl lowlevel_init 将跳转到 board/samsung/x210 目录下的 lowlevel_init.S 文件中 lowlevel_init 符号处。


接下来的代码是lowlevel_init.S的代码1. 检查复位状态

代码:44 ~ 52 行



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

2. IO状态复位

代码:54 ~ 59 行



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


(1)IO 状态复位与主线启动代码无关,所以无需去管。

3. 关看门狗

代码:61 ~ 64 行



  •         /* Disable Watchdog */



  •         ldr        r0, =ELFIN_WATCHDOG_BASE        /* 0xE2700000 */



  •         mov        r1, #0



  •         str        r1, [r0]


4. 与 SRAM 、SROM 相关的 GPIO 的设置

代码:66 ~ 97 行



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


(1)与主线启动代码无关,可以不用管

5. 供电锁存

代码:99 ~ 104 行



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


(1)开发板供电锁存

6. 判断当前代码运行时运行在 SRAM 中还是运行在 DDR 中

代码:106 ~ 115 行



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


(1)上面几行代码作用就是判定当前代码的执行位置是在 SRAM 中还是在 DDR 中。

为什么要做这个判定?

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

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

(2)bic r1, pc, r0  ——  将 pc 中的某些 bit 位清零,剩下一些特殊的 bit 位赋值给 r1(r0 中为 1 那些位清零),相当于:

r1 = pc & ~(0xFF000FFF)

(3)ldr r2, _TEXT_BASE  ;  bic    r2, r2, r0  ——  链接地址加载到 r2,然后将 r2 的相应位清零,留下特定位。

(4)最后比较 r1 和 r2

总结:这一段代码是通过读取当前的运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前是运行在 SRAM (不相等)中还是 DDR (相等)中,从而决定是否跳过下面的时钟和 DDR 的初始化。

7. 初始化时钟系统

代码:117 ~ 118 行



  •         /* init system clock */



  •         bl system_clock_init


(1)system_clock_init 函数的定义在本文件的 205 ~ 285 行。这里的时钟初始化过程和裸机中的初始化过程是一样的,只是更加完整,而且是用汇编代码编写的。

(2)在 x210_sd.h 中的 300 ~ 428 行都是和时钟相关的配置值。这些宏定义就决定了 210 的时钟配置是多少。如果移植时需要更改 CPU 的时钟配置,不需要懂代码,只需要在 x210_sd.h 中更改即可。

8. 内存初始化

代码:120 ~ 121 行



  •         /* Memory initialize */



  •         bl mem_ctrl_asm_init


(1)mem_ctrl_asm_init  ——  该函数是用来初始化 DDR 的,它位于 cpu/s5pc11x/s5pc110/cpu_init.S 文件中。该函数与裸机中初始化 DDR 的代码是一样的。实际上裸机中初始化 DDR 的代码就是从这里移植过去的。

9. 初始化串口

代码:124 ~ 125 行



  •         /* for UART */



  •         bl uart_asm_init


(1)uart_asm_init 函数位于本文件的 392 ~ 432 行,该函数主要做的工作有:初始化串口和通过串口发送一个 'O' 。

10. 可信任区域初始化

代码:127 行

        bl tzpc_init

(1)tzpc_init  ——  该函数位于本文件的 494 ~ 520 行,主要工作是初始化可信任区域。(目前用不到,不用管)

11. 返回

代码:152 ~ 156 行



  •         /* Print 'K' */



  •         ldr        r0, =ELFIN_UART_CONSOLE_BASE



  •         ldr        r1, =0x4b4b4b4b



  •         str        r1, [r0, #UTXH_OFFSET]







  •         pop        {pc}


(1)在返回前通过串口打印 ‘K’

15. 总结

lowlevel_init.S做的工作包括:检查复位状态、IO 复位、关看门狗、开发板供电锁存、时钟初始化、DDR 初始化、串口初始化并打印 'O'、tpzc 初始化、打印 'K'

需要关注的包括:关看门狗、开发板供电锁存、时钟初始化、DDR 初始化、打印 "OK"

上面的 lowlevel_init.S 已经分析完了,从这里开始重新进入到 start.S。
6. 再次开发板供电锁存

代码:292 ~ 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]


(1)再一次设置开发板供电锁存没有意义,但是没有错

7. 第二次设置栈

代码:297 ~ 299 行



  •         ldr        sp, _TEXT_PHY_BASE        /* setup temp stack pointer */



  •         sub        sp, sp, #12



  •         mov        fp, #0                        /* no previous frame, so fp=0 */


(1)第一次设置栈是在调用 lowlevel_init 之前(284 ~ 286 行),那时 DDR 尚未初始化,程序是在 SRAM 中运行的,所以在 SRAM 中分配了一部分内存作为栈,这一次因为 DDR 已经初始化了(在 lowlevel_init.S 中调用的 mem_ctrl_asm_init 函数,这个函数是用来初始化 DDR 的,它的定义放在 uboot/cpu/s5pc11x/s5pc110/cpu_init.S 文件中),因此要把栈移到 DDR 中,所以要重新设置栈,这是第二次设置栈。

(2)_TEXT_PHY_BASE的值为 0x33E00000,这里的栈的地址刚好在uboot下面紧挨着。

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

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

代码:305 ~ 310 行



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


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

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

9. uboot 重定位

代码: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,



  •         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 通道启动时,这个数字为 0xEB000000;当从 SD2 通道启动时,这个数字为 0xEB200000。

(2)在代码 260 行确定了从 MMCSD 启动,然后又在代码 278 行将 #BOOT_MMCSD 写入了 INF_REG3寄存器中存储着,然后又在代码 322 行将其读出来,再和 #BOOT_MMCSD 比较,确定从 MMCSD 启动,最终跳转到 mmcsd_boot 处去执行重定位动作。

(3)在 mmcsd_boot 处调用 movi_bl2_copy 函数(在 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)                                                                                                                                        /* 有CONFIG_EVT1宏定义,因此执行if后面的部分 */



  •         ch = *(volatile u32 *)(0xD0037488);                                                                                                                /* 地址为0xD0037488的值是被硬件自动设置的,表示从哪个SD卡通道启动 */



  •         copy_sd_mmc_to_mem copy_bl2 =                       



  •             (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));                                                                                /* copy函数 */







  •         #if defined(CONFIG_SECURE_BOOT)                                                                                                                        /* 无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) {                                                                                                                                        /* 从sd0通道启动 */



  •                 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) {                                                                                                                        /* 从sd2通道启动 */



  •                 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 中封装好的从 SDMMC 复制数据到 MEM 函数完成 uboot 从 SDMMC 中到 DDR 中的迁移,如下所示:

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 中的起始地址(0x33E00000)

10. 虚拟地址映射

代码:357 ~ 382 行



  • 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







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


cp15 协处理器内部有 c0 ~ c15 共 16 个寄存器,这些寄存器每一个都有自己的作用,可以通过 mrc、mcr 指令来访问这些寄存器,所谓的操作 cp 协处理器其实就是操作 cp15 的这些寄存器。

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

c3 寄存器在 MMU 中的作用就是控制域访问,域访问是和 MMU 的访问控制有关的

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

TTB 就是 translation table base,转换表基地址。TT 是 translation table,转换表。

转换表是建立一条虚拟地址映射的关键。转换表分为两部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一堆表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。(映射的基本规定中规定了内存映射和管理是以块为单位的,至于块有多大,要看 MMU 的支持和个人的选择,在 ARM 中支持 3 中块大小:细表 1KB、粗表 4KB、段 1 MB)。真正的转换表就是由若干个转换单元构成的,每个单元负责 1 个内存块,总体的转换表负责整体内存空间(0 ~ 4G)的映射。

整个建立虚拟地址映射的主要工作就是建立这张转换表。

转换表放置在内存中,放置时要求起始地址在内存中要 xxx 位对齐,转换表不需要软件去干涉使用,而是将基地址 TTB 设置到 cp15 的 c2 寄存器中,然后 MMU 工作时会自动去查转换表。

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

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

(4)地址转换表

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



  • 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

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

思考:为什么配置时将链接地址设置为 0xC3E00000?因为这个地址将来会被映射到 0x33E00000 这个物理地址。

11. 第三次设置栈

代码:384 ~ 398 行



  • skip_hw_init:



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


(1)这次设置栈还是在 DDR 中,之前虽然已经在 DDR 中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适的地方(安全、紧凑而不浪费内存)。

(2)我们实际将栈设置在从 uboot 的起始地址开始算起向上 2MB - 4KB 处,这样安全的栈空间是 200KB ~ 2MB - 4KB,这个空间既没有浪费内存,又足够安全。

12. 清理 bss

代码:400 ~ 409 行



  • 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


(1)这里的清理 bss 的代码和裸机中的代码是一样的。需要注意的是 bss 段的开头和结尾地址的符号是从链接脚本 uboo.lds 得来的。

13. 跳转到第二阶段

代码:411 行

        ldr        pc, _start_armboot

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

(2)远跳转的含义就是这里加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从 SRAM 中第一阶段跳转到 DDR 中的第二阶段。

(3)这里的这个跳转就是 uboot 的第一阶段和第二阶段的分界线。

14. 总结

uboot的第一阶段做的工作:

(1)构建异常向量表

(2)设置 CPU 为 SVC 模式

(3)关看门狗

(4)开发板供电置锁

(5)时钟初始化

(6)DDR 初始化

(7)串口初始化并打印 "OK"

(8)重定位

(9)建立映射表并开启 MMU

(10)跳转到第二阶段


1 个回复

倒序浏览

很不错,受教了
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马