基于aarch64分析kernel源码 三:启动代码分析

这篇具有很好参考价值的文章主要介绍了基于aarch64分析kernel源码 三:启动代码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、内核启动入口点

/*
 * 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_state
	bl	preserve_boot_args
	bl	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, 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

	/*
	 * 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 > 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
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 地址计算给寄存器 x0x1,并将 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, CurrentEL
	cmp	x19, #CurrentEL_EL2
	mrs	x19, sctlr_el1
	b.ne	0f
	mrs	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 bit
	csel	x19, xzr, x19, eq		// clear x19 if Z
	ret

	/*
	 * 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_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
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-endianbig-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_el2sctlr_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=FDT

	adr_l	x0, boot_args			// record the contents of
	stp	x21, x1, [x0]			// x0 .. x3 at kernel entry
	stp	x2, x3, [x0, #16]

	cbnz	x19, 0f				// skip cache invalidation if MMU is on
	dmb	sy				// needed before dc ivac with
						// MMU off

	add	x1, x0, #0x20			// 4 x 8 bytes
	b	dcache_inval_poc		// tail call
0:	str_l   x19, mmu_enabled_at_boot, x0
	ret
SYM_CODE_END(preserve_boot_args)

这段代码是一个本地符号 preserve_boot_args,其主要目的是保留引导加载程序传递给内核的参数。下面是对每行代码的解释:

/*
 * Preserve the arguments passed by the bootloader in x0 .. x3
 * 在 x0 .. x3 中保留引导加载进程传递的参数
 */

此注释指出了该函数的目标,即保留由引导加载程序传递给内核的参数(保存在 x0x3 寄存器中)。

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 指令将 x21x1x2x3 的值按顺序保存到 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,我们不需要配置额外的转换级别,但是顶级表有更多的条目。
	 */
#endif
	adrp	x0, init_idmap_pg_dir
	adrp	x3, _text
	adrp	x6, _end + MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
	mov	x7, SWAPPER_RX_MMUFLAGS

	map_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, _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

	/* Remap the FDT after the kernel image */
	/* 在内核映像之后重新映射FDT */
	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 重映射FDT地址
	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

	/*
	 * 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	sy

	adrp	x0, init_idmap_pg_dir
	adrp	x1, init_idmap_pg_end
	bl	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_MMUFLAGS

map_memory x0, x1, x3, x6, x7, x3, IDMAP_PGD_ORDER, x10, x11, x12, x13, x14, EXTRA_SHIFT

这几行代码使用adrpmov指令加载一些变量地址,并调用了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

这几行代码同样使用adrpmov指令加载一些变量地址,并调用了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

这几行代码同样使用adrpmov指令加载一些变量地址,并调用了remap_region函数重新映射文件描述符表(FDT)

cbnz	x19, 0f				// skip cache invalidation if MMU is on
dmb	sy

adrp	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, CurrentEL
	cmp	x1, #CurrentEL_EL2
	b.eq	init_el2

SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)
	mov_q	x0, INIT_SCTLR_EL1_MMU_OFF
	pre_disable_mmu_workaround
	msr	sctlr_el1, x0
	isb
	mov_q	x0, INIT_PSTATE_EL1
	msr	spsr_el1, x0
	msr	elr_el1, lr
	mov	w0, #BOOT_CPU_MODE_EL1
	eret

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 on
	cbz	x0, 0f
	adrp	x0, __hyp_idmap_text_start
	adr_l	x1, __hyp_text_end
	adr_l	x2, dcache_clean_poc
	blr	x2
0:
	mov_q	x0, HCR_HOST_NVHE_FLAGS
	msr	hcr_el2, x0
	isb

	init_el2_state

	/* Hypervisor stub */
	adr_l	x0, __hyp_stub_vectors
	msr	vbar_el2, x0
	isb

	mov_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_el2
	and	x0, x0, #HCR_E2H
	cbz	x0, 1f

	/* Set a sane SCTLR_EL1, the VHE way */
	pre_disable_mmu_workaround
	msr_s	SYS_SCTLR_EL12, x1
	mov	x2, #BOOT_CPU_FLAG_E2H
	b	2f

1:
	pre_disable_mmu_workaround
	msr	sctlr_el1, x1
	mov	x2, xzr
