uboot学习笔记

一、启动流程的分析

  1. 4412芯片启动流程
    TBL0: ARM的起始地址都是0地址, 三星的芯片一般将0地址映射到iROM中, BL0就是指iROM中固化的启动代码, 主要负责加载BL1
    BL1: 三星对于bootloader的加密代码bl1.bin, 要放在外设中uboot.bin的头上, 和一部分uboot.bin一起加载到iRAM中运行.
    BL2: 从(nand/sd/usb)中拷贝的uboot.bin头最大14K到iRAM中代码中除去bl1.bin后剩余的部分, 负责设置CPU为SVC模式, 关闭MMU, 关闭中断, 关闭iCache, 关闭看门狗, 初始化DRAM,初始化时钟, 初始化串口, 设置栈, 校验BL2并将其搬移到DRAM高位地址, 重定位到DRAM中执行BL3
    BL3:是指在代码重定向后在内存中执行的uboot的完整代码, 负责初始化外设,更新向量表, 清BSS, 准备内核启动参数, 加载并运行OS内核
    我们常说的uboot分为两个过程,主要指的事BL2和BL3.
    BL2主要是汇编实现的
    BL3是c语言实现的。

  2. cpu 模式的理解
    cpu 有七种模式,每种模式对应的权限是不同的,可以这样理解这个权限,首次按arm有31个通用寄存器和6个状态寄存器,某些寄存器在特定模式下才能被访问。Svc代表管理者模式,权限可以理解为最高。

  3. mmu代表内存管理单元
    mmu的工作只有一个,就是把虚拟地址映射到物理地址。为什么需要mmu,考虑一个问题,32位的cpu,可访问的地址空间是4G,但是cpu的ram是远远没有4g的,比如说一个开发板的ddr是512m。但是怎么实现4g的访问空间,但实际只有512m的ram那?这就需要MMU了。具体实现原理就不过多叙述了。

  4. cache
    什么是cache,cache是一个高速缓存单元,它介于cpu和内存之间。为什么需要cache?因为cpu读写内存的速率比较慢,影响性能,cache的读写速率接近cpu的处理速率。为什么一开始要关闭cache和mmu那,我们知道内存使用前是需要初始化的,内存还没有初始化,就是用介于内存和cpu之间的cache和mmu,肯定会出问题,所以一开始就要禁用。

  5. uboot运行流程
    在这里插入图片描述

二结合实例,分析uboot

  1. 填充16字节的校验位
	.word 0x2000
	.word 0x0
	.word 0x0
	.word 0x0
  • uboot.bin镜像在开头需要加16字节的检验头(详见 irom application
    note文件)。这一部分主要功能是在代码段最开始处放置16字节的填充位,即通过伪.word指令定义4个word(32位)的空间来在代码段最开始处占16字节。
  • 在此处只是占据16字节的空间,校验头的正确值在编译阶段通过mkv210image.c中计算并填充的。
  1. 初始化异常向量表

板子上电后首先执行的时设置异常向量表,arm公司一共规定了七种异常向量,其中复位也是异常的一种。复位的入口地址为0.b和ldr都是跳转指令。PC是程序计数器,cpu总是按照pc的指向进行命令抓去,所以pc决定了程序运行的流向。所以第一条程序就执行了reset指向的函数。

_start:
 
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
	.word	CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
 
	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
  1. 设置全局变量
_TEXT_BASE:
	.word	TEXT_BASE

/*
 * Below variable is very important because we use MMU in U-Boot.
 * Without it, we cannot run code correctly before MMU is ON.
 * by scsuh.
 */
_TEXT_PHY_BASE:
	.word	CFG_PHY_UBOOT_BASE

.globl _armboot_start
_armboot_start: 
	.word _start

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
	.word __bss_start

.globl _bss_end
_bss_end:
	.word _end
	/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
	.word	0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de
  • TEXT_BASE是U-Boot代码的链接地址,为 0xc3e00000.
  • CFG_PHY_UBOOT_BASE设置U-Boot在DDR中的物理地址,即运行地址,U-Boot重定位将整个U-Boot拷贝至DDR中的_TEXT_PHY_BASE,CFG_PHY_UBOOT_BASE定义在\include\configs\itop_4412_ubuntu.h文件中。为什么是0x40000000,这个要看具体芯片的的内存分配,4412为ddr分配的起始地址为0x40000000.为什么是0x 0xc3e00000.
  • _armboot_start为uboot的入口地址,应为uboot有一次重定位的过程,所以这个值应该是会变得。
  • _bss_start为bss段的起始地址。
  • _bss_end为bss段的结束地址。
  • IRQ_STACK_START和FIQ_STACK_START设置中断栈和异常栈起始地址。

