1.5. 清除bss段

1.5.1. clear_bss

clear_bss:1
	ldr	r0, _bss_start2		/* find start of bss segment        */
	ldr	r1, _bss_end3		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */
        

2

此处的_bss_start是:

.globl _bss_start
_bss_start:
	.word __bss_start
                

3

而_bss_end,是:

.globl _bss_end
_bss_end:
	.word _end
                

1

对应的,__bss_start和_end,都在前面提到过的那个链接脚本里面:

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

中的:

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

即bss段的起始地址和结束地址。

1.5.2. clear css loop

1clbss_l:str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l
        

1

此段代码含义也很清晰,那就是,

先将r2,即0x0,存到地址为r0的内存中去,然后r0地址加上4,比较r0地址和r1地址,即比较当前地址是否到了bss段的结束位置,如果le,little or equal,小于或等于,那么就跳到clbss_l,即接着这几个步骤,直到地址超过了bss的_end位置,即实现了将整个bss段,都清零。

1.5.3. ldr pc

#if 01
	/* try doing this stuff after the relocation */
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMR
	str	r1, [r0]

	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
	/* END stuff after relocation */
#endif

	ldr	pc, _start_armboot
2
_start_armboot:	.word start_armboot
        

1

此处忽略已经注释掉的代码

2

最后的那两行,意思也很简单,那就是将地址为_start_armboot中的内容,即

start_armboot,赋值给PC,即调用start_armboot函数。

至此,汇编语言的start.S的整个工作,就完成了。

而start_armboot函数,在C文件中:

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

中:

void start_armboot (void)
{
    ......
}
                

这就是传说中的,调用第二层次,即C语言级别的初始化了,去初始化各个设备了。

其中包括了CPU,内存等,以及串口,正常初始化后,就可以从串口看到uboot的打印信息了。

1.5.4. cpu_init_crit

/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0
	mcr1	p15, 0, r0, c7, c7, 0	/* flush v3/v42 cache */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */3
        

1

关于mcr的来龙去脉:

http://apps.hi.baidu.com/share/detail/32319228

ARM 微处理器可支持多达 16 个协处理器,用于各种协处理操作,在程序执行的过程中,每个协处理器只执行针对自身的协处理指令,忽略 ARM 处理器和其他协处理器的指令。ARM 的协处理器指令主要用于 ARM 处理器初始化 ARM 协处理器的数据处理操作,以及在ARM 处理器的寄存器和协处理器的寄存器之间传送数据,和在 ARM 协处理器的寄存器和存储器之间传送数据。 ARM 协处理器指令包括以下 5 条:

  1. CDP 协处理器数操作指令
  2. LDC 协处理器数据加载指令
  3. STC 协处理器数据存储指令
  4. MCR ARM 处理器寄存器到协处理器寄存器的数据传送指令
  5. MRC 协处理器寄存器到ARM 处理器寄存器的数据传送指令

......

CP15系统控制协处理器

CP15 —系统控制协处理器 (the system control coprocessor)他通过协处理器指令MCR和MRC提供具体的寄存器来配置和控制caches、MMU、保护系统、配置时钟模式(在bootloader时钟初始化用到)

CP15的寄存器只能被MRC和MCR(Move to Coprocessor from ARM Register )指令访问

一些要说明的内容,见下::

http://infocenter.arm.com/help/topic/com.arm.doc.ddi0151c/ARM920T_TRM1_S.pdf

you can only access CP15 registers with MRC and MCR instructions in a privileged mode. The assembler for these instructions is:

MCR/MRC{cond} P15,opcode_1,Rd,CRn,CRm,opcode_2

The CRn field of MRC and MCR

instructions specifies the coprocessor register to access. The CRm field and opcode_2 fields specify a particular action when addressing registers. The L bit distinguishes between an MRC (L=1) and an MCR (L=0).

Note:

Attempting to read from a nonreadable register, or to write to a nonwritable register causes unpredictable results.

The opcode_1, opcode_2, and CRm fields should be zero, except when the values specified are used to select the desired operations, in all instructions that access CP15.

