S3C44B0X的启动代码分析_第1页
S3C44B0X的启动代码分析_第2页
S3C44B0X的启动代码分析_第3页
S3C44B0X的启动代码分析_第4页
S3C44B0X的启动代码分析_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、. 这两天把S3C44B0X的bootloader通读了一遍,也在网上查了很多资料,其中有几段看得人吐血。把我理解到的注释一遍,望高手指点,酱油党的帮顶! 先来个bootloader扫盲吧。传说中的bootloader就是在我们程序运行前要运行的一段程序呀,或者说系统上电后最先执行的一段程序。首先它用来初始化软硬件(存储器,堆栈,I/O,定时器.),反正就是为程序运行建立合适的环境吧。接下来bootloader把我们的系统内核(image)加载到内存中,然后把控制权交给image,这样image就可以运行了。PC机的启动也可以这样解释,但是它还多了个BIOS在前面。BIOS主要完成硬件检测与资

2、源分配,过后他就会把控制权交给我们操作系统的bootloader了,bootloader过后我们的系统就启动了。 要知道,根据具体CPU体系结构和硬件设备来编写自己的bootloader可以达到高效节省存储空间的目的。但是也有些功能很强大的bootloader它们可以支持很多的体系结构。比如U-Boot就很强大,它支持ARM,powerPC,还有x86,所以在我们的PC机启动的时候经常会看到U-Boot.了。另一个比较常用的bootloader就是GRUB,可以用来引导LINUX哈。 当然,我们可以在ARM上移植U-Boot,但是如果不在ARM板上跑系统的话,完全可以自己编写一个短小精悍的bo

3、otloader。我买的S3C44B0X的一块板子,里面的许多实验程序就是用它自己定义的一个bootloader,这段bootloader全部用汇编写成,所以执行效率还是很高的。 好了,步入正题。这个简单的bootloader主要包含两个汇编文件Vecter.s和Sysinit.s。先来看看Vecter.s。 开始(对ARM汇编还不太熟啊,要多写些关于这方面的东东):ModeMask EQU0x1F;EQU,这么弱智的就不说了,x86里面也有,这个ModeMask是什么呢?我们可以把它看做一个掩码,ARM体系有个32位程序状态寄存器CPSR(x86中叫PSW啦!),其中CPSR4.0这五位叫模

4、式位,这些位记录了处理器的工作模式,在不同模式下资源的访问权限是有差别的。具体有:管理模式(10011), IRQ模式(10010),FIQ模式(10001),用户模式(10000),终止模式(10111),未定义模式(10111),系统模式(11111)七种。所以ModeMask也可以看成是系统模式时CPSR4.0的值啦。同时可以把ModeMask各位取反,然后和CPSR相与,这样就完全清除了CPSR的模式位了。这就是掩码啊。SVC32ModeEQU0x13;管理模式,为什么是0x13,上面已经讲的很清楚了IRQ32ModeEQU0x12;IRQ模式FIQ32ModeEQU0x11;FIQ模

5、式User32ModeEQU0x10;用户模式Abort32ModeEQU0x17;终止模式Undef32ModeEQU0x1B;未定义模式 IRQ_BITEQU0x80;继续,CPSR7用于禁止和允许IRQ中断,为1时表示禁止 FIQ_BITEQU0x40;同理,CPSR6用于禁止和允许FIQ中断,为1时表示禁止。再提下CPSR5,它代表处理器的运行状态,是在Thumb态还是ARM态,1代表Thumb态,这里再唐僧一下,伪指令EQU只用于为常量定义一个符号名称,在编译的时候编译器会把用到符号名称的地方自动替换为该常量。因此编译后这几句是不占任何空间的.ok. GBLSMainEntry;GB