在这里插入图片描述

#define MEMORY_BASE_ADDRESS	0x40000000
#define CFG_PHY_UBOOT_BASE	MEMORY_BASE_ADDRESS + 0x3e00000

在这里插入图片描述

  1. 禁止中断和异常,设置管理者模式
	/*
	 * set the cpu to SVC32 mode and IRQ & FIQ disable
	 */
	mrs	r0, cpsr
	bic	r0, r0, #0x3f
	orr	r0, r0, #0xd3
	msr	cpsr, r0
  • mrs
    mrs指令用于将程序状态寄存器的内容传入到普通寄存器中。当需要修改程序状态寄存器时,首先把程序状态寄存器的值读入到普通寄存器,修改后在传入到程序状态寄存器。
  • cpsr
    cpsr是程序状态寄存器,用于管理中断、异常和模式,反应运行状态。
    程序状态寄存器的读写只能用msr和mrs指令。
    在这里插入图片描述
  • MSR指令
    对状态寄存器CPSR和SPSR进行写操作。与MRS配合使用,可以实现对CPSR或SPSR寄存器的读-修改-写操作,可以切换处理器模式、或者允许/禁止IRQ/FIQ中断等。
  • BIC
    BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器、或一个立即数。操作数2为32位的掩码,如果在掩码中置了某一位1,则清除这一位。未设置的掩码位保持不变
BIC{条件}{S}  目的寄存器,操作数1,操作数2
  1. 关闭mmu和cache

由前面分析知,最后跳转到了这个函数。这个函数的作用就是关闭mmu和cache,为什么关闭这两个,前面已经介绍过。这里牵涉到一个cp15知识点的扫盲。这个是协处理器。协处理器的主要是管理mmu和cache、保护系统、配置时钟模式等。Cp15只能被MRC和MCR指令访问。具体内容现在这里挖个坑,有空下看,这里主要移植捋下思路。

