在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 汇编文件中实现。
arch 下arm64 是arm 64 armv8 架构实现。
首先将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页定义如下
具体每一位定义可以查看手册。
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 , 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层选择寄存器
使用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函数功能就算结束了 。
执行完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 有效。
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。
在执行完arm64DCacheDisable 后执行arm64ICacheInvalidateAll函数,无效全部的指令Cache。
;/*********************************************************************************************************
; ARM64 无效整个 ICACHE
;*********************************************************************************************************/
FUNC_DEF(arm64ICacheInvalidateAll)
IC IALLU
ARM_ISB()
RET
FUNC_END()
IC IALLU 是 无效指令Cache 到PoU 这个,同时刷新指令分支预测。
在无效指令时有PoC 和PoU 具体差别可以参考蜗窝科技 的ARM64的启动过程之(三):为打开MMU而进行的CPU初始化
linux和SylixOS arm64 很多汇编时相同的,SylixOS 无效DataCache 应该时从linux 里面参考的。
执行完成指令无效后,需要关闭指令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。
关闭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中
此函数是切换到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等级。
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 用来配置当前状态
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层级,完成切换。
此函数从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 如下图
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_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位的设置。
第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 , 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是一样的。
图片来自链接
aff0 是cpuid。设置堆栈首先获取cpu的id
此函数主要获取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()