系列文章汇总:《鸿蒙OH-v5.0源码分析之 Uboot+Kernel 部分】000 - 文章链接汇总》
本文链接:《【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】004 - Kernel 启动引导代码head.S 源码逐行分析》
head.S 主要工作如下:
# linux-5.10\arch\arm64\kernel\head.S
// Kernel 启动时的入口, 需要关闭MMU,D-cache, DTS 设备树地址保存在 x0 寄存器中
// Kernel startup entry point.
// The requirements are: MMU = off, D-cache = off, I-cache = on or off, x0 = physical address to the FDT blob.
__HEAD
_head:
b primary_entry // branch to kernel start, magic
{--------------------------->
+ SYM_CODE_START(primary_entry)
+ //# 1. 保存内核启动参数, 无效化处理器缓存
+ bl preserve_boot_args
+ {----------------------------->
+ + SYM_CODE_START_LOCAL(preserve_boot_args)
+ +
+ + //# (1) 将设备树地址转存到 x21中, x0中保存着fdt设备树地址
+ + mov x21, x0 // x21=FDT
+ +
+ + //# (2) 加载 boot_args 的地址到寄存器 x0 (指向内核命令行参数字符串的位置)
+ + // boot_args 中保存了
+ + // boot_args[0] is free-mem start
+ + // boot_args[1] is ptr to command line
+ + // kernel\src_tmp\linux-5.10\arch\parisc\kernel\setup.c
+ + adr_l x0, boot_args // record the contents of
+ +
+ + //# (3) 将x21,x1,x2,x3 寄存器的值依次保存到boot_args地址中, 顺序为:boot_args[0] = x21, boot_args[1] = x1, boot_args[2] = x2, boot_args[3] = x3
+ + // 将 x21和x1 中的内容存储到由 x0 指向的内存位置
+ + // 第一个源寄存器 (x21) 的内容将被存储到x0所指向的地址 (64bit = 8byte)
+ + // 第二个源寄存器 (x1) 的内容将被存储到 x0 地址的偏移8 byte中 (64bit = 8byte)
+ + stp x21, x1, [x0] // x0 .. x3 at kernel entry
+ +
+ + // 第一个源寄存器 (x2) 的内容将被存储到 x0 地址的偏移16 byte中 (64bit = 8byte)
+ + // 第一个源寄存器 (x3) 的内容将被存储到 x0 地址的偏移24 byte中 (64bit = 8byte)
+ + stp x2, x3, [x0, #16]
+ +
+ + //# (4) 数据内存屏障, 确保之前的所有内存访问操作完成后, 才能执行该指令之后的内存访问操作
+ + dmb sy // needed before dc ivac with MMU off
+ +
+ + //# (5) 无效化处理器缓存管理, 清理及关闭处理器的缓存操作
+ + mov x1, #0x20 // 4 x 8 bytes
+ + b __inval_dcache_area // tail call
+ +
+ + {-------------------------->
+ + + // kernel\src_tmp\linux-5.10\arch\arm64\mm\cache.S
+ + + // __inval_dcache_area(kaddr, size)
+ + + // - kaddr - kernel address)
+ + + // - size - size in question
+ + + //
+ + + // 需要确保在地址范围 [kaddr, kaddr+size) 内的所有数据缓存行被标记为无效, 任何读取操作都是直接访问物理内存
+ + + // Ensure that any D-cache lines for the interval [kaddr, kaddr+size) are invalidated.
+ + +
+ + + // 处理区间两端可能存在的部分缓存行 (即缓存行只有一部分属于 [kaddr, kaddr+size) 范围) , 通常缓存以缓存行 (cache lines) 为单位操作, 每行固定大小, 比如 64 字节
+ + + // 要求这些部分缓存行需要被“cleaned to PoC”即清理到一致性点(Point of Coherency), 意味着那些缓存行中变动过的数据 (脏数据) 要被写回主存中, 目的是为了防止数据丢失——保证即使这些缓存行将被清空或无效化, 其中包含的任何有效数据都已同步到主存中
+ + + // Any partial lines at the ends of the interval are also cleaned to PoC to prevent data loss.
+ + + SYM_FUNC_START_LOCAL(__dma_inv_area)
+ + + SYM_FUNC_START_PI(__inval_dcache_area)
+ + +
+ + + // x1 指向 x0 (boot_args) 的偏移 32byte 处
+ + + add x1, x1, x0 // x1 = x0 + 0x20, 即指向 x0 地址处的 偏移32byte
+ + +
+ + + // 获取D-cache (数据缓存) 行的大小, 并将其放入 x2 寄存器
+ + + dcache_line_size x2, x3
+ + +
+ + + // 将缓存行大小 (x2 中的值) 减去1, 将结果存入 x3, 用于创建一个掩码, 检测地址是否与缓存行对齐
+ + + sub x3, x2, #1
+ + +
+ + + // 判断x1 是缓存行是否对齐, 将 x1 (区间的结束地址) 与 x3 (缓存行对齐掩码) 进行位测试 (AND操作) , 如结果为0说明对齐是
+ + + tst x1, x3 // end cache line aligned?
+ + + bic x1, x1, x3 // 从 x1 中清除由 x3 指定的位, 结果存回 x1
+ + +
+ + + // 如果 tst 测试x1 是缓存行对齐, 则跳转标签 1 运行
+ + + b.eq 1f
+ + +
+ + + // 如果 x1 不是对齐的, 则执行这条指令, 它清理并无效化 x1 地址所在的D-cache行
+ + + dc civac, x1 // clean & invalidate D / U line
+ + +
+ + + // 检测 x0 (区间的开始地址) 是否与缓存行对齐
+ + + 1: tst x0, x3 // start cache line aligned?
+ + + bic x0, x0, x3 // 从 x0 中清除由 x3 指定的位, 结果存回 x0
+ + +
+ + + // 如果 x0 是对齐的, 则跳转到标签 2:
+ + + b.eq 2f
+ + + // 如果 x0 不是对齐的, 清理并无效化 x0 地址所在的D-cache行, 然后跳转标签 3
+ + + dc civac, x0 // clean & invalidate D / U line
+ + + b 3f
+ + +
+ + + // 无效化 x0 地址所在的D-cache行 (不进行清理)
+ + + 2: dc ivac, x0 // invalidate D / U line
+ + +
+ + + // x0 = x0 + x2, 比较 x0 和 x1 的值 是否相等
+ + + 3: add x0, x0, x2
+ + + cmp x0, x1
+ + +
+ + + // 如果 x0 (当前处理的地址) 低于 x1 (区间的结束地址) , 即还有更多的缓存行需要处理, 就跳转回标签 2: 继续执行缓存无效化操作。
+ + + b.lo 2b
+ + +
+ + + // 内存屏障指令, 确保给定的地址范围 (从 x0 到 x1) 的数据缓存行被清理并无效化, 等前面的指令都执行完毕后, 才会开始取指令
+ + + dsb sy
+ + + ret // 函数返回
+ + + SYM_FUNC_END_PI(__inval_dcache_area)
+ + + SYM_FUNC_END(__dma_inv_area)
+ + +
+ + -------------------------->}
+ +
+ + SYM_CODE_END(preserve_boot_args)
+ <----------------------------}
+
+ //# 2. 配置CPU异常处理级别, 做一些CPU虚拟化配置,同时配置Hypervisor的中断向量表, 配置了异常返回时的程序状态和返回地址
+ // 虚拟化Hypervision 需要CPU 运行在 EL2 级别下启动 boot kernel, 其他情况需要在 EL1级别下启动 boot kernel
+ bl el2_setup // Drop to EL1, w0=cpu_boot_mode
+ {---------------------------->
+ +
+ + SYM_FUNC_START(el2_setup)
+ + //# (1) 设置SP_EL1/EL2寄存器
+ + // 选择异常级别 1 (EL1) 的堆栈指针
+ + // 设置堆栈指针选择器寄存器 (SPsel) 的值, 用于选择当前异常级别 (Current Exception Level) 下的堆栈指针
+ + msr SPsel, #1 // We want to use SP_EL{1,2}
+ +
+ + //# (2) 检查当前异常级别
+ + // 获取当前的异常处理等级
+ + mrs x0, CurrentEL
+ + // 判断当前的异常等级
+ + cmp x0, #CurrentEL_EL2
+ +
+ + // 如果当前是 EL2 等级的话, 跳转到 标签 1 执行
+ + b.eq 1f
+ +
+ + // 配置当前异常等级模式为 EL1
+ + mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)
+ + msr sctlr_el1, x0
+ +
+ + // 保存 BOOT_CPU_MODE_EL1的值到 w0 寄存器中
+ + mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1
+ +
+ + // 指令同步屏障, 更新了异常级别模式, 需要确保在该指令之前的所有指令完成后才执行后续的指令
+ + isb
+ + ret
+ +
+ + //# (3) 配置当前异常等级模式为 EL2
+ + 1: mov_q x0, (SCTLR_EL2_RES1 | ENDIAN_SET_EL2)
+ + msr sctlr_el2, x0
+ +
+ + #ifdef CONFIG_ARM64_VHE // 已使能
+ + //# (4) 检查是否支持虚拟化主机扩展 (VHE) , 在剩余的 EL2 设置过程中, 如果x2非零, 则表示我们确实拥有VHE, 并且内核预期将在EL2下运行
+ + mrs x2, id_aa64mmfr1_el1
+ + ubfx x2, x2, #ID_AA64MMFR1_VHE_SHIFT, #4
+ + #else
+ + mov x2, xzr
+ + #endif
+ +
+ + //# (5) 配置 Hypervision 虚拟化
+ + /* Hyp configuration. */
+ + mov_q x0, HCR_HOST_NVHE_FLAGS // 把非虚拟化主机环境标志 (NVHE) 移动到寄存器 x0
+ + cbz x2, set_hcr // 如果 x2 为 0, 即不支持虚拟化, 跳转到标签 set_hcr
+ + mov_q x0, HCR_HOST_VHE_FLAGS // 把虚拟化主机环境标志 (VHE) 移动到寄存器 x0
+ +
+ + set_hcr:
+ + //# (6) 配置当前 EL2 支持虚拟化
+ + msr hcr_el2, x0
+ +
+ + // 指令同步屏障
+ + isb
+ +
+ + // 允许非安全状态的 EL1 和 EL0 访问物理定时器和计数器
+ + // 宿主内核运行在 EL2,
+ + // 当 HCR_EL2.E2H == 1 时, CNTHCTL_EL2 具有与 CNTKCTL_EL1 相同的位布局,并且访问 CNTKCTL_EL1 的指令被重新定义为访问 CNTHCTL_EL2
+ + // 这允许设计运行在 EL1 的内核通过在 EL2 中访问 CNTKCTL_EL1, 透明地处理 EL0 位
+ + /*
+ + * Allow Non-secure EL1 and EL0 to access physical timer and counter.
+ + * This is not necessary for VHE, since the host kernel runs in EL2,
+ + * and EL0 accesses are configured in the later stage of boot process.
+ + * Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout
+ + * as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined
+ + * to access CNTHCTL_EL2. This allows the kernel designed to run at EL1
+ + * to transparently mess with the EL0 bits via CNTKCTL_EL1 access in
+ + * EL2.
+ + */
+ + cbnz x2, 1f // 检测寄存器x2中的值是否非零, 如果不为0, 则跳转到 标签1
+ + // 将系统控制寄存器cnthctl_el2的值移动到寄存器x0中, cnthctl_el2是一个特别的寄存器, 用于控制EL2的物理计数器和定时器硬件。
+ + mrs x0, cnthctl_el2
+ + //# (7) 启用与定时器和计数器相关的控制位
+ + orr x0, x0, #3 // Enable EL1 physical timers
+ + // 将寄存器x0的值写回到cnthctl_el2寄存器, 在EL2 中配置更新使能 EL1 定时器和计数器的相关控制设置
+ + msr cnthctl_el2, x0
+ + 1:
+ + // 将零值写入 cntvoff_el2 寄存器, 将虚拟计数器的虚拟偏移量重置为零
+ + msr cntvoff_el2, xzr // Clear virtual offset
+ +
+ + //# (8) 如果支持 GIC V3 中断, 同配置中断相关寄存器
+ + // 首先检查处理器是否支持 GICv3, 然后配置中断控制状态, 使其可以在EL2异常级别下处理, 且使能了系统寄存器接口, 且确保了这些更改被成功生效
+ + // 最后将另一个中断控制寄存器 (SYS_ICH_HCR_EL2) 重置回默认状态。
+ + #ifdef CONFIG_ARM_GIC_V3
+ + /* GICv3 system register access */
+ + mrs x0, id_aa64pfr0_el1 // 从id_aa64pfr0_el1寄存器中读取CPU功能, 并将值存储到x0寄存器
+ + ubfx x0, x0, #ID_AA64PFR0_GIC_SHIFT, #4 // 从x0中提取从ID_AA64PFR0_GIC_SHIFT开始的4位, 存回x0, 检查GIC的版本
+ + cbz x0, 3f // 如果提取到的值为0, 即没有GIC或不支持GICv3, 跳转到标签3
+ +
+ + // 如果支持 GIC V3
+ + mrs_s x0, SYS_ICC_SRE_EL2 // 从系统寄存器SYS_ICC_SRE_EL2读取中断控制状态, 并将值存储到x0寄存器
+ + orr x0, x0, #ICC_SRE_EL2_SRE // Set ICC_SRE_EL2.SRE==1 使能安全状态被路由到EL2
+ + orr x0, x0, #ICC_SRE_EL2_ENABLE // Set ICC_SRE_EL2.Enable==1 使能系统寄存器接口
+ + msr_s SYS_ICC_SRE_EL2, x0 // 将x0的值写入系统寄存器SYS_ICC_SRE_EL2
+ +
+ + isb // 指令同步屏障 Make sure SRE is now set
+ +
+ + mrs_s x0, SYS_ICC_SRE_EL2 // Read SRE back, 再次从SYS_ICC_SRE_EL2读取值到x0, 确保设置成功
+ + tbz x0, #0, 3f // and check that it sticks 检查ICC_SRE_EL2.SRE的值是否为1 (是否设置成功) , 如果没有成功跳转到标签3
+ + msr_s SYS_ICH_HCR_EL2, xzr // Reset ICC_HCR_EL2 to defaults , 将xzr (零值) 写入SYS_ICH_HCR_EL2寄存器, 重置到默认值
+ +
+ + 3:
+ + #endif
+ +
+ + //# (9) 读取当前CPU核心的ID寄存器, 并将其值映射到相应的虚拟化寄存器中
+ + /* Populate ID registers. */
+ + mrs x0, midr_el1 // 从MIDR_EL1寄存器 (Main ID Register) 中读取当前核心的主ID, 并将结果放入x0, 提供处理器型号等信息
+ + mrs x1, mpidr_el1 // 从MPIDR_EL1寄存器 (Multiprocessor Affinity Register) 中读取核心的affinity信息, 并将结果放入x1
+ + msr vpidr_el2, x0 // 将x0 (包含MIDR_EL1的值) 写入VPIDR_EL2寄存器, VPIDR_EL2是虚拟化的MIDR_EL1
+ + msr vmpidr_el2, x1 // 将x1 (包含MPIDR_EL1的值) 写入VMPIDR_EL2寄存器, VMPIDR_EL2是虚拟化的MPIDR_EL1
+ +
+ + #ifdef CONFIG_COMPAT
+ + //# (10) 禁用CP15陷阱(如果支持兼容模式)
+ + // 将hstr_el2寄存器的所有位清零, 关闭对EL2的所有CP15访问的陷入
+ + // 如果其他较低异常级别的代码 (例如EL0或EL1) 试图访问CP15兼容的系统控制寄存器, 这些访问不会被陷入Hypervisor来处理,
+ + // 而是允许它们直接访问或者遭遇访问错误, 取决于硬件实现和当前的其他系统配置状态
+ + msr hstr_el2, xzr // Disable CP15 traps to EL2
+ + #endif
+ +
+ + //# (11) 配置调试寄存器, 初始化和配置ARMv8处理器上的统计分析和性能监测功能
+ + /* EL2 debug */
+ + mrs x1, id_aa64dfr0_el1 // 将ID_AA64DFR0_EL1的值读到寄存器X1中
+ + sbfx x0, x1, #ID_AA64DFR0_PMUVER_SHIFT, #4 // 提取PMU版本字段
+ + cmp x0, #1 // 将PMU版本与1进行比较
+ + b.lt 4f // Skip if no PMU present, 如果X0小于1 (PMU版本<1) , 说明不存在PMU, 则跳转到标签4
+ + mrs x0, pmcr_el0 // Disable debug access traps 将PMCR_EL0的值读到寄存器X0中
+ + ubfx x0, x0, #11, #5 // to EL2 and allow access to 从PMCR_EL0中提取[15:11]位
+ + 4:
+ + csel x3, xzr, x0, lt // all PMU counters from EL1 如果存在PMU , 则将将x0的值赋给x3, 用于判断PMU版本
+ +
+ + /* Statistical profiling */
+ + ubfx x0, x1, #ID_AA64DFR0_PMSVER_SHIFT, #4 // 从x1寄存器提取统计分析版本信息到x0寄存器
+ + cbz x0, 7f // 如果统计分析 (SPE) 不可用 (x0为0) , 则跳转到标签7 Skip if SPE not present
+ + cbnz x2, 6f // 如果VHE (Virtualization Host Extensions) 标志为真 (x2非0) , 则跳转到标签6
+ +
+ + // 系统寄存器SYS_PMBIDR_EL1的值读入到寄存器x4中, 检查SYS_PMBIDR_EL1寄存器的P位, 如果P位为1, 则跳转到标签5
+ + mrs_s x4, SYS_PMBIDR_EL1 // 将 If SPE available at EL2,
+ + and x4, x4, #(1 << SYS_PMBIDR_EL1_P_SHIFT)
+ + cbnz x4, 5f // then permit sampling of physical
+ +
+ + //# (12) 使能物理地址抽样和物理计数器
+ + // 设置x4用于更新PMSCR_EL2寄存器, 使能物理地址抽样和物理计数器
+ + mov x4, #(1 << SYS_PMSCR_EL2_PCT_SHIFT | 1 << SYS_PMSCR_EL2_PA_SHIFT)
+ + msr_s SYS_PMSCR_EL2, x4 // addresses and physical counter
+ +
+ + 5:
+ + mov x1, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT) // 设置用于MDCR_EL2寄存器的值
+ + orr x3, x3, x1 // If we don't have VHE, then // 更新x3, 设置EL1和EL0使用同样的地址转换
+ + b 7f // use EL1&0 translation. // 跳转到标签7
+ + 6: // For VHE, use EL2 translation
+ + orr x3, x3, #MDCR_EL2_TPMS // and disable access from EL1 // 设置MDCR_EL2寄存器, 使用EL2的地址转换并禁止EL1访问
+ + 7:
+ + msr mdcr_el2, x3 // Configure debug traps
+ +
+ + //# (13) 清除硬件支持的本地操作区域
+ + /* LORegions 清除硬件支持的本地操作区域 */
+ + mrs x1, id_aa64mmfr1_el1 // 从系统寄存器 id_aa64mmfr1_el1 中读取当前CPU的内存管理特征寄存器1的值到寄存器 x1 中
+ + ubfx x0, x1, #ID_AA64MMFR1_LOR_SHIFT, 4 // 提取 id_aa64mmfr1_el1 寄存器中从 ID_AA64MMFR1_LOR_SHIFT 开始的4个位存在 x0 寄存器, 这4个位代表LORegions (硬件支持的本地操作区域) 的数量
+ + cbz x0, 1f // 判断的是是否有支持的LORegions, 如果不支持则跳转到 标签1
+ + msr_s SYS_LORC_EL1, xzr // 如果有支持的LORegions, 清除 SYS_LORC_EL1 寄存器的值, 对本地操作区域进行清除
+ + 1:
+ +
+ + //# (14) 设置虚拟化翻译表基址寄存器(vttbr_el2)
+ + /* Stage-2 translation 对第二阶段地址转换进行设置 */
+ + msr vttbr_el2, xzr // 将虚拟化转换表基址寄存器 vttbr_el2 设置为 0, 禁用EL2阶段的地址转换
+ +
+ + cbz x2, install_el2_stub // 检查寄存器 x2 是否为零, 如果是则跳转到 install_el2_stub
+ +
+ + mov w0, #BOOT_CPU_MODE_EL2 // 配置 CPU 在EL2 级别下执行, This CPU booted in EL2
+ +
+ + isb // 指令同步屏障
+ + ret
+ +
+ + SYM_INNER_LABEL(install_el2_stub, SYM_L_LOCAL)
+ + // 当VHE (虚拟化主机扩展) 未被使用时, 需要在此处进行EL2 (异常级别2) 和EL1 (异常级别1) 的早期初始化
+ + // 当VHE 正在 使用时, 主机将不会使用EL1, 因此不需要配置, 而所有非特定于hypervisor的EL2设置将通过__cpu_setup中的_EL1系统寄存器别名来完成
+ +
+ + // 配置CPU 运行在 EL1 级别
+ + mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)
+ + msr sctlr_el1, x0
+ +
+ + // 对系统控制寄存器、协处理器陷阱寄存器以及SVE向量长度寄存器等ARM系统寄存器的配置
+ + /* Coprocessor traps. */
+ + mov x0, #0x33ff
+ +
+ + //# (15) 禁用协处理器陷阱
+ + msr cptr_el2, x0 // 禁止协处理器向异常级别2发送陷阱 Disable copro. traps to EL2
+ +
+ + /* SVE register access */
+ + mrs x1, id_aa64pfr0_el1 // 从ID_AA64PFR0_EL1寄存器读取AArch64处理器功能寄存器0的值到寄存器x1中
+ + ubfx x1, x1, #ID_AA64PFR0_SVE_SHIFT, #4 // 将x1中从ID_AA64PFR0_SVE_SHIFT指定的偏移开始的4位提取到x1的低位
+ + cbz x1, 7f // 如果寄存器x1的值为0, 则跳转到标签7f
+ +
+ + //# (16) 配置SVE寄存器访问, 禁用特定于信任区域 (TrustZone) 的SVE (Scalable Vector Extension) 陷阱访问
+ + bic x0, x0, #CPTR_EL2_TZ // 禁用特定于信任区域 (TrustZone) 的SVE (Scalable Vector Extension) 陷阱相关 Also disable SVE traps
+ + msr cptr_el2, x0 // 更新协处理器陷阱寄存器CPTR_EL2的值, 以反映新的陷阱配置 Disable copro. traps to EL2
+ + isb // 执行指令同步屏障
+ +
+ + // 为EL1启用完整的SVE向量长度
+ + mov x1, #ZCR_ELx_LEN_MASK // SVE: Enable full vector
+ + msr_s SYS_ZCR_EL2, x1 // length for EL1.
+ +
+ +
+ + //# (17) 设置了Hypervisor的中断向量表, 配置了异常返回时的程序状态和返回地址
+ + /* Hypervisor stub */
+ + // 获取标签__hyp_stub_vectors的绝对地址, 并将这个地址存入寄存器x0中
+ + // __hyp_stub_vectors指向Hypervisor模式下的中断向量表。
+ + 7: adr_l x0, __hyp_stub_vectors
+ + {----------------------------------->
+ + + // 此处之所有有 EL1和EL2, 因方早期的ARMv8 CPU只支持Hyp模式的EL2, 该EL2还不足以实现完整的虚拟机和app, 需要HOST OS运行在EL1
+ + + // ARMv8 拓展版, 支持VHE模式, 即Host OS可以直接运行在EL2模式下, 此时VMM 和 Host OS可以共用EL2上的异常向量表
+ + +
+ + + // OH-V5.0\kernel\src_tmp\linux-5.10\arch\arm64\kernel\hyp-stub.S
+ + + SYM_CODE_START(__hyp_stub_vectors)
+ + + ventry el2_sync_invalid // b el2_sync_invalid // Synchronous EL2t 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL0时, 发生同步异常的向量入口
+ + + ventry el2_irq_invalid // b el2_irq_invalid // IRQ EL2t 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL0时, 发生IRQ中断异常的向量入口
+ + + ventry el2_fiq_invalid // b el2_fiq_invalid // FIQ EL2t 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL0时, 发生FIQ快速中断异常的向量入口
+ + + ventry el2_error_invalid // b el2_error_invalid // Error EL2t 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL0时, 发生错误异常的向量入口
+ + + //
+ + + ventry el2_sync_invalid // b el2_sync_invalid // Synchronous EL2h 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL2时, 发生同步异常的向量入口
+ + + ventry el2_irq_invalid // b el2_irq_invalid // IRQ EL2h 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL2时, 发生IRQ中断异常的向量入口
+ + + ventry el2_fiq_invalid // b el2_fiq_invalid // FIQ EL2h 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL2时, 发生FIQ快速中断异常的向量入口
+ + + ventry el2_error_invalid // b el2_error_invalid // Error EL2h 当CPU运行在EL2模式, 且堆栈寄存器为SP_EL2时, 发生错误异常的向量入口
+ + + //
+ + + ventry el1_sync // b el1_sync // Synchronous 64-bit EL1 当CPU AArch64模式, 运行在 EL0/EL1时, 发生同步异常的向量入口
+ + + ventry el1_irq_invalid // b el1_irq_invalid // IRQ 64-bit EL1 当CPU AArch64模式, 运行在 EL0/EL1时, 发生IRQ中断异常的向量入口
+ + + ventry el1_fiq_invalid // b el1_fiq_invalid // FIQ 64-bit EL1 当CPU AArch64模式, 运行在 EL0/EL1时, 发生FIQ快速中断异常的向量入口
+ + + ventry el1_error_invalid // b el1_error_invalid // Error 64-bit EL1 当CPU AArch64模式, 运行在 EL0/EL1时, 发生错误异常的向量入口
+ + + //
+ + + ventry el1_sync_invalid // b el1_sync_invalid // Synchronous 32-bit EL1 当CPU AArch32模式, 运行在 EL0/EL1时, 发生同步异常的向量入口
+ + + ventry el1_irq_invalid // b el1_irq_invalid // IRQ 32-bit EL1 当CPU AArch32模式, 运行在 EL0/EL1时, 发生IRQ中断异常的向量入口
+ + + ventry el1_fiq_invalid // b el1_fiq_invalid // FIQ 32-bit EL1 当CPU AArch32模式, 运行在 EL0/EL1时, 发生FIQ快速中断异常的向量入口
+ + + ventry el1_error_invalid // b el1_error_invalid // Error 32-bit EL1 当CPU AArch32模式, 运行在 EL0/EL1时, 发生错误异常的向量入口
+ + +
+ + + ------------------------->
+ + + + .macro invalid_vector label
+ + + + SYM_CODE_START_LOCAL(\label)
+ + + + b \label
+ + + + SYM_CODE_END(\label)
+ + + + .endm
+ + + +
+ + + + invalid_vector el2_sync_invalid // b el2_sync_invalid
+ + + + invalid_vector el2_irq_invalid // b el2_irq_invalid
+ + + + invalid_vector el2_fiq_invalid // b el2_fiq_invalid
+ + + + invalid_vector el2_error_invalid // b el2_error_invalid
+ + + + invalid_vector el1_sync_invalid // b el1_sync_invalid
+ + + + invalid_vector el1_irq_invalid // b el1_irq_invalid
+ + + + invalid_vector el1_fiq_invalid // b el1_fiq_invalid
+ + + + invalid_vector el1_error_invalid // b el1_error_invalid
+ + + <-------------------------
+ + +
+ + + SYM_CODE_END(__hyp_stub_vectors)
+ + <-----------------------------------}
+ +
+ + // 写入向量基地址寄存器VBAR_EL2, 配置 EL2 中断向量表的地址
+ + msr vbar_el2, x0
+ +
+ + // 使能 快速中断、普通中断、异步中断和调试异常屏蔽位, 以及处理器模式设置为EL1
+ + /* spsr */
+ + mov x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT | PSR_MODE_EL1h)
+ + msr spsr_el2, x0
+ +
+ + // 把链接寄存器lr的值 (通常包含返回地址) 写入到异常链接寄存器ELR_EL2
+ + msr elr_el2, lr
+ + // CPU 运行在EL2 模式
+ + mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2
+ +
+ + // 使处理器从EL2返回到之前由ELR_EL2所设置的地址, 并恢复SPSR_EL2中保存的程序状态
+ + eret
+ + SYM_FUNC_END(el2_setup)
+ <----------------------------}
+
+ // 计算 __PHYS_OFFSET 的物理地址的偏移大小, 保存在 x23 中
+ adrp x23, __PHYS_OFFSET
+ and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
+
+ //# 3. 根据 W0 的值, 配直 boot_mode_flag
+ bl set_cpu_boot_mode_flag
+ {---------------------------->
+ + // 根据w0 寄存器中的值 来判断当前是 CPU 运行在什么异常模式下
+ + SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
+ +
+ + adr_l x1, __boot_cpu_mode // 将 __boot_cpu_mode 的址址保存在 x1 中
+ + cmp w0, #BOOT_CPU_MODE_EL2 // 比较 w0 与 EL2
+ + b.ne 1f // 如果不等于 EL2, 说明在 EL1, 则跳转运行 标签1
+ + add x1, x1, #4 // x1 = x1 + 4 , 即将 x1 的地址偏移4byte
+ + 1: str w0, [x1] // 将 W0 的值写入[x1] 指向的地址, EL1 对应 [x1], EL2对应[x1+4]
+ +
+ + dmb sy // 内存屏障指令, 确保任何先前的写入操作在继续执行之前完成
+ + dc ivac, x1 // Invalidate potentially stale cache line
+ + ret
+ + SYM_FUNC_END(set_cpu_boot_mode_flag)
+ <----------------------------}
+
+ //# 4. 初始化idmap_pg_dir 和 init_pg_dir页表, 设置和更新系统的内存映射, 包括映射特定的内存区域(如内核镜像)到虚拟地址空间
+ bl __create_page_tables
+ {---------------------------->
+ + /* 设置初始的页表。我们只设置最小限度的页表, 以使内核能够运行
+ + * Setup the initial page tables. We only setup the barest amount which is required to get the kernel running. The following sections are required:
+ + * - identity mapping to enable the MMU (low address, TTBR0) 用于启用内存管理单元 (MMU) 的身份映射 (低地址, TTBR0)
+ + * - first few MB of the kernel linear mapping to jump to once the MMU has been enabled 在MMU启用后跳转所需的内核线性映射的最初几兆字节 (MB)
+ + */
+ + SYM_FUNC_START_LOCAL(__create_page_tables)
+ + // 保存函数返回地址到 x28 中
+ + mov x28, lr
+ +
+ + // 配置 init_pg_dir 页表区域缓存无效
+ + // 使初始化页表无效, 以避免潜在的脏缓存行被逐出, 其他页表作为内核映像的一部分被分配在只读数据区
+ + adrp x0, init_pg_dir // 加载 init_pg_dir 页表地址到 x0 中
+ + adrp x1, init_pg_end // 加载 init_pg_end 页表地址到 x1 中
+ + sub x1, x1, x0 // 读算 init_pg_dir 页表的大小
+ + bl __inval_dcache_area // 调用 __inval_dcache_area 指定 init_pg_dir页表部分的内存缓存无效
+ +
+ + //# (1)循环清零 init_pg_dir 页表区域的数据, 每次循环清零 64 byte大小
+ + adrp x0, init_pg_dir
+ + adrp x1, init_pg_end
+ + sub x1, x1, x0
+ + 1: stp xzr, xzr, [x0], #16
+ + stp xzr, xzr, [x0], #16
+ + stp xzr, xzr, [x0], #16
+ + stp xzr, xzr, [x0], #16
+ + subs x1, x1, #64
+ + b.ne 1b
+ +
+ + // 将MMU (内存管理单元) 标志加载到寄存器x7中
+ + mov x7, SWAPPER_MM_MMUFLAGS
+ +
+ + // 加载身份页表
+ + // 创建身份映射机制, 主要作用是实现物理地址到虚拟地址的直接映射, 启用MMU(内存管理单元)
+ + // (1) 身份映射将特定的物理内存区域(通常是内核代码和数据的初始部分)直接映射到相应的虚拟地址空间, 在这些区域内, 物理地址和虚拟地址是相同的, 简化了地址转换过程
+ + // (2) 在启用MMU之前, CPU直接访问物理内存, 为了提供内存保护、地址转换等功能, 内核需要配置MMU,身份映射是配置MMU过程中的一个重要步骤, 因为它确保了内核代码和数据在MMU启用后仍然可以被正确访问
+ + adrp x0, idmap_pg_dir
+ + adrp x3, __idmap_text_start // __pa(__idmap_text_start)
+ +
+ + #ifdef CONFIG_ARM64_VA_BITS_52
+ + // 通过读取系统寄存器 SYS_ID_AA64MMFR2_EL1 来确定实际的虚拟地址位数
+ + mrs_s x6, SYS_ID_AA64MMFR2_EL1
+ + and x6, x6, #(0xf << ID_AA64MMFR2_LVA_SHIFT)
+ + mov x5, #52
+ + cbnz x6, 1f // 如果 x6 的值不为0 , 则跳转运行标签1
+ + #endif
+ + mov x5, #VA_BITS_MIN // 如果没有定义52位虚拟地址, 则使用最小的虚拟地址位数
+ + 1:
+ + adr_l x6, vabits_actual // 将实际的虚拟地址位数存储到vabits_actual变量中
+ + str x5, [x6]
+ + dmb sy // 内存屏障
+ +
+ + // 使包含vabits_actual的内存行无效, 以确保后续对该变量的访问不会受到缓存中旧值的影响
+ + dc ivac, x6 // Invalidate potentially stale cache line
+ +
+ + // 如果系统RAM在物理地址空间中位置足够高, VA_BITS可能太小, 无法创建覆盖系统RAM的ID映射
+ + // 因此, 对于ID映射, 在这种情况下要使用扩展的虚拟范围, 并在需要时配置额外的转换级别
+ + // 计算TCR_EL1.T0SZ的最大允许值, 以便可以映射整个ID映射区域
+ + // 由于T0SZ等于(64 - #使用的位数), 这个数字恰好等于__idmap_text_end的物理地址中前导零的数量
+ +
+ + adrp x5, __idmap_text_end
+ + clz x5, x5
+ + cmp x5, TCR_T0SZ(VA_BITS_MIN) // default T0SZ small enough?
+ + b.ge 1f // .. then skip VA range extension
+ +
+ + adr_l x6, idmap_t0sz
+ + str x5, [x6]
+ + dmb sy
+ + dc ivac, x6 // Invalidate potentially stale cache line
+ +
+ + #if (VA_BITS < 48)
+ + #define EXTRA_SHIFT (PGDIR_SHIFT + PAGE_SHIFT - 3)
+ + #define EXTRA_PTRS (1 << (PHYS_MASK_SHIFT - EXTRA_SHIFT))
+ +
+ + // 如果VA_BITS小于48, 我们必须配置额外的表级别
+ + // 当前VA_BITS的值是这样选择的:所有转换级别都被充分利用, 并且降低T0SZ的值总是会导致需要配置额外的转换级别
+ + #if VA_BITS != EXTRA_SHIFT
+ + #error "Mismatch between VA_BITS and page size/number of translation levels"
+ + #endif
+ +
+ + mov x4, EXTRA_PTRS
+ + create_table_entry x0, x3, EXTRA_SHIFT, x4, x5, x6
+ + #else
+ + // 如果VA_BITS等于48, 我们不需要配置额外的转换级别, 但是顶级表有更多的条目
+ + mov x4, #1 << (PHYS_MASK_SHIFT - PGDIR_SHIFT)
+ + str_l x4, idmap_ptrs_per_pgd, x5
+ + #endif
+ +
+ + 1:
+ + // 加载idmap_ptrs_per_pgd的值到寄存器x4, idmap_ptrs_per_pgd表示每个页全局目录(PGD)中的指针数量
+ + ldr_l x4, idmap_ptrs_per_pgd
+ +
+ + // x3保存的是__pa(__idmap_text_start)的值, 即__idmap_text_start的物理地址
+ + mov x5, x3 // __pa(__idmap_text_start)
+ +
+ + // 加载__idmap_text_end的地址到x6, 即__idmap_text_end的物理地址
+ + adr_l x6, __idmap_text_end // __pa(__idmap_text_end)
+ +
+ + //# (2)它将从x3到x6的地址范围映射到某个虚拟地址空间, 对应参数为:x0, x1, x7, x3, x4, x10, x11, x12, x13, x14
+ + map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14
+ +
+ + //# (3)映射 Kernel内核 镜像所在的地址到虚拟地址空间
+ + // Map the kernel image (starting with PHYS_OFFSET)
+ + adrp x0, init_pg_dir // 加载init_pg_dir的地址到x0, 这是初始页全局目录的物理地址
+ + mov_q x5, KIMAGE_VADDR // 将编译时确定的__va(_text)(内核镜像的虚拟地址)加载到x5 ompile time __va(_text)
+ + add x5, x5, x23 // 将x23(KASLR位移)加到x5上, 调整内核镜像的虚拟地址 add KASLR displacement
+ + mov x4, PTRS_PER_PGD // 将每个页全局目录中的指针数量加载到x4
+ + adrp x6, _end // 加载 _end 的地址到x6 runtime __pa(_end)
+ + adrp x3, _text // 加载 _text 的地址到x3 runtime __pa(_text)
+ + sub x6, x6, x3 // 计算内核镜像的大小 _end - _text
+ + add x6, x6, x5 // 将内核镜像的大小加到其虚拟起始地址上, 得到虚拟结束地址 runtime __va(_end)
+ +
+ + // 映射内核镜像到虚拟地址空间
+ + map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14
+ +
+ + // 数据内存屏障操作
+ + dmb sy
+ +
+ + //# (4)调用__inval_dcache_area函数使idmap_pg_dir到idmap_pg_end地址范围内的数据缓存无效, 确保新的内存映射立即生效, 而不会被旧的缓存数据干扰
+ + adrp x0, idmap_pg_dir
+ + adrp x1, idmap_pg_end
+ + sub x1, x1, x0
+ + bl __inval_dcache_area
+ +
+ + //# (5)调用__inval_dcache_area函数使init_pg_dir到init_pg_end地址范围内的数据缓存无效, 确保新的内存映射立即生效, 而不会被旧的缓存数据干扰
+ + adrp x0, init_pg_dir
+ + adrp x1, init_pg_end
+ + sub x1, x1, x0
+ + bl __inval_dcache_area
+ +
+ + // 函数返回
+ + ret x28
+ + SYM_FUNC_END(__create_page_tables)
+ <----------------------------}
+
+ //# 5. 配置CPU, 配置TCR(转换控制寄存器)
+ bl __cpu_setup // initialise processor
+ {---------------------------->
+ + // kernel\src_tmp\linux-5.10\arch\arm64\mm\proc.S
+ +
+ + // 将接下来的代码段放入名为.idmap.text的段中,并设置该段的属性为awx(allocatable, writable, executable)
+ + .pushsection ".idmap.text", "awx"
+ +
+ + SYM_FUNC_START(__cpu_setup)
+ + //# (1) 无效化TLB, 清除本地TLB
+ + tlbi vmalle1 // 无效本地的TLB(Translation Lookaside Buffer) Invalidate local TLB
+ + dsb nsh // 确保之前的指令完成后再执行后续的指令, nsh表示不等待之前的存储操作完成
+ +
+ + //# (2) 启用FP/ASIMD,启用调试异常,并禁用PMU和AMU从EL0的访问权限
+ + //启用FP/ASIMD(浮点运算单元/高级SIMD),并重置调试控制寄存器mdscr_el1,禁止从EL0访问DCC(Debug Communication Channel),启用了调试异常,并禁用了性能监控单元(PMU)和自动监测单元(AMU)从EL0的访问权限
+ + mov x1, #3 << 20
+ + msr cpacr_el1, x1 // 使能浮点/ASIMD功能 Enable FP/ASIMD
+ +
+ + // 重置mdscr_el1并禁用从EL0访问DCC(Debug Communication Channel)
+ + mov x1, #1 << 12 // Reset mdscr_el1 and disable
+ + msr mdscr_el1, x1 // access to the DCC from EL0
+ + isb // 执行指令同步屏障 Unmask debug exceptions now,
+ +
+ + // 启用调试异常
+ + enable_dbg // since this is per-cpu
+ + ----------------------------->
+ + + .macro enable_dbg
+ + + msr daifclr, #8 // 使能DAIF(Debug, Abort, Interrupt, FIQ)寄存器中的Debug功能,能够响应debug中断
+ + + .endm
+ + + // D(Debug):调试异常屏蔽位。
+ + + // A(Abort):中止异常屏蔽位。
+ + + // I(Interrupt):普通中断屏蔽位。
+ + + // F(FIQ):快速中断请求屏蔽位。
+ + + // 当这些标志位被设置时,对应的异常或中断将被屏蔽,CPU不会响应它们。相反,当这些标志位被清除时,CPU将能够响应对应的异常或中断。
+ + +
+ + <-----------------------------
+ +
+ + reset_pmuserenr_el0 x1 // 禁用从EL0对PMU(Performance Monitor Unit)的访问 Disable PMU access from EL0
+ + reset_amuserenr_el0 x1 // 禁用从EL0对AMU(Activity Monitor Unit)的访问 Disable AMU access from EL0
+ +
+ + //# (3)内存区域属性设置
+ + // Memory region attributes
+ + mov_q x5, MAIR_EL1_SET // 将MAIR_EL1_SET的值移动到x5寄存器中,MAIR_EL1_SET是内存区域属性的设置值
+ +
+ + #ifdef CONFIG_ARM64_MTE
+ +
+ + //Update MAIR_EL1, GCR_EL1 and TFSR*_EL1 if MTE is supported (ID_AA64PFR1_EL1[11:8] > 1)
+ + // 将 ID_AA64PFR1_EL1 系统寄存器的值读取到x10寄存器中, 包含了ARM架构的某些特性支持信息
+ + mrs x10, ID_AA64PFR1_EL1
+ +
+ + // 提取的是ID_AA64PFR1_MTE_SHIFT到ID_AA64PFR1_MTE_SHIFT + 4之间的位保存在 x10 中
+ + ubfx x10, x10, #ID_AA64PFR1_MTE_SHIFT, #4
+ +
+ + // 检查MTE特性是否支持
+ + cmp x10, #ID_AA64PFR1_MTE
+ + // 如果不支持 MTE ,则跳转 标签1
+ + b.lt 1f
+ +
+ + // Normal Tagged memory type at the corresponding MAIR index
+ + // 将MAIR_ATTR_NORMAL_TAGGED的值移动到x10寄存器中, MAIR_ATTR_NORMAL_TAGGED是一个常量,表示正常标记内存的属性
+ + mov x10, #MAIR_ATTR_NORMAL_TAGGED
+ + // 将x10寄存器中的值插入到x5寄存器的指定位置
+ + bfi x5, x10, #(8 * MT_NORMAL_TAGGED), #8
+ +
+ + // 设置SYS_GCR_EL1寄存器的值,以排除所有非零标签
+ + // initialize GCR_EL1: all non-zero tags excluded by default
+ + mov x10, #(SYS_GCR_EL1_RRND | SYS_GCR_EL1_EXCL_MASK)
+ + msr_s SYS_GCR_EL1, x10
+ +
+ + // 将xzr(零寄存器)的值写入到SYS_TFSR_EL1和SYS_TFSRE0_EL1系统寄存器中,以清除任何挂起的标签检查故障
+ +
+ + // 如果GCR_EL1.RRND=1的实现方式与RRND=0相同, 那么为了让IRG(伪随机数生成器)生成伪随机数, RGSR_EL1.SEED 必须是非零值
+ + // 由于 RGSR_EL1 在复位后处于未知状态, 我们必须对其进行初始化
+ + mrs x10, CNTVCT_EL0
+ + ands x10, x10, #SYS_RGSR_EL1_SEED_MASK
+ + csinc x10, x10, xzr, ne
+ + lsl x10, x10, #SYS_RGSR_EL1_SEED_SHIFT
+ + msr_s SYS_RGSR_EL1, x10
+ +
+ + // 清除任何挂起的标签检查故障 clear any pending tag check faults in TFSR*_EL1
+ + msr_s SYS_TFSR_EL1, xzr
+ + msr_s SYS_TFSRE0_EL1, xzr
+ + 1:
+ + #endif
+ +
+ + // 将x5寄存器中的值写入到MAIR_EL1(Memory Attribute Indirection Register at Exception Level 1)系统寄存器中
+ + // MAIR_EL1寄存器用于定义内存属性的间接寻址,它允许操作系统或软件为特定的内存区域指定访问权限和属性
+ + msr mair_el1, x5
+ +
+ + //# (4) 初始化和配置TCR_EL1寄存器
+ + // 设置TCR_EL1初始值
+ + // 配置TCR寄存器的不同方面,如地址空间大小(TCR_TxSZ)、缓存策略(TCR_CACHE_FLAGS)、多处理器支持(TCR_SMP_FLAGS)
+ + // 标签粒度(TCR_TG_FLAGS)、内核地址空间布局随机化(TCR_KASLR_FLAGS)、ASID大小(TCR_ASID16)、TBI(Tag-checked Bypass Index)配置(TCR_TBI0)、
+ + // 地址空间标识符(TCR_A1)以及内核地址消毒器(TCR_KASAN_FLAGS)等。
+ + // Set/prepare TCR and TTBR. We use 512GB (39-bit) address range for both user and kernel.
+ + mov_q x10, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | TCR_TBI0 | TCR_A1 | TCR_KASAN_FLAGS
+ +
+ + // 清除x10寄存器中TCR配置的错误修正位(errata bits),并将结果存储在x9寄存器中,同时使用x5寄存器作为可能的输入或参数
+ + tcr_clear_errata_bits x10, x9, x5
+ +
+ + // 配置TCR_EL1中的T1SZ和T0SZ
+ + #ifdef CONFIG_ARM64_VA_BITS_52
+ + ldr_l x9, vabits_actual // 将vabits_actual地址处的值加载到x9寄存器中, vabits_actual是一个存储了实际虚拟地址位数(VA bits)的内存地址
+ + sub x9, xzr, x9 // 将x9寄存器中的值取反(因为xzr总是包含0)
+ + add x9, x9, #64 // 将x9寄存器中的值加上64
+ + tcr_set_t1sz x10, x9 // 设置x10寄存器中TCR的T1SZ(Translation Table 1 Size)字段, T1SZ字段用于指定第一级翻译表的大小
+ + #else
+ + // 将idmap_t0sz地址处的值加载到x9寄存器中, idmap_t0sz可能是一个存储了与ID映射表0大小相关的值的内存地址
+ + ldr_l x9, idmap_t0sz
+ + #endif
+ +
+ + // 将x9寄存器中的值设置到x10寄存器所代表的TCR(Translation Control Register)的T0SZ(Translation Table 0 Size)字段中
+ + // T0SZ字段用于指定第一级(或零级)翻译表的大小
+ + tcr_set_t0sz x10, x9
+ +
+ + // 设置TCR_EL1中的IPS位
+ + // 传参 x5,x6 计算物理地址空间的大小,并将结果存储在x10寄存器中
+ + tcr_compute_pa_size x10, #TCR_IPS_SHIFT, x5, x6
+ +
+ + // 可选地启用硬件Access Flag更新
+ + #ifdef CONFIG_ARM64_HW_AFDBM
+ + /*
+ + * Enable hardware update of the Access Flags bit.
+ + * Hardware dirty bit management is enabled later,
+ + * via capabilities.
+ + */
+ + // 将系统控制寄存器ID_AA64MMFR1_EL1的值读取到x9寄存器中
+ + mrs x9, ID_AA64MMFR1_EL1
+ + and x9, x9, #0xf // 将x9寄存器中的值与立即数0xf进行按位与操作,结果存回x9寄存器
+ + cbz x9, 1f // 如果x9寄存器中的值为零,则跳转到标签1f处执行
+ + // 配置使能 硬件访问标志(Hardware Access flag)
+ + orr x10, x10, #TCR_HA // hardware Access flag update
+ + 1:
+ + #endif /* CONFIG_ARM64_HW_AFDBM */
+ + msr tcr_el1, x10 // 将x10寄存器中的值写回到系统控制寄存器TCR_EL1中
+ +
+ + // Prepare SCTLR
+ + // SCTLR_EL1_SET是一个用于设置系统控制寄存器SCTLR_EL1的位掩码, 将其作为返回值保存在 x0中
+ + mov_q x0, SCTLR_EL1_SET
+ + ret // return to head.S
+ + SYM_FUNC_END(__cpu_setup)
+ +
+ <----------------------------}
+
+ //# 6. 使能 MMU, 重定位Kernel, 禁用MMU,重定位Kernel内存映射表,然后再使能MMU
+ b __primary_switch
+ {---------------------------->
+ + SYM_FUNC_START_LOCAL(__primary_switch)
+ + #ifdef CONFIG_RANDOMIZE_BASE
+ + // 将新的SCTLR_EL1值保存到x19寄存器中
+ + mov x19, x0 // preserve new SCTLR_EL1 value
+ + // 将系统控制寄存器SCTLR_EL1的当前值读取到x20寄存器中
+ + mrs x20, sctlr_el1 // preserve old SCTLR_EL1 value
+ + #endif
+ +
+ + //# (1)为了后续配置MMU时指定页表的起始地址, 将init_pg_dir标签的地址(通常是页目录的起始地址)加载到x1寄存器的高32位中
+ + adrp x1, init_pg_dir
+ +
+ + //# (2)跳转到__enable_mmu函数, 启用MMU,设置页表、配置TLB(Translation Lookaside Buffer)等
+ + bl __enable_mmu
+ + {--------------------->
+ + + SYM_FUNC_START(__enable_mmu)
+ + + mrs x2, ID_AA64MMFR0_EL1 // 将ID_AA64MMFR0_EL1系统寄存器的值读取到x2寄存器中, 包含了关于内存模型特性的信息
+ + + ubfx x2, x2, #ID_AA64MMFR0_TGRAN_SHIFT, 4 // 从x2寄存器中提取一个4位的无符号位字段,起始位置由ID_AA64MMFR0_TGRAN_SHIFT宏定义指定,并将提取的值存回x2寄存器
+ + + cmp x2, #ID_AA64MMFR0_TGRAN_SUPPORTED_MIN // 检查是否小于 最小粒度值
+ + + b.lt __no_granule_support // 如果小于最小粒度值,则跳转 __no_granule_support
+ + + cmp x2, #ID_AA64MMFR0_TGRAN_SUPPORTED_MAX // 检查是否大于 最小粒度值
+ + + b.gt __no_granule_support // 如果大于最小粒度值,则跳转 __no_granule_support
+ + + update_early_cpu_boot_status 0, x2, x3 // 更新CPU启动状态的相关信息
+ + + adrp x2, idmap_pg_dir // 将idmap_pg_dir标签的地址加载到x2寄存器的高32位中
+ + +
+ + + phys_to_ttbr x1, x1 // 将 x1 物理地址转换为TTBR(Translation Table Base Register)寄存器所需的格式,并更新相应的寄存器
+ + + phys_to_ttbr x2, x2 // 将 x2 物理地址转换为TTBR(Translation Table Base Register)寄存器所需的格式,并更新相应的寄存器
+ + + msr ttbr0_el1, x2 // load TTBR0 将x2寄存器的值写入TTBR0_EL1和TTBR1_EL1系统寄存器中,用于设置翻译表的基地址
+ + + offset_ttbr1 x1, x3
+ + + msr ttbr1_el1, x1 // load TTBR1 将x1寄存器的值写入TTBR0_EL1和TTBR1_EL1系统寄存器中,用于设置翻译表的基地址
+ + + isb // 指令同步屏障
+ + + msr sctlr_el1, x0 // 设置系统控制寄存器的值,从而启用或配置MMU
+ + + isb // 指令同步屏障
+ + + /*
+ + + * Invalidate the local I-cache so that any instructions fetched
+ + + * speculatively from the PoC are discarded, since they may have
+ + + * been dynamically patched at the PoU.
+ + + */
+ + + ic iallu // 使统一缓存中的所有指令缓存行失效, 确保在更改执行环境或执行特定类型的内存操作后,指令缓存不会包含旧的或无效的数据
+ + + dsb nsh // Data Synchronization Barrier 数据同步屏障
+ + + isb // 指令同步屏障
+ + + ret
+ + + SYM_FUNC_END(__enable_mmu)
+ + <---------------------}
+ +
+ + // 内核重定向
+ + #ifdef CONFIG_RELOCATABLE
+ +
+ + #ifdef CONFIG_RELR
+ + mov x24, #0 // no RELR displacement yet
+ + #endif
+ +
+ + //# (3)循环重定向内核
+ + bl __relocate_kernel
+ + {------------------------------------>
+ + + #ifdef CONFIG_RELOCATABLE
+ + + SYM_FUNC_START_LOCAL(__relocate_kernel)
+ + + // Iterate over each entry in the relocation table, and apply the relocations in place
+ + + ldr w9, =__rela_offset // 加载重定位表的偏移量到寄存器w9 offset to reloc table
+ + + ldr w10, =__rela_size // 加载重定位表的大小到寄存器w10 size of reloc table
+ + +
+ + + mov_q x11, KIMAGE_VADDR // 将默认的虚拟偏移量加载到寄存器x11
+ + + add x11, x11, x23 // 使用x23寄存器中的值更新x11,得到实际的虚拟偏移量
+ + + add x9, x9, x11 // 将x11的值加到x9上,获得重定位表的实际地址(__va(.rela))
+ + + add x10, x9, x10 // 将x10的值加到x9上,获得重定位表末尾的位置(__va(.rela) + sizeof(.rela))
+ + +
+ + + 0: cmp x9, x10 // 比较当前重定位表的位置x9和重定位表的末尾x10
+ + + b.hs 1f // 如果x9大于等于x10,说明拷贝结束,则跳转到标号1处(结束循环)
+ + + ldp x12, x13, [x9], #24 // 加载24字节的数据到寄存器x12和x13,并将指针x9向后移动24字节
+ + + ldr x14, [x9, #-8] // 加载x9前8字节的数据到寄存器x14
+ + + cmp w13, #R_AARCH64_RELATIVE// 检查x13中的低位字(w13)是否为R_AARCH64_RELATIVE类型
+ + + b.ne 0b // 如果不是R_AARCH64_RELATIVE类型,则跳回到标号0处继续循环
+ + + add x14, x14, x23 // 对x14中的地址进行重定位
+ + + str x14, [x12, x23] // 将重定位后的值写回原位置
+ + + b 0b // 跳回到标号0处继续循环
+ + +
+ + + 1:
+ + + #ifdef CONFIG_RELR
+ + + // 说明下如何解析和处理RELR格式的重定位数据
+ + + // RELR是一种用于存储相对重定位信息的压缩格式, 它的编码序列看起来像这样:
+ + + // [ AAAAAAAA BBBBBBB1 BBBBBBB1 ... AAAAAAAA BBBBBB1 ... ]
+ + + // AAAAAAAA 表示一个地址条目
+ + + // BBBBBBBB1 表示一个位图条目
+ + + // 编码规则:
+ + + // 地址条目:序列以一个地址条目开始,这个地址条目包含了1个重定位信息
+ + + // 位图条目:随后可以跟随任意数量的位图条目,每个位图条目最多可以编码63个重定位信息,这些重定位信息位于最后一个地址条目之后的连续位置
+ + + // 位图格式:位图条目的最低有效位(LSB)必须为1。这是因为假设地址条目的最低有效位不可能为1,因此奇数地址不受支持。
+ + + // 解析规则
+ + + // 除了最低有效位之外,位图中的每一个非零比特代表一个需要被重定位的机器字(word),这些机器字紧跟着基地址条目出现
+ + + // 第二个最低有效位表示紧跟在初始地址后面的机器字,接下来的每个比特依次表示后续的机器字,按线性顺序排列
+ + + // 一个位图可以在一个64位的对象中编码最多63个重定位信息
+ + + // 实现细节
+ + + // 在实现过程中,我们将下一个RELR表条目的地址存储在寄存器x9中,当前地址或位图条目所要重定位的地址存储在寄存器x13中,而当前比特所要重定位的地址则存储在寄存器x14中
+ + + // 由于附加值(addend)已经存储在二进制文件中,RELR重定位不能被幂等应用。我们使用寄存器x24来跟踪当前已应用的位移,以便在__relocate_kernel函数被两次调用且位移不为零的情况下正确地进行重定位(即存在物理对齐错误和KASLR位移的情况)
+ + + //
+ + +
+ + + // 处理RELR格式的重定位表中的地址条目和位图条目,确保每个需要重定位的位置都被正确地更新
+ + +
+ + + // 初始化重定位表的相关信息
+ + + ldr w9, =__relr_offset // 加载重定位表的偏移量到寄存器w9 offset to reloc table
+ + + ldr w10, =__relr_size // 加载重定位表的大小到寄存器w10 size of reloc table
+ + + add x9, x9, x11 // 将重定位表的虚拟地址计算并存储到x9 __va(.relr)
+ + + add x10, x9, x10 // 计算重定位表结束地址,并存储到x10 __va(.relr) + sizeof(.relr)
+ + +
+ + + // 检查是否有新的偏移量需要处理
+ + + sub x15, x23, x24 // 计算新旧偏移量之间的差值,并存储到x15 delta from previous offset
+ + + cbz x15, 7f // 如果没有变化,则跳转到标签7f nothing to do if unchanged
+ + + mov x24, x23 // 否则,保存新的偏移量到x24 save new offset
+ + +
+ + + // 检查重定位表是否已经被完全处理
+ + + 2: cmp x9, x10 // 比较x9和x10,如果x9大于等于x10则跳转到标签7f
+ + + b.hs 7f
+ + + ldr x11, [x9], #8 // 从x9指向的位置加载一个8字节的数据到x11
+ + + tbnz x11, #0, 3f // 如果x11的最低有效位为0,则跳转到标签3f处理位图 branch to handle bitmaps
+ + +
+ + + // 处理非位图的重定位条目
+ + + add x13, x11, x23 // 将地址条目的值加上旧偏移量,并存储到x13
+ + + ldr x12, [x13] // 从x13指向的位置加载一个8字节的数据到x12 relocate address entry
+ + + add x12, x12, x15 // 对x12进行重定位
+ + + str x12, [x13], #8 // 将重定位后的值写回到x13指向的位置,并调整x9到下一个条目 adjust to start of bitmap
+ + + b 2b
+ + +
+ + + // 处理位图中的位
+ + + 3: mov x14, x13 // 初始化x14为x13
+ + + 4: lsr x11, x11, #1 // 将x11右移一位
+ + + cbz x11, 6f // 如果x11为0,则跳转到标签6f
+ + + tbz x11, #0, 5f // 如果当前位为0,则跳过并继续处理下一个位
+ + + ldr x12, [x14] // 从x14指向的位置加载一个8字节的数据到x12
+ + + add x12, x12, x15 // 对x12进行重定位
+ + + str x12, [x14] // 将重定位后的值写回到x14指向的位置
+ + +
+ + + // 移动到下一个位的地址,并循环处理剩余的位
+ + + 5: add x14, x14, #8 // 移动到下一个位的地址
+ + + b 4b // 跳回标签4b继续处理位图中的其他位
+ + +
+ + + 6: //Move to the next bitmap's address. 8 is the word size, and 63 is the number of significant bits in a bitmap entry.
+ + + add x13, x13, #(8 * 63) // 移动到下一个位图的地址
+ + + b 2b // 跳回标签2b继续处理重定位表中的其他条目
+ + +
+ + + 7:
+ + + #endif
+ + + ret
+ + +
+ + + // 有关重定位位图的理解
+ + + // 假设有8个地址需要检查是否需要重定位, 地址 A1, A2, A3, A4, A5, A6, A7, A8
+ + + // 每个格子代表一个比特位,标记为1表示需要重定位,标记为0表示不需要重定位。
+ + + // 当处理器读取这个位图时,它会检查每个比特位的状态, 处理器可以更高效地跳过那些不需要重定位的位置,从而加快整个重定位的过程
+ + + // +---+---+---+---+---+---+---+---+
+ + + // | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
+ + + // +---+---+---+---+---+---+---+---+
+ + + // | A1 A2 A3 A4 A5 A6 A7 A8|
+ + + // +---+---+---+---+---+---+---+---+
+ + + //
+ + +
+ + + SYM_FUNC_END(__relocate_kernel)
+ + + #endif
+ + <-------------------------------------}
+ +
+ + #ifdef CONFIG_RANDOMIZE_BASE
+ +
+ + //# (4)跳转运行 __primary_switch, 传参 x0 = __PHYS_OFFSET
+ + ldr x8, =__primary_switched
+ + adrp x0, __PHYS_OFFSET
+ + blr x8
+ +
+ +
+ +
+ + //# (5)正常来说,此处不会返回,如果返回了,说明前面重定位有问题,则需要重新创建页表,重定位内核
+ +
+ + // 如果在此处返回,由于KASLR机制的应用,导致x23寄存器中存储的内核映射基地址发生了偏移,
+ + // 我们需要丢弃当前的内核内存映射,并创建一个新的映射来确保正确性
+ +
+ + //# (6)禁用MMU
+ + pre_disable_mmu_workaround // 禁用MMU之前做一些准备工作,比如保存状态或者设置标志位
+ + msr sctlr_el1, x20 // 禁用MMU disable the MMU
+ + isb // 执行指令流水线屏障
+ +
+ + //# (7)重新初始化idmap_pg_dir 和 init_pg_dir页表, 重新创建内核映射
+ + bl __create_page_tables // recreate kernel mapping
+ +
+ + //# (8)执行TLB(转换后备缓冲器)全局无效化操作,清除所有的TLB条目,这样可以确保任何旧的页表项不会被使用
+ + tlbi vmalle1 // Remove any stale TLB entries
+ + dsb nsh // 执行数据流屏障
+ + isb // 执行指令流水线屏障
+ +
+ + //# (9)重新启用MMU
+ + msr sctlr_el1, x19 // re-enable the MMU
+ + isb // 执行指令流水线屏障
+ +
+ + //# (10)执行指令缓存全局无效化(Invalidate Instruction Cache All)操作,清除指令缓存,确保CPU不会从旧的映射中获取指令
+ + ic iallu // flush instructions fetched
+ + dsb nsh // 执行数据流屏障 via old mapping
+ + isb // 执行指令流水线屏障
+ +
+ + //# (11)重定位内核
+ + bl __relocate_kernel
+ + #endif
+ + #endif
+ +
+ + //# (12)再次跳转运行 __primary_switched
+ + ldr x8, =__primary_switched
+ + adrp x0, __PHYS_OFFSET
+ + br x8
+ + SYM_FUNC_END(__primary_switch)
+ <----------------------------}
+
+ //# 7. 跳转运行 start_kernel 开始初始化内核
+ __primary_switched
+ {---------------------------->
+ + // x0 = __PHYS_OFFSET
+ + SYM_FUNC_START_LOCAL(__primary_switched)
+ + // 初始化内核的栈、设置中断向量表地址、保存重要的内存指针,并且清空未初始化的数据段,为内核的进一步运行做准备
+ +
+ + //# (1) 栈初始化, 为当前的内核线程分配了一个栈空间
+ + // 通过地址寄存器偏移加载(Address Register Pair)指令将init_thread_union的高20位地址加载到x4寄存器中
+ + // init_thread_union是内核线程上下文的一个起始地址
+ + adrp x4, init_thread_union
+ +
+ + // 计算栈顶指针sp,使其指向init_thread_union之后的THREAD_SIZE字节处。这是为当前线程分配栈空间
+ + add sp, x4, #THREAD_SIZE // THREAD_SIZE则定义了栈的大小
+ +
+ + //# (2) 保存线程信息, 记录线程的初始状态
+ + // 加载init_task的地址到x5寄存器中, init_task是指向第一个内核任务的指针
+ + adr_l x5, init_task
+ +
+ + // 将x5寄存器中的值(即init_task的地址)保存到EL0(最低权限级别)的SP寄存器中, 这是为了保存线程信息
+ + msr sp_el0, x5 // Save thread_info
+ +
+ + //# (3) 指针认证初始化, 目的是为了增强系统的安全性,通过初始化指针认证密钥来保护重要数据免受篡改
+ + #ifdef CONFIG_ARM64_PTR_AUTH // 如果启用了指针认证功能
+ + __ptrauth_keys_init_cpu x5, x6, x7, x8 // 初始化指针认证密钥, 为了提高系统的安全性
+ + #endif
+ +
+ + //# (4) 设置中断向量表地址
+ + // 加载中断向量表的地址到x8寄存器中
+ + adr_l x8, vectors // load VBAR_EL1 with virtual
+ + // 将x8寄存器中的值(中断向量表的地址)写入到vbar_el1寄存器中, 设置异常向量基址寄存器,使得处理器知道在哪里查找异常处理程序
+ + msr vbar_el1, x8 // vector table address
+ + isb // 指令流水线屏障
+ +
+ + //# (5) 保存寄存器和设置帧指针, 便于后续的函数调用能够正确恢复上下文
+ + // 将xzr和x30寄存器的值保存到栈上,并更新栈指针, 保存返回地址和其他状态
+ + stp xzr, x30, [sp, #-16]!
+ + // 将当前的栈指针复制到x29寄存器中, x29通常被用作帧指针
+ + mov x29, sp
+ +
+ + //# (6) 影子调用栈初始化, 作用是初始化一个额外的栈,用于增加对缓冲区溢出攻击的防御能力
+ + #ifdef CONFIG_SHADOW_CALL_STACK // 如果启用了影子调用栈
+ + // 加载影子调用栈的起始地址到scs_sp寄存器中
+ + adr_l scs_sp, init_shadow_call_stack // Set shadow call stack
+ + #endif
+ +
+ + //# (7) 保存FDT指针设备树的信息
+ + // 将x21寄存器的值存储到__fdt_pointer所指向的位置,x5提供了基地址, 保存FDT(Flat Device Tree)设备树指针
+ + str_l x21, __fdt_pointer, x5 // Save FDT pointer
+ +
+ + //# (8) 计算并保存内核虚拟/物理地址偏移量, 有助于内核在不同的内存布局中正确地定位自身
+ + // 加载内核设备树映像的虚拟地址到x4寄存器中
+ + ldr_l x4, kimage_vaddr // Save the offset between
+ + // 计算内核虚拟地址和物理地址之间的偏移量
+ + sub x4, x4, x0 // the kernel virtual and
+ + // 将计算出的偏移量存储到kimage_voffset所指向的位置,x5提供了基地址
+ + str_l x4, kimage_voffset, x5 // physical mappings
+ +
+ + //# (9) 清空未初始化的数据段(BSS段)
+ + adr_l x0, __bss_start // 加载未初始化的数据段(BSS段)的起始地址到x0寄存器中
+ + mov x1, xzr // 将x1寄存器设置为0(xzr表示零寄存器)
+ + adr_l x2, __bss_stop // 加载BSS段的结束地址到x2寄存器中
+ + sub x2, x2, x0 // 计算BSS段的大小
+ + bl __pi_memset // 调用__pi_memset函数,将BSS段清零, 初始化未初始化的数据区域
+ + dsb ishst // 执行数据同步屏障(Data Synchronization Barrier),确保BSS段被正确清零,并且该修改对页面表写入器(Page Table Walker)可见
+ +
+ + //# (10) 初始化内核指针认证(KASAN), KASAN是一个内核指针认证机制,用于检测内核中的指针错误
+ + #ifdef CONFIG_KASAN
+ + bl kasan_early_init
+ + #endif
+ +
+ + #ifdef CONFIG_RANDOMIZE_BASE
+ + //# (11) 检查是否已经进行了基址随机化
+ + // 如果编译配置CONFIG_RANDOMIZE_BASE被启用,则测试x23寄存器的值,判断内核是否已经被随机化。
+ + // ~(MIN_KIMG_ALIGN - 1)确保x23的低几位全为1,这样可以通过AND操作来检测x23的低位是否为0,
+ + // 如果为0则表示尚未随机化,否则已经随机化
+ + tst x23, ~(MIN_KIMG_ALIGN - 1) // already running randomized?
+ + b.ne 0f
+ +
+ + //# (12) 初始化内核地址空间布局随机化(KASLR),如果KASLR被启用且尚未随机化,则记录其偏移量,并返回到之前的函数, 如果KASLR未启用或者已经随机化,则直接清理栈空间并启动内核
+ + // 解析FDT以获取KASLR选项
+ + // 如果内核尚未随机化,则将FDT(Flat Device Tree)地址传递给x0寄存器,
+ + // 并调用kaslr_early_init函数来解析FDT,查找有关KASLR的选项
+ + // 如果kaslr_early_init函数返回x0寄存器中的值为0,则表示KASLR未启用,继续执行后面的代码
+ + mov x0, x21 // pass FDT address in x0
+ + bl kaslr_early_init // parse FDT for KASLR options
+ + cbz x0, 0f // KASLR disabled? just proceed
+ +
+ + // 记录KASLR偏移量
+ + orr x23, x23, x0 // record KASLR offset
+ +
+ + // 启用KASLR并返回
+ + ldp x29, x30, [sp], #16 // we must enable KASLR, return
+ + ret // to __primary_switch()
+ + 0:
+ + #endif
+ +
+ + //# (13) 清理栈空间并跳转到启动内核, 运行C函数 start_kernel()
+ + add sp, sp, #16
+ + mov x29, #0
+ + mov x30, #0
+ + b start_kernel
+ + SYM_FUNC_END(__primary_switched)
+ +
+ <----------------------------}
+
+ SYM_CODE_END(primary_entry)
<---------------------------}
.long 0 // reserved
.quad 0 // Image load offset from start of RAM, little-endian
le64sym _kernel_size_le // Effective size of kernel image, little-endian
le64sym _kernel_flags_le // Informative flags, little-endian
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.ascii ARM64_IMAGE_MAGIC // Magic number #define ARM64_IMAGE_MAGIC "ARM\x64"
.long 0 // reserved