6、LS用于定义一个全局字符串变量MainEntry,并初始化为空。此外还有两个定义全局变量的伪指令:GBLA 伪指令用于定义一个全局的数字变量,并初始化为0;GBLL 伪指令用于定义一个全局的逻辑变量,并初始化为F(假)。MainEntrySETSmain;SETS 用于给一个已经定义的全局变量或局部字符串变量赋值,此外还有SETA 伪指令用于给一个数字变量赋值,SETL 伪指令用于给一个逻辑变量赋值。与上面的相对应啦。 IMPORT$MainEntry;IMPORT通知编译器要使用的标号在其他的源文件中定义,这里main函数是我们编写的C函数的入口,上面这其实就是IMPORT main,但为什

7、么要像上面那样绕个大弯子呢,不懂?难道是为了程序的移植与更新。;检查是否是Thumb指令GBLL THUMBCODE;首先定义一个THUMBCODE的逻辑变量保存检查结果,初始为0(假)。 CONFIG = 16;CONFIG是ADS汇编器已经预定义的一个变量,CONFIG=32表示编译为32位的ARM指令,CONFIG=16表示编译为16位的Thumb指令THUMBCODE SETLTRUE;传说.|.是一个if-else语句 CODE32;CODE32是一条伪指令,它告诉编译器后面的语句编译为32位的ARM指令,还有个CODE16伪指令意思类推,前面半句意思是:if(CONFIG=16)则

8、THUMBCODE=1;CODE32; | ;否则:THUMBCODE SETLFALSE;THUMBCODE=0 THUMBCODE ;if(THUMBCODE=1)则CODE32?这个语句好像是多余的 CODE32 ;for start-up code for Thumb mode ;总之,这一小段代码保证了后面的代码编译为32位的ARM指令.go on.AREASelfBoot,CODE,READONLY ;定义一个名为SelfBoot的只读代码段,这里这个SelfBoot要注意啊,在ADS的ARM linker设置中有这样一句entry 0x0C000000-ro-base 0x0C0

9、00000-first vector.o(selfboot),意思就是告诉链接器我们程序的入口地址在0x0C000000,代码段起始地址是0x0C000000,并且将目标文件vector.o的SelfBoot段放在代码段的起始地方。这就对了。IMPORTUDF_INS_VECTOR;引入外部定义的标号,未定义指令异常的处理程序的标号IMPORTSWI_SVC_VECTOR;软中断处理程序的标号IMPORTINS_ABT_VECTOR;预取指终止异常的处理程序标号IMPORTDAT_ABT_VECTOR;数据终止异常处理程序标号IMPORTIRQ_SVC_VECTOR;IRQ中断处理程序标号IM

10、PORTFIQ_SVC_VECTOR;FIQ中断处理程序标号上面几个标号到底是什么东东呢,其实它们是在Sysinit.s中被定义了的,可以认为他们是用来保存相应中断处理程序的入口地址的,但是他们的定义是通过MAP方式产生的,这将在Sysinit.s中具体说明。这里只需明白是引入的外部变量就OK了。ENTRY;这么久终于到达入口啦IF :DEF: |ads$version|;这个语句是从ADS参考手册上来的,它指出你可以通过ads$versionELSE;来判断是使用ADS进行编译还是用SDT来编译,if后面放ADS编译时想执行的语句,else后EXPORT_main;放SDT编译时想执行的语句

11、。对SDT不太熟,这里如果用SDT来编译的话,就要_main;声明一个main的标号?这一点可能是SDT中规定的,不敢确定哈。ENDIF;ResetEntry;编译后代码段真正开始的地方在这里,前面的全是伪指令。吐血。;。未完待续。;睡醒了,继续写。昨天说到编译后程序真正开始的地方。ResetEntry ;不重复了bSYS_RST_HANDLER ;跳到标号SYS_RST_HANDLER处执行(这才是系统上电后运行的第一条语句)bUDF_INS_HANDLER;同理,跳到*处bSWI_SVC_HANDLER ;同理bINS_ABT_HANDLER;._bDAT_ABT_HANDLER ;.b.

