嵌入式Linux -- uboot启动之第一阶段分析总结

​ 在uboot启动阶段开始之前,先理清uboot整体启动过程原理,uboot大小假定为200KB。启动过程首先是开机上电后,板载的BL0开始运行,BL0会判断外部接入的启动设备(Flash)的类型并加载外部启动设备中的uboot的前16KB(BL1)到SRAM中去运行,BL1运行时会初始化DDR,初始化时钟等等一系列操作,最后把整个uboot读取到DDR中,然后用一句长跳转(从SRAM跳转到DDR)指令从SRAM中直接跳转到DDR中继续执行uboot直到uboot完全启动,uboot启动后在uboot命令行中去启动OS,结束uboot运行。

目录

      • starts.S解析
        • 1.1、u-boot.lds中的开始入口_start
        • 2.1、包含头文件
        • 3.1、启动代码的16字节头部
        • 4.1、构建异常向量表
        • 5.1、TEXT_BASE、CFG_PHY_UBOOT_BASE
        • 6.1、对CPU进行相应初始化1
          • 6.1.1、解析disable_l2cache
          • 6.1.2、解析set_l2cache_auxctrl_cycle
          • 6.1.3、解析enable_l2cache
        • 6.2、对CPU进行相应初始化2
          • 6.2.1、对L1 cache进行操作
          • 6.2.2、关闭MMU
        • 7.1、识别启动介质
        • 8.1、第一次设置栈(SRAM中的栈)
        • 9.1、解析lowlevel_init.S
          • 9.1.1、推栈并检查复位状态
          • 9.1.2、IO状态恢复
          • 9.1.3、关看门狗并且对外部SRAM SROM相关GPIO进行设置
          • 9.1.4、供电锁存
          • 9.1.5、判断是否需要重定位
          • 9.1.6、system_clock_init、mem_ctrl_asm_init
          • 9.1.7、uart_asm_init、tzpc_init
          • 9.1.8、检查复位状态、关闭ABB并打印K
        • 10.1、在start.S中再次将开发板置锁
        • 11.1、第二次设置栈
        • 12.1、再次进行是否需要重定位的判断
          • 12.1.1、movi_bl2_copy函数解析
          • 12.1.2、after_copy解析
        • 13.1、第三次设置栈
        • 14.1、清理.bss段
        • 15.1、结束第一阶段

starts.S解析

1.1、u-boot.lds中的开始入口_start

​ uboot的链接脚本中开始几行代码先选择了输出字体,CPU架构类型,然后用ENTRY(_ start)进入start.S中开始运行。

2.1、包含头文件

#include 
#include 
#if defined(CONFIG_ENABLE_MMU)
#include 
#endif
#include 

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

​ 分别包含了uboot/include文件夹中config.h、version.h、asm/proc/domain.h、regs.h,下面的条件没有满足,所以不进行下面的宏定义。

