Chinaunix首页 | 论坛 | 博客
  • 博客访问: 517210
  • 博文数量: 91
  • 博客积分: 9223
  • 博客等级: 中将
  • 技术积分: 1777
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-02 17:37
个人简介

!!!!!!!!!!!!

文章分类

全部博文(91)

文章存档

2013年(3)

2012年(4)

2011年(37)

2010年(36)

2009年(9)

2008年(2)

分类: LINUX

2010-09-29 18:00:57

           S3C44B0X的启动代码分析(bootloader)

(转)

这两天把S3C44B0Xbootloader通读了一遍,也在网上查了很多资料,其中有几段看得人吐血。把我理解到的注释一遍,望高手指点,酱油党的帮顶!!

  先来个bootloader扫盲吧。传说中的bootloader就是在我们程序运行前要运行的一段程序呀,或者说系统上电后最先执行的一段程序。首先它用来初始化软硬件(存储器,堆栈,I/O,定时器....),反正就是为程序运行建立合适的环境吧。接下来bootloader把我们的系统内核(image)加载到内存中,然后把控制权交给image,这样image就可以运行了。PC机的启动也可以这样解释,但是它还多了个BIOS在前面。BIOS主要完成硬件检测与资源分配,过后他就会把控制权交给我们操作系统的bootloader了,bootloader过后我们的系统就启动了。

  要知道,根据具体CPU体系结构和硬件设备来编写自己的bootloader可以达到高效节省存储空间的目的。但是也有些功能很强大的bootloader它们可以支持很多的体系结构。比如U-Boot就很强大,它支持ARMpowerPC,还有x86,所以在我们的PC机启动的时候经常会看到U-Boot..了。另一个比较常用的bootloader就是GRUB,可以用来引导LINUX哈。

  当然,我们可以在ARM上移植U-Boot,但是如果不在ARM板上跑系统的话,完全可以自己编写一个短小精悍的bootloader。我买的S3C44B0X的一块板子,里面的许多实验程序就是用它自己定义的一个bootloader,这段bootloader全部用汇编写成,所以执行效率还是很高的。

  好了,步入正题。这个简单的bootloader主要包含两个汇编文件Vecter.sSysinit.s。先来看看Vecter.s。。。

  开始(ARM汇编还不太熟啊,要多写些关于这方面的东东)

 ModeMask   EQU 0x1FEQU,这么弱智的就不说了,x86里面也有,这个ModeMask是什么呢?我们可以把它看做一个掩码,ARM体系有个32位程序状态寄存器CPSR(x86中叫PSW啦!),其中CPSR[4..0]这五位叫模式位,这些位记录了处理器的工作模式,在不同模式下资源的访问权限是有差别的。具体有:管理模式(10011), IRQ模式(10010),FIQ模式(10001),用户模式(10000),终止模式(10111),未定义模式(10111),系统模式(11111)七种。所以ModeMask也可以看成是系统模式时CPSR[4..0]的值啦。同时可以把ModeMask各位取反,然后和CPSR相与,这样就完全清除了CPSR的模式位了。这就是掩码啊。。。。

 SVC32Mode  EQU 0x13;管理模式,为什么是0x13,上面已经讲的很清楚了
 IRQ32Mode  EQU 0x12
IRQ模式
 FIQ32Mode  EQU 0x11
FIQ模式
User32Mode  EQU 0x10
;用户模式
Abort32Mode EQU 0x17
;终止模式
Undef32Mode EQU 0x1B
;未定义模式
  IRQ_BIT   EQU 0x80
;继续,CPSR[7]用于禁止和允许IRQ中断,为1时表示禁止
  FIQ_BIT   EQU 0x40
;同理,CPSR[6]用于禁止和允许FIQ中断,为1时表示禁止。再提下CPSR[5],它代表处理器的运行状态,是在Thumb态还是ARM态,1代表Thumb态,这里再唐僧一下,伪指令EQU只用于为常量定义一个符号名称,在编译的时候编译器会把用到符号名称的地方自动替换为该常量。因此编译后这几句是不占任何空间的..ok..
     GBLS MainEntry
GBLS用于定义一个全局字符串变量MainEntry,并初始化为空。此外还有两个定义全局变量的伪指令:GBLA 伪指令用于定义一个全局的数字变量,并初始化为0GBLL 伪指令用于定义一个全局的逻辑变量,并初始化为F()

