s3c2440启动代码分析2


板子上电后就会从这里开始执行,主要完成基本初始化,还有判断是从 nor 还是 nand 启动,再实现
把程序搬到 SDRAM 当中,在搬运成功后再跳到 main 函数里面执行。 
我们现在开始来看看它的具体代码吧! 
GET 和 INCLUDE 的功能是相同的,功能都是引进一些编译过的文件。 
 GET option.inc 
 GET memcfg.inc 
 GET 2440addr.inc 
定义 SDRAM 工作在 Reflesh 模式下,SDRAM 有两种刷新模式:selfreflesh,autoreflesh。后者是在
其使用过程当中设置的。 
 BIT_SELFREFRESH EQU (1<<22) 
下面是对 arm 处理器模式寄存器对应的常数进行赋值,arm 处理器有一个 CPSR 寄存器,它的后五位
决定了处理器处于哪个模式下。可以看出常数的定义都不会超过后 5 位的。 
USERMODE    EQU  0x10 
FIQMODE     EQU  0x11 
IRQMODE     EQU  0x12 
SVCMODE     EQU  0x13 
ABORTMODE   EQU  0x17 
UNDEFMODE   EQU  0x1b 
MODEMASK    EQU  0x1f 
NOINT       EQU  0xc0 
各个异常模式的堆栈 
UserStack EQU (_STACK_BASEADDRESS-0x3800) ;0x33ff4800 ~ 
SVCStack EQU (_STACK_BASEADDRESS-0x2800) ;0x33ff5800 ~ 
UndefStack EQU (_STACK_BASEADDRESS-0x2400) ;0x33ff5c00 ~ 
AbortStack EQU (_STACK_BASEADDRESS-0x2000) ;0x33ff6000 ~ 
IRQStack EQU (_STACK_BASEADDRESS-0x1000) ;0x33ff7000 ~ 
FIQStack EQU (_STACK_BASEADDRESS-0x0) ;0x33ff8000 ~ 
这一段是统一 arm 的工作状态和对应的软件编译方式(16 位编译环境使用 tasm.exe 编译)。arm 处
理器的工作状态分为两种:32 位,arm 执行字对齐的 arm 指令集;16 位,arm 执行半字对齐的 Thumb
指令集。不同的工作状态,编译方式也不一样。所以下面的程序就是判断 arm 的工作方式来确定它的
编译方式。 
 GBLL    THUMBCODE//定义 THUMBCODE 这个变量 GBLL 声明一个全局逻辑变量并初始化为{FALSE}  
 [ {CONFIG} = 16//"["表示"if","|"表示"else","]"表示"endif",对于 CONFIG 是在 ADS 编译中定
义的内部变量。 
THUMBCODE SETL  {TRUE} 
     CODE32 
   | 
THUMBCODE SETL  {FALSE} 
    ]//如果 ARM 是在 16 位的工作状态的话,就使全局变量 THUMBCODE 设置为 ture。 
   MACRO//这个是宏定义的关键字 
 MOV_PC_LR//作用是子程序返回 
   [ THUMBCODE 
     bx lr//当目标程序是 Thumb 时,就要使用 BX 跳转返回,并转换模式。 
   | 
     mov pc,lr//目标程序是 ARM 指令集,直接把 lr 赋给 pc 就可以了。 
   ] 
 MEND//宏定义的结束标志。 
   MACRO 
 MOVEQ_PC_LR//这个是带“相等”条件的子程序返回。和上面说的类似。 
   [ THUMBCODE 
        bxeq lr 
   | 
     moveq pc,lr 
   ] 
 MEND 
在宏定义下面的 handlexxx HANDLER handlexxx 都会展成以下的程序段,这段程序主要把中断服务程
序的入口地址传送给 pc,在程序的用 34 字空间来存放中断服务程序的入口地址,每个字空间都会有
一个标号,以 handlerxxx 开头的。 
 MACRO 
