arm啟動代碼詳細分析
所謂啟動代碼,就是處理器在啟動的時候執行的一段代碼,主要任務是初始化處理器模式,設置堆棧,初始化變量等等.由于以上的操作均與處理器體系結構和系統配置密切相關,所以一般由匯編來編寫.
具體到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,也需要一條跳轉指令.具體代碼如下:
RESET
B SYSINIT ; Reset
B UDFHANDLER ; UNDEFINED
B SWIHANDLER ; SWI
B PABTHANDLER ; PREFETCH ABORT
B DABTHANDLER ; DATA ABORT
B . ; RESERVED
B VECTORED_IRQ_HANDLER
B . ; ADD FIQ CODE HERE
UDFHANDLER
B .
SWIHANDLER
B .
PABTHANDLER
B .
DABTHANDLER
B .
請注意,B指令經匯編后會替換為當前PC值加上一個修正值(+/-),所以這條指令是代碼位置無關的,也就是不管這條指令是在0地址還是在 0x100000執行,都能跳轉到指定的位置,而LDR PC,=???將向PC直接裝載一個標號的值,請注意,標號在編譯過后將被替換為一個與RO相對應的值,也就是說,這樣的指令無論在哪里執行,都只會跳轉到一個指定的位置.下面舉一個具體的例子來說明兩者的區別:
假定有如下程序:
RESET
B 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 0x10
MODSYS EQU 0x1F
MODSVC EQU 0x13
MODABT EQU 0x17
MODUDF EQU 0x1B
MODIRQ EQU 0x12
MODFIQ EQU 0x11
IRQBIT EQU 0x80
FIQBIT EQU 0x40
RAMEND EQU 0x00204000 ; S64 : 16KB RAM
VECTSIZE 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,CPSR
BIC R0,R0,#0x1F
MOV R2,#RAMEND
ORR R1,R0,#(MODSVC :OR: IRQBIT :OR: FIQBIT)
MSR cpsr_cxsf,R1 ; ENTER SVC MODE
MOV sp,R2
SUB R2,R2,#SvcStkSz
ORR R1,R0,#(MODFIQ :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER FIQ MODE
MOV sp,R2
SUB R2,R2,#FiqStkSz
ORR R1,R0,#(MODIRQ :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER IRQ MODE
MOV sp,R2
SUB R2,R2,#IrqStkSz
ORR R1,R0,#(MODUDF :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER UDF MODE
MOV sp,R2
SUB R2,R2,#UdfStkSz
ORR R1,R0,#(MODABT :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER ABT MODE
MOV sp,R2
SUB R2,R2,#AbtStkSz
;ORR R1,R0,#(MODUSR :OR: IRQBIT :OR: FIQBIT)
;MSR CPSR_cxsf,R1 ; ENTER USR MODE
;MOV sp,R2
;SUB R2,R2,#UsrStkSz
ORR R1,R0,#(MODSYS :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER SYS MODE
MOV sp,R2 ;
三,初始化變量
編譯完成之后,連接器會生成三個基本的段,分別是RO,RW,ZI,并會在image中順序擺放.顯然,RW,ZI在運行開始時并不位于指定的RW位置,因此必須初始化
LDR R0,=|Image$$RO$$Limit|
LDR R1,=|Image$$RW$$Base|
LDR R2,=|Image$$ZI$$Base|
1
CMP R1,R2
LDRLO R3,[R0],#4
STRLO R3,[R1],#4
BLO ?
MOV R3,#0
LDR R1,=|Image$$ZI$$Limit|
2
CMP R2,R1
STRLO R3,[R2],#4
BLO ?
四,復制異常向量
由于代碼于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_RAM
LDR R0,=|Image$$RO$$Base|
LDR R1,=SYSINIT
LDR R2,=0x200000 ; RAM START
0
CMP R0,R1
LDRLO R3,[R0],#4
STRLO R3,[R2],#4
BLO ?
這段程序將SYSINIT之前的代碼,也就是異常處理函數,全部復制到RAM中, 這就意味著不能將RW設置為0x200000,這樣會使得向量被沖掉.
四,在RAM中運行
如果有必要,且代碼足夠小,可以將代碼置于RAM中運行,由于RAM中本身沒有代碼,就需要將代碼復制到RAM中:
COPY_BEGIN
LDR R0,=0x200000
LDR R1,=RESET ; =|Image$$RO$$Base|
CMP R1,R0 ;
BLO COPY_END ;
ADR R0,RESET
ADR R2,COPY_END
SUB R0,R2,R0
ADD R1,R1,R0
LDR R3,=|Image$$RO$$Limit|
3
CMP R1,R3
LDRLO R4,[R2],#4
STRLO R4,[R1],#4
BLO ?
LDR PC,=COPY_END
COPY_END
程序首先取得RESET的連接地址,判斷程序是否時是在RAM中運行,方法是與RAM起始地址比較,如果小于,那么就跳過代碼復制.
在復制代碼的時候需要注意,在這段程序結束之前的代碼沒有必要復制,因為這些代碼都已經執行過了,所以,先取得COPY_END,作為復制起始地址,然后計算其相對RESET的偏移,然后以RO的值加上這個偏移,就是復制目的地的起始地址,然后開始復制.
五,開始主程序
以上步驟完成,就可以跳轉到main運行
IMPORT Main
LDR PC,=Main
B .
六,器件初始化
主程序首先要進行器件的初始化,對S64而言,應該先初始化WDT,因為默認情況下,WDT是打開的,然后是各設備的時鐘分配,最后應該remap
具體到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,也需要一條跳轉指令.具體代碼如下:
RESET
B SYSINIT ; Reset
B UDFHANDLER ; UNDEFINED
B SWIHANDLER ; SWI
B PABTHANDLER ; PREFETCH ABORT
B DABTHANDLER ; DATA ABORT
B . ; RESERVED
B VECTORED_IRQ_HANDLER
B . ; ADD FIQ CODE HERE
UDFHANDLER
B .
SWIHANDLER
B .
PABTHANDLER
B .
DABTHANDLER
B .
請注意,B指令經匯編后會替換為當前PC值加上一個修正值(+/-),所以這條指令是代碼位置無關的,也就是不管這條指令是在0地址還是在 0x100000執行,都能跳轉到指定的位置,而LDR PC,=???將向PC直接裝載一個標號的值,請注意,標號在編譯過后將被替換為一個與RO相對應的值,也就是說,這樣的指令無論在哪里執行,都只會跳轉到一個指定的位置.下面舉一個具體的例子來說明兩者的區別:
假定有如下程序:
RESET
B 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 0x10
MODSYS EQU 0x1F
MODSVC EQU 0x13
MODABT EQU 0x17
MODUDF EQU 0x1B
MODIRQ EQU 0x12
MODFIQ EQU 0x11
IRQBIT EQU 0x80
FIQBIT EQU 0x40
RAMEND EQU 0x00204000 ; S64 : 16KB RAM
VECTSIZE 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,CPSR
BIC R0,R0,#0x1F
MOV R2,#RAMEND
ORR R1,R0,#(MODSVC :OR: IRQBIT :OR: FIQBIT)
MSR cpsr_cxsf,R1 ; ENTER SVC MODE
MOV sp,R2
SUB R2,R2,#SvcStkSz
ORR R1,R0,#(MODFIQ :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER FIQ MODE
MOV sp,R2
SUB R2,R2,#FiqStkSz
ORR R1,R0,#(MODIRQ :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER IRQ MODE
MOV sp,R2
SUB R2,R2,#IrqStkSz
ORR R1,R0,#(MODUDF :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER UDF MODE
MOV sp,R2
SUB R2,R2,#UdfStkSz
ORR R1,R0,#(MODABT :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER ABT MODE
MOV sp,R2
SUB R2,R2,#AbtStkSz
;ORR R1,R0,#(MODUSR :OR: IRQBIT :OR: FIQBIT)
;MSR CPSR_cxsf,R1 ; ENTER USR MODE
;MOV sp,R2
;SUB R2,R2,#UsrStkSz
ORR R1,R0,#(MODSYS :OR: IRQBIT :OR: FIQBIT)
MSR CPSR_cxsf,R1 ; ENTER SYS MODE
MOV sp,R2 ;
三,初始化變量
編譯完成之后,連接器會生成三個基本的段,分別是RO,RW,ZI,并會在image中順序擺放.顯然,RW,ZI在運行開始時并不位于指定的RW位置,因此必須初始化
LDR R0,=|Image$$RO$$Limit|
LDR R1,=|Image$$RW$$Base|
LDR R2,=|Image$$ZI$$Base|
1
CMP R1,R2
LDRLO R3,[R0],#4
STRLO R3,[R1],#4
BLO ?
MOV R3,#0
LDR R1,=|Image$$ZI$$Limit|
2
CMP R2,R1
STRLO R3,[R2],#4
BLO ?
四,復制異常向量
由于代碼于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_RAM
LDR R0,=|Image$$RO$$Base|
LDR R1,=SYSINIT
LDR R2,=0x200000 ; RAM START
0
CMP R0,R1
LDRLO R3,[R0],#4
STRLO R3,[R2],#4
BLO ?
這段程序將SYSINIT之前的代碼,也就是異常處理函數,全部復制到RAM中, 這就意味著不能將RW設置為0x200000,這樣會使得向量被沖掉.
四,在RAM中運行
如果有必要,且代碼足夠小,可以將代碼置于RAM中運行,由于RAM中本身沒有代碼,就需要將代碼復制到RAM中:
COPY_BEGIN
LDR R0,=0x200000
LDR R1,=RESET ; =|Image$$RO$$Base|
CMP R1,R0 ;
BLO COPY_END ;
ADR R0,RESET
ADR R2,COPY_END
SUB R0,R2,R0
ADD R1,R1,R0
LDR R3,=|Image$$RO$$Limit|
3
CMP R1,R3
LDRLO R4,[R2],#4
STRLO R4,[R1],#4
BLO ?
LDR PC,=COPY_END
COPY_END
程序首先取得RESET的連接地址,判斷程序是否時是在RAM中運行,方法是與RAM起始地址比較,如果小于,那么就跳過代碼復制.
在復制代碼的時候需要注意,在這段程序結束之前的代碼沒有必要復制,因為這些代碼都已經執行過了,所以,先取得COPY_END,作為復制起始地址,然后計算其相對RESET的偏移,然后以RO的值加上這個偏移,就是復制目的地的起始地址,然后開始復制.
五,開始主程序
以上步驟完成,就可以跳轉到main運行
IMPORT Main
LDR PC,=Main
B .
六,器件初始化
主程序首先要進行器件的初始化,對S64而言,應該先初始化WDT,因為默認情況下,WDT是打開的,然后是各設備的時鐘分配,最后應該remap
評論