Chinaunix首页 | 论坛 | 博客
  • 博客访问: 699058
  • 博文数量: 130
  • 博客积分: 2192
  • 博客等级: 大尉
  • 技术积分: 1410
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-31 15:37
文章分类

全部博文(130)

文章存档

2013年(1)

2012年(4)

2011年(27)

2010年(97)

2009年(1)

分类: 嵌入式

2010-08-01 22:26:19

2440的初始化程序,也可以说是bootloader中的精华部分,承载着程序执行的第一条指令
的重要任务,所有得程序要想执行,那么首先必须过Init.s这一关啊。

那么Init究竟做了些什么呢,咳,这的确是一个头大的问题,起初真的是一头雾水啊

首先,定义了入口地址,一般处理器上电以后,都会从存储器的0地址开始执行,好了,现
在就有第一个问题出现了,这个存储器的0地址是个什么概念;

要解决这个问题,首先必须弄清楚你的系统有哪些存储器。一般来说,不外乎rom和ram,
还有flash,flash是很重要的,因为我们的程序就放在那儿。可是我们知道,flash的物理
地址一般不会是从0开始啊,呵呵,的确是这么回事,那这个0到底是什么呢,很简单,偏
移量啊,就是flash的base+xx,这个xx就是0,^_^,就这么简单

接下来,出现第二个问题,程序从0地址执行,那么下一个要执行的代码是什么呢,他是通
过什么跳转到第二个代码呢?

这个问题的回答要针对具体的处理器了,一般代码的存放都是连续存放的,就是第一个地
址,第二个地址,代码占多大空间,那么第二个地址就是第一个地址+第一条指令占居的
空间
比如说我们现在使用的2440,它是32位宽的,就是说每条指令译码后,为一个32位的二进
制数,它的不同的位分别代表了这个指令要做什么。那么这样一条代码在存储器中就要占
据32位。那我们知道现在一般存储器都是按字节存放的,如此算来,一条指令要占据4个字
节,事实上也确实如此。

由此我们得知,假如说我们的第一条指令存放在0x00000000处,那么第二条指令就应该存
放在0x00000004处了。^_^一点都不假。并且,这里还要说明一点,由于它每条指令都是4
字节,也即一个字的宽度,同时arm在寻址的时候,是字对齐的,所以,arm的地址位的低
两位(如A1,A0)在到存储器中寻找的时候,是没有效的,因为存储器按字节存取,而一
条指令占4个字节,所以呢,处理器每次都是4个字节的拿或放,因而2^2=4,所以A1和A0的
变化,只会出现0x00000001,0x00000002,0x00000003这样的数,而这样的数在寻址的时
候,都是指向了0x00000000起始的四个字节,所以啊,即使出现这样的数,都会自动对齐
到0x00000000了,因而才有上面一说。如此,也应该能够明白,在我们读写ram的时候,地
址的结尾只能是0,4,8,c了啊,^_^

那么究竟这个第二个地址的读取是通过一种什么机制呢,那就是PC,也就是Program Coun
t,程序计数器,就是它,在复位或者上电时刻,指向了0地址,即PC=0x00000000,然后
,在指向第二条指令的时候,它做了这样一个运算,PC=PC+4,很简单吧;但是!,因为
2440是三级流水线结构,PC的值就一定小心啊,下面来讲讲为什么
三级流水线,故名思义,就是一个时刻做三件事情,对于2440来说,那就是取指,译码,
执行
取指   译码   执行。。。
       取指   译码   执行。。。
              取指   译码   执行。。。
那么问一个问题,PC究竟是指向取指呢,译码呢,还是执行,^_^,对了,取指令!!由此
便带来一个问题,当前指令执行的时候,PC的值已经不是当前指令所在的地址了,而是  
当前地址+8
      0x00000000      
      0x00000004        ---假设为当前执行的指令地址
      0x00000008
PC-> 0x0000000c        PC指向的是这儿

上电之时,PC便首先指向了flash的0地址,这个地方的指令将被执行。那么我们的程序如
何才能被放在这个0地址呢,当然不能随便写,汇编中,这些都是有固定格式的,如下 :

       AREA   Init,CODE,READONLY
       ENTRY
这个ENTRY便是表明,下面的程序为整个工程代码的入口点,至于如何放到flash的0地址,
那个就是flash烧写的工作了,不归我管,^_^

