BL1阶段代码的分析以start.s文件作为主要的目标,此篇博文主要对整个个流程进行分析。
BL1阶段的代码固化在IROM中的BL0调用执行,在上电之后会会执行,他的主要主要工作就是初始化soc,为uboot的主体代码也就是BL2阶段做好一切准备。
(1)CPU相关的一些基础的初始化;
(2)初始化DRAM;
(3)从sd卡中拷贝BL2(其实是拷贝整个uboot)到DRAM中;
(4)设置三次不同用处的栈;
(5)初始化MMU,虚拟地址映射的建立;
(6)最后通过长跳转执行BL2阶段的代码。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
在裸机开发时,mkv210image.c完成了这个功能,进行镜像前16字节的填充占位。在之后被填充为坏牛肉:deadbeef
.balignl 16,0xdeadbeef
.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
cpu有异常事件发生时会在此向量表内进行查找,然后执行对应的函数,而这些函数具体的实现是参照硬件来实现。 在uboot中的异常向量表较少,因为他的主要工作是驱动硬件,而对于各种异常的处理不是主要的目标。
_TEXT_BASE:
.word TEXT_BASE
这里的TEXT_BASE 就是我们配置阶段分析的 make 时传入的变量。
注:对于这里的汇编的语法做简单的说明:类似于定义了一个名为_TEXT_BASE的指针,而这个指针指向的是一个四字节的名为 TEXT_BASE 的变量
TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE MEMORY_BASE_ADDRESS + 0x3e00000
指定一个uboot进行运行的物理地址,而这个物理地址经过分析为:0x33e0000。 这个地址也是_TEXT_BASE指定的0xc3e00000链接地址所实际对应的物理地址,在之后的MMU初始化之后会将两个地址进行映射。
.globl _armboot_start
.globl _bss_end
.globl IRQ_STACK_START
.globl FIQ_STACK_START
这些全局变量在之后的程序中会被调用
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
cpsr:程序状态寄存器,向程序状态寄存器的c位写入0xd3。而0xd3对应的寄存器的含义是:将cpu设置为禁止FIQ IRQ,ARM状态,SVC模式。CPU在复位时默认会进入SVC模式,但是这里为了保险起见通过软件再次设置。
一些比较底层的初始化:
cpu_init_crit:
bl disable_l2cache
bl set_l2cache_auxctrl
bl enable_l2cache
禁用MMU的东西和缓存:
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
读取boot的启动方式信息:
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
S5PV210启动方式由SOC的OM5~OM0这6个引脚的高低电平决定,在芯片中地址为0xe0000004的区域有一个特殊功能寄存器,这个寄存器的值是硬件根据OM引脚的设置而自动设置的,所以通过读取这个寄存器的值可以得知硬件的启动方式。
然后在后面的程序中通过与一些已设置的值进行比对以具体的确定启动的方式:
/* 以 SD/MMC BOOT 的一部分为例*/
cmp r2, #0xc
moveq r3, #BOOT_MMCSD
/* Uart BOOTONG failed */
cmp r2, #(0x1<<4)
moveq r3, #BOOT_SEC_DEV
ldr r0, =INF_REG_BASE
str r3, [r0, #INF_REG3_OFFSET]
将前一步中r3中的值也就是BOOT_MMCSD放进一个名为INF_REG_BASE的寄存器中进行存储,在后面重定位是会用到这个寄存器
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 */
此阶段属于启动的BL1阶段,代码运行在SRAM中,所以设置的栈也就是在SRAM中(内存分布图的查找得知是在SRAM中)。 对于 lowlevel_init 的分析请看关于 lowlevel_init 的具体分析
ldr r0, =0xE010E81C /* PS_HOLD_CONTROL register */
ldr r1, =0x00005301 /* PS_HOLD output high */
str r1, [r0]
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
这里的_TEXT_PHY_BASE前面已经分析过,为ox33e00000,刚好紧挨着uboot的链接地址,但又因为uboot中的栈是满减栈,所以不会对uboot的代码段造成影响。
这里设置栈的主要原因是SRAM中内存仅为96kb,所以将栈设置到内存较大的DRAM1中以避免因为内存溢出等内存不够造成的安全问题,然后设置完栈就可以调用c函数进行初始化。
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 */
再次判断此时刻程序运行的地址,如果是运行的地址与我们配置编译阶段指定TEXT_BASE链接地址相同的话,就执行下面的重定向函数,将BL2阶段的程序加载前面lowlevel_init函数中已经初始化的DRAM内存空间中去运行。
在这里有两种方式都可以进入我们的重定位函数:
(1)通过读取 0xD0037488 (SRAM一个名为V210_SDMMC_BASE 的,存储启动方式的寄存器)的寄存器的值然后与一个已设置的值进行比对已确定启动的方式进而进入重定位函数:
#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
(2)通过比对第8步中赋值的寄存器(储存uboot启动方式信息)的值来判断是否为sd卡启动:
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 */
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
最后都是调用mmcsd_boot。
追踪mmcsd_boot 函数指针可知他的实现主要靠movi_bl2_copy函数来实现:
#if defined(CONFIG_EVT1)
ch = *(volatile u32 *)(0xD0037488);
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
首先定义了一个0xD0037488所指向的函数类型的函数
else if (ch == 0xEB200000) {
ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
和13步第一种方法一样的比对方式,重点是执行copy_bl2()函数。
值得一提的是copy_bl2()这个函数:这个函数是实现重定向的具体函数,函数传入五个参数,分别是sd卡的通道号、sd卡的开始扇区号、读取扇区的个数、读取后放入的地址内存(BL2在DRAM中的储存地址)以及以一个无关的数,最后重定位的位置是0x33e00000
/* 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
分析虚拟地址映射之前不得不说说虚拟地址映射相关的一些知识:
(1)MMU是一个内存管理单元,是SOC中的一个硬件设备,主要完成的功能就是实现虚拟地址到物理地址的映射。
(2)MMU通过对cp15协处理器进行控制,从而实现虚拟地址的映射,也就是说操作MMU进行虚拟地址映射的方法就是对cp15协处理器的寄存器进行编程;
(3)虚拟地址映射在完成物理地址到虚拟地址的映射外还实现了访问控制,访问控制就是:对内存进行分块处理,然后每一块虚拟地址单独的进行映射,同时实现了读、写、执行的控制;
(4)cach的工作也与虚拟地址映射有关,cpu将自己比较常用的一些数据存储在cach中,然后在需要的时间直接从cach中进行读取。
在这里设置了虚拟地址映射的转换表TTB:
转换表是建立虚拟地址映射的关键,转换表分为表索引和表项两部分,表索引对应虚拟地址映射,表项对应物理地址,一个表索引和表项构成一个转换单元,能够对一个内存块进行虚拟地址转换。(内存管理和映射以快为单位)转换表放置在内存之中,放置时要求起始地址在内存之中要xx位对齐,转换表不需要软件的支持,而是将基地址TTB设置到cp15的c2寄存器之中,然后MMU工作时会自动查询转换表。
进过分析转换表的可知:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。
c0000000-d0000000 30000000-40000000 256MB 3G-3.25G
skip_hw_init:
/* Set up the stack */
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
本次设置栈主要是为了设置一个更加安全的栈,设置了栈的起始位置,设置了栈的大小:
栈的起始位置:CFG_UBOOT_BASE + CFG_UBOOT_SIZE:uboot起始地址0x33e00000上方2MB处
栈的大小:CFG_UBOOT_BASE + CFG_UBOOT_SIZE-0x1000 约为1.8MB
这次设置使得这个栈离我们uboot的源码储存的地方尽量的近,从而紧凑而不浪费的使用了内存空间。(uboot中的栈是满减栈,向下进行储存,所以设置的栈在减去uboot源码的空间之后有着巨大的空间可以供开发使用)
而bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
这个远跳转是uboot启动过程第一阶段和第二阶段的分界线,直接跳转至DDR中的第二阶段开始的地址处。 而start_armboot是一个指针,指向的是uboot启动源码第二部分在DDR之中存放的地址。它存放在uboot/lib_arm/board.c中。
END...........