uboot启动第一阶段是用汇编语言实现的,大部分都是Soc内部的初始化,可以理解成一些通用的初始化,只要使用该款Soc,第一阶段的初始化流程基本是一样的。不直接用C语言进行初始化是因为,C语言运行需要一定的环境,比如栈的设置,而汇编代码刚好可以为C语言的运行初始化环境。在汇编代码的结束阶段就是跳转到C语言实现的函数继续进行初始化。
uboot是个裸机代码,用汇编代码和C语言代码共同组成,和平时我们写的应用层的C语言程序不一样,uboot的入口不是main函数。uboot的入口可以在链接脚本(
ENTRY(_start)
)中得到,_start标号处就是uboot启动的第一句代码。详细介绍可以看博客:《u-boot的链接脚本分析》。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
S5PV210的uboot要求有16字节的头信息,里面会放一些校验消息,其他平台会不会要求16字节的头不太确定。这里只是定义了4个int型变量,具体的内容不重要,作用就是先预留出16字节的头空间,后面会去填充。
.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//快速中断
异常向量表在内存的地址时可以配置的,只需要提前在异常向量表的地址处存入各种异常的处理函数地址,当异常发生时硬件会自动跳转到该种异常的异常向量表去执行。这里是将各种异常的处理函数存入异常向量表,实际的异常处理函数是空的,因为uboot的主要作用是启动内核,各种异常等内核去处理。如果uboot在启动中遇到异常就直接挂掉或者重启。更详细的细节参考:《ARM的37个寄存器和异常处理机制详解》。
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
_TEXT_BASE:
.word TEXT_BASE //uboot的链接地址
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE//uboot所在的物理地址
.globl _armboot_start
_armboot_start:
.word _start //_start标号的地址,也就是uboot执行第一句代码的地址
.globl _bss_start //bss段的开始,后面清除bss段需要用
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end//bss段的结束
#if defined(CONFIG_USE_IRQ)
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de //IRQ模式下的栈空间地址
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de//FIQ模式下的栈空间地址
#endif
这些全局变量主要是uboot启动要用到的一些特殊地址,要根据编译脚本和链接脚本进行分析。
.balignl 16,0xdeadbeef
:balignl 是对齐指令,该语句的作用是后面的代码按16字节对齐,如果没有对齐则用0xdeadbeef进行填充。
@;mrs r0,cpsr
@;bic r0,r0,#0x1f
@;orr r0,r0,#0xd3
@;msr cpsr,r0
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
SVC模式下有特权,不然uboot初始化有些操作不被允许,整个uboot都是在SVC模式下,禁止IRQ和FIQ是因为uboot主要作用是启动内核,不需要也不想被中断打扰。整个设置操作cpsr寄存器,具体设置步骤要查询cpsr的位定义。
bl disable_l2cache
bl set_l2cache_auxctrl_cycle
bl enable_l2cache
初始化l2cache,具体作用不了解。
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
禁止掉TLB和icache,此时DDR还没有初始化,TLB(页表转换)和icache都用不上,相关的设置都是操作协处理器。
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的c1寄存器,经典的"读-改-写"三部曲完成设置。
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1//只保留特定位数据
将[PRO_ID_BASE+OMR_OFFSET]地址处的数据读出来,并将特定几位的数据赋值给r2。详细信息看博客:《u-boot中如何确定启动方式》。
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)设置栈就是将sp寄存器中写入分配的栈空间的地址,ARM默认是满减栈;此时DDR还没有初始化,所以栈空间是在iRAM中。这里必须设置栈的原因是,后面要开始调用汇编函数,虽然LR寄存器能保存函数返回地址,但是只有一个LR寄存器,不能实现函数的多级调用。
(2)lowlevel_init函数主要功能有设置时钟系统、初始化内存;参考博客:《uboot启动——lowlevel_init函数详解》。
/* 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]
作用是开发板上电后电源按键弹起开发板也不会断电。详情参见:《开发板的上电锁存》。
/* 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 */
此时DDR已经初始化完成,将栈空间设置在DDR中,因为iRAM中的空间太小了,这也是为调用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 */
原理就是从PC寄存器中读出当前的运行地址,然后运行地址和链接地址的某几位高位进行比较。如果相等,说明uboot已经进行了重定位,此次启动可能是从休眠状态启动,不用再重定位BL2;如果不相等,说明此时还运行在iRAM中,要进行BL2的重定位。
#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
从[0xD0037488]地址处读取数据,然后与0xEB200000进行比较,如果相等则说明是从SD卡通道2启动,跳转到mmcsd_boot函数指定,进行BL2的重定位,第15步会被跳过。[0xD0037488]是个特殊地址,硬件会根据启动方式自动去写入数据,这是S5PV210芯片的特性。
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
根据不同的启动方式,调用不同启动介质的启动函数,要结合第九步进行分析。
#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
详解介绍参考博客:《嵌入式开发(S5PV210)——u-boot中开启MMU》。
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
sub sp, r0, #12 /* leave 3 words for abort-stack */
此时uboot启动第一阶段马上就要结束了,DDR已经初始化,uboot中对内存是有规划的使用,这里会给栈一个够用的空间。
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段清零。
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
跳转到start_armboot函数执行,这是用C语言实现的函数,接下来就是uboot用C语言进行的第二阶段的启动。