MainEntry SETS "main"
SETS 用于给一个已经定义的全局变量或局部字符串变量赋值,此外还有SETA 伪指令用于给一个数字变量赋值,SETL 伪指令用于给一个逻辑变量赋值。与上面的相对应啦。。。
   IMPORT $MainEntry
IMPORT通知编译器要使用的标号在其他的源文件中定义,这里main函数是我们编写的C函数的入口,上面这其实就是IMPORT main,但为什么要像上面那样绕个大弯子呢,不懂???难道是为了程序的移植与更新。。

;检查是否是Thumb指令

  GBLL    THUMBCODE;首先定义一个THUMBCODE的逻辑变量保存检查结果,初始为0()
    [ {CONFIG} = 16 
CONFIGADS汇编器已经预定义的一个变量CONFIG=32表示编译为32位的ARM指令,CONFIG=16表示编译为16位的Thumb指令
THUMBCODE SETL {TRUE}
传说[..|..]是一个if-else语句
    CODE32
CODE32是一条伪指令,它告诉编译器后面的语句编译为32位的ARM指令,还有个CODE16伪指令意思类推,前面半句意思是:if(CONFIG=16) THUMBCODE=1CODE32
    |               
;否则:
THUMBCODE SETL {FALSE}
THUMBCODE=0
    ]

    [ THUMBCODE     if(THUMBCODE=1) CODE32 ???这个语句好像是多余的

      CODE32   ;for start-up code for Thumb mode
    ]   
;总之,这一小段代码保证了后面的代码编译为32位的ARM指令....go on....

 AREA SelfBoot, CODE, READONLY  ;定义一个名为SelfBoot的只读代码段,这里这个SelfBoot要注意啊,在ADSARM linker设置中有这样一句entry 0x0C000000-ro-base 0x0C000000-first vector.o(selfboot),意思就是告诉链接器我们程序的入口地址在0x0C000000,代码段起始地址是0x0C000000,并且将目标文件vector.oSelfBoot段放在代码段的起始地方。这就对了。。
  IMPORT UDF_INS_VECTOR
;引入外部定义的标号,未定义指令异常的处理程序的标号
 IMPORT SWI_SVC_VECTOR
;软中断处理程序的标号
 IMPORT INS_ABT_VECTOR
;预取指终止异常的处理程序标号
 IMPORT DAT_ABT_VECTOR
;数据终止异常处理程序标号
 IMPORT IRQ_SVC_VECTOR
IRQ中断处理程序标号
 IMPORT FIQ_SVC_VECTOR
FIQ中断处理程序标号 
上面几个标号到底是什么东东呢,其实它们是在Sysinit.s中被定义了的,可以认为他们是用来保存相应中断处理程序的入口地址的,但是他们的定义是通过MAP方式产生的,这将在Sysinit.s中具体说明。这里只需明白是引入的外部变量就OK了。

 ENTRY;这么久终于到达入口啦
 IF :DEF: |ads$version|
;这个语句是从ADS参考手册上来的,它指出你可以通过ads$version
 ELSE
;来判断是使用ADS进行编译还是用SDT来编译,if后面放ADS编译时想执行的语句,else

 EXPORT __main
;放SDT编译时想执行的语句。对SDT不太熟,这里如果用SDT来编译的话,就要

__main;声明一个main的标号??这一点可能是SDT中规定的,不敢确定哈。。。
 ENDIF

ResetEntry;编译后代码段真正开始的地方在这里,前面的全是伪指令。。吐血。。

;。。。。。。。。。。。。。。。。未完待续。。。。。。。。。。。。。。。

;睡醒了,继续写。。。昨天说到编译后程序真正开始的地方。。

ResetEntry  ;不重复了
 b SYS_RST_HANDLER 
;跳到标号SYS_RST_HANDLER处执行(这才是系统上电后运行的第一条语句)
 b UDF_INS_HANDLER  
;同理,跳到***

 b SWI_SVC_HANDLER 
;同理
 b INS_ABT_HANDLER  
.^_^
 b DAT_ABT_HANDLER 
.@@.
 b .                
;注意这里后面有一个点哦,狂晕好小

 b IRQ_SVC_HANDLER 
pass
 b FIQ_SVC_HANDLER  
