Uboot中start.S源码的指令级的详尽解析(一)

目录

正文之前
1. 本文内容 2. 本文目标 3. 代码来源 4. 阅读此文所要具有的前提知识 5. 声明
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
2. start.S的总结
2.1. start.S各个部分的总结 2.2. Uboot中的内存的Layout
3. 相关知识点详解
3.1. 如何查看C或汇编的源代码所对应的真正的汇编代码 3.2. uboot初始化中,为何要设置CPU为SVC模式而不是设置为其他模式 3.3. 什么是watchdog + 为何在要系统初始化的时候关闭watchdog
3.3.1. 什么是watchdog 3.3.2. 为何在要系统初始化的时候关闭watchdog
3.4. 为何ARM7中PC=PC+8
3.4.1. 为何ARM9和ARM7一样,也是PC=PC+8
3.5. AMR寄存器的别名 + APCS
3.5.1. ARM中的寄存器的别名 3.5.2. 什么是APCS
3.6. 为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈
3.6.1. 保存现场/上下文
3.6.1.1. 什么叫做上下文context
3.6.2. 传递参数 3.6.3. 举例分析C语言函数调用是如何使用堆栈的
3.7. 关于为何不直接用mov指令,而非要用adr伪指令 3.8. mov指令的操作数的取值范围到底是多少 3.9. 汇编学习总结记录
3.9.1. 汇编中的标号=C中的标号 3.9.2. 汇编中的跳转指令=C中的goto 3.9.3. 汇编中的.globl=C语言中的extern 3.9.4. 汇编中用bl指令和mov pc,lr来实现子函数调用和返回 3.9.5. 汇编中的对应位置有存储值的标号 = C语言中的指针变量 3.9.6. 汇编中的ldr+标号,来实现C中的函数调用 3.9.7. 汇编中设置某个寄存器的值或给某个地址赋值
参考书目

正文之前

1. 本文内容

此文主要内容就是分析start.S这个汇编文件的内容,即ARM上电后的最开始那一段的启动过程。

2. 本文目标

本文的目标是,希望看完此文的读者,可以达到:

  1. 微观上,对此start.S的每一行,都有了基本的了解
  2. 宏观上,对基于ARM核的S3C24X0的CPU的启动过程,有更加清楚的概念

这样的目的,是为了读者看完本文后,再去看其他类似的启动相关的源码,能明白需要做什么事情,然后再看别的系统是如何实现相关的内容的,达到一定程度的触类旁通。

总体说就是,要做哪些,为何要这么做,如何实现的,即英语中常说的:

  • do what
  • why do
  • how do

此三方面都清楚理解了,那么也才能算真正懂了。

3. 代码来源

所用代码来自TQ2440官网,天嵌的bbs上下载下来的uboot中的源码:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\start.S

下载地址为:2010年6月 最新TQ2440光盘下载 (Linux内核,WinCE的eboot,uboot均有更新)

4. 阅读此文所要具有的前提知识

阅读此文之前,你至少要对TQ2440的板子有个基本的了解,

以及要了解开发板初始化的大概要做的事情,比如设置输入频率,设置堆栈等等。

另外,至少要有一定的C语言的基础,这样更利于理解汇编代码。

5. 声明

由于水平有限,难免有误,欢迎指正:admin (at) crifan.com

欢迎转载,但请注明作者。

第 1 章 start.S详解

摘要

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

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

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

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

  • 设置CPU模式
  • 关闭看门狗
  • 关闭中断
  • 设置堆栈sp指针
  • 清除bss段
  • 异常中断处理

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

1.1. 设置CPU模式

1.1.1. globl

/*
 *  armboot - Startup Code for ARM920 CPU-core
 *
 *  Copyright (c) 2001	Marius Gr鰃er <mag@sysgo.de>
 *  Copyright (c) 2002	Alex Z黳ke <azu@sysgo.de>
 *  Copyright (c) 2002	Gary Jennejohn <gj@denx.de>
 *
 * 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 withfill (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 1UND 模式

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模式而不是设置为其他模式”

1.2. 关闭看门狗

1.2.1. pWTCON INTMOD INTMSK INTSUBMSK CLKDIVN

/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON		0x15300000
# define INTMSK		0x14400008	/* Interupt-Controller base addresses */
# define CLKDIVN	0x14800014	/* clock divisor register */
#elif defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)1
# define pWTCON		0x53000000
# define INTMOD		0X4A000004
# define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
# define INTSUBMSK	0x4A00001C
# define CLKDIVN	0x4C000014	/* clock divisor register */
#endif
        