Using other values results in unpredictable behavior

CP15有很多个寄存器,分别叫做寄存器0(Register 0),到寄存器15(Register 15),

每个寄存器分别控制不同的功能,而且有的是只读,有的是只写,有的是可读写。

而且这些寄存器的含义,随着版本ARM内核版本变化而不断扩展,详情请参考:Processor setup via co-processor 15 and about co-processors

其中,根据我们此处关心的内容,摘录部分内容如下:

ARM 710

  • Register 7 - IDC flush (write only)

    Any data written to this location will cause the IDC (Instruction/Data cache) to be flushed.

......

StrongARM SA110

......

  • Register 7 - Cache control (write only)

    Any data written to this location will cause the selected cache to be flushed.

    The OPC_2 and CRm co-processor fields select which cache

    operation should occur:

    Function OPC_2 CRm Data

    Flush I + D %0000 %0111 -

    Flush I %0000 %0101 -

    Flush D %0000 %0110 -

    Flush D single %0001 %0110 Virtual address

    Clean D entry %0001 %1010 Virtual address

    Drain write buf. %0100 %1010 -

  • Register 8 - TLB operations (write only)

    Any data written to this location will cause the selected TLB flush operation.

    The OPC_2 and CRm co-processor fields select which cache

    operation should occur:

    Function OPC_2 CRm Data

    Flush I + D %0000 %0111 -

    Flush I %0000 %0101 -

    Flush D %0000 %0110 -

    Flush D single %0001 %0110 Virtual address”

而MCR的详细的语法为:

MCR指令

MCR指令将ARM处理器的寄存器中的数据传送到协处理器寄存器中。如果协处理器不能成功地执行该操作,将产生未定义的指令异常中断。

指令语法格式

MCR{<cond>} <p>,< opcode_1>,<Rd>,<CRn>,<CRm>{,<opcode_2>}

MCR{<cond>} p15,0,<Rd>,<CRn>,<CRm>{,<opcode_2>}

其中

  • <cond>

    指令执行的条件码.当<cond>忽略时指令为无条件执行。

  • <opcode_1>

    协处理器将执行的操作的操作码。对于CP15协处理器来说,<opcode_1>永远为0b000,当<opcode_1>不为0b000时,该指令操作结果不可预知。

  • <Rd>

    作为源寄存器的ARM寄存器,其值将被传送到协处理器寄存器中

  • <CRn>

    作为目标寄存器的协处理器寄存器,其编号可能是C0,C1,…,C15。

<CRm>和<opcode_2>两者组合决定对协处理器寄存器进行所需要的操作,如果没有指定,则将为<CRm>为C0,opcode_2为0

对照上面的那行代码:

mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */

可以看出,其中

rd为r0=0

CRn为C7

CRm为C7

对于这行代码的作用,以此按照语法,来一点点解释如下:

首先,mcr做的事情,其实很简单,就是“ARM处理器的寄存器中的数据传送到协处理器寄存器中”,

此处即是,将ARM的寄存器r0中的数据,此时r0=0,所以就是把0这个数据,传送到协处理器CP15中。

而对应就是写入到“<CRn>”这个“目标寄存器的协处理器寄存器”,此处CRn为C7,即将0写入到寄存器7(Register 7)中去。

而上面关于Register 7的含义中也说了,“Any data written to this location will cause the selected cache to be flushed”,即你往这个寄存器7中写入任何数据,都会导致对应的缓存被清空。而到底那个缓存被清空呢,即我们这行指令

mcr	p15, 0, r0, c7, c7, 0

起了什么作用呢

那是由“<CRm>和<opcode_2>两者组合决定”的。

而此处CRm为C7,opcode_2为0,而对于C7和0的组合的作用,参见上面的那个表中Register 7中的Flash I+D那一行,

当opcode_2为0,CRm为0111=7,就是我们要找的,其作用是“Flush I + D”,即清空指令缓存I Cache和数据缓存D Cache。

根据该表,同理,如果是opcode_2=0,而CRm=0101b=5,那么对应的就是去“Flush I”,即只清除指令缓存I Cache了。

而对应的指令也就是

mcr	p15, 0, r0, c7, c5, 0

了。

2

此注释说此行代码的作用是,清理v3或v4的缓存

其中v4,我们很好理解,因为我们此处的CPU是ARM920T的核心,是属于ARM V4的,而为何又说,也可以清除v3的cache呢?

那是因为,本身这些寄存器位域的定义,都是向下兼容的,参见上面引用的内容,也写到了:

ARM 710

  • Register 7 - IDC flush (write only)

    Any data written to this location will cause the IDC (Instruction/Data cache) to be flushed.

即,对于ARM7的话,你写同样的这行代码

mcr	p15, 0, r0, c7, c7, 0

也还是向register 7中写入了数据0,这也同样满足了其所说的“Any data written to this location”,也会产生同样的效果“cause the IDC (Instruction/Data cache) to be flushed”。

3

同理,可以看出此行是去操作寄存器8,而对应的各个参数为:

rd为r0=0

CRn为C8

CRm为C7

opcode_2为0

对照寄存器8的表:

  • Register 8 - TLB operations (write only)

    Any data written to this location will cause the selected TLB flush operation.

    The OPC_2 and CRm co-processor fields select which cache

    operation should occur:

    Function OPC_2 CRm Data

    Flush I + D %0000 %0111 -

    Flush I %0000 %0101 -

    Flush D %0000 %0110 -

    Flush D single %0001 %0110 Virtual address”

其含义为:

向寄存器8中写入数据,会导致对应的TLB被清空。具体是哪个TLB,由opcode_2和CRm组合决定,

此处opcode_2为0,CRm为7=0111b,所以对应的作用是“Flush I + D”,即清空指令和数据的TLB。

[提示] 提示

上述两行代码,其实都可以ARM的官方网站上面找到:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdcfejb.html

Function Rd Instruction
Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdifbjc.html

Function Rd Instruction
Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0

1.5.5. disable MMU

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 01
        

1

此处,对应的值为:

rd为r0=0

CRn为C1

CRm为C0

opcode_2为0

即,此行代码是将r0的值,即0,写入到CP15的寄存器1中。

寄存器1的相关的定义为:

http://www.heyrick.co.uk/assembler/coprocmnd.html

StrongARM SA110

  • Register 1 - Control (read/write)

    All values set to 0 at power-up.

    • Bit 0 - On-chip MMU turned off (0) or on (1)
    • Bit 1 - Address alignment fault disabled (0) or enabled (1)
    • Bit 2 - Data cache turned off (0) or on (1)
    • Bit 3 - Write buffer turned off (0) or on (1)
    • Bit 7 - Little-endian operation if 0, big-endian if 1
    • Bit 8 - System bit - controls the MMU permission system
    • Bit 9 - ROM bit - controls the MMU permission system
    • Bit 12 - Instruction cache turned off (0) or on (1)”

所以,对应内容就是,向bit[CRm]中写入opcode_2,即向bit[0]写入0,对应的作用为“On-chip MMU turned off”,即关闭MMU。

1.5.6. clear bits

	1bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)2
	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)3
	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align4
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache5
	mcr	p15, 0, r0, c1, c0, 06
        

1

此处几行代码,注释中写的也很清楚了,就是去清楚对应的位和设置对应的位,具体位域的含义见下:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdifbjc.html

表 1.6. 控制寄存器1的位域含义

Register bits Name Function Value
31 iA bit Asynchronous clock select See Table 2.11
30 nF bit notFastBus select See Table 2.11
29:15 - Reserved

Read = Unpredictable
Write = Should be zero

14 RR bit Round robin replacement

0 = Random replacement
1 = Round-robin replacement

13 V bit Base location of exception registers

0 = Low addresses = 0x00000000
1 = High addresses = 0xFFFF0000

12 I bit ICache enable

0 = ICache disabled
1 = ICache enabled

