allwinner h6 armv8 SylixOS 启动分析

在SylixOS为了让代码统一,针对不同的体系结构汇编文件差别,声明了统一的宏。如下是arm64  在内核的头文件中声明:

#define EXPORT_LABEL(label)       .global label
#define IMPORT_LABEL(label)       .extern label

#define FUNC_LABEL(func)          func:
#define LINE_LABEL(line)          line:

#define FUNC_DEF(func)               \
        .balign   8;                 \
        .type     func, %function;   \
func:

#define FUNC_END()                   \
        .ltorg
        
#define MACRO_DEF(mfunc...)          \
        .macro  mfunc
        
#define MACRO_END()                  \
        .endm

#define FILE_BEGIN()                 \
        .text;                       \
        .balign    8;

#define FILE_END()                   \
        .end
        
#define SECTION(sec)                 \
        .section   sec

#define WEAK(sym)                    \
        .weak      sym;

 text代码段第一个执行的函数为reset。

;/*********************************************************************************************************
;  复位入口
;*********************************************************************************************************/
    SECTION(.text)

FUNC_DEF(reset)

;/*********************************************************************************************************
;  关闭 D-Cache(回写并无效)
;  关闭 I-Cache(无效)
;  无效并关闭分支预测
;  关闭 MMU(无效 TLB)
;*********************************************************************************************************/
    BL      arm64DCacheClearAll
    BL      arm64DCacheDisable
    BL      arm64ICacheInvalidateAll
    BL      arm64ICacheDisable
    BL      arm64MmuDisable

在链接脚本中 指定了uboot让系统启动的第一行代码是reset。 BL是跳转指令,跳转到arm64DCacheClearAll 函数。

arm64DCacheClearAll函数在arm64CacheAsm.S 汇编文件中实现。

allwinner h6 armv8 SylixOS 启动分析_第1张图片

arch 下arm64 是arm 64 armv8 架构实现。

 函数 arm64DCacheClearAll 

首先将LR 复制到x14 寄存器,LR是链接寄存器器保存子程序返回地址。将X0 寄存器复制为1

;/*********************************************************************************************************
;  ARMv8 回写并无效 全部 DCACHE
;*********************************************************************************************************/

FUNC_DEF(arm64DCacheClearAll)
    MOV     X14 , LR                                                    ;/*  记录返回地址                */
    MOV     X0  , #1
    BL      arm64DCacheAll
    MOV     LR  , X14
    RET
    FUNC_END()

然后跳转到arm64DCacheAll 函数。 arm64DCacheAll 函数执行完成后,将X14寄存器值返回给 LR。,RET返回调用函数,继续向下执行。这里所以要把LR寄存器值保存到X14,因为调用 子函数arm64DCacheAll 时LR寄存器值会改变。

armv8 64位寄存器发生了一些变化,有34个寄存器,包括31个通用寄存器,sp,pc,spsr。

X0-X30 64bit 通用寄存器,当32bit使用时 W0-W30

LR(X30) 通常称为X30 为程序链接寄存器,保存了子程序结束后需要执行的下一条指令。

FP(X29) 保存栈帧地址。

SP 保存栈指针,使用SP/WSP 来进行对SP寄存器的访问。

PC 程序计数寄存器

CPSR 状态寄存器

零寄存器,通常写为X31

 

FUNC_DEF(arm64DCacheAll)
    MOV     X1  , X0
    ARM_DSB()
    MRS     X10 , CLIDR_EL1                                             ;/*  读取 Cache Level ID         */
    LSR     X11 , X10 , #24                                             ;/*  CLIDR_EL1 24位为 一致性级别 */
    AND     X11 , X11 , #0x7                                            ;/*  获取一致性级别(LOC)       */
    CBZ     X11 , finished                                              ;/*  如果 LOC 为 0,返回         */
    MOV     X15 , LR                                                    ;/*  记录返回地址                */
    MOV     X0  , #0                                                    ;/*  开始 flush level 0 的 Cache */
                                                                        ;/*  X0  <- cache level          */
                                                                        ;/*  X10 <- clidr_el1            */
                                                                        ;/*  X11 <- LOC                  */
                                                                        ;/*  x15 <- return address       */
LINE_LABEL(loop_level)
    LSL     X12 , X0  , #1                                              ;/*  X12 <- Cache level << 1     */
    ADD     X12 , X12 , X0                                              ;/*  X12 <- 3 倍的 Cache level   */
    LSR     X12 , X10 , X12
    AND     X12 , X12 , #7                                              ;/*  X12 记录 Cache Type         */
    CMP     X12 , #2                                                    ;/*  比较 Cache 类型是否为 DCache*/
    B.LT    skip                                                        ;/*  如果不是 DCache 则跳过      */
    BL      arm64DCacheLevel                                            ;/*  X1 = 0 clean only,          */
                                                                        ;/*  X1 = 1 clean and invalidate */
                                                                        ;/*  X1 = 2 invalidate only      */
LINE_LABEL(skip)
    ADD     X0  , X0 , #1                                               ;/*  Cache Level 递增            */
    CMP     X11 , X0                                                    ;/*  比较 Cache Level 到达最大   */
    B.GT    loop_level
    MOV     X0  , #0
    MSR     CSSELR_EL1 , X0                                             ;/*  重置 CSSELR_EL1             */
    ARM_DSB()
    ARM_ISB()
    MOV     LR  , X15
      
LINE_LABEL(finished)
    RET
    FUNC_END()

 ARM_DSB() 实际上是DSB 如下图