12、;注意这里后面有一个点哦,狂晕好小bIRQ_SVC_HANDLER ;passbFIQ_SVC_HANDLER;pass;上面这几句是什么意思呢,还有那个点是什么东西?要知道这里编译后是放在程序最开始的地方啊(如果放在flash中的话那就是ox0咯),所以这个刚刚开始的地方就是ARM的中断向量表了哈,上面的每条指令编译后占四个字节,而且他们的顺序是固定了的哈,不能随便改动。当相应的异常或中断发生的时候,PC会自动的跳到这个表的相应位置取指。比如发生未定义指令中断的时候,PC自动的跳到0x00000004这里,而此处恰好又是一条跳转指令bUDF_INS_HANDLER,所以PC就跑到UDF_IN

13、S_HANDLER处执行相应的处理了哈。系统复位也是一种异常,复位后PC就转到SYS_RST_HANDLER处执行啦(我太啰嗦了)。最后,再说说那个点。它代表当前地址,跳到当前地址执行,明显的死循环啊。为什么要这样写呢,因为在中断向量表的0x00000014处这里是保留的,就是说没有任何异常或中断与它对应,所以PC一般不会跑到这里来的,如果真的来了那就说明你的程序出了很大的问题啦,我就在这里死循环气死你。最后需指出,有些处理器的中断向量表是从0xffff0000开始的,意思可以类推哈。马上就来上面说到的各个xxx_xxx_handler,但是别急,在这之前我们先来讲一个宏的定义。宏的具有很多优

14、点(相对于函数来说),这里就不说了。这里定义的这个宏其实在这个bootloader里面没有用到,但它还是定义了,可能是为了方便用户程序的编写吧。这个宏的功能是跳到某个地址执行,执行完后返回。不说了,上代码。MACRO;宏开始的标志$Label HANDLER $Vector ;$Label是你想在宏展开后用到的标号,HANDLER宏名,$Vector是传递给宏的参数$Label ;标号的放置的位置sublr, lr, #4 ;lr=(lr-4)stmfdsp!, r0-r3, lr ;入栈ldrr0, =$Vectorldrpc, r0;将PC转到地址Vector内保存的地址处执行,比较绕口(

15、怎么返回?)ldmfdsp!, r0-r3, pc;出栈,注意最后有个“”号,表示把异常模式的SPSR复制到CPSR(仔细读读LDM的用法吧,这个“”还有很多需要注意的地方)MEND;这段宏相当的让人晕,首先没明白它到底要干什么,因为它在这个bootloader里面根本没有被用到。个人觉得它应该会被IRQ,FIQ异常处理程序用到。在发生IRQ,FIQ异常后,硬件先把IRQ,FIQ的LR预置为当前执行指令的下一条指令的地址(当前指令并没有被执行),在异常处理程序中首先将lr=(lr-4),这样当中断返回时PC就可以继续执行那条未被执行完的指令了,这样的话程序才不会错啊。接着把相关寄存器入栈;接着

16、PC跑到地址Vector内保存的地址处执行,这里的Vector可以是我们最开始的时候IMPORT的xxx_xxx_vector吧。但是这里还有一点不懂,PC跑到相应地方执行后怎么返回呢?狂晕是不是有问题。个人觉得应该在ldrpc, r0前加一句mov lr,pc,然后跳转到的地方处理结束后加一句mov pc,lr就好了。最后出栈。退出中断处理程序。这段纯属个人理解。来个简单的,看看怎么用调用这个宏吧,这个最实际。假如在程序中这样调用:zsl HANDLER IRQ_SVC_HANDLER编译后就为:zsl sublr, lr, #4stmfdsp!, r0-r3, lrldrr0, =IRQ_

17、SVC_HANDLER 清楚了吧.好了现在可以来看看我们的那些xxx_xxx_handler了。UDF_INS_HANDLER ;未定义指令中断处理入口,进入前硬件已经自动将LR设置为未定义指令的后一条指令的地址了stmfdsp!, r0-r3, lr ;入栈,这里说说为什么要有r0-r3呢,c语言函数的前4个参数默认用r0-r3传递的,更多的参数就用栈传递,所以r0-r3可能在调用函数时被赋值,当然要保存啦ldrr0, =UDF_INS_VECTOR;UDF_INS_VECTOR这个说过了在Sysinit.s中被定义。这里看明movlr, pc;白了没有啊,这一段没有实现什么中断处理,而是把