$HandlerLabel HANDLER $HandleLabel 
$HandlerLabel 
 sub sp,sp,#4 //先预留空间,为了存储跳转地址。 
 stmfd sp!,{r0} //把工作寄存器按入堆栈。 
 ldr     r0,=$HandleLabel 
 ldr     r0,[r0] //这两句的功能是把中断程序的入口地址先放在中间变量 r0 处。 
 str     r0,[sp,#4]//把中断服务程序的入口地址按入堆栈。      
 ldmfd   sp!,{r0,pc}//最后把堆栈中的中断程序入口地址弹给 pc 寄存器,这样就可以执行相应的
中断服务程序了。     
 MEND 
S3C2440 有两种中断模式:一种有中断向量表的,一种则没有。有表的话实时性比较好。当一个外部
中断 0 发生后,程序自动跳转到地址 0x20 处,0x20 地址单元的指令为“ldr pc, = HandlerEINT0”,
因此程序跳转到 HandlerEINT0 处执行这个宏操作,就是把外部中断地址赋给 PC。 
一个 arm 程序是由 R0,RW,ZI 三个段组成。其中 R0 为代码段,RW 是已经初始化的全局变量,ZI 是未
初始化的全局变量,BOOTLOADER 要将 RW 段复制到 RAM 中并将 ZI 段清零。 
编译器使用下列段来记录各段的起始地址和结束地址 
|Image$$RO$$Base| ; RO 段起始地址|Image$$RO$$Limit| ; RO 段结束地址加 1|Image$$RW$$Base|
 ; RW 段起始地址 
|Image$$RW$$Limit| ; RW 段结束地址加 1|Image$$ZI$$Base| ; ZI 段起始地址|Image$$ZI$$Limit
| ; ZI 段结束地址加 1 
这些标号的值是通过编译器的设定来确定的如编译软件中对 ro-base 和 rw-base 的设定,例如 ro-b
ase=0xc000000 rw-base=0xc5f0000,在这里用 IMPORT 伪指令( 和 c 语言的 extren 一样) 引入|Im
age$$RO$$Base|,|Image$$RO$$Limit|...等比较古怪的变量是编译器生成的。RO, RW, ZI  这三个段
都保存在 Flash 中,但 RW,ZI 在 Flash 中的地址肯定不是程序运行时变量所存储的位置,因此我
们的程序在初始化时应该把 Flash 中的 RW,ZI 拷贝到 RAM 的对应位置。这些变量是通过 ADS 的工
程设置里面设定的 RO Base 和 RW Base 设定的,最终由编译脚本和连接程序导入程序. 
IMPORT |Image$$RO$$Base| 
IMPORT |Image$$RO$$Limit| 
IMPORT |Image$$RW$$Base| 
IMPORT |Image$$ZI$$Base| 
IMPORT |Image$$ZI$$Limit| 
引入外部变量 mmu 的快速总线模式和同步总线模式两个变量 
IMPORT MMU_SetAsyncBusMode 
IMPORT MMU_SetFastBusMode 
我们所熟知的 main 函数 
IMPORT  Main 
把镜像从 Nandflash 拷贝到 SDRAM 的函数 
IMPORT  RdNF2SDRAM 
定义 arm 汇编程序段,段名叫 init 段,为只读段 
       AREA    Init,CODE,READONLY 
       ENTRY 
  
       EXPORT __ENTRY//导出__ENTRY 标号 
__ENTRY 
ResetEntry 
ASSERT :DEF:ENDIAN_CHANGE//判断模式改变是否定义过(ASSERT 是伪指令,:DEF:lable 判断 labl
e 是否定义过了) 
[ ENDIAN_CHANGE 
  ASSERT  :DEF:ENTRY_BUS_WIDTH//判断是否定义了总线宽度 
  [ ENTRY_BUS_WIDTH=32//如果存储器是 32 位的总线宽度 
   b ChangeBigEndian     ;DCD 0xea000007 
  ] 
  [ ENTRY_BUS_WIDTH=16//如果存储器是 16 位的总线宽度 
   andeq r14,r7,r0,lsl #20   ;DCD 0x0007ea00 
  ] 
  [ ENTRY_BUS_WIDTH=8//如果是存储器是 8 位总线宽度 
   streq r0,[r0,-r10,ror #1] ;DCD 0x070000ea 
  ] 
|//如果总线宽度没有定义的话,就直接跳转到复位中断 
  b ResetHandler//程序执行的地跳跳转指令 

 b HandlerUndef ;handler for Undefined mode 
 b HandlerSWI ;handler for SWI interrupt 
 b HandlerPabort ;handler for PAbort 
 b HandlerDabort ;handler for DAbort 
 b .  ;reserved 
 b HandlerIRQ ;handler for IRQ interrupt 
 b HandlerFIQ ;handler for FIQ interrupt 
;@0x20 
 b EnterPWDN ; Must be @0x20.//进入 powerdown 模式 
以上 8 条跳转指令,是 8 个异常中断处理向量,一定要按照顺序排好,据我了解,每次出现异常的话,
是由硬件自行查表的。 
HandlerFIQ HANDLER HandleFIQ 
HandlerIRQ HANDLER HandleIRQ 
HandlerUndef HANDLER HandleUndef 
HandlerSWI HANDLER HandleSWI 
HandlerDabort HANDLER HandleDabort 
HandlerPabort HANDLER HandlePabort 
下面这段程序很重要,他是实现第二次查表的程序。arm 把所有中断都归为一个 IRQ 和一个 FIRQ 中
断异常,我们为了要知道具体的中断,从而才可以跳到中断对应的中断服务程序。 
IsrIRQ 
 sub sp,sp,#4       //保留 pc 寄存器的值 
 stmfd sp!,{r8-r9}//把 r8 r9 按入堆栈 
 ldr r9,=INTOFFSET//把中断偏移 INTOFFSET 的地址装入 r9 里面 
 ldr r9,[r9]//取出 INTOFFSET 单元里面的值给 r9 
 ldr r8,=HandleEINT0//向量表的入口地址赋给 r8 
 add r8,r8,r9,lsl #2//求出具体中断向量的地址 
 ldr r8,[r8]//中断向量里面存储的中断服务程序的入口地址赋给 r8 
 str r8,[sp,#8]//按入堆栈 
 ldmfd sp!,{r8-r9,pc}//堆栈弹出,跳转到相应的中断服务程序 
 LTORG//声明文字池 
板子上电后就,程序就执行 0x00 处的 b ResetHandler 
ResetHandler 
 ldr r0,=WTCON     //关闭看门狗   
 ldr r1,=0x0 
 str r1,[r0] 
 ldr r0,=INTMSK 
 ldr r1,=0xffffffff  //关闭所有中断 
 str r1,[r0] 
 ldr r0,=INTSUBMSK 
 ldr r1,=0x7fff  //关闭所有子中断 
 str r1,[r0] 
 [ {FALSE} 
  ;rGPFDAT = (rGPFDAT & ~(0xf<<4)) | ((~data & 0xf)<<4); 
  ; Led_Display 
  ldr r0,=GPBCON 
  ldr r1,=0x155500 
  str r1,[r0]//使 GPB10~GPB4 为输出口,GPB3~GPB0 为输入口 
  ldr r0,=GPBDAT 
  ldr r1,=0x0 
  str r1,[r0]//使 GPB10~GPB4 输出为低电平,GPB3~GPB0 输入为低电平 
 ] 
通过数据手册可以发现,当输出为 1 时,LED 灭,反之亦然。 
LOCKTIME 是 pll 的 lock time 计数器。为了减少 pll 的 lock time,调整 LOCKTIME 寄存器。 
 ldr r0,=LOCKTIME 
 ldr r1,=0xffffff//赋给这个值后,UPLL 和 MPLL 的 locktime 的值都会设定好了。具体为什么是设
定这个值,你就去问问三星公司吧,我也不太懂。 
 str r1,[r0] 
说到这里,大家可能不太懂。我就在这里细说一下吧。这个涉及到 arm9 的时钟模块的知识。arm9 有
个时钟控制逻辑,它可以产生 cpu 的 FCLK 时钟、AHB 总线外围接口器件的 HCLK 时钟以及 APB 总线外
围接口器件的 PCLK 时钟。arm9 有两个锁相环 PLL,一个用于 FCLK、HCLK、HCLK。一个用于 USB 模块。
这两个 PLL 我们分别称之为 MPLL 和 UPLL。在系统复位之后,PLL 按照默认的配置进行操作,由于认
为它这时是一个不稳定的状态,所以这时用外部时钟作为 FCLK 时钟的输出。只有当向 PLLCON 寄存器
设置相应的值后,PLL 就会按照软件设置的频率运行了。这时就换成使用 PLL 的输出作为 FCLK 了。
对于 FCLK 先后不是有两次不同时钟作为输入,这样就余姚一个适应的时间,这个时间的设定就是我
们这里在 LOCKTIME 寄存器里面设置的常数啦。 
[ PLL_ON_START//设置 CLKDIVN 的值在 PLL 锁存时间之后有效。 
  ldr r0,=CLKDIVN 
  ldr r1,=CLKDIV_VAL  ; 0=1:1:1, 1=1:1:2, 2=1:2:2, 3=1:2:4, 4=1 :4:4, 5=1:4:8, 6=1:3:3, 
7=1:3:6. 
  str r1,[r0] 
可以看出是对 FCLK、PCLK 以及 HCLK 三者的比率设置。只要通过对 CLKDIVN 执行操作就可以得到相应
需要的比率了。 
  [ CLKDIV_VAL>1   //如果 Fclk:Hclk 不是 1:1 的话执行下面 
    mrc p15,0,r0,c1,c0,0 
    orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA 
    mcr p15,0,r0,c1,c0,0 
   | 
    mrc p15,0,r0,c1,c0,0 
    bic r0,r0,#0xc0000000;R1_iA:OR:R1_nF 
    mcr p15,0,r0,c1,c0,0 
  ]  
这里可以看出,如果 FCLK:HCLK 不是 1:1 的关系的话,就要转成异步总线模式。反之,如果是这个
比例关系的话,就转成快速总线模式。 
  ldr r0,=UPLLCON//对 UPLL 进行配置 
  ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)//这里就是非常熟悉的 PMS 啦,Fin  = 12.0MHz, U
CLK = 48MHz 
  str r1,[r0] 
  nop ; Caution: After UPLL setting, at least 7-clocks delay mu st be inserted for setti
ng hardware be completed. 
  nop 
  nop 
  nop 
  nop 
  nop 
  nop 
  ldr r0,=MPLLCON//对 MPLL 进行配置 
  ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV)    ;Fin = 12.0MHz,  FCLK = 400MHz 
  str r1,[r0] 
 ] 
 ldr r1,=GSTATUS2 
 ldr r0,[r1] 
 tst r0,#0x2 
判断是否是从休眠模式唤醒的,对 GSTATUS2[2]的检测就可以判断出是否从休眠模式唤醒的。 
bne WAKEUP_SLEEP//如果是的话就跳转。 
EXPORT StartPointAfterSleepWakeUp//定义一个外部的 StartPointAfterSleepWakeUp 
StartPointAfterSleepWakeUp 
    adrl r0, SMRDATA  
    ldr r1,=BWSCON  
    add r2, r0, #52  

    ldr r3, [r0], #4 
    str r3, [r1], #4 
    cmp r2, r0 
    bne %B0 
这段代码的作用就是设置存储控制器。在代码的后面有一个 SMRDATA 的数据区,用 r0 来定义它的起
始地址,用 r2 来定义它的结束地址。r3 是代表那 13 个存储控制器.代码很明显,就是把内存的数据
赋给这 13 个存储控制器里面的。 
 ldr r0,=GPFCON 
 ldr r1,=0x0 
 str r1,[r0]//对 GPF 设置为输入的功能 
 ldr r0,=GPFUP 
 ldr r1,=0xff 
 str r1,[r0]//禁止上拉电阻 
 ldr r1,=GPFDAT 
 ldr r0,[r1] 
 bic r0,r0,#(0x1e<<1)//bic 是 r0 与#(0x1e<<1)的反码按位相与。 
 tst r0,#0x1//这里就是测试最后一位是否为 0,为 0 时说明是有按键按下了。 
 bne %F1//当按键 0 没有被按下的时候,就跳转啦。 
这段代码是检测 EINT0 是否被按下了。 
 ldr r0,=GPFCON 
 ldr r1,=0x55aa 
 str r1,[r0]//GPF7~GPF4 设置为输出,GPF3~GPF0 设置为 EINT0~EINT3 
 ldr r0,=GPFDAT 
 ldr r1,=0x0 
 str r1,[r0] //很明显,GPF7~GPF4 设置为 LED 灯的控制,低电平全部亮了。起到指示的用途。 
 mov r1,#0 
 mov r2,#0 
 mov r3,#0 
 mov r4,#0 
 mov r5,#0 
 mov r6,#0 
 mov r7,#0 
 mov r8,#0 
 ldr r9,=0x4000000   ;64MB 
 ldr r0,=0x30000000 
0  
 stmia r0!,{r1-r8} 
 subs r9,r9,#32  
 bne %B0 
很明显可以看出,程序利用 r1~r8 这几个寄存器把 0x30000000 到 0x34000000 的内存全部清零了。

 bl InitStacks//初始化堆栈 
 
 ldr r0, =BWSCON 
 ldr r0, [r0] 
 ands r0, r0, #6//OM[1:0] != 0, 从 NOR FLash 或者内存启动,不用读取 NAND FLASH 
 bne copy_proc_beg//不需要从 NAND FLASH 启动就在这里跳转啦 
 adr r0, ResetEntry//OM[1:0] == 0,就从 NAND FLash 启动  
 cmp r0, #0//在进行比较,是否入口地址是在 0 处,如果不是则是用仿真器    
 bne copy_proc_beg//仿真器也不需要在 NAND FLASH 启动 
nand_boot_beg 
 [ {TRUE} 
  bl RdNF2SDRAM 
 ] 
  ldr pc, =copy_proc_beg 
我们来看下 RdNF2SDRAM 具体是怎么工作的,这段代码的作用就是把 NAND 的程序读到 RAM 里面。 
 void RdNF2SDRAM( ) 

  U32 i; 
  U32 start_addr = 0x0; 
  unsigned char * to = (unsigned char *)0x30000000; 
  U32 size = 0x100000;//可以算出是 8M 的大小。 
  rNF_Init();//我们来仔细看看这个函数吧。 
  如下: 
         static void rNF_Init(void) 

 rNFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<0);//TACLS=1 ,TWRPH0=4,TWRPH1=0 初始化
