← Kernel Series DUJINLABS.COM

Linux 6.18.37 LTS · arm64 · arch/arm64/kernel/head.S

Linux 6.18.37 LTS arm64 head.S 라인 바이 라인 읽기

bootloader가 넘긴 FDT 주소에서 시작해서 idmap 생성, MMU enable, EL1/EL2 정리, secondary CPU entry, start_kernel 진입까지를 원본 코드와 같이 끊어서 읽습니다.

큰 지도

이 파일은 아직 scheduler, allocator, IRQ core가 없는 시점의 코드입니다. 그래서 일반 C 함수처럼 읽으면 안 됩니다. 먼저 레지스터가 어떤 상태를 운반하는지, 어느 범위가 idmap으로 실행되어야 하는지, MMU를 켠 뒤 어떤 지점부터 virtual address 전제가 생기는지를 표시해야 합니다.

bootloader ABIx0=FDT, Image header, EFI/PE header
identity mappingMMU 전환 중에도 실행 가능한 .idmap.text
handoff__primary_switched에서 start_kernel로 진입

원본 코드 좌표

원본 코드:arch/arm64/kernel/head.S · Linux 6.18.37 LTS · GPL-2.0-only

아래 글은 로컬에 저장한 원문을 기준으로 line number를 붙였습니다. 외부 링크는 stable tree mirror의 같은 tag를 가리킵니다.

라인 바이 라인

1. 라이선스, include, EFI header 연결

원본 코드: arch/arm64/kernel/head.S, lines 1-39
   1: /* SPDX-License-Identifier: GPL-2.0-only */
   2: /*
   3:  * Low-level CPU initialisation
   4:  * Based on arch/arm/kernel/head.S
   5:  *
   6:  * Copyright (C) 1994-2002 Russell King
   7:  * Copyright (C) 2003-2012 ARM Ltd.
   8:  * Authors:	Catalin Marinas <[email protected]>
   9:  *		Will Deacon <[email protected]>
  10:  */
  11: 
  12: #include <linux/linkage.h>
  13: #include <linux/init.h>
  14: #include <linux/pgtable.h>
  15: 
  16: #include <asm/asm_pointer_auth.h>
  17: #include <asm/assembler.h>
  18: #include <asm/boot.h>
  19: #include <asm/bug.h>
  20: #include <asm/ptrace.h>
  21: #include <asm/asm-offsets.h>
  22: #include <asm/cache.h>
  23: #include <asm/cputype.h>
  24: #include <asm/el2_setup.h>
  25: #include <asm/elf.h>
  26: #include <asm/image.h>
  27: #include <asm/kernel-pgtable.h>
  28: #include <asm/kvm_arm.h>
  29: #include <asm/memory.h>
  30: #include <asm/pgtable-hwdef.h>
  31: #include <asm/page.h>
  32: #include <asm/scs.h>
  33: #include <asm/smp.h>
  34: #include <asm/sysreg.h>
  35: #include <asm/stacktrace/frame.h>
  36: #include <asm/thread_info.h>
  37: #include <asm/virt.h>
  38: 
  39: #include "efi-header.S"

설명: 첫 39줄은 실행 코드는 아니지만 이 파일을 읽는 좌표계입니다. 어떤 매크로가 어디서 오는지, EFI/PE 헤더가 왜 head.S에 섞이는지, arm64 부팅 코드가 assembler helper에 얼마나 기대는지 먼저 잡습니다.

  • SPDX는 이 파일의 라이선스 경계입니다. 이 페이지에 원문을 싣는 이유도 이 표시와 GPL-2.0-only 조건을 같이 보여 주기 위해서입니다.
  • asm/image.h, asm/boot.h, asm/kernel-pgtable.h는 Image header와 초기 page table 계약을 설명하는 핵심 include입니다.
  • efi-header.S는 UEFI 부팅 경로를 위해 PE/COFF 헤더를 조립 단계에서 붙입니다. head.S가 단순 reset vector만이 아니라 이미지 포맷의 첫 면이기도 하다는 뜻입니다.
1. 라이선스, include, EFI header 연결 흐름
source metadataSPDX, authors
assembler helperslinkage, sysreg, page table macros
EFI bridgeefi-header.S

2. Image header와 bootloader ABI

원본 코드: arch/arm64/kernel/head.S, lines 41-72
  41: #if (PAGE_OFFSET & 0x1fffff) != 0
  42: #error PAGE_OFFSET must be at least 2MB aligned
  43: #endif
  44: 
  45: /*
  46:  * Kernel startup entry point.
  47:  * ---------------------------
  48:  *
  49:  * The requirements are:
  50:  *   MMU = off, D-cache = off, I-cache = on or off,
  51:  *   x0 = physical address to the FDT blob.
  52:  *
  53:  * Note that the callee-saved registers are used for storing variables
  54:  * that are useful before the MMU is enabled. The allocations are described
  55:  * in the entry routines.
  56:  */
  57: 	__HEAD
  58: 	/*
  59: 	 * DO NOT MODIFY. Image header expected by Linux boot-loaders.
  60: 	 */
  61: 	efi_signature_nop			// special NOP to identity as PE/COFF executable
  62: 	b	primary_entry			// branch to kernel start, magic
  63: 	.quad	0				// Image load offset from start of RAM, little-endian
  64: 	le64sym	_kernel_size_le			// Effective size of kernel image, little-endian
  65: 	le64sym	_kernel_flags_le		// Informative flags, little-endian
  66: 	.quad	0				// reserved
  67: 	.quad	0				// reserved
  68: 	.quad	0				// reserved
  69: 	.ascii	ARM64_IMAGE_MAGIC		// Magic number
  70: 	.long	.Lpe_header_offset		// Offset to the PE header.
  71: 
  72: 	__EFI_PE_HEADER

설명: 이 부분은 bootloader가 보는 첫 계약입니다. ARM64 Image header는 branch 명령 하나와 크기, flags, magic, PE header offset을 고정된 위치에 둡니다. 여기서 한 필드라도 밀리면 커널 C 코드까지 가지 못합니다.

  • PAGE_OFFSET 2MB 정렬 검사는 block mapping 전제를 보호합니다. early mapping에서 2MB granule을 깔끔하게 쓰기 위한 compile-time 방어선입니다.
  • efi_signature_nop 다음 b primary_entry가 실제 시작 분기입니다. 동시에 PE/COFF 식별을 위한 자리이므로 함부로 바꾸면 UEFI path가 깨질 수 있습니다.
  • ARM64_IMAGE_MAGIC과 PE header offset은 부트로더가 이 파일을 Linux arm64 Image로 판정하는 근거입니다.
2. Image header와 bootloader ABI 흐름
bootloaderImage load
fixed headersize, flags, magic
primary_entryreal execution starts

3. primary_entry: idmap을 만들고 MMU 전환 직전까지 간다