pass

;上面这几句是什么意思呢,还有那个点是什么东西?要知道这里编译后是放在程序最开始的地方啊(如果放在flash中的话那就是ox0),所以这个刚刚开始的地方就是ARM的中断向量表了哈,上面的每条指令编译后占四个字节,而且他们的顺序是固定了的哈,不能随便改动。当相应的异常或中断发生的时候,PC会自动的跳到这个表的相应位置取指。比如发生未定义指令中断的时候,PC自动的跳到0x00000004这里,而此处恰好又是一条跳转指令 b UDF_INS_HANDLER,所以PC就跑到UDF_INS_HANDLER处执行相应的处理了哈。系统复位也是一种异常,复位后PC就转到SYS_RST_HANDLER处执行啦(我太啰嗦了)。最后,再说说那个点。它代表当前地址,跳到当前地址执行,明显的死循环啊。为什么要这样写呢,因为在中断向量表的0x00000014处这里是保留的,就是说没有任何异常或中断与它对应,所以PC一般不会跑到这里来的,如果真的来了那就说明你的程序出了很大的问题啦,我就在这里死循环气死你。最后需指出,有些处理器的中断向量表是从0xffff0000开始的,意思可以类推哈。

    马上就来上面说到的各个xxx_xxx_handler,但是别急,在这之前我们先来讲一个宏的定义。宏的具有很多优点(相对于函数来说),这里就不说了。这里定义的这个宏其实在这个bootloader里面没有用到,但它还是定义了,可能是为了方便用户程序的编写吧。这个宏的功能是跳到某个地址执行,执行完后返回。不说了,上代码。。。

MACRO ;宏开始的标志
$Label  HANDLER  $Vector 
$Label是你想在宏展开后用到的标号,HANDLER 宏名, $Vector 是传递给宏的参数
$Label                   
;标号的放置的位置
 sub lr, lr, #4          
lr=(lr-4)

 stmfd sp!, {r0-r3, lr}   ;入栈
 ldr r0, =$Vector
 ldr pc, [r0]  
;将PC转到地址Vector内保存的地址处执行,比较绕口(怎么返回?)

 ldmfd sp!, {r0-r3, pc}^ ;出栈,注意最后有个“^”号,表示把异常模式的SPSR复制到CPSR(仔细读读LDM的用法吧,这个“^”还有很多需要注意的地方)
 MEND

;这段宏相当的让人晕,首先没明白它到底要干什么,因为它在这个bootloader里面根本没有被用到。个人觉得它应该会被IRQ,FIQ异常处理程序用到。在发生IRQ,FIQ异常后,硬件先把IRQ,FIQLR预置为当前执行指令的下一条指令的地址(当前指令并没有被执行),在异常处理程序中首先将lr=(lr-4),这样当中断返回时PC就可以继续执行那条未被执行完的指令了,这样的话程序才不会错啊。接着把相关寄存器入栈;接着PC跑到地址Vector内保存的地址处执行,这里的Vector可以是我们最开始的时候IMPORTxxx_xxx_vector吧。但是这里还有一点不懂,PC跑到相应地方执行后怎么返回呢?狂晕是不是有问题。。。个人觉得应该在ldr pc, [r0]前加一句mov lr,pc,然后跳转到的地方处理结束后加一句mov pclr就好了。。最后出栈。。退出中断处理程序。。这段纯属个人理解。。来个简单的,看看怎么用调用这个宏吧,这个最实际。假如在程序中这样调用:zsl  HANDLER  IRQ_SVC_HANDLER

编译后就为:zsl  sub lr, lr, #4

                 stmfd sp!, {r0-r3, lr}

                 ldr r0, =IRQ_SVC_HANDLER

                 …………

清楚了吧..好了现在可以来看看我们的那些xxx_xxx_handler了。

UDF_INS_HANDLER            ;未定义指令中断处理入口,进入前硬件已经自动将LR设置为未定义指令的后一条指令的地址了
 stmfd sp!, {r0-r3, lr}   
;入栈,这里说说为什么要有r0-r3呢,c语言函数的前4个参数默认用r0-r3传递的,更多的参数就用栈传递,所以r0-r3可能在调用函数时被赋值,当然要保存啦
 ldr r0, =UDF_INS_VECTOR