#if 1 //*****ly
cache_init:
	mrc	p15, 0, r0, c0, c0, 0	@ read main ID register读芯片的id
	and	r1, r0, #0x00f00000	@ variant
	and	r2, r0, #0x0000000f	@ revision
	orr	r2, r2, r1, lsr #20-4	@ combine variant and revision
	cmp	r2, #0x30
	mrceq	p15, 0, r0, c1, c0, 1	@ read ACTLR
	orreq	r0, r0, #0x6		@ Enable DP1(2), DP2(1)
	mcreq	p15, 0, r0, c1, c0, 1	@ write ACTLR
	/*
	 * 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

	/*
	 * 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, #0x00001000	@ set bit 12 (---I) Icache
	orr	r0, r0, #0x00000002	@ set bit 1  (--A-) Align
	orr	r0, r0, #0x00000800	@ set bit 11 (Z---) BTB
	mcr	p15, 0, r0, c1, c0, 0
#endif

  • 为什么要关闭mmu?
    因为MMU是把虚拟地址转化为物理地址得作用,而我们现在是要设置控制寄存器,而控制寄存器本来就是实地址(物理地址),再使能MMU,不就是多此一举了吗?
  • 为什么要关闭cache?
    catch和MMU是通过CP15管理的,刚上电的时候,CPU还不能管理他们。所以上电的时候MMU必须关闭,指令cache可关闭,可不关闭,但数据cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从catch里面取,而这时候RAM中数据还没有cache过来,导致数据预取异常
  • cp15协处理器
    CP15 —系统控制协处理器 (the system control coprocessor),通过协处理器指令 MCR和 MRC 提供具体的寄存器来配置和控制 caches、MMU、保护系统、配置时钟模式(在 bootloader时钟初始化用到)。CP15包含16个32位的寄存器,其编号为0~15。具体请查看这篇博客:https://blog.csdn.net/deep_l_zh/article/details/84852260
  1. 判断启动介质
	/* Read booting information */
        ldr	r0, =POWER_BASE
        ldr	r1, [r0,#OMR_OFFSET]
        bic	r2, r1, #0xffffffc1

	/* 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, #0xA
        moveq   r3, #BOOT_ONENAND

@	cmp	r2, #0x6		@ 4KB 5-cycle	16-bit ECC
@	moveq	r3, #BOOT_NAND

	/* SD/MMC BOOT */
	cmp     r2, #0x4
        moveq   r3, #BOOT_MMCSD	

	/* eMMC4.3 BOOT */
	cmp		r2, #0x6
	moveq	r3, #BOOT_EMMC43

	/* eMMC441 BOOT */
	cmp		r2, #0x28
	moveq	r3, #BOOT_EMMC441
	
        /* NOR BOOT */
@	cmp     r2, #0x14
@	moveq   r3, #BOOT_NOR	

	/* For second device booting */
	/* OneNAND BOOTONG failed */
@	cmp     r2, #0x8
@	moveq   r3, #BOOT_SEC_DEV

	/* Uart BOOTONG failed */
@	cmp     r2, #(0x1<<4)
@	moveq   r3, #BOOT_SEC_DEV
	ldr	r0, =INF_REG_BASE
	str	r3, [r0, #INF_REG3_OFFSET]
  1. POWER_BASE定义如下,搜索芯片手册,这个寄存器定义为电源管理寄存器基地址。[r0,#OMR_OFFSET]表示OM_STAT寄存器,这个寄存器保存的是启动模式。关于启动模式的介绍,在芯片手册的uboot章节有介绍,拨码开关拨到不同位置代表不同的启动方式。后面通过比较值,选择不同的启动值。把启动方式保存在r3寄存器中。
  2. 最后两句,INF_REG_BASE这个值为 0x10020800,基地址为依然为 0x10020000,偏移地址为0x00000800,我检索数据手册,结果如下,这是一类寄存器,用于保存用户的数据的。str指令的意思就是把r3寄存器值保存到,后面地址空间,就是把启动方式保存到用于保存用户信息的寄存器3中。
#define POWER_BASE		        0x10020000
#define OMR_OFFSET			0x0

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. lowlevel_init
	/*
	 * Go setup Memory and board specific bits prior to relocation.
	 */
	//b     .
	bl	lowlevel_init	/* go setup pll,mux,memory */

跳转到lowlevel_init函数,首先初始化了栈,为后面的c准备环境,然后判断了启动模式,如果是ram启动就会跳过时钟和内存初始化。然后是串口初始化。

8. 初始化栈

	/* use iROM stack in bl2 */
	ldr	sp, =0x02060000
	push	{lr}

这个函数首先是初始化了栈,0x02060000是一个地址,这个地址是什么哪?为什么是这个地址哪?我们要看一下datasheet的memmorymap部分,如图所示,这个地址代表的是iRAM的地址的首地址,目前程序运行在iram中,所以就使用了这个地址。 sp是堆栈指针,push是压栈的意思,lr保存函数调用的返回指针。那么为什么需要这段程序?lr寄存器只有一个,lr寄存器只能保存一次cp指针的值,当出现嵌套掉调用函数时,需要保存多个cp指针的当前指向地址,一个不够用,所以需要初始化堆栈。

在这里插入图片描述

  1. 判断复位类型
	/* check reset status  */
	ldr     r0, =(INF_REG_BASE + INF_REG1_OFFSET)
    ldr     r1, [r0]
	/* AFTR wakeup reset */
	ldr	r2, =S5P_CHECK_DIDLE
	cmp	r1, r2
	beq	exit_wakeup
	/* Sleep wakeup reset */
	ldr	r2, =S5P_CHECK_SLEEP
	cmp	r1, r2
	beq	wakeup_reset

这段代码的意义是什么?复位分为很多种复位,比如我们的手机,可能是关 机后重新开机,需要初始化一边所有的硬件,这是冷启动;还有一种就是手 机 长时间不 自动进入休眠模式,当再次使用时,重新唤醒也可以引发复位,这种情况下是不需要初 始化一边硬件 。4412这款芯片有五种复位,分别为冷启动复位、看门狗复位、热启动复位、软件复位和休眠唤醒复位。 不同的复位引发的效果不一样,比如冷启动复位会初始化ddr,休眠唤醒不会初始化DDR,具体如图所示。NF_REG_BASE 和INF_REG1_OFFSET 这两个宏是什么意思,我们搜索下文件,这两个宏定义在include的目录下,如下。这是什么意思哪,我们通过0x10020800这个关键字检索下手册,如图,读取这个寄存器的值,INF_REGx_REG是一系列寄存器,这寄存器我们上面提到了,用于保存一些信息,这些寄存器是当不同复位时保存不同的值,如图,冷启动复位会重新复位这一系列寄存器的值,休眠唤醒复位就不会重新复位这些值,通过这一点就可以区分是否是冷启动复位,还是休眠唤醒复位。S5P_CHECK_DIDLE和S5P_CHECK_SLEEP这两个宏进行比较,如果一样则进入相应的处理程序。这部分的复位要看电源管理部分。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#define INF_REG_BASE			0x10020800
#define INF_REG0_REG			__REG(INF_REG_BASE+INF_REG0_OFFSET)
#define INF_REG1_REG			__REG(INF_REG_BASE+INF_REG1_OFFSET)
#define INF_REG2_REG			__REG(INF_REG_BASE+INF_REG2_OFFSET)
#define INF_REG3_REG			__REG(INF_REG_BASE+INF_REG3_OFFSET)
#define INF_REG4_REG			__REG(INF_REG_BASE+INF_REG4_OFFSET)
#define INF_REG5_REG			__REG(INF_REG_BASE+INF_REG5_OFFSET)
#define INF_REG6_REG			__REG(INF_REG_BASE+INF_REG6_OFFSET)
#define INF_REG7_REG			__REG(INF_REG_BASE+INF_REG7_OFFSET)

10. 供电锁存

  • 0x1002330c这个地址就是PS_HOLD_CONTROL寄存器地址,这个寄存器的描述如图,大概意思就是当冷复位时就要复位此处,所以就要8、9位置1,所以就有“orr r1, r1, #0x300”,orr是位或的意思。
  • 地址0x11000c08表示控制gpio口x组的上下拉的,如图,置0表示禁止上下拉模式。STR指令用亍从源寄存器中将一个32位的字数据传送到存储器中。这里禁止上下拉不太理解什么意思。不太理解这里的供电锁存是什么意思。这个地方移植时可能需要修改。
    /* PS-Hold high */
    ldr r0, =0x1002330c
    ldr r1, [r0]
    orr r1, r1, #0x300
    str r1, [r0]
    ldr     r0, =0x11000c08
    ldr r1, =0x0
    str r1, [r0]

在这里插入图片描述
在这里插入图片描述

11. 禁止看门狗

 /* Clear  MASK_WDT_RESET_REQUEST  */
  ldr r0, =0x1002040c
  ldr r1, =0x00
  str r1, [r0]

为什么禁止看门狗,因为现在还没有喂狗的程序,如果现在使能看门狗,那么程序会不停的复位。0x1002040c为看门狗寄器MASK_WDT_RESET_REQUEST,如图。
在这里插入图片描述

12. 检测程序当前的执行位置(SRAM或DDR)

#ifdef check_mem /*liyang 20110822*/
	/* 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   */
#endif
  • 本段的功能是检测当前代码的执行位置。判断是在SRAM中还是DDR中,即CPU是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR的初始化代码。
  • bic是位清除指令,其功能是:将pc中的某些位清零(r0中为1的位清零),剩下一些特殊的bit位赋值给r1。
  • 比较链接地址和当前地址的特定位,即比较r1和r2。若比较链接地址和当前地址的特定位相等,说明当前代码处于DDR中(休眠唤醒),则跳转到标号1处执行后面的代码(即跳过时钟、DDR的初始化代码);否则,继续执行后续的时钟、DDR的初始化代码。_TEXT_BASE表示uboot被重定位时要加载到的起始地址。
  • 判断代码执行的位置的原理,就是根据sram和DDR在内存空间地址不同,通过比较当前程序指针指向的地址和DDR地址空间

13. 初始化时钟、DDR、串口

/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
1:
/* for UART */
bl uart_asm_init //初始化完成打印'O'
bl tzpc_init //基本没用
#if defined(CONFIG_ONENAND)
bl onenandcon_init
#endif
#if defined(CONFIG_NAND)
/* simple init for NAND */
bl nand_asm_init

跳转执行时钟初始化函数system_clock_init,然后返回。时钟的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改,移植过程基本不需要需改该部分代码。跳转执行DDR初始化函数mem_ctrl_asm_init,然后返回。其中,DMC0,DMC1等设置很重要,直接和板子上内存的大小和分布有关,需要注意。DDR的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改。跳转执行串口初始化函数uart_asm_init,成功执行后打印‘O’,然后返回。‘O’可作为调试的帮助。若定义了ONFIG_ONENAND,则初始化ONENAND;若定义CONFIG_NAND,则初始化NAND。在返回start.S前打印了‘K’,与之前的‘O’组成“OK”,这是uboot的第一条打印信息,可以用来判断lowlevel_init是否正常运行。
把之前保存在栈中的lr值弹出到pc中,来返回到start.S 。这部分可能需要针对不同的开发板进行修改。

  1. 再次进行供电锁存
	ldr	r0, =0x1002330C  /* PS_HOLD_CONTROL register */
	ldr	r1, =0x00005300	 /* PS_HOLD output high	*/
	str	r1, [r0]
  1. 初始化栈(DDR中)
	/* 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 */

为了即将执行的c程序做准备,这里开始第二次设置栈,设置于DDR中。这里将栈设置在_TEXT_PHY_BASE,即U-Boot在DDR中的真正物理地址(U-Boot运行地址)。由于栈是满减栈,所以紧挨着uboot放置也不会冲突。注意这两次的不同,第一次设置栈是在sram中,目的是为跳转嵌套做铺垫。

  1. 再次检测当前程序执行地址(SRAM或DDR)
	/* 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   */

在lowlevel_init中,检测当前代码的执行位置。判断是在SRAM中还是DDR中,即cpu是冷启动还是休眠唤醒复位,从而来决定是否要跳过后面的时钟、DDR和串口的初始化代码。在此处再次检测当前代码的执行位置,从而来决定是否要跳过重定位代码。

  1. 通过引脚检测启动介质
	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_EMMC441
	beq     emmc441_boot
	
	cmp     r1, #BOOT_EMMC43
	beq     emmc_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  /*goto 0x1010*/
	b	after_copy
//ly
second_mmcsd_boot:
	ldr   r3, =BOOT_MMCSD	
	ldr	r0, =INF_REG_BASE
	str	r3, [r0, #INF_REG3_OFFSET]
	
mmcsd_boot:
#ifdef CONFIG_CLK_1000_400_200
	ldr	r0, =CMU_BASE
	ldr	r2, =CLK_DIV_FSYS2_OFFSET
	ldr	r1, [r0, r2]
	orr r1, r1, #0xf
	str r1, [r0, r2]
#endif
	bl      
	
	b       after_copy

emmc_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
	ldr	r0, =CMU_BASE
	ldr	r2, =CLK_DIV_FSYS1_OFFSET
	ldr	r1, [r0, r2]
	orr r1, r1, #0x3
	str r1, [r0, r2]
#endif
	bl		emmc_uboot_copy
	b	after_copy

emmc441_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
	ldr	r0, =CMU_BASE
	ldr	r2, =CLK_DIV_FSYS3_OFFSET
	ldr	r1, [r0, r2]
	orr r1, r1, #0x3
	str r1, [r0, r2]
#endif
	bl		emmc441_uboot_copy
//ly 20110824
	ldr   r0, =0x43e00000
	ldr   r1, [r0]
	ldr   r2, =0x2000
	cmp r1, r2
	bne  second_mmcsd_boot
	b	after_copy


nor_boot:
@	bl	read_hword
	b	after_copy

启动介质不同,代码重定位的方式肯定也不一样。emmc441_uboot_copy代码就是进行代码重定位的。

  1. 建立虚拟地址映射表并开启MMU
#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

从代码重定位函数返回之后,将跳转至start.S->after_copy,建立虚拟地址映射表并开启MMU。

  1. 第三次初始化栈(DDR中)
#ifdef CONFIG_EVT1
	/* store DMC density information in u-boot C level variable */
	ldr	r0, = CFG_UBOOT_BASE
	sub	r0, r0, #4
	ldr	r1, [r0]
	ldr	r0, _dmc_density
	str	r1, [r0]
#endif

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

第三次设置栈,仍然设在DDR中。虽然上一次已经在DDR中设置过,但是是紧挨着uboot存放的,位置不合理。所以本次将栈设置uboot链接地址上方2MB处,这个位置合理、紧凑、安全。

  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是链接脚本中定义的。
利用循环清零。

  1. 远跳转至start_armboot(U-Boot第二阶段)
ldr	pc, _start_armboot

至此,第一阶段已经运行完成,进入第二阶段。汇编部分也结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值