1.4. 设置堆栈sp指针

1.4.1. stack_setup

	/* Set up the stack						    */
stack_setup:
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */1
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
        

1

此句含义是,把地址为_TEXT_BASE的内存中的内容给r0,即,将所有的中断都mask了。

而查看前面的相关部分的代码,即:

_TEXT_BASE:
	.word	TEXT_BASE
                

得知,地址为_TEXT_BASE的内存中的内容,就是

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

中的:

TEXT_BASE = 0x33D00000

所以,此处即:

r0

= TEXT_BASE

= 0x33D00000

而关于sub指令:

SUB : 减法

(Subtraction)

SUB{条件}{S} <dest>, <op 1>, <op 2>

dest = op_1 - op_2

SUB 用操作数 one 减去操作数 two,把结果放置到目的寄存器中。操作数 1 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即值:

SUB R0, R1, R2 ; R0 = R1 - R2

SUB R0, R1, #256 ; R0 = R1 - 256

SUB R0, R2, R3,LSL#1 ; R0 = R2 - (R3 << 1)

减法可以在有符号和无符号数上进行。

所以对应含义为:

r0 = r0 - #CFG_MALLOC_LEN

r0 = r0 - #CFG_GBL_DATA_SIZE

其中,对应的两个宏的值是:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\configs\EmbedSky.h

中:

#define CONFIG_64MB_Nand		0		//添加了对64MB Nand Flash支持

/*
 * Size of malloc() pool
 */
#define CFG_MALLOC_LEN				(CFG_ENV_SIZE + 128*1024)
#define CFG_GBL_DATA_SIZE			128	/* size in bytes reserved for initial data */

#if(CONFIG_64MB_Nand == 1)
#define CFG_ENV_SIZE			0xc000	/* Total Size of Environment Sector */
#else
#define CFG_ENV_SIZE			0x20000	/* Total Size of Environment Sector */
#endif
                

所以,从源码中的宏定义中可以看出,

CFG_MALLOC_LEN

= (CFG_ENV_SIZE + 128*1024)

= 0x20000 + 128*1024

= 0x40000

= 256*1024

= 256KB

CFG_GBL_DATA_SIZE

= 128

所以,此三行的含义就是算出r0的值:

r0

= (r0 - #CFG_MALLOC_LEN) - #CFG_GBL_DATA_SIZE

= r0 - 0x40000 – 128

= r0 – 0x40080

= 33CBFF80

1.4.2. calc stack

#ifdef CONFIG_USE_IRQ
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)1
#endif
	sub	sp, r0, #122		/* leave 3 words for abort-stack    */
        

1

如果定义了CONFIG_USE_IRQ,即如果使用中断的话,那么再把r0的值减去IRQ和FIQ的堆栈的值,

而对应的宏的值也是在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\configs\EmbedSky.h

中:

/*-------------------------------------------------------------------
 * Stack sizes
 *
 * The stack sizes are set up in start.S using the settings below
 */
#define CONFIG_STACKSIZE		(128*1024)	/* regular stack */
#ifdef CONFIG_USE_IRQ
#define CONFIG_STACKSIZE_IRQ		(4*1024)	/* IRQ stack */
#define CONFIG_STACKSIZE_FIQ		(4*1024)	/* FIQ stack */
#endif
                

所以,此时r0的值就是:

#ifdef CONFIG_USE_IRQ

r0

= r0 - #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

= r0 – (4*1024 + 4*1024)

= r0 – 8*1024

= 33CBFF80 – 8*1024

= 33CBDF80

#endif

2

最后,再减去终止异常所用到的堆栈大小,即12个字节。

现在r0的值为:

#ifdef CONFIG_USE_IRQ

r0

= r0 – 12

= 33CBDF80 - 12

= 33CBDF74

#else

r0

= r0 – 12

= 33CBFF80 - 12

= 33CBFF74

#endif

然后将r0的值赋值给sp,即堆栈指针。

关于:

sp代表stack pointer,堆栈指针;

和后面要提到的ip寄存器:

ip代表instruction pointer,指令指针。

更多详情参见下面的解释。

关于ARM的寄存器的别名和相关的APCS,参见本文后面的内容:第 3.5 节 “AMR寄存器的别名 + APCS”

1.4.3. bl clock_init

	bl clock_init1
        

1

在上面,经过计算,算出了堆栈的地址,然后赋值给了sp,此处,接着才去调用函数clock_init去初始化时钟。

其中此函数是在C文件:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\boot_init.c

中:

void clock_init(void)
{
...设置系统时钟clock的相关代码...
}
                

看到这里,让我想起,关于其他人的关于此start.S代码解释中说到的,此处是先去设置好堆栈,即初始化sp指针,然后才去调用C语言的函数clock_init的。

