所有 CPU 都一致的值。如果在 EL1 模式下进入内核,则 CNTHCTL_EL2 中的 EL1PCTEN (bit 0) 必须置位。
- 一致性 通过内核启动的所有 CPU 在内核入口地址上必须处于相同的一致性域中。 这可能要根据具体实现来定义初始化过程,以使能每个CPU上对维护操作的 接收。
- 系统寄存器 在进入内核映像的异常级中,所有构架中可写的系统寄存器必须通过软件 在一个更高的异常级别下初始化,以防止在 未知 状态下运行。
对于拥有 GICv3 中断控制器并以 v3 模式运行的系统:
- 如果 EL3 存在: ICC_SRE_EL3.Enable (位 3) 必须初始化为 0b1。
ICC_SRE_EL3.SRE (位 0) 必须初始化为 0b1。
- 若内核运行在 EL1: ICC_SRE_EL2.Enable (位 3) 必须初始化为 0b1。
ICC_SRE_EL2.SRE (位 0) 必须初始化为 0b1。
- 设备树(DT)或 ACPI 表必须描述一个 GICv3 中断控制器。
对于拥有 GICv3 中断控制器并以兼容(v2)模式运行的系统:
- 如果 EL3 存在: ICC_SRE_EL3.SRE (位 0) 必须初始化为 0b0。
- 若内核运行在 EL1: ICC_SRE_EL2.SRE (位 0) 必须初始化为 0b0。
- 设备树(DT)或 ACPI 表必须描述一个 GICv2 中断控制器。
这里有个很关键的问题:为什么跳转到内核时指令高速缓存可以打开,数据高速缓存必须关闭?
(1)CPU启动取数据的时候首先去访问数据高速缓存,这个数据高速缓存有可能缓存了bootloader的数据,这个数据对于内核可能是错误的。因此数据高速缓存必须关闭。
(2)bootloader和内核的指令无冲突。因为bootloader指令运行完成后不会再次运行,直接运行内核的指令。因此指令高速缓存可以不关闭。
2. 启动汇编接口primary_entry分析
/*
* The following callee saved general purpose registers are used on the
* primary lowlevel boot path:
*
* Register Scope Purpose
* x21 primary_entry() .. start_kernel() FDT pointer passed at boot in x0
* x23 primary_entry() .. start_kernel() physical misalignment/KASLR offset
* x28 __create_page_tables() callee preserved temp register
* x19/x20 __primary_switch() callee preserved temp registers
* x24 __primary_switch() .. relocate_kernel() current RELR displacement
*/
SYM_CODE_START(primary_entry)
bl preserve_boot_args
bl init_kernel_el // w0=cpu_boot_mode
adrp x23, __PHYS_OFFSET --- '__PHYS_OFFSET加载到x23寄存器'
and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
bl set_cpu_boot_mode_flag
bl __create_page_tables
/*
* The following calls CPU setup code, see arch/arm64/mm/proc.S for
* details.
* On return, the CPU will be ready for the MMU to be turned on and
* the TCR will have been set.
*/
bl __cpu_setup // initialise processor
b __primary_switch
SYM_CODE_END(primary_entry)
2.1 preserve_boot_args
??功能:把bootloader传进来的x0 .. x3保存到boot_args数组中。
/*
* Preserve the arguments passed by the bootloader in x0 .. x3
*/
SYM_CODE_START_LOCAL(preserve_boot_args)
mov x21, x0 // x21=FDT(x0寄存器保存devicetree的地址),devicetree保存到x21寄存器
adr_l x0, boot_args // record the contents of. boot_args数组地址保存到x0
stp x21, x1, [x0] // x0 .. x3 at kernel entry
stp x2, x3, [x0, #16] '参数x0 .. x3保存到boot_args数组中'
dmb sy // needed before dc ivac with.内存屏障指令(+sy表示全系统高速缓存范围内做一次内存屏障)
// MMU off
add x1, x0, #0x20 // 4 x 8 bytes
b dcache_inval_poc // tail call 清除boot_args数组对应的高速缓存
SYM_CODE_END(preserve_boot_args)
2.2 init_kernel_el
??判断启动的模式是EL2还是非安全模式的EL1,并进行相关级别的系统配置(ARMv8中EL2是hypervisor模式,EL1是标准的内核模式),然后使用w0返回启动模式(BOOT_CPU_MODE_EL1或BOOT_CPU_MODE_EL2)。通常来讲系统启动时运行在EL3,uboot会把处理器置于EL2,内核运行到init_kernel_el会设为EL1。
/*
* Starting from EL2 or EL1, configure the CPU to execute at the highest
* reachable EL supported by the kernel in a chosen default state. If dropping
* from EL2 to EL1, configure EL2 before configuring EL1.
*
* Since we cannot always rely on ERET synchronizing writes to sysregs (e.g. if
* SCTLR_ELx.EOS is clear), we place an ISB prior to ERET.
*
* Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in w0 if
* booted in EL1 or EL2 respectively.
*/
SYM_FUNC_START(init_kernel_el)
mrs x0, CurrentEL '获取当前PSTATE异常等级'
cmp x0, #CurrentEL_EL2
b.eq init_el2 '如果PSTATE异常等级为EL2,则跳转到init_el2'