一、内核启动入口点
/** Kernel startup entry point.* ---------------------------** The requirements are:*   MMU = off, D-cache = off, I-cache = on or off,*   x0 = physical address to the FDT blob.* 这部分注释说明了内核启动入口点的要求和约束条件。* 要求包括:MMU(内存管理单元)关闭,数据缓存(D-cache)关闭,指令缓存(I-cache)可以开启或关闭,* 以及需要将物理地址传递给 FDT(平台设备树)blob 的寄存器 x0** Note that the callee-saved registers are used for storing variables* that are useful before the MMU is enabled. The allocations are described* in the entry routines.* 请注意,被调用方保存的寄存器用于存储启用 MMU 之前有用的变量。分配在条目例程中描述。*//* 标识内核启动入口点 */__HEAD/** DO NOT MODIFY. Image header expected by Linux boot-loaders.* 请勿修改。Linux 引导加载进程所需的映像标头。*/efi_signature_nop			// special NOP to identity as PE/COFF executable 特殊 NOP 标识为 PE/COFF 可执行文档b	primary_entry			// branch to kernel start, magic 跳转到内核启动代码/* 这部分代码是内核启动入口点后面的一条 .head 段,包含了用于 Linux 引导加载器期望的镜像头信息。其中包括偏移量、大小、标志等信息 */.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 幻数.long	.Lpe_header_offset		// Offset to the PE header. PE头的偏移量__EFI_PE_HEADER/* 这行代码标识接下来的代码是 EFI(扩展固件接口)PE(可执行文件)头部信息 */.section ".idmap.text","a"
二、primary_entry
	/* 这行代码标识接下来的代码是 EFI(扩展固件接口)PE(可执行文件)头部信息 */.section ".idmap.text","a"/** The following callee saved general purpose registers are used on the* primary lowlevel boot path:* 以下被调用者保存的通用寄存器用于主低级引导路径**  寄存器		 上下文					  目标*  Register   Scope                      Purpose*  x19        primary_entry() .. start_kernel()        whether we entered with the MMU on*  x20        primary_entry() .. __primary_switch()    CPU boot mode *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0*  x22        create_idmap() .. start_kernel()         ID map VA of the DT blob*  x23        primary_entry() .. start_kernel()        physical misalignment/KASLR offset*  x24        __primary_switch()                       linear map KASLR seed*  x25        primary_entry() .. start_kernel()        supported VA size*  x28        create_idmap()                           callee preserved temp register*/