원본 코드: arch/arm64/kernel/head.S, lines 76-131
  76: 	/*
  77: 	 * The following callee saved general purpose registers are used on the
  78: 	 * primary lowlevel boot path:
  79: 	 *
  80: 	 *  Register   Scope                      Purpose
  81: 	 *  x19        primary_entry() .. start_kernel()        whether we entered with the MMU on
  82: 	 *  x20        primary_entry() .. __primary_switch()    CPU boot mode
  83: 	 *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0
  84: 	 */
  85: SYM_CODE_START(primary_entry)
  86: 	bl	record_mmu_state
  87: 	bl	preserve_boot_args
  88: 
  89: 	adrp	x1, early_init_stack
  90: 	mov	sp, x1
  91: 	mov	x29, xzr
  92: 	adrp	x0, __pi_init_idmap_pg_dir
  93: 	mov	x1, xzr
  94: 	bl	__pi_create_init_idmap
  95: 
  96: 	/*
  97: 	 * If the page tables have been populated with non-cacheable
  98: 	 * accesses (MMU disabled), invalidate those tables again to
  99: 	 * remove any speculatively loaded cache lines.
 100: 	 */
 101: 	cbnz	x19, 0f
 102: 	dmb     sy
 103: 	mov	x1, x0				// end of used region
 104: 	adrp    x0, __pi_init_idmap_pg_dir
 105: 	adr_l	x2, dcache_inval_poc
 106: 	blr	x2
 107: 	b	1f
 108: 
 109: 	/*
 110: 	 * If we entered with the MMU and caches on, clean the ID mapped part
 111: 	 * of the primary boot code to the PoC so we can safely execute it with
 112: 	 * the MMU off.
 113: 	 */
 114: 0:	adrp	x0, __idmap_text_start
 115: 	adr_l	x1, __idmap_text_end
 116: 	adr_l	x2, dcache_clean_poc
 117: 	blr	x2
 118: 
 119: 1:	mov	x0, x19
 120: 	bl	init_kernel_el			// w0=cpu_boot_mode
 121: 	mov	x20, x0
 122: 
 123: 	/*
 124: 	 * The following calls CPU setup code, see arch/arm64/mm/proc.S for
 125: 	 * details.
 126: 	 * On return, the CPU will be ready for the MMU to be turned on and
 127: 	 * the TCR will have been set.
 128: 	 */
 129: 	bl	__cpu_setup			// initialise processor
 130: 	b	__primary_switch
 131: SYM_CODE_END(primary_entry)

설명: primary_entry는 boot CPU의 첫 실행 루틴입니다. MMU가 켜져 들어왔는지 기록하고, x0-x3 boot argument를 보존하고, init idmap page table을 만든 뒤 EL 초기화와 CPU setup을 거쳐 __primary_switch로 넘어갑니다.

  • x19, x20, x21은 start_kernel까지 의미가 이어지는 callee-saved register입니다. 이 셋은 단순 임시 레지스터가 아니라 early boot state carrier입니다.
  • __pi_create_init_idmap 이후 cache maintenance 분기가 나뉩니다. MMU off로 page table을 썼다면 speculative cache line을 지워야 하고, MMU on으로 들어왔다면 idmap text를 PoC까지 clean해야 합니다.
  • __cpu_setup은 TCR/SCTLR 등 MMU enable에 필요한 CPU-local system register를 준비합니다. 아직 C runtime이 아닙니다.
3. primary_entry: idmap을 만들고 MMU 전환 직전까지 간다 흐름
record statex19
preserve argsx21/FDT
create idmapinit page table
__cpu_setupMMU-ready CPU
__primary_switchturn on MMU

4. record_mmu_state: 들어온 MMU/cache 상태를 x19로 압축