ECC,CLE&ALE 持续时间的设置,TWRPH0 和 TWRPH1 持续时间的设置。 
 rNFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)| (1<<4)|(1<<1)|(1<<0);//
在读写 NANDFLASH 之前,对 6,5,4 位的设置是确保可以使用 ECC;对 13 位清零,使得可以写,擦
除还有读 0x4E000038~0x4E00003C 区域的内容;由于对于这范围区域的读写我们不加任何限制,所以
我们就不用设置中断来通知系统这个范围的区域被读写了,也就是 10 位清零了;RnB 是表示存储器
现在是否处于忙碌状态,9 位的设置为 1 时,表示可以用中断来通知 CPU 现在存储器的状态,而 8 位
的设置是用来说明是上升沿触发还是下降沿触发。 
 rNFSTAT = 0; 
 rNF_Reset(); 

我们来看下 rNF_Reset()它的具体代码吧,代码如下: 
static void rNF_Reset() 

 NF_CE_L(); 
 NF_CLEAR_RB(); 
 NF_CMD(CMD_RESET);   
 NF_DETECT_RB(); 
 NF_CE_H(); 

代码看上去很烦人,其实不是的,就是一堆宏定义,我直接翻译一下吧,翻译如下: 
rNFCONT &= ~(1<<1); //位 1 清零,表示片选使能,这样片子就可以工作了。 
rNFSTAT |= (1<<2);//清零 2 位,这里不需要判断片子是否忙碌。 
rNFCMD  = (CMD_RESET);//其中 CMD_RESET=0xff。 
while(!(rNFSTAT&(1<<2)));//当 RnB 从低电平变换到高电平的时候,就会跳出这个循环。就是在等
待 NANDFLASH 操作完毕。 
rNFCONT |= (1<<1);//使片子停止工作。 
这样 NANDFLASH 的初始化工作终于完成了。我们现在回到 RdNF2SDRAM 里面来,接着往下分析。 
switch(rNF_ReadID())我们来分析一下里面这个函数吧,代码如下: 
static char rNF_ReadID() 

 char pMID; 
 char pDID; 
 char nBuff; 
 char n4thcycle; 
 int i; 
 
 NF_nFCE_L();//又是使能片子工作    
 NF_CLEAR_RB();//清除 NFSTAT 的 2 位,为以后判断片子是否工作完毕。 
 NF_CMD(CMD_READID); //往 NFCMD 送读 ID 指令。 
 NF_ADDR(0x0);//往 NFADDR 送地址 
 for ( i = 0; i < 100; i++ ); 
 pMID = NF_RDDATA8(); 
 pDID = NF_RDDATA8(); 
 nBuff     = NF_RDDATA8(); 
 n4thcycle = NF_RDDATA8(); 
 NF_nFCE_H(); 
 
 return (pDID); 
}//最后返回 pDID 为什么会有其它值,我就不太理解了。我们再返回到主程序里面看看。 
switch(rNF_ReadID()) 
 { 
  case 0x76: 
   for(i = (start_addr >> 9); size > 0; )//在这种情况下,认为一页的大小为 512 字节  
   { 
    rSB_ReadPage(i, to); 
    size -= 512; 
    to += 512; 
    i ++; 
   } 
   break; 
  case 0xf1: 
  case 0xda: 
  case 0xdc: 
  case 0xd3: 
   for(i = (start_addr >> 11); size > 0; )//在这种情况下,认为是 2048 字节为一页  
   { 
    rLB_ReadPage(i, to); 
    size -= 2048; 
    to += 2048; 
    i ++; 
   } 
   break; 
 } 
}   
其实都是把 NANDFLASH 的开始第二页的内容存放在一个指针数组里面,这个指针数组的起始地址在 0
x30000000。就是我们等下在下面看到的 to[i]数组了。下面两个函数完成的功能是一样的,只是区
别在于一页是多大,512 或者是 2048。 
static void rSB_ReadPage(U32 addr, unsigned char * to) 

 U32 i; 
 rNF_Reset(); 
 //  Enable the chip 
 NF_nFCE_L(); 
 NF_CLEAR_RB(); 
 // Issue Read command 
 NF_CMD(CMD_READ); 
 //  Set up address 
 NF_ADDR(0x00); 
 NF_ADDR((addr) & 0xff); 
 NF_ADDR((addr >> 8) & 0xff); 
 NF_ADDR((addr >> 16) & 0xff); 
 
 NF_DETECT_RB();  // wait tR(max 12us) 
 for (i = 0; i < 512; i++) 
 { 
  to[i] =  NF_RDDATA8(); 
 } 
 NF_nFCE_H(); 

