在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      arm64DCacheClearAllBL      arm64DCacheDisableBL      arm64ICacheInvalidateAllBL      arm64ICacheDisableBL      arm64MmuDisable

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

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

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

函数 arm64DCacheClearAll

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

;/*********************************************************************************************************
;  ARMv8 回写并无效 全部 DCACHE
;*********************************************************************************************************/FUNC_DEF(arm64DCacheClearAll)MOV     X14 , LR                                                    ;/*  记录返回地址                */MOV     X0  , #1BL      arm64DCacheAllMOV     LR  , X14RETFUNC_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  , X0ARM_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 , X12AND     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_levelMOV     X0  , #0MSR     CSSELR_EL1 , X0                                             ;/*  重置 CSSELR_EL1             */ARM_DSB()ARM_ISB()MOV     LR  , X15LINE_LABEL(finished)RETFUNC_END()

ARM_DSB() 实际上是DSB 如下图

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

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

综合起来就是 ISB > DSB>DMB

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

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

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寄存器。

根据手册,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 , X2ORR     X9 , X9 , X7                                                ;/*  详见 SetWay 说明            */TBZ     W1 , #0 , 1f                                                ;/*  判断 X1                     */CMP     X1 , #2BNE     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_waySUBS    X4 , X4 , #1                                                ;/*  递减 set                    */B.GE    loop_setRETFUNC_END()

CSSELR_EL1 是cache层选择寄存器

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

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

在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 是无效 清楚数据指令

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

arm64DCacheClearAll函数功能就算结束了 。

arm64DCacheDisable

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

FUNC_DEF(arm64DCacheDisable)MRS     X0  , SCTLR_EL1AND     X0  , X0 , #(1 << 2)CMP     X0  , #0MOV     X13 , LR                                                    ;/*  记录返回地址                */BEQ     dcache_not_enBL      arm64DCacheClearAllARM_DSB()ARM_ISB()MRS     X0 ,  SCTLR_EL1BIC     X0 ,  X0 , #(1 << 2)MSR     SCTLR_EL1 , X0MOV     LR , X13RETLINE_LABEL(dcache_not_en)            BL      arm64DCacheInvalidateAll  MOV     LR , X13RETFUNC_END()

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

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

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

AND     X0  , X0 , #(1 << 2)CMP     X0  , #0MOV     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  , #1BL      arm64DCacheAllMOV     LR  , X14RETFUNC_END()

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

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

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

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

arm64ICacheInvalidateAll

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

;/*********************************************************************************************************
;  ARM64 无效整个 ICACHE
;*********************************************************************************************************/FUNC_DEF(arm64ICacheInvalidateAll)IC      IALLUARM_ISB()RETFUNC_END()

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

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

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

arm64ICacheDisable

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

FUNC_DEF(arm64ICacheDisable)MRS     X0 , SCTLR_EL1BIC     X0 , X0 , #(1 << 12)MSR     SCTLR_EL1 , X0RETFUNC_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_EL1BIC     X0 , X0 , #1MSR     SCTLR_EL1 , X0ARM_DSB()ARM_ISB()RETFUNC_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 ,  CurrentELCMP     \xreg ,  0xcB.EQ    \el3_labelCMP     \xreg ,  0x8B.EQ    \el2_labelCMP     \xreg ,  0x4B.EQ    \el1_labelMACRO_END();/*********************************************************************************************************
;  切换到 EL1
;*********************************************************************************************************/FUNC_DEF(arm64SwitchToEl1)MOV         X18, LRSWITCH_EL   X6 , el3, el2, el1LINE_LABEL(el3)BL      arm64El3SwitchEl2BL      arm64EL2SwitchEL1MOV     LR , X18RETLINE_LABEL(el2)BL      arm64EL2SwitchEL1MOV     LR , X18RETLINE_LABEL(el1)MOV     LR , X18RET;/*********************************************************************************************************
;  EL3 切换到 EL2
;*********************************************************************************************************/FUNC_DEF(arm64El3SwitchEl2)MOV     X0 ,       #0x5b1                                            ;/*  Non-secure EL0/1 |HVC|64bit*/MSR     SCR_EL3 ,  X0MSR     CPTR_EL3,  XZR                                               ;/*  禁能协处理器陷入 EL3       */;/*; * 从 EL3 跳转至 EL2 AARCH64; */MOV     X0 ,       SP                                                ;/*  Ret EL2_SP2 mode from EL3  */MSR     SP_EL2 ,   X0                                                ;/*  Migrate SP                 */MOV     X0 ,       #0x3c9MSR     SPSR_EL3 , X0                                                ;/*  EL2_SP2 | D | A | I | F    */MSR     ELR_EL3  , LRERETFUNC_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 , X0MSR     CNTVOFF_EL2 , XZRMRS     X0 , MIDR_EL1                                                ;/*  初始化 MPID/MPIDR          */MSR     VPIDR_EL2  , X0MRS     X0 , MPIDR_EL1MSR     VMPIDR_EL2 , X0MOV     X0 , #CPTR_EL2_RES1                                          ;/*  禁能协处理器的陷入         */MSR     CPTR_EL2 , X0                                                ;/*  禁能协处理器陷入 EL2       */MSR     HSTR_EL2 , XZRMOV     X0 , #CPACR_EL1_FPEN_EN                                      ;/*  Enable FP/SIMD at EL1      */MSR     CPACR_EL1 , X0MRS     X0,  FPEXC32_EL2ORR     X0,  X0, #(1 << 30)MSR     FPEXC32_EL2, X0MSR     DAIFSET ,  #2                                                ;/*  关中断                    */ADRP    X0 ,       vectorADD     X0 ,       X0 , #:lo12:vectorMSR     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 , X0LDR     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 , X0MSR     ELR_EL2  , X30ERETFUNC_END()FILE_END()
;/*********************************************************************************************************
;  END
;*********************************************************************************************************/

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

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