在arm v8-A 体系结构手册中,可以查找到定义。但是看了没太看明白他们的差别,一下时网上找到的解释:

他们的区别在于:DMB可以继续执行之后的指令,只要这条指令不是内存访问指令。而DSB不管它后面的什么指令,都会强迫CPU等待它之前的指令执行完毕。其实在很多处理器设计的时候,DMB和DSB没有区别(DMB完成和DSB同样的功能)。

综合起来就是 ISB > DSB>DMB

CLIDR_EL1 是获取cache信息的寄存器,在arm v8-A手册2737页定义如下

allwinner h6 armv8 SylixOS 启动分析_第2张图片

 具体每一位定义可以查看手册。

MRS     X10 , CLIDR_EL1         将cache信息寄存器读到X10 寄存器中,然后 LSR     X11 , X10 , #24   将x10右移了24位放入到了x11寄存器中。  AND     X11 , X11 , #0x7  将x11寄存器的值与0x7 与操作,获得cache一致级别。 CBZ     X11 , finished  比较X11是否为0, 为0直接跳转到finished函数结束。不为0向下执行。保存LR寄存器到X15 ,X0寄存器复制为0

LINE_LABEL(loop_level) 这里是实现了一个循环 。 LSL     X12 , X0  , #1  将x10 寄存器左移1位然后复制给X12.  ADD     X12 , X12 , X0  ADD是相加指令。也就是将X12的值加上X0的值。 LSR     X12 , X10 , X12  将x10右移x12位,然后AND进行与操作。

CMP 是比较指令,这里判断是否小于2, CMP由于只是做减法,影响标志位,所以需要使用BLT指令,BLT指令是小于跳转。综合的这两句的意思是如果小于2,就跳转到skip。

LINE_LABEL(skip)
    ADD     X0  , X0 , #1                                               ;/*  Cache Level 递增            */
    CMP     X11 , X0                                                    ;/*  比较 Cache Level 到达最大   */
    B.GT    loop_level

可以看到skip 这里把x0加一,比对是否大于了x11,x11中保存了cache一致性等级。不大于则返回loop_level 继续进行循环。为什么比对小于2,有加1 接着循环。需要看armv8 -A CCSIDR_EL1寄存器。

allwinner h6 armv8 SylixOS 启动分析_第3张图片

  

根据手册,cache最高是7层,当小于2时是无cache或者只有指令cache。大于等于2才有data cache。

判断存在data cache时调用arm64DCacheLevel 函数。

;/*********************************************************************************************************
;  ARMv8 DCACHE All 相关操作
;*********************************************************************************************************/

FUNC_DEF(arm64DCacheLevel)
    LSL     X12 , X0 , #1                                               ;/*  CSSELR_EL1 3-1 为 Level 选择*/
    MSR     CSSELR_EL1 , X12                                            ;/*  选择 Cache Level            */
    ARM_ISB()                                                           ;/*  同步 CCSIDR_EL1             */
    MRS     X6 , CCSIDR_EL1                                             ;/*  读取 CCSIDR_EL1             */
    AND     X2 , X6 , #7                                                ;/*  CCSIDR_EL1 2-0 为 LineSize-4*/
    ADD     X2 , X2 , #4                                                ;/*  X2 记录 LineSize            */
    MOV     X3 , #0x3ff                                                 ;/*  CCSIDR_EL1 12-3  为 ways - 1*/
    AND     X3 , X3 , X6 , LSR #3                                       ;/*  X3 记录最大的 ways          */
    CLZ     W5 , W3                                                     ;/*  记录 ways 的 bit 数         */
    MOV     X4 , #0x7fff                                                ;/*  CCSIDR_EL1 27-13 为 sets - 1*/
    AND     X4 , X4 , X6 , LSR #13                                      ;/*  X4 记录最大的 sets          */
                                                                        ;/*  X12 <- cache level << 1     */
                                                                        ;/*  X2  <- line size            */
                                                                        ;/*  X3  <- number of ways - 1   */
                                                                        ;/*  X4  <- number of sets - 1   */
                                                                        ;/*  X5  <- bit position of #ways*/
                                                                        ;/*  X1 = 0 clean only,          */
                                                                        ;/*  X1 = 1 clean and invalidate */
                                                                        ;/*  X1 = 2 invalidate only      */

LINE_LABEL(loop_set)                                                    ;/*  依次处理每个 set            */
    MOV     X6 , X3                                                     ;/*  X6 <- working copy of #ways */
LINE_LABEL(loop_way)                                                    ;/*  依次处理每个 way            */
    LSL     X7 , X6 , X5                                                ;/*  CISW 3 - 1 为 Level         */
    ORR     X9 , X12, X7                                                ;/*  CISW 31- 4 为 SetWay        */
    LSL     X7 , X4 , X2
    ORR     X9 , X9 , X7                                                ;/*  详见 SetWay 说明            */
    TBZ     W1 , #0 , 1f                                                ;/*  判断 X1                     */
    CMP     X1 , #2
    BNE     2f                                                          ;/*  如果不是 "只是无效" 则跳转  */
    DC      ISW, X9                                                     ;/*  只是 无效  DCache           */
    B       3f
1:
    DC      CSW  ,  X9                                                  ;/*  回写 DCache                 */
2:
    DC      CISW , X9                                                   ;/*  回写并无效 DCache           */