UDF_INS_VECTOR这个说过了在Sysinit.s中被定义。这里看明
 mov lr, pc        
;白了没有啊,这一段没有实现什么中断处理,而是把PC跳到了
 ldr pc, [r0]      
UDF_INS_VECTOR保存的地址处,这就是二级跳转吧,这样我们
 ldmfd sp!, {r0-r3, pc}^ 
;可以在UDF_INS_VECTOR保存的地址处写上我们想要的中断服务程序了。绕了这么大一圈终于到达想要到的地方了,ARM为什么要搞得这么复杂呢?解释一下,首先发生异常或中断后PC会跳到前面的那个中断向量表那里,这是没办法的哈,设计者是这样设计的。中断向量表后再跳到这个UDF_INS_HANDLER啦,当然你也可以在这里写上你全部的中断服务程序,但是要注意一点,目前为止这些程序还是放在FLASH中,所以这个执行速度会让你受不了哈,特别是实时性要求很高的场合,你会气死去。相反如果我们跳到UDF_INS_VECTOR保存的地址处的话,这个地址常常就在RAM中了,这样的话你的中断服务程序执行速度就提上去了哈。而且UDF_INS_VECTOR保存的值你可以改动,所以这样你的服务程序放哪里就不重要了,这样是不是就很方便了呢!

SWI_SVC_HANDLER        ;软中断处理入口
 stmfd sp!, {r0-r3, lr}
 ldr r0, =SWI_SVC_VECTOR
 mov lr, pc
 ldr pc, [r0]
 ldmfd sp!, {r0-r3, pc}^

INS_ABT_HANDLER   ;预取指中断处理入口
 sub lr, lr, #4   
;这里要lr-4哦,因为发生这类中断时硬件是把相应的lr置为无效指令(未执行)的下一条指令的地址,在排除终止原因后,必须回到那条未执行的指令上,这时这条指令肯定是不会再错了的哈。
 stmfd sp!, {r0-r3, lr}
 ldr r0, =INS_ABT_VECTOR
 mov lr, pc
 ldr pc, [r0]
 ldmfd sp!, {r0-r3, pc}^

DAT_ABT_HANDLER   ;数据终止中断处理入口
 sub lr, lr, #4   
;这里的#4非上面的#4,这里写4表示返回后引起终止的那条指令不用再执行,若要重新执行引起终止的那条指令应当写#8,没办法人家就是这样设计的。哎。
 stmfd sp!, {r0-r3, lr}
 ldr r0, =DAT_ABT_VECTOR
 mov lr, pc
 ldr pc, [r0]
 ldmfd sp!, {r0-r3, pc}^

IRQ_SVC_HANDLER      IRQ中断处理入口
 sub lr, lr, #4     
;此#4亦彼#4,即中断时的那条指令必须重新执行,因为CPU是在
 stmfd sp!, {r0-r12, lr} 
;执行每条指令周期的最前面的那个时序检查IRQ中断的,所以
 mrs r0, spsr       
;那条指令根本还来不及被执行就被别人抢了控制权了
 stmfd sp!, {r0}
 ldr r0, =IRQ_SVC_VECTOR
 ldr pc, [r0]       
;注意这里没有返回,所以在编写IRQ服务程序的最后记得写返回

FIQ_SVC_HANDLER      FIQ中断处理入口
 sub lr, lr, #4
 stmfd sp!, {r0-r12, lr} 
 mrs r0, spsr
 stmfd sp!, {r0}
 ldr r0, =IRQ_SVC_VECTOR
 ldr pc, [r0]       
;同上

;下面重量级的东东出现了,因为这个handler实现了系统所有硬件的初始化,并把我们要执行的程序拷贝到内存中,然后让出系统控制权,这样我们编写的程序就真正的运行起来了。开始。

SYS_RST_HANDLER;系统复位后,从0x00000000跳到这里

 mrs r0, cpsr    ;读状态寄存器的值
 bic r0, r0, #ModeMask
;前面定义的掩码起作用了哦,将CPSR5位清0,注意bic的用法
 orr r0, r0, #(SVC32Mode :OR: IRQ_BIT :OR: FIQ_BIT)
;设置为管理模式,禁止两类中断
 msr cpsr_c, r0      
pass
 
 IMPORT InitSystem    