LINE_LABEL(el3)BL      arm64El3SwitchEl2BL      arm64EL2SwitchEL1MOV     LR , X18RET

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

FUNC_DEF(arm64El3SwitchEl2)MOV     X0 ,       #0x5b1                                            ;/*  Non-secure EL0/1 |HVC|64bit*/MSR     SCR_EL3 ,  X0MSR     CPTR_EL3,  XZR                                               ;/*  禁能协处理器陷入 EL3       */;/*; * 从 EL3 跳转至 EL2 AARCH64; */MOV     X0 ,       SP                                                ;/*  Ret EL2_SP2 mode from EL3  */MSR     SP_EL2 ,   X0                                                ;/*  Migrate SP                 */MOV     X0 ,       #0x3c9MSR     SPSR_EL3 , X0                                                ;/*  EL2_SP2 | D | A | I | F    */MSR     ELR_EL3  , LRERETFUNC_END()

SCR_EL3 用来配置当前状态

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

SMC arm手册定义如下:

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

 HVC 指令是调用Hypervisor模式,其他的arm64可以参考这篇文章的介绍。ARM64的启动过程之(六):异常向量表的设定

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

由于在每个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 , X0MSR     CNTVOFF_EL2 , XZRMRS     X0 , MIDR_EL1                                                ;/*  初始化 MPID/MPIDR          */MSR     VPIDR_EL2  , X0MRS     X0 , MPIDR_EL1MSR     VMPIDR_EL2 , X0MOV     X0 , #CPTR_EL2_RES1                                          ;/*  禁能协处理器的陷入         */MSR     CPTR_EL2 , X0                                                ;/*  禁能协处理器陷入 EL2       */MSR     HSTR_EL2 , XZRMOV     X0 , #CPACR_EL1_FPEN_EN                                      ;/*  Enable FP/SIMD at EL1      */MSR     CPACR_EL1 , X0MRS     X0,  FPEXC32_EL2ORR     X0,  X0, #(1 << 30)MSR     FPEXC32_EL2, X0MSR     DAIFSET ,  #2                                                ;/*  关中断                    */ADRP    X0 ,       vectorADD     X0 ,       X0 , #:lo12:vectorMSR     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 , X0LDR     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 , X0MSR     ELR_EL2  , X30ERETFUNC_END()FILE_END()

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

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 是控制寄存器

CPACR_EL1_FPEN_EN 是宏定义是 3 << 20.

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

   MRS     X0,  FPEXC32_EL2ORR     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 ,       vectorADD     X0 ,       X0 , #:lo12:vectorMSR     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位的设置。

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

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

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

初始化堆栈

