uboot启动第一阶段详解——汇编代码部分start.S

Posted 代二毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uboot启动第一阶段详解——汇编代码部分start.S相关的知识,希望对你有一定的参考价值。

前言

uboot启动第一阶段是用汇编语言实现的,大部分都是Soc内部的初始化,可以理解成一些通用的初始化,只要使用该款Soc,第一阶段的初始化流程基本是一样的。不直接用C语言进行初始化是因为,C语言运行需要一定的环境,比如栈的设置,而汇编代码刚好可以为C语言的运行初始化环境。在汇编代码的结束阶段就是跳转到C语言实现的函数继续进行初始化。

1、找到uboot的入口

uboot是个裸机代码,用汇编代码和C语言代码共同组成,和平时我们写的应用层的C语言程序不一样,uboot的入口不是main函数。uboot的入口可以在链接脚本(ENTRY(_start))中得到,_start标号处就是uboot启动的第一句代码。详细介绍可以看博客:《u-boot的链接脚本分析》

2、16字节的头信息填充

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

S5PV210的uboot要求有16字节的头信息,里面会放一些校验消息,其他平台会不会要求16字节的头不太确定。这里只是定义了4个int型变量,具体的内容不重要,作用就是先预留出16字节的头空间,后面会去填充。

3、构建异常向量表

.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个寄存器和异常处理机制详解》

4、定义全局变量

.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进行填充。

5、设置CPU为管理员模式(SVC)并禁止IRQ、FIQ

	@;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的位定义。

6、初始化l2cache

bl	disable_l2cache
bl	set_l2cache_auxctrl_cycle
bl	enable_l2cache

初始化l2cache,具体作用不了解。

7、禁止L1 iCache和dCache

   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都用不上,相关的设置都是操作协处理器。

8、禁止MMU和cache

        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寄存器,经典的"读-改-写"三部曲完成设置。

9、读取并判断启动方式

        ldr	r0, =PRO_ID_BASE
        ldr	r1, [r0,#OMR_OFFSET]
        bic	r2, r1, #0xffffffc1//只保留特定位数据

将[PRO_ID_BASE+OMR_OFFSET]地址处的数据读出来,并将特定几位的数据赋值给r2。详细信息看博客:《u-boot中如何确定启动方式》

10、设置栈并执行lowlevel_init函数

	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函数主要功能有设置时钟系统、初始化内存;

11、上电锁存

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

作用是开发板上电后电源按键弹起开发板也不会断电。详情参见:《开发板的上电锁存》

12、第二次设置栈

	/* 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语言做准备。

13、判断当前是在IRAM还是在DDR中运行

	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的重定位。

14、判断当前是否从SD卡通道2启动

#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芯片的特性。

15、根据启动方式读取BL2

	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

根据不同的启动方式,调用不同启动介质的启动函数,要结合第九步进行分析。

16、打开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

详解介绍参考博客:《嵌入式开发(S5PV210)——u-boot中开启MMU》

17、第三次设置栈

ldr	sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
sub	sp, r0, #12		/* leave 3 words for abort-stack    */

此时uboot启动第一阶段马上就要结束了,DDR已经初始化,uboot中对内存是有规划的使用,这里会给栈一个够用的空间。

18、清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段清零。

19、跳转到C语言执行第二阶段初始化

ldr	pc, _start_armboot

_start_armboot:
	.word start_armboot

跳转到start_armboot函数执行,这是用C语言实现的函数,接下来就是uboot用C语言进行的第二阶段的启动。

以上是关于uboot启动第一阶段详解——汇编代码部分start.S的主要内容,如果未能解决你的问题,请参考以下文章

uboot启动过程

uboot分析:uboot的启动过程分析

内核启动过程分析

uboot启动第一阶段分析

内核启动分析

内核启动12