3:
    SUBS    X6 , X6 , #1                                                ;/*  递减 way                    */
    B.GE    loop_way
    SUBS    X4 , X4 , #1                                                ;/*  递减 set                    */
    B.GE    loop_set

    RET
    FUNC_END()

CSSELR_EL1 是cache层选择寄存器

allwinner h6 armv8 SylixOS 启动分析_第4张图片

使用X0来选择cache层,在arm中调用函数的前四个参数是是通过寄存器传递的 ,这也是在前面使用X0寄存器来进行递增的原因,调用arm64DCacheLevel 函数时,cache层级直接通过x0传递过来。由于CSSELR_EL1 第1到3位才是cache层选择,所以这里x0 需要左移一位。

CCSIDR_EL1 寄存器提供了当前cache使用的信息。

allwinner h6 armv8 SylixOS 启动分析_第5张图片

 allwinner h6 armv8 SylixOS 启动分析_第6张图片

在cache 中是以line为单位的,并且使用组路相连。 set代表一组,way代表一行line。 首先通过AND     X2 , X6 , #7 获得linesize -4 的值,为什么是减4 在armv8手册里有详细介绍。 ADD     X2 , X2 , #4  这里加4 求出linesize大小。

 MOV     X3 , #0x3ff    ,  AND     X3 , X3 , X6 , LSR #3 这两句求出way的数量,也就是line的数量,保存在x3中。

下边相同的原理将set求出放在x4中。在下边是循环set将每个way回写。

DC ISW  是对Data cache的无效命令。DC  CSW 是clean 数据, DC CISW 是无效 清楚数据指令

allwinner h6 armv8 SylixOS 启动分析_第7张图片

allwinner h6 armv8 SylixOS 启动分析_第8张图片

allwinner h6 armv8 SylixOS 启动分析_第9张图片

这里的无效时数据cache中的数据将没有任何作用,但是数据没有写回到内存中。clean 其实是回写的意思。回写到内存中。

arm64DCacheClearAll函数功能就算结束了 。

arm64DCacheDisable

执行完arm64DCacheClearAll函数后,BL 跳转到arm64DcacheDiable函数,此函数的功能时关闭DCache。与arm64DCacheClearAll在一个文件中。

FUNC_DEF(arm64DCacheDisable)
    MRS     X0  , SCTLR_EL1
    AND     X0  , X0 , #(1 << 2)
    CMP     X0  , #0
    MOV     X13 , LR                                                    ;/*  记录返回地址                */
    BEQ     dcache_not_en
    BL      arm64DCacheClearAll

    ARM_DSB()
    ARM_ISB()

    MRS     X0 ,  SCTLR_EL1
    BIC     X0 ,  X0 , #(1 << 2)
    MSR     SCTLR_EL1 , X0
    MOV     LR , X13
    RET

LINE_LABEL(dcache_not_en)            
    BL      arm64DCacheInvalidateAll  
    MOV     LR , X13
    RET
    FUNC_END()

SCTLR_EL1 提供了系统控制的寄存器,只能在EL1和EL0 有效。 

allwinner h6 armv8 SylixOS 启动分析_第10张图片

 

C bit[2]是用来enable或者disable EL0 & EL1 的data cache.

M bit[0]是用来enable或者disable EL0 & EL1 的MMU。

AND     X0  , X0 , #(1 << 2)
    CMP     X0  , #0
    MOV     X13 , LR                                                    ;/*  记录返回地址                */
    BEQ     dcache_not_en

这三句代码判断第二位是否为1,也就是data cache 是否是能,如果是没有使能跳转到 dcache_not_en 函数执行。 dcache_not_en调用 arm64DCacheInvalidateAll 函数,此函数调用前面讲过的arm64DCacheAll

;/*********************************************************************************************************
;  ARM64 无效 全部 DCACHE
;*********************************************************************************************************/

FUNC_DEF(arm64DCacheInvalidateAll)
    MOV     X14 , LR                                                    ;/*  记录返回地址                */
    MOV     X0  , #1
    BL      arm64DCacheAll
    MOV     LR  , X14
    RET
    FUNC_END()

如果判断当前Dcache是能 则调用arm64DCacheClearAll函数,arm64DCacheClearAll函数在前面也讲过,这里也是调用arm64DCacheAll,也就是无论时无效还是清除,在SylixOS都是调用arm64DCacheAll函数进行invaild和clean操作。

 MRS     X0 ,  SCTLR_EL1
    BIC     X0 ,  X0 , #(1 << 2)
    MSR     SCTLR_EL1 , X0

BIC 是取反码进行与操作,这里将第二位设置为0,也就是关闭DCache。然后就是返回

arm64DCacheDisable函数功能就结束了,就是无效和清除DCache,然后将使能位设置为0。

arm64ICacheInvalidateAll

在执行完arm64DCacheDisable 后执行arm64ICacheInvalidateAll函数,无效全部的指令Cache。

;/*********************************************************************************************************
;  ARM64 无效整个 ICACHE
;*********************************************************************************************************/

FUNC_DEF(arm64ICacheInvalidateAll)
    IC      IALLU
    ARM_ISB()
    RET
    FUNC_END()

IC IALLU 是 无效指令Cache 到PoU 这个,同时刷新指令分支预测。

allwinner h6 armv8 SylixOS 启动分析_第11张图片

 在无效指令时有PoC 和PoU 具体差别可以参考蜗窝科技 的ARM64的启动过程之(三):为打开MMU而进行的CPU初始化 

linux和SylixOS  arm64 很多汇编时相同的,SylixOS 无效DataCache 应该时从linux 里面参考的。