18、PC跳到了ldrpc, r0;UDF_INS_VECTOR保存的地址处,这就是二级跳转吧,这样我们ldmfdsp!, r0-r3, pc;可以在UDF_INS_VECTOR保存的地址处写上我们想要的中断服务程序了。绕了这么大一圈终于到达想要到的地方了,ARM为什么要搞得这么复杂呢?解释一下,首先发生异常或中断后PC会跳到前面的那个中断向量表那里,这是没办法的哈,设计者是这样设计的。中断向量表后再跳到这个UDF_INS_HANDLER啦,当然你也可以在这里写上你全部的中断服务程序,但是要注意一点,目前为止这些程序还是放在FLASH中,所以这个执行速度会让你受不了哈,特别是实时性要求很高的场合,你

19、会气死去。相反如果我们跳到UDF_INS_VECTOR保存的地址处的话,这个地址常常就在RAM中了,这样的话你的中断服务程序执行速度就提上去了哈。而且UDF_INS_VECTOR保存的值你可以改动,所以这样你的服务程序放哪里就不重要了,这样是不是就很方便了呢!SWI_SVC_HANDLER;软中断处理入口stmfdsp!, r0-r3, lrldrr0, =SWI_SVC_VECTORmovlr, pcldrpc, r0ldmfdsp!, r0-r3, pcINS_ABT_HANDLER;预取指中断处理入口sublr, lr, #4;这里要lr-4哦,因为发生这类中断时硬件是把相应的lr置为无

20、效指令(未执行)的下一条指令的地址,在排除终止原因后,必须回到那条未执行的指令上,这时这条指令肯定是不会再错了的哈。stmfdsp!, r0-r3, lrldrr0, =INS_ABT_VECTORmovlr, pcldrpc, r0ldmfdsp!, r0-r3, pcDAT_ABT_HANDLER;数据终止中断处理入口sublr, lr, #4;这里的#4非上面的#4,这里写4表示返回后引起终止的那条指令不用再执行,若要重新执行引起终止的那条指令应当写#8,没办法人家就是这样设计的。哎。stmfdsp!, r0-r3, lrldrr0, =DAT_ABT_VECTORmovlr, pcld

21、rpc, r0ldmfdsp!, r0-r3, pcIRQ_SVC_HANDLER;IRQ中断处理入口sublr, lr, #4 ;此#4亦彼#4,即中断时的那条指令必须重新执行,因为CPU是在stmfdsp!, r0-r12, lr;执行每条指令周期的最前面的那个时序检查IRQ中断的,所以mrsr0, spsr ;那条指令根本还来不及被执行就被别人抢了控制权了stmfdsp!, r0ldrr0, =IRQ_SVC_VECTORldrpc, r0 ;注意这里没有返回,所以在编写IRQ服务程序的最后记得写返回FIQ_SVC_HANDLER;FIQ中断处理入口sublr, lr, #4stmfds

22、p!, r0-r12, lrmrsr0, spsrstmfdsp!, r0ldrr0, =IRQ_SVC_VECTORldrpc, r0 ;同上;下面重量级的东东出现了,因为这个handler实现了系统所有硬件的初始化,并把我们要执行的程序拷贝到内存中,然后让出系统控制权,这样我们编写的程序就真正的运行起来了。开始。SYS_RST_HANDLER;系统复位后,从0x00000000跳到这里mrsr0, cpsr;读状态寄存器的值bicr0, r0, #ModeMask;前面定义的掩码起作用了哦,将CPSR低5位清0,注意bic的用法orrr0, r0, #(SVC32Mode :OR: IRQ

23、_BIT :OR: FIQ_BIT);设置为管理模式,禁止两类中断msrcpsr_c, r0 ;passIMPORTInitSystem;引进外部文件声明的标号,这个在Sysinit.s中被定义blInitSystem ;转到InitSystem处执行,InitSystem主要完成系统一系列硬件包括内存、堆栈、LED端口、串口、定时器等的初始化。记住使用bl语句时,系统自动完成了LC=PC-4的任务,所以InitSystem的最后直接用movpc, lr就可以返回了。 当系统从InitSystem成功返回后,接下来就该是开始拷贝程序到内存空间了,这是个艰巨难懂的过程。休息休息,明天继续。再困难

