ARM启动代码详解.
- 格式:doc
- 大小:34.50 KB
- 文档页数:19
ARM启动代码详细注释syd168 2009-07-232410Init.s包括了板子上电后的初始化过程,具体有以下几个步骤:1.屏蔽所有中断,关看门狗。
2.根据工作频率设置PLL寄存器3.初始化存储控制相关寄存器4.初始化各模式下的栈指针5.设置缺省中断处理函数6.将数据段拷贝到RAM中,将零初始化数据段清零7.跳转到C语言Main入口函数中;=========================================; NAME: 2410INIT.S; DESC: C start up codes; Configure memory, ISR ,stacks; Initialize C-variables; HISTORY:; 2002.02.25:kwtark: ver 0.0; 2002.03.20:purnnamu: Add some functions for testing STOP,POWER_OFF mode; 2002.04.10:SJS:sub interrupt disable 0x3ff -> 0x7ff;=========================================GET option.s ;GET相当INCLUDE 将一个源文件包含到当前源文件,这里表示包含option.s,并在当前位置进行汇编GET memcfg.sGET 2410addr.sBIT_SELFREFRESH EQU (1<<22);SDRAM/DRAM 刷新控制器bit22 REFMD位0:CBR/AUTO REFRESH 1:SHIF REFRESH;下面是对arm处理器模式寄存器对应值的常数定义,arm处理器中有一个CPSR程序状态寄存器,后五位决定目前的处理器模式。
;Pre-defined constants ;PSR 模式位USERMODE EQU 0x10 ;用户模式FIQMODE EQU 0x11 ;FIQ快速中断模式IRQMODE EQU 0x12 ;中断模式SVCMODE EQU 0x13 ;超级用户模式ABORTMODE EQU 0x17 ;中止模式UNDEFMODE EQU 0x1b ;未定义指令模式MODEMASK EQU 0x1f ;系统模式NOINT EQU 0xc0 ;禁止IRQ和FIQ中断在option.s中, _STACK_BASEADDRESS EQU (SDRAM_END-0x8000) ;0x33ff8000;;定义各模式堆栈地址_STACK_BASEADDRESSUserStack EQU (_STACK_BASEADDRESS-0x3800) ;0x33ff4800 ~ ; 用户模式堆栈SVCStack EQU (_STACK_BASEADDRESS-0x2800) ;0x33ff5800 ~0x3ff47ff ; 超级用户模式堆栈4kUndefStack EQU (_STACK_BASEADDRESS-0x2400) ;0x33ff5c00 ~0x3ff57ff ; 未定义指令模式堆栈1kAbortStack EQU (_STACK_BASEADDRESS-0x2000) ;0x33ff6000 ~0x3ff5bff ; 中止模式堆栈1kIRQStack EQU (_STACK_BASEADDRESS-0x1000) ;0x33ff7000 ~0x3ff5fff ; 中断模式堆栈4kFIQStack EQU (_STACK_BASEADDRESS-0x0) ;0x33ff8000 ~0x33ff6fff ; 快速中断模式堆栈4k;arm处理器有两种工作状态;1.arm:32位这种工作状态下执行字对齐的arm指令;2.Thumb:16位这种工作状;态执行半字对齐的Thumb指令;因为处理器分为16位32位两种工作状态程序的编译器也是分16位和32两种编译方式;所以下面的程序用于根据处理器工作状态确定编译器编译方式;code16伪指令指示汇编编译器,后面的指令为16位的thumb指令;code32伪指令指示汇编编译器,后面的指令为32位的arm指令;这段是为了统一目前的处理器工作状态和软件编译方式(16位编译环境使用tasm.exe编译);===========================================================================================;Check if tasm.exe(armasm -16 ...@ADS 1.0) is used. 检查是否是用tasm.exe进行16位编译GBLL THUMBCODE ;声明一个全局变量并初始化为{FALSE}[ {CONFIG} = 16 ;if config=16这里表示用16位编译方式THUMBCODE SETL {TRUE} ;SETL 给全局变量赋值,;设置THUMBCODE 为true CODE32 ;转入32位编译模式| ; | 等同ELSETHUMBCODE SETL {FALSE} ;设置THUMBCODE 为false] ; ] 等同ENDIF;========================================================================================== MACRO ;MACRO 定义宏MOV_PC_LR ;宏名MOV_PC_LR[ THUMBCODE ;if THUMBCODE=truebx lr ;THUMBCODE 模式上返回ARM状态| ;elsemov pc,lr ;ARM状态下返回] ;end ifMEND ;宏结束;================================================================MACRO ;宏定义开始MOVEQ_PC_LR ;宏名为MOVEQ_PC_LR[ THUMBCODE ;if THUMBCODE=truebxeq lr ;EQ 相等则跳转回ARM状态| ;elsemoveq pc,lr ;????] ;end ifMEND ;宏结束;==================================================================;注意下面这段程序是个宏定义;下面包含的HandlerXXX HANDLER HandleXXX将都被下面这段程序展开;这段程序用于把中断服务程序的首地址装载到pc中,有人称之为“加载程序”。
ARM启动代码分析
ARM启动代码的第一步是设置处理器的模式和栈指针。
ARM处理器有
多种模式,如用户模式、系统模式、中断模式和监视模式等,而每种模式
都有自己的寄存器集合,因此需要选择一个合适的模式。
一般情况下,一
开始会进入特权模式(如系统模式),然后将栈指针设置到RAM的一些合
适位置。
ARM启动代码的第三步是设置不同的中断服务例程。
ARM处理器支持
多种中断,如IRQ中断和FIQ中断等。
具体中断处理过程包括保存现场、
执行中断服务例程、恢复现场等步骤,这些都需要在启动代码中进行相关
的设置。
ARM启动代码的第四步是初始化内存管理单元(MMU)。
MMU是用来处
理虚拟地址和物理地址之间的映射关系的硬件单元,它可以提供更高的系
统性能和更灵活的内存管理能力。
初始化MMU需要设置页表和相关的控制
寄存器。
除了上述主要的步骤,ARM启动代码还可能包括其他一些完成特定任
务的代码,如初始化外设、配置中断控制器、设置缓存等。
这些任务的完
成都是为了保证系统正常启动和运行。
总之,ARM启动代码是用来初始化ARM处理器和系统环境的一段代码,它完成了处理器模式设置、时钟频率配置、中断服务例程设置、MMU初始
化以及跳转到操作系统内核等主要任务。
通常情况下,ARM启动代码由汇
编语言编写,具有高度灵活性和直接性。
ARM启动代码分析-philips的LPC2xxx系列***********************************************************************************************File: startup.s*Author: Embest w.h.xie 2005.02.21*Desc: lpc22xx\lpc212x\lpc211x\lpc210x startup code*History:* note modify:cui jian jie 2006-4-25*comment:**********************************************************************************************/# 处理器的七种工作方式的常量定义.EQU Mode_USR, 0x10 #用户模式.EQU Mode_FIQ, 0x11 #FIQ模式.EQU Mode_IRQ, 0x12 #IRQ模式.EQU Mode_SVC, 0x13 #超级用户模式.EQU Mode_ABT, 0x17 #终止模式.EQU Mode_UND, 0x1B #未定义模式.EQU Mode_SYS, 0x1F #系统模式# 中断屏蔽位.EQU I_Bit, 0x80 //IRQ中断控制位,当被置位时,IRQ中断被禁止.EQU F_Bit, 0x40 //FIQ中断控制位,当被置位时,FIQ中断被禁止# 状态屏蔽位.EQU T_bit, 0x20 //T位,置位时在Thumb模式下运行,清零时在ARM下运行# 定义程序入口点.globl _start.code 32.TEXT_start:# 中断向量表Vectors:LDR PC, Reset_Addr //把Reset_Addr地址处的内容放入PC中LDR PC, Undef_AddrLDR PC, SWI_AddrLDR PC, PAbt_AddrLDR PC, DAbt_Addr.long 0xb9205f80 @ keep interrupt vectors sum is 0LDR PC, [PC, #-0xff0] //当前PC值减去0xFF0等于IRQ中断入口地址LDR PC, FIQ_Addr#地址表Reset_Addr: #该地址标号存放Reset_Handler程序段的入口地址 .long Reset_HandlerUndef_Addr: #该地址标号存放Undef_Handler程序段的入口地址 .long Undef_HandlerSWI_Addr: #该地址标号存放SWI_Handler程序段的入口地址 .long SWI_HandlerPAbt_Addr: #该地址标号存放PAbt_Handler程序段的入口地址 .long PAbt_HandlerDAbt_Addr:.long DAbt_Handler.long 0IRQ_Addr: #地址标号处存放一个无效的数据.long 0FIQ_Addr: #该地址标号存放FIQ_Handler程序段的入口地址 .long FIQ_HandlerUndef_Handler:B Undef_HandlerPAbt_Handler:B PAbt_HandlerDAbt_Handler:B DAbt_Handler#软中断的中断服务子程序入口地址SWI_Handler:STMFD sp!, {r0-r3, r12, lr} //入栈,现场数据保护MOV r1, sp //把堆栈指针SP存入R1中MRS r0, spsr //把SPSR值存入R0,SPSR值为产生软中断时的CPSRTST r0, #T_bit //判断R0(SPSR)的T位是否为0#SPSR的T位不为0,工作在Thumb模式下LDRNEH r0, [lr,#-2] //SPSR的T位不为0,则[lr-2]-〉r0BICNE r0, r0, #0xFF00 // SPSR的T位不为0,清除r0的Bit8~Bit15位# SPSR的T位为0,工作在ARM模式下LDREQ r0, [lr,#-4] // SPSR的T位为0,则[lr-4] -〉r0BICEQ r0, r0, #0xFF000000 // SPSR的T位为0,清除r0的Bit24~Bit131位# R0 is interrupt number //R0是中断号# R1 is stack point //R1是堆栈指针BL SWI_Exception //进入软中断处理程序LDMFD sp!, {r0-r3, r12, pc}^ //出栈,现场数据恢复# 快速响应中断的中断服务自程序的入口地址FIQ_Handler:STMFD SP!, {R0-R3, LR} //入栈的现场保护# BL FIQ_Exception //进入FIQ的中断处理程序LDMFD SP!, {R0-R3, LR} //出栈,恢复现场SUBS PC, LR, #4 //返回到主程序# 复位后程序处理的入口地址Reset_Handler:BL RemapSRAM //进行存储器映射的操作#下面几行代码用来判断当前的工作模式MRS R0, CPSR //读CPSR到寄存器R0AND R0, R0, #0x1F //R0 = R0 AND 0x1FCMP R0, #Mode_USR //比较R0 和#Mode_USR,二者相减//如果相等则说明当前处在用户模式下,需要通过产生11号软中断进入系统模式。
PART1系统初始化流程如下:禁止看门狗----》在中断控制器中屏蔽所有中断----》系统时钟设置----》初始化端口----》DMA设置----》cashe和总线设置----》存储器设置,初始化SDRAM----》初始化堆栈----》设置IRQ和FIQ的入口----》地址重映射。
必须由汇编来完成的任务有:异常中断向量表的设置、IRQ向量表(向量模式)或ISR初始化(非向量模式)、二级ISR地址表的定义、Flash和SDRAM的设置(否则系统无法加载代码)、堆栈设置和模式切换、拷贝RW和ZI代码、设置系统时钟等。
对于非向量模式,不使用IRQ中断向量表,但二级ISR地址表的设置是相同的。
PART2理解启动代码(ADS)所谓启动代码,就是处理器在启动的时候执行的一段代码,主要任务是初始化处理器模式,设置堆栈,初始化变量等等.由于以上的操作均与处理器体系结构和系统配置密切相关,所以一般由汇编来编写.具体到S64,启动代码分成两部分,一是与ARM7TDMI内核相关的部分,包括处理器各异常向量的配置,各处理器模式的堆栈设置,如有必要,复制向量到RAM,以便remap之后处理器正确处理异常,初始化数据(包括RW与ZI),最后跳转到Main.二是与处理器外部设备相关的部分,这和厂商的联系比较大.虽然都采用了ARM7TDMI的内核,但是不同的厂家整合了不同的片上外设,需要不同的初始化,其中比较重要的是初始化WDT,初始化各子系统时钟,有必要的话,进行remap.这一部分与一般控制器的初始化类似,因此,本文不作重点描述.在进行分析之前,请确认如下相关概念:S64片上FLASH起始于0x100000,共64kB,片上RAM起始于0x200000,共16kB.S64复位之后,程序会从0开始执行,此时FLASH被映射到0地址,因此,S64可以取得指令并执行.显然,此时还是驻留在0x100000地址.如果使用remap命令,将会把RAM映射到0地址,同样的这时0地址的内容也只是RAM的镜像.S64的FLASH可以保证在最差情况时以30MHz进行单周期访问,而RAM可以保证在最大速度时的单周期访问.OK,以下开始分析启动代码.一,处理器异常S64将异常向量至于0地址开始的几个直接,这些是必需要处理的.由于复位向量位于0,也需要一条跳转指令.具体代码如下:RESETB SYSINIT ; ResetB UDFHANDLER ; UNDEFINEDB SWIHANDLER ; SWIB PABTHANDLER ; PREFETCH ABORTB DABTHANDLER ; DATA ABORTB . ; RESERVEDB VECTORED_IRQ_HANDLERB . ; ADD FIQ CODE HERE UDFHANDLERB . SWIHANDLERB . PABTHANDLERB . DABTHANDLERB .请注意,B指令经汇编后会替换为当前PC值加上一个修正值(+/-),所以这条指令是代码位置无关的,也就是不管这条指令是在0地址还是在0x100000执行,都能跳转到指定的位置,而LDR PC,=???将向PC直接装载一个标号的值,请注意,标号在编译过后将被替换为一个与RO 相对应的值,也就是说,这样的指令无论在哪里执行,都只会跳转到一个指定的位置.下面举一个具体的例子来说明两者的区别: 假定有如下程序:RESETB INIT 或者LDR PC,=INIT…INIT…其中RESET为起始时的代码,也就是这条代码的偏移为0,设INIT的偏移量为offset.如果将这段程序按照RO=0x1000000编译, 那么B INIT可理解为ADD PC, PC, #offset,而LDR PC,=INIT可被理解为MOV PC,#(RO+offset) .显然当系统复位时,程序从0开始运行,而0地址有FLASH的副本,执行B INIT将把PC指向位于0地址处的镜像代码位置,也即INIT;如果执行LDR PC,=INIT将会将PC 直接指向位于FLASH中的原始代码.因此以上两者都能正确运行.下面将RO设置为0x200000,编译后生成代码,还是得烧写到FLASH中,也就是还是0x100000,系统复位后从0地址执行,还是FLASH的副本,此时执行B INIT,将跳到副本中的INIT位置执行,此处有对应的代码;但是如果执行LDR PC,=INIT,将向PC加载0x200000+offset,这将使得PC跳到RAM中,而此时由于代码没有复制,RAM中的指定位置并没有代码,程序无法运行.二,处理器模式ARM的处理器可工作于多种模式,不同模式有不同的堆栈,以下设置各模式及其堆栈.预定义一些参数:MODUSR EQU 0x10MODSYS EQU 0x1FMODSVC EQU 0x13MODABT EQU 0x17MODUDF EQU 0x1BMODIRQ EQU 0x12MODFIQ EQU 0x11IRQBIT EQU 0x80FIQBIT EQU 0x40RAMEND EQU 0x00204000 ; S64 : 16KB RAMVECTSIZE EQU 0x100 ;UsrStkSz EQU 8 ; size of USR stack SysStkSz EQU 128 ; size of SYS stack SvcStkSz EQU 8 ; size of SVC stack UdfStkSz EQU 8 ; size of UDF stack AbtStkSz EQU 8 ; size of ABT stack IrqStkSz EQU 128 ; size of IRQ stack FiqStkSz EQU 16 ; size of FIQ stack修改这些值即可修改相应模式堆栈的尺寸.以下为各模式代码:SYSINIT;MRS R0,CPSRBIC R0,R0,#0x1FMOV R2,#RAMENDORR R1,R0,#(MODSVC :OR: IRQBIT :OR: FIQBIT)MSR cpsr_cxsf,R1 ; ENTER SVC MODEMOV sp,R2SUB R2,R2,#SvcStkSzORR R1,R0,#(MODFIQ :OR: IRQBIT :OR: FIQBIT)MSR CPSR_cxsf,R1 ; ENTER FIQ MODEMOV sp,R2SUB R2,R2,#FiqStkSzORR R1,R0,#(MODIRQ :OR: IRQBIT :OR: FIQBIT)MSR CPSR_cxsf,R1 ; ENTER IRQ MODEMOV sp,R2SUB R2,R2,#IrqStkSzORR R1,R0,#(MODUDF :OR: IRQBIT :OR: FIQBIT)MSR CPSR_cxsf,R1 ; ENTER UDF MODEMOV sp,R2SUB R2,R2,#UdfStkSzORR R1,R0,#(MODABT :OR: IRQBIT :OR: FIQBIT)MSR CPSR_cxsf,R1 ; ENTER ABT MODEMOV sp,R2SUB R2,R2,#AbtStkSz;ORR R1,R0,#(MODUSR :OR: IRQBIT :OR: FIQBIT);MSR CPSR_cxsf,R1 ; ENTER USR MODE;MOV sp,R2;SUB R2,R2,#UsrStkSzORR R1,R0,#(MODSYS :OR: IRQBIT :OR: FIQBIT)MSR CPSR_cxsf,R1 ; ENTER SYS MODEMOV sp,R2 ;三,初始化变量编译完成之后,连接器会生成三个基本的段,分别是RO,RW,ZI,并会在image中顺序摆放.显然,RW,ZI在运行开始时并不位于指定的RW位置,因此必须初始化LDR R0,=|Image$$RO$$Limit|LDR R1,=|Image$$RW$$Base|LDR R2,=|Image$$ZI$$Base|1CMP R1,R2LDRLO R3,[R0],#4STRLO R3,[R1],#4BLO %B1MOV R3,#0LDR R1,=|Image$$ZI$$Limit|2CMP R2,R1STRLO R3,[R2],#4BLO %B2四,复制异常向量由于代码于RAM运行时,有明显的速度优势,而且变量可以动态配置,因此可以通过remap将RAM映射到0,使得出现异常时ARM从RAM中取得向量.IMPORT |Image$$RO$$Base|IMPORT |Image$$RO$$Limit|IMPORT |Image$$RW$$Base|IMPORT |Image$$RW$$Limit|IMPORT |Image$$ZI$$Base|IMPORT |Image$$ZI$$Limit|COPY_VECT_TO_RAMLDR R0,=|Image$$RO$$Base|LDR R1,=SYSINITLDR R2,=0x200000 ; RAM STARTCMP R0,R1LDRLO R3,[R0],#4STRLO R3,[R2],#4BLO %B0这段程序将SYSINIT之前的代码,也就是异常处理函数,全部复制到RAM中, 这就意味着不能将RW设置为0x200000,这样会使得向量被冲掉.四,在RAM中运行如果有必要,且代码足够小,可以将代码置于RAM中运行,由于RAM中本身没有代码,就需要将代码复制到RAM中:COPY_BEGINLDR R0,=0x200000LDR R1,=RESET ; =|Image$$RO$$Base|CMP R1,R0 ;BLO COPY_END ;ADR R0,RESETADR R2,COPY_ENDSUB R0,R2,R0ADD R1,R1,R0LDR R3,=|Image$$RO$$Limit|3CMP R1,R3LDRLO R4,[R2],#4STRLO R4,[R1],#4BLO %B3LDR PC,=COPY_ENDCOPY_END程序首先取得RESET的连接地址,判断程序是否时是在RAM中运行,方法是与RAM起始地址比较,如果小于,那么就跳过代码复制.在复制代码的时候需要注意,在这段程序结束之前的代码没有必要复制,因为这些代码都已经执行过了,所以,先取得COPY_END,作为复制起始地址,然后计算其相对RESET的偏移,然后以RO的值加上这个偏移,就是复制目的地的起始地址,然后开始复制.五,开始主程序以上步骤完成,就可以跳转到main运行IMPORT MainLDR PC,=MainB .六,器件初始化主程序首先要进行器件的初始化,对S64而言,应该先初始化WDT,因为默认情况下,WDT是打开的,然后是各设备的时钟分配,最后应该remap.以上是必要的启动步骤,实际应用中可以根据具体情况添加一些代码.PART3ARM启动程序分析字体: 小中大 | 打印发布: 2007-7-17 11:05 作者: 网络转载来源: 网络查看: 4次ARM启动程序分析TIMER1用来触发和IRQ中断代码在FLASH中运行这个例子有一下几个文件:1. 中断向量表(ivt.s)2. 汇编启动代码 (init.s)3. C主函数文件中断向量表;代码必须链接在地址0X0。
linuxarm64启动代码:汇编部分1. 汇编部分主流程1.1 时序图1.2 代码解析1 ENTRY(stext)2/*3 * Kernel startup entry point.4 * ---------------------------5 *6 * The requirements are:7 * MMU = off, D-cache = off, I-cache = on or off,8 * x0 = physical address to the FDT blob.9 *10 * This code is mostly position independent so you call this at11 * __pa(PAGE_OFFSET + TEXT_OFFSET).12 *13 * Note that the callee-saved registers are used for storing variables14 * that are useful before the MMU is enabled. The allocations are described15 * in the entry routines.16*/17 __HEAD1819/*20 * DO NOT MODIFY. Image header expected by Linux boot-loaders.21*/22 b stext // 跳转到内核初始化中23 .long0// reserved24 .quad TEXT_OFFSET // Image load offset from start of RAM25 .quad 0// reserved26 .quad 0// reserved27 .quad 0// reserved28 .quad 0// reserved29 .quad 0// reserved30 .byte0x41// Magic number, "ARM\x64"31 .byte0x5232 .byte0x4d33 .byte0x6434 .word 0// reserved3536 ENTRY(stext)37 mov x21, x0 // x21=FDT,FDT是bootloader传过来的,是DTS的基地址38 bl __calc_phys_offset // 计算物理地址和物理地址和线性地址偏移x24=PHYS_OFFSET, x28=PHYS_OFFSET-PAGE_OFFSET39 bl el2_setup // 从el2模式退回到el1模式40 mrs x22, midr_el1 // 获得cpuid x22=cpuid41 mov x0, x2242 bl lookup_processor_type // 搜索处理器类型,在arch/arm64/kernel/cputable.c中定义43 mov x23, x0 // x23=current cpu_table44 cbz x23, __error_p // invalid processor (x23=0)?45 bl __vet_fdt // 检测FDT是否是8字节对齐,位于物理地址的前512MB之内46 bl __create_page_tables // 创建内核页表,x25=TTBR0, x26=TTBR147/*48 * The following calls CPU specific code in a position independent49 * manner. See arch/arm64/mm/proc.S for details. x23 = base of50 * cpu_info structure selected by lookup_processor_type above.51 * On return, the CPU will be ready for the MMU to be turned on and52 * the TCR will have been set.53*/54 ldr x27, __switch_data // 需要注意这⾥,处理器初始化完成后跳转到__switch_data55// 这时候MMU已经打开56 adr lr, __enable_mmu // 初始化完成以后ret的时候返回到__enable_mmu所在的地址57 ldr x12, [x23, #CPU_INFO_SETUP]58 add x12, x12, x28 // cpu_info的地址转换成物理地址59 br x12 // 初始化处理器60 ENDPROC(stext)。
ARM启动代码研究(附源代码)1:PRESERVE8:Reguire8和Preserve8C和汇编有8位对齐的要求,这两个伪指令可以满足此要求,存在REQUIRE8<——>PRESERVE8的对应关系,但不是说有一个REQUIRE8就要有一个PRESERVE8,如果是一个c文件和一个汇编文件的调用,也就涉及一个PRESERVE8或者是一个REQUIRE8.另外,REQUIRE8和PRESERVE8并不完成8byte对齐的操作,对齐由ALIGN完成。
将ADS的代码移植到KEILMDK上需要做的修改:当用户拥有ADS遗留工程的所有源代码时,1:PRESERVE8:Reguire8和Preserve8C和汇编有8位对齐的要求,这两个伪指令可以满足此要求,存在REQUIRE8<——> PRESERVE8的对应关系,但不是说有一个REQUIRE8就要有一个 PRESERVE8,如果是一个c文件和一个汇编文件的调用,也就涉及一个PRESERVE8或者是一个REQUIRE8.另外,REQUIRE8和PRESERVE8并不完成8 byte 对齐的操作,对齐由ALIGN完成。
将ADS的代码移植到KEIL MDK上需要做的修改:当用户拥有ADS遗留工程的所有源代码时,使用MDK重新编译链接全部代码是最好的解决方法,MDK中的新版本编译工具会重新生成满足堆栈8BYTE对齐要求的目标文件,避免由于堆栈不对齐引起的链接错误.从ADS到KEIL很重要的一个修改的地方就是这里的8BYTE对齐,想要编译通过,在startup.s里面我们必须加入PRESERVE8指令,使得寄存器8BYTE对齐.代码:CODE32PRESERVE8 ;这个在KEIL里面是必须的,要求8BYTE对齐.在ADS的启动代码中就没有.AREA vectors,CODE,READONLY2: ARM的处理器可工作于多种模式,下面设置个模式的一些参数.Mode_USR EQU 0x10 用户模式Mode_FIQ EQU 0x11快中断模式Mode_IRQ EQU 0x12中断模式Mode_SVC EQU 0x13 管理模式Mode_ABT EQU 0x17 中止模式Mode_UND EQU 0x1B 未定义模式Mode_SYS EQU 0x1F系统模式参数的由来:这里各个模式的参数是由寄存器CPSR的模式位设置M[4:0]得来的,比如这里的用户模式,CPSR的M[4:0]设置为10000就是0x10,同理其他.详见<<ARM嵌入式系统基础教程>>P47页,CPSR设置很关键!3:I_Bit EQU 0x80 ; when I bit is set, IRQ is disabledF_Bit EQU 0x40 ; when F bit is set, FIQ is disabled也和CPSR寄存器的设置有关,这里两位是禁止/开启快速中断和一般中断的设置.4: 各模式下定义的堆栈地址.UND_Stack_Size EQU 0x00000000SVC_Stack_Size EQU 0x00000100ABT_Stack_Size EQU 0x00000000FIQ_Stack_Size EQU 0x00000000IRQ_Stack_Size EQU 0x00000100USR_Stack_Size EQU 0x00000200设置堆栈大小Stack_Size EQU (UND_Stack_Size + SVC_Stack_Size + ABT_Stack_Size +FIQ_Stack_Size +IRQ_Stack_Size + USR_Stack_Size)AREA STACK, NOINIT, READWRITE, ALIGN=3Stack_Mem SPACE Stack_SizeStack_Top EQU Stack_Mem + Stack_Size堆栈大小的设置,各公司写的启动代码有所不同,但是不影响大局,可以借鉴一些你认为比较简单的启动代码,然后写自己的堆栈地址和大小设置程序.5:堆的设置Heap_Size EQU 0x00000000AREA HEAP, NOINIT, READWRITE, ALIGN=3Heap_Mem SPACE Heap_SizeAREA Init,CODE,READONLY,ALIGN=3 //指定后面的指令为8位对齐(2的3次方) align n它的含义就是使得下面的代码按一定规则对齐,.align n 指令的对齐值有两种方案,n 或 2^n ,各种平台最初的汇编器一般都不是gas,采取方案1或2的都很多,gas的目标是取代原来的汇编器,必然要保持和原来汇编器的兼容,因此在gas中如何解释 .align指令会显得有些混乱,原因在于保持兼容。
ARM 启动代码详解(Vectors.c、Init.s、Target.c、Target.h)启动代码是芯片复位后进入C语言的main()函数前执行的一段代码,主要是为运行C语言程序提供基本运行环境,如初始化存储器系统等。
ARM公司只设计内核,不自己生产芯片,只是把内核授权给其它厂商,其它厂商购买了授权且加入自己的外设后生产出各具特色的芯片。
这样就促进了基于ARM处理器核的芯片多元化,但也使得每一种芯片的启动代码差别很大,不易编写出统一的启动代码。
ADS(针对ARM 处理器核的C语言编译器)的策略是不提供完整的启动代码,启动代码不足部分或者由厂商提供,或者自己编写。
启动代码划分为4个文件:Vectors.c、Init.s、Target.c、Target.h。
Vectors.c包含异常向量表、堆栈初始化及中断服务程序与C程序的接口。
Init.s包含统初始化代码,并跳转到ADS提供的初始化代码。
Target.c和Target.h包含目标板特殊的代码,包括异常处理程序和目标板初始化程序。
这样做的目的是为了尽量减少汇编代码,同时把不需要修改的代码独立出来以减少错误。
§4.2.1 Vectors.c文件的编写§4.2.1.1 中断向量表VectorsLDR PC, ResetAddrLDR PC, UndefinedAddrLDR PC, SWI_AddrLDR PC, PrefetchAddrLDR PC, DataAbortAddrDCD 0xb9205f80LDR PC, [PC, #-0xff0]LDR PC, FIQ_AddrResetAddr DCD ResetUndefinedAddr DCD UndefinedSWI_Addr DCD SoftwareInterruptPrefetchAddr DCD PrefetchAbortDataAbortAddr DCD DataAbortnouse DCD 0IRQ_Addr DCD IRQ_HandlerFIQ_Addr DCD FIQ_Handler异常是由内部或外部源产生的以引起处理器处理的一个事件。
PART1系统初始化流程如下:禁止看门狗----》在中断控制器中屏蔽所有中断----》系统时钟设置----》初始化端口----》DMA设置----》cashe和总线设置----》存储器设置,初始化SDRAM----》初始化堆栈----》设置IRQ和FIQ的入口----》地址重映射。
必须由汇编来完成的任务有:异常中断向量表的设置、IRQ向量表(向量模式或ISR 初始化(非向量模式、二级ISR地址表的定义、Flash和SDRAM的设置(否则系统无法加载代码、堆栈设置和模式切换、拷贝RW和ZI代码、设置系统时钟等。
对于非向量模式,不使用IRQ中断向量表,但二级ISR地址表的设置是相同的。
PART2理解启动代码(ADS所谓启动代码,就是处理器在启动的时候执行的一段代码,主要任务是初始化处理器模式,设置堆栈,初始化变量等等.由于以上的操作均与处理器体系结构和系统配置密切相关,所以一般由汇编来编写.具体到S64,启动代码分成两部分,一是与ARM7TDMI内核相关的部分,包括处理器各异常向量的配置,各处理器模式的堆栈设置,如有必要,复制向量到RAM,以便remap之后处理器正确处理异常,初始化数据(包括RW与ZI,最后跳转到Main.二是与处理器外部设备相关的部分,这和厂商的联系比较大.虽然都采用了ARM7TDMI的内核,但是不同的厂家整合了不同的片上外设,需要不同的初始化,其中比较重要的是初始化WDT,初始化各子系统时钟,有必要的话,进行remap.这一部分与一般控制器的初始化类似,因此,本文不作重点描述.在进行分析之前,请确认如下相关概念:S64片上FLASH起始于0x100000,共64kB,片上RAM起始于0x200000,共16kB.S64复位之后,程序会从0开始执行,此时FLASH被映射到0地址,因此,S64可以取得指令并执行.显然,此时还是驻留在0x100000地址.如果使用remap命令,将会把RAM映射到0地址,同样的这时0地址的内容也只是RAM的镜像.S64的FLASH可以保证在最差情况时以30MHz进行单周期访问,而RAM可以保证在最大速度时的单周期访问.OK,以下开始分析启动代码.一,处理器异常S64将异常向量至于0地址开始的几个直接,这些是必需要处理的.由于复位向量位于0,也需要一条跳转指令.具体代码如下:RESETB SYSINIT ; ResetB UDFHANDLER ; UNDEFINEDB SWIHANDLER ; SWIB PABTHANDLER ; PREFETCH ABORTB DABTHANDLER ; DATA ABORTB . ; RESERVEDB VECTORED_IRQ_HANDLERB . ; ADD FIQ CODE HERE UDFHANDLERB . SWIHANDLERB . PABTHANDLERB . DABTHANDLERB .请注意,B指令经汇编后会替换为当前PC值加上一个修正值(+/-,所以这条指令是代码位置无关的,也就是不管这条指令是在0地址还是在0x100000执行,都能跳转到指定的位置,而LDR PC,=???将向PC直接装载一个标号的值,请注意,标号在编译过后将被替换为一个与RO 相对应的值,也就是说,这样的指令无论在哪里执行,都只会跳转到一个指定的位置.下面举一个具体的例子来说明两者的区别: 假定有如下程序:RESETB INIT 或者LDR PC,=INIT…INIT…其中RESET为起始时的代码,也就是这条代码的偏移为0,设INIT的偏移量为offset.如果将这段程序按照RO=0x1000000编译, 那么B INIT可理解为ADD PC, PC, #offset,而LDR PC,=INIT可被理解为MOV PC,#(RO+offset .显然当系统复位时,程序从0开始运行,而0地址有FLASH的副本,执行B INIT将把PC指向位于0地址处的镜像代码位置,也即INIT;如果执行LDR PC,=INIT将会将PC 直接指向位于FLASH中的原始代码.因此以上两者都能正确运行.下面将RO设置为0x200000,编译后生成代码,还是得烧写到FLASH中,也就是还是0x100000,系统复位后从0地址执行,还是FLASH的副本,此时执行B INIT,将跳到副本中的INIT位置执行,此处有对应的代码;但是如果执行LDR PC,=INIT,将向PC加载0x200000+offset,这将使得PC跳到RAM中,而此时由于代码没有复制,RAM中的指定位置并没有代码,程序无法运行.二,处理器模式ARM的处理器可工作于多种模式,不同模式有不同的堆栈,以下设置各模式及其堆栈.预定义一些参数:MODUSR EQU 0x10MODSYS EQU 0x1FMODSVC EQU 0x13MODABT EQU 0x17MODUDF EQU 0x1BMODIRQ EQU 0x12MODFIQ EQU 0x11IRQBIT EQU 0x80FIQBIT EQU 0x40RAMEND EQU 0x00204000 ; S64 : 16KB RAMVECTSIZE EQU 0x100 ;UsrStkSz EQU 8 ; size of USR stack SysStkSz EQU 128 ; size of SYS stack SvcStkSz EQU 8 ; size of SVC stack UdfStkSz EQU 8 ; size of UDF stack AbtStkSz EQU 8 ; size of ABT stack IrqStkSz EQU 128 ; size of IRQ stack FiqStkSz EQU 16 ; size of FIQ stack修改这些值即可修改相应模式堆栈的尺寸.以下为各模式代码:SYSINIT;MRS R0,CPSRBIC R0,R0,#0x1FMOV R2,#RAMENDORR R1,R0,#(MODSVC :OR: IRQBIT :OR: FIQBIT MSR cpsr_cxsf,R1 ; ENTER SVC MODEMOV sp,R2SUB R2,R2,#SvcStkSzORR R1,R0,#(MODFIQ :OR: IRQBIT :OR: FIQBIT MSR CPSR_cxsf,R1 ; ENTER FIQ MODEMOV sp,R2SUB R2,R2,#FiqStkSzORR R1,R0,#(MODIRQ :OR: IRQBIT :OR: FIQBIT MSR CPSR_cxsf,R1 ; ENTER IRQ MODEMOV sp,R2SUB R2,R2,#IrqStkSzORR R1,R0,#(MODUDF :OR: IRQBIT :OR: FIQBITMSR CPSR_cxsf,R1 ; ENTER UDF MODEMOV sp,R2SUB R2,R2,#UdfStkSzORR R1,R0,#(MODABT :OR: IRQBIT :OR: FIQBITMSR CPSR_cxsf,R1 ; ENTER ABT MODEMOV sp,R2SUB R2,R2,#AbtStkSz;ORR R1,R0,#(MODUSR :OR: IRQBIT :OR: FIQBIT;MSR CPSR_cxsf,R1 ; ENTER USR MODE;MOV sp,R2;SUB R2,R2,#UsrStkSzORR R1,R0,#(MODSYS :OR: IRQBIT :OR: FIQBITMSR CPSR_cxsf,R1 ; ENTER SYS MODEMOV sp,R2 ;三,初始化变量编译完成之后,连接器会生成三个基本的段,分别是RO,RW,ZI,并会在image中顺序摆放.显然,RW,ZI在运行开始时并不位于指定的RW位置,因此必须初始化LDR R0,=|Image$$RO$$Limit|LDR R1,=|Image$$RW$$Base|LDR R2,=|Image$$ZI$$Base|1CMP R1,R2LDRLO R3,[R0],#4STRLO R3,[R1],#4BLO %B1MOV R3,#0LDR R1,=|Image$$ZI$$Limit|2CMP R2,R1STRLO R3,[R2],#4BLO %B2四,复制异常向量由于代码于RAM运行时,有明显的速度优势,而且变量可以动态配置,因此可以通过remap将RAM映射到0,使得出现异常时ARM从RAM中取得向量.IMPORT |Image$$RO$$Base|IMPORT |Image$$RO$$Limit|IMPORT |Image$$RW$$Base|IMPORT |Image$$RW$$Limit|IMPORT |Image$$ZI$$Base|IMPORT |Image$$ZI$$Limit|COPY_VECT_TO_RAMLDR R0,=|Image$$RO$$Base|LDR R1,=SYSINITLDR R2,=0x200000 ; RAM STARTCMP R0,R1LDRLO R3,[R0],#4STRLO R3,[R2],#4BLO %B0这段程序将SYSINIT之前的代码,也就是异常处理函数,全部复制到RAM中, 这就意味着不能将RW设置为0x200000,这样会使得向量被冲掉.四,在RAM中运行如果有必要,且代码足够小,可以将代码置于RAM中运行,由于RAM中本身没有代码,就需要将代码复制到RAM中:COPY_BEGINLDR R0,=0x200000LDR R1,=RESET ; =|Image$$RO$$Base|CMP R1,R0 ;BLO COPY_END ;ADR R0,RESETADR R2,COPY_ENDSUB R0,R2,R0ADD R1,R1,R0LDR R3,=|Image$$RO$$Limit|3CMP R1,R3LDRLO R4,[R2],#4STRLO R4,[R1],#4BLO %B3LDR PC,=COPY_ENDCOPY_END程序首先取得RESET的连接地址,判断程序是否时是在RAM中运行,方法是与RAM起始地址比较,如果小于,那么就跳过代码复制.在复制代码的时候需要注意,在这段程序结束之前的代码没有必要复制,因为这些代码都已经执行过了,所以,先取得COPY_END,作为复制起始地址,然后计算其相对RESET的偏移,然后以RO的值加上这个偏移,就是复制目的地的起始地址,然后开始复制.五,开始主程序以上步骤完成,就可以跳转到main运行IMPORT MainLDR PC,=MainB .六,器件初始化主程序首先要进行器件的初始化,对S64而言,应该先初始化WDT,因为默认情况下,WDT是打开的,然后是各设备的时钟分配,最后应该remap.以上是必要的启动步骤,实际应用中可以根据具体情况添加一些代码.PART3ARM启动程序分析字体: 小中大 | 打印发布: 2007-7-17 11:05 作者: 网络转载来源: 网络查看: 4次ARM启动程序分析TIMER1用来触发和IRQ中断代码在FLASH中运行这个例子有一下几个文件:1. 中断向量表(ivt.s2. 汇编启动代码 (init.s3. C主函数文件中断向量表;代码必须链接在地址0X0。