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() 语句对称使用,表明汇编程序结束。


(此篇完)

你可能感兴趣的:(操作系统,驱动程序,BSP,SylixOS)