arm64ICacheDisable

执行完成指令无效后,需要关闭指令cache,这里和数据时一致的,都是先无效或者回写,然后关闭。

FUNC_DEF(arm64ICacheDisable)
    MRS     X0 , SCTLR_EL1
    BIC     X0 , X0 , #(1 << 12)
    MSR     SCTLR_EL1 , X0
    RET
    FUNC_END()

 这里和数据cache关闭是一样。都是通过SCTLR_EL1 寄存器。根据手册这个寄存器的第12位是Instruction cache 使能位。

BIC x0,x0, #(1<< 12) 将1左移12位,然后取反,将值与x0进行与操作。这时就把12位设置为0,关闭指令cache。

 arm64MmuDisable

关闭mmu函数

FUNC_DEF(arm64MmuDisable)
    ARM_DSB()
    ARM_ISB()
    MRS     X0 , SCTLR_EL1
    BIC     X0 , X0 , #1
    MSR     SCTLR_EL1 , X0
    ARM_DSB()
    ARM_ISB()
    RET
    FUNC_END()

关闭MMU也是通过SCTLR_EL1 ,在 SCTLR_EL1寄存器的第0为是MMU使能位。

此函数在arch/arm64/mm/mmu/arm64MmuAsm.S中

arm64SwitchToEl1

此函数是切换到EL1,因为arm上电刚开始是在最高的异常层,但是不一定是EL3,根据芯片厂商自己的定义,可能EL2是最高的。linux和SylixOS 都是运行在EL1 层,所以要跳转到EL1。arm中跳转不能由EL3直接进入EL1。需要EL3->EL2->EL1 逐层降低  。下面是跳转代码

;/*********************************************************************************************************
;  EL 状态切换
;*********************************************************************************************************/

MACRO_DEF(SWITCH_EL, xreg, el3_label, el2_label, el1_label)
    MRS     \xreg ,  CurrentEL
    CMP     \xreg ,  0xc
    B.EQ    \el3_label
    CMP     \xreg ,  0x8
    B.EQ    \el2_label
    CMP     \xreg ,  0x4
    B.EQ    \el1_label
    MACRO_END()

;/*********************************************************************************************************
;  切换到 EL1 
;*********************************************************************************************************/

FUNC_DEF(arm64SwitchToEl1)
    MOV         X18, LR
    SWITCH_EL   X6 , el3, el2, el1

LINE_LABEL(el3)
    BL      arm64El3SwitchEl2
    BL      arm64EL2SwitchEL1
    MOV     LR , X18
    RET

LINE_LABEL(el2)
    BL      arm64EL2SwitchEL1
    MOV     LR , X18
    RET

LINE_LABEL(el1)
    MOV     LR , X18
    RET

;/*********************************************************************************************************
;  EL3 切换到 EL2 
;*********************************************************************************************************/

FUNC_DEF(arm64El3SwitchEl2)
    MOV     X0 ,       #0x5b1                                            ;/*  Non-secure EL0/1 |HVC|64bit*/
    MSR     SCR_EL3 ,  X0
    MSR     CPTR_EL3,  XZR                                               ;/*  禁能协处理器陷入 EL3       */

    ;/*
    ; * 从 EL3 跳转至 EL2 AARCH64
    ; */
    MOV     X0 ,       SP                                                ;/*  Ret EL2_SP2 mode from EL3  */
    MSR     SP_EL2 ,   X0                                                ;/*  Migrate SP                 */
    MOV     X0 ,       #0x3c9
    MSR     SPSR_EL3 , X0                                                ;/*  EL2_SP2 | D | A | I | F    */
    MSR     ELR_EL3  , LR

    ERET
    FUNC_END()

;/*********************************************************************************************************
;  EL2 切换到 EL1
;*********************************************************************************************************/