1

上面几个宏定义所对应的地址,都可以在对应的datasheet中找到对应的定义:

其中,S3C2410和TQ2440开发板所用的CPU S3C2440,两者在这部分的寄存器定义,都是一样的,所以此处,采用CONFIG_S3C2410所对应的定义。

关于S3C2440相关的软硬件资料,这个网站提供的很全面:

http://just4you.springnote.com/pages/1052612

其中有S3C2440的CPU的datasheet:

s3c2440a_um_rev014_040712.pdf

其中有对应的寄存器定义:

图 1.3. pWTCON

pWTCON

图 1.4. INTMOD

INTMOD

图 1.5. INTMSK

INTMSK

图 1.6. INTSUBMSK

INTSUBMSK

图 1.7. CLKDIVN

CLKDIVN

而关于每个寄存器的具体含义,见后面的分析。

1.2.2. ldr pWTCON

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)
	ldr1     r0, =pWTCON
        

1

这里的ldr和前面介绍的ldr指令不是一个意思。

这里的ldr是伪指令ldr。

[提示]伪指令

伪指令,就是“伪”的指令,是针对“真”的指令而言的。

真的指令就是那些常见的指令,比如上面说的arm的ldr,bic,msr等等指令,是arm体系架构中真正存在的指令,你在arm汇编指令集中找得到对应的含义。

而伪指令是写出来给汇编程序看的,汇编程序能看的伪指令具体表示的是啥意思,然后将其翻译成真正的指令或者进行相应的处理。

伪指令ldr语法和含义:

http://blog.csdn.net/lihaoweiV/archive/2010/11/24/6033003.aspx

另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。ldr伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中,比如:

ldr r0, =0x12345678

这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。

只不过mov指令后面的立即数是有限制的,这个立即数,能够必须由一个8位的二进制数,即属于0x00-0xFF内的某个值,经过偶数次右移后得到,这样才是合法数据,而ldr伪指令没有这个限制。

那为何ldr伪指令的操作数没有限制呢,那是因为其是伪指令,写出来的伪指令,最终会被编译器解释成为真正的,合法的指令的,一般都是对应的mov指令。

这样的话,写汇编程序的时候,使用MOV指令是比较麻烦的,因为有些简单的数据比较容易看出来,有些数据即不容易看出来是否是合法数据。所以,对此,ldr伪指令的出现,就是为了解决这个问题的,你只管放心用ldr伪指令,不用关心操作数,而写出的ldr伪指令,编译器会帮你翻译成对应的真正的汇编指令的。

而关于编译器是如何将这些ldr伪指令翻译成为真正的汇编指令的,我的理解是,其自动会去算出来对应的操作数,是否是合法的mov 的操作数,如果是,就将该ldr伪指令翻译成mov指令,否则就用别的方式处理,我所观察到的,其中一种方式就是,单独申请一个4字节的空间用于存放操作数,然后用ldr指令实现。

在uboot中,最后make完毕之后,会生产u-boot,

通过:

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

就可以把对应的汇编代码输出到该txt文件了,其中就能找到伪指令:

ldr     r0, =0x53000000

所对应的,真正的汇编代码:

33d00068:	e3a00453 	mov	r0, #1392508928	; 0x53000000

所以被翻译成了mov指令。

而经过我的尝试,故意将0x53000000改为0x53000010,对应的生产的汇编代码为:

33d00068:	e59f0408 	ldr	r0, [pc, #1032]	; 33d00478 <fiq+0x58>
......
33d00478:	53000010 	.word	0x53000010
                    

其中可以看到,由于0x53000010不是有效的mov的操作数,没法找到合适的0x00-0Xff去通过偶数次循环右移而得到,所以只能换成此处这种方式,即在另外申请一个word的空间用于存放这个值:

33d00478:	53000010 	.word	0x53000010

然后通过计算出相对当前PC的偏移,得到的地址,用ldr指令去除该地址中的值,即0x53000010,送给r0,比起mov指令,要复杂的多,也多消耗了一个word的空间。

对应地,其他的方式,个人理解,好像也可以通过MVN指令来实现,具体细节,有待进一步探索。

而这里的:

ldr     r0, =pWTCON

意思就很清楚了,就是把宏pWTCON的值赋值给r0寄存器,即

r0=0x53000000

1.2.3. mov

	mov1     r1, #0x0
        

1

mov指令语法:

1、 MOV指令

MOV指令的格式为:

MOV{条件}{S} 目的寄存器,源操作数

MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令不更新CPSR中条件标志位的值。

指令示例:

MOV R1,R0 ;将寄存器R0的值传送到寄存器R1

MOV PC,R14 ;将寄存器R14的值传送到PC,常用于子程序返回

MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1

不过对于MOV指令多说一句,那就是,一般可以用类似于:

MOV R0,R0

的指令来实现NOP操作。

上面这句mov指令很简单,就是把0x0赋值给r1,即

r1=0x0

1.2.4. str

	str1     r1, [r0]
        

1

str指令语法:

4、STR指令

STR指令的格式为:

STR{条件} 源寄存器,<存储器地址>

STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。

指令示例:

STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并

将新地址R1+8写入R1。

STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。

所以这句str的作用也很简单,那就是将r1寄存器的值,传送到地址值为r0的(存储器)内存中。

用C语言表示就是:

*r0 = r1

所以,上面几行代码意思也很清楚:

先是用r0寄存器存pWTCON的值,然后r1=0,再将r1中的0写入到pWTCON中,其实就是

pWTCON = 0;

而pWTCON寄存器的具体含义是什么呢?下面就来了解其详细含义:

图 1.8. WTCON寄存器的位域

WTCON寄存器的位域

注意到bit[0]是Reset Enable/Disable,而设置为0的话,那就是关闭Watchdog的reset了,所以其他位的配置选项,就更不需要看了。

我们只需要了解,在此处禁止了看门狗WatchDog(的复位功能),即可。

关于看门狗的作用,以及为何要在系统初始化的时候关闭看门狗,请参见本文档后面的章节:第 3.3 节 “什么是watchdog + 为何在要系统初始化的时候关闭watchdog”

1.3. 关闭中断

1.3.1. set INTMSK

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

1

上面这几行代码,和前面的很类似,作用很简单,就是将INTMSK寄存器设置为0xffffffff,即,将所有的中端都mask了。

关于每一位的定义,其实可以不看的,反正此处都已mask了,不过还是贴出来,以备后用:

图 1.9. INTMSK寄存器的位域

INTMSK寄存器的位域

此处,关于mask这个词,解释一下。

mask这个单词,是面具的意思,而中断被mask了,就是中断被掩盖了,即虽然硬件上中断发生了,但是此处被屏蔽了,所以从效果上来说,就相当于中断被禁止了,硬件上即使发生了中断,CPU也不会去执行对应中断服务程序ISR了。

关于中断的内容的详细解释,推荐看这个,解释的很通俗易懂:【转】ARM9 2410移植之ARM中断原理, 中断嵌套的误区,中断号的怎么来的

1.3.2. set INTSUBMSK

# if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff1
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# elif defined(CONFIG_S3C2440)
	ldr	r1, =0x7fff2
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# endif
        

1

此处是将2410的INTSUBMSK设置为0x3ff。

后经HateMath的提醒后,去查证,的确此处设置的0x3ff,是不严谨的。

因为,根据2410的datasheet中关于INTSUBMSK的解释,bit[10:0]共11位,虽然默认reset的每一位都是1,但是此处对应的mask值,应该是11位全为1=0x7ff。

即写成0x3ff,虽然是可以正常工作的,但是却不够严谨的。

2

此处CPU是是S3C2440,所以用到0x7fff这段代码。

其意思也很容易看懂,就是将INTSUBMSK寄存器的值设置为0x7fff。

先贴出对应每一位的含义:

图 1.10. INTSUBMSK寄存器的位域

INTSUBMSK寄存器的位域

然后我们再来分析对应的0x7fff是啥含义。

其实也很简单,意思就是:

0x7fff = bit[14:0]全是1 = 上表中的全部中断都被屏蔽(mask)。

1.3.3. set CLKDIVN

#if 0
	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN1
	mov	r1, #3
	str	r1, [r0]
#endif
#endif	/* CONFIG_S3C2400 || CONFIG_S3C2410 || CONFIG_S3C2440 */2
        

1

此处,关于CLKDIVN的具体含义,参见下表:

图 1.11. INTSUBMSK寄存器的位域

INTSUBMSK寄存器的位域

而此处代码被#if 0注释掉了。

问:为何要注释掉,难道想要使用其默认的值,即HDIVN和PDIVN上电后,默认值Initial State,都是0,对应的含义为,FCLK:HCLK:PCLK = 1:1:1 ???

答:不是,是因为我们在其他地方会去初始化时钟,去设置对应的CLKDIVN,详情参考后面的代码第 1.4.3 节 “bl clock_init”的部分。

2

此处是结束上面的#ifdef

1.3.4. bl

	/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl1	cpu_init_crit
#endif
        

1

关于bl指令的含义:

b指令,是单纯的跳转指令,即CPU直接跳转到某地址继续执行。

而BL是Branch with Link,带分支的跳转,而Link指的是Link Register,链接寄存器R14,即lr,所以,bl的含义是,除了包含b指令的单纯的跳转功能,在跳转之前,还把r15寄存器=PC=cpu地址,赋值给r14=lr,然后跳转到对应位置,等要做的事情执行完毕之后,再用

mov pc, lr

使得cpu再跳转回来,所以整个逻辑就是调用子程序的意思。

bl的语法为:

2、 BL指令

BL指令的格式为:

BL{条件} 目标地址

BL 是另一个跳转指令,但跳转之前,会在寄存器R14中保存PC的当前内容,因此,可以通过将R14 的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。以下指令:

BL Label ;当程序无条件跳转到标号Label处执行时,同时将当前的PC值保存到R14中

对于上面的代码来说,意思就很清晰了,就是当没有定义CONFIG_SKIP_LOWLEVEL_INIT的时候,就掉转到cpu_init_crit的位置,而在后面的代码cpu_init_crit中,你可以看到最后一行汇编代码就是

mov pc, lr,

又将PC跳转回来,所以整个的含义就是,调用子程序cpu_init_crit,等cpu_init_crit执行完毕,再返回此处继续执行下面的代码。

于此对应地b指令,就只是单纯的掉转到某处继续执行,而不能再通过mov pc, lr跳转回来了。

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启动,然后决定从哪里拷贝所需要的代码到对应的目标地址中。

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 SBZMCR 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) SBZMCR 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 11

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 ManagerAccesses 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
                