有了这样的入口,Init都要做些什么,先大概说说,包括几个部分吧,这些,也就是对
2440的初始化了
%定义入口地址
%建立异常向量表
%初始化存储器系统
%初始化堆栈
%初始化必要IO
%初始化中断系统要求的RAM向量
%开中断
%如有必要,改变处理器模式
%如有必要,改变处理器状态
%初始化C语言要求的存储器空间
%跳转到C去执行

建立异常向量表
我们知道,中断机制是PC机中一个非常重要的地方,中断节省CPU资源,加快了突发事件
的处理速度。在arm中,也差不多采取了类似的机制。PC中我们叫中断向量表,中断向量
表存放了一系列的跳转指令,当有中断产生的时候,系统便会自动到中断向量表查找相应
的中断向量号,然后根据中断号跳转到相应的地址去执行。
对于异常向量表,其实也是类似的功能,只不过对于一个arm来说,它上升到了工作模式
的高度,也就是说我们通常用的是用户模式,当有情况发生的时候,它要切换到相应的模
式下进行工作,比如中断,就是其中的一种!其他的还有管理模式,未定义指令模式,快
速中断模式,系统模式等等

异常向量表通常如下所示

b ResetHandler
b HandlerUndef
b HandlerSWI
b HandlerPabort
b HandlerDabort
b .
b IsrIRQ
b HandlerFIQ

那么,当有情况发生的时候,程序便会跳转到这个异常向量表的相应位置,执行相应的
跳转,如IRQ发生的时候,它就跳转到b IsrIRQ,进而执行这个跳转,去执行IsrIRQ标号
的代码

好了,知道了这个机制,便要关心这个表放在什么地方,才能让异常发生的时候,跳转
到这个表去执行相应的指令呢,呵呵,这肯定是放在一个特殊的地方,那就是0地址,
不过要注意了,这里0地址是哪里的0地址呢,呵呵
我们知道,异常的发生肯定是操作系统正在进行工作的时候,操作系统的工作是在
哪里进行??^_^,知道了,就是SDRAM,好了,操作系统在工作的时候,是不会在
返回到Nand Flash中去寻找咚咚的,明白了,这个异常向量表就是放在SDRAM的0地
址处,而且,一定要放在这个地方,这个地方也不能放别的东西,它就是放异常向量表

呵呵,插一段,我们要说说对于2440,它的启动流程是什么样子的,PC是怎么变化
的,简单说说吧

在Nand Flash中,bootloader,内核启动参数,内核,根文件系统是顺序存放的,并且,
bootloader是存放在0地址的,上电之后,PC就是指向这个0地址,并且开始执行,
bootloader要做很多事情,并且这些指令都是对CPU进行操作的,所以,很重要。而Init
就是bootloader中的一个部分,它的任务可以说是bootloader的子集;PC顺序执行,
首先关中断啊,初始化时钟啊等等,然后,将bootloader第二阶段的内容复制到SDRAM中
,包括Main主函数、内核和文件系统等复制过去,然后跳转到SDRAM中去执行,在执行
到跳转到内核去执行的时候,它要做一件事情,就是内存重映射,将PC重新定向到0地址
,并执行放在那儿的跳转指令……

ARM汇编程序分析过程中,比较难理解的是他的伪操作、宏指令和伪指令。本文是结合44B0X引导程序中出现伪操作、宏指令和伪指令进行总结,便于进一步分析44B0X的引导。

*****************************************************
    一、GET option.s

// GET和INCLUDE功能相同
功能:引进一个被编译过的文件。
格式:GET    filename
其中:fiename    汇编时引入的文件名,可以有路径名。
    GET符号在汇编时对宏定义,EQU符号以及存储映射时是很有用的,在引入文件汇编完以后,汇编将从GET符号后开始。在被引入的文件中可能有GET符号再引入其他的文件。GET符号不能用来引入目标文件。

*****************************************************
    二、INTPND EQU 0x01e00004

//EQU可以用“*”代替,在阅读源程序时注意。
功能:对一个数字常量赋予一个符号名。
格式:name    EQU   expression
其中:name   符号名。Expression    寄存器相关或者程序相关的固定值。
    使用EQU定义常量,与C语言中用#define定义一个常量相同。
例:num   EQU   2    ;   数字2赋予符号num

*****************************************************
    三、GBLL    THUMBCODE
        [ {CONFIG} = 16
            THUMBCODE SETL {TRUE}
            CODE32
        |   
            THUMBCODE SETL {FALSE}
        ]
        [ THUMBCODE
            CODE32   ;for start-up code for Thumb mode
        ]