FUNC_DEF(arm64EL2SwitchEL1)
    MRS     X0 , CNTHCTL_EL2                                             ;/*  初始化通用定时器           */
    ORR     X0 , X0 , #(CNTHCTL_EL2_EL1PCEN_EN | \
                        CNTHCTL_EL2_EL1PCTEN_EN)                         ;/*  使能 EL1 对 定时器的访问   */
    MSR     CNTHCTL_EL2 , X0
    MSR     CNTVOFF_EL2 , XZR

    MRS     X0 , MIDR_EL1                                                ;/*  初始化 MPID/MPIDR          */
    MSR     VPIDR_EL2  , X0
    MRS     X0 , MPIDR_EL1
    MSR     VMPIDR_EL2 , X0

    MOV     X0 , #CPTR_EL2_RES1                                          ;/*  禁能协处理器的陷入         */
    MSR     CPTR_EL2 , X0                                                ;/*  禁能协处理器陷入 EL2       */
    MSR     HSTR_EL2 , XZR

    MOV     X0 , #CPACR_EL1_FPEN_EN                                      ;/*  Enable FP/SIMD at EL1      */
    MSR     CPACR_EL1 , X0

    MRS     X0,  FPEXC32_EL2
    ORR     X0,  X0, #(1 << 30)
    MSR     FPEXC32_EL2, X0

    MSR     DAIFSET ,  #2                                                ;/*  关中断                    */
    ADRP    X0 ,       vector
    ADD     X0 ,       X0 , #:lo12:vector
    MSR     VBAR_EL2 , X0                                                ;/*  设置 EL2 的向量表地址     */
    MSR     VBAR_EL1 , X0                                                ;/*  设置 EL1 的向量表地址     */

    ;/*
    ; * SCTLR_EL1 初始化
    ; */
    LDR     X0 , =(SCTLR_EL1_RES1        | SCTLR_EL1_UCI_DIS    | \
                   SCTLR_EL1_EE_LE       | SCTLR_EL1_WXN_DIS    | \
                   SCTLR_EL1_NTWE_DIS    | SCTLR_EL1_NTWI_DIS   | \
                   SCTLR_EL1_UCT_DIS     | SCTLR_EL1_DZE_DIS    | \
                   SCTLR_EL1_ICACHE_DIS  | SCTLR_EL1_UMA_DIS    | \
                   SCTLR_EL1_SED_EN      | SCTLR_EL1_ITD_EN     | \
                   SCTLR_EL1_CP15BEN_DIS | SCTLR_EL1_SA0_DIS    | \
                   SCTLR_EL1_SA_DIS      | SCTLR_EL1_DCACHE_DIS | \
                   SCTLR_EL1_ALIGN_DIS   | SCTLR_EL1_MMU_DIS)
    MSR     SCTLR_EL1 , X0
    LDR     X0 , =(HCR_EL2_RW_AARCH64 | HCR_EL2_HCD_DIS)                 ;/*  初始化 HCR_EL2            */
    MSR     HCR_EL2 , X0

    ;/*  
    ; * 从 EL2 跳转至 EL1 AARCH64 
    ; */
    LDR     X0 , =(SPSR_D_BIT | SPSR_A_BIT      | \
                   SPSR_F_BIT | SPSR_MODE64_BIT | SPSR_MODE_EL1h)
    MSR     SPSR_EL2 , X0
    MSR     ELR_EL2  , X30
    ERET
    FUNC_END()

    FILE_END()
;/*********************************************************************************************************
;  END
;*********************************************************************************************************/

是自己定义的函数SWITCH_EL 。 SWITCH_EL函数首先读取CurrentEL  寄存器, CurrentEL 寄存器存放当前EL等级。

allwinner h6 armv8 SylixOS 启动分析_第12张图片

allwinner h6 armv8 SylixOS 启动分析_第13张图片

cmp 比对当前异常等级,进入对应的函数中处理当前等级。首先看如果当前在EL3等级

LINE_LABEL(el3)
    BL      arm64El3SwitchEl2
    BL      arm64EL2SwitchEL1
    MOV     LR , X18
    RET

分别调用跳转到EL2函数和跳转到EL1函数。跳转到EL2函数如下。

FUNC_DEF(arm64El3SwitchEl2)
    MOV     X0 ,       #0x5b1                                            ;/*  Non-secure EL0/1 |HVC|64bit*/
    MSR     SCR_EL3 ,  X0
    MSR     CPTR_EL3,  XZR                                               ;/*  禁能协处理器陷入 EL3       */

    ;/*
    ; * 从 EL3 跳转至 EL2 AARCH64
    ; */
    MOV     X0 ,       SP                                                ;/*  Ret EL2_SP2 mode from EL3  */
    MSR     SP_EL2 ,   X0                                                ;/*  Migrate SP                 */
    MOV     X0 ,       #0x3c9
    MSR     SPSR_EL3 , X0                                                ;/*  EL2_SP2 | D | A | I | F    */
    MSR     ELR_EL3  , LR

    ERET
    FUNC_END()

 SCR_EL3 用来配置当前状态

allwinner h6 armv8 SylixOS 启动分析_第14张图片

0x5b1是将EL1是设置nor-secure 模式 ,SMC,HVC 是设置为UNDEFINED 在这个UNDEFINED 单词是未定义意思,不知道是不是代表关闭这个功能。这个比较疑惑还没找到资料。同时使能了EL2位64位模式

SMC arm手册定义如下:

allwinner h6 armv8 SylixOS 启动分析_第15张图片

我个人理解是通过SMC指令 进入到secure Monitor模式

allwinner h6 armv8 SylixOS 启动分析_第16张图片 HVC 指令是调用Hypervisor模式,其他的arm64可以参考这篇文章的介绍。ARM64的启动过程之(六):异常向量表的设定

CPTR_EL3 寄存器控制了去往 EL3的通道。XZR 是零寄存器,是将CPTR_EL3  赋值为0,关闭去往EL3

allwinner h6 armv8 SylixOS 启动分析_第17张图片

由于在每个EL层都有自己SP寄存器。所以获取当前的SP赋值给SP_EL2 。切换到EL2 就要是SP_EL2寄存器当做栈寄存器。 

SPSR_EL3 是EL3 层的状态寄存器 。设置完异常来自EL2, 将返回地址赋值给ELR_EL3 执行eret 此时触发异常回到EL2层级,完成切换。

arm64EL2SwitchEL1 

此函数从EL2 切换到EL1层

