uboot启动第一阶段——start.S(二)

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, #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)

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 - 16KB 处,这样安全的栈空间是 2MB - 16KB - 200KB,这个空间既没有浪费内存,又足够安全。

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)跳转到第二阶段

猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/82385321