是对应的。

1.6. 异常中断处理

摘要

1.6.1. macros stmia

/*
 *************************************************************************
 *
 * Interrupt handling
 *
 *************************************************************************
 */

@
@ IRQ stack frame.
@
#define S_FRAME_SIZE	72
1
#define S_OLD_R0	68
#define S_PSR		64
#define S_PC		60
#define S_LR		56
#define S_SP		52

#define S_IP		48
#define S_FP		44
#define S_R10		40
#define S_R9		36
#define S_R8		32
#define S_R7		28
#define S_R6		24
#define S_R5		20
#define S_R4		16
#define S_R3		12
#define S_R2		8
#define S_R1		4
#define S_R0		0

#define MODE_SVC 0x13
#define I_BIT	 0x80

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */
	.macro2	bad_save_user_regs

	sub	sp, sp, #S_FRAME_SIZE3
	stmia4	sp, {r0 - r12}			@ Calling r0-r12
	ldr	r2, _armboot_start  5  
        

1

此处很简单,只是一些宏定义而已。

后面用到的时候再解释。

2

.macro和后面的.endm相对应,其语法是:

图 1.12. macro的语法

macro的语法

所以,此处就相当于一个无参数的宏bad_save_user_regs,也就相当于一个函数了。

3

sp

= sp- S_FRAME_SIZE

= sp - 72

4

stmia的语法为:

图 1.13. LDM/STM的语法

LDM/STM的语法

其中,条件域的具体含义如下:

图 1.14. 条件码的含义

条件码的含义

更具体的含义:

六、批量数据加载/存储指令ARM微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。常用的加载存储指令如下:

LDM(或STM)指令

LDM(或STM)指令的格式为:

LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}

LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。

其中,{类型}为以下几种情况:

IA 每次传送后地址加1;

IB 每次传送前地址加1;

DA 每次传送后地址减1;

DB 每次传送前地址减1;

FD 满递减堆栈;

ED 空递减堆栈;

FA 满递增堆栈;

EA 空递增堆栈;

{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。

基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。

{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

指令示例:

STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到

R12,LR)存入堆栈。

LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到

R12,LR)。

所以,此行的含义是,