2:
	mov	w0, #BOOT_CPU_MODE_EL2
	orr	x0, x0, x2
	eret
SYM_FUNC_END(init_kernel_el)

这段代码是用于初始化内核执行级别(EL)的函数init_kernel_el。以下对每行代码进行解释:

SYM_FUNC_START(init_kernel_el)
	mrs	x1, CurrentEL
	cmp	x1, #CurrentEL_EL2
	b.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_OFF
	pre_disable_mmu_workaround
	msr	sctlr_el1, x0
	isb
	mov_q	x0, INIT_PSTATE_EL1
	msr	spsr_el1, x0
	msr	elr_el1, lr
	mov	w0, #BOOT_CPU_MODE_EL1
	eret

这几行代码定义了一个内部标签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 on
	cbz	x0, 0f
	adrp	x0, __hyp_idmap_text_start
	adr_l	x1, __hyp_text_end
	adr_l	x2, dcache_clean_poc
	blr	x2
0:
	mov_q	x0, HCR_HOST_NVHE_FLAGS
	msr	hcr_el2, x0
	isb

	init_el2_state

	/* Hypervisor stub */
	adr_l	x0, __hyp_stub_vectors
	msr	vbar_el2, x0
	isb

	mov_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_el2
	and	x0, x0, #HCR_E2H
	cbz	x0, 1f

	/* Set a sane SCTLR_EL1, the VHE way */
	pre_disable_mmu_workaround
	msr_s	SYS_SCTLR_EL12, x1
	mov	x2, #BOOT_CPU_FLAG_E2H
	b	2f

1:
	pre_disable_mmu_workaround
	msr	sctlr_el1, x1
	mov	x2, xzr
2:
	mov	w0, #BOOT_CPU_MODE_EL2
	orr	x0, x0, x2
	eret
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 TLB
	dsb	nsh

	mov	x1, #3 << 20
	msr	cpacr_el1, x1			// Enable FP/ASIMD
	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
	reset_pmuserenr_el0 x1			// Disable PMU access from EL0
	reset_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	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

	tcr_clear_errata_bits tcr, x9, x5

#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

	/*
	 * 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_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
	/*
	 * Prepare SCTLR
	 */
	mov_q	x0, INIT_SCTLR_EL1_MMU_ON
	ret					// 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

这几行代码定义了一些寄存器常量,其中包括mairtcr以及一系列用于构建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指令将mairtcr的值分别写入mair_el1tcr_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_dir
	adrp	x2, init_idmap_pg_dir
	bl	__enable_mmu
#ifdef CONFIG_RELOCATABLE
	adrp	x23, KERNEL_START
	and	x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASE
	mov	x0, x22
	adrp	x1, init_pg_end
	mov	sp, x1
	mov	x29, xzr
	bl	__pi_kaslr_early_init
	and	x24, x0, #SZ_2M - 1		// capture memstart offset seed 捕获memstart偏移种子
	bic	x0, x0, #SZ_2M - 1
	orr	x23, x23, x0			// record kernel offset 记录内核偏移量
#endif
#endif
	bl	clear_page_tables
	bl	create_kernel_mapping

	adrp	x1, init_pg_dir
	load_ttbr1 x1, x1, x2
#ifdef CONFIG_RELOCATABLE
	bl	__relocate_kernel
#endif
	ldr	x8, =__primary_switched
	adrp	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_dirinit_idmap_pg_dir地址分别加载到寄存器x1x2中,然后调用__enable_mmu函数启用MMU功能

#ifdef CONFIG_RELOCATABLE
	adrp	x23, KERNEL_START
	and	x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASE
	mov	x0, x22
	adrp	x1, init_pg_end
	mov	sp, x1
	mov	x29, xzr
	bl	__pi_kaslr_early_init
	and	x24, x0, #SZ_2M - 1		// capture memstart offset seed
	bic	x0, x0, #SZ_2M - 1
	orr	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 - 1x0进行位与运算,并将结果保存在x24寄存器中,以记录起始内存偏移种子。再次使用bic指令将x0与掩码SZ_2M - 1进行位清除操作。最后,使用orr指令将x0x23进行位或运算,将内核偏移量记录在x23寄存器中。

bl	clear_page_tables
bl	create_kernel_mapping