24、的事情如果除以一百也会变成很简单的事情,我需要的只是时间。 昨天写到bootloader初始化系统硬件,这里要注意一下它对内存的初始化哈。这里还是多讲一点,我们知道用户可支配的S3C44B0X的地址空间只有256M(其他的4G-256M系统或保留或有其他用途吧,这里不管),地址范围是0x00000000到0x10000000,通常ARM7体系把这256M的空间分为8个Bank(Bank0-Bank7),每个Bank有32M大小。每个Bank可分配给ROM,SRAM,SDRAM,各种接口如键盘,USB,LCD等,具体怎么分配用户可以自己定义(当然是有一定的限制哈)。对我的实验板来说,它就把Ban

25、k0分配给了flash以当外存用,把Bank6分配给了SDRAM以当内存用,所以我的板子内存的起始地址就是Bank6的起始地址0x0c000000,外存的起始地址对应的就是0x00000000咯。当然各个Bank的总线宽度和其他一些控制寄存器的初始化也就是初始化内存的必要步骤了哈。当内存初始化好了以后我们就可以把程序从外面的flash调进来运行了。 好了,目前为止我们的内存已经初始化好了,我们可以把外部程序(或者image)搬进来运行了。但是在继续讲解程序的搬运之前我想对这个image再啰嗦两句,因为这会帮助我们更好的理解搬运过程。ADS中对image(程序映像)的结构是这样定义的:它必须包含

26、regions和output sections(暂且翻译成域和输出段哈);要包括域和输出段被保存时的位置;还要包括域和输出段在运行时的位置。哎呀,我发现越讲越讲不清了,因为东西太多了。一切从简吧,就是一个程序映像应该有代码段(RO),已初始化的全局变量段(RW),未初始化全局变量段(ZI),当然后两者也可能没有,要看具体程序。当这个映像被保存着未运行时,它就有个保存地址,程序映像在这里按着RO,RW,ZI的顺序的被保存着(其实ZI没有真正被保存的,因为它们的值没有初始化啊没有保存的必要,只保存个ZI段的大小就OK了),注意顺序二字,这代表着他们之间没有任何间隙哦。在开发板上调试的时候就可以直接

27、把这个映像保存在SDRAM中,这个时候保存地址就是0x0c000000咯,如果你把它烧到FLASH最开始的地方,那么他的保存地址就是0x00000000咯,当然你也可以烧到FLASH的其他位置,并不一定从0x0开始。但是有一点你要保证,你要知道这个位置的起点在哪里。到时要运行或搬运的时候才找的得啊。 令一方面当这个映像被运行的时侯,RO,RW,ZI都有个运行地址。这个地址是怎么得到的呢。当然是在连接器连接前我们用命令告诉它的,用指令_ro_base指定运行时RO段的基址,用指令_rw_base指定运行时RW段的基址(ADS的ARM linker设置中仔细的话你就可以看到哦),ZI一般紧根在RW

28、段后面,所以不用对它指定了(要指定也行,自己去看参考手册吧)。连接器连接的时候就根据我们设置的RO,RW段起始值相应的设置各标号的运行地址值。简单点,假如距程序开头4字节处有个zsl的标号,我们又用语句ro-base 0x0C000000设置RO的基址,这样连接结束后,zsl标号在程序运行时的实际地址就是0x0C000004,这个地址值就是所谓image中保存的运行地址。完毕。晕吧。 接着昨天的。当从InitSystem返回后。注意这里我要讲一个会让你很晕的东东了。问你个问题,假如编译后我们的程序运行到这里,那这时我们这段程序(image)是使用的上面的讲的哪种地址呢?你也许会说:程序运行起来