而我们可以看到,前面那行代码:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif
                

就不需要先设置好堆栈,再去进行函数调用。

其中cpu_init_crit对应的代码也在start.S中(详见后面对应部分的代码),是用汇编实现的。

而对于C语言,为何就需要堆栈,而汇编却不需要堆栈的原因,请参见本文后面的内容:第 3.6 节 “为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈”

1.4.4. adr

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:				/* relocate U-Boot to RAM	    */
	adr1	r0, _start		/* r0 <- current position of code   */
        

1

adr指令的语法和含义:

http://blog.mcuol.com/User/cdkfGao/article/8057_1.htm

1、ADR伪指令--- 小范围的地址读取

ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。

在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。

ADR伪指令格式 :ADR{cond} register, expr

地址表达式expr的取值范围:

当地址值是字节对齐时,其取指范围为: +255 ~ 255B;

当地址值是字对齐时,其取指范围为: -1020 ~ 1020B;

所以,上述:

adr r0, _start

的意思其实很简单,就是将_start的地址赋值给r0.但是具体实现的方式就有点复杂了,对于用adr指令实现的话,说明_start这个地址,相对当前PC的偏移,应该是很小的,意思就是向前一段后者向后一段去找,肯定能找到_start这个标号地址的,此处,自己通过看代码也可以看到_start,就是在当前指令的前面,距离很近,编译后,对应汇编代码,也可以猜得出,应该是上面所说的,用sub来实现,即当前PC减去某个值,得到_start的值,

参照前面介绍的内容,去:

arm-inux-objdump –d u-boot > dump_u-boot.txt

然后打开dump_u-boot.txt,可以找到对应的汇编代码,如下:

33d00000 <_start>:
33d00000:	ea000014 	b	33d00058 <reset>
。。。
33d000a4 <relocate>:
33d000a4:	e24f00ac 	sub	r0, pc, #172	; 0xac
                

可以看到,这个相对当前PC的距离是0xac=172,细心的读者可以看到,那条指令的地址减去0xac,却并不等于_start的值,即

33d000a4 - 33d00000 = 0xa4 != 0xac

而0xac – 0xa4 = 8,

那是因为,由于ARM920T的五级流水线的缘故导致指令执行那一时刻的PC的值等于该条指令PC的值加上8,即

sub r0, pc, #172中的PC的值是

sub r0, pc, #172

指令地址:33d000a4,再加上8,即33d000a4+8 = 33d000ac,

所以,33d000ac – 0xac,才等于我们看到的33d00000,才是_start的地址。

这个由于流水线导致的PC的值和当前指令地址不同的现象,就是我们常说的,ARM中,PC=PC+8。

对于为何是PC=PC+8,请参见后面的内容:第 3.4 节 “为何ARM7中PC=PC+8”

对于此处为何不直接用mov指令,却使用adr指令,请参见后面内容:第 3.7 节 “关于为何不直接用mov指令,而非要用adr伪指令”

对于mov指令的操作数的取值范围,请参见后面内容:第 3.8 节 “mov指令的操作数的取值范围到底是多少”

adr	r0, _start

的伪代码,被翻译成实际汇编代码为:

33d000a4:	e24f00ac 	sub	r0, pc, #172	; 0xac

其含义就是,通过计算PC+8-172 ⇒ _start的地址,

而_start的地址,即相对代码段的0地址,是这个地址在运行时刻的值,而当ARM920T加电启动后,,此处是从Nor Flash启动,对应的代码,也是在Nor Flash中,对应的物理地址是0x0,所以,此时_start的值就是0,而不是0x33d00000。

所以,此时:

r0 = 0x0

1.4.5. clear_bss

	ldr	r1, _TEXT_BASE1		/* test if we run from flash or RAM */
	2cmp     r0, r1                  /* don't reloc during debug         */
	3beq     clear_bss
        

1

这里的_TEXT_BASE的含义,前面已经说过了,那就是:

_TEXT_BASE:
	.word	TEXT_BASE
                    

得知,地址为_TEXT_BASE的内存中的内容,就是

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

中的:

TEXT_BASE = 0x33D00000

所以,此处就是

r1 = 0x33D00000

2

含义很简单,就是比较r0和r1。而

r0 = 0x0

r1 = 0x33D00000

所以不相等

3

因此beq发现两者不相等,就不会去跳转到clear_bss,不会去执行对应的将bss段清零的动作了。

1.4.6. cal armboot size from _armboot_start

	1ldr	r2, _armboot_start
	ldr	r3, _bss_start
	2sub	r2, r3, r2		/* r2 <- size of armboot            */
        

1

