SylixOS ARM BSP 第二篇【startup.S】

此篇博客爲 SylixOS ARM BSP 編寫連載的第二篇,主要介紹 startup.S 文件具體實現。

startup.S 爲 BSP 啓動代碼入口,通常由 bootloader 裝載完 SylixOS 鏡像後調用,下面以 S3C2440A 處理器爲例,逐塊介紹 startup.S 代碼。

#ifndef ASSEMBLY
#define ASSEMBLY 1
#endif

此段代碼告知後面引用的頭文件,此文件爲彙編程序。

#include <arch/assembler.h>

引用操作系統彙編頭文件,此頭文件根據編譯器和平臺類型,定義了很多編譯器與平臺相關的處理宏,可以統一不同平臺,不同編譯器之間對彙編語言關鍵字的差異。例如 FILE_BEGIN() 宏就定義在此文件,表示彙編語言文件起始。


#define UND_STACK_SIZE  0x00001000
#define ABT_STACK_SIZE  0x00001000
#define FIQ_STACK_SIZE  0x00001000
#define IRQ_STACK_SIZE  0x00001000
#define SVC_STACK_SIZE  0x00001000
#define SYS_STACK_SIZE  0x00001000

這段代碼定義 ARM 各個模式下預設堆棧的大小,一般不需要調整。


IMPORT_LABEL(archIntEntry)
IMPORT_LABEL(archAbtEntry)
IMPORT_LABEL(archPreEntry)
IMPORT_LABEL(archUndEntry)
IMPORT_LABEL(archSwiEntry)
IMPORT_LABEL(sdramInit)
IMPORT_LABEL(targetInit)
IMPORT_LABEL(bspInit)

IMPORT_LABEL()表示此文件需要引用的外部符號,相當於 C 程序中 extern 關鍵字。


SECTION(.vector)

表示之下的所有代碼或者文字池編譯後存放在名爲 .vertor 的節區中,鏈接器根據鏈接腳本,將此節設定爲系統入口。


FUNC_DEF(vector)
    LDR     PC, resetEntry
    LDR     PC, undefineEntry
    LDR     PC, swiEntry
    LDR     PC, prefetchEntry
    LDR     PC, abortEntry
    LDR     PC, reserveEntry
    LDR     PC, irqEntry
    LDR     PC, fiqEntry
    FUNC_END()
    
FUNC_LABEL(resetEntry)
    .word   reset
FUNC_LABEL(undefineEntry)
    .word   archUndEntry
FUNC_LABEL(swiEntry)
    .word   archSwiEntry
FUNC_LABEL(prefetchEntry)
    .word   archPreEntry
FUNC_LABEL(abortEntry)
    .word   archAbtEntry
FUNC_LABEL(reserveEntry)
    .word   0
FUNC_LABEL(irqEntry)
    .word   archIntEntry
FUNC_LABEL(fiqEntry)
    .word   0

FUNC_DEF()表示定義一個函數,這裏定義一個名爲 vector 的函數,根據順序,此函數將是 .vector 節區的入口函數。

FUNC_END()表示函數的結束。

這段代碼是根據 ARM 向量規範編寫的跳轉表,進入不同的異常跳轉到不同的地址,其中 ARM FIQ 快速中斷這裏並沒有處理,說明 SylixOS 默認情況下並不接管 FIQ 異常,用戶如果有需要可自行編寫相關處理函數。

以上代碼我們可以看出,系統復位向量最終跳轉到 reset 函數(或者標號)處,接下來我們看看 reset 函數實現。


SECTION(.text)

與 SECTION(.vector)含義相同,表明以下代碼或者文字池存放在 .text 節區中。這裏需要說明的是一般一個程序至少分爲三個節區(段)分別是代碼段(.text)、數據段(.data)、清零段(.bss)。其中代碼段裏面存放的一般爲程序代碼和運行時不修改的表結構;數據段中主要存放有初值的全局變量;清零段存放着在運行主要程序之前需要清零操作的全局變量。鏈接器根據鏈接腳本來確定鏈接 target 各個節區(段)的內存佈局情況。具體鏈接腳本的編寫,我們放在之後的章節介紹。


FUNC_DEF(reset)
    LDR     R0 , =WTCON
    LDR     R1 , =0x0
    STR     R1 , [R0]