//其中[=IF ,|=ELSE ,]= ENDIF, CODE32 表明一下操作都在ARM状态。这些都是伪操作这段理解为设定THUMCODE的值,然后确定,用户的程序是在ARM状态还是THUM状态。
*****************************************************
    四、MACRO
        $HandlerLabel HANDLER $HandleLabel
        $HandlerLabel
        sub sp,sp,#4 ;decrement sp(to store jump address)
        stmfd sp!,{r0} USH the work register to stack
        ldr r0,=$HandleLabel;load the address of HandleXXX to r0
        ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX
        str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack
        ldmfd sp!,{r0,pc} OP the work register and pc(jump to ISR)
        MEND

//MACRO……MEND
功能:标志一下宏的定义。
格式:MACRO
     Macro_prototype
     MEND
宏表达式的格式如下:
{$label}   macroname    {$ parameter{,parameter2}…}
其中:
$ label   参数,在宏使用时,被给定的符号替代。
Macroname   宏的名称,并不一定以一条指令或者符号名开始。
$parameter    在宏使用时,被替代的参数,格式为:$parameter=“default value”
   在宏体中,参数如:$parameter和变量一样使用,在被宏引用时,被赋于新值,参数必须用“$”符号加于区别。$label在宏定义内部符号时很 有用,可以看作宏的参数。使用“|”符号作为使用一个参数缺省值的变量,如果使用的是一个空格符串,将省去该变量。在使用内部标志的宏定义中,将内部标志 定义为带后缀的标志,将会很有用。如果在扩展中空间不够,可以作为参数和后继文字之间或者参数之间使用圆点隔开,但在文本和后继参数之间不能使用圆点。宏 可以定义局部变量的范围。宏还可以嵌套使用。
例:
MACRO
$label    xmac    $p1,$p2
          LCLS   err
$labell,loopl
          BGE    $pl
$labell,loop2
          BL     $p1
          BEG      $p1
          BEG      $labell,loop2
MEND  

*****************************************************
    五、$和$$

//$临时变量替换,若程序中需要用字符$则用$$来表示,通常情况下,包含在两个||之间的$并不表示进行变量替换,但是如果|线是在双引号内,则将进行变量替换。用“.”来分割出变量名的用法,
GBLS STR1
GBLS STR2
STR1 SETS "AAA"
STR2 SETS "BBB$$STR1.CCC"  //汇编后STR2的值为bbAAACCC

*****************************************************
    六、 IMPORT  Main    ; The main entry of mon program

//该伪操作告诉编译器当前的符号不是在本文件中定义的,在本源文件中可能引用该符号,而不论该源文件是否使用该符号,该符号都将被加入到本源文件中。
格式:
IMPORT symbol {[WEAK]}
    symbol 引用的符号的名称,他是区分大小写的,[WEAK]指定这个选项后,如果symbol所在的源文件中没有被定义,编译器也不会报错。他和EXTERN作用 相同,不同之处在于,如果本源文件没有实际引用该符号,该符号将不会被加入到本源文件的符号表中。
*****************************************************
    七、AREA    Init,CODE,READONLY
       ENTRY

//功能:指示汇编器汇编一段新的代码或新的数据区。
格式:
name  给出的特定段名。以数字开头,必须加竖线,否则,将报错,例如:|1_Data-Area|。某些名字已保留,如:|C$$code|已经被C编译器用作代码,或者用作与C库相连的代码段。
Attr    段名属性,下列属性是有效的:
ALIGN=expression
缺省状态下,AOF段将按4个字节对准,expression可以是2~31之间的整数,该段将按2(上标为expression)字节对准。例如,espression等于
10,该段将按1KB对准。

CODE        特定机器指令,缺省为READONLY。
COMDEF      通用段定义。该AOF段可能包括代码和数据,但必须与其他段名相区别。
COMMON      通用数据段,无须再注释定义任何代码和数据,通常由链接器初始化为零。


DATA        包含数据,但是不包含指令,缺省为READWRITE
INTERWORK   表明代码段可以适用ARM/Thumb interworking功能。
NOINIT      表明数据段可以初始化为零,只包含指示符。
PIC         表明定位独立段,可以不修改情况下,在任意地址执行。
READONLY    表明该段可读可写。

   汇编时,必须至少有一个AREA指示符。使用AREA符号可以将源程序区分,但是必须不重名。通常需要独立的AOF段做为代码或者数据段,较大程序可以 分为多个代码段。AOF段可以定义局部标签的范围,可以使用ROUT符号。如果没有任何的AREA指示符定义,汇编器将会产生名为|$$$$$$$|的 AOF段和一条诊断信息,将限制由于缺少指示符而产生的错误信息,但是并不一定会成功汇编。

