代码: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 模式。
注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 呢?因为一开始还没有做虚拟地址映射,还不能操作相关的地址,等后面需要的时候再打开。
代码: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 中记录了一个数,这个数等于某个特定值时,就表示从哪种介质启动。
代码: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 的寄存器中。(这一步了解就可以)
代码: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 符号处。
代码: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。
代码: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 状态复位与主线启动代码无关,所以无需去管。
代码:61 ~ 64 行
/* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]
代码: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)与主线启动代码无关,可以不用管
代码: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)开发板供电锁存
代码: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 的初始化。
代码: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 中更改即可。
代码:120 ~ 121 行
/* Memory initialize */
bl mem_ctrl_asm_init
(1)mem_ctrl_asm_init —— 该函数是用来初始化 DDR 的,它位于 cpu/s5pc11x/s5pc110/cpu_init.S 文件中。该函数与裸机中初始化 DDR 的代码是一样的。实际上裸机中初始化 DDR 的代码就是从这里移植过去的。
代码:124 ~ 125 行
/* for UART */
bl uart_asm_init
(1)uart_asm_init 函数位于本文件的 392 ~ 432 行,该函数主要做的工作有:初始化串口和通过串口发送一个 'O' 。
代码:127 行
bl tzpc_init
(1)tzpc_init —— 该函数位于本文件的 494 ~ 520 行,主要工作是初始化可信任区域。(目前用不到,不用管)
代码:152 ~ 156 行
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
pop {pc}
(1)在返回前通过串口打印 ‘K’
lowlevel_init.S做的工作包括:检查复位状态、IO 复位、关看门狗、开发板供电锁存、时钟初始化、DDR 初始化、串口初始化并打印 'O'、tpzc 初始化、打印 'K'
需要关注的包括:关看门狗、开发板供电锁存、时钟初始化、DDR 初始化、打印 "OK"
代码: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)再一次设置开发板供电锁存没有意义,但是没有错
代码: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 上来,为了避免使用栈的时候溢出。
代码: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),整个加载的过程就叫重定位。
代码: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 通道启动时,这个数字为 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)
代码: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 这个物理地址。
代码: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,这个空间既没有浪费内存,又足够安全。
代码: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 得来的。
代码:411 行
ldr pc, _start_armboot
(1)start_armboot 是 lib_arm/board.c 文件中的一个函数,这是一个 C 语言函数,这个函数就是 uboot 的第二阶段。这句代码的作用就是将 uboot 第二阶段执行的函数的地址传给 pc,实际上就是使用了一个远跳转直接跳转到 DDR 中的第二个阶段开始地址处。
(2)远跳转的含义就是这里加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从 SRAM 中第一阶段跳转到 DDR 中的第二阶段。
(3)这里的这个跳转就是 uboot 的第一阶段和第二阶段的分界线。
uboot的第一阶段做的工作:
(1)构建异常向量表
(2)设置 CPU 为 SVC 模式
(3)关看门狗
(4)开发板供电置锁
(5)时钟初始化
(6)DDR 初始化
(7)串口初始化并打印 "OK"
(8)重定位
(9)建立映射表并开启 MMU
(10)跳转到第二阶段