启动 AArch64 Linux¶
作者:Will Deacon <will.deacon@arm.com>
日期:2012 年 9 月 7 日
本文档基于 Russell King 编写的 ARM 启动文档,与所有 AArch64 Linux 内核的公开发布版本相关。
AArch64 异常模型由多个异常级别(EL0 - EL3)组成,其中 EL0、EL1 和 EL2 具有安全和非安全对应项。 EL2 是虚拟机监控程序级别,EL3 是最高优先级级别,并且仅存在于安全模式下。两者在架构上都是可选的。
就本文档而言,我们将使用术语 boot loader 来简单地定义在控制权传递给 Linux 内核之前在 CPU 上执行的所有软件。 这可能包括安全监视器和虚拟机监控程序代码,或者可能只是一些用于准备最小启动环境的指令。
本质上,引导加载程序(boot loader)应提供(至少)以下内容:
设置并初始化 RAM
设置设备树
解压内核镜像
调用内核镜像
1. 设置并初始化 RAM¶
要求:强制
引导加载程序应查找并初始化内核将用于系统中易失性数据存储的所有 RAM。 它以依赖机器的方式执行此操作。(它可以使用内部算法来自动定位所有 RAM 的大小,或者它可以使用机器中 RAM 的知识,或者引导加载程序设计者认为合适的任何其他方法。)
对于 Arm 机密计算领域,这包括确保所有受保护的 RAM 都具有“RAM”的 Realm IPA 状态 (RIPAS)。
2. 设置设备树¶
要求:强制
设备树 blob (dtb) 必须放置在 8 字节边界上,并且不得超过 2 兆字节大小。 由于 dtb 将使用最大 2 兆字节大小的块以可缓存的方式映射,因此不得将其放置在任何必须使用任何特定属性映射的 2M 区域中。
注意:v4.2 之前的版本还要求 DTB 放置在以低于内核映像 text_offset 字节开始的 512 MB 区域内。
3. 解压内核镜像¶
要求:可选
AArch64 内核当前不提供解压缩器,因此如果使用压缩的 Image 目标(例如 Image.gz),则需要由引导加载程序执行解压缩(gzip 等)。 对于未实现此要求的引导加载程序,可以使用未压缩的 Image 目标代替。
4. 调用内核镜像¶
要求:强制
解压后的内核镜像包含一个 64 字节的头部,如下所示:
u32 code0; /* Executable code */
u32 code1; /* Executable code */
u64 text_offset; /* Image load offset, little endian */
u64 image_size; /* Effective Image size, little endian */
u64 flags; /* kernel flags, little endian */
u64 res2 = 0; /* reserved */
u64 res3 = 0; /* reserved */
u64 res4 = 0; /* reserved */
u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */
u32 res5; /* reserved (used for PE COFF offset) */
头部注释
从 v3.17 开始,除非另有说明,否则所有字段均为小端字节序。
code0/code1 负责分支到 stext。
通过 EFI 启动时,最初会跳过 code0/code1。 res5 是 PE 头的偏移量,PE 头具有 EFI 入口点 (efi_stub_entry)。 当 stub 完成其工作后,它会跳转到 code0 以恢复正常的启动过程。
在 v3.17 之前,未指定 text_offset 的字节序。 在这些情况下,image_size 为零,text_offset 在内核的字节序中为 0x80000。 如果 image_size 为非零值,则 image_size 为小端字节序,并且必须遵守。 如果 image_size 为零,则可以假定 text_offset 为 0x80000。
flags 字段(在 v3.17 中引入)是一个小端 64 位字段,组成如下:
位 0
内核字节序。 如果为 BE 则为 1,如果为 LE 则为 0。
位 1-2
内核页面大小。
0 - 未指定。
1 - 4K
2 - 16K
3 - 64K
位 3
内核物理位置
- 0
2MB 对齐的基地址应尽可能接近 DRAM 的基地址,因为其下方的内存无法通过线性映射访问
- 1
2MB 对齐的基地址,使得从映像开始计算的所有 image_size 字节都在物理内存的 48 位可寻址范围内
位 4-63
保留。
当 image_size 为零时,引导加载程序应尝试保留尽可能多的内存,以供内核在内核映像结束后立即使用。 所需的空间量将因所选功能而异,并且实际上是不受限制的。
映像必须放置在可用系统 RAM 中任何位置的 2MB 对齐基地址开始的 text_offset 字节处,并在那里调用。 2 MB 对齐基地址和映像开始之间的区域对内核没有特殊意义,可用于其他目的。 映像开始后的至少 image_size 字节必须可供内核使用。 注意:v4.6 之前的版本无法使用映像物理偏移量以下的内存,因此建议将映像尽可能靠近系统 RAM 的起始位置放置。
如果在启动时将 initrd/initramfs 传递给内核,则它必须完全驻留在高达 32 GB 的 1 GB 对齐的物理内存窗口内,该窗口完全覆盖内核映像。
描述给内核的任何内存(即使在映像的开始位置下方)未标记为内核保留的内存(例如,设备树中的 memreserve 区域)将被视为内核可用。
在跳转到内核之前,必须满足以下条件:
使所有支持 DMA 的设备静止,以便内存不会被虚假的网路数据包或磁盘数据损坏。 这将为您节省大量的调试时间。
主 CPU 通用寄存器设置
x0 = 系统 RAM 中设备树 blob (dtb) 的物理地址。
x1 = 0(保留供将来使用)
x2 = 0(保留供将来使用)
x3 = 0(保留供将来使用)
CPU 模式
所有形式的中断都必须在 PSTATE.DAIF(调试、SError、IRQ 和 FIQ)中屏蔽。 CPU 必须处于非安全状态,要么在 EL2 中(推荐,以便可以访问虚拟化扩展),要么在 EL1 中。
缓存、MMU
MMU 必须关闭。
指令缓存可以打开或关闭,并且不得包含与加载的内核映像对应的任何过时条目。
与加载的内核映像对应的地址范围必须清除到 PoC。 在存在启用缓存的系统缓存或其他一致性主设备的情况下,这通常需要通过 VA 而不是集合/方式操作进行缓存维护。 尊重架构缓存维护(通过 VA 操作)的系统缓存必须配置并启用。 不尊重架构缓存维护(通过 VA 操作)的系统缓存(不推荐)必须配置并禁用。
架构计时器
CNTFRQ 必须使用计时器频率进行编程,CNTVOFF 必须在所有 CPU 上使用一致的值进行编程。 如果在 EL1 级别进入内核,则 CNTHCTL_EL2 必须设置 EL1PCTEN(位 0)(如果可用)。
一致性
内核要启动的所有 CPU 在进入内核时都必须是同一一致性域的一部分。 这可能需要根据 IMPLEMENTATION DEFINED 进行初始化,以启用在每个 CPU 上接收维护操作。
系统寄存器
内核映像将在其中进入的异常级别或以下的,所有可写架构系统寄存器都必须由较高异常级别的软件初始化,以防止在 UNKNOWN 状态下执行。
对于所有系统:- 如果存在 EL3
SCR_EL3.FIQ 必须在内核正在执行的所有 CPU 上具有相同的值。
SCR_EL3.FIQ 的值必须与引导时存在的值相同,无论内核何时执行。
如果存在 EL3 并且内核在 EL2 级别进入
SCR_EL3.HCE(位 8)必须初始化为 0b1。
对于要在 v3 模式下使用的,带有 GICv3 中断控制器的系统:- 如果存在 EL3
ICC_SRE_EL3.Enable(位 3)必须初始化为 0b1。
ICC_SRE_EL3.SRE(位 0)必须初始化为 0b1。
ICC_CTLR_EL3.PMHE(位 6)必须在内核正在执行的所有 CPU 上设置为相同的值,并且在内核的生命周期内必须保持不变。
如果内核在 EL1 级别进入
ICC_SRE_EL2.Enable(位 3)必须初始化为 0b1
ICC_SRE_EL2.SRE(位 0)必须初始化为 0b1。
DT 或 ACPI 表必须描述一个 GICv3 中断控制器。
对于要在兼容(v2)模式下使用的,带有 GICv3 中断控制器的系统
如果存在 EL3
ICC_SRE_EL3.SRE(位 0)必须初始化为 0b0。
如果内核在 EL1 级别进入
ICC_SRE_EL2.SRE(位 0)必须初始化为 0b0。
DT 或 ACPI 表必须描述一个 GICv2 中断控制器。
对于具有指针身份验证功能的 CPU
如果存在 EL3
SCR_EL3.APK(位 16)必须初始化为 0b1
SCR_EL3.API(位 17)必须初始化为 0b1
如果内核在 EL1 级别进入
HCR_EL2.APK(位 40)必须初始化为 0b1
HCR_EL2.API(位 41)必须初始化为 0b1
对于具有活动监视器单元 v1 (AMUv1) 扩展的 CPU
如果存在 EL3
CPTR_EL3.TAM(位 30)必须初始化为 0b0
CPTR_EL2.TAM(位 30)必须初始化为 0b0
AMCNTENSET0_EL0 必须初始化为 0b1111
AMCNTENSET1_EL0 必须初始化为特定于平台的值,对于每个存在的辅助计数器,其对应位设置为 0b1。
如果内核在 EL1 级别进入
AMCNTENSET0_EL0 必须初始化为 0b1111
AMCNTENSET1_EL0 必须初始化为特定于平台的值,对于每个存在的辅助计数器,其对应位设置为 0b1。
对于具有精细粒度陷阱 (FEAT_FGT) 扩展的 CPU
如果存在 EL3 并且内核在 EL2 级别进入
SCR_EL3.FGTEn(位 27)必须初始化为 0b1。
对于具有精细粒度陷阱 2 (FEAT_FGT2) 扩展的 CPU
如果存在 EL3 并且内核在 EL2 级别进入
SCR_EL3.FGTEn2(位 59)必须初始化为 0b1。
对于具有 HCRX_EL2 支持 (FEAT_HCX) 的 CPU
如果存在 EL3 并且内核在 EL2 级别进入
SCR_EL3.HXEn(位 38)必须初始化为 0b1。
对于具有高级 SIMD 和浮点支持的 CPU
如果存在 EL3
CPTR_EL3.TFP(位 10)必须初始化为 0b0。
如果存在 EL2 并且内核在 EL1 级别进入
CPTR_EL2.TFP(位 10)必须初始化为 0b0。
对于具有可伸缩矢量扩展 (FEAT_SVE) 的 CPU
如果存在 EL3
CPTR_EL3.EZ(位 8)必须初始化为 0b1。
ZCR_EL3.LEN 必须为内核在其上执行的所有 CPU 初始化为相同的值。
如果内核在 EL1 级别进入并且 EL2 存在
CPTR_EL2.TZ(位 8)必须初始化为 0b0。
CPTR_EL2.ZEN(位 17:16)必须初始化为 0b11。
ZCR_EL2.LEN 必须为内核将在其上执行的所有 CPU 初始化为相同的值。
对于具有可伸缩矩阵扩展 (FEAT_SME) 的 CPU
如果存在 EL3
CPTR_EL3.ESM(位 12)必须初始化为 0b1。
SCR_EL3.EnTP2(位 41)必须初始化为 0b1。
SMCR_EL3.LEN 必须为内核将在其上执行的所有 CPU 初始化为相同的值。
如果内核在 EL1 级别进入并且 EL2 存在
CPTR_EL2.TSM(位 12)必须初始化为 0b0。
CPTR_EL2.SMEN(位 25:24)必须初始化为 0b11。
SCTLR_EL2.EnTP2(位 60)必须初始化为 0b1。
SMCR_EL2.LEN 必须为内核将在其上执行的所有 CPU 初始化为相同的值。
HWFGRTR_EL2.nTPIDR2_EL0(位 55)必须初始化为 0b01。
HWFGWTR_EL2.nTPIDR2_EL0(位 55)必须初始化为 0b01。
HWFGRTR_EL2.nSMPRI_EL1(位 54)必须初始化为 0b01。
HWFGWTR_EL2.nSMPRI_EL1(位 54)必须初始化为 0b01。
对于具有可伸缩矩阵扩展 FA64 特性 (FEAT_SME_FA64) 的 CPU
如果存在 EL3
SMCR_EL3.FA64(位 31)必须初始化为 0b1。
如果内核在 EL1 级别进入并且 EL2 存在
SMCR_EL2.FA64(位 31)必须初始化为 0b1。
对于具有内存标记扩展特性 (FEAT_MTE2) 的 CPU
如果存在 EL3
SCR_EL3.ATA(位 26)必须初始化为 0b1。
如果内核在 EL1 级别进入并且 EL2 存在
HCR_EL2.ATA(位 56)必须初始化为 0b1。
对于具有可伸缩矩阵扩展版本 2 (FEAT_SME2) 的 CPU
如果存在 EL3
SMCR_EL3.EZT0(位 30)必须初始化为 0b1。
如果内核在 EL1 级别进入并且 EL2 存在
SMCR_EL2.EZT0(位 30)必须初始化为 0b1。
对于具有性能监视器扩展 (FEAT_PMUv3p9) 的 CPU
如果存在 EL3
MDCR_EL3.EnPM2(位 7)必须初始化为 0b1。
如果内核在 EL1 级别进入并且 EL2 存在
HDFGRTR2_EL2.nPMICNTR_EL0(位 2)必须初始化为 0b1。
HDFGRTR2_EL2.nPMICFILTR_EL0(位 3)必须初始化为 0b1。
HDFGRTR2_EL2.nPMUACR_EL1(位 4)必须初始化为 0b1。
HDFGWTR2_EL2.nPMICNTR_EL0(位 2)必须初始化为 0b1。
HDFGWTR2_EL2.nPMICFILTR_EL0(位 3)必须初始化为 0b1。
HDFGWTR2_EL2.nPMUACR_EL1(位 4)必须初始化为 0b1。
对于具有内存复制和内存设置指令 (FEAT_MOPS) 的 CPU
如果内核在 EL1 级别进入并且 EL2 存在
HCRX_EL2.MSCEn(位 11)必须初始化为 0b1。
HCRX_EL2.MCE2(位 10)必须初始化为 0b1,并且虚拟机监控程序必须按照 虚拟机监控程序要求中的描述处理 MOPS 异常。
对于具有扩展转换控制寄存器特性的 CPU (FEAT_TCR2)
如果存在 EL3
SCR_EL3.TCR2En(位 43)必须初始化为 0b1。
如果内核在 EL1 级别进入并且 EL2 存在
HCRX_EL2.TCR2En(位 14)必须初始化为 0b1。
对于具有第一阶段权限间接扩展特性的 CPU (FEAT_S1PIE)
如果存在 EL3
SCR_EL3.PIEn(位 45)必须初始化为 0b1。
如果内核在 EL1 级别进入并且 EL2 存在
HFGRTR_EL2.nPIR_EL1(位 58)必须初始化为 0b1。
HFGWTR_EL2.nPIR_EL1(位 58)必须初始化为 0b1。
HFGRTR_EL2.nPIRE0_EL1(位 57)必须初始化为 0b1。
HFGRWR_EL2.nPIRE0_EL1(位 57)必须初始化为 0b1。
对于具有受保护控制堆栈 (FEAT_GCS) 的 CPU
GCSCR_EL1 必须初始化为 0。
GCSCRE0_EL1 必须初始化为 0。
如果存在 EL3
SCR_EL3.GCSEn(位 39)必须初始化为 0b1。
如果存在 EL2
GCSCR_EL2 必须初始化为 0。
如果内核在 EL1 级别进入并且 EL2 存在
HCRX_EL2.GCSEn 必须初始化为 0b1。
HFGITR_EL2.nGCSEPP(位 59)必须初始化为 0b1。
HFGITR_EL2.nGCSSTR_EL1(位 58)必须初始化为 0b1。
HFGITR_EL2.nGCSPUSHM_EL1(位 57)必须初始化为 0b1。
HFGRTR_EL2.nGCS_EL1(位 53)必须初始化为 0b1。
HFGRTR_EL2.nGCS_EL0(位 52)必须初始化为 0b1。
HFGWTR_EL2.nGCS_EL1(位 53)必须初始化为 0b1。
HFGWTR_EL2.nGCS_EL0(位 52)必须初始化为 0b1。
对于具有调试架构的 CPU,即 FEAT_Debugv8pN(所有版本)
如果存在 EL3
MDCR_EL3.TDA(位 9)必须初始化为 0b0
对于具有 FEAT_PMUv3 的 CPU
如果存在 EL3
MDCR_EL3.TPM(位 6)必须初始化为 0b0
上述针对 CPU 模式、缓存、MMU、架构计时器、一致性和系统寄存器的要求适用于所有 CPU。 所有 CPU 必须以相同的异常级别进入内核。 在文档记录的值禁用陷阱的情况下,如果更高异常级别透明地处理这些陷阱,就好像设置了文档记录的值一样,则允许启用这些陷阱。
引导加载程序应以以下方式在每个 CPU 上进入内核:
主 CPU 必须直接跳转到内核映像的第一条指令。 此 CPU 传递的设备树 blob 必须包含每个 cpu 节点的“enable-method”属性。 支持的启用方法如下所述。
预计引导加载程序会生成这些设备树属性,并在进入内核之前将其插入到 blob 中。
具有“spin-table”启用方法的 CPU 必须在其 cpu 节点中具有“cpu-release-addr”属性。 此属性标识一个自然对齐的 64 位零初始化内存位置。
这些 CPU 应在内核外部的保留内存区域(通过设备树中的 /memreserve/ 区域传达给内核)中旋转,轮询其 cpu-release-addr 位置,该位置必须包含在保留区域中。 可以插入 wfe 指令以减少忙循环的开销,并且主 CPU 将发出 sev。 当对 cpu-release-addr 指向的位置的读取返回非零值时,CPU 必须跳转到此值。 该值将以单个 64 位小端值写入,因此 CPU 必须在跳转到该值之前将其读取的值转换为其本机字节序。
具有“psci”启用方法的 CPU 应保留在内核外部(即,在内存节点中描述给内核的内存区域之外,或在设备树的 /memreserve/ 区域描述给内核的保留内存区域中)。 内核将发出 ARM 文档编号 ARM DEN 0022A(“ARM 处理器上的电源状态协调接口系统软件”)中描述的 CPU_ON 调用,以将 CPU 带入内核。
设备树应包含一个“psci”节点,如 Documentation/devicetree/bindings/arm/psci.yaml 中所述。
辅助 CPU 通用寄存器设置
x0 = 0(保留供将来使用)
x1 = 0(保留供将来使用)
x2 = 0(保留供将来使用)
x3 = 0(保留供将来使用)