*****************************************************
    八、LTORG

//LTORG是在此指令出现的地方放一个文本池(literal pool). 在ARM汇编中常用到
    ldr   r0, =instruction     将地址instruction载入r0
    此时编译器将ldr尽可能的转变成mov或mvn指令。 如果转变不成, 将产生一个ldr指令,通过pc相对地址从一块保存常数的内存区读出instruction的值。此内存区既是文本池。一般的, 文本池放在END指令之后的地方。但是, 如果偏移地址大于4k空间, ldr指令会出错(因为ldr的相对偏移地址为12-bit的值). 此时使用LTORG放到会出错的ldr指令附近,以解决此问题。 编译器会收集没有分配的ldr的值放到此文本池中
。所以必须在LDR指令前后4KB的范围内用LTORG显式地在代码段中添加一个文字池。

*****************************************************
    九、LDR r0,=WTCON ;watch dog disable
      LDR r1,=0x0

功能:将一个32位常量或地址读取至寄存器。
格式:
LDR{condition} register,=[expression|Label-expression]
其中:
condition             可选的条件代码。
register              读取的寄存器。
expression            数字常量:
    如果该数字常量在MOV或MVN指令的范围中,汇编器会产生合适的指令;
    如果该数字量不在MOV或MVN指令的范围中,汇编器把该常量于程序后,用程序相关的LDR伪指令读取,PC与该常量的偏移量不得超过4KB。
Label-expression      程序相关的或外部的表达式。汇编器将其存放在程序后的常量库(称为文字池(literal pool))中,用程序相关的LDR伪指令读取,PC与与该常量的偏移量不得超过4KB。
LDR伪指令的使用有两个目的:
     对于不能被MOV和MVN指令所读取的立即数,将其变成常量,进行读取。
     将一个程序相关的或外部的表达式读取进寄存器中。
例:
LDR  R1, =0xfff
LDR  R2, =place

*****************************************************
    十、DCD 0x11110090
      ;Bank0=OM[1:0], Bank1~Bank7=16bit, bank2=8bit;

//DCD或“&”
功能: 分配一个或多个字,从4个字节边界开始。
格式:
{label}DCD  expression{,expression}…
其中:
expression    可以是:
一个数学表达式;
一个程序相关的表达式。
    如果在Thumb代码中,使用DCD符号定义带标志的数据时则必须使用DATA符号。
    按4个字节对准时,DCD符号会在第一个字节之前插入3个字节的空字符,如果无须对准的话,可以使用DCDU符号。
例:
datal   DCD    1,5,20
data2   DCD    mem06
data3    DCD  glb+4

*****************************************************
    十一、ALIGN

//功能:从1个字边界开始。
格式:
ALIGN  {expression  {,offset-expression} }
其中:
    expression    2(上标为0)到2(上标为31)之间的任意数幂,当前按2(上标为n)字节对准,如果该参数没有指定,ALIGN将按字对准。
    Offset-expression  定义expression指定的对准方式的字节偏移量。
    使用ALIGN符号,保证程序正确对准。对于Thumb地址,使用ALIGN符号保证其按字对准,例如:ADR  Thuub伪指令只能读取字对准的地址。
    在代码段出现数据定义符时,使用ALIGE符号。当在代码段使用数据定义符(DCB,DCW,DCWU,DCDU和%),程序计数器PC并不一定按字对准。
    汇编器会在下一条指令时插入3个字节,保证:
    ARM状态下按字对准;
    Thumb状态下按半字对准。
    在Thumb状态下,可以使用ALIGN2对Thumb代码按半字对准。
    使用ALIGN状态下,还可以充分利用一些ARM处理器的Cache,例如,ARM940T有一个每行4字的Cache,使用ALIGN16按16字节对准,从而最大限度使用Cache。

*****************************************************
    十二、^ _ISR_STARTADDRESS

//MAP与"^"
    MAP用于定义一个结构化的内存表(StorageMAP)的首地址。此时,内存表的位置计数器{VAR}(汇编器的内置变量)设置成该地址值。MAP可以用”^”代替。
语法:MAP  expr {,base-register}
    其中,expr为数字表达式或者是程序中已经定义过的标号。Base-register为一个寄存器。当指令中没有Base-register 时,expr为结构化内存表的首地址。此时,内存表的位置计数器{VAR}设置成该地址值。当指令中包含这一项时,结构化内存表的首地址为expr和 Base-register寄存器内容的和。