;引进外部文件声明的标号,这个在Sysinit.s中被定义

 bl InitSystem       
;转到InitSystem处执行,InitSystem主要完成系统一系列硬件包括内存、堆栈、LED端口、串口、定时器等的初始化。记住使用bl语句时,系统自动完成了LC=PC-4的任务,所以InitSystem的最后直接用mov pc, lr就可以返回了。
   
当系统从InitSystem成功返回后,接下来就该是开始拷贝程序到内存空间了,这是个艰巨难懂的过程。。。休息休息,明天继续。。。

再困难的事情如果除以一百也会变成很简单的事情,我需要的只是时间。

    昨天写到bootloader初始化系统硬件,这里要注意一下它对内存的初始化哈。这里还是多讲一点,我们知道用户可支配的S3C44B0X的地址空间只有256M(其他的[4G-256M]系统或保留或有其他用途吧,这里不管),地址范围是0x000000000x10000000通常ARM7体系把这256M的空间分为8Bank(Bank0-Bank7),每个Bank32M大小。每个Bank可分配给ROM,SRAMSDRAM,各种接口如键盘,USB,LCD等,具体怎么分配用户可以自己定义(当然是有一定的限制哈)。对我的实验板来说,它就把Bank0分配给了flash以当外存用,Bank6分配给了SDRAM以当内存用,所以我的板子内存的起始地址就是Bank6的起始地址0x0c000000,外存的起始地址对应的就是0x00000000咯。当然各个Bank的总线宽度和其他一些控制寄存器的初始化也就是初始化内存的必要步骤了哈。当内存初始化好了以后我们就可以把程序从外面的flash调进来运行了。

    好了,目前为止我们的内存已经初始化好了,我们可以把外部程序(或者image)搬进来运行了。但是在继续讲解程序的搬运之前我想对这个image再啰嗦两句,因为这会帮助我们更好的理解搬运过程。ADS中对image(程序映像)的结构是这样定义的:它必须包含regionsoutput sections(暂且翻译成域和输出段哈);要包括域和输出段被保存时的位置;还要包括域和输出段在运行时的位置。哎呀,我发现越讲越讲不清了,因为东西太多了。一切从简吧,就是一个程序映像应该有代码段(RO),已初始化的全局变量段(RW),未初始化全局变量段(ZI),当然后两者也可能没有,要看具体程序。当这个映像被保存着未运行时,它就有个保存地址,程序映像在这里按着RO,RW,ZI的顺序的被保存着(其实ZI没有真正被保存的,因为它们的值没有初始化啊没有保存的必要,只保存个ZI段的大小就OK),注意"顺序"二字,这代表着他们之间没有任何间隙哦。在开发板上调试的时候就可以直接把这个映像保存在SDRAM中,这个时候保存地址就是0x0c000000咯,如果你把它烧到FLASH最开始的地方,那么他的保存地址就是0x00000000咯,当然你也可以烧到FLASH的其他位置,并不一定从0x0开始。但是有一点你要保证,你要知道这个位置的起点在哪里。到时要运行或搬运的时候才找的得啊。

    令一方面当这个映像被运行的时侯,RO,RW,ZI都有个运行地址。这个地址是怎么得到的呢。当然是在连接器连接前我们用命令告诉它的,用指令_ro_base指定运行时RO段的基址,用指令_rw_base指定运行时RW段的基址(ADSARM linker设置中仔细的话你就可以看到哦)ZI一般紧根在RW段后面,所以不用对它指定了(要指定也行,自己去看参考手册吧)。连接器连接的时候就根据我们设置的RORW段起始值相应的设置各标号的运行地址值。简单点,假如距程序开头4字节处有个zsl的标号,我们又用语句ro-base 0x0C000000设置RO的基址,这样连接结束后,zsl标号在程序运行时的实际地址就是0x0C000004,这个地址值就是所谓image中保存的运行地址。完毕。。晕吧。。。

     接着昨天的。。当从InitSystem返回后。。。。注意这里我要讲一个会让你很晕的东东了。问你个问题,假如编译后我们的程序运行到这里,那这时我们这段程序(image)是使用的上面的讲的哪种地址呢?你也许会说:程序运行起来了啊,那他就是运行地址咯!但是如果是运行态那为什么没有将这些代码搬移到我们设定的RO,RW,ZI的地方运行呢?是的,程序是运行起来了,但是你发现没,前面的所有指令的执行都是与代码段位置无关的,也就是说无论你把这个程序放到哪里,只要你把PC的值指向这段程序的开始,那么运行到这里他们得到的结果都是一模一样的。但是接下来要执行的一些指令可能就与RO,RW,ZI的位置有关了,只要在这个时候我们把接下来的指令搬到他们的运行地址处,程序就可以正确的执行了。。这就是程序搬运的目的了。慢慢理解..

    PS:当然也有不用搬运的情况,即当保存地址和运行地址完全一样时,我的开发板就是这样。因为我们连接时一般设置ro-base 0x0C000000啦,而用AXD+JTAG调试的时候AXD也是直接把image下载到我们指定的ro-base开始的地方进行调试的。这就是保存地址和运行地址完全一样了,这个时候代码的搬移就没有必要了,多的只是扩展一下ZI段,想想为什么,不讲了。。。

     好了,可以步入正题了,讲解这段搬移程序。。。小二,上代码。。。。。。

     期盼ing。。。怎么还不来,。。。

     欲知后事如何,请听下回分解。。。