FUNC_DEF(arm64EL2SwitchEL1)
    MRS     X0 , CNTHCTL_EL2                                             ;/*  初始化通用定时器           */
    ORR     X0 , X0 , #(CNTHCTL_EL2_EL1PCEN_EN | \
                        CNTHCTL_EL2_EL1PCTEN_EN)                         ;/*  使能 EL1 对 定时器的访问   */
    MSR     CNTHCTL_EL2 , X0
    MSR     CNTVOFF_EL2 , XZR

    MRS     X0 , MIDR_EL1                                                ;/*  初始化 MPID/MPIDR          */
    MSR     VPIDR_EL2  , X0
    MRS     X0 , MPIDR_EL1
    MSR     VMPIDR_EL2 , X0

    MOV     X0 , #CPTR_EL2_RES1                                          ;/*  禁能协处理器的陷入         */
    MSR     CPTR_EL2 , X0                                                ;/*  禁能协处理器陷入 EL2       */
    MSR     HSTR_EL2 , XZR

    MOV     X0 , #CPACR_EL1_FPEN_EN                                      ;/*  Enable FP/SIMD at EL1      */
    MSR     CPACR_EL1 , X0

    MRS     X0,  FPEXC32_EL2
    ORR     X0,  X0, #(1 << 30)
    MSR     FPEXC32_EL2, X0

    MSR     DAIFSET ,  #2                                                ;/*  关中断                    */
    ADRP    X0 ,       vector
    ADD     X0 ,       X0 , #:lo12:vector
    MSR     VBAR_EL2 , X0                                                ;/*  设置 EL2 的向量表地址     */
    MSR     VBAR_EL1 , X0                                                ;/*  设置 EL1 的向量表地址     */

    ;/*
    ; * SCTLR_EL1 初始化
    ; */
    LDR     X0 , =(SCTLR_EL1_RES1        | SCTLR_EL1_UCI_DIS    | \
                   SCTLR_EL1_EE_LE       | SCTLR_EL1_WXN_DIS    | \
                   SCTLR_EL1_NTWE_DIS    | SCTLR_EL1_NTWI_DIS   | \
                   SCTLR_EL1_UCT_DIS     | SCTLR_EL1_DZE_DIS    | \
                   SCTLR_EL1_ICACHE_DIS  | SCTLR_EL1_UMA_DIS    | \
                   SCTLR_EL1_SED_EN      | SCTLR_EL1_ITD_EN     | \
                   SCTLR_EL1_CP15BEN_DIS | SCTLR_EL1_SA0_DIS    | \
                   SCTLR_EL1_SA_DIS      | SCTLR_EL1_DCACHE_DIS | \
                   SCTLR_EL1_ALIGN_DIS   | SCTLR_EL1_MMU_DIS)
    MSR     SCTLR_EL1 , X0
    LDR     X0 , =(HCR_EL2_RW_AARCH64 | HCR_EL2_HCD_DIS)                 ;/*  初始化 HCR_EL2            */
    MSR     HCR_EL2 , X0

    ;/*  
    ; * 从 EL2 跳转至 EL1 AARCH64 
    ; */
    LDR     X0 , =(SPSR_D_BIT | SPSR_A_BIT      | \
                   SPSR_F_BIT | SPSR_MODE64_BIT | SPSR_MODE_EL1h)
    MSR     SPSR_EL2 , X0
    MSR     ELR_EL2  , X30
    ERET
    FUNC_END()

    FILE_END()

CNTHCTL_EL2 控制了EL1层的定时器,CNTHCTL_EL2  每个位作用根据  hcr_el2寄存器对应位标志位。由于reset后hcr_el2大部分位默认都是0,所以CNTHCTL_EL2 如下图

allwinner h6 armv8 SylixOS 启动分析_第18张图片

MRS     X0 , CNTHCTL_EL2                                             ;/*  初始化通用定时器           */
    ORR     X0 , X0 , #(CNTHCTL_EL2_EL1PCEN_EN | \
                        CNTHCTL_EL2_EL1PCTEN_EN)                         ;/*  使能 EL1 对 定时器的访问   */
    MSR     CNTHCTL_EL2 , X0

这几句实现对EL1定时器使能。以下几个寄存器的介绍是参考自蜗窝科技 的 arm64启动(-)

cntvoff_el2是virtual counter offset,所谓virtual counter,其值就是physical counter的值减去一个offset的值(也就是cntvoff_el2的值了),这里把offset值清零,因此virtual counter的计数和physical counter的计数是一样的。

MIDR_EL1,Main ID Register主要给出了该PE的architecture信息,Implementer是谁等等信息。MPIDR_EL1,Multiprocessor Affinity Register,该寄存器保存了processor ID。vpidr_el2和vmpidr_el2是上面的两个寄存器是对应的,只不过是for virtual processor的。

CPACR_EL1 是控制寄存器

allwinner h6 armv8 SylixOS 启动分析_第19张图片

 CPACR_EL1_FPEN_EN 是宏定义是 3 << 20.

allwinner h6 armv8 SylixOS 启动分析_第20张图片

SVE  是矢量扩展 ,  SIMD 是单指令多数据指令集。   MOV     X0 , #CPACR_EL1_FPEN_EN          MSR     CPACR_EL1 , X0 这里是使能了浮点运算, SVE,SIMD。

   MRS     X0,  FPEXC32_EL2
    ORR     X0,  X0, #(1 << 30)
    MSR     FPEXC32_EL2, X0

 这一句,根据手册上的说明,FPEXC32_EL2 30位 设置为1 是设置在各个异常层都是控制SIMD FP的权限

 MSR     DAIFSET ,  #2               这句是关中断什么配置赋值为2是关中断, 没找到相关的解释,我的理解是PSTATE还包括DAIF四个bit,D在EL0下一般忽略,A是SError类型终端,I和F分别是IRQ和FIQ,取值为1表示mask,取值为0表示打开,值为1表示cpu不接受。设置DAIF有两条指令,一个DAIFSET,把DAIF都设置为1,一个DAIFCLR,把DAIF都设置为0,EL0下是否能访问PSTATE,是由SCTLR_EL1.UMA决定的。也就是在在使用DAIFSET 设置值为2 时关闭了 IRQ。

 

    ADRP    X0 ,       vector
    ADD     X0 ,       X0 , #:lo12:vector
    MSR     VBAR_EL2 , X0                                                ;/*  设置 EL2 的向量表地址     */
    MSR     VBAR_EL1 , X0                                                ;/*  设置 EL1 的向量表地址     */

