嵌入式linux启动代码详解.docx
《嵌入式linux启动代码详解.docx》由会员分享,可在线阅读,更多相关《嵌入式linux启动代码详解.docx(30页珍藏版)》请在冰豆网上搜索。
嵌入式linux启动代码详解
1、程序入口
•初始化程序中必须指明入口地址,当处理器复位(仿真时,装载image文件)后PC要找到入口开始执行代码,当各
种异常(或中断)产生时也要找到各个异常的入口开始执行代码。
•AREAInit,CODE,READONLY
•ENTRY
•这里使用AREA伪操作定义一个代码段Init,使用ENTRY伪操作定义程序的入口。
ENTRY只是定义一个普通的入口
点,且在程序中可以多处定义,它告知链接器,不要在执行优化链接的过程中把这些输入段作为不使用的段删除
掉,如果要使它作为整个映像文件(或称整个程序)的唯一的入口点,还需要设置链接器中的相关选项。
使用
CodeWarriorforARMDeveloperSuite环境打开一个工程项目文件,在Edit->DebugSetting->ARMLinker-
>Option->Imageentrypoint项可以指定映像的入口,它指定的是一个地址,通常我们将它和ROBASE设在相同
的地址,或是空着为系统默认设置。
在Edit->DebugSetting->ARMLinker->Layout->Placeatbeginningof
image项中有Object/Symbol和Section,其中Object/Symbol指定目标文件,这里要写入启动代码的目标文件名,
如2410init.o。
Section指定输入的段名,这里是Init。
这样当编译、链接后,映像文件就有了唯一的程序入口点。
•ASSERT:
DEF:
ENDIAN_CHANGE
•[ENDIAN_CHANGE
•ASSERT:
DEF:
ENTRY_BUS_WIDTH
•[ENTRY_BUS_WIDTH=32
•bChangeBigEndian;DCD0xea000007
•]
•[ENTRY_BUS_WIDTH=16
•andeqr14,r7,r0,lsl#20;DCD0x0007ea00
•]
•[ENTRY_BUS_WIDTH=8
•streqr0,[r0,-r10,ror#1];DCD0x070000ea
•]
•|
•bResetHandler
•]
•这段程序的开始使用ASSERT伪操作判断ENDIAN_CHANGE是否被定义,如果没有定义,ASSERT伪操作将报告
错误类型,并终止汇编。
ENDIAN_CHANGE是在option.s文件中定义的,读者可以用鼠标点击2410init.s文件的编
辑器左上角h标记(如图5.2所示)打开option.s文件查看,另外它还定义了ENTRY_BUS_WIDTH,如下:
•GBLLENDIAN_CHANGE
•ENDIAN_CHANGESETL{FALSE}
•GBLAENTRY_BUS_WIDTH
•ENTRY_BUS_WIDTHSETA16
•图5.2打开option.s文件由ENDIAN_CHANGE和ENTRY_BUS_WIDTH的定义可知,IF伪操作的逻辑表达式为
FALSE,上述的一堆程序也可以只看成是:
•bResetHandler
2.看门狗及中断的禁止
•ResetHandler
•ldrr0,=WTCON;watchdogdisable
•ldrr1,=0x0
•strr1,[r0]
•ldrr0,=INTMSK
•ldrr1,=0xffffffff;allinterruptdisable
•strr1,[r0]
•ldrr0,=INTSUBMSK
•ldrr1,=0x7ff;allsubinterruptdisable,
2002/04/10
•strr1,[r0]
3.测试LED的显示
•[{FALSE}
•;rGPFDAT=(rGPFDAT&~(0xf<<4))|((~data&0xf)<<4);
•;Led_Display
•ldrr0,=GPFCON
•ldrr1,=0x5500
•strr1,[r0]
•ldrr0,=GPFDAT
•ldrr1,=0x10
•strr1,[r0]
•]
4、系统时钟初始化
•;ToreducePLLlocktime,adjusttheLOCKTIMEregister.
•ldrr0,=LOCKTIME
•ldrr1,=0xffffff
•strr1,[r0]
•LOCKTIME为PLL锁定时间计数寄存器(PLLlocktimecountregister)。
当重新设定分频值时,PLL进入锁定,输出稳
定频率的时钟需要一定的时间。
S3C2410A用户手册上给出的值必须大于150us,即(1/Fin)*n>150us,Fin为外部时钟
源频率12MHz,n为U--_LTIME或M_LTIME,所以n>150us*12MHz=1800,此处将LOCKTIME=0xffffff,即U_LTIME和
M_LTIME都设成最大的0xfff,以远远满足锁定要求(0xfff=4096>1800)。
•[PLL_ON_START
•;ConfigureMPLL
•ldrr0,=MPLLCON
•ldrr1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV);Fin=12MHz,Fout=50MHz
•strr1,[r0]
•]
•MPLLCON为主时钟锁相环控制寄存器,用于设置MPLL的分频系数。
MPLL的输出频率Fout=(m*Fin)/(p*2s),其中
m=(M_MDIV+8),p=(M_PDIV+2),s=M_SDIV。
M_MDIV、M_PDIV、M_SDIV都在options.s文件中定义,另外该文件还
定义了PLL_ON_START变量及逻辑初值,以确定该段代码是否有效,定义如下:
•GBLLPLL_ON_START
•PLL_ON_STARTSETL{TRUE}
•……
•GBLAFCLK
•FCLKSETA50000000
•……
•[FCLK=50000000
•M_MDIVEQU0x5c;Fin=12.0MHzFout=50.0MHz
•M_PDIVEQU0x4
•M_SDIVEQU0x2
•]
•由于外接时钟为12MHz,由前述公式得MPLL的输出频率Fout为50MHz。
5、关闭低功耗模式
•;Checkifthebootiscausedbythewake-upfromPOWER_OFFmode.
•ldrr1,=GSTATUS2
•ldrr0,[r1]
•tstr0,#0x2
•;Incaseofthewake-upfromPOWER_OFFmode,goto
POWER_OFF_WAKEUPhandler.
•bneWAKEUP_POWER_OFF
•GSTATUS2为复位状态寄存器,bit0、bit1、bit2分别对应上电复位、掉电模式
(POWER_OFF)复位和看门狗复位。
该段程序判断bit2以确定处理器是否从掉电模
式唤醒,如果是从掉电模式唤醒则跳转到掉电唤醒处理程序。
6、初始化内存控制器
•1》参数配置
•;BANK0CON
•B0_TacsEQU0x0;0clk
•B0_TcosEQU0x0;0clk
•B0_TaccEQU0x7;14clk
•B0_TcohEQU0x0;0clk
•B0_TahEQU0x0;0clk
•B0_TacpEQU0x0
•B0_PMCEQU0x0;normal
•;Bank6parameter
•B6_MTEQU0x3;SDRAM
•;B6_TrcdEQU0x0;2clk
•B6_TrcdEQU0x1;3clk
•B6_SCANEQU0x1;9bit
•……
•;REFRESHparameter
•REFENEQU0x1;Refreshenable
•TREFMDEQU0x0;CBR(CASbeforeRAS)/Autorefresh
•TrpEQU0x0;2clk
•TrcEQU0x3;7clk
•TchrEQU0x2;3clk
•REFCNTEQU1113;period=15.6us,HCLK=60Mhz,(2048+1-15.6*60)
•END
•该部分的详细内容请参考memcfg.s文件,它直观的定义了内存控制器相关特殊寄存器的所有功能位的具体数值,当
读者初次扩展一个外设或希望提高某个外设的读写速度时,就可以修改该文件的这些具体时序参数。
•2》功能寄存器数值表的定义
•SMRDATADATA
•DCD
(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20
)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
•DCD
((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
;GCS0
•DCD
((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
;GCS1
•DCD
((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
;GCS2
•DCD
((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
;GCS3
•DCD
((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
;GCS4
•DCD
((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
;GCS5
•DCD((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN));GCS6
•DCD((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN));GCS7
•DCD((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
•DCD0x32;SCLKpowersavingmode,BANKSIZE128M/128M
•DCD0x30;MRSR6CL=3clk
•DCD0x30;MRSR7
•这段程序的开始使用DATA伪操作指明SMRDATA标号处为一段数据,而非代码。
接下来就都用DCD分配一个字
的内存单元,且初始化成由memcfg.s文件中定义的参数运算得出的各寄存器值,共13个字(52字节)数据。
•3》功能寄存器初始化
•;Setmemorycontrolregisters
•ldrr0,=SMRDATA
•ldrr1,=BWSCON;BWSCONAddress
•addr2,r0,#52;EndaddressofSMRDATA
•0
•ldrr3,[r0],#4
•strr3,[r1],#4
•cmpr2,r0
•bne%B0
•这段代码就是将上节定义的功能寄存器数值表的数据依次传送给实践的内存
控制器的每个特殊功能寄存器,以达到对它们的初始化目的。
7、堆栈初始化
•1》堆栈地址区间定义
•;Startaddressofeachstacks,
•_STACK_BASEADDRESSEQU0x33ff8000
•……
•;Thelocationofstacks
•UserStackEQU(_STACK_BASEADDRESS-0x3800);0x33ff4800~
•SVCStackEQU(_STACK_BASEADDRESS-0x2800);0x33ff5800~
•UndefStackEQU(_STACK_BASEADDRESS-0x2400);0x33ff5c00~
•AbortStackEQU(_STACK_BASEADDRESS-0x2000);0x33ff6000~
•IRQStackEQU(_STACK_BASEADDRESS-0x1000);0x33ff7000~
•FIQStackEQU(_STACK_BASEADDRESS-0x0);0x33ff8000~
•堆栈基地址定义“_STACK_BASEADDRESSEQU0x33ff8000”位于option.s文件,
而各种模式下的堆栈指针地址直接在2410init.s文件的开始处定义。
0x33ff8000地址是
属于Bank6的64MSDRAM空间,如果读者系统的SDRAM不同需要重新修改该地址,
以指向有效的SDRAM地址空间。
2》各种模式的堆栈指针初始化
•;Initializestacks
•blInitStacks
•……
•;functioninitializingstacks
•InitStacks
•mrsr0,cpsr
•bicr0,r0,#MODEMASK
•orrr1,r0,#UNDEFMODE|NOINT
•msrcpsr_cxsf,r1;UndefMode
•ldrsp,=UndefStack
•程序先能过mrs指令将状态寄存器值读取到r0,然后将r0对应的处理器模式位
修改成未定义指令中止模式,再写回状态寄存器使处理器真正切换到未定义
指令中止模式,这也就是所谓的“读出-修改-写回”的方式来修改状态寄存器的
内容。
最后将该模式的规模指针sp指向UndefStack定义的地址。
下述其它模
式的操作方法也是相同的。
8、中断向量表初始化
•1》中断入口
•AREAInit,CODE,READONLY
•ENTRY
•bResetHandler
•bHandlerUndef;handlerforUndefinedmode
•bHandlerSWI;handlerforSWIinterrupt
•bHandlerPabort;handlerforPAbort
•bHandlerDabort;handlerforDAbort
•b.;reserved
•bHandlerIRQ;handlerforIRQinterrupt
•bHandlerFIQ;handlerforFIQinterrupt
•这部分是位于0x0地址开始的连续32字节的各个中断(也称异常)的入口,每个中断占
用4字节的存储空间,对于S3C2410A这些中断入口地址是固定和唯一的,类似于51等
系列单片机的中断入口。
由于4字节只能放置一条ARM指令,所以总是一条跳转指令使
程序跳转到存储器的其它地方再去作进一步处理。
2》中断服务程序入口地址表
•_ISR_STARTADDRESSEQU0x33ffff00
•……
•该处是在option.s文件中定义的用以保存中断服务程序入口地址的内存表的基地址。
•AREARamData,DATA,READWRITE
•^_ISR_STARTADDRESS
•HandleReset#4
•HandleUndef#4
•HandleSWI#4
•HandlePabort#4
•HandleDabort#4
•HandleReserved#4
•HandleIRQ#4
•HandleFIQ#4
•;Don'tusethelabel'IntVectorTable',
•;ThevalueofIntVectorTableisdifferentwiththeaddressyouthinkitmaybe.
•;IntVectorTable
•HandleEINT0#4
•HandleEINT1#4
•……
•HandleRTC#4
•HandleADC#4
•该处是在2410init.s文件中定义的,先用AREA伪操作定义可读写的数据段,又用MAP(^)伪操作定义首
地址为0x33ffff00(_ISR_STARTADDRESS)的结构化内存表,用FIELD(#)定义内存表中的数据域。
其它指令往往将通过引用FIELD前的标号(如:
HandleIR、HandleFIQ、HandleEINT0等)等来写入和读
出对应的中
•写入和读出对应的中断服务程序的入口地址,如下:
•;SetupIRQhandler
•ldrr0,=HandleIRQ;Thisroutineisneeded
•ldrr1,=IsrIRQ;ifthereisn't'subspc,lr,#4'at0x18,0x1c
•strr1,[r0]
•该处将IRQ中断处理程序的IsrIRQ标号地址保存到内存表中的HandleIR数据域中。
•HandlerIRQ
•subsp,sp,#4;decrementsp(tostorejumpaddress)
•stmfdsp!
{r0};PUSHtheworkregistertostack(lrdoes'tpushbecauseitreturn
•tooriginaladdress)
•ldrr0,=HandleIRQ;loadtheaddressofHandleXXXtor0
•ldrr0,[r0];loadthecontents(serviceroutinestartaddress)ofHandleXXX
•strr0,[sp,#4];storethecontents(ISR)ofHandleXXXtostack
•ldmfdsp!
{r0,pc};POPtheworkregisterandpc(jumptoISR)
•该处是从IRQ中断入口(0x18)跳转执行的程序,它从内存表的HandleIRQ数据域中取IRQ中断
处理程序入口地址(IsrIRQ)给PC,实现进入IsrIRQ程序执行目的。
图5.4中断源的INTOFFSET偏移值
•……
•#define_ISR_STARTADDRESS0x33ffff00
•……
•该处是在option.h文件中定义的用以保存中断服务程序地址的内存表的基地址。
它要和
汇编文件中的_ISR_STARTADDRESS值一样,因为它们代表的是同一个表,自然也就
是同一个物理地址,只是他们一个在汇编文件中使用,一个在C文件中使用而已。
•#definepISR_RESET(*(unsigned*)(_ISR_STARTADDRESS+0x0))
•#definepISR_UNDEF(*(unsigned*)(_ISR_STARTADDRESS+0x4))
•#definepISR_SWI(*(unsigned*)(_ISR_STARTADDRESS+0x8))
•#definepISR_PABORT(*(unsigned*)(_ISR_STARTADDRESS+0xc))
•#definepISR_DABORT(*(unsigned*)(_ISR_STARTADDRESS+0x10))
•#definepISR_RESERVED(*(unsigned*)(_ISR_STARTADDRESS+0x14))
•#definepISR_IRQ(*(unsigned*)(_ISR_STARTADDRESS+0x18))
•#definepISR_FIQ(*(unsigned*)(_ISR_STARTADDRESS+0x1c))
•#definepISR_EINT0(*(unsigned*)(_ISR_STARTADDRESS+0x20))
•#definepISR_EINT1(*(unsigned*)(_ISR_STARTADDRESS+0x24))
•……
•#definepISR_RTC(*(unsigned*)(_ISR_STARTADDRESS+0x98))
•#definepISR_ADC(*(unsigned*)(_ISR_STARTADDRESS+0x9c))
•该处是在2410addr.h文件中定义的,它同样和汇编文件(2410init.s)的用以保存中断服
务程序入口地址的内存表是同一个。
通常在中断初始化程序中,将中断服务程序的入口地址对它们进行初始化,如下:
•pISR_UNDEF=(unsigned)HaltUndef;
•pISR_SWI=(unsigned)HaltSwi;
•pISR_PABORT=(unsigned)HaltPabort;
•pISR_DABORT=(unsigned)HaltDabort;
•……
•pISR_FIQ=(U32)T0Int;//Timer0FIQinterrupt
•pISR_TIMER1=(U32)T1Int;//Timer1Interrupt
•等号右边都为各中断服务函数,如:
•voidHaltUndef(void){
•Uart_Printf("Undefinedinstructionexception.\n");
•while
(1);
•}
•……
•staticvoid__irqT0Int(void){//FIQ
•ClearPending(BIT_TIMER0);
•fiqCnt0+=1;
•}
3》中断服务程序的寻找过程
•HandlerFIQHANDLERHandleFIQ
•HandlerIRQHANDLERHandleIRQ
•HandlerUndefHANDLERHandleUndef
•HandlerSWIHANDLERHandleSWI
•Ha