启动 AArch64 Linux¶
作者:Will Deacon <will.deacon@arm.com>
日期:2012 年 9 月 7 日
本文档基于 Russell King 的 ARM 启动文档,适用于所有 AArch64 Linux 内核的公共版本。
AArch64 异常模型由多个异常级别(EL0 - EL3)组成,其中 EL0、EL1 和 EL2 具有安全和非安全对应项。EL2 是管理程序级别,EL3 是最高优先级级别,仅存在于安全模式下。两者在架构上都是可选的。
就本文档而言,我们将术语 引导加载程序 简单地定义为在控制权传递给 Linux 内核之前在 CPU 上执行的所有软件。这可能包括安全监视器和管理程序代码,或者可能只是一些用于准备最小启动环境的指令。
本质上,引导加载程序应(至少)提供以下内容
设置并初始化 RAM
设置设备树
解压缩内核镜像
调用内核镜像
1. 设置并初始化 RAM¶
要求:强制
引导加载程序应查找并初始化内核将在系统中用于易失性数据存储的所有 RAM。它以机器相关的方式执行此操作。(它可以使用内部算法自动定位和调整所有 RAM 的大小,或者可以使用机器中 RAM 的知识,或者引导加载程序设计人员认为合适的任何其他方法。)
对于 Arm 机密计算领域,这包括确保所有受保护的 RAM 都具有“RAM”的领域 IPA 状态(RIPAS)。
2. 设置设备树¶
要求:强制
设备树 blob(dtb)必须放置在 8 字节边界上,并且大小不得超过 2 兆字节。由于 dtb 将使用最大 2 兆字节大小的块进行缓存映射,因此不得将其放置在必须使用任何特定属性映射的任何 2M 区域内。
注意:v4.2 之前的版本还要求 DTB 放置在内核 Image 以下 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)。当存根完成其工作后,它会跳转到 code0 以恢复正常的启动过程。
在 v3.17 之前,text_offset 的字节序未指定。在这些情况下,image_size 为零,text_offset 为内核字节序的 0x80000。如果 image_size 非零,则 image_size 为小端字节序,必须遵守。如果 image_size 为零,则可以假定 text_offset 为 0x80000。
flags 字段(在 v3.17 中引入)是一个小端字节序 64 位字段,其组成如下
位 0
内核字节序。如果是大端字节序,则为 1,如果是小端字节序,则为 0。
位 1-2
内核页面大小。
0 - 未指定。
1 - 4K
2 - 16K
3 - 64K
位 3
内核物理位置
- 0
2MB 对齐的基址应尽可能靠近 DRAM 的基址,因为无法通过线性映射访问它下面的内存
- 1
2MB 对齐的基址,使得从映像开始计算的所有 image_size 字节都在物理内存的 48 位可寻址范围内
位 4-63
保留。
当 image_size 为零时,引导加载程序应尝试在内核镜像结束后立即保留尽可能多的内存供内核使用。所需的空间量将因所选功能而异,并且实际上是无界的。
Image 必须放置在可用系统 RAM 中的任何位置,且与 2MB 对齐的基址相距 text_offset 字节,并在那里调用。2 MB 对齐的基址和镜像开始之间的区域对内核没有特殊意义,可以用于其他目的。从镜像开始的至少 image_size 字节必须可供内核使用。注意:v4.6 之前的版本无法利用 Image 物理偏移量以下的内存,因此建议将 Image 放置在尽可能靠近系统 RAM 开始的位置。
如果在启动时将 initrd/initramfs 传递给内核,则它必须完全驻留在最大 32 GB 的 1 GB 对齐的物理内存窗口内,该窗口也完全覆盖内核 Image。
描述给内核的任何内存(即使是镜像开始以下的内存),如果未从内核标记为保留(例如,使用设备树中的 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 进行编程,并且必须在所有 CPU 上使用一致的值对 CNTVOFF 进行编程。如果在 EL1 处进入内核,则 CNTHCTL_EL2 必须设置 EL1PCTEN(位 0)(如果可用)。
一致性
内核要启动的所有 CPU 必须在进入内核时属于同一一致性域。这可能需要实现定义的初始化,以便在每个 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。
必须在内核执行的所有 CPU 上将 ICC_CTLR_EL3.PMHE(位 6)设置为相同的值,并且在内核的整个生命周期内必须保持不变。
如果内核在 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。
对于支持 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_MOPS) 的 CPU
如果内核在 EL1 进入并且存在 EL2
HCRX_EL2.MSCEn(位 11)必须初始化为 0b1。
HCRX_EL2.MCE2(位 10)必须初始化为 0b1,并且 hypervisor 必须按照 Hypervisor 要求 中所述处理 MOPS 异常。
对于具有扩展转换控制寄存器功能的 CPU (FEAT_TCR2)
如果存在 EL3
SCR_EL3.TCR2En(位 43)必须初始化为 0b1。
如果内核在 EL1 进入并且存在 EL2
HCRX_EL2.TCR2En(位 14)必须初始化为 0b1。
对于具有第 1 阶段权限间接扩展功能的 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 模式、缓存、MMU、架构定时器、一致性和系统寄存器描述的要求适用于所有 CPU。 所有 CPU 必须在同一异常级别进入内核。 如果文档中记录的值禁用陷阱,则允许启用这些陷阱,只要这些陷阱由更高的异常级别透明地处理,就像设置了文档中记录的值一样。
引导加载程序应以以下方式在每个 CPU 上进入内核
主 CPU 必须直接跳转到内核映像的第一个指令。此 CPU 传递的设备树 blob 必须包含每个 cpu 节点的“enable-method”属性。下面描述了支持的 enable-method。
引导加载程序应该生成这些设备树属性,并在内核进入之前将其插入到 blob 中。
具有“spin-table”enable-method 的 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(保留供将来使用)