VBAR 是存放异常表的地址的寄存器。 adrp指令 是4k对齐的所以低12位回全部清零,所以只存放了vector地址12位前的,这里使用ADD 命令将这个低12位加到x0中,然后分别把值赋给EL1和EL2下的VBAR寄存器。由于VBAR_EL 寄存器是低12是保留的,所以异常向量表必须2K对齐。

 ;/*
    ; * SCTLR_EL1 初始化
    ; */
    LDR     X0 , =(SCTLR_EL1_RES1        | SCTLR_EL1_UCI_DIS    | \
                   SCTLR_EL1_EE_LE       | SCTLR_EL1_WXN_DIS    | \
                   SCTLR_EL1_NTWE_DIS    | SCTLR_EL1_NTWI_DIS   | \
                   SCTLR_EL1_UCT_DIS     | SCTLR_EL1_DZE_DIS    | \
                   SCTLR_EL1_ICACHE_DIS  | SCTLR_EL1_UMA_DIS    | \
                   SCTLR_EL1_SED_EN      | SCTLR_EL1_ITD_EN     | \
                   SCTLR_EL1_CP15BEN_DIS | SCTLR_EL1_SA0_DIS    | \
                   SCTLR_EL1_SA_DIS      | SCTLR_EL1_DCACHE_DIS | \
                   SCTLR_EL1_ALIGN_DIS   | SCTLR_EL1_MMU_DIS)
    MSR     SCTLR_EL1 , X0

这里是配置EL1的控制寄存器。参考这个文章对控制寄存器的每一位作用了解armV8控制寄存器。

HCR_EL2 中第31位使能EL1 是32位还是64位的设置。

allwinner h6 armv8 SylixOS 启动分析_第21张图片

第29位是关闭EL1 的HVC指令,也就是禁止了开启Hypervisor模式。Hypervisor 是arm的一种虚拟化扩展。

 

allwinner h6 armv8 SylixOS 启动分析_第22张图片

 

    LDR     X0 , =(SPSR_D_BIT | SPSR_A_BIT      | \
                   SPSR_F_BIT | SPSR_MODE64_BIT | SPSR_MODE_EL1h)
    MSR     SPSR_EL2 , X0
    MSR     ELR_EL2  , X30
    ERET

spsr_el2 使能了DAF,设置异常来自el1并且位64位。然后将lr(x30) 传入到elr_el2 触发异常回到el1 层 这里中断少了IRQ中断,并没有对IRQ中断使能。跳转到EL1到此就结束了。

初始化堆栈

/*********************************************************************************************************
;  初始化堆栈
;*********************************************************************************************************/

LINE_LABEL(start)
    BL      arm64MpidrGet
    MOV     X2 , X0                                                    ;/*  X2 暂时记录 MPIDR            */

    LDR     X1 , =__stack_end                                          ;/*  栈区顶端地址                 */
LINE_LABEL(0)
    CMP     X0 , #0
    B.EQ    1f
    SUB     X1 , X1 , #__BOOT_STACK_SIZE
    SUB     X0 , X0 , #1
    B.NE    0b