/*********************************************************************************************************
;  初始化堆栈
;*********************************************************************************************************/LINE_LABEL(start)BL      arm64MpidrGetMOV     X2 , X0                                                    ;/*  X2 暂时记录 MPIDR            */LDR     X1 , =__stack_end                                          ;/*  栈区顶端地址                 */
LINE_LABEL(0)CMP     X0 , #0B.EQ    1fSUB     X1 , X1 , #__BOOT_STACK_SIZESUB     X0 , X0 , #1B.NE    0bLINE_LABEL(1)BIC     X1 , X1 , #0xf                                              ;/*  SP 向下 16 字节对齐         */SUB     X1 , X1 , ARCH_REG_CTX_SIZE                                 ;/*  预留上下文保存空间          */MOV     SP , X1MSR     TPIDR_EL1, X1                                               ;/*  设置异常临时栈(使用启动栈)  */STR     X2 , [X1 , #CPUID_OFFSET]                                   ;/*  保存 CPU ID 到核私有内存    */

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

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

图片来自链接

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

arm64MpidrGet

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

;/*********************************************************************************************************
;  获取当前核硬件 ID
;  对于 ARMv8 处理器,MPIDR 采用点分十进制的方式进行定义:
;  如 <Aff3>.<Aff2>.<Aff1>.<Aff0>
;*********************************************************************************************************/FUNC_DEF(arm64MpidrGet)MRS     X1 , MPIDR_EL1AND     X0 , X1 , #0x3                                              ;/*  获取 Aff0 cpu id            */MOV     X3 , #0xffAND     X2 , X3 , X1, LSR #8ADD     X0 , X0 , X2, LSL #2                                        ;/*  获取 Aff1 cluster id        */AND     X2 , X3 , X1, LSR #16ADD     X0 , X0 , X2, LSL #4                                        ;/*  获取 Aff2 cluster id        */AND     X2 , X3 , X1, LSR #32ADD     X0 , X0 , X2, LSL #6                                        ;/*  获取 Aff3 cluster id        */RETFUNC_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 , X1MSR     TPIDR_EL1, X1                                               ;/*  设置异常临时栈(使用启动栈)  */STR     X2 , [X1 , #CPUID_OFFSET]                                   ;/*  保存 CPU ID 到核私有内存    */

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

;/*********************************************************************************************************
;  关中断并设置异常向量表
;*********************************************************************************************************/MSR     DAIFSET ,  #2ADRP    X0 ,       vectorADD     X0 ,       X0 , #:lo12:vectorMSR     VBAR_EL1 , X0;/*********************************************************************************************************
;  如果不是 Primary CPU, 则跳转到 secondaryCpuResetEntry
;*********************************************************************************************************/BL      archMpCurCMP     X0 , #0B.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] , #8CMP     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  , #0MOV     X1  , #0MOV     X2  , #0MOV     X29 , #0                                                    ;/*  FP 指针                     */LDR     X10, =halPrimaryCpuMainBLR     X10B       .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      arm64DCacheInvalidateAllBL      arm64DCacheDisableBL      arm64ICacheInvalidateAllBL      arm64ICacheDisableBL      arm64MmuDisable;/*********************************************************************************************************
;  关中断并设置异常向量表
;*********************************************************************************************************/MSR     DAIFSET ,  #2ADRP    X0 ,       vectorADD     X0 ,       X0 , #:lo12:vectorMSR     VBAR_EL1 , X0;/*********************************************************************************************************
;  进入 halSecondaryCpuMain 函数 (argc = 0, argv = NULL, frame pointer = NULL)
;*********************************************************************************************************/MOV     X0  , #0MOV     X1  , #0MOV     X2  , #0MOV     X29 , #0LDR     X10, =halSecondaryCpuMainBLR     X10B       .FUNC_END()