reset 函數首先關閉 2440 處理器內部看門狗,以防止啓動的過程中被看門狗復位。這裏擴展說兩句,如果目標機爲 SMP (對稱多處理器)處理器,例如 ARM Cortex-A9 多核,所有的核程序入口都爲 reset,所以在 reset 函數剛開始就需要判斷此核爲主核(primary)或者從核(secondary),系統啓動時不同的核初始化順序不同,但進入多任務狀態後各個核之間不在有明確的分別。


    LDR     R0 , =0x31800000                   /*  內核高端地址                 */
    MSR     CPSR_c, #(UND32_MODE | DIS_INT)
    MOV     SP , R0
    SUB     R0 , R0, #UND_STACK_SIZE
    MSR     CPSR_c, #(ABT32_MODE | DIS_INT)
    MOV     SP , R0
    SUB     R0 , R0, #ABT_STACK_SIZE
    MSR     CPSR_c, #(IRQ32_MODE | DIS_INT)
    MOV     SP , R0
    SUB     R0 , R0, #IRQ_STACK_SIZE
    MSR     CPSR_c, #(FIQ32_MODE | DIS_INT)
    MOV     SP , R0
    SUB     R0 , R0, #FIQ_STACK_SIZE
    MSR     CPSR_c, #(SVC32_MODE | DIS_INT)
    MOV     SP , R0
    SUB     R0 , R0, #SVC_STACK_SIZE
    MSR     CPSR_c, #(SYS32_MODE | DIS_INT)
    MOV     SP , R0
    SUB     R0 , R0, #SYS_STACK_SIZE
    MSR     CPSR_c, #(SVC32_MODE | DIS_INT)

此段代碼初始化 ARM 處理器各種模式下的堆棧,雖然 ARM 處理器既支持堆棧從高地址向低地址增長,又支持堆棧從低地址向高地址增長,但一般情況下使用高地址向低地址增長這種模式。所以我們將堆棧其實點定義在內核高地址位置,這個地址是根據 bspMap.h 設置的內存佈局獲得的,稍後會在 bspMap.h 裏面詳細介紹。


    BL      sdramInit
    BL      targetInit

初始完 ARM 堆後,我們調用兩個子函數去初始化 2440 的 SDRAM 接口與 PPL 鎖相環,當然針對不同的處理器,這裏的代碼也不盡相同,如果由 bootloader 引導,其實不需要這一部分代碼,bootloader 已經初始化好了相關的參數。


    LDR     R1 , =_etext                    /*  -> ROM data end             */
    LDR     R2 , =_data                     /*  -> data start               */
    LDR     R3 , =_edata                    /*  -> end of data              */
1:
    CMP     R2 , R3                         /*  check if data to move       */
    LDRLO   R0 , [R1] , #4                  /*  copy it                     */
    STRLO   R0 , [R2] , #4
    BLO     1b                              /*  loop until done             */

這部分代碼爲一個區域拷貝函數,我們目前只需要知道這段代碼是給數據段(.data)賦初值就可以了,因爲這裏涉及到鏈接腳本中關於裝載段與運行段不相同時的處理支持,要完全說清楚,篇幅較大,需要讀者自己根據鏈接腳本內容查閱相關知識。當然 RealCoder 會根據用戶 BSP 嚮導中用戶設置的相關參數,自動完成鏈接腳本的生成。


    MOV     R0 , #0                          /*  get a zero                  */
    LDR     R1 , =__bss_start                /*  -> bss start                */
    LDR     R2 , =__bss_end                  /*  -> bss end                  */
2:
    CMP     R1 , R2                          /*  check if data to clear      */
    STRLO   R0 , [R1],   #4                  /*  clear 4 bytes               */
    BLO     2b                               /*  loop until done             */

以上代碼與數據段(.data)賦初值類似,這段代碼是清零段(.bss)清零操作循環。

以上代碼可以得出一個結論:代碼段(.text)是由 bootloader 初始化的,數據段(.data)與清零段(.bss)是由 BSP 入口函數初始化的,當這三個段初始化完畢,我們就可以進入 bspInit() 函數開始正式初始化操作系統,調用 bspInit() 函數代碼如下:

    LDR     R10, =bspInit
    MOV     LR , PC
    BX      R10


startup.S 文件最後有一句 FILE_END(),它是與 FILE_BEGIN() 語句對稱使用,表明彙編程序結束。


(此篇完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章