这两行代码分别调用clear_page_tablescreate_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宏函数,将x1x1x2作为参数,用于加载TTBR1Translation Table Base Register 1)寄存器。

#ifdef CONFIG_RELOCATABLE
	bl	__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_task
	init_cpu_task x4, x5, x6

	adr_l	x8, vectors			// load VBAR_EL1 with virtual
	msr	vbar_el1, x8			// vector table address
	isb

	stp	x29, x30, [sp, #-16]!
	mov	x29, sp

	str_l	x21, __fdt_pointer, x5		// Save FDT pointer

	ldr_l	x4, kimage_vaddr		// Save the offset between
	sub	x4, x4, x0			// the kernel virtual and
	str_l	x4, kimage_voffset, x5		// physical mappings

	mov	x0, x20
	bl	set_cpu_boot_mode_flag

	// Clear BSS
	adr_l	x0, __bss_start
	mov	x1, xzr
	adr_l	x2, __bss_stop
	sub	x2, x2, x0
	bl	__pi_memset
	dsb	ishst				// Make zero page visible to PTW

#if VA_BITS > 48
	adr_l	x8, vabits_actual		// Set this early so KASAN early init
	str	x25, [x8]			// ... observes the correct value
	dc	civac, x8			// Make visible to booting secondaries
#endif

#ifdef CONFIG_RANDOMIZE_BASE
	adrp	x5, memstart_offset_seed	// Save KASLR linear map seed
	strh	w24, [x5, :lo12:memstart_offset_seed]
#endif
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
	bl	kasan_early_init
#endif
	mov	x0, x21				// pass FDT address in x0
	bl	early_fdt_map			// Try mapping the FDT early
	mov	x0, x20				// pass the full boot status
	bl	init_feature_override		// Parse cpu feature overrides
#ifdef CONFIG_UNWIND_PATCH_PAC_INTO_SCS
	bl	scs_patch_vmlinux
#endif
	mov	x0, x20
	bl	finalise_el2			// Prefer VHE if possible
	ldp	x29, x30, [sp], #16
	bl	start_kernel
	ASM_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宏将x4x5x6作为参数初始化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的地址加载到寄存器x0x2中,然后使用sub指令计算BSS段的大小,并将结果存储在x2中。接着,调用__pi_memset函数将BSS段清零,并使用dsb指令确保对BSS段的修改对Page Table Walkers(PTW)可见。

#if VA_BITS > 48
	adr_l	x8, vabits_actual
	str	x25, [x8]
	dc	civac, x8
#endif

这部分代码是根据虚拟地址位数进行条件编译的。如果虚拟地址位数超过48位,则使用adr_l指令将vabits_actual的地址加载到寄存器x8中,然后将寄存器x25的值存储到x8所指示的地址中,并使用dc指令刷新数据缓存

#ifdef CONFIG_RANDOMIZE_BASE
	adrp	x5, memstart_offset_seed
	strh	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_SCS
	bl	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 语言环境初始化内核。文章来源地址https://www.toymoban.com/news/detail-618297.html

到了这里,关于基于aarch64分析kernel源码 三:启动代码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • pytorch-v2.0.1 cuda arm64 aarch64 torch 2.0.1+cu118 源码编译笔记

    Kylin Linux Advanced Server V10 (Tercel) aarch64安装NVIDIA-Linux-aarch64-520.61.05.run驱动Tesla T4 16G笔记_hkNaruto的博客-CSDN博客 Ubuntu 22.04 x86_64 源码编译 pytorch-v2.0.1 笔记_hkNaruto的博客-CSDN博客 Ubuntu 22.04 x86_64 源码编译 pytorch-v2.0.1 笔记【2】编译成功_hkNaruto的博客-CSDN博客 环境 安装依赖 编译pytho

    2024年02月09日
    浏览(35)
  • (三)基于arm64/aarch64架构的Debian Linux中mysql5.7+详细安装过程

    (一)移动端安卓手机改造成linux服务器Linux中安装软件踩坑历险记 (二)Debian Linux系统中安装oracle JDK1.8详细过程(arm64/aarch64架构下) 在上一篇文章中,主要分享了 改造后的安卓手机Linux系统中 JDK1.8的安装与环境变量配置等内容。本文就 基于arm/aarch64指令集架构的Linux系统中 mysql5

    2024年01月21日
    浏览(55)
  • pytorch-v2.0.1 cuda arm64 aarch64 torch 2.0.1+cu118 源码编译笔记【2】验证cuda安装 成功

    接上篇 pytorch-v2.0.1 cuda arm64 aarch64 torch 2.0.1+cu118 源码编译笔记_hkNaruto的博客-CSDN博客 由于采用/usr/local/bin/gcc编译,先设置LD_LIBRARY_PATH,再启动python3 import torch报错 # /usr/local/Python-3.10.12/bin/python3 Python 3.10.12 (main, Sep  4 2023, 10:01:29) [GCC 9.2.0] on linux Type \\\"help\\\", \\\"copyright\\\", \\\"credits\\\" or \\\"l

    2024年02月09日
    浏览(44)
  • Linux Kernel源码阅读: x86-64 系统调用实现细节(超详细)

    本文采用Linux 内核 v3.10 版本 本文不涉及调试、跟踪及异常处理的细节 一、系统调用简介 系统调用是用户空间程序与内核交互的主要机制。系统调用与普通函数调用不同,因为它调用的是内核里的代码。使用系统调用时,需要特殊指令以使处理器权限转换到内核态。另外,被

    2024年02月06日
    浏览(42)
  • nanopc t6 的ubuntu22.04用docker 安装openwrt 23.05.2,基于arm64 (aarch64)架构

    我是用nanopc t6的官方系统,rk3588-XYZ-ubuntu-jammy-x11-desktop-arm64-YYYYMMDD.img.gz,可以从官方的百度网盘下载,根据官方教材刷入nanopc t6中即可。官方网址如下: NanoPC-T6/zh - FriendlyELEC WiKi 毕竟刚刷的系统,先设置root密码 默认的账户和密码都是pi,我是自己添加了sudo用户,删除了pi用

    2024年01月17日
    浏览(68)
  • ARMv8-AArch64 的异常处理模型详解之异常处理详解(同步异常和异步异常的分析和处理)

    在文章ARMv8-AArch64 的异常处理模型详解之异常类型 Exception types中提到过,同步异常是处理器在执行指令时产生的异常,是一种精确的,可以具体定位到是哪条指令导致异常的产生。下面笔者将介绍三个用于定位并分析同步异常产生的寄存器。 在文章ARMv8-AArch64 的异常处理模型

    2024年03月27日
    浏览(46)
  • 【Linux内核解析-linux-5.14.10-内核源码注释】内核启动kernel_init解释

    static int __ref kernel_init(void *unused) : 声明一个静态整型函数 kernel_init() ,该函数不会被其他文件访问,使用 __ref 标记表示该函数是可重定位的,并且该函数不需要任何参数。 wait_for_completion(kthreadd_done); : 等待 kthreadd 线程完成初始化, wait_for_completion() 函数会阻塞当前进程,直到

    2024年02月02日
    浏览(70)
  • arm64和aarch64之间的区别

    直接给出结论:arm64已经与aarch64合并,因为aarch64和arm64指的是同一件事。 AArch64是ARMv8 架构的一种执行状态。 为了更广泛地向企业领域推进,需要引入 64 位构架。同时也需要在 ARMv8 架构中引入新的 AArch64 执行状态。AArch64 不是一个单纯的 32 位 ARM 构架扩展,而是 ARMv8 内全新

    2024年02月11日
    浏览(33)
  • 【SA8295P 源码分析】20 - GVM Android Kernel NFS Support 配置

    【源码分析】 因为一些原因,本文需要移除, 对于已经购买的兄弟,不用担心,不是跑路, 我会继续持续提供技术支持, 有什么模块想学习的,或者有什么问题有疑问的, 请私聊我,我们 +VX 沟通技术问题,一起学习,一起进步 接下来,我一一私聊已经购买的兄弟添加V

    2024年02月12日
    浏览(45)
  • Swupdate的aarch64交叉编译

    上一篇博客我们简单测试了swupdate的基础功能,当时使用的是buildroot进行编译的,依赖关心有buildroot进行处理了。今天我自己手动编译一些swupdate。下面记录了编译swupdate的过程。 基本过程就是下载源码,配置选项,然后进行编译。 swupdate是在github上下载的,下载地址为: l

    2024年02月12日
    浏览(70)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包