static void rLB_ReadPage(U32 addr, unsigned char * to) 

 U32 i; 
 rNF_Reset(); 
 //  Enable the chip 
 NF_nFCE_L();    
 NF_CLEAR_RB(); 
 // Issue Read command 
 NF_CMD(CMD_READ); 
 //  Set up address 
 NF_ADDR(0x00); 
 NF_ADDR(0x00); 
 NF_ADDR((addr) & 0xff); 
 NF_ADDR((addr >> 8) & 0xff); 
 NF_ADDR((addr >> 16) & 0xff); 
 NF_CMD(CMD_READ3); 
 NF_DETECT_RB();  // wait tR(max 12us) 
 for (i = 0; i < 2048; i++) 
 { 
  to[i] =  NF_RDDATA8(); 
 } 
 NF_nFCE_H(); 

可以看出刚开始的时候都是先复位一下的,不同的地方在于每次是怎样把传进来的地址经过转换再付
给 NFADDR 寄存器的,具体怎么样要看 NAND 的数据手册。 
 我们接着回到 2440init.s 的程序来,接着就有以下一句: 
ldr pc, =copy_proc_beg 
在前面也看到 copy_proc_beg 这个标号出现很多次,这个标号下面的代码完成的功能就是把 nand fl
ash 的内容拷贝到 ram 当中。 
copy_proc_beg 
 adr r0, ResetEntry 
 ldr r2, BaseOfROM 
 cmp r0, r2//两个进行比较 
 ldreq r0, TopOfROM//如果相同的话,为 r0 赋上 R0 的结束位置,也是 RW 的起始位置。 
 beq InitRam //如果相同的话,就跳到这个标号的位置。 
 ldr r3, TopOfROM//以下代码是针对代码在 NOR FLASH 时的拷贝方法。 
