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 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 启动分析相关推荐
- Uboot启动分析--start.S启动分析(1)
总目录 NXP i.MX8M secure boot流程 Uboot链接脚本分析述 Uboot启动分析–start.S启动分析(1) Uboot启动分析–start.S启动分析(2) Uboot启动分 ...
- 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_ ...
- 【Android 性能优化】应用启动优化 ( 安卓应用启动分析 | Launcher 应用启用普通安卓应用 | 应用进程分析 )
文章目录 一. Launcher 应用 startActivitySafely 方法分析 二. Launcher 中的 startActivity(View v, Intent intent, Obj ...
- springboot 启动分析【难点】——如何自动扫描 @SpringBootApplication||如何加载自动配置类 @EnableAutoConfiguration||如何加载前端控制器
springboot 启动分析[难点] 自动扫描的特点 默认扫描与 启动类 同级的所有包及其子包都可以自动扫描 如果不可要使用@ComponentScan(basePackage={"&qu ...
- 全志h3linux移植教程,全志H3启动分析,移植主线UBOOT
全志H3启动分析,移植主线UBOOT 参考资源 启动流程 因为使用的是外扩SD卡,因此主要参考了这部分内容:Bootable SD card SD卡Layout如下: start sector siz ...
- STM8启动分析及IAP
STM8启动分析及IAP 转载于:https://www.cnblogs.com/LittleTiger/p/5535041.html
- ADS中startup.s文件启动分析
映像文件分析,ADS 中startup.s 文件启动分析,学嵌入式开发ADS 必看 2010-04-17 10:21 声明: 我也是转来的,不是原创,由于别人是网易的日志,不能直接转,所以-- 感谢原 ...
- SylixOS启动读取配置文件
1 概述 SylixOS启动时会加载环境变量配置文件profile,网络配置文件ifparam.ini以及系统启动脚本startup.sh. 2 环境变量配置文件profile SylixOS启动时会 ...
- 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 ...
- BSP板机支持包、linux启动分析、ARM裸机编程
文章目录 一.BSP 二.驱动 驱动的基本要素 三.启动分析 1.uboot 2.uboot的作用 3.uboot相关命令 关键的内容: 1)bootargs,启动参数 2)启动命令 3)修改启动延时 ...
最新文章
- 《OpenCV3编程入门》学习笔记10 角点检测(二)Shi-Tomasi角点检测
- wps图表xy轴颠倒_还在嫌自己的图表丑?50+种可视化图表随你选
- 中加减乘除_【泓泰花园】精雕细琢的好房子,加减乘除里都是幸福
- 程序员最害怕的5件事,你中招了吗?
- 第一个Java程序示例——Hello World
- 黑神话:悟空中演示视频中一些设计浅析与建议
- springboot+sockjs进行消息推送(群发)
- 不用asp.net MVC,用WebForm照样能够实现MVC
- java jpasswordfield_Java JPasswordField
- 【iOS】打印方法名
- Android 四大组件学习之ContentProvider五
- 用傅里叶分析得到频域信息 MATLAB,信号频谱分析
- 网吧局域网搭建(思科网络方案课程设计)
- 交互式反汇编器 linux,Carbon:交互式反汇编工具
- font标签的size属性
- 美国范德堡大学计算机科学博士,范德堡大学计算机科学专业是什么?相关信息是哪些呢?...
- 上海双非改考408,与上海计算所联合培养!上海第二工业大学计算机专硕!
- 【六】【vlc-android】vlc的decoder控制层传输数据与ffmpeg视频解码模块decoder层的数据交互流程源码分析
- 项目需求到设计的理解
- mbit职业测试软件,MBTI职业性格测试(高考专业填报版)(手机版)