SYM_CODE_START(primary_entry)bl	record_mmu_statebl	preserve_boot_argsbl	create_idmap/** If we entered with the MMU and caches on, clean the ID mapped part* of the primary boot code to the PoC so we can safely execute it with* the MMU off.* 如果我们在 MMU 和缓存打开的情况下输入,请清理主启动代码的 ID 映射部分到 PoC,* 以便我们可以在关闭 MMU 的情况下安全地执行它。*/cbz	x19, 0fadrp	x0, __idmap_text_startadr_l	x1, __idmap_text_endadr_l	x2, dcache_clean_pocblr	x2
0:	mov	x0, x19bl	init_kernel_el			// w0=cpu_boot_modemov	x20, x0/** The following calls CPU setup code, see arch/arm64/mm/proc.S for* details.* 下面调用 CPU 设置代码,请参阅 arch/arm64/mm/proc.S 了解详细信息。* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.* 在返回时,CPU将准备好MMU被打开,TCR将被设置。*/
#if VA_BITS > 48mrs_s	x0, SYS_ID_AA64MMFR2_EL1tst	x0, #0xf << ID_AA64MMFR2_EL1_VARange_SHIFTmov	x0, #VA_BITSmov	x25, #VA_BITS_MINcsel	x25, x25, x0, eqmov	x0, x25
#endifbl	__cpu_setup			// initialise processor 初始化处理器b	__primary_switch
SYM_CODE_END(primary_entry)
这段代码是主要的入口点函数 primary_entry,在启动过程中执行一系列操作后将控制权切换到内核。
以下是对每行代码的解释:
SYM_CODE_START(primary_entry)
这行代码表示这是一个本地符号,标识 primary_entry 函数的开始位置。
bl	record_mmu_state
bl	preserve_boot_args
bl	create_idmap
这三行代码调用了三个不同的函数。首先调用 record_mmu_state 函数来记录 MMU 的状态,然后调用 preserve_boot_args 函数来保留引导参数,最后调用 create_idmap 函数来创建 ID 映射(identity mapping)。
cbz	x19, 0f
adrp	x0, __idmap_text_start
adr_l	x1, __idmap_text_end
adr_l	x2, dcache_clean_poc
blr	x2
0:	mov	x0, x19
bl	init_kernel_el			// w0=cpu_boot_mode
mov	x20, x0
这几行代码根据 x19 寄存器的值进行条件分支。如果 x19 为零(等于零),则跳转到标号 0 处。在标号 0 处,会将 __idmap_text_start 和 __idmap_text_end 地址计算给寄存器 x0 和 x1,并将 dcache_clean_poc 地址计算给寄存器 x2,然后通过 blr 指令将控制权转移给 dcache_clean_poc 函数进行 ID 映射部分的清理操作。如果 x19 不为零,则继续执行下一行代码,将 x19 的值传递给 init_kernel_el 函数,并将函数的返回值存储在 x20 寄存器中。
#if VA_BITS > 48
mrs_s	x0, SYS_ID_AA64MMFR2_EL1
tst	x0, #0xf << ID_AA64MMFR2_EL1_VARange_SHIFT
mov	x0, #VA_BITS
mov	x25, #VA_BITS_MIN
csel	x25, x25, x0, eq
mov	x0, x25
#endif
bl	__cpu_setup			// initialise processor
b	__primary_switch
这部分代码是一个条件编译块,在条件编译宏 VA_BITS 大于 48 时才会被编译。它首先读取 SYS_ID_AA64MMFR2_EL1 寄存器的值到 x0 中,然后根据位偏移和掩码进行测试,并根据结果设置 x0 的值。接下来,调用 __cpu_setup 函数来初始化处理器。最后,通过无条件分支指令 b 转移到 __primary_switch 位置。
SYM_CODE_END(primary_entry)
这行代码表示 primary_entry 函数的结束位置。
通过这段代码,可以看出 primary_entry 函数在启动过程中调用了几个辅助函数,其中包括记录 MMU 状态、保留引导参数和创建 ID 映射。然后根据条件分支和处理器初始化,将控制权切换到内核的 __primary_switch 位置,最终完成内核的初始化和启动。
三、record_mmu_state
SYM_CODE_START_LOCAL(record_mmu_state)mrs	x19, CurrentELcmp	x19, #CurrentEL_EL2mrs	x19, sctlr_el1b.ne	0fmrs	x19, sctlr_el2
0:
CPU_LE( tbnz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
CPU_BE( tbz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)tst	x19, #SCTLR_ELx_C		// Z := (C == 0)and	x19, x19, #SCTLR_ELx_M		// isolate M bitcsel	x19, xzr, x19, eq		// clear x19 if Zret/** Set the correct endianness early so all memory accesses issued* before init_kernel_el() occur in the correct byte order. Note that* this means the MMU must be disabled, or the active ID map will end* up getting interpreted with the wrong byte order.* 尽早设置正确的字节序,以便在 init_kernel_el() 之前发出的所有内存访问都以正确的字节顺序发生。* 请注意,这意味着必须禁用 MMU,否则活动 ID 映射最终将以错误的字节顺序进行解释。*/
1:	eor	x19, x19, #SCTLR_ELx_EEbic	x19, x19, #SCTLR_ELx_Mb.ne	2fpre_disable_mmu_workaroundmsr	sctlr_el2, x19b	3f
2:	pre_disable_mmu_workaroundmsr	sctlr_el1, x19
3:	isbmov	x19, xzrret
SYM_CODE_END(record_mmu_state)
这段代码是一个本地符号 record_mmu_state,该函数的主要目的是记录 MMU(内存管理单元)的状态。下面是对每行代码的解释:
SYM_CODE_START_LOCAL(record_mmu_state)
这行代码表示这是一个本地符号,用于定义 record_mmu_state 函数的开始位置。
mrs x19, CurrentEL
cmp x19, #CurrentEL_EL2
这两行代码将当前异常级别的值读取到寄存器 x19 中,并与宏 CurrentEL_EL2 进行比较,以检查是否处于 EL2 异常级别。
mrs x19, sctlr_el1
b.ne 0f
mrs x19, sctlr_el2
0:
这几行代码根据上述比较结果,如果不在 EL2 异常级别,则将 sctlr_el1 寄存器的值读取到寄存器 x19 中;否则,跳转到标号 0 处并将 sctlr_el2 寄存器的值读取到寄存器 x19 中。
CPU_LE( tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f )
CPU_BE( tbz x19, #SCTLR_ELx_EE_SHIFT, 1f )
这两行代码根据 CPU 的大小端模式(little-endian或big-endian),分别进行条件分支。根据 SCTLR_ELx_EE 寄存器的值,如果满足条件,则跳转到标号 1 处。
tst x19, #SCTLR_ELx_C          // Z := (C == 0)
and x19, x19, #SCTLR_ELx_M     // isolate M bit
csel x19, xzr, x19, eq         // clear x19 if Z
ret
这几行代码进行位操作和条件选择,并最终返回结果。它们根据 SCTLR_ELx_C 寄存器的值执行相应的操作,并将结果存储在寄存器 x19 中,然后通过 ret 指令返回函数。
1: eor x19, x19, #SCTLR_ELx_EE
bic x19, x19, #SCTLR_ELx_M
b.ne 2f
pre_disable_mmu_workaround
msr sctlr_el2, x19
b 3f
2: pre_disable_mmu_workaround
msr sctlr_el1, x19
3: isb
mov x19, xzr
ret
这部分代码根据条件分支进行不同的操作。根据前面的条件比较结果,它们对寄存器 x19 进行位操作和函数调用,并最终返回结果。具体操作包括将 SCTLR_ELx_EE 寄存器的值与 SCTLR_ELx_M 寄存器的值进行异或和清除操作,然后根据条件选择将结果写入 sctlr_el2 或 sctlr_el1 寄存器。最后通过 isb 指令刷新指令流水线,并将寄存器 x19 设置为零,并通过 ret 指令返回函数。
SYM_CODE_END(record_mmu_state)
这行代码表示 record_mmu_state 函数的结束位置。
通过这段代码,可以看出该函数根据当前的异常级别和 CPU 的大小端模式,获取相应的寄存器值,并进行一系列位操作和条件分支来记录 MMU 状态。具体的操作和处理根据不同的条件和配置进行调整,以满足特定的需求和约束。
四、preserve_boot_args
/** Preserve the arguments passed by the bootloader in x0 .. x3*/
SYM_CODE_START_LOCAL(preserve_boot_args)mov	x21, x0				// x21=FDTadr_l	x0, boot_args			// record the contents ofstp	x21, x1, [x0]			// x0 .. x3 at kernel entrystp	x2, x3, [x0, #16]cbnz	x19, 0f				// skip cache invalidation if MMU is ondmb	sy				// needed before dc ivac with// MMU offadd	x1, x0, #0x20			// 4 x 8 bytesb	dcache_inval_poc		// tail call
0:	str_l   x19, mmu_enabled_at_boot, x0ret
SYM_CODE_END(preserve_boot_args)
这段代码是一个本地符号 preserve_boot_args,其主要目的是保留引导加载程序传递给内核的参数。下面是对每行代码的解释:
/** Preserve the arguments passed by the bootloader in x0 .. x3* 在 x0 .. x3 中保留引导加载进程传递的参数*/
此注释指出了该函数的目标,即保留由引导加载程序传递给内核的参数(保存在 x0 到 x3 寄存器中)。
SYM_CODE_START_LOCAL(preserve_boot_args)
这行代码表示这是一个本地符号,用于定义 preserve_boot_args 函数的开始位置。
mov x21, x0              // x21=FDT
将 x0 的值(引导加载程序传递的第一个参数)复制到寄存器 x21 中,以便进一步处理。
adr_l x0, boot_args       // record the contents of
stp x21, x1, [x0]        // x0 .. x3 at kernel entry
stp x2, x3, [x0, #16]
这几行代码使用 adr_l 指令将 boot_args 地址加载到寄存器 x0 中,并使用 stp 指令将 x21、x1、x2 和 x3 的值按顺序保存到 boot_args 地址所指向的内存中。
cbnz x19, 0f             // skip cache invalidation if MMU is on
dmb sy                  // needed before dc ivac with// MMU off
根据 x19 的值进行条件分支。如果 x19 不为零,则跳转到标号 0 处。在标号 0 处,执行 dmb sy 指令,该指令用于在 MMU 关闭时,在执行 dc ivac 操作之前保证数据的内存一致性。
add x1, x0, #0x20       // 4 x 8 bytes
b dcache_inval_poc      // tail call
计算出新的地址(x0 + 0x20)并保存到寄存器 x1 中,然后使用尾调用方式跳转到 dcache_inval_poc 函数,该函数用于清除缓存中的数据以确保数据的一致性。
0: str_l x19, mmu_enabled_at_boot, x0
ret
在标号 0 处,将 x19 的值保存到内存中的 mmu_enabled_at_boot 地址处,并通过 ret 指令返回函数。
SYM_CODE_END(preserve_boot_args)
这行代码表示 preserve_boot_args 函数的结束位置。
通过这段代码,可以看出该函数的主要作用是保留由引导加载程序传递给内核的参数,并记录这些参数的值。此外,根据 MMU 的状态,还包括对缓存进行清理和一致性操作,以确保数据的正确性和可靠性。
五、create_idmap
SYM_FUNC_START_LOCAL(create_idmap)mov	x28, lr/** The ID map carries a 1:1 mapping of the physical address range* covered by the loaded image, which could be anywhere in DRAM. This* means that the required size of the VA (== PA) space is decided at* boot time, and could be more than the configured size of the VA* space for ordinary kernel and user space mappings.* ID 映射带有加载映像所覆盖的物理地址范围的 1:1 映射,该映射可能位于 DRAM 中的任何位置。* 这意味着所需的 VA (== PA) 空间大小是在引导时确定的,并且可能大于普通内核和用户空间映射的 VA 空间的配置大小。** There are three cases to consider here:* 这里有三种情况需要考虑:* - 39 <= VA_BITS < 48, and the ID map needs up to 48 VA bits to cover*   the placement of the image. In this case, we configure one extra*   level of translation on the fly for the ID map only. (This case*   also covers 42-bit VA/52-bit PA on 64k pages).* - 39 = VA BITS 48, ID映射需要多达48个VA BITS来覆盖图像的位置。* 在本例中,我们仅为ID映射动态地配置了一个额外的转换级别。(本例还包括64k页上的42位va52位PA)。** - VA_BITS == 48, and the ID map needs more than 48 VA bits. This can*   only happen when using 64k pages, in which case we need to extend*   the root level table rather than add a level. Note that we can*   treat this case as 'always extended' as long as we take care not*   to program an unsupported T0SZ value into the TCR register.* - VA BITS == 48, ID映射需要大于48个VA位。这只有在使用64k页时才会发生,* 在这种情况下,我们需要扩展根级表,而不是添加一个级别。* 请注意,只要我们注意不将不支持的T0SZ值编程到TCR寄存器中,我们就可以将这种情况视为“始终扩展”。** - Combinations that would require two additional levels of*   translation are not supported, e.g., VA_BITS==36 on 16k pages, or*   VA_BITS==39/4k pages with 5-level paging, where the input address*   requires more than 47 or 48 bits, respectively.* - 不支持需要两个额外转换级别的组合,* 例如,16k 页上的 VA_BITS==36,或 5 级分页的 VA_BITS==39/4k 页面,其中输入地址分别需要超过 47 位或 48 位。*/
#if (VA_BITS < 48)
#define IDMAP_PGD_ORDER	(VA_BITS - PGDIR_SHIFT)
#define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)/** If VA_BITS < 48, we have to configure an additional table level.* First, we have to verify our assumption that the current value of* VA_BITS was chosen such that all translation levels are fully* utilised, and that lowering T0SZ will always result in an additional* translation level to be configured.* 如果VA_BITS < 48,我们必须配置一个额外的表级别。 * 首先,我们必须验证我们的假设,即选择VA_BITS的当前值,以便充分利用所有翻译级别,* 并且降低 T0SZ 将始终导致要配置额外的转换级别。*/
#if VA_BITS != EXTRA_SHIFT
#error "Mismatch between VA_BITS and page size/number of translation levels"
#endif
#else
#define IDMAP_PGD_ORDER	(PHYS_MASK_SHIFT - PGDIR_SHIFT)
#define EXTRA_SHIFT/** If VA_BITS == 48, we don't have to configure an additional* translation level, but the top-level table has more entries.* 如果VA BITS == 48,我们不需要配置额外的转换级别,但是顶级表有更多的条目。*/
#endifadrp	x0, init_idmap_pg_diradrp	x3, _textadrp	x6, _end + MAX_FDT_SIZE + SWAPPER_BLOCK_SIZEmov	x7, SWAPPER_RX_MMUFLAGSmap_memory x0, x1, x3, x6, x7, x3, IDMAP_PGD_ORDER, x10, x11, x12, x13, x14, EXTRA_SHIFT/* Remap the kernel page tables r/w in the ID map *//* 在ID映射中重新映射内核页表 */adrp	x1, _textadrp	x2, init_pg_diradrp	x3, init_pg_endbic	x4, x2, #SWAPPER_BLOCK_SIZE - 1mov	x5, SWAPPER_RW_MMUFLAGSmov	x6, #SWAPPER_BLOCK_SHIFTbl	remap_region/* Remap the FDT after the kernel image *//* 在内核映像之后重新映射FDT */adrp	x1, _textadrp	x22, _end + SWAPPER_BLOCK_SIZEbic	x2, x22, #SWAPPER_BLOCK_SIZE - 1bfi	x22, x21, #0, #SWAPPER_BLOCK_SHIFT		// remapped FDT address 重映射FDT地址add	x3, x2, #MAX_FDT_SIZE + SWAPPER_BLOCK_SIZEbic	x4, x21, #SWAPPER_BLOCK_SIZE - 1mov	x5, SWAPPER_RW_MMUFLAGSmov	x6, #SWAPPER_BLOCK_SHIFTbl	remap_region/** Since the page tables have been populated with non-cacheable* accesses (MMU disabled), invalidate those tables again to* remove any speculatively loaded cache lines.* 由于页表已填充了不可缓存的访问(已禁用 MMU),因此请再次使这些表失效以删除任何推测加载的缓存行*/cbnz	x19, 0f				// skip cache invalidation if MMU is on 如果MMU打开,则跳过缓存无效dmb	syadrp	x0, init_idmap_pg_diradrp	x1, init_idmap_pg_endbl	dcache_inval_poc
0:	ret	x28
SYM_FUNC_END(create_idmap)
这段代码是用于在Linux内核中创建ID映射表的函数create_idmap。以下对每行代码进行解释:
SYM_FUNC_START_LOCAL(create_idmap)mov	x28, lr
这两行代码定义了一个本地符号和保存链接寄存器lr(返回地址)到寄存器x28。
#if (VA_BITS < 48)
这行代码是一个条件编译指令,判断虚拟地址位数VA_BITS是否小于48。
#define IDMAP_PGD_ORDER	(VA_BITS - PGDIR_SHIFT)
#define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)
这两行代码根据条件编译的结果定义了宏,计算了ID映射表的页目录级别和额外位移。
adrp	x0, init_idmap_pg_dir
adrp	x3, _text
adrp	x6, _end + MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
mov	x7, SWAPPER_RX_MMUFLAGSmap_memory x0, x1, x3, x6, x7, x3, IDMAP_PGD_ORDER, x10, x11, x12, x13, x14, EXTRA_SHIFT
这几行代码使用adrp和mov指令加载一些变量的地址或值,并调用了map_memory函数,将这些地址按照一定的映射关系进行映射。
adrp	x1, _text
adrp	x2, init_pg_dir
adrp	x3, init_pg_end
bic	x4, x2, #SWAPPER_BLOCK_SIZE - 1
mov	x5, SWAPPER_RW_MMUFLAGS
mov	x6, #SWAPPER_BLOCK_SHIFT
bl	remap_region
这几行代码同样使用adrp和mov指令加载一些变量的地址或值,并调用了remap_region函数,重新映射内核页表。
adrp	x1, _text
adrp	x22, _end + SWAPPER_BLOCK_SIZE
bic	x2, x22, #SWAPPER_BLOCK_SIZE - 1
bfi	x22, x21, #0, #SWAPPER_BLOCK_SHIFT		// remapped FDT address
add	x3, x2, #MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
bic	x4, x21, #SWAPPER_BLOCK_SIZE - 1
mov	x5, SWAPPER_RW_MMUFLAGS
mov	x6, #SWAPPER_BLOCK_SHIFT
bl	remap_region
这几行代码同样使用adrp和mov指令加载一些变量的地址或值,并调用了remap_region函数,重新映射文件描述符表(FDT)。
cbnz	x19, 0f				// skip cache invalidation if MMU is on
dmb	syadrp	x0, init_idmap_pg_dir
adrp	x1, init_idmap_pg_end
bl	dcache_inval_poc
0:	ret	x28
SYM_FUNC_END(create_idmap)
这几行代码用于在 MMU 开启时执行缓存失效操作,在 MMU 关闭时直接跳过。然后,通过bl指令调用了dcache_inval_poc函数,进一步执行缓存失效操作。最后,通过ret指令返回之前保存的链接寄存器lr中的值。
综上所述,这段代码的主要功能是创建 ID 映射表,并对一些关键内存区域进行重新映射和缓存失效操作。
六、init_kernel_el
/** Starting from EL2 or EL1, configure the CPU to execute at the highest* reachable EL supported by the kernel in a chosen default state. If dropping* from EL2 to EL1, configure EL2 before configuring EL1.* 从EL2或EL1开始,将CPU配置为在所选的默认状态下以内核支持的最高可达EL执行。* 如果从EL2降为EL1,请先配置EL2,再配置EL1。** Since we cannot always rely on ERET synchronizing writes to sysregs (e.g. if* SCTLR_ELx.EOS is clear), we place an ISB prior to ERET.* 由于我们不能总是依赖 ERET 同步写入系统(例如,如果 SCTLR_ELx.EOS 是明确的),我们将 ISB 放在 ERET 之前。** Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in x0 if* booted in EL1 or EL2 respectively, with the top 32 bits containing* potential context flags. These flags are *not* stored in __boot_cpu_mode.* 如果分别在 EL1 或 EL2 中引导,则在 x0 中返回 BOOT_CPU_MODE_EL1 或 BOOT_CPU_MODE_EL2,* 前 32 位包含潜在的上下文标志。这些标志不存储在__boot_cpu_mode中。** x0: whether we are being called from the primary boot path with the MMU on* x0: 在MMU打开的情况下,我们是否从主引导路径被调用*/
SYM_FUNC_START(init_kernel_el)mrs	x1, CurrentELcmp	x1, #CurrentEL_EL2b.eq	init_el2SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)mov_q	x0, INIT_SCTLR_EL1_MMU_OFFpre_disable_mmu_workaroundmsr	sctlr_el1, x0isbmov_q	x0, INIT_PSTATE_EL1msr	spsr_el1, x0msr	elr_el1, lrmov	w0, #BOOT_CPU_MODE_EL1eretSYM_INNER_LABEL(init_el2, SYM_L_LOCAL)msr	elr_el2, lr// clean all HYP code to the PoC if we booted at EL2 with the MMU oncbz	x0, 0fadrp	x0, __hyp_idmap_text_startadr_l	x1, __hyp_text_endadr_l	x2, dcache_clean_pocblr	x2
0:mov_q	x0, HCR_HOST_NVHE_FLAGSmsr	hcr_el2, x0isbinit_el2_state/* Hypervisor stub */adr_l	x0, __hyp_stub_vectorsmsr	vbar_el2, x0isbmov_q	x1, INIT_SCTLR_EL1_MMU_OFF/** Fruity CPUs seem to have HCR_EL2.E2H set to RES1,* making it impossible to start in nVHE mode. Is that* compliant with the architecture? Absolutely not!*/mrs	x0, hcr_el2and	x0, x0, #HCR_E2Hcbz	x0, 1f/* Set a sane SCTLR_EL1, the VHE way */pre_disable_mmu_workaroundmsr_s	SYS_SCTLR_EL12, x1mov	x2, #BOOT_CPU_FLAG_E2Hb	2f1:pre_disable_mmu_workaroundmsr	sctlr_el1, x1mov	x2, xzr
2:mov	w0, #BOOT_CPU_MODE_EL2orr	x0, x0, x2eret
SYM_FUNC_END(init_kernel_el)
这段代码是用于初始化内核执行级别(EL)的函数init_kernel_el。以下对每行代码进行解释:
SYM_FUNC_START(init_kernel_el)mrs	x1, CurrentELcmp	x1, #CurrentEL_EL2b.eq	init_el2
这几行代码获取当前的执行级别(EL)到寄存器x1,并与常量CurrentEL_EL2比较。如果相等,则跳转到标签init_el2处理 EL2 的初始化。
SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)mov_q	x0, INIT_SCTLR_EL1_MMU_OFFpre_disable_mmu_workaroundmsr	sctlr_el1, x0isbmov_q	x0, INIT_PSTATE_EL1msr	spsr_el1, x0msr	elr_el1, lrmov	w0, #BOOT_CPU_MODE_EL1eret
这几行代码定义了一个内部标签init_el1,将寄存器 x0 设置为一个控制 EL1 配置的值,禁用 MMU 前的工作,使用msr指令将x0的值写入sctlr_el1系统寄存器,使用isb指令同步指令流水线,将寄存器x0设置为一个控制程序状态的值,使用msr指令将x0的值写入spsr_el1系统寄存器,使用msr指令将链接寄存器(返回地址)的值写入elr_el1系统寄存器,将寄存器w0设置为常量BOOT_CPU_MODE_EL1,使用eret指令从异常状态退出,并将处理器切换到 EL1 级别。
SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)msr	elr_el2, lr// clean all HYP code to the PoC if we booted at EL2 with the MMU oncbz	x0, 0fadrp	x0, __hyp_idmap_text_startadr_l	x1, __hyp_text_endadr_l	x2, dcache_clean_pocblr	x2
0:mov_q	x0, HCR_HOST_NVHE_FLAGSmsr	hcr_el2, x0isbinit_el2_state/* Hypervisor stub */adr_l	x0, __hyp_stub_vectorsmsr	vbar_el2, x0isbmov_q	x1, INIT_SCTLR_EL1_MMU_OFF/** Fruity CPUs seem to have HCR_EL2.E2H set to RES1,* making it impossible to start in nVHE mode. Is that* compliant with the architecture? Absolutely not!*/mrs	x0, hcr_el2and	x0, x0, #HCR_E2Hcbz	x0, 1f/* Set a sane SCTLR_EL1, the VHE way */pre_disable_mmu_workaroundmsr_s	SYS_SCTLR_EL12, x1mov	x2, #BOOT_CPU_FLAG_E2Hb	2f1:pre_disable_mmu_workaroundmsr	sctlr_el1, x1mov	x2, xzr
2:mov	w0, #BOOT_CPU_MODE_EL2orr	x0, x0, x2eret
SYM_FUNC_END(init_kernel_el)
这段代码定义了一个内部标签init_el2,使用msr指令将链接寄存器(返回地址)的值写入elr_el2系统寄存器。
然后,根据传入的参数判断是否为 EL2 级别,如果是,则执行一些清理操作并配置 EL2 的状态,将寄存器x0设置为常量HCR_HOST_NVHE_FLAGS的值,并使用msr指令将x0的值写入hcr_el2系统寄存器,使用isb指令同步指令流水线。接着,通过调用init_el2_state函数初始化 EL2 的状态,设置了 Hypervisor stub 的异常向量表,将寄存器x1设置为一个控制 EL1 配置的值。
接下来,代码检查了处理器的配置,如果发现处理器不支持在 VHE 模式下启动(具体通过HCR_EL2.E2H寄存器判断),则使用非 VHE 方式设置合理的EL1配置。
最后,使用pre_disable_mmu_workaround函数禁用 MMU 前的工作,将寄存器w0设置为常量BOOT_CPU_MODE_EL2,并使用orr指令与寄存器x2进行或操作,将标志位信息添加到返回值中。最后,使用eret指令从异常状态退出,并将处理器切换到 EL2 级别。
综上所述,这段代码的主要功能是根据当前的执行级别(EL)和参数设置内核执行的EL级别,并进行相应的初始化操作。
七、__cpu_setup
/**	__cpu_setup**	Initialise the processor for turning the MMU on.* 初始化处理器以启动MMU。** Input:*	x0 - actual number of VA bits (ignored unless VA_BITS > 48)* Output:*	Return in x0 the value of the SCTLR_EL1 register.*/.pushsection ".idmap.text", "a"
SYM_FUNC_START(__cpu_setup)tlbi	vmalle1				// Invalidate local TLBdsb	nshmov	x1, #3 << 20msr	cpacr_el1, x1			// Enable FP/ASIMDmov	x1, #1 << 12			// Reset mdscr_el1 and disablemsr	mdscr_el1, x1			// access to the DCC from EL0isb					// Unmask debug exceptions now,enable_dbg				// since this is per-cpureset_pmuserenr_el0 x1			// Disable PMU access from EL0reset_amuserenr_el0 x1			// Disable AMU access from EL0/** Default values for VMSA control registers. These will be adjusted* below depending on detected CPU features.* VMSA控制寄存器的默认值。这些将根据检测到的CPU特性在下面进行调整。*/mair	.req	x17tcr	.req	x16mov_q	mair, MAIR_EL1_SETmov_q	tcr, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \TCR_TBI0 | TCR_A1 | TCR_KASAN_SW_FLAGS | TCR_MTE_FLAGStcr_clear_errata_bits tcr, x9, x5#ifdef CONFIG_ARM64_VA_BITS_52sub		x9, xzr, x0add		x9, x9, #64tcr_set_t1sz	tcr, x9
#elseidmap_get_t0sz	x9
#endiftcr_set_t0sz	tcr, x9/** Set the IPS bits in TCR_EL1.* 设置TCR EL1的IPS位。*/tcr_compute_pa_size tcr, #TCR_IPS_SHIFT, x5, x6
#ifdef CONFIG_ARM64_HW_AFDBM/** Enable hardware update of the Access Flags bit.* Hardware dirty bit management is enabled later,* via capabilities.* 启用Access Flags位的硬件更新。硬件脏位管理稍后通过功能启用。*/mrs	x9, ID_AA64MMFR1_EL1and	x9, x9, #0xfcbz	x9, 1forr	tcr, tcr, #TCR_HA		// hardware Access flag update 硬件访问标志更新
1:
#endif	/* CONFIG_ARM64_HW_AFDBM */msr	mair_el1, mairmsr	tcr_el1, tcr/** Prepare SCTLR*/mov_q	x0, INIT_SCTLR_EL1_MMU_ONret					// return to head.S.unreq	mair.unreq	tcr
SYM_FUNC_END(__cpu_setup)
这段代码是一个名为__cpu_setup的函数,用于初始化处理器以开启MMU功能。以下对每行代码进行解释:
.pushsection ".idmap.text", "a"
SYM_FUNC_START(__cpu_setup)
这两行代码将函数放置在.idmap.text节中,并开始__cpu_setup函数。
tlbi	vmalle1				// Invalidate local TLB
dsb	nsh
这两行代码执行TLBI指令来使本地TLB无效,并使用DSB指令确保操作执行顺序正确。
mov	x1, #3 << 20
msr	cpacr_el1, x1			// Enable FP/ASIMD
这两行代码将寄存器x1设置为控制协处理器访问权限的值,并使用msr指令将其写入cpacr_el1系统寄存器,以启用浮点和SIMD指令功能。
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
这些代码将寄存器x1设置为控制调试器及其相关功能的mdscr_el1寄存器的值,并使用msr指令将其写入mdscr_el1系统寄存器以重置相关配置。然后,使用isb指令同步指令流水线,通过enable_dbg函数启用调试异常。
reset_pmuserenr_el0 x1			// Disable PMU access from EL0
reset_amuserenr_el0 x1			// Disable AMU access from EL0
这两行代码使用自定义的宏函数禁用来自EL0级别的性能计数器和监视器的访问。
mair	.req	x17
tcr	.req	x16
mov_q	mair, MAIR_EL1_SET
mov_q	tcr, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \TCR_TBI0 | TCR_A1 | TCR_KASAN_SW_FLAGS | TCR_MTE_FLAGS
这几行代码定义了一些寄存器和常量,其中包括mair、tcr以及一系列用于构建tcr_el1寄存器值的掩码常量。
tcr_clear_errata_bits tcr, x9, x5
这行代码使用一个自定义的宏函数清除TCR_EL1寄存器中已知的硬件错误的位。
#ifdef CONFIG_ARM64_VA_BITS_52
sub		x9, xzr, x0
add		x9, x9, #64
tcr_set_t1sz	tcr, x9
#else
idmap_get_t0sz	x9
#endif
tcr_set_t0sz	tcr, x9
这些代码根据配置选项判断是否使用52位的虚拟地址空间。如果是,则将寄存器x9设置为一个控制TCR_EL1寄存器中T1SZ字段的值;否则,调用自定义的函数获取t0sz的值,并将其写入tcr_el1寄存器的相应字段(T0SZ)。
tcr_compute_pa_size tcr, #TCR_IPS_SHIFT, x5, x6
#ifdef CONFIG_ARM64_HW_AFDBM
mrs	x9, ID_AA64MMFR1_EL1
and	x9, x9, #0xf
cbz	x9, 1f
orr	tcr, tcr, #TCR_HA		// hardware Access flag update
1:
#endif	/* CONFIG_ARM64_HW_AFDBM */
msr	mair_el1, mair
msr	tcr_el1, tcr
这些代码使用自定义的宏函数计算物理地址大小,并将计算结果写入tcr_el1寄存器的相应字段(IPS)。
 如果启用了ARM64硬件更新访问标志位(AFDBM)功能,这部分代码会读取ID_AA64MMFR1_EL1系统寄存器的值,然后根据配置选项进行处理,并将得到的掩码值与tcr寄存器进行逻辑或操作,以使硬件能够更新Access Flags位。最后,使用msr指令将mair和tcr的值分别写入mair_el1和tcr_el1系统寄存器。
mov_q	x0, INIT_SCTLR_EL1_MMU_ON
ret					// return to head.S.unreq	mair
.unreq	tcr
SYM_FUNC_END(__cpu_setup)
这些代码将寄存器x0设置为一个常量值INIT_SCTLR_EL1_MMU_ON,该值将作为返回值存储在x0寄存器中,并通过ret指令返回到汇编语言代码的起点。
总结而言,这段代码是用于初始化处理器并开启MMU功能的函数。它包括**TLB的使能**、协处理器权限的设置、调试器相关功能的配置、性能计数器和监视器的禁用、VMSA控制寄存器的设置、虚拟地址空间大小的计算以及最终的SCTLR_EL1寄存器的初始化。
八、__primary_switch
SYM_FUNC_START_LOCAL(__primary_switch)adrp	x1, reserved_pg_diradrp	x2, init_idmap_pg_dirbl	__enable_mmu
#ifdef CONFIG_RELOCATABLEadrp	x23, KERNEL_STARTand	x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASEmov	x0, x22adrp	x1, init_pg_endmov	sp, x1mov	x29, xzrbl	__pi_kaslr_early_initand	x24, x0, #SZ_2M - 1		// capture memstart offset seed 捕获memstart偏移种子bic	x0, x0, #SZ_2M - 1orr	x23, x23, x0			// record kernel offset 记录内核偏移量
#endif
#endifbl	clear_page_tablesbl	create_kernel_mappingadrp	x1, init_pg_dirload_ttbr1 x1, x1, x2
#ifdef CONFIG_RELOCATABLEbl	__relocate_kernel
#endifldr	x8, =__primary_switchedadrp	x0, KERNEL_START		// __pa(KERNEL_START)br	x8
SYM_FUNC_END(__primary_switch)
这段代码是一个名为__primary_switch的本地函数(SYM_FUNC_START_LOCAL表示本地函数),以下对每行代码进行解释:
SYM_FUNC_START_LOCAL(__primary_switch)
这行代码开始了__primary_switch函数。
adrp	x1, reserved_pg_dir
adrp	x2, init_idmap_pg_dir
bl	__enable_mmu
这几行代码使用adrp指令将reserved_pg_dir和init_idmap_pg_dir的地址分别加载到寄存器x1和x2中,然后调用__enable_mmu函数启用MMU功能。
#ifdef CONFIG_RELOCATABLEadrp	x23, KERNEL_STARTand	x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASEmov	x0, x22adrp	x1, init_pg_endmov	sp, x1mov	x29, xzrbl	__pi_kaslr_early_initand	x24, x0, #SZ_2M - 1		// capture memstart offset seedbic	x0, x0, #SZ_2M - 1orr	x23, x23, x0			// record kernel offset
#endif
#endif
这部分代码是根据配置选项进行条件编译的。如果启用了可重定位内核(CONFIG_RELOCATABLE),则会执行相应的操作。首先,使用adrp指令将KERNEL_START的地址加载到寄存器x23中,并使用and指令和掩码MIN_KIMG_ALIGN - 1对其进行位与运算。如果启用了随机基址(CONFIG_RANDOMIZE_BASE),则将x22的值移动到x0寄存器,并将init_pg_end的地址加载到x1寄存器中。然后,将栈指针sp设置为x1寄存器的值,将x29寄存器(帧指针)清零,并调用__pi_kaslr_early_init函数执行早期地址空间布局随机化(KASLR)的初始化操作。接下来,使用and指令和掩码SZ_2M - 1对x0进行位与运算,并将结果保存在x24寄存器中,以记录起始内存偏移种子。再次使用bic指令将x0与掩码SZ_2M - 1进行位清除操作。最后,使用orr指令将x0与x23进行位或运算,将内核偏移量记录在x23寄存器中。
bl	clear_page_tables
bl	create_kernel_mapping
这两行代码分别调用clear_page_tables和create_kernel_mapping函数。clear_page_tables函数用于清除页表,而create_kernel_mapping函数用于创建内核映射。
adrp	x1, init_pg_dir
load_ttbr1 x1, x1, x2
这几行代码使用adrp指令将init_pg_dir的地址加载到寄存器x1中,然后调用load_ttbr1宏函数,将x1、x1和x2作为参数,用于加载TTBR1(Translation Table Base Register 1)寄存器。
#ifdef CONFIG_RELOCATABLEbl	__relocate_kernel
#endif
这部分代码也是根据配置选项进行条件编译的。如果启用了可重定位内核(CONFIG_RELOCATABLE),则会调用__relocate_kernel函数,该函数用于重新定位内核。
ldr	x8, =__primary_switched
adrp	x0, KERNEL_START		// __pa(KERNEL_START)
br	x8
这几行代码使用ldr指令将__primary_switched的地址加载到寄存器x8中,然后使用adrp指令将KERNEL_START的地址加载到寄存器x0中(此处的__pa(KERNEL_START)表示获取KERNEL_START符号的物理地址)。最后,使用br指令跳转到x8寄存器的值所指示的地址。
总结而言,这段代码是一个处理器切换函数__primary_switch,其中包括启用MMU功能、初始化内核地址空间布局随机化、清除页表、创建内核映射、加载TTBR1寄存器以及可选的重新定位内核等操作。最后,通过跳转到__primary_switched函数来完成处理器切换。
1、__primary_switched
/** The following fragment of code is executed with the MMU enabled.* 下面的代码片段在启用MMU的情况下执行。**   x0 = __pa(KERNEL_START)*/
SYM_FUNC_START_LOCAL(__primary_switched)adr_l	x4, init_taskinit_cpu_task x4, x5, x6adr_l	x8, vectors			// load VBAR_EL1 with virtualmsr	vbar_el1, x8			// vector table addressisbstp	x29, x30, [sp, #-16]!mov	x29, spstr_l	x21, __fdt_pointer, x5		// Save FDT pointerldr_l	x4, kimage_vaddr		// Save the offset betweensub	x4, x4, x0			// the kernel virtual andstr_l	x4, kimage_voffset, x5		// physical mappingsmov	x0, x20bl	set_cpu_boot_mode_flag// Clear BSSadr_l	x0, __bss_startmov	x1, xzradr_l	x2, __bss_stopsub	x2, x2, x0bl	__pi_memsetdsb	ishst				// Make zero page visible to PTW#if VA_BITS > 48adr_l	x8, vabits_actual		// Set this early so KASAN early initstr	x25, [x8]			// ... observes the correct valuedc	civac, x8			// Make visible to booting secondaries
#endif#ifdef CONFIG_RANDOMIZE_BASEadrp	x5, memstart_offset_seed	// Save KASLR linear map seedstrh	w24, [x5, :lo12:memstart_offset_seed]
#endif
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)bl	kasan_early_init
#endifmov	x0, x21				// pass FDT address in x0bl	early_fdt_map			// Try mapping the FDT earlymov	x0, x20				// pass the full boot statusbl	init_feature_override		// Parse cpu feature overrides
#ifdef CONFIG_UNWIND_PATCH_PAC_INTO_SCSbl	scs_patch_vmlinux
#endifmov	x0, x20bl	finalise_el2			// Prefer VHE if possibleldp	x29, x30, [sp], #16bl	start_kernelASM_BUG()
SYM_FUNC_END(__primary_switched)
这段代码是在启用MMU的情况下执行的。以下对每行代码进行解释:
SYM_FUNC_START_LOCAL(__primary_switched)
这行代码开始了名为__primary_switched的本地函数。
adr_l	x4, init_task
init_cpu_task x4, x5, x6
这两行代码使用adr_l指令将init_task的地址加载到寄存器x4中,然后调用init_cpu_task宏将x4、x5和x6作为参数初始化CPU任务。
adr_l	x8, vectors
msr	vbar_el1, x8
isb
这几行代码使用adr_l指令将vectors的地址加载到寄存器x8中,然后使用msr指令将x8的值写入VBAR_EL1寄存器,即异常向量表地址寄存器。最后,使用isb指令刷新指令流水线。
stp	x29, x30, [sp, #-16]!
mov	x29, sp
这几行代码将帧指针寄存器x29和链接寄存器x30保存在栈上,然后将栈指针寄存器sp的值赋给x29,建立新的帧。
str_l	x21, __fdt_pointer, x5
这行代码使用str_l指令将寄存器x21中的值存储到__fdt_pointer变量的地址中。
ldr_l	x4, kimage_vaddr
sub	x4, x4, x0
str_l	x4, kimage_voffset, x5
这几行代码使用ldr_l指令将kimage_vaddr的值加载到寄存器x4中,然后使用sub指令计算内核虚拟和物理地址之间的偏移量,并将结果存储在kimage_voffset变量中。
mov	x0, x20
bl	set_cpu_boot_mode_flag
这两行代码将寄存器x20的值移动到x0寄存器中,并调用set_cpu_boot_mode_flag函数设置CPU引导模式标志。
adr_l	x0, __bss_start
mov	x1, xzr
adr_l	x2, __bss_stop
sub	x2, x2, x0
bl	__pi_memset
dsb	ishst
这几行代码使用adr_l指令将__bss_start和__bss_stop的地址加载到寄存器x0和x2中,然后使用sub指令计算BSS段的大小,并将结果存储在x2中。接着,调用__pi_memset函数将BSS段清零,并使用dsb指令确保对BSS段的修改对Page Table Walkers(PTW)可见。
#if VA_BITS > 48adr_l	x8, vabits_actualstr	x25, [x8]dc	civac, x8
#endif
这部分代码是根据虚拟地址位数进行条件编译的。如果虚拟地址位数超过48位,则使用adr_l指令将vabits_actual的地址加载到寄存器x8中,然后将寄存器x25的值存储到x8所指示的地址中,并使用dc指令刷新数据缓存。
#ifdef CONFIG_RANDOMIZE_BASEadrp	x5, memstart_offset_seedstrh	w24, [x5, :lo12:memstart_offset_seed]
#endif
这部分代码是根据配置选项进行条件编译的。如果启用了随机基址(CONFIG_RANDOMIZE_BASE),则会使用adrp指令将memstart_offset_seed的地址加载到寄存器x5中,然后使用strh指令将w24的低12位存储到x5所指示的地址中。
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)bl	kasan_early_init
#endif
这部分代码也是根据配置选项进行条件编译的。如果启用了KASAN(内核地址检测和调试工具)的通用模式(CONFIG_KASAN_GENERIC)或软件标记模式(CONFIG_KASAN_SW_TAGS),则会调用kasan_early_init函数执行早期的**KASAN初始化操作**。
mov	x0, x21
bl	early_fdt_map
这两行代码将寄存器x21的值移动到x0寄存器中,并调用early_fdt_map函数尝试在早期映射FDT(设备树)。
mov	x0, x20
bl	init_feature_override
这两行代码将寄存器x20的值移动到x0寄存器中,并调用init_feature_override函数解析CPU特性的覆盖设置。
#ifdef CONFIG_UNWIND_PATCH_PAC_INTO_SCSbl	scs_patch_vmlinux
#endif
这部分代码也是根据配置选项进行条件编译的。如果启用了将PAC修补应用到SCS(System Control Space)的功能(CONFIG_UNWIND_PATCH_PAC_INTO_SCS),则会调用scs_patch_vmlinux函数进行相应的操作。
mov	x0, x20
bl	finalise_el2
这两行代码将寄存器x20的值移动到x0寄存器中,并调用finalise_el2函数,用于在可能的情况下首选VHE(Virtualization Host Extensions)。
ldp	x29, x30, [sp], #16
bl	start_kernel
这两行代码从栈上恢复帧指针寄存器x29和链接寄存器x30的值,然后调用start_kernel函数启动内核。
ASM_BUG()
SYM_FUNC_END(__primary_switched)
这两行代码使用ASM_BUG()宏来标记一个汇编错误,并结束__primary_switched函数的定义。
总结而言,这段代码是在启用MMU的情况下执行的处理器切换函数__primary_switched,其中包括初始化CPU任务、设置异常向量表地址、保存FDT指针、计算内核虚拟和物理地址偏移、清零BSS段、初始化KASAN、映射FDT、解析CPU特性覆盖设置等操作。最后,恢复寄存器并调用start_kernel函数启动内核。
九、总结
1、通过 __HEAD 将启动代码链接到文件开始位置。
2、调用 primary_entry ,进行初始化相关操作。
3、调用 __primary_switch - > __primary_switched 调用 start_kernel 函数。
4、start_kernel 函数进入 C 语言环境初始化内核。