원본 코드: arch/arm64/kernel/head.S, lines 134-165
 134: SYM_CODE_START_LOCAL(record_mmu_state)
 135: 	mrs	x19, CurrentEL
 136: 	cmp	x19, #CurrentEL_EL2
 137: 	mrs	x19, sctlr_el1
 138: 	b.ne	0f
 139: 	mrs	x19, sctlr_el2
 140: 0:
 141: CPU_LE( tbnz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
 142: CPU_BE( tbz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
 143: 	tst	x19, #SCTLR_ELx_C		// Z := (C == 0)
 144: 	and	x19, x19, #SCTLR_ELx_M		// isolate M bit
 145: 	csel	x19, xzr, x19, eq		// clear x19 if Z
 146: 	ret
 147: 
 148: 	/*
 149: 	 * Set the correct endianness early so all memory accesses issued
 150: 	 * before init_kernel_el() occur in the correct byte order. Note that
 151: 	 * this means the MMU must be disabled, or the active ID map will end
 152: 	 * up getting interpreted with the wrong byte order.
 153: 	 */
 154: 1:	eor	x19, x19, #SCTLR_ELx_EE
 155: 	bic	x19, x19, #SCTLR_ELx_M
 156: 	b.ne	2f
 157: 	pre_disable_mmu_workaround
 158: 	msr	sctlr_el2, x19
 159: 	b	3f
 160: 2:	pre_disable_mmu_workaround
 161: 	msr	sctlr_el1, x19
 162: 3:	isb
 163: 	mov	x19, xzr
 164: 	ret
 165: SYM_CODE_END(record_mmu_state)

설명: 이 함수는 entry 당시 MMU와 D-cache 상태를 하나의 early state로 압축합니다. 또한 endian이 기대와 다르면 MMU를 끄고 SCTLR의 EE bit를 맞춘 뒤 다시 진행합니다.

  • CurrentEL이 EL2인지 EL1인지에 따라 sctlr_el2 또는 sctlr_el1을 읽습니다. 같은 SCTLR 이름이 아니라 현재 실행 레벨의 제어 레지스터를 읽는 것이 중요합니다.
  • C bit가 꺼져 있으면 x19를 0으로 지웁니다. 이후 primary_entry의 cache maintenance 분기는 이 값 하나로 갈립니다.
  • endianness가 맞지 않을 때는 MMU가 켜져 있으면 안 됩니다. idmap 자체를 다른 byte order로 해석하는 순간 더 이상 신뢰할 코드 경로가 아닙니다.
4. record_mmu_state: 들어온 MMU/cache 상태를 x19로 압축 흐름
CurrentELEL1 or EL2
SCTLRM/C/EE bits
x19boot MMU state

5. preserve_boot_args: FDT와 boot args를 잃지 않는다

원본 코드: arch/arm64/kernel/head.S, lines 170-185
 170: SYM_CODE_START_LOCAL(preserve_boot_args)
 171: 	mov	x21, x0				// x21=FDT
 172: 
 173: 	adr_l	x0, boot_args			// record the contents of
 174: 	stp	x21, x1, [x0]			// x0 .. x3 at kernel entry
 175: 	stp	x2, x3, [x0, #16]
 176: 
 177: 	cbnz	x19, 0f				// skip cache invalidation if MMU is on
 178: 	dmb	sy				// needed before dc ivac with
 179: 						// MMU off
 180: 
 181: 	add	x1, x0, #0x20			// 4 x 8 bytes
 182: 	b	dcache_inval_poc		// tail call
 183: 0:	str_l   x19, mmu_enabled_at_boot, x0
 184: 	ret
 185: SYM_CODE_END(preserve_boot_args)

설명: bootloader가 x0-x3에 넘긴 값을 전역 boot_args에 저장합니다. 특히 x0의 FDT physical address는 x21에 보존되어 __primary_switched 이후 __fdt_pointer로 넘어갑니다.

  • x21은 FDT pointer 전용으로 쓰입니다. start_kernel까지 살아야 하므로 일반 scratch register처럼 쓰면 안 됩니다.
  • MMU off로 들어온 경우 boot_args를 저장한 뒤 해당 cache line을 invalidate합니다. 이후 C 코드가 stale 값을 보면 부팅 전체가 흔들립니다.
  • MMU on으로 들어온 경우 mmu_enabled_at_boot에 x19를 저장합니다. 이 정보는 boot protocol 위반이나 특수 진입 경로를 해석할 때 중요합니다.
5. preserve_boot_args: FDT와 boot args를 잃지 않는다 흐름
x0-x3bootloader ABI
boot_argsearly global copy
x21FDT pointer

6. __primary_switched: virtual world에서 start_kernel 호출

원본 코드: arch/arm64/kernel/head.S, lines 195-248
 195: 	.macro	init_cpu_task tsk, tmp1, tmp2
 196: 	msr	sp_el0, \tsk
 197: 
 198: 	ldr	\tmp1, [\tsk, #TSK_STACK]
 199: 	add	sp, \tmp1, #THREAD_SIZE
 200: 	sub	sp, sp, #PT_REGS_SIZE
 201: 
 202: 	stp	xzr, xzr, [sp, #S_STACKFRAME]
 203: 	mov	\tmp1, #FRAME_META_TYPE_FINAL
 204: 	str	\tmp1, [sp, #S_STACKFRAME_TYPE]
 205: 	add	x29, sp, #S_STACKFRAME
 206: 
 207: 	scs_load_current
 208: 
 209: 	adr_l	\tmp1, __per_cpu_offset
 210: 	ldr	w\tmp2, [\tsk, #TSK_TI_CPU]
 211: 	ldr	\tmp1, [\tmp1, \tmp2, lsl #3]
 212: 	set_this_cpu_offset \tmp1
 213: 	.endm
 214: 
 215: /*
 216:  * The following fragment of code is executed with the MMU enabled.
 217:  *
 218:  *   x0 = __pa(KERNEL_START)
 219:  */
 220: SYM_FUNC_START_LOCAL(__primary_switched)
 221: 	adr_l	x4, init_task
 222: 	init_cpu_task x4, x5, x6
 223: 
 224: 	adr_l	x8, vectors			// load VBAR_EL1 with virtual
 225: 	msr	vbar_el1, x8			// vector table address
 226: 	isb
 227: 
 228: 	stp	x29, x30, [sp, #-16]!
 229: 	mov	x29, sp
 230: 
 231: 	str_l	x21, __fdt_pointer, x5		// Save FDT pointer
 232: 
 233: 	adrp	x4, _text			// Save the offset between
 234: 	sub	x4, x4, x0			// the kernel virtual and
 235: 	str_l	x4, kimage_voffset, x5		// physical mappings
 236: 
 237: 	mov	x0, x20
 238: 	bl	set_cpu_boot_mode_flag
 239: 
 240: #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
 241: 	bl	kasan_early_init
 242: #endif
 243: 	mov	x0, x20
 244: 	bl	finalise_el2			// Prefer VHE if possible
 245: 	ldp	x29, x30, [sp], #16
 246: 	bl	start_kernel
 247: 	ASM_BUG()
 248: SYM_FUNC_END(__primary_switched)

설명: MMU가 켜진 뒤의 첫 안정 지점입니다. init_task로 CPU task context를 세팅하고, vector table을 VBAR_EL1에 올리고, FDT pointer와 kimage_voffset을 저장한 뒤 start_kernel로 들어갑니다.

  • init_cpu_task 매크로는 sp_el0, task stack, frame record, shadow call stack, per-cpu offset을 한 번에 세팅합니다.
  • kimage_voffset은 kernel virtual address와 physical address의 차이입니다. 이후 __pa/__va 계열 계산의 핵심 전제가 됩니다.
  • start_kernel은 돌아오면 안 됩니다. 바로 다음 ASM_BUG는 이 함수의 control-flow contract를 드러냅니다.
6. __primary_switched: virtual world에서 start_kernel 호출 흐름
init_taskcurrent task context
vectorsexception base
kimage_voffsetVA-PA delta
start_kernelC world

7. init_kernel_el: EL1/EL2 실행 상태 정리

원본 코드: arch/arm64/kernel/head.S, lines 270-331
 270: SYM_FUNC_START(init_kernel_el)
 271: 	mrs	x1, CurrentEL
 272: 	cmp	x1, #CurrentEL_EL2
 273: 	b.eq	init_el2
 274: 
 275: SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)
 276: 	mov_q	x0, INIT_SCTLR_EL1_MMU_OFF
 277: 	pre_disable_mmu_workaround
 278: 	msr	sctlr_el1, x0
 279: 	isb
 280: 	mov_q	x0, INIT_PSTATE_EL1
 281: 	msr	spsr_el1, x0
 282: 	msr	elr_el1, lr
 283: 	mov	w0, #BOOT_CPU_MODE_EL1
 284: 	eret
 285: 
 286: SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
 287: 	msr	elr_el2, lr
 288: 
 289: 	// clean all HYP code to the PoC if we booted at EL2 with the MMU on
 290: 	cbz	x0, 0f
 291: 	adrp	x0, __hyp_idmap_text_start
 292: 	adr_l	x1, __hyp_text_end
 293: 	adr_l	x2, dcache_clean_poc
 294: 	blr	x2
 295: 
 296: 	mov_q	x0, INIT_SCTLR_EL2_MMU_OFF
 297: 	pre_disable_mmu_workaround
 298: 	msr	sctlr_el2, x0
 299: 	isb
 300: 0:
 301: 
 302: 	init_el2_hcr	HCR_HOST_NVHE_FLAGS
 303: 	init_el2_state
 304: 
 305: 	/* Hypervisor stub */
 306: 	adr_l	x0, __hyp_stub_vectors
 307: 	msr	vbar_el2, x0
 308: 	isb
 309: 
 310: 	mov_q	x1, INIT_SCTLR_EL1_MMU_OFF
 311: 
 312: 	mrs	x0, hcr_el2
 313: 	and	x0, x0, #HCR_E2H
 314: 	cbz	x0, 2f
 315: 
 316: 	/* Set a sane SCTLR_EL1, the VHE way */
 317: 	msr_s	SYS_SCTLR_EL12, x1
 318: 	mov	x2, #BOOT_CPU_FLAG_E2H
 319: 	b	3f
 320: 
 321: 2:
 322: 	msr	sctlr_el1, x1
 323: 	mov	x2, xzr
 324: 3:
 325: 	mov	x0, #INIT_PSTATE_EL1
 326: 	msr	spsr_el2, x0
 327: 
 328: 	mov	w0, #BOOT_CPU_MODE_EL2
 329: 	orr	x0, x0, x2
 330: 	eret
 331: SYM_FUNC_END(init_kernel_el)

설명: CPU가 EL1로 들어왔는지 EL2로 들어왔는지 판정하고, 커널이 기대하는 PSTATE/SCTLR/HCR 상태로 맞춥니다. EL2에서는 VHE/nVHE 가능성을 반영하고 EL1로 eret합니다.

  • EL1 path는 SCTLR_EL1을 MMU-off 초기값으로 맞춘 뒤 spsr/elr을 세팅하고 eret합니다.
  • EL2 path는 HYP idmap text cache clean, EL2 state 초기화, hyp stub vector 세팅, VHE 여부에 따른 SCTLR_EL12 처리를 포함합니다.
  • 반환값은 BOOT_CPU_MODE_EL1/EL2와 E2H flag를 담습니다. 이 값은 이후 set_cpu_boot_mode_flag와 finalise_el2에서 소비됩니다.
7. init_kernel_el: EL1/EL2 실행 상태 정리 흐름
CurrentELEL1/EL2
SCTLR/HCRknown state
eretsynchronized transition
boot modex0 result

8. secondary CPU: holding pen부터 secondary_start_kernel까지

원본 코드: arch/arm64/kernel/head.S, lines 333-406
 333: 	/*
 334: 	 * This provides a "holding pen" for platforms to hold all secondary
 335: 	 * cores are held until we're ready for them to initialise.
 336: 	 */
 337: SYM_FUNC_START(secondary_holding_pen)
 338: 	mov	x0, xzr
 339: 	bl	init_kernel_el			// w0=cpu_boot_mode
 340: 	mrs	x2, mpidr_el1
 341: 	mov_q	x1, MPIDR_HWID_BITMASK
 342: 	and	x2, x2, x1
 343: 	adr_l	x3, secondary_holding_pen_release
 344: pen:	ldr	x4, [x3]
 345: 	cmp	x4, x2
 346: 	b.eq	secondary_startup
 347: 	wfe
 348: 	b	pen
 349: SYM_FUNC_END(secondary_holding_pen)
 350: 
 351: 	/*
 352: 	 * Secondary entry point that jumps straight into the kernel. Only to
 353: 	 * be used where CPUs are brought online dynamically by the kernel.
 354: 	 */
 355: SYM_FUNC_START(secondary_entry)
 356: 	mov	x0, xzr
 357: 	bl	init_kernel_el			// w0=cpu_boot_mode
 358: 	b	secondary_startup
 359: SYM_FUNC_END(secondary_entry)
 360: 
 361: SYM_FUNC_START_LOCAL(secondary_startup)
 362: 	/*
 363: 	 * Common entry point for secondary CPUs.
 364: 	 */
 365: 	mov	x20, x0				// preserve boot mode
 366: 
 367: #ifdef CONFIG_ARM64_VA_BITS_52
 368: alternative_if ARM64_HAS_VA52
 369: 	bl	__cpu_secondary_check52bitva
 370: alternative_else_nop_endif
 371: #endif
 372: 
 373: 	bl	__cpu_setup			// initialise processor
 374: 	adrp	x1, swapper_pg_dir
 375: 	adrp	x2, idmap_pg_dir
 376: 	bl	__enable_mmu
 377: 	ldr	x8, =__secondary_switched
 378: 	br	x8
 379: SYM_FUNC_END(secondary_startup)
 380: 
 381: 	.text
 382: SYM_FUNC_START_LOCAL(__secondary_switched)
 383: 	mov	x0, x20
 384: 	bl	set_cpu_boot_mode_flag
 385: 
 386: 	mov	x0, x20
 387: 	bl	finalise_el2
 388: 
 389: 	str_l	xzr, __early_cpu_boot_status, x3
 390: 	adr_l	x5, vectors
 391: 	msr	vbar_el1, x5
 392: 	isb
 393: 
 394: 	adr_l	x0, secondary_data
 395: 	ldr	x2, [x0, #CPU_BOOT_TASK]
 396: 	cbz	x2, __secondary_too_slow
 397: 
 398: 	init_cpu_task x2, x1, x3
 399: 
 400: #ifdef CONFIG_ARM64_PTR_AUTH
 401: 	ptrauth_keys_init_cpu x2, x3, x4, x5
 402: #endif
 403: 
 404: 	bl	secondary_start_kernel
 405: 	ASM_BUG()
 406: SYM_FUNC_END(__secondary_switched)

설명: 보조 CPU는 primary와 같은 start_kernel을 타지 않습니다. holding pen에서 release 값을 기다리거나 hotplug entry로 들어와 init_kernel_el, __cpu_setup, __enable_mmu를 거쳐 secondary_start_kernel으로 갑니다.

  • secondary_holding_pen은 MPIDR을 보고 자기 차례가 올 때까지 wfe loop에 머뭅니다. 이 구조는 firmware/bootloader가 모든 CPU를 동시에 풀어놓는 플랫폼을 흡수합니다.
  • secondary_startup은 52-bit VA 지원 여부를 확인하고 CPU setup 후 swapper_pg_dir/idmap_pg_dir로 MMU를 켭니다.
  • __secondary_switched는 secondary_data의 task를 가져와 init_cpu_task를 수행하고, ptrauth key 초기화 후 secondary_start_kernel로 진입합니다.
8. secondary CPU: holding pen부터 secondary_start_kernel까지 흐름
holding penwait for release
__cpu_setupCPU local registers
__enable_mmushared kernel mapping
secondary_start_kernelonline path

9. boot mode flag와 early failure status

원본 코드: arch/arm64/kernel/head.S, lines 408-443
 408: SYM_FUNC_START_LOCAL(__secondary_too_slow)
 409: 	wfe
 410: 	wfi
 411: 	b	__secondary_too_slow
 412: SYM_FUNC_END(__secondary_too_slow)
 413: 
 414: /*
 415:  * Sets the __boot_cpu_mode flag depending on the CPU boot mode passed
 416:  * in w0. See arch/arm64/include/asm/virt.h for more info.
 417:  */
 418: SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
 419: 	adr_l	x1, __boot_cpu_mode
 420: 	cmp	w0, #BOOT_CPU_MODE_EL2
 421: 	b.ne	1f
 422: 	add	x1, x1, #4
 423: 1:	str	w0, [x1]			// Save CPU boot mode
 424: 	ret
 425: SYM_FUNC_END(set_cpu_boot_mode_flag)
 426: 
 427: /*
 428:  * The booting CPU updates the failed status @__early_cpu_boot_status,
 429:  * with MMU turned off.
 430:  *
 431:  * update_early_cpu_boot_status tmp, status
 432:  *  - Corrupts tmp1, tmp2
 433:  *  - Writes 'status' to __early_cpu_boot_status and makes sure
 434:  *    it is committed to memory.
 435:  */
 436: 
 437: 	.macro	update_early_cpu_boot_status status, tmp1, tmp2
 438: 	mov	\tmp2, #\status
 439: 	adr_l	\tmp1, __early_cpu_boot_status
 440: 	str	\tmp2, [\tmp1]
 441: 	dmb	sy
 442: 	dc	ivac, \tmp1			// Invalidate potentially stale cache line
 443: 	.endm

설명: 부팅 성공 경로만 있으면 bring-up 디버깅이 안 됩니다. 이 블록은 CPU가 너무 늦거나 granule/VA 조건을 만족하지 못할 때 전역 상태에 실패 이유를 남기는 장치입니다.

  • __secondary_too_slow는 recover path가 아니라 park loop입니다. CPU_BOOT_TASK가 준비되지 않은 secondary가 잘못 진행하는 것을 막습니다.
  • set_cpu_boot_mode_flag는 __boot_cpu_mode의 EL1/EL2 슬롯에 결과를 저장합니다. 커널이 hyp 관련 결정을 할 때 이 정보가 뒤에서 쓰입니다.
  • update_early_cpu_boot_status는 dmb와 dc ivac까지 포함합니다. MMU-off 상태에서 다른 CPU가 이 값을 보게 하려면 단순 store만으로는 부족합니다.
9. boot mode flag와 early failure status 흐름
failure pathpark CPU
__boot_cpu_modeEL state
__early_cpu_boot_statusvisible reason

10. __enable_mmu: TTBR과 SCTLR로 MMU를 켠다

원본 코드: arch/arm64/kernel/head.S, lines 445-473
 445: /*
 446:  * Enable the MMU.
 447:  *
 448:  *  x0  = SCTLR_EL1 value for turning on the MMU.
 449:  *  x1  = TTBR1_EL1 value
 450:  *  x2  = ID map root table address
 451:  *
 452:  * Returns to the caller via x30/lr. This requires the caller to be covered
 453:  * by the .idmap.text section.
 454:  *
 455:  * Checks if the selected granule size is supported by the CPU.
 456:  * If it isn't, park the CPU
 457:  */
 458: 	.section ".idmap.text","a"
 459: SYM_FUNC_START(__enable_mmu)
 460: 	mrs	x3, ID_AA64MMFR0_EL1
 461: 	ubfx	x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
 462: 	cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN
 463: 	b.lt    __no_granule_support
 464: 	cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX
 465: 	b.gt    __no_granule_support
 466: 	phys_to_ttbr x2, x2
 467: 	msr	ttbr0_el1, x2			// load TTBR0
 468: 	load_ttbr1 x1, x1, x3
 469: 
 470: 	set_sctlr_el1	x0
 471: 
 472: 	ret
 473: SYM_FUNC_END(__enable_mmu)

설명: MMU enable은 단순히 SCTLR.M bit를 세우는 일이 아닙니다. CPU가 선택된 translation granule을 지원하는지 확인하고, TTBR0/TTBR1을 올린 뒤 set_sctlr_el1로 전환합니다.

  • ID_AA64MMFR0_EL1의 TGRAN field를 읽어 선택한 page granule이 CPU에서 지원되는지 확인합니다.
  • TTBR0에는 idmap root를, TTBR1에는 kernel mapping root를 넣습니다. caller가 .idmap.text 안에 있어야 ret가 안전합니다.
  • set_sctlr_el1 이후부터 instruction fetch/data access의 주소 해석 전제가 달라집니다. 그래서 이 함수는 항상 idmap으로 자기 자신을 덮어야 합니다.
10. __enable_mmu: TTBR과 SCTLR로 MMU를 켠다 흐름
ID_AA64MMFR0granule check
TTBR0idmap
TTBR1kernel map
SCTLR_EL1MMU on

11. 52-bit VA와 granule 실패 park path

원본 코드: arch/arm64/kernel/head.S, lines 475-506
 475: #ifdef CONFIG_ARM64_VA_BITS_52
 476: SYM_FUNC_START(__cpu_secondary_check52bitva)
 477: #ifndef CONFIG_ARM64_LPA2
 478: 	mrs_s	x0, SYS_ID_AA64MMFR2_EL1
 479: 	and	x0, x0, ID_AA64MMFR2_EL1_VARange_MASK
 480: 	cbnz	x0, 2f
 481: #else
 482: 	mrs	x0, id_aa64mmfr0_el1
 483: 	sbfx	x0, x0, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
 484: 	cmp	x0, #ID_AA64MMFR0_EL1_TGRAN_LPA2
 485: 	b.ge	2f
 486: #endif
 487: 
 488: 	update_early_cpu_boot_status \
 489: 		CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1
 490: 1:	wfe
 491: 	wfi
 492: 	b	1b
 493: 
 494: 2:	ret
 495: SYM_FUNC_END(__cpu_secondary_check52bitva)
 496: #endif
 497: 
 498: SYM_FUNC_START_LOCAL(__no_granule_support)
 499: 	/* Indicate that this CPU can't boot and is stuck in the kernel */
 500: 	update_early_cpu_boot_status \
 501: 		CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2
 502: 1:
 503: 	wfe
 504: 	wfi
 505: 	b	1b
 506: SYM_FUNC_END(__no_granule_support)

설명: 지원하지 않는 VA 크기나 translation granule로 부팅하면 계속 진행하면 안 됩니다. 이 경로는 실패 이유를 남긴 뒤 CPU를 wfe/wfi loop에 세워 둡니다.

  • CONFIG_ARM64_VA_BITS_52에서는 LPA2 여부와 ID register를 보고 52-bit VA가 실제 CPU와 맞는지 확인합니다.
  • granule 미지원은 page table을 만들 수 없다는 뜻입니다. fallback으로 대충 다른 granule을 쓰는 경로가 아닙니다.
  • CPU_STUCK_REASON_* 값은 bring-up 로그 없이도 왜 CPU가 멈췄는지 해석할 수 있게 해 주는 최소한의 breadcrumb입니다.
11. 52-bit VA와 granule 실패 park path 흐름
feature checkVA/granule
status updatereason bits
park loopwfe/wfi

12. __primary_switch: kernel map/relocation 후 __primary_switched로 점프

원본 코드: arch/arm64/kernel/head.S, lines 508-523
 508: SYM_FUNC_START_LOCAL(__primary_switch)
 509: 	adrp	x1, reserved_pg_dir
 510: 	adrp	x2, __pi_init_idmap_pg_dir
 511: 	bl	__enable_mmu
 512: 
 513: 	adrp	x1, early_init_stack
 514: 	mov	sp, x1
 515: 	mov	x29, xzr
 516: 	mov	x0, x20				// pass the full boot status
 517: 	mov	x1, x21				// pass the FDT
 518: 	bl	__pi_early_map_kernel		// Map and relocate the kernel
 519: 
 520: 	ldr	x8, =__primary_switched
 521: 	adrp	x0, KERNEL_START		// __pa(KERNEL_START)
 522: 	br	x8
 523: SYM_FUNC_END(__primary_switch)

설명: primary boot path의 마지막 assembly 관문입니다. reserved_pg_dir와 init idmap으로 MMU를 켜고, __pi_early_map_kernel이 실제 kernel mapping과 relocation을 처리한 뒤 virtual address 세계의 __primary_switched로 점프합니다.

  • reserved_pg_dir는 아직 최종 swapper_pg_dir로 완성되기 전의 안전한 kernel mapping root입니다.
  • __pi_early_map_kernel은 FDT와 boot status를 받아 kernel mapping/relocation 전제를 만듭니다. 여기서 끝나야 start_kernel이 virtual address 기준으로 실행됩니다.
  • ldr x8, =__primary_switched; br x8은 link register call이 아니라 절대 점프입니다. 이 이후 control-flow는 MMU-on virtual text로 넘어갑니다.
12. __primary_switch: kernel map/relocation 후 __primary_switched로 점프 흐름
reserved_pg_dirtemporary kernel root
__enable_mmuidmap-covered switch
__pi_early_map_kernelmap/relocate
__primary_switchedC entry setup

원본 코드 전문

아래 전문은 Linux 6.18.37 LTS arch/arm64/kernel/head.S입니다. 위 섹션의 line number와 같은 기준입니다.

   1: /* SPDX-License-Identifier: GPL-2.0-only */
   2: /*
   3:  * Low-level CPU initialisation
   4:  * Based on arch/arm/kernel/head.S
   5:  *
   6:  * Copyright (C) 1994-2002 Russell King
   7:  * Copyright (C) 2003-2012 ARM Ltd.
   8:  * Authors:	Catalin Marinas <[email protected]>
   9:  *		Will Deacon <[email protected]>
  10:  */
  11: 
  12: #include <linux/linkage.h>
  13: #include <linux/init.h>
  14: #include <linux/pgtable.h>
  15: 
  16: #include <asm/asm_pointer_auth.h>
  17: #include <asm/assembler.h>
  18: #include <asm/boot.h>
  19: #include <asm/bug.h>
  20: #include <asm/ptrace.h>
  21: #include <asm/asm-offsets.h>
  22: #include <asm/cache.h>
  23: #include <asm/cputype.h>
  24: #include <asm/el2_setup.h>
  25: #include <asm/elf.h>
  26: #include <asm/image.h>
  27: #include <asm/kernel-pgtable.h>
  28: #include <asm/kvm_arm.h>
  29: #include <asm/memory.h>
  30: #include <asm/pgtable-hwdef.h>
  31: #include <asm/page.h>
  32: #include <asm/scs.h>
  33: #include <asm/smp.h>
  34: #include <asm/sysreg.h>
  35: #include <asm/stacktrace/frame.h>
  36: #include <asm/thread_info.h>
  37: #include <asm/virt.h>
  38: 
  39: #include "efi-header.S"
  40: 
  41: #if (PAGE_OFFSET & 0x1fffff) != 0
  42: #error PAGE_OFFSET must be at least 2MB aligned
  43: #endif
  44: 
  45: /*
  46:  * Kernel startup entry point.
  47:  * ---------------------------
  48:  *
  49:  * The requirements are:
  50:  *   MMU = off, D-cache = off, I-cache = on or off,
  51:  *   x0 = physical address to the FDT blob.
  52:  *
  53:  * Note that the callee-saved registers are used for storing variables
  54:  * that are useful before the MMU is enabled. The allocations are described
  55:  * in the entry routines.
  56:  */
  57: 	__HEAD
  58: 	/*
  59: 	 * DO NOT MODIFY. Image header expected by Linux boot-loaders.
  60: 	 */
  61: 	efi_signature_nop			// special NOP to identity as PE/COFF executable
  62: 	b	primary_entry			// branch to kernel start, magic
  63: 	.quad	0				// Image load offset from start of RAM, little-endian
  64: 	le64sym	_kernel_size_le			// Effective size of kernel image, little-endian
  65: 	le64sym	_kernel_flags_le		// Informative flags, little-endian
  66: 	.quad	0				// reserved
  67: 	.quad	0				// reserved
  68: 	.quad	0				// reserved
  69: 	.ascii	ARM64_IMAGE_MAGIC		// Magic number
  70: 	.long	.Lpe_header_offset		// Offset to the PE header.
  71: 
  72: 	__EFI_PE_HEADER
  73: 
  74: 	.section ".idmap.text","a"
  75: 
  76: 	/*
  77: 	 * The following callee saved general purpose registers are used on the
  78: 	 * primary lowlevel boot path:
  79: 	 *
  80: 	 *  Register   Scope                      Purpose
  81: 	 *  x19        primary_entry() .. start_kernel()        whether we entered with the MMU on
  82: 	 *  x20        primary_entry() .. __primary_switch()    CPU boot mode
  83: 	 *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0
  84: 	 */
  85: SYM_CODE_START(primary_entry)
  86: 	bl	record_mmu_state
  87: 	bl	preserve_boot_args
  88: 
  89: 	adrp	x1, early_init_stack
  90: 	mov	sp, x1
  91: 	mov	x29, xzr
  92: 	adrp	x0, __pi_init_idmap_pg_dir
  93: 	mov	x1, xzr
  94: 	bl	__pi_create_init_idmap
  95: 
  96: 	/*
  97: 	 * If the page tables have been populated with non-cacheable
  98: 	 * accesses (MMU disabled), invalidate those tables again to
  99: 	 * remove any speculatively loaded cache lines.
 100: 	 */
 101: 	cbnz	x19, 0f
 102: 	dmb     sy
 103: 	mov	x1, x0				// end of used region
 104: 	adrp    x0, __pi_init_idmap_pg_dir
 105: 	adr_l	x2, dcache_inval_poc
 106: 	blr	x2
 107: 	b	1f
 108: 
 109: 	/*
 110: 	 * If we entered with the MMU and caches on, clean the ID mapped part
 111: 	 * of the primary boot code to the PoC so we can safely execute it with
 112: 	 * the MMU off.
 113: 	 */
 114: 0:	adrp	x0, __idmap_text_start
 115: 	adr_l	x1, __idmap_text_end
 116: 	adr_l	x2, dcache_clean_poc
 117: 	blr	x2
 118: 
 119: 1:	mov	x0, x19
 120: 	bl	init_kernel_el			// w0=cpu_boot_mode
 121: 	mov	x20, x0
 122: 
 123: 	/*
 124: 	 * The following calls CPU setup code, see arch/arm64/mm/proc.S for
 125: 	 * details.
 126: 	 * On return, the CPU will be ready for the MMU to be turned on and
 127: 	 * the TCR will have been set.
 128: 	 */
 129: 	bl	__cpu_setup			// initialise processor
 130: 	b	__primary_switch
 131: SYM_CODE_END(primary_entry)
 132: 
 133: 	__INIT
 134: SYM_CODE_START_LOCAL(record_mmu_state)
 135: 	mrs	x19, CurrentEL
 136: 	cmp	x19, #CurrentEL_EL2
 137: 	mrs	x19, sctlr_el1
 138: 	b.ne	0f
 139: 	mrs	x19, sctlr_el2
 140: 0:
 141: CPU_LE( tbnz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
 142: CPU_BE( tbz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
 143: 	tst	x19, #SCTLR_ELx_C		// Z := (C == 0)
 144: 	and	x19, x19, #SCTLR_ELx_M		// isolate M bit
 145: 	csel	x19, xzr, x19, eq		// clear x19 if Z
 146: 	ret
 147: 
 148: 	/*
 149: 	 * Set the correct endianness early so all memory accesses issued
 150: 	 * before init_kernel_el() occur in the correct byte order. Note that
 151: 	 * this means the MMU must be disabled, or the active ID map will end
 152: 	 * up getting interpreted with the wrong byte order.
 153: 	 */
 154: 1:	eor	x19, x19, #SCTLR_ELx_EE
 155: 	bic	x19, x19, #SCTLR_ELx_M
 156: 	b.ne	2f
 157: 	pre_disable_mmu_workaround
 158: 	msr	sctlr_el2, x19
 159: 	b	3f
 160: 2:	pre_disable_mmu_workaround
 161: 	msr	sctlr_el1, x19
 162: 3:	isb
 163: 	mov	x19, xzr
 164: 	ret
 165: SYM_CODE_END(record_mmu_state)
 166: 
 167: /*
 168:  * Preserve the arguments passed by the bootloader in x0 .. x3
 169:  */
 170: SYM_CODE_START_LOCAL(preserve_boot_args)
 171: 	mov	x21, x0				// x21=FDT
 172: 
 173: 	adr_l	x0, boot_args			// record the contents of
 174: 	stp	x21, x1, [x0]			// x0 .. x3 at kernel entry
 175: 	stp	x2, x3, [x0, #16]
 176: 
 177: 	cbnz	x19, 0f				// skip cache invalidation if MMU is on
 178: 	dmb	sy				// needed before dc ivac with
 179: 						// MMU off
 180: 
 181: 	add	x1, x0, #0x20			// 4 x 8 bytes
 182: 	b	dcache_inval_poc		// tail call
 183: 0:	str_l   x19, mmu_enabled_at_boot, x0
 184: 	ret
 185: SYM_CODE_END(preserve_boot_args)
 186: 
 187: 	/*
 188: 	 * Initialize CPU registers with task-specific and cpu-specific context.
 189: 	 *
 190: 	 * Create a final frame record at task_pt_regs(current)->stackframe, so
 191: 	 * that the unwinder can identify the final frame record of any task by
 192: 	 * its location in the task stack. We reserve the entire pt_regs space
 193: 	 * for consistency with user tasks and kthreads.
 194: 	 */
 195: 	.macro	init_cpu_task tsk, tmp1, tmp2
 196: 	msr	sp_el0, \tsk
 197: 
 198: 	ldr	\tmp1, [\tsk, #TSK_STACK]
 199: 	add	sp, \tmp1, #THREAD_SIZE
 200: 	sub	sp, sp, #PT_REGS_SIZE
 201: 
 202: 	stp	xzr, xzr, [sp, #S_STACKFRAME]
 203: 	mov	\tmp1, #FRAME_META_TYPE_FINAL
 204: 	str	\tmp1, [sp, #S_STACKFRAME_TYPE]
 205: 	add	x29, sp, #S_STACKFRAME
 206: 
 207: 	scs_load_current
 208: 
 209: 	adr_l	\tmp1, __per_cpu_offset
 210: 	ldr	w\tmp2, [\tsk, #TSK_TI_CPU]
 211: 	ldr	\tmp1, [\tmp1, \tmp2, lsl #3]
 212: 	set_this_cpu_offset \tmp1
 213: 	.endm
 214: 
 215: /*
 216:  * The following fragment of code is executed with the MMU enabled.
 217:  *
 218:  *   x0 = __pa(KERNEL_START)
 219:  */
 220: SYM_FUNC_START_LOCAL(__primary_switched)
 221: 	adr_l	x4, init_task
 222: 	init_cpu_task x4, x5, x6
 223: 
 224: 	adr_l	x8, vectors			// load VBAR_EL1 with virtual
 225: 	msr	vbar_el1, x8			// vector table address
 226: 	isb
 227: 
 228: 	stp	x29, x30, [sp, #-16]!
 229: 	mov	x29, sp
 230: 
 231: 	str_l	x21, __fdt_pointer, x5		// Save FDT pointer
 232: 
 233: 	adrp	x4, _text			// Save the offset between
 234: 	sub	x4, x4, x0			// the kernel virtual and
 235: 	str_l	x4, kimage_voffset, x5		// physical mappings
 236: 
 237: 	mov	x0, x20
 238: 	bl	set_cpu_boot_mode_flag
 239: 
 240: #if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
 241: 	bl	kasan_early_init
 242: #endif
 243: 	mov	x0, x20
 244: 	bl	finalise_el2			// Prefer VHE if possible
 245: 	ldp	x29, x30, [sp], #16
 246: 	bl	start_kernel
 247: 	ASM_BUG()
 248: SYM_FUNC_END(__primary_switched)
 249: 
 250: /*
 251:  * end early head section, begin head code that is also used for
 252:  * hotplug and needs to have the same protections as the text region
 253:  */
 254: 	.section ".idmap.text","a"
 255: 
 256: /*
 257:  * Starting from EL2 or EL1, configure the CPU to execute at the highest
 258:  * reachable EL supported by the kernel in a chosen default state. If dropping
 259:  * from EL2 to EL1, configure EL2 before configuring EL1.
 260:  *
 261:  * Since we cannot always rely on ERET synchronizing writes to sysregs (e.g. if
 262:  * SCTLR_ELx.EOS is clear), we place an ISB prior to ERET.
 263:  *
 264:  * Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in x0 if
 265:  * booted in EL1 or EL2 respectively, with the top 32 bits containing
 266:  * potential context flags. These flags are *not* stored in __boot_cpu_mode.
 267:  *
 268:  * x0: whether we are being called from the primary boot path with the MMU on
 269:  */
 270: SYM_FUNC_START(init_kernel_el)
 271: 	mrs	x1, CurrentEL
 272: 	cmp	x1, #CurrentEL_EL2
 273: 	b.eq	init_el2
 274: 
 275: SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)
 276: 	mov_q	x0, INIT_SCTLR_EL1_MMU_OFF
 277: 	pre_disable_mmu_workaround
 278: 	msr	sctlr_el1, x0
 279: 	isb
 280: 	mov_q	x0, INIT_PSTATE_EL1
 281: 	msr	spsr_el1, x0
 282: 	msr	elr_el1, lr
 283: 	mov	w0, #BOOT_CPU_MODE_EL1
 284: 	eret
 285: 
 286: SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
 287: 	msr	elr_el2, lr
 288: 
 289: 	// clean all HYP code to the PoC if we booted at EL2 with the MMU on
 290: 	cbz	x0, 0f
 291: 	adrp	x0, __hyp_idmap_text_start
 292: 	adr_l	x1, __hyp_text_end
 293: 	adr_l	x2, dcache_clean_poc
 294: 	blr	x2
 295: 
 296: 	mov_q	x0, INIT_SCTLR_EL2_MMU_OFF
 297: 	pre_disable_mmu_workaround
 298: 	msr	sctlr_el2, x0
 299: 	isb
 300: 0:
 301: 
 302: 	init_el2_hcr	HCR_HOST_NVHE_FLAGS
 303: 	init_el2_state
 304: 
 305: 	/* Hypervisor stub */
 306: 	adr_l	x0, __hyp_stub_vectors
 307: 	msr	vbar_el2, x0
 308: 	isb
 309: 
 310: 	mov_q	x1, INIT_SCTLR_EL1_MMU_OFF
 311: 
 312: 	mrs	x0, hcr_el2
 313: 	and	x0, x0, #HCR_E2H
 314: 	cbz	x0, 2f
 315: 
 316: 	/* Set a sane SCTLR_EL1, the VHE way */
 317: 	msr_s	SYS_SCTLR_EL12, x1
 318: 	mov	x2, #BOOT_CPU_FLAG_E2H
 319: 	b	3f
 320: 
 321: 2:
 322: 	msr	sctlr_el1, x1
 323: 	mov	x2, xzr
 324: 3:
 325: 	mov	x0, #INIT_PSTATE_EL1
 326: 	msr	spsr_el2, x0
 327: 
 328: 	mov	w0, #BOOT_CPU_MODE_EL2
 329: 	orr	x0, x0, x2
 330: 	eret
 331: SYM_FUNC_END(init_kernel_el)
 332: 
 333: 	/*
 334: 	 * This provides a "holding pen" for platforms to hold all secondary
 335: 	 * cores are held until we're ready for them to initialise.
 336: 	 */
 337: SYM_FUNC_START(secondary_holding_pen)
 338: 	mov	x0, xzr
 339: 	bl	init_kernel_el			// w0=cpu_boot_mode
 340: 	mrs	x2, mpidr_el1
 341: 	mov_q	x1, MPIDR_HWID_BITMASK
 342: 	and	x2, x2, x1
 343: 	adr_l	x3, secondary_holding_pen_release
 344: pen:	ldr	x4, [x3]
 345: 	cmp	x4, x2
 346: 	b.eq	secondary_startup
 347: 	wfe
 348: 	b	pen
 349: SYM_FUNC_END(secondary_holding_pen)
 350: 
 351: 	/*
 352: 	 * Secondary entry point that jumps straight into the kernel. Only to
 353: 	 * be used where CPUs are brought online dynamically by the kernel.
 354: 	 */
 355: SYM_FUNC_START(secondary_entry)
 356: 	mov	x0, xzr
 357: 	bl	init_kernel_el			// w0=cpu_boot_mode
 358: 	b	secondary_startup
 359: SYM_FUNC_END(secondary_entry)
 360: 
 361: SYM_FUNC_START_LOCAL(secondary_startup)
 362: 	/*
 363: 	 * Common entry point for secondary CPUs.
 364: 	 */
 365: 	mov	x20, x0				// preserve boot mode
 366: 
 367: #ifdef CONFIG_ARM64_VA_BITS_52
 368: alternative_if ARM64_HAS_VA52
 369: 	bl	__cpu_secondary_check52bitva
 370: alternative_else_nop_endif
 371: #endif
 372: 
 373: 	bl	__cpu_setup			// initialise processor
 374: 	adrp	x1, swapper_pg_dir
 375: 	adrp	x2, idmap_pg_dir
 376: 	bl	__enable_mmu
 377: 	ldr	x8, =__secondary_switched
 378: 	br	x8
 379: SYM_FUNC_END(secondary_startup)
 380: 
 381: 	.text
 382: SYM_FUNC_START_LOCAL(__secondary_switched)
 383: 	mov	x0, x20
 384: 	bl	set_cpu_boot_mode_flag
 385: 
 386: 	mov	x0, x20
 387: 	bl	finalise_el2
 388: 
 389: 	str_l	xzr, __early_cpu_boot_status, x3
 390: 	adr_l	x5, vectors
 391: 	msr	vbar_el1, x5
 392: 	isb
 393: 
 394: 	adr_l	x0, secondary_data
 395: 	ldr	x2, [x0, #CPU_BOOT_TASK]
 396: 	cbz	x2, __secondary_too_slow
 397: 
 398: 	init_cpu_task x2, x1, x3
 399: 
 400: #ifdef CONFIG_ARM64_PTR_AUTH
 401: 	ptrauth_keys_init_cpu x2, x3, x4, x5
 402: #endif
 403: 
 404: 	bl	secondary_start_kernel
 405: 	ASM_BUG()
 406: SYM_FUNC_END(__secondary_switched)
 407: 
 408: SYM_FUNC_START_LOCAL(__secondary_too_slow)
 409: 	wfe
 410: 	wfi
 411: 	b	__secondary_too_slow
 412: SYM_FUNC_END(__secondary_too_slow)
 413: 
 414: /*
 415:  * Sets the __boot_cpu_mode flag depending on the CPU boot mode passed
 416:  * in w0. See arch/arm64/include/asm/virt.h for more info.
 417:  */
 418: SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
 419: 	adr_l	x1, __boot_cpu_mode
 420: 	cmp	w0, #BOOT_CPU_MODE_EL2
 421: 	b.ne	1f
 422: 	add	x1, x1, #4
 423: 1:	str	w0, [x1]			// Save CPU boot mode
 424: 	ret
 425: SYM_FUNC_END(set_cpu_boot_mode_flag)
 426: 
 427: /*
 428:  * The booting CPU updates the failed status @__early_cpu_boot_status,
 429:  * with MMU turned off.
 430:  *
 431:  * update_early_cpu_boot_status tmp, status
 432:  *  - Corrupts tmp1, tmp2
 433:  *  - Writes 'status' to __early_cpu_boot_status and makes sure
 434:  *    it is committed to memory.
 435:  */
 436: 
 437: 	.macro	update_early_cpu_boot_status status, tmp1, tmp2
 438: 	mov	\tmp2, #\status
 439: 	adr_l	\tmp1, __early_cpu_boot_status
 440: 	str	\tmp2, [\tmp1]
 441: 	dmb	sy
 442: 	dc	ivac, \tmp1			// Invalidate potentially stale cache line
 443: 	.endm
 444: 
 445: /*
 446:  * Enable the MMU.
 447:  *
 448:  *  x0  = SCTLR_EL1 value for turning on the MMU.
 449:  *  x1  = TTBR1_EL1 value
 450:  *  x2  = ID map root table address
 451:  *
 452:  * Returns to the caller via x30/lr. This requires the caller to be covered
 453:  * by the .idmap.text section.
 454:  *
 455:  * Checks if the selected granule size is supported by the CPU.
 456:  * If it isn't, park the CPU
 457:  */
 458: 	.section ".idmap.text","a"
 459: SYM_FUNC_START(__enable_mmu)
 460: 	mrs	x3, ID_AA64MMFR0_EL1
 461: 	ubfx	x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
 462: 	cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN
 463: 	b.lt    __no_granule_support
 464: 	cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX
 465: 	b.gt    __no_granule_support
 466: 	phys_to_ttbr x2, x2
 467: 	msr	ttbr0_el1, x2			// load TTBR0
 468: 	load_ttbr1 x1, x1, x3
 469: 
 470: 	set_sctlr_el1	x0
 471: 
 472: 	ret
 473: SYM_FUNC_END(__enable_mmu)
 474: 
 475: #ifdef CONFIG_ARM64_VA_BITS_52
 476: SYM_FUNC_START(__cpu_secondary_check52bitva)
 477: #ifndef CONFIG_ARM64_LPA2
 478: 	mrs_s	x0, SYS_ID_AA64MMFR2_EL1
 479: 	and	x0, x0, ID_AA64MMFR2_EL1_VARange_MASK
 480: 	cbnz	x0, 2f
 481: #else
 482: 	mrs	x0, id_aa64mmfr0_el1
 483: 	sbfx	x0, x0, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
 484: 	cmp	x0, #ID_AA64MMFR0_EL1_TGRAN_LPA2
 485: 	b.ge	2f
 486: #endif
 487: 
 488: 	update_early_cpu_boot_status \
 489: 		CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1
 490: 1:	wfe
 491: 	wfi
 492: 	b	1b
 493: 
 494: 2:	ret
 495: SYM_FUNC_END(__cpu_secondary_check52bitva)
 496: #endif
 497: 
 498: SYM_FUNC_START_LOCAL(__no_granule_support)
 499: 	/* Indicate that this CPU can't boot and is stuck in the kernel */
 500: 	update_early_cpu_boot_status \
 501: 		CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2
 502: 1:
 503: 	wfe
 504: 	wfi
 505: 	b	1b
 506: SYM_FUNC_END(__no_granule_support)
 507: 
 508: SYM_FUNC_START_LOCAL(__primary_switch)
 509: 	adrp	x1, reserved_pg_dir
 510: 	adrp	x2, __pi_init_idmap_pg_dir
 511: 	bl	__enable_mmu
 512: 
 513: 	adrp	x1, early_init_stack
 514: 	mov	sp, x1
 515: 	mov	x29, xzr
 516: 	mov	x0, x20				// pass the full boot status
 517: 	mov	x1, x21				// pass the FDT
 518: 	bl	__pi_early_map_kernel		// Map and relocate the kernel
 519: 
 520: 	ldr	x8, =__primary_switched
 521: 	adrp	x0, KERNEL_START		// __pa(KERNEL_START)
 522: 	br	x8
 523: SYM_FUNC_END(__primary_switch)
 524: