第 1 章 start.S详解

目录

1.1. 设置CPU模式
1.1.1. globl
1.1.2. _start
1.1.3. ldr
1.1.4. .word
1.1.5. .balignl
1.1.6. _TEXT_BASE _armboot_start
1.1.7. _bss_start _bss_end
1.1.8. FREE_RAM_END FREE_RAM_SIZE
1.1.9. IRQ_STACK_START FIQ_STACK_START
1.1.10. cpsr
1.1.11. bic
1.1.12. orr
1.1.13. msr
1.2. 关闭看门狗
1.2.1. pWTCON INTMOD INTMSK INTSUBMSK CLKDIVN
1.2.2. ldr pWTCON
1.2.3. mov
1.2.4. str
1.3. 关闭中断
1.3.1. set INTMSK
1.3.2. set INTSUBMSK
1.3.3. set CLKDIVN
1.3.4. bl
1.4. 设置堆栈sp指针
1.4.1. stack_setup
1.4.2. calc stack
1.4.3. bl clock_init
1.4.4. adr
1.4.5. clear_bss
1.4.6. cal armboot size from _armboot_start
1.4.7. cal armboot size from CopyCode2Ram
1.5. 清除bss段
1.5.1. clear_bss
1.5.2. clear css loop
1.5.3. ldr pc
1.5.4. cpu_init_crit
1.5.5. disable MMU
1.5.6. clear bits
1.5.7. bl lowlevel_init
1.6. 异常中断处理
1.6.1. macros stmia
1.6.2. cal reg value and store
1.6.3. irq_save_user_regs irq_restore_user_regs
1.6.4. exception handlers
1.6.5. Launch
1.6.6. int_return

摘要

下面将详细解释uboot中的start.S中的每一行代码。详细到,每个指令的语法和含义,都进行详细讲解,使得此文读者可以真正搞懂具体的含义,即what。

以及对于一些相关的问题,深入探究为何要这么做,即why。

对于uboot的start.S,主要做的事情就是系统的各个方面的初始化。

从大的方面分,可以分成这几个部分:

下面来对start.S进行详细分析,看看每一个部分,是如何实现的。

1.1. 设置CPU模式

1.1.1. globl

/*
 *  armboot - Startup Code for ARM920 CPU-core
 *
 *  Copyright (c) 2001	Marius Gr鰃er <[email protected]>
 *  Copyright (c) 2002	Alex Z黳ke <[email protected]>
 *  Copyright (c) 2002	Gary Jennejohn <[email protected]>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <config.h>
#include <version.h>


/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


.globl1 _start
        

1

globl是个关键字,对应含义为:

http://re-eject.gbadev.org/files/GasARMRef.pdf

表 1.1. global的语法

Directive Description Syntax Example
.global Makes symbol visible to the linker .global symbol .global MyAsmFunc
.globl Same as .global .globl symbol .globl MyOtherAsmFunc

所以,意思很简单,就是相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问

所以,你可以看到

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

中,有用到此变量:

ENTRY(_start)

即指定入口为_start,而由下面的_start的含义可以得知,_start就是整个start.S的最开始,即整个uboot的代码的开始。

1.1.2. _start

_start1:	b       reset
        

1

_start后面加上一个冒号’:’,表示其是一个标号Label,类似于C语言goto后面的标号。

而同时,_start的值,也就是这个代码的位置了,此处即为代码的最开始,相对的0的位置。

而此处最开始的相对的0位置,在程序开始运行的时候,如果是从NorFlash启动,那么其地址是0,

_stat=0

如果是重新relocate代码之后,就是我们定义的值了,即,在

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

中的:

TEXT_BASE = 0x33D00000

表示是代码段的基地址,即

_start=TEXT_BASE=0x33D00000

关于标号的语法解释:

http://sourceware.org/binutils/docs-2.20/as/Labels.html#Labels

A label is written as a symbol immediately followed by a colon `:'. The symbol then represents the current value of the active location counter, and is, for example, a suitable instruction operand. You are warned if you use the same symbol to represent two different locations: the first definition overrides any other definitions.

而_start标号后面的:

b       reset

就是跳转到对应的标号为reset的位置。

1.1.3. ldr

	ldr1	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
        

1

ldr命令的语法为:

http://wenku.baidu.com/view/f7cc280102020740be1e9bea.html

LDR指令的格式为:

LDR{条件} 目的寄存器,<存储器地址>

LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,且寻址方式灵活多样,请读者认真掌握。

指令示例:

LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0。

LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。

LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。

LDR R0,[R1,R2]! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。

LDR R0,[R1,#8]! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。

LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。

LDR R0,[R1,R2,LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。

LDRR0,[R1],R2,LSL#2 ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。”

http://www.pczpg.com/a/2010/0607/11062.html

ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令。

比如想把数据从内存中某处读取到寄存器中,只能使用ldr

比如:

ldr r0, 0x12345678

就是把0x12345678这个地址中的值存放到r0中。

上面那些ldr的作用,以第一个_undefined_instruction为例,就是将地址为_undefined_instruction中的一个word的值,赋值给pc。

1.1.4. .word

_undefined_instruction:	.word1 undefined_instruction
_software_interrupt:	.word software_interrupt
_prefetch_abort:	.word prefetch_abort
_data_abort:		.word data_abort
_not_used:		.word not_used
_irq:			.word irq
_fiq:			.word fiq
        

1

http://blogold.chinaunix.net/u3/115924/showart_2280163.html

.word .word expr {,expr}… 分配一段字内存单元,并用expr初始化字内存单元(32bit)

http://re-eject.gbadev.org/files/GasARMRef.pdf

表 1.2. .word的语法

Directive Description Syntax Example
.word Define word expr (32bit numbers) .word expr {, …} .word 144511, 0x11223

所以上面的含义,以_undefined_instruction为例,就是,此处分配了一个word=32bit=4字节的地址空间,里面存放的值是undefined_instruction。

而此处_undefined_instruction也就是该地址空间的地址了。用C语言来表达就是:

_undefined_instruction = &undefined_instruction

*_undefined_instruction = undefined_instruction

在后面的代码,我们可以看到,undefined_instruction也是一个标号,即一个地址值,对应着就是在发生“未定义指令”的时候,系统所要去执行的代码。

(其他几个对应的“软件中断”,“预取指错误”,“数据错误”,“未定义”,“(普通)中断”,“快速中断”,也是同样的做法,跳转到对应的位置执行对应的代码。)

所以:

ldr pc, 标号1
......
标号1:.word 标号2
......
标号2:
......(具体要执行的代码)
                

的意思就是,将地址为标号1中内容载入到pc,而地址为标号1中的内容,正好装的是标号2。

用C语言表达其实很简单:

PC = *(标号1) = 标号2

对PC赋值,即是实现代码跳转,所以整个这段汇编代码的意思就是:

跳转到标号2的位置,执行对应的代码。

1.1.5. .balignl

	.balignl1 16,0xdeadbeef
        

1

balignl这个标号的语法及含义:

http://re-eject.gbadev.org/files/GasARMRef.pdf

表 1.3. balignl的语法

Directive Description Syntax Example
.balignl Word align the following code to alignment byte boundary (default=4). Fill skipped words with fill (default=0 or NOP). If the number of bytes skipped is greater than max, then don't align (default=alignment ). .balignl {alignment} {, fill} {, max} .balignl

所以意思就是,接下来的代码,都要16字节对齐,不足之处,用0xdeadbeef填充。

其中关于所要填充的内容0xdeadbeef,刚开始没看懂是啥意思,后来终于搞懂了。

经过(等)多位网友提示和纠正,觉得这样解释会更加合理些:

此处0xdeadbeef本身没有真正的意义,但是很明显,字面上的意思是,(坏)死的牛肉。

虽然其本身没有实际意义,但是其是在十六进制下,能表示出来的,为数不多的,可读的单词之一了。

另外一个相对常见的是:0xbadc0de,意思是bad code,坏的代码,注意其中的o是0,因为十六进制中是没有o的。

这些“单词”,相对的作用是,使得读代码的人,以及在查看程序运行结果时,容易看懂,便于引起注意。

而关于自己之前,随意杜撰出来的,希望起到搞笑作用,表示good beef(好的牛肉)的0xgoodbeef,实际上,在十六进制下,会出错的,因为十六进制下没有o和 g这两个字母。

1.1.6. _TEXT_BASE _armboot_start

/*
 *************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */

_TEXT_BASE1:
	.word	TEXT_BASE

2.globl _armboot_start
_armboot_start:
	.word _start
        

1

此处和上面的类似,_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

2

同理,此含义可用C语言表示为:

*(_armboot_start) = _start

1.1.7. _bss_start _bss_end

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
	.word __bss_start
1
.globl _bss_end
_bss_end:
	.word _end
        

1

关于_bss_start和_bss_end都只是两个标号,对应着此处的地址。

而两个地址里面分别存放的值是__bss_start和_end,这两个的值,根据注释所说,是定义在开发板相关的链接脚本里面的,我们此处的开发板相关的链接脚本是:

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

其中可以找到__bss_start和_end的定义:

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

而关于_bss_start和_bss_end定义为.glogl即全局变量,是因为uboot的其他源码中要用到这两个变量,详情请自己去搜索源码。

1.1.8. FREE_RAM_END FREE_RAM_SIZE

.globl FREE_RAM_END
FREE_RAM_END:
	.word	0x0badc0de
1
.globl FREE_RAM_SIZE
FREE_RAM_SIZE:
	.word	0x0badc0de
        

1

关于FREE_RAM_END和FREE_RAM_SIZE,这里只是两个标号,之所以也是声明为全局变量,是因为uboot的源码中会用到这两个变量。

但是这里有点特别的是,这两个变量,将在本源码start.S中的后面要用到,而在后面用到这两个变量之前,uboot的C源码中,会先去修改这两个值,具体的逻辑是:

本文件start.S中,后面有这两句:

	ldr	pc, _start_armboot

_start_armboot:	.word start_armboot
                

意思很明显,就是去调用start_armboot函数。

而start_armboot函数是在:

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

中:

init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
......
	NULL,
};

void start_armboot (void)
{
	init_fnc_t **init_fnc_ptr;
......

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
......

}
                

即在start_armboot去调用了cpu_init。

cpu_init函数是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\cpu.c

中:

cpu_init源码. 

int cpu_init (void)
{
    /*
     * setup up stacks if necessary
     */
#ifdef CONFIG_USE_IRQ
    IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
    FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE;
    FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1;
#else    
    FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE;
    FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1;
#endif
    return 0;
}
                    

在cpu_init中,根据我们的一些定义,比如堆栈大小等等,去修改了IRQ_STACK_START ,FIQ_STACK_START ,FREE_RAM_END和FREE_RAM_SIZE的值。

至于为何这么修改,后面遇到的时候会具体再解释。

1.1.9. IRQ_STACK_START FIQ_STACK_START

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
	.word	0x0badc0de
1
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de
#endif
        

1

同上,IRQ_STACK_START和FIQ_STACK_START,也是在cpu_init中用到了。

不过此处,是只有当定义了宏CONFIG_USE_IRQ的时候,才用到这两个变量,其含义也很明显,

只有用到了中断IRQ,才会用到中断的堆栈,才有中端堆栈的起始地址。

快速中断FIQ,同理。

1.1.10. cpsr

/*
 * the actual reset code
 */

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs1	r0,cpsr2
        

2

CPSR 是当前的程序状态寄存器(Current Program Status Register),

而 SPSR 是保存的程序状态寄存器(Saved Program Status Register)。

具体细节,可参考:

表 1.4. CPSR Bitfield

31 30 29 28 --- 7 6 - 4 3 2 1 0 说明
N Z C V   I F   M4 M3 M2 M1 M0  
  0 0 0 0 0 User26 模式
  0 0 0 0 1 FIQ26 模式
  0 0 0 1 0 IRQ26 模式
  0 0 0 1 1 SVC26 模式
  1 0 0 0 0 User 模式
  1 0 0 0 1 FIQ 模式
  1 0 0 1 0 IRQ 模式
  1 0 0 1 1 SVC 模式
  1 0 1 1 1 ABT 模式
  1 1 0 1 1 UND 模式

1

MRS - Move From Status Register

MRS指令的语法为:

四、程序状态寄存器访问指令

1、 MRS指令

MRS指令的格式为:

MRS{条件} 通用寄存器,程序状态寄存器(CPSR或SPSR)

MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:

Ⅰ.当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。

Ⅱ.当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。

指令示例:

MRS R0,CPSR ;传送CPSR的内容到R0

MRS R0,SPSR ;传送SPSR的内容到R0”

所以,上述汇编代码含义为,将CPSR的值赋给R0寄存器。

1.1.11. bic

	bic1	r0,r0,#0x1f
        

1

bic指令的语法是:

16、BIC指令

BIC指令的格式为:

BIC{条件}{S} 目的寄存器,操作数1,操作数2

BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,

操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。操作数2为32位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。

而0x1f=11111b

所以,此行代码的含义就是,清除r0的bit[4:0]位。

1.1.12. orr

	orr1	r0,r0,#0xd3
        

1

orr指令的语法是:

14、ORR指令

ORR指令的格式为:

ORR{条件}{S} 目的寄存器,操作数1,操作数2

ORR指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数1的某些位。

指令示例:

ORR R0,R0,#3 ; 该指令设置R0的0、1位,其余位保持不变。

所以此行汇编代码的含义为:

而0xd3=1101 0111[4:0]位。

将r0与0xd3算数或运算,然后将结果给r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置为1。

1.1.13. msr

	msr1	cpsr,r0
        

1

MSR - Move to Status Register

msr的指令格式是:

四、程序状态寄存器访问指令

......

2、 MSR指令

MSR指令的格式为:

MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数

MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器可分为4个域:

位[31:24]为条件标志位域,用f表示;

位[23:16]为状态位域,用s表示;

位[15:8]为扩展位域,用x表示;

位[7:0]为控制位域,用c表示;

该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。

指令示例:

MSR CPSR,R0 ;传送R0的内容到CPSR

MSR SPSR,R0 ;传送R0的内容到SPSR

MSR CPSR_c,R0 ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域

此行汇编代码含义为,将r0的值赋给CPSR。

所以,上面四行汇编代码的含义就很清楚了。

先是把CPSR的值放到r0寄存器中,然后清除bit[4:0],然后再或上

0xd3=11   0  10111b

表 1.5. CPSR=0xD3的位域及含义

CPSR位域 7 6 5 4 3 2 1 0
位域含义 I F   M4 M3 M2 M1 M0
0xD3 1 1 0 1 0 0 1 1
对应含义 关闭中断IRQ 关闭快速中断FIQ   设置CPU为SVC模式,这和上面代码注释中的“set the cpu to SVC32 mode”,也是一致的。

关于为何设置CPU为SVC模式,而不是设置为其他模式,请参见本文档后面的章节:第 3.2 节 “uboot初始化中,为何要设置CPU为SVC模式而不是设置为其他模式”