继续继续。。

   在拿出代码之前还是有必要交代一下ADS中预定义的几个变量以及它们的作用。Image$$RO$$Base表示连接完成后程序映像RO段的起始地址;Image$$RO$$Limit表示连接完成后程序映像RO段的结束地址;Image$$RW$$Base表示连接完成后程序映像RW段的起始地址;Image$$ZI$$Base表示连接完成后程序映像ZI段的起始地址,注意由于RW段与ZI段在运行时是紧密挨着的哦,所以这个值也就代表了RW段的结束地址;Image$$ZI$$Limit表示连接完成后程序映像ZI段的结束地址。为了书写和阅读的方便,我们分别把上面几个值赋给变量BaseOfROMTopOfROMBaseOfBSSBaseOfZeroEndOfBSS(后面会讲到)这样看起来就很简结了。终于终于代码出现...

 adr r0, ResetEntry ;得到这个image存储时的起始地址(ResetEntry是整个程序最开始处的标号,看前面),注意adr是相对于当前PC寻址哦,意思就是r0的值等于(当前PC的值)-(Reset标号地址与当前这条语句地址之间的差值),想想,结果就是image存储时的起始地址咯
 ldr r1, BaseOfROM  
;得到RO段的起始地址
 cmp r0, r1        
;比较
 ldreq r0, TopOfROM
;如果相等,就把r0设置为RO段的结束地址
 beq InitRamData   
;如果相等,说明image保存地址和RO段开始地址完全一样(利用AXD调试一般会设置成这样),这时代码段就不用被搬移咯,就直接跳到后面搬移数据段的地方去。

如果不等,就要进行代码段的搬移了:

ldr r2, =CopyProcBegCopyProcBeg是后面的一个程序标号,记住这里用的ldr,这里得到的就是标号CopyProcBeg的运行地址啊。。
 sub r1, r2, r1 
;想清楚,r2-r1就等价于保存态时CopyProcBeg相对于ResetEntry的偏移量
 add r0, r0, r1 
;相加后,r0是不是就是CopyProcBeg在保存态时的地址值(这个弯绕得。。)
 ldr r3, =CopyProcEnd 
CopyProcEnd也是后面的一个地址标号,r3得到它的运行态地址


 ldmia r0!, {r4-r7} 
;把保存态CopyProcBeg处以后的四个字一次装入r4-r7r0的值递增
 stmia r2!, {r4-r7}
;把r4-r7放入运行态时CopyProcBeg的地址处,r2的值递增
 cmp r2, r3        
;若r2说明CopyProcBegCopyProcEnd间的数据还未搬运完
 bcc %B0           
;跳到上一个标号0处,继续搬运

到这里,保存态的CopyProcBegCopyProcEnd间的部分就被搬运到他们的运行态的位置上了哦
 ldr r3, TopOfROM  
r3保存RO段的结束地址
 ldr pc, =CopyProcBeg 
;将PC指向运行态的CopyProcBeg的地址处执行
 