​ 对于include文件夹下的config.h,它是配置过程中产生的文件(详情看此文章第六部分第七节:https://blog.csdn.net/jn_statham/article/details/106440133),内容是一个宏定义,我使用的配置生成的宏定义的内容为#include

​ 对于include文件夹下的version.h,里面的内容为

#ifndef __VERSION_H__
#define __VERSION_H__

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

#endif

​ 包含了version_autogenerated.h,而此头文件是配置编译时自动生成的uboot版本信息,对应原理在uboot的主Makefile中开始部分。

​ 对于include文件夹下asm/proc/domain.h,实际asm和proc是在mkconfig中条件编译生成的两个指向相应文件夹的链接文件,本次使用的配置实际文件地址为uboot/include/asm-arm/proc-armv/domain.h,用来配置相应的域。

​ 对于include文件夹下的regs.h,此文件夹是在mkconfig中条件编译生成的一个链接文件,我们配置的这个链接文件指向的是&6.h,也就是include文件夹下的s5pc110.h,对应的是一堆寄存器的宏定义。

3.1、启动代码的16字节头部

​ 本开发板启动时需要16字节的校验头,在start.S中使用下方代码进行填充

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

4.1、构建异常向量表

.globl _start
_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
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

​ 首先跳转到reset将IRQ和FIQ关掉,设置SVC栈,然后进行异常向量表的搭建。下面创建了一个字符对齐的内容,填充deadbeef进行十六字节对齐

_end_vect:
	.balignl 16,0xdeadbeef

5.1、TEXT_BASE、CFG_PHY_UBOOT_BASE

_TEXT_BASE:
	.word	TEXT_BASE

​ 这是Makefile中配置时设置的一个数,我们使用的配置是sd_x210_config,也就是在这个配置中设置的,对应的TEXT_BASE = 0xc3e00000。

_TEXT_PHY_BASE:
	.word	CFG_PHY_UBOOT_BASE

​ 这是uboot在DDR中的物理地址,它的值对应为CFG_PHY_UBOOT_BASE = 33e00000

6.1、对CPU进行相应初始化1

cpu_init_crit:

#ifndef CONFIG_EVT1
#if 0	
	bl	v7_flush_dcache_all
#else
	bl	disable_l2cache

	mov	r0, #0x0	@ 
	mov	r1, #0x0	@ i	
	mov	r3, #0x0
	mov	r4, #0x0
lp1:
	mov	r2, #0x0	@ j
lp2:	
	mov	r3, r1, LSL #29		@ r3 = r1(i) <<29
	mov	r4, r2, LSL #6		@ r4 = r2(j) <<6
	orr	r4, r4, #0x2		@ r3 = (i<<29)|(j<<6)|(1<<1)
	orr	r3, r3, r4
	mov	r0, r3			@ r0 = r3
	bl	CoInvalidateDCacheIndex
	add	r2, #0x1		@ r2(j)++
	cmp	r2, #1024		@ r2 < 1024
	bne	lp2			@ jump to lp2
	add	r1, #0x1		@ r1(i)++
	cmp	r1, #8			@ r1(i) < 8
	bne	lp1			@ jump to lp1

	bl	set_l2cache_auxctrl
	bl	enable_l2cache
#endif
#endif
	
	bl	disable_l2cache
	bl	set_l2cache_auxctrl_cycle
	bl	enable_l2cache

​ 因为我们定义了CONFIG_EVT1的值,所以这个条件不成立,执行后面的代码bl disable_l2cache、
bl set_l2cache_auxctrl_cycle和bl enable_l2cache,下面对disable_l2cache、set_l2cache_auxctrl_cycle以及enable_l2cache进行相应解析。

6.1.1、解析disable_l2cache

​ 它在start.S中,内容如下

.global disable_l2cache
disable_l2cache:
	mrc     p15, 0, r0, c1, c0, 1
	bic     r0, r0, #(1<<1)
	mcr     p15, 0, r0, c1, c0, 1
	mov	pc, lr

​ 对应操作协处理器CP15中的C1寄存器,用来禁止L2 cache。

6.1.2、解析set_l2cache_auxctrl_cycle

​ 它在start.S中,内容如下

       .align  5
.global set_l2cache_auxctrl_cycle
set_l2cache_auxctrl_cycle:
	mrc 	p15, 1, r0, c9, c0, 2
	bic 	r0, r0, #(0x1<<29)
	bic 	r0, r0, #(0x1<<21)
	bic 	r0, r0, #(0x7<<6)
	bic 	r0, r0, #(0x7<<0)
	mcr 	p15, 1, r0, c9, c0, 2
	mov     pc,lr

​ 在协处理器CP15的C9寄存器中进行操作,用来完成L2 cache的初始化。

6.1.3、解析enable_l2cache

​ 它在start.S中,内容如下

       .align  5
.global enable_l2cache
enable_l2cache:
	mrc     p15, 0, r0, c1, c0, 1
	orr     r0, r0, #(1<<1)
	mcr     p15, 0, r0, c1, c0, 1
	mov     pc, lr

​ 在协处理器CP15的C1寄存器中进行操作,用来使能L2 cache。

6.2、对CPU进行相应初始化2

6.2.1、对L1 cache进行操作
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

​ 操作CP15协处理器使TLBs和icache无效。

6.2.2、关闭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

​ 操作CP15协处理器来关闭MMU。

7.1、识别启动介质

        ldr	r0, =PRO_ID_BASE
        ldr	r1, [r0,#OMR_OFFSET]
        bic	r2, r1, #0xffffffc1

​ 判断OM引脚值来确定当前启动介质,将某个值存到r2寄存器并进行比较。确定启动介质后,在r3寄存器中存入一个数,我们这里存的是#BOOT_MMCSD,也就是0x03。

8.1、第一次设置栈(SRAM中的栈)

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

​ 因为我们的DDR未初始化,所以在SRAM设置栈,我们下面要使用bl来调用一个函数lowlevel_init,设置栈的目的是为了保存地址,在调用函数完毕后可以返回该地址继续进行下面的代码。

9.1、解析lowlevel_init.S

9.1.1、推栈并检查复位状态
	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

​ 第一句将lr推入栈,以便返回时返回正确的地址。下面判断现在是处于哪种复位状态,冷上电时需要对DDR进行初始化,而热启动或者低功耗状态下的复位,不需要再次初始化DDR。

9.1.2、IO状态恢复
	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口可以正常工作。

9.1.3、关看门狗并且对外部SRAM SROM相关GPIO进行设置
	/* 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口进行设置。

9.1.4、供电锁存
	/* 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]	

​ 对开发板进行供电锁存, 分两步来放数据是考虑到非法立即数的原因。供电锁存的原因是:如果是软开关,这个开关是一个不会自锁的按钮,当按下时给芯片通电,弹起时芯片断电,想要给芯片持续供电,看需要软件进行供电锁存。

9.1.5、判断是否需要重定位
	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

	bl tzpc_init

​ 判断当前代码所在的位置是在SRAM中还是DDR中,从而判断是否进行重定位。因为在SRAM中和DDR中各有一份BL1,所以需要此判断来判断我们是处于冷启动状态还是低功耗状态,从而进行下面的操作。

​ 这段代码先将pc的值中的0xff000fff中为1的bit位清零,剩下的bit位赋值给r1。再将链接地址加载到r2,然后将链接地址r2的相应位清零,剩下特定位,最后去比较r1和r2。如果相等就跳过时钟和DDR的初始化,如果不相等就说明是冷启动,进行下面相应的时钟、DDR、串口等初始化。

9.1.6、system_clock_init、mem_ctrl_asm_init

​ system_clock_init是对时钟进行相应配置和初始化的代码,mem_ctrl_asm_init是对DDR进行初始化的代码。将DMC0分配了大小为256MB的内存空间,内存地址范围为0x30000000-0x3FFFFFFF。

9.1.7、uart_asm_init、tzpc_init
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

​ 串口初始化函数uart_asm_init先对uart的GPIO进行初始化,然后再对uart的其他配置进行设置,在所有配置设置完成之后打印出"O"的字样,最后跳转到返回地址。

tzpc_init:

	ldr	r0, =ELFIN_TZPC0_BASE
 	mov	r1, #0x0
 	str	r1, [r0]
 	mov	r1, #0xff
 	str	r1, [r0, #TZPC_DECPROT0SET_OFFSET]
 	str	r1, [r0, #TZPC_DECPROT1SET_OFFSET]
	str	r1, [r0, #TZPC_DECPROT2SET_OFFSET] 	

 	ldr 	r0, =ELFIN_TZPC1_BASE
 	str	r1, [r0, #TZPC_DECPROT0SET_OFFSET]
 	str	r1, [r0, #TZPC_DECPROT1SET_OFFSET]
	str	r1, [r0, #TZPC_DECPROT2SET_OFFSET] 	

 	ldr	r0, =ELFIN_TZPC2_BASE
 	str	r1, [r0, #TZPC_DECPROT0SET_OFFSET]
 	str	r1, [r0, #TZPC_DECPROT1SET_OFFSET]
	str	r1, [r0, #TZPC_DECPROT2SET_OFFSET]
	str	r1, [r0, #TZPC_DECPROT3SET_OFFSET] 

 	ldr	r0, =ELFIN_TZPC3_BASE
 	str	r1, [r0, #TZPC_DECPROT0SET_OFFSET]
 	str	r1, [r0, #TZPC_DECPROT1SET_OFFSET]
	str	r1, [r0, #TZPC_DECPROT2SET_OFFSET] 	

 	mov	pc, lr

​ trust zone的作用是用于安全保护的,用tzpc_init函数对其进行初始化。

9.1.8、检查复位状态、关闭ABB并打印K
#if defined(CONFIG_NAND)
	/* simple init for NAND */
	bl nand_asm_init
#endif

	/* check reset status  */
	
	ldr	r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
	ldr	r1, [r0]
	bic	r1, r1, #0xfffeffff
	cmp	r1, #0x10000
	beq	wakeup_reset_pre

	/* ABB disable */
	ldr	r0, =0xE010C300
	orr	r1, r1, #(0x1<<23)
	str	r1, [r0]

	/* Print 'K' */
	ldr	r0, =ELFIN_UART_CONSOLE_BASE
	ldr	r1, =0x4b4b4b4b
	str	r1, [r0, #UTXH_OFFSET]

	pop	{pc}

​ 先跳转到nand_asm_init来对Flash进行初始化,然后检查复位状态,之后将ABB关闭并且打印"K"的字样,最后返回跳转到这个函数时的地址,即回到start.S中。

10.1、在start.S中再次将开发板置锁

	ldr	r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
	ldr	r1, =0x00005301	 /* PS_HOLD output high	*/
	str	r1, [r0]

​ 为了保险起见,再次将开发板置锁。

11.1、第二次设置栈

	ldr	sp, _TEXT_PHY_BASE	/* setup temp stack pointer */		//33e00000
	sub	sp, sp, #12
	mov	fp, #0			/* no previous frame, so fp=0 */

​ 因为第一次设置的栈在SRAM中,空间小容易栈溢出,现在DDR已经被初始化了,所以将栈设到DDR中。

12.1、再次进行是否需要重定位的判断

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

#if defined(CONFIG_EVT1)
	/* If BL1 was copied from SD/MMC CH2 */
	ldr	r0, =0xD0037488		//globalBlockSize
	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
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

​ 再次用第一次判断重定位的代码来判断是否需要重定位,为了决定是否进行uboot的重定位。冷启动时当前情况是uboot的前一部分在SRAM中,也就是处于第一阶段,uboot的第二部分还在Flash中,判断如果是冷启动,就需要重定位将uboot的第二部分加载到DDR中链接地址0x33e00000处。

​ 因为我们在上面将#BOOT_MMCSD写入了INF_REG3寄存器,后面又将其读出并与#BOOT_MMCSD比较从而确定是从MMCSD启动,所以跳转到了uboot/cpu/s5pc11x/Movi.c中的movi_bl2_copy函数进行对BL2的重定位。

12.1.1、movi_bl2_copy函数解析
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;
}

​ 用typedef定义了一个函数指针类型,在这个函数指针中传参从而对BL2进行重定位,下面我们分析对它的传参

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

​ 其中2表示通道2,MOVI_BL2_POS是开始扇区号(必须和烧录uboot时烧录的位置相同),MOVI_BL2_BLKCNT是uboot长度占用的扇区数,CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址。

12.1.2、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

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


#if defined(CONFIG_ENABLE_MMU)
_mmu_table_base:
	.word mmu_table
#endif

​ MMU(Memory Management Unit)在CP15协处理器中进行控制,创建虚拟地址映射可以进行访问控制和cache的功能,并且可以通过软件层对物理层进行控制,如果要进行虚拟地址映射,需要对CP15进行相应的操作。

​ 代码先进行使能域,在CP15的C3寄存器中划分了16个域,对这16个域的内存检查权限进行设置,从而实现访问控制的功能。接着进行TTB(Translation Table Base)的设置,设置了转换表,将表索引(虚拟地址)和表项(物理地址)进行设置,一对表索引和表项组成一个转换表单元,能够对一个内存块进行虚拟地址转换,每个单元负责一个内存块,整个表的许多单元从而负责对整个内存空间的映射。最后在CP15的C1处理器中使能MMU。

13.1、第三次设置栈

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

​ 第二次设置栈将栈放的地方不是很合适,这里将栈放在一个更为安全合适并且不浪费内存的地方。

14.1、清理.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

​ 此段代码将_ bss_start和_bss_end进行比较,如果不相等就一直进行循环写0,从而清bss段。

15.1、结束第一阶段

ldr	pc, _start_armboot

_start_armboot:
	.word start_armboot

​ 用ldr指令将pc指向第二阶段的开始函数_start_armboot从而结束第一阶段。

你可能感兴趣的:(嵌入式Linux学习)