LINE_LABEL(1)
    BIC     X1 , X1 , #0xf                                              ;/*  SP 向下 16 字节对齐         */
    SUB     X1 , X1 , ARCH_REG_CTX_SIZE                                 ;/*  预留上下文保存空间          */
    MOV     SP , X1
    MSR     TPIDR_EL1, X1                                               ;/*  设置异常临时栈(使用启动栈)  */
    STR     X2 , [X1 , #CPUID_OFFSET]                                   ;/*  保存 CPU ID 到核私有内存    */

每个core,根据属性层次的不同,使用不同的标号来识别。如下图所示,是一个4层结构,那么对于一个core来说,就可以用 xxx.xxx.xxx.xxx 来识别。

这种标识方式,和ARMv8架构的使用MPIDR_EL1寄存器,来标识core是一样的。

allwinner h6 armv8 SylixOS 启动分析_第23张图片

图片来自链接

aff0 是cpuid。设置堆栈首先获取cpu的id

arm64MpidrGet

此函数主要获取cpu物理信息

;/*********************************************************************************************************
;  获取当前核硬件 ID
;  对于 ARMv8 处理器,MPIDR 采用点分十进制的方式进行定义:
;  如 ...
;*********************************************************************************************************/

FUNC_DEF(arm64MpidrGet)
    MRS     X1 , MPIDR_EL1
    AND     X0 , X1 , #0x3                                              ;/*  获取 Aff0 cpu id            */
    MOV     X3 , #0xff
    AND     X2 , X3 , X1, LSR #8
    ADD     X0 , X0 , X2, LSL #2                                        ;/*  获取 Aff1 cluster id        */
    AND     X2 , X3 , X1, LSR #16
    ADD     X0 , X0 , X2, LSL #4                                        ;/*  获取 Aff2 cluster id        */
    AND     X2 , X3 , X1, LSR #32
    ADD     X0 , X0 , X2, LSL #6                                        ;/*  获取 Aff3 cluster id        */
    RET
    FUNC_END()

这个函数aff0 保留了两位aff1保留了两位,aff2保留了三位,aff3保留了6位到x0寄存器中。

在获得当前的cpu id后将值保存到x2寄存器中,然后将链接脚本中__stack_end 存放x1 寄存器中。

然后判断是不是0核,如果是0核跳转到


LINE_LABEL(1)
    BIC     X1 , X1 , #0xf                                              ;/*  SP 向下 16 字节对齐         */
    SUB     X1 , X1 , ARCH_REG_CTX_SIZE                                 ;/*  预留上下文保存空间          */
    MOV     SP , X1
    MSR     TPIDR_EL1, X1                                               ;/*  设置异常临时栈(使用启动栈)  */
    STR     X2 , [X1 , #CPUID_OFFSET]                                   ;/*  保存 CPU ID 到核私有内存    */

堆栈是从高字节到低字节,所以首先对齐,然后预留上下文切换使用的空间,将当前栈指针赋值给sp寄存器,同时把栈指针赋值给线程ID寄存器,也就是通过线程ID寄存器保存当前的栈指针。然后将x2保存到堆栈中,这里的str指令时存到x1地址偏移CPUID_OFFSET 的地方,这个偏移定义为0,就是把cpu物理核信息放到堆栈中。

;/*********************************************************************************************************
;  关中断并设置异常向量表
;*********************************************************************************************************/

    MSR     DAIFSET ,  #2
    ADRP    X0 ,       vector
    ADD     X0 ,       X0 , #:lo12:vector
    MSR     VBAR_EL1 , X0

;/*********************************************************************************************************
;  如果不是 Primary CPU, 则跳转到 secondaryCpuResetEntry
;*********************************************************************************************************/

    BL      archMpCur
    CMP     X0 , #0
    B.NE    secondaryCpuResetEntry

;/*********************************************************************************************************
;  初始化 DATA 段
;*********************************************************************************************************/

    LDR     X1 , =_etext                                                ;/*  -> ROM data end             */
    LDR     X2 , =_data                                                 ;/*  -> data start               */
    LDR     X3 , =_edata                                                ;/*  -> end of data              */

LINE_LABEL(1)
    LDR     X0 , [X1] , #8                                              ;/*  copy it                     */
    STR     X0 , [X2] , #8
    CMP     X2 , X3                                                     ;/*  check if data to move       */
    B.LT    1b                                                          ;/*  loop until done             */

;/*********************************************************************************************************
;  清零 BSS 段
;*********************************************************************************************************/

    MOV     X0 , #0                                                     ;/*  get a zero                  */
    LDR     X1 , =__bss_start                                           ;/*  -> bss start                */
    LDR     X2 , =__bss_end                                             ;/*  -> bss end                  */

LINE_LABEL(2)
    STR     X0 , [X1] , #8                                              ;/*  clear 8 bytes               */
    CMP     X1 , X2                                                     ;/*  check if data to clear      */
    B.LT    2b                                                          ;/*  loop until done             */

;/*********************************************************************************************************
;  进入 bspInit 函数 (argc = 0, argv = NULL, frame pointer = NULL)
;*********************************************************************************************************/

    MOV     X0  , #0
    MOV     X1  , #0
    MOV     X2  , #0
    MOV     X29 , #0                                                    ;/*  FP 指针                     */

    LDR     X10, =halPrimaryCpuMain
    BLR     X10

    B       .
    FUNC_END()

然后关中断设置异常向量向量表,设置完成异常向量表后,判断是不是主核,是主核初始化则初始化data和bss段。由于data段的LMA和VMA不相同,所以需要搬运。bss不搬运但是需要将VMA位置清零,然后调用halPrimaryCpuMain函数,这个函数是c语言的,也就是c语言入口。这里将启动系统。

 

如果不是主核,回将x1减少__BOOT_STACK_SIZE。这个__BOOT_STACK_SIZE 是每个核的启动栈大小,也就是使用1核时。要把栈指针向下减少这么多,空出那部分是0核启动需要使用。同理2核需要空出两个__BOOT_STACK_SIZE。然后就是核主核一样向下对齐16个字节,然后留出上下文切换的栈空间,然后判断是从核还是主核。从核跳转到从核初始化函数

FUNC_DEF(secondaryCpuResetEntry)

;/*********************************************************************************************************
;  关闭 D-Cache(无效)
;  关闭 I-Cache(无效)
;  无效并关闭分支预测
;  关闭 MMU(无效 TLB)
;*********************************************************************************************************/

    BL      arm64DCacheInvalidateAll
    BL      arm64DCacheDisable
    BL      arm64ICacheInvalidateAll
    BL      arm64ICacheDisable
    BL      arm64MmuDisable

;/*********************************************************************************************************
;  关中断并设置异常向量表
;*********************************************************************************************************/

    MSR     DAIFSET ,  #2
    ADRP    X0 ,       vector
    ADD     X0 ,       X0 , #:lo12:vector
    MSR     VBAR_EL1 , X0

;/*********************************************************************************************************
;  进入 halSecondaryCpuMain 函数 (argc = 0, argv = NULL, frame pointer = NULL)
;*********************************************************************************************************/

    MOV     X0  , #0
    MOV     X1  , #0
    MOV     X2  , #0
    MOV     X29 , #0

    LDR     X10, =halSecondaryCpuMain
    BLR     X10

    B       .

    FUNC_END()

 

你可能感兴趣的:(SylixOS)