allwinner h6 armv8 SylixOS 启动分析相关推荐

  1. Uboot启动分析--start.S启动分析(1)

    总目录 NXP i.MX8M secure boot流程 Uboot链接脚本分析述 Uboot启动分析–start.S启动分析(1) Uboot启动分析–start.S启动分析(2) Uboot启动分 ...

  2. u-boot启动分析02(board_init_f,board_init_r)

    文章目录 1._main函数分析 2. board_init_f() 2.1 初始化gd(global data)全局结构体变量 2.2 一些硬件相关的初始化 2.3 初始化DRAM 3.board_ ...

  3. 【Android 性能优化】应用启动优化 ( 安卓应用启动分析 | Launcher 应用启用普通安卓应用 | 应用进程分析 )

    文章目录 一. Launcher 应用 startActivitySafely 方法分析 二. Launcher 中的 startActivity(View v, Intent intent, Obj ...

  4. springboot 启动分析【难点】——如何自动扫描 @SpringBootApplication||如何加载自动配置类 @EnableAutoConfiguration||如何加载前端控制器

    springboot 启动分析[难点] 自动扫描的特点 默认扫描与 启动类 同级的所有包及其子包都可以自动扫描 如果不可要使用@ComponentScan(basePackage={"&qu ...

  5. 全志h3linux移植教程,全志H3启动分析,移植主线UBOOT

    全志H3启动分析,移植主线UBOOT 参考资源 启动流程 因为使用的是外扩SD卡,因此主要参考了这部分内容:Bootable SD card SD卡Layout如下: start sector siz ...

  6. STM8启动分析及IAP

    STM8启动分析及IAP 转载于:https://www.cnblogs.com/LittleTiger/p/5535041.html

  7. ADS中startup.s文件启动分析

    映像文件分析,ADS 中startup.s 文件启动分析,学嵌入式开发ADS 必看 2010-04-17 10:21 声明: 我也是转来的,不是原创,由于别人是网易的日志,不能直接转,所以-- 感谢原 ...

  8. SylixOS启动读取配置文件

    1 概述 SylixOS启动时会加载环境变量配置文件profile,网络配置文件ifparam.ini以及系统启动脚本startup.sh. 2 环境变量配置文件profile SylixOS启动时会 ...

  9. 2022版u-boot启动分析笔记之一(start.S与lowlevel_init.S)

    u-boot-2022.01-rc4启动分析笔记之一(start.S与lowlevel_init.S U-Boot启动过程概述 从链接脚本u-boot.lds说起 start.S start.S从re ...

  10. BSP板机支持包、linux启动分析、ARM裸机编程

    文章目录 一.BSP 二.驱动 驱动的基本要素 三.启动分析 1.uboot 2.uboot的作用 3.uboot相关命令 关键的内容: 1)bootargs,启动参数 2)启动命令 3)修改启动延时 ...

最新文章

  1. 《OpenCV3编程入门》学习笔记10 角点检测(二)Shi-Tomasi角点检测
  2. wps图表xy轴颠倒_还在嫌自己的图表丑?50+种可视化图表随你选
  3. 中加减乘除_【泓泰花园】精雕细琢的好房子,加减乘除里都是幸福
  4. 程序员最害怕的5件事,你中招了吗?
  5. 第一个Java程序示例——Hello World
  6. 黑神话:悟空中演示视频中一些设计浅析与建议
  7. springboot+sockjs进行消息推送(群发)
  8. 不用asp.net MVC,用WebForm照样能够实现MVC
  9. java jpasswordfield_Java JPasswordField
  10. 【iOS】打印方法名
  11. Android 四大组件学习之ContentProvider五
  12. 用傅里叶分析得到频域信息 MATLAB,信号频谱分析
  13. 网吧局域网搭建(思科网络方案课程设计)
  14. 交互式反汇编器 linux,Carbon:交互式反汇编工具
  15. font标签的size属性
  16. 美国范德堡大学计算机科学博士,范德堡大学计算机科学专业是什么?相关信息是哪些呢?...
  17. 上海双非改考408,与上海计算所联合培养!上海第二工业大学计算机专硕!
  18. 【六】【vlc-android】vlc的decoder控制层传输数据与ffmpeg视频解码模块decoder层的数据交互流程源码分析
  19. 项目需求到设计的理解
  20. mbit职业测试软件,MBTI职业性格测试(高考专业填报版)(手机版)

热门文章

  1. 指针 多维数组 数组指针 指针数组
  2. java根据回车符分隔字符串_如何通过换行符分割字符串?
  3. Spring Security基本原理
  4. 人人都能懂的Vue源码系列—08—initLifecycle
  5. vue.js原生组件化开发(一)——组件开发基础
  6. Asp.net Boilerplate之AbpSession扩展
  7. C基础(41——45)
  8. Shiro框架中有三个核心概念:Subject ,SecurityManager和Realms。
  9. 用ssh2连接linux实现putty功能范例代码
  10. 【WePY小程序框架实战四】-使用asyncawait异步请求数据