0  
 ldmia r0!, {r4-r7} 
 stmia r2!, {r4-r7} 
 cmp r2, r3 
 bcc %B0//这几段代码的功能就是把 ResetEntry 的内容搬到 BaseOfROM(R0 的起始位置,后面有声
明的)。 
  
 sub r2, r2, r3 
 sub r0, r0, r2 //这里使 ResetEntry 的位置往下移,为了后面的数据拷贝做准备。   
   
InitRam  
 ldr r2, BaseOfBSS 
 ldr r3, BaseOfZero  

 cmp r2, r3 
 ldrcc r1, [r0], #4 
 strcc r1, [r2], #4 
 bcc %B0 //可以看出这一段是对 ResetEntry 里面定义好的数据拷贝到 RW 段。 
 mov r0, #0 
 ldr r3, EndOfBSS 
1  
 cmp r2, r3 
 strcc r0, [r2], #4 
 bcc %B1//如果拷贝完数据后还剩下多余的空间的话,就往里面填充 0 
  
 ldr pc, =%F2  ;goto compiler address 

 ldr r0,=HandleIRQ  
 ldr r1,=IsrIRQ  
 str r1,[r0]//这三条语句很明显就是说明了,HandleIRQ 这个中断向量的存储单元被赋上了 IsrIR
Q 标号的地址,这样发生 IRQ 中断后就会直接去到二级表,去确认具体发生哪个中断。 
    [ :LNOT:THUMBCODE 
   bl Main //到这里,我们就看到了进入 MAIN 函数了。 
   b . 
    ] 
    [ THUMBCODE  ;for start-up code for Thumb mode 
   orr lr,pc,#1 
   bx lr 
   CODE16 
   bl Main //可以看到以上代码表示如果 arm 是在 THUMBCODE 指令模式下的话,就进行模式转换。
   b . 
  CODE32 
    ] 
到这里,我们已经把 2440init.s 的启动代码分析了一遍了。如有任何错误的话,请大家指出!谢谢!
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

developer_wgl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值