这两行代码意思也很清楚,分别装载_armboot_start和_bss_start地址中的值,赋值给r2和r3

而_armboot_start和_bss_start的值,前面都已经提到过了,就是:

.globl _armboot_start
_armboot_start:
	.word _start

.globl _bss_start
_bss_start:
	.word __bss_start
                
_TEXT_BASE:
	.word	TEXT_BASE
                    

而其中的_start,是我们uboot的代码的最开始的位置,而__bss_start的值,是在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\u-boot.lds

中的:

SECTIONS
{
	. = 0x00000000;

	. = ALIGN(4);
	.text      :
...
	. = ALIGN(4);
	.rodata : { *(.rodata) }

	. = ALIGN(4);
	.data : { *(.data) }

...
	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) }
	_end = .;
}
                

所以,可以看出,__bss_start的位置,是bss的start开始位置,同时也是text+rodata+data的结束位置,即代码段,只读数据和已初始化的可写的数据的最末尾的位置。

其实我们也可以通过前面的方法,objdump出来,看到对应的值:

33d00048 <_bss_start>:
33d00048:	33d339d4 	.word	0x33d339d4
                

是0x33d339d4。

[注意] 注意

【总结】

r2 = _start = 0x33d00000

r3 =__bss_start = 0x33d339d4

2

此处的意思就很清楚了,就是r2 = r3-r2,计算出

text + rodata + data

的大小,即整个需要载入的数据量是多少,用于下面的函数去拷贝这么多的数据到对应的内存的位置。

这里的实际的值是

r2

= r3 –r2

= 0x33d339d4 - 0x33d00000

= 0x000339d4

[注意] 注意

【总结】

到此刻位置,假定是从Nor Flash启动的话:

r0 = 0x0 = 我们代码此刻所在的位置

r1 = 0x33D00000 = 我们想要把我们的代码放到哪里

r2 = 0x000339d4 = 对应的代码的大小(此处的代码 = text + rodata + data)

1.4.7. cal armboot size from CopyCode2Ram

#if 1
	1bl  CopyCode2Ram		/* r0: source, r1: dest, r2: size */
#else
	add	r2, r0, r2		/* r2 <- source end address         */

copy_loop:
	ldmia	r0!, {r3-r10}		/* copy from source address [r0]    */
	stmia	r1!, {r3-r10}		/* copy to   target address [r1]    */
	cmp	r0, r2			/* until source end addreee [r2]    */
	ble	copy_loop
#endif
#endif	/* CONFIG_SKIP_RELOCATE_UBOOT */
        

1

此处,代码很简单,只是注释掉了原先的那些代码,而单纯的只是去调用CopyCode2Ram这个函数。

CopyCode2Ram函数,前面也提到过了,是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\boot_init.c

中:

int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size)
{
	unsigned int *pdwDest;
	unsigned int *pdwSrc;
	int i;

	if (bBootFrmNORFlash())
	{
		pdwDest = (unsigned int *)buf;
		pdwSrc  = (unsigned int *)start_addr;
		/* 从 NOR Flash启动 */
		for (i = 0; i < size / 4; i++)
		{
			pdwDest[i] = pdwSrc[i];
		}
		return 0;
	}
	else
	{
		/* 初始化NAND Flash */
		nand_init_ll();

		/* 从 NAND Flash启动 */
		if (NF_ReadID() == 0x76 )
			nand_read_ll(buf, start_addr, (size + NAND_BLOCK_MASK)&~(NAND_BLOCK_MASK));
		else
			nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP));
		return 0;
	}
}
                

可以看到,其有三个参数,start_addr,*buf和size,这三个参数,分别正好对应着我们刚才所总结的r0,r1和r2.

这些寄存器和参数的对应关系,也是APSC中定义的:

实际参数

APCS 没有定义记录、数组、和类似的格局。这样语言可以自由的定义如何进行这些活动。但是,如果你自己的实现实际上不符合 APCS 的精神,那么将不允许来自你的编译器的代码与来自其他编译器的代码连接在一起。典型的,使用 C 语言的惯例。

  • 前4个整数实参(或者更少!)被装载到 a1 - a4
  • 前 4 个浮点实参(或者更少!)被装载到 f0 - f3
  • 其他任何实参(如果有的话)存储在内存中,用进入函数时紧接在 sp 的值上面的字来指向。换句话说,其余的参数被压入栈顶。所以要想简单。最好定义接受 4 个或更少的参数的函数

上面说的a1-a4,就是寄存器r0-r3。

而CopyCode2Ram函数的逻辑也很清晰,就是先去判断是从Nor Flash启动还是从Nand Flash启动,然后决定从哪里拷贝所需要的代码到对应的目标地址中。