下面这段程序已经被搬运到他们的运行位置了哦,上面说了,要跑到CopyProcBeg处运行。现在来看看处于运行态的这段CopyProcBeg到底是干什么的呢?看名字就知道了吧,拷贝代码啊,拷贝什么代码呢,就是拷贝保存态的CopyProcEnd到代码段结束的全部代码。有可能这部分代码很大哦,所以我们先把CopyProcBeg搬运到他的运行位置(你可以理解为把它从ROM中搬到RAM),然后再在RAM里面运行它,用它来拷贝剩下的代码,这样有更高的拷贝速度哦。

;***********************************************
CopyProcBeg 

 ldmia r0!, {r4-r11}
;接着原来的拷贝

 stmia r2!, {r4-r11} pass
 cmp r2, r3         
;此时r3保存的是RO段的结束地址哦

 bcc %B0            
;跳到上一个标号0处,只要让r2r3小就继续拷贝
CopyProcEnd         
;至此,代码段的拷贝完全结束,下面接着拷贝数据段
 
 sub r1, r2, r3     
r2-r3为什么呢,跳出上面的那个循环后肯定是r2>=r3
 sub r0, r0, r1     
r0减去这个差值就是保存态数据段的起始地址了,一定要记住,保存态的RO段和RW段是紧密挨在一起的。

数据段(RW,ZI)的搬运与初始化(记住这些代码都被CopyProcBeg搬到他们的运行地址上去了,也就是说他们现在是在运行态在运行哦):
InitRamData 
 ldr r2, BaseOfBSS   
;得到你指定的数据段起始地址

 ldr r3, BaseOfZero   ;数据段的结束地址
0
 cmp r2, r3
 ldrcc r1, [r0], #4  
;晕,现在它要一个字一个字的搬运,你也可以设计成上面那样四

                      ;个字四个字的搬运
 strcc r1, [r2], #4
 bcc %B0             
;至此,ZW段搬运结束,此时肯定是r2=r3

 mov r0, #0           ;送0r0,这里应该是要将ZI段全部初始化为0
 ldr r3, EndOfBSS    
;得到ZI段结束地址

 cmp r2, r3
 strcc r0, [r2], #4  
;将0送到ZI段内,这里不啰嗦了
 bcc %B1             
;跳到上一个标号1
        
 ldr pc, GotoMain 
;最后,bootloader把系统控制权交给main函数啦,我们自己编写的

GotoMain DCD $MainEntry  ;程序就这样运行起来了哦

哎,终于把这个过程讲完了,可能你现在会很晕,呵呵,正常,多看几遍。至此bootloader的主要任务也就完成了,接下来还有一点扫尾的东西:

;***********************************************
 IMPORT |Image$$RO$$Base| ; 
引入这几个ARM Linker定义的变量
 IMPORT |Image$$RO$$Limit| ; RAM data starts after ROM program
 IMPORT |Image$$RW$$Base| ; Pre-initialised variables
 IMPORT |Image$$ZI$$Base| ; uninitialised variables
 IMPORT |Image$$ZI$$Limit| ; End of variable RAM space


BaseOfROM DCD |Image$$RO$$Base|  ;重新定义几个变量,分别把上面的几个值赋给他
TopOfROM DCD |Image$$RO$$Limit| 
;们,上面讲得很清楚了,不说了

BaseOfBSS DCD |Image$$RW$$Base|
BaseOfZero DCD |Image$$ZI$$Base|
EndOfBSS DCD |Image$$ZI$$Limit|

 EXPORT GetBaseOfROM       ;几个函数的声明,在我们以后编程时可能要调用他们

 EXPORT GetEndOfROM
 EXPORT GetBaseOfBSS
 EXPORT GetBaseOfZero
 EXPORT GetEndOfBSS
 
GetBaseOfROM                     
;得到RO基址

 ldr r0, BaseOfROM
 mov pc, lr 
GetEndOfROM                      
;类似
 ldr r0, TopOfROM
 mov pc, lr
GetBaseOfBSS                      
pass
 ldr r0, BaseOfBSS
 mov pc, lr
GetBaseOfZero                    
pass
 ldr r0, BaseOfZero
 mov pc, lr
GetEndOfBSS                      
pass
 ldr r0, EndOfBSS
 mov pc, lr
 
;*********************************************** 
 
 END                             
;程序结束

     终于终于完成了,,,以后再写点Sysinit.s里面关于硬件初始化的东西。。

     收工了。。。。。。。。。。

阅读(1595) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~