将r0到r12的值,一个个地传送到对应的地址上,基地址是sp的值,传完一个,sp的值加4,一直到传送完为止。

此处,可见,前面那行代码:

sp = sp - 72

就是为此处传送r0到r12,共13个寄存器,地址空间需要13*4=72个字节,

即前面sp减去72,就是为了腾出空间,留此处将r0到r12的值,放到对应的位置的。

5

此处的含义就是,将_armboot_start中的值,参考前面内容,即为_start,

而_start的值:

从 Nor Flash启动时:_stat=0

relocate代码之后为:_start=TEXT_BASE=0x33D00000

此处是已经relocate代码了,所以应该理解为后者,即_start=0x33D00000

所以:

r2=0x33D00000

1.6.2. cal reg value and store

	sub	r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)1
	sub	r2, r2, #(CFG_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack2
	ldmia	r2, {r2 - r3}			@ get pc, cpsr3
	add	r0, sp, #S_FRAME_SIZE		@ restore sp_SVC4
	add	r5, sp, #S_SP5
	mov	r1, lr6
	stmia	r5, {r0 - r3}			@ save sp_SVC, lr_SVC, pc, cpsr7
	mov	r0, sp8
	.endm9
        

1

此处:

r2

= r2 - ( CONFIG_STACKSIZE+CFG_MALLOC_LEN)

= r2 – (128*1024 + 256*1024)

= 0x33D00000 - 384KB

= 0x33CA0000

2

此处:

r2

= r2 - (CFG_GBL_DATA_SIZE + 8)

= 0x33CA0000 – (128 + 8)

= 0x33C9FF78

3

分别将地址为r2和r2+4的内容,即地址为0x33C9FF780x33C9FF7C中的内容,load载入给r2和r3寄存器。

4

将sp的值,加上72,送给r0

5

前面的定义是:

#define S_SP		52

所以此处就是将sp的值,加上52,送给r5

6

将lr给r1

7

然后将r0到r3中的内容,存储到地址为r5-r5+12中的位置去。

8

将sp再赋值给r0

9

结束宏bad_save_user_regs

此处虽然每行代码基本看懂了,但是到底此bad_save_user_regs函数是做什么的,还是不太清楚,有待以后慢慢深入理解。

