【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】004 - Kernel 启动引导代码head.S 源码逐行分析

【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】004 - Kernel 启动引导代码head.S 源码逐行分析


系列文章汇总:《鸿蒙OH-v5.0源码分析之 Uboot+Kernel 部分】000 - 文章链接汇总》
本文链接:《【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】004 - Kernel 启动引导代码head.S 源码逐行分析》


head.S 主要工作如下:

    1. 保存内核启动参数, 无效化处理器缓存
      (1) 将设备树地址转存到 x21中, x0中保存着fdt设备树地址
      (2) 加载 boot_args 的地址到寄存器 x0 (指向内核命令行参数字符串的位置)
      (3) 将x21,x1,x2,x3 寄存器的值依次保存到boot_args地址中, 顺序为:boot_args[0] = x21, boot_args[1] = x1, boot_args[2] = x2, boot_args[3] = x3
      (4) 数据内存屏障, 确保之前的所有内存访问操作完成后, 才能执行该指令之后的内存访问操作
      (5) 无效化处理器缓存管理, 清理及关闭处理器的缓存操作
    1. 配置CPU异常处理级别, 做一些CPU虚拟化配置,同时配置Hypervisor的中断向量表, 配置了异常返回时的程序状态和返回地址
      (1) 设置SP_EL1/EL2寄存器
      (2) 检查当前异常级别
      (3) 配置当前异常等级模式为 EL2
      (4) 检查是否支持虚拟化主机扩展 (VHE) , 在剩余的 EL2 设置过程中, 如果x2非零, 则表示我们确实拥有VHE, 并且内核预期将在EL2下运行
      (5) 配置 Hypervision 虚拟化
      (6) 配置当前 EL2 支持虚拟化
      (7) 启用与定时器和计数器相关的控制位
      (8) 如果支持 GIC V3 中断, 同配置中断相关寄存器
      (9) 读取当前CPU核心的ID寄存器, 并将其值映射到相应的虚拟化寄存器中
      (10) 禁用CP15陷阱(如果支持兼容模式)
      (11) 配置调试寄存器, 初始化和配置ARMv8处理器上的统计分析和性能监测功能
      (12) 使能物理地址抽样和物理计数器
      (13) 清除硬件支持的本地操作区域
      (14) 设置虚拟化翻译表基址寄存器(vttbr_el2)
      (15) 禁用协处理器陷阱
      (16) 配置SVE寄存器访问, 禁用特定于信任区域 (TrustZone) 的SVE (Scalable Vector Extension) 陷阱访问
      (17) 设置了Hypervisor的中断向量表, 配置了异常返回时的程序状态和返回地址
    1. 根据 W0 的值, 配直 boot_mode_flag
    1. 初始化idmap_pg_dir 和 init_pg_dir页表, 设置和更新系统的内存映射, 包括映射特定的内存区域(如内核镜像)到虚拟地址空间
      (1)循环清零 init_pg_dir 页表区域的数据, 每次循环清零 64 byte大小
      (2)它将从x3到x6的地址范围映射到某个虚拟地址空间, 对应参数为:x0, x1, x7, x3, x4, x10, x11, x12, x13, x14
      (3)映射 Kernel内核 镜像所在的地址到虚拟地址空间
      (4)调用__inval_dcache_area函数使idmap_pg_dir到idmap_pg_end地址范围内的数据缓存无效, 确保新的内存映射立即生效, 而不会被旧的缓存数据干扰
      (5)调用__inval_dcache_area函数使init_pg_dir到init_pg_end地址范围内的数据缓存无效, 确保新的内存映射立即生效, 而不会被旧的缓存数据干扰
    1. 配置CPU, 配置TCR(转换控制寄存器)
      (1) 无效化TLB, 清除本地TLB
      (2) 启用FP/ASIMD,启用调试异常,并禁用PMU和AMU从EL0的访问权限
      (3)内存区域属性设置
      (4) 初始化和配置TCR_EL1寄存器
    1. 使能 MMU, 重定位Kernel, 禁用MMU,重定位Kernel内存映射表,然后再使能MMU
      (1)为了后续配置MMU时指定页表的起始地址, 将init_pg_dir标签的地址(通常是页目录的起始地址)加载到x1寄存器的高32位中
      (2)跳转到__enable_mmu函数, 启用MMU,设置页表、配置TLB(Translation Lookaside Buffer)等
      (3)循环重定向内核
      (4)跳转运行 __primary_switch, 传参 x0 = __PHYS_OFFSET
      (5)正常来说,此处不会返回,如果返回了,说明前面重定位有问题,则需要重新创建页表,重定位内核
      (6)禁用MMU
      (7)重新初始化idmap_pg_dir 和 init_pg_dir页表, 重新创建内核映射
      (8)执行TLB(转换后备缓冲器)全局无效化操作,清除所有的TLB条目,这样可以确保任何旧的页表项不会被使用
      (9)重新启用MMU
      (10)执行指令缓存全局无效化(Invalidate Instruction Cache All)操作,清除指令缓存,确保CPU不会从旧的映射中获取指令
      (11)重定位内核
      (12)再次跳转运行 __primary_switched
    1. 跳转运行 start_kernel 开始初始化内核
      (1) 栈初始化, 为当前的内核线程分配了一个栈空间
      (2) 保存线程信息, 记录线程的初始状态
      (3) 指针认证初始化, 目的是为了增强系统的安全性,通过初始化指针认证密钥来保护重要数据免受篡改
      (4) 设置中断向量表地址
      (5) 保存寄存器和设置帧指针, 便于后续的函数调用能够正确恢复上下文
      (6) 影子调用栈初始化, 作用是初始化一个额外的栈,用于增加对缓冲区溢出攻击的防御能力
      (7) 保存FDT指针设备树的信息
      (8) 计算并保存内核虚拟/物理地址偏移量, 有助于内核在不同的内存布局中正确地定位自身
      (9) 清空未初始化的数据段(BSS段)
      (10) 初始化内核指针认证(KASAN), KASAN是一个内核指针认证机制,用于检测内核中的指针错误
      (11) 检查是否已经进行了基址随机化
      (12) 初始化内核地址空间布局随机化(KASLR),如果KASLR被启用且尚未随机化,则记录其偏移量,并返回到之前的函数, 如果KASLR未启用或者已经随机化,则直接清理栈空间并启动内核
      (13) 清理栈空间并跳转到启动内核, 运行C函数 start_kernel()
# 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


你可能感兴趣的:(鸿蒙OH-v5.0源码分析之,Uboot+Kernel,部分,harmonyos,linux,华为)