11:10 - Reserved

Read = 00
Write = 00

9 R bit ROM protection This bit modifies the MMU protection system. See Domain access control
8 S bit System protection This bit modifies the MMU protection system. See Domain access control
7 B bit Endianness

0 = Little-endian operation
1 = Big-endian operation

6:3 - Reserved

Read = 1111
Write = 1111

2 C bit DCache enable

0 = DCache disabled
1 = DCache enabled

1 A bit Alignment fault enable Data address alignment fault checking

0 = Fault checking disabled
1 = Fault checking enabled

0 M bit MMU enable

0 = MMU disabled
1 = MMU enabled


表 1.7. 时钟模式

Clocking mode iA nF
FastBus mode 0 0
Synchronous 0 1
Reserved 1 0
Asynchronous 1 1

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0151c/I273867.html

Domain access control

表 1.8. 关于访问控制位在域访问控制寄存器中的含义

Value Meaning Description
00 No access Any access generates a domain fault
01 Client Accesses are checked against the access permission bits in the section or page descriptor
10 Reserved Reserved. Currently behaves like the no access mode
11 Manager Accesses are not checked against the access permission bits so a permission fault cannot be generated

表 1.9 “关于访问允许(AP)位的含义”shows how to interpret the Access Permission (AP) bits and how their interpretation is dependent on the S and R bits (control register bits 8 and 9)

表 1.9. 关于访问允许(AP)位的含义

AP S R Supervisor permissions User permissions Description
00 0 0 No access No access Any access generates a permission fault
00 1 0 Read-only No access Only Supervisor read permitted
00 0 1 Read-only Read-only Any write generates a permission fault
00 1 1 Reserved - -
01 x x Read/write No access Access allowed only in Supervisor mode
10 x x Read/write Read-only Writes in User mode cause permission fault
11 x x Read/write Read/write All access types permitted in both modes
xx 1 1 Reserved -  

2

此行作用是:

  1. 清除bit[13]

    Base location of exception register(异常寄存器基地址)

    0 = Low address = 0x0000 0000

  2. 清除bit[9]和bit[8]

    此处不是很懂,待后续深入了解。

    目前的理解是:

    不论是Supervisor还是user,谁都不能访问,否则就出现权限错误“Any access generates a permission fault”

3

此行作用是:

  1. 清除bit[7]

    使用little endian

  2. 清除bit[2-0]

    DCache disabled,关闭Dcache;

    Alignment Fault checking disabled,关闭地址对齐的错误检查;

    MMU disabled,关闭MMU。

4

此行作用是:

  1. 设置bit[1]

    “Enable Data address alignment fault checking”打开数据地址对齐的错误检查,即如果数据地址为非法(奇数?)地址,就报错。

5

此行作用是:

  1. 设置bit[12]

    开启指令缓存I cache。

6

mcr指令,将刚才设置的r0的值,再写入到寄存器1中。

1.5.7. bl lowlevel_init

	/*
	 * before relocating, we have to setup RAM timing
	 * because memory timing is board-dependend, you will
	 * find a lowlevel_init.S in your board directory.
	 */
	mov	ip, lr
	bl	lowlevel_init
	mov	lr, ip1
	mov	pc, lr2
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */3
        

1

将lr的值给ip,即指令指针r12,此处之所以要保存一下lr是因为此处是在子函数cpu_init_crit中,lr已经保存了待会用于返回主函数的地址,即上次调用时候的pc的值,而此处如果在子函数cpu_init_crit中继续调用其他子函数lowlevel_init,而不保存lr的话,那么调用完lowlevel_init返回来时候,就丢失了cpu_init_crit要返回的位置。

说白了就是,每次你要调用函数之前,你自己要确保是否已经正确保存了lr的值,要保证函数调用完毕后,也能正常返回。当然,如果你此处根本不需要返回,那么就不用去保存lr的值了。

2

典型的子函数调用,通过将lr的值赋值给pc,实现函数调用完成后而返回的。

3

这里,其是和前面的代码:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif
                

是对应的。