29、了啊,那他就是运行地址咯!但是如果是运行态那为什么没有将这些代码搬移到我们设定的RO,和RW,ZI的地方运行呢?是的,程序是运行起来了,但是你发现没,前面的所有指令的执行都是与代码段位置无关的,也就是说无论你把这个程序放到哪里,只要你把PC的值指向这段程序的开始,那么运行到这里他们得到的结果都是一模一样的。但是接下来要执行的一些指令可能就与RO,RW,ZI的位置有关了,只要在这个时候我们把接下来的指令搬到他们的运行地址处,程序就可以正确的执行了。这就是程序搬运的目的了。慢慢理解.PS:当然也有不用搬运的情况,即当保存地址和运行地址完全一样时,我的开发板就是这样。因为我们连接时一般设置ro-ba

30、se 0x0C000000啦,而用AXD+JTAG调试的时候AXD也是直接把image下载到我们指定的ro-base开始的地方进行调试的。这就是保存地址和运行地址完全一样了,这个时候代码的搬移就没有必要了,多的只是扩展一下ZI段,想想为什么,不讲了。 好了,可以步入正题了,讲解这段搬移程序。小二,上代码。 期盼ing。怎么还不来,。 欲知后事如何,请听下回分解。继续继续。 在拿出代码之前还是有必要交代一下ADS中预定义的几个变量以及它们的作用。Image$RO$Base表示连接完成后程序映像RO段的起始地址;Image$RO$Limit表示连接完成后程序映像RO段的结束地址;Image$RW$

31、Base表示连接完成后程序映像RW段的起始地址;Image$ZI$Base表示连接完成后程序映像ZI段的起始地址,注意由于RW段与ZI段在运行时是紧密挨着的哦,所以这个值也就代表了RW段的结束地址;Image$ZI$Limit表示连接完成后程序映像ZI段的结束地址。为了书写和阅读的方便,我们分别把上面几个值赋给变量BaseOfROM,TopOfROM,BaseOfBSS,BaseOfZero,EndOfBSS(后面会讲到)这样看起来就很简结了。终于终于代码出现.adrr0, ResetEntry ;得到这个image存储时的起始地址(ResetEntry是整个程序最开始处的标号,看前面),注意

32、adr是相对于当前PC寻址哦,意思就是r0的值等于(当前PC的值)-(Reset标号地址与当前这条语句地址之间的差值),想想,结果就是image存储时的起始地址咯ldrr1,BaseOfROM;得到RO段的起始地址cmpr0,r1 ;比较ldreqr0, TopOfROM ;如果相等,就把r0设置为RO段的结束地址beqInitRamData ;如果相等,说明image保存地址和RO段开始地址完全一样(利用AXD调试一般会设置成这样),这时代码段就不用被搬移咯,就直接跳到后面搬移数据段的地方去。如果不等,就要进行代码段的搬移了:ldrr2,=CopyProcBeg;CopyProcBeg是后面

33、的一个程序标号,记住这里用的ldr,这里得到的就是标号CopyProcBeg的运行地址啊。subr1, r2, r1 ;想清楚,r2-r1就等价于保存态时CopyProcBeg相对于ResetEntry的偏移量addr0, r0, r1 ;相加后,r0是不是就是CopyProcBeg在保存态时的地址值(这个弯绕得。)ldrr3,=CopyProcEnd;CopyProcEnd也是后面的一个地址标号,r3得到它的运行态地址0ldmiar0!, r4-r7;把保存态CopyProcBeg处以后的四个字一次装入r4-r7,r0的值递增stmiar2!, r4-r7 ;把r4-r7放入运行态时CopyProcBeg的地址处,r2的值递增cmpr2, r3 ;若r2=r3subr0, r0, r1 ;r0减去这个差值就是保存态数据段的起始地址了,一定要记住,保存态的RO段和RW段是紧密挨在一起的。数据段(RW,ZI)的搬运与初始化(记住这些代码都被CopyProcBeg搬到他们的运行地址上去了,也就是说他们现在是在运行态在运行哦):InitRamDataldrr2, BaseOfBSS ;得到你指定的数据段起始地址ldrr3, BaseOfZero ;数据段的结束地址0cmpr2, r3ldrccr1, r0, #4 ;晕,现在它要一个字一个字的搬

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论