1.6.3. irq_save_user_regs irq_restore_user_regs

	.macro	irq_save_user_regs
	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0-r12
	add     r8, sp, #S_PC
	stmdb   r8, {sp, lr}^                   @ Calling SP, LR
	str     lr, [r8, #0]                    @ Save calling PC
	mrs     r6, spsr
	str     r6, [r8, #4]                    @ Save CPSR
	str     r0, [r8, #8]                    @ Save OLD_R0
	mov	r0, sp
	.endm

	.macro	irq_restore_user_regs
	ldmia	sp, {r0 - lr}^			@ Calling r0 - lr
	mov	r0, r0
	ldr	lr, [sp, #S_PC]			@ Get PC
	add	sp, sp, #S_FRAME_SIZE
	subs	pc, lr, #4			@ return & move spsr_svc into cpsr
	.endm
1
	.macro get_bad_stack
	ldr	r13, _armboot_start		@ setup our mode stack
	sub	r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
	sub	r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
	str	lr, [r13]			@ save caller lr / spsr
	mrs	lr, spsr
	str     lr, [r13, #4] 
	mov	r13, #MODE_SVC			@ prepare SVC-Mode
	@ msr	spsr_c, r13
	msr	spsr, r13
	mov	lr, pc
	movs	pc, lr
	.endm
2
	.macro get_irq_stack			@ setup IRQ stack
	ldr	sp, IRQ_STACK_START
	.endm
3
	.macro get_fiq_stack			@ setup FIQ stack
	ldr	sp, FIQ_STACK_START
	.endm
4
        

1

上面两段代码,基本上和前面很类似,虽然每一行都容易懂,但是整个两个函数的意思,除了看其宏的名字irq_save_user_regs和irq_restore_user_regs,分别对应着中断中,保存和恢复用户模式寄存器,之外,其他的,个人目前还是没有太多了解。

2

此处的get_bad_stack被后面undefined_instruction,software_interrupt等处调用,目前能理解的意思是,在出错的时候,获得对应的堆栈的值。

3

此处的含义很好理解,就是把地址为IRQ_STACK_START中的值赋值给sp。

即获得IRQ的堆栈的起始地址。

而对于IRQ_STACK_START,是前面就提到过的cpu_init源码

而此处,就是用到了,前面已经在cpu_init()中重新计算正确的值了。

即算出IRQ堆栈的起始地址,其算法很简单,就是:

	IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;

即,先减去malloc预留的空间,和global data,即在

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

中定义的全局变量:

DECLARE_GLOBAL_DATA_PTR;

而此宏对应的值在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\asm-arm\global_data.h

中:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

即,用一个固定的寄存器r8来存放此结构体的指针。

[提示]提示

这也对应着编译uboot的时候,你所看到的编译参数-ffixed-r8

此gd_t的结构体,不同体系结构,用的不一样。

而此处arm的平台中,gd_t的定义在同一文件中:

typedef	struct	global_data {
	bd_t		*bd;
	unsigned long	flags;
	unsigned long	baudrate;
	unsigned long	have_console;	/* serial_init() was called */
	unsigned long	reloc_off;	/* Relocation Offset */
	unsigned long	env_addr;	/* Address  of Environment struct */
	unsigned long	env_valid;	/* Checksum of Environment valid? */
	unsigned long	fb_base;	/* base address of frame buffer */
#ifdef CONFIG_VFD
	unsigned char	vfd_type;	/* display type */
#endif
#if 0
	unsigned long	cpu_clk;	/* CPU clock in Hz!		*/
	unsigned long	bus_clk;
	unsigned long	ram_size;	/* RAM size */
	unsigned long	reset_status;	/* reset status register at boot */
#endif
	void		**jt;		/* jump table */
} gd_t;
                

而此全局变量gd_t *gd会被其他很多文件所引用,详情自己去代码中找。

4

此处和上面类似,把地址为FIQ_STACK_START中的内容,给sp。

其中:

	FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;

即FIQ的堆栈起始地址,是IRQ堆栈起始地址减去IRQ堆栈的大小。

1.6.4. exception handlers

/*
 * exception handlers
 */
	.align  5
undefined_instruction:1
	get_bad_stack
	bad_save_user_regs
	bl 	do_undefined_instruction
	.align	5
    

software_interrupt:
	get_bad_stack
	bad_save_user_regs
	bl 	do_software_interrupt

	.align	5
prefetch_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_prefetch_abort

	.align	5
data_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_data_abort

	.align	5
not_used:
	get_bad_stack
	bad_save_user_regs
	bl 	do_not_used
2
        

1

如果发生未定义指令异常,CPU会掉转到start.S开头中对应的位置:

	ldr	pc, _undefined_instruction

即把地址为_undefined_instruction中的内容给pc,即跳转到此处执行对应的代码。

其做的事情依次是:

获得出错时候的堆栈

保存用户模式寄存器

跳转到对应的函数:do_undefined_instruction

而do_undefined_instruction函数是在:

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

中:

void bad_mode (void)
{
	panic ("Resetting CPU ...\n");
	reset_cpu (0);
}

void do_undefined_instruction (struct pt_regs *pt_regs)
{
	printf ("undefined instruction\n");
	show_regs (pt_regs);
	bad_mode ();
}
                

可以看到,此处起始啥事没错,只是打印一下出错时候的寄存器的值,然后跳转到bad_mode中取reset CPU,直接重启系统了。

2

以上几个宏,和前面的do_undefined_instruction是类似的,就不多说了。

1.6.5. Launch

@ HJ
.globl Launch1
    .align	4
Launch:    
    mov r7, r0
    @ diable interrupt
	@ disable watch dog timer
	mov	r1, #0x53000000
	mov	r2, #0x0
	str	r2, [r1]

    ldr r1,=INTMSK
    ldr r2,=0xffffffff  @ all interrupt disable
    str r2,[r1]

    ldr r1,=INTSUBMSK
    ldr r2,=0x7ff       @ all sub interrupt disable
    str r2,[r1]

    ldr     r1, = INTMOD
    mov r2, #0x0        @ set all interrupt as IRQ (not FIQ)
    str     r2, [r1]

    @ 
	mov	ip, #0
	mcr	p15, 0, ip, c13, c0, 0      @	/* zero PID */
	mcr	p15, 0, ip, c7, c7, 0       @	/* invalidate I,D caches */
	mcr	p15, 0, ip, c7, c10, 4      @	/* drain write buffer */
	mcr	p15, 0, ip, c8, c7, 0       @	/* invalidate I,D TLBs */
	mrc	p15, 0, ip, c1, c0, 0       @	/* get control register */
	bic	ip, ip, #0x0001             @	/* disable MMU */
	mcr	p15, 0, ip, c1, c0, 0       @	/* write control register */

    @ MMU_EnableICache
    @mrc p15,0,r1,c1,c0,0
    @orr r1,r1,#(1<<12)
    @mcr p15,0,r1,c1,c0,0

#ifdef CONFIG_SURPORT_WINCE
    bl Wince_Port_Init
#endif

    @ clear SDRAM: the end of free mem(has wince on it now) to the end of SDRAM
    ldr     r3, FREE_RAM_END
    ldr     r4, =PHYS_SDRAM_1+PHYS_SDRAM_1_SIZE    @ must clear all the memory unused to zero
    mov     r5, #0

    ldr     r1, _armboot_start
    ldr     r2, =On_Steppingstone
    sub     r2, r2, r1
    mov     pc, r2
On_Steppingstone:
2:  stmia   r3!, {r5}
    cmp     r3, r4
    bne     2b

    @ set sp = 0 on sys mode
    mov sp, #0

    @ add by HJ, switch to SVC mode
	msr	cpsr_c,	#0xdf	@ set the I-bit = 1, diable the IRQ interrupt
	msr	cpsr_c,	#0xd3	@ set the I-bit = 1, diable the IRQ interrupt
    ldr sp, =0x31ff5800	
    
    nop
	nop
    nop
	nop

	mov     pc, r7  @ Jump to PhysicalAddress
	nop
    mov pc, lr
        

1

此处相当于一个叫做Launch的函数,做了也是类似的系统初始化的动作。

但是没找到此函数在哪里被调用的。具体不太清楚。

1.6.6. int_return

1
#ifdef CONFIG_USE_IRQ
	.align	5
irq:
/* add by www.embedsky.net to use IRQ for USB and DMA */
	sub	lr, lr, #4			        @ the return address
	ldr	sp, IRQ_STACK_START	        @ the stack for irq
	stmdb	sp!, 	{ r0-r12,lr }	@ save registers
	
	ldr	lr,	=int_return		        @ set the return addr
	ldr	pc, =IRQ_Handle2		        @ call the isr
int_return:
	ldmia	sp!, 	{ r0-r12,pc }^	@ return from interrupt
	.align	5
fiq:3
	get_fiq_stack
	/* someone ought to write a more effiction fiq_save_user_regs */
	irq_save_user_regs
	bl 	do_fiq4
	irq_restore_user_regs
#else
5
	.align	5
irq:
	get_bad_stack
	bad_save_user_regs
	bl 	do_irq

	.align	5
fiq:
	get_bad_stack
	bad_save_user_regs
	bl 	do_fiq

#endif
        

1

此处,做的事情,很容易看懂,就是中断发生后,掉转到这里,然后保存对应寄存器,然后跳转到对应irq函数IRQ_Handle中去。

但是前面为何sp为何去减去4,原因不太懂。

2

关于IRQ_Handle,是在:

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

中:

void IRQ_Handle(void)
{
	unsigned long oft = intregs->INTOFFSET;
	S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

//	printk("IRQ_Handle: %d\n", oft);

	//清中断
	if( oft == 4 ) gpio->EINTPEND = 1<<7;	
	intregs->SRCPND = 1<<oft;
	intregs->INTPND	= intregs->INTPND;

	/* run the isr */
	isr_handle_array[oft]();
}
                

此处细节就不多解释了,大体含义是,找到对应的中断源,然后调用对应的之前已经注册的中断服务函数ISR。

3

此处也很简单,就是发生了快速中断FIQ的时候,保存IRQ的用户模式寄存器,然后调用函数do_fiq,调用完毕后,再恢复IRQ的用户模式寄存器。

4

do_fiq()是在:

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

中:

void do_fiq (struct pt_regs *pt_regs)
{
	printf ("fast interrupt request\n");
	show_regs (pt_regs);
	bad_mode ();
}
                

和前面提到过的do_undefined_instruction的一样,就是打印寄存器信息,然后跳转到bad_mode()去重启CPU而已。

5

此处就是,如果没有定义CONFIG_USE_IRQ,那么就用这段代码,可以看到,都只是直接调用do_irq和do_fiq,也没做什么实际工作。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值