使用说明:MAP伪操作和FIELD伪操作配合使用来定义结构化的内存表结构。
举例:MAP伪操作
MAP  fun  ;fun就是内存表的首地址
MAP   0x100,R9  ;内存表的首地址为 R9+0x100

*****************************************************
     十三、HandleReset # 4
        HandleUndef # 4
        HandleSWI # 4

//FIELD和"#"
FIELD 用于定义一个结构化的内存表中的数据域。FIELD 可用“#”代替。
语法:{label} FIELD expr
其中:{label}为可选的。当指令中包含这一项时,label的值为当前内存表的位置计数器{VAR}的值。汇编编译器处理了这条FIELD伪操作后。
内存表计数器的值将加上expr.expr表示本数据域在内存中所占的字节数。
使用说明:MAP伪操作和FIELD伪操作配合使用来定义结构化的内存表结构。MAP伪操作定义内存表的首地址。FIELD伪操作定义内存表的数据域的字节长度,并可以为每一格数据域指定一个标号,其他指令可以引用该标号。
MAP伪操作中的Base-registe寄存器值队以其后所有FIELD伪操作定义的数据域是默认使用的,直到遇到新的包含Base-registe项 的MAP伪操作需要特别注意的是,MAP伪操作和FIELD伪操作仅仅是定义数据结构,他们并不实际分配内存单元。由MAP伪操作和FIELD伪操作配合 定义的内存表有3种:基于绝对地址的内存表,基于相对地址的内存表和基于PC的内存表。
举例:基于绝对地址的内存表
    用伪操作序列定义一个内存表,其首地址为固定的地址8192(0X2000),该内存表中包括5个数据域。   
    Consta长度为4个字节;constb长为4个字节,x长为8字节;y长为8字节;string长为16字节。这种内存表成为基于绝对地址的内存表。
MAP  8192 ; //内存表的首地址8192(0x2000)
Consta FIELD 4 ; //consta 长为4字节,相对位置为0
Constb FIELD 4; //constb长为4字节,相对位置为4
X   FIELD  8; // X长为8字节,相对位置为8
Y    FIELD 8; // y长为8字节,相对位置为16
String FIELD 16 ;// String为16字节,相对位置为24
在指令中,可以这样引用内存表中的数据域;
LDR R0,consta; //将consta地址处对应内存加载到R0上面的指令仅仅可以访问LDR指令前后4KB地址范围的数据域。
举例:相对绝对地址的内存表
    下面的伪操作序列定义一个内存表,其首地址为0与R9寄存器值得和,该内存表中包含5个数据域。这种表称为相对地址的内存表。
MAP 0,R9;//内存表的首地址寄存器R9的值
Consta FIELD 4 ; //consta 长为4字节,相对位置为0
Constb FIELD 4; //constb长为4字节,相对位置为4
X   FIELD  8; // X长为8字节,相对位置为8
Y    FIELD 8; // y长为8字节,相对位置为16
String FIELD 16;// String为16字节,相对位置为24
可以通过下面的指令访问地址范围超过4KB的数据;
ADR  R9, Field ;  //伪指令
LDR  R5,Constb;//相当于LDR R5,[R9,#4]
    在这里,内存表中的数据都是相对于R9寄存器的内容,而不是相对于一个固定的地址。通过在LDR中指定不同的基址寄存器的值,定义的内存表结构可以在程序中有多个实例。可多次使用LDR指令,用以实现不同的程序实例。
举例:基于PC的内存表
Data   SPACE 100 ; //分配100字节的内存单元,并初始化为0
MAP Data;//内存表的首地址为Datastruc内存单元
Consta FIELD 4 ; //consta 长为4字节,相对位置为0
Constb FIELD 4; //constb长为4字节,相对位置为4
X   FIELD  8; // X长为8字节,相对位置为8
Y    FIELD 8; // y长为8字节,相对位置为16
String FIELD 16;// String为16字节,相对位置为24
可以通过下面的指令访问范围不超过4kb的数据;
LDR R5,constb ;相当于 LDR R5,[PC,offset]

*****************************************************
    十四、RN

在局部标号中:
%表示引用操作
F指示编译器只向前搜索。
B指示编译器只向后搜索。
A指示编译器搜索宏的所有嵌套层。
T指示编译器搜索宏的当前层次。
若F、B没有指定则先向前搜索,再向后搜索。
若A、T都没有指定则先从当前层到最高层,比当前层低的不再搜索。
阅读(1165) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~