← 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"

라인별 주석

빈 줄은 제외하고, 하나의 의미를 이루는 주석과 연속 선언만 묶었습니다.

1/* SPDX-License-Identifier: GPL-2.0-only */

이 파일 전체에 적용되는 라이선스를 기계가 읽을 수 있는 형태로 선언합니다. only이므로 GPL 2.0 조건만 적용되고, 임의로 이후 버전을 선택하는 or-later 파일이 아닙니다.

2-10/* Low-level CPU initialisation ... */

32비트 ARM의 arch/arm/kernel/head.S에서 계보가 시작되었음을 밝히고 주요 저자를 기록합니다. 실행 결과에는 영향을 주지 않지만 코드의 역사와 저작권 범위를 남기는 부분입니다.

12#include <linux/linkage.h>

SYM_FUNC_START, SYM_CODE_START처럼 ELF 심볼의 타입·가시성·정렬을 표현하는 매크로를 가져옵니다.

13-14#include <linux/init.h>
#include <linux/pgtable.h>

초기화 전용 섹션 표식과 공통 page-table 정의를 가져옵니다. 초기 부팅 코드와 임시 page table의 수명 경계를 기술할 때 사용됩니다.

16#include <asm/asm_pointer_auth.h>

AArch64 pointer authentication key 초기화에 필요한 assembler 매크로를 제공합니다. secondary CPU 경로의 ptrauth_keys_init_cpu가 여기서 연결됩니다.

17#include <asm/assembler.h>

adr_l, mov_q, str_l처럼 명령 한 개로 표현하기 어려운 주소·상수 연산과 cache helper를 모은 핵심 헤더입니다.

18#include <asm/boot.h>

bootloader가 지켜야 할 레지스터·메모리 계약과 early boot 상태값을 가져옵니다. head.S와 외부 부팅 주체 사이의 ABI 경계입니다.

19-20#include <asm/bug.h>
#include <asm/ptrace.h>

ASM_BUG()pt_regs stack frame 배치에 필요한 정의입니다. start_kernel이 잘못 반환했을 때의 trap과 초기 stack frame 작성에 쓰입니다.

21#include <asm/asm-offsets.h>

C 구조체 필드를 assembly가 접근할 수 있도록 빌드 중 생성된 수치 offset을 제공합니다. TSK_STACK, TSK_TI_CPU, CPU_BOOT_TASK가 대표적입니다.

22-23#include <asm/cache.h>
#include <asm/cputype.h>

cache line 유지보수와 CPU 식별값을 위한 정의입니다. MMU 전환 전후의 PoC clean/invalidate 및 MPIDR 처리에 사용됩니다.

24#include <asm/el2_setup.h>

EL2의 HCR, timer, trap 상태를 커널이 기대하는 값으로 만드는 init_el2_* 매크로를 가져옵니다.

25-26#include <asm/elf.h>
#include <asm/image.h>

ELF 배치와 arm64 Image header의 magic·flags·필드 형식을 정의합니다. 아래 64-byte header가 임의 구조가 아니라 공식 부팅 규격을 따르게 합니다.

27#include <asm/kernel-pgtable.h>

초기 identity map과 kernel map의 레벨 수, 크기, table 속성 등 부팅용 page-table 상수를 제공합니다.

28-31#include <asm/kvm_arm.h>
#include <asm/memory.h>
#include <asm/pgtable-hwdef.h>
#include <asm/page.h>

EL2/VHE 플래그, 물리·가상 주소 관계, 하드웨어 descriptor 비트, page 크기를 정의합니다. 주소 변환을 실제 register 값과 descriptor로 내릴 때 필요한 묶음입니다.

32#include <asm/scs.h>

Shadow Call Stack을 현재 task에 연결하는 scs_load_current를 제공합니다.

33-34#include <asm/smp.h>
#include <asm/sysreg.h>

secondary CPU bring-up 공유 자료구조와 system register field 정의를 가져옵니다. CurrentEL, SCTLR, ID_AA64MMFR* 판독의 기반입니다.

35-36#include <asm/stacktrace/frame.h>
#include <asm/thread_info.h>

초기 stack frame metadata와 task/thread_info 관련 offset을 제공합니다. unwind가 가능한 첫 frame을 만들 때 필요합니다.

37#include <asm/virt.h>

BOOT_CPU_MODE_EL1, BOOT_CPU_MODE_EL2, E2H flag처럼 커널의 가상화 실행 모드를 표현하는 값을 가져옵니다.

39#include "efi-header.S"

PE/COFF header 생성 매크로를 같은 디렉터리의 assembly 소스에서 포함합니다. UEFI가 raw Linux Image를 EFI executable로도 식별할 수 있게 하는 연결점입니다.

설명: 첫 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. 소스 조각이 링크되어 ARM64 Image의 진입 영역이 되는 과정
head.S 빌드 및 링크 구조 헤더와 어셈블리 소스가 전처리, 어셈블, 링크되어 ARM64 Image의 head text와 idmap text 영역을 만드는 과정 입력 소스 head.S Image header · primary entry · MMU 전환 asm/*.h sysreg · pgtable · linkage 매크로 efi-header.S PE/COFF header 조각 vmlinux.lds.S · 섹션 위치 CPP 조건부 매크로 확장 AS head.o 생성 LD 주소와 섹션 확정 arch/arm64/boot/Image 64-byte Image header .idmap.text MMU 전환을 덮는 identity-mapped code .text / .rodata / .data MMU-on 이후 실행되는 커널 본체 .bss PC 최초 진입 head.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

라인별 주석

41#if (PAGE_OFFSET & 0x1fffff) != 0

PAGE_OFFSET의 하위 21비트가 모두 0인지 검사합니다. 즉 kernel virtual base가 2 MiB 경계에 놓이는지 전처리 단계에서 확인합니다.

42-43#error PAGE_OFFSET must be at least 2MB aligned
#endif

조건이 참이면 빌드를 즉시 중단합니다. 잘못된 정렬로 early block mapping이 어긋나는 문제를 실행 시점까지 미루지 않습니다.

45-56/* Kernel startup entry point ... */

진입 ABI를 문서화합니다. MMU와 D-cache는 꺼져 있어야 하고, I-cache는 어느 상태든 허용되며, x0에는 FDT blob의 물리 주소가 들어와야 합니다. callee-saved register는 MMU가 켜질 때까지 임시 전역 변수처럼 사용됩니다.

57__HEAD

뒤따르는 bytes를 image 시작부의 head 섹션에 배치하고 필요한 정렬·심볼 속성을 적용합니다. bootloader가 점프하는 첫 주소와 linker 배치를 일치시킵니다.

58-60/* DO NOT MODIFY. Image header expected by Linux boot-loaders. */

아래 필드의 순서와 offset이 Linux arm64 boot protocol의 일부임을 경고합니다. 내부 구현용 구조체가 아니라 외부 bootloader가 직접 파싱하는 ABI입니다.

61efi_signature_nop

일반 AArch64 실행에서는 안전한 NOP 계열 명령으로 동작하면서, PE/COFF 경로에서는 EFI image 식별에 필요한 signature 위치를 만족합니다.

62b primary_entry

CPU가 header의 나머지 데이터 필드를 명령으로 실행하지 않도록 실제 커널 entry로 즉시 분기합니다. 이 명령 자체가 Image header의 두 번째 32-bit word입니다.

63.quad 0

RAM 시작점 기준 image load offset 필드입니다. 현재 형식에서는 0을 기록하며 little-endian 64-bit 값으로 공간을 정확히 8바이트 차지합니다.

64le64sym _kernel_size_le

linker가 계산한 유효 kernel image 크기를 little-endian 64-bit 값으로 넣습니다. bootloader가 image가 차지할 RAM 범위를 계산할 때 사용합니다.

65le64sym _kernel_flags_le

page size, 물리 배치 방식 등 arm64 Image protocol의 정보성 flag를 little-endian으로 기록합니다.

66-68.quad 0
.quad 0
.quad 0

향후 규격 확장을 위해 예약된 24바이트입니다. 지금은 반드시 0이며, 세 줄을 줄이거나 다른 데이터를 넣으면 뒤쪽 magic의 고정 offset이 깨집니다.

69.ascii ARM64_IMAGE_MAGIC

bootloader가 파일을 arm64 Linux Image로 판별하는 4-byte magic을 그대로 배치합니다. 문자열 종단 NUL을 붙이는 .asciz가 아니라 정확한 길이의 .ascii를 씁니다.

70.long .Lpe_header_offset

Image 시작점에서 PE header까지의 상대 offset을 32-bit 값으로 기록합니다. UEFI loader가 PE/COFF 구조를 찾아가는 포인터 역할을 합니다.

72__EFI_PE_HEADER

efi-header.S에서 정의된 매크로를 확장해 실제 PE/COFF header와 section 정보를 이어 붙입니다.

설명: 이 부분은 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. RAM의 Kernel Image에서 64-byte header를 확대해 본 배치
ARM64 Image header 확대도 RAM에 2MB 정렬된 커널 이미지와 첫 64바이트 Image header의 필드별 배치를 확대하여 연결한 그림 물리 주소 공간 Kernel Image Image + 0x0 · 64-byte header Image body 2MB aligned Image header 확대 0x00 efi_signature_nop 0x04 b primary_entry 0x08 text_offset 0x10 image_size 0x18 flags 0x20..0x37 reserved 0x38 magic 0x3c PE offset Bootloader 첫 두 word는 데이터가 아니라 CPU가 실행하는 AArch64 명령이다.

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)

라인별 주석

76-84/* x19 ... x20 ... x21 ... */

세 callee-saved register의 수명을 먼저 고정합니다. x19는 entry MMU/cache 상태, x20은 CPU boot mode, x21은 FDT 물리 주소이며 일반 scratch register로 재사용할 수 없습니다.

85SYM_CODE_START(primary_entry)

primary_entry 심볼의 시작을 선언합니다. 일반 C ABI 함수라기보다 특수 entry code이므로 code symbol 매크로를 사용합니다.

86bl record_mmu_state

현재 EL의 SCTLR을 읽어 진입 당시 MMU·D-cache·endianness 상태를 정리하고 결과를 x19에 남깁니다.

87bl preserve_boot_args

bootloader가 넘긴 x0-x3를 덮어쓰기 전에 전역 boot_args에 복사하고 FDT 주소를 x21에 보존합니다.

89-91adrp x1, early_init_stack
mov sp, x1
mov x29, xzr

early_init_stack이 있는 page 주소를 계산해 임시 stack pointer로 사용하고 frame pointer를 0으로 초기화합니다. 아직 일반 task stack이 준비되지 않은 구간입니다.

92adrp x0, __pi_init_idmap_pg_dir

초기 identity map의 root page-table 주소를 page-relative 방식으로 x0에 만듭니다. 현재 PC와 대상이 같은 position-independent image 안에 있다는 전제를 이용합니다.

93mov x1, xzr

__pi_create_init_idmap에 넘길 두 번째 인자를 0으로 설정합니다. 선택 flag가 없는 기본 idmap 생성 경로입니다.

94bl __pi_create_init_idmap

MMU를 켜는 순간 현재 실행 주소가 끊기지 않도록 초기 identity mapping을 작성합니다. 반환된 x0는 사용한 page-table 영역의 끝을 가리킵니다.

96-100/* If the page tables ... invalidate ... */

MMU-off 상태에서 non-cacheable store로 table을 썼더라도 speculative access가 같은 line을 cache에 올렸을 수 있음을 설명합니다. 따라서 table publish 전에 stale cache line을 제거해야 합니다.

101cbnz x19, 0f

x19 != 0이면 진입 당시 MMU와 D-cache가 켜진 경로이므로 label 0의 idmap text clean으로 이동합니다. 0이면 바로 아래 table invalidate 경로를 실행합니다.

102dmb sy

page-table store가 모든 관찰자에게 순서상 먼저 완료되도록 full-system data memory barrier를 둡니다. 이어지는 invalidate가 store보다 앞서 보이지 않게 합니다.

103-106mov x1, x0
adrp x0, __pi_init_idmap_pg_dir
adr_l x2, dcache_inval_poc
blr x2

반환받은 끝 주소를 x1에 두고 root 시작 주소를 x0에 다시 만든 뒤, 그 구간을 PoC까지 invalidate하는 helper 주소를 x2로 불러 간접 호출합니다.

107b 1f

MMU-off table invalidate를 마친 뒤 MMU-on 전용 text clean 블록을 건너뛰고 두 경로가 합쳐지는 label 1로 갑니다.

109-113/* If we entered with the MMU and caches on ... */

MMU-on으로 진입했다가 곧 MMU를 끌 수 있으므로, 이후 물리 fetch할 idmap code를 PoC까지 clean해야 한다는 이유를 설명합니다.

114-1170: adrp x0, __idmap_text_start
adr_l x1, __idmap_text_end
adr_l x2, dcache_clean_poc
blr x2

__idmap_text_start..end 범위를 잡아 PoC까지 clean합니다. dirty instruction bytes가 RAM에 반영되지 않은 채 MMU-off alias에서 실행되는 일을 막습니다.

119-1211: mov x0, x19
bl init_kernel_el
mov x20, x0

진입 상태를 인자로 EL 초기화 함수에 넘기고, 반환된 EL1/EL2 및 E2H 정보를 장기 보존 register인 x20에 저장합니다.

123-128/* The following calls CPU setup code ... */

다음 호출이 MIDR별 processor setup을 수행해 TCR과 cache/MMU 관련 register를 준비한다는 함수 계약입니다. 세부 구현은 arch/arm64/mm/proc.S에 있습니다.

129bl __cpu_setup

현재 CPU 구현에 맞는 TCR, MAIR 및 MMU 전제 register를 설정합니다. 이 호출 뒤에는 page table base를 설치하고 MMU를 켤 수 있는 상태가 됩니다.

130b __primary_switch

link register를 새로 저장하지 않는 tail branch로 primary MMU 전환 경로에 제어를 넘깁니다. 정상적으로는 primary_entry로 돌아오지 않습니다.

131SYM_CODE_END(primary_entry)

assembler와 ELF 도구에 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의 시간축과 x19·x20·x21의 생존 구간
primary_entry 실행 시간축 primary_entry에서 함수 호출이 진행되는 순서와 x19, x20, x21 레지스터 값이 유지되는 범위 실행 시간 → record_mmu_statex19에 진입 상태 저장 preserve_boot_argsx21과 boot_args 저장 create_init_idmap현재 PC 범위 매핑 init_kernel_elx20 = boot mode __cpu_setupTCR/SCTLR 준비→ __primary_switch x19 진입 당시 SCTLR 상태 · start_kernel까지 유지 x20 EL1/EL2 + E2H boot status · __primary_switch까지 x21 FDT physical address · start_kernel까지 sp early_init_stack · MMU 전 helper 호출용 MMU off / idmap 준비 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)

라인별 주석

134SYM_CODE_START_LOCAL(record_mmu_state)

외부로 export하지 않는 local code symbol을 시작합니다. 호출 규약보다 entry state를 다루는 특수 코드라는 성격을 표시합니다.

135mrs x19, CurrentEL

현재 exception level을 system register에서 읽습니다. 이 순간의 x19는 최종 상태값이 아니라 EL 판정을 위한 임시값입니다.

136-139cmp x19, #CurrentEL_EL2
mrs x19, sctlr_el1
b.ne 0f
mrs x19, sctlr_el2

기본으로 SCTLR_EL1을 읽고, CurrentEL이 EL2였을 때만 SCTLR_EL2로 덮어씁니다. 이후 x19에는 실제로 실행 중인 EL의 M/C/EE 제어 비트가 있습니다.

1400:

EL1과 EL2의 SCTLR 선택 경로가 합쳐지는 local numeric label입니다. 앞의 b.ne 0f에서 f는 현재 위치보다 앞쪽이 아니라 다음에 나오는 label 0을 뜻합니다.

141CPU_LE(tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f)

little-endian 커널에서 EE 비트가 1이면 현재 byte order가 기대와 반대이므로 보정 경로 1로 갑니다.

142CPU_BE(tbz x19, #SCTLR_ELx_EE_SHIFT, 1f)

big-endian 커널에서는 반대로 EE 비트가 0일 때 보정 경로로 갑니다. 빌드 endian에 따라 두 매크로 중 하나만 유효한 코드로 남습니다.

143tst x19, #SCTLR_ELx_C

D-cache enable 비트 C를 검사해 condition flag의 Z를 설정합니다. C가 0이면 Z=1입니다.

144and x19, x19, #SCTLR_ELx_M

SCTLR 전체에서 MMU enable 비트 M만 남깁니다. 이 명령은 ands가 아니므로 앞의 tst가 만든 condition flag는 보존됩니다.

145csel x19, xzr, x19, eq

D-cache가 꺼져 Z=1이면 x19=0, 켜져 있으면 앞에서 추출한 M 비트를 유지합니다. 결과적으로 뒤의 cache 경로는 하나의 값으로 판정할 수 있습니다.

146ret

endianness가 이미 맞는 정상 경로를 호출자에게 반환합니다. x19는 진입 상태 요약값으로 남습니다.

148-153/* Set the correct endianness early ... */

byte order를 바꾸는 동안 active idmap을 반대 endian으로 해석하지 않도록 MMU도 반드시 꺼야 한다는 설계 이유를 적습니다.

1541: eor x19, x19, #SCTLR_ELx_EE

현재 EE 비트를 반전해 커널 빌드가 기대하는 byte order로 맞춥니다.

155bic x19, x19, #SCTLR_ELx_M

동시에 M 비트를 지워 MMU를 끌 값을 만듭니다. 주소 변환이 켜진 상태에서 endian만 바뀌는 위험한 중간 상태를 만들지 않습니다.

156b.ne 2f

line 136의 CurrentEL 비교 flag를 이용합니다. EL2가 아니었다면 label 2의 EL1 register write 경로로 분기합니다.

157-159pre_disable_mmu_workaround
msr sctlr_el2, x19
b 3f

EL2용 CPU erratum workaround를 적용한 뒤 보정된 값을 SCTLR_EL2에 쓰고 공통 동기화 지점으로 이동합니다.

160-1612: pre_disable_mmu_workaround
msr sctlr_el1, x19

EL1 진입이었다면 동일한 사전 workaround 뒤 SCTLR_EL1을 갱신합니다.

1623: isb

SCTLR 변경이 이후 instruction fetch에 확실히 반영되도록 instruction synchronization barrier를 실행합니다.

163-165mov x19, xzr
ret
SYM_CODE_END(record_mmu_state)

보정 경로에서는 MMU가 확실히 꺼졌으므로 상태 요약을 0으로 만들고 반환한 뒤 심볼을 닫습니다.

설명: 이 함수는 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. CurrentEL과 SCTLR 비트로 x19를 만드는 실제 분기
record_mmu_state 결정 트리 현재 exception level에 따라 SCTLR EL1 또는 EL2를 읽고 endian, cache, MMU 비트로 x19 값을 결정하는 흐름 mrs x19, CurrentEL현재 EL 판정 EL1EL2 mrs x19, sctlr_el1M · C · EE 비트 mrs x19, sctlr_el2M · C · EE 비트 EE == kernel endian? C bit == 1? C = 0C = 1, endian 정상endian 불일치 x19 = 0 · MMU-off 경로 MMU off + EE 보정 후 재진입 x19 = SCTLR · clean 경로

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)

라인별 주석

170SYM_CODE_START_LOCAL(preserve_boot_args)

boot argument 보존 helper의 local code symbol을 시작합니다.

171mov x21, x0

FDT의 물리 주소를 장기 생존 register x21에 복사합니다. 이후 함수 호출들이 x0를 인자로 사용해도 FDT를 잃지 않습니다.

173adr_l x0, boot_args

position-independent long-address 매크로로 전역 boot_args의 주소를 x0에 만듭니다.

174stp x21, x1, [x0]

entry 당시 x0였던 FDT 값과 x1을 첫 16바이트에 한 쌍으로 저장합니다.

175stp x2, x3, [x0, #16]

x2x3을 다음 16바이트에 저장해 총 32바이트의 boot argument snapshot을 완성합니다.

177cbnz x19, 0f

entry 당시 MMU/cache가 켜져 있었다면 MMU-off 전용 cache invalidate를 건너뛰고 상태 기록 경로로 갑니다.

178-179dmb sy
/* needed before dc ivac with MMU off */

방금 수행한 pair store가 완료된 뒤 invalidate가 일어나도록 순서를 강제합니다. MMU-off alias에서 stale line이 남는 문제를 피합니다.

181add x1, x0, #0x20

invalidate 범위의 끝을 boot_args + 32로 계산합니다. x0는 시작 주소, x1은 배타적 끝 주소가 됩니다.

182b dcache_inval_poc

link register를 바꾸지 않는 tail call입니다. helper가 반환하면 곧바로 원래 preserve_boot_args 호출자에게 돌아갑니다.

1830: str_l x19, mmu_enabled_at_boot, x0

MMU-on 진입 경로에서는 상태값을 전역 mmu_enabled_at_boot에 저장합니다. 마지막 x0는 주소 계산용 scratch register입니다.

184-185ret
SYM_CODE_END(preserve_boot_args)

호출자에게 복귀하고 helper 심볼의 범위를 닫습니다.

설명: 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. 부트 레지스터가 x21과 32-byte boot_args 영역으로 복사되는 과정
부트 인자 보존 구조 x0부터 x3까지의 부트 인자가 boot_args 메모리와 x21 레지스터에 복사되고 캐시 일관성을 처리하는 과정 부트로더가 넘긴 레지스터 x0FDT physical x1 · reserved x2 · reserved x3 · reserved x21 = x0start_kernel까지 유지 boot_args 메모리 +0x00 · x21(FDT)8 bytes +0x08 · x1 +0x10 · x2 +0x18 · x3 한 cache line 안의연속 32 bytes MMU off: dmb sydcache_inval_poc MMU onx19 전역 저장

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)

라인별 주석

195.macro init_cpu_task tsk, tmp1, tmp2

task pointer와 두 scratch register를 인자로 받는 assembly 매크로를 정의합니다. primary와 secondary CPU가 동일한 task-context 초기화 순서를 공유합니다.

196msr sp_el0, \tsk

현재 task pointer를 SP_EL0에 기록합니다. 커널이 EL1 stack을 쓰는 동안 이 register를 current task 보관 용도로 사용합니다.

198-200ldr \tmp1, [\tsk, #TSK_STACK]
add sp, \tmp1, #THREAD_SIZE
sub sp, sp, #PT_REGS_SIZE

task의 stack base를 읽고 stack top으로 이동한 다음 pt_regs가 들어갈 공간을 미리 뺍니다. AArch64 stack이 낮은 주소 방향으로 자라는 점을 반영합니다.

202-205stp xzr, xzr, [sp, #S_STACKFRAME]
mov \tmp1, #FRAME_META_TYPE_FINAL
str \tmp1, [sp, #S_STACKFRAME_TYPE]
add x29, sp, #S_STACKFRAME

초기 frame의 FP/LR 자리를 0으로 만들고 이 frame이 unwind chain의 끝임을 표시한 뒤 x29가 그 frame record를 가리키게 합니다.

207scs_load_current

현재 task에 할당된 Shadow Call Stack pointer를 CPU register에 적재합니다. SCS가 꺼진 빌드에서는 적절한 빈 매크로로 사라집니다.

209-212adr_l \tmp1, __per_cpu_offset
ldr w\tmp2, [\tsk, #TSK_TI_CPU]
ldr \tmp1, [\tmp1, \tmp2, lsl #3]
set_this_cpu_offset \tmp1

task가 속한 CPU 번호로 __per_cpu_offset[cpu]을 읽고 현재 CPU의 per-CPU base register를 설정합니다. 이후 this_cpu 접근이 올바른 영역을 가리킵니다.

213.endm

init_cpu_task 매크로 정의를 끝냅니다.

215-219/* executed with the MMU enabled; x0 = __pa(KERNEL_START) */

이후 코드는 이미 높은 kernel virtual address에서 실행되며, x0에는 kernel 시작점의 물리 주소가 들어온다는 진입 조건입니다.

220SYM_FUNC_START_LOCAL(__primary_switched)

MMU-on primary 경로의 local function symbol을 시작합니다.

221-222adr_l x4, init_task
init_cpu_task x4, x5, x6

boot CPU의 current task를 정적 init_task로 정하고, 앞에서 정의한 매크로로 stack·frame·SCS·per-CPU base를 한꺼번에 초기화합니다.

224-226adr_l x8, vectors
msr vbar_el1, x8
isb

EL1 exception vector의 가상 주소를 VBAR_EL1에 설치하고 ISB로 즉시 효력을 보장합니다. 이후 exception은 정식 커널 vector로 진입합니다.

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

현재 FP/LR을 stack에 push하고 새 frame pointer를 만듭니다. 여기부터 일반 함수 호출과 stack unwinding이 가능한 형태를 갖춥니다.

231str_l x21, __fdt_pointer, x5

초기부터 보존한 FDT 물리 주소를 C 코드가 읽는 전역 __fdt_pointer에 저장합니다. x5는 주소 계산용 scratch입니다.

233-235adrp x4, _text
sub x4, x4, x0
str_l x4, kimage_voffset, x5

현재 보이는 kernel virtual _text 주소에서 전달받은 물리 KERNEL_START를 빼 kimage_voffset을 계산하고 전역에 저장합니다.

237-238mov x0, x20
bl set_cpu_boot_mode_flag

보존해 둔 EL1/EL2 boot mode를 인자로 넘겨 전역 __boot_cpu_mode의 해당 slot에 기록합니다.

240-242#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
bl kasan_early_init
#endif

KASAN이 필요한 빌드에서만 일반 allocator보다 먼저 shadow memory의 early mapping을 초기화합니다.

243-244mov x0, x20
bl finalise_el2

boot mode를 다시 전달해 EL2 구성을 최종 확정합니다. 하드웨어가 허용하면 VHE를 우선하고, 그렇지 않으면 nVHE 상태를 정리합니다.

245ldp x29, x30, [sp], #16

앞에서 만든 호출 frame을 pop해 FP/LR을 복원합니다.

246bl start_kernel

generic kernel 초기화의 C entry로 들어갑니다. scheduler, memory, IRQ, initcall 등 나머지 부팅은 여기서 시작됩니다.

247-248ASM_BUG()
SYM_FUNC_END(__primary_switched)

start_kernel은 반환하지 않는 함수이므로 돌아오면 즉시 BUG trap을 발생시킵니다. 이어서 심볼 범위를 닫습니다.

설명: 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. 하나의 물리 Image가 idmap과 높은 커널 VA에 동시에 매핑되는 모습
물리 주소와 커널 가상 주소 매핑 물리 메모리의 커널 이미지가 identity map과 KIMAGE VADDR에 매핑되고 kimage voffset으로 두 주소의 차이를 저장하는 구조 물리 주소 공간 PA(_text) Image body PA(_end) RAM에 적재된 실제 bytes 커널 가상 주소 공간 KIMAGE_VADDR kernel body VA(_end) kimage_voffset VA(_text) - PA(_text) TTBR0 · identity map전환 순간 PA == VA 별칭 TTBR1 · kernel map__primary_switched 실행 같은 물리 페이지를 서로 다른 VA로 참조한다.

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)

라인별 주석

270-273SYM_FUNC_START(init_kernel_el)
mrs x1, CurrentEL
cmp x1, #CurrentEL_EL2
b.eq init_el2

함수를 시작하고 현재 EL을 판정합니다. EL2이면 별도 초기화 경로로 가고, 아니면 바로 아래 EL1 경로로 fall-through합니다.

275SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)

같은 함수 내부에 local EL1 entry label을 만듭니다. 코드 크기와 unwind 범위는 바깥 init_kernel_el에 속합니다.

276-279mov_q x0, INIT_SCTLR_EL1_MMU_OFF
pre_disable_mmu_workaround
msr sctlr_el1, x0
isb

EL1 SCTLR을 커널이 정의한 MMU-off 기준값으로 만들고 CPU workaround를 거쳐 기록한 뒤, ISB로 새 제어 상태를 동기화합니다.

280-282mov_q x0, INIT_PSTATE_EL1
msr spsr_el1, x0
msr elr_el1, lr

eret 뒤 적용될 PSTATE와 복귀 PC를 준비합니다. 호출 당시 LR을 ELR에 넣어 EL 변경 뒤에도 원래 호출 흐름으로 돌아가게 합니다.

283-284mov w0, #BOOT_CPU_MODE_EL1
eret

반환값을 EL1 boot mode로 정하고 exception return을 수행합니다. 일반 ret과 달리 SPSR/ELR의 상태를 함께 복원합니다.

286-287SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
msr elr_el2, lr

EL2 내부 label을 시작하고 최종적으로 EL1로 내려간 뒤 이어갈 주소를 ELR_EL2에 저장합니다.

289-294cbz x0, 0f
adrp x0, __hyp_idmap_text_start
adr_l x1, __hyp_text_end
adr_l x2, dcache_clean_poc
blr x2

MMU/cache-on 상태로 EL2에 들어온 경우에만 HYP idmap text를 PoC까지 clean합니다. MMU를 끈 뒤 같은 bytes를 물리 alias로 실행해도 최신 명령을 보게 합니다.

296-300mov_q x0, INIT_SCTLR_EL2_MMU_OFF
pre_disable_mmu_workaround
msr sctlr_el2, x0
isb
0:

EL2 SCTLR을 MMU-off 기준값으로 바꾸고 동기화합니다. 원래 MMU가 꺼져 있던 경로도 label 0에서 합류합니다.

302-303init_el2_hcr HCR_HOST_NVHE_FLAGS
init_el2_state

HCR을 host nVHE 기준으로 설정하고 timer, trap, translation 등 EL2 system state를 알려진 값으로 초기화합니다.

305-308adr_l x0, __hyp_stub_vectors
msr vbar_el2, x0
isb

정식 hypervisor vector가 준비되기 전 사용할 stub vector를 VBAR_EL2에 설치하고 동기화합니다.

310mov_q x1, INIT_SCTLR_EL1_MMU_OFF

EL1로 내려갈 때 적용할 기준 SCTLR 값을 x1에 준비합니다.

312-314mrs x0, hcr_el2
and x0, x0, #HCR_E2H
cbz x0, 2f

HCR의 E2H 비트를 읽어 VHE host mode인지 확인합니다. E2H가 0이면 label 2의 일반 EL1 register 접근으로 갑니다.

316-319msr_s SYS_SCTLR_EL12, x1
mov x2, #BOOT_CPU_FLAG_E2H
b 3f

VHE에서는 EL2에서 EL1 system register를 보는 alias인 SCTLR_EL12에 값을 쓰고, 반환 boot status에 합칠 E2H flag를 x2에 둡니다.

321-3242: msr sctlr_el1, x1
mov x2, xzr
3:

nVHE에서는 실제 SCTLR_EL1을 직접 설정하고 E2H flag를 0으로 둡니다. 두 경로는 label 3에서 합류합니다.

325-326mov x0, #INIT_PSTATE_EL1
msr spsr_el2, x0

EL2의 eret이 EL1의 원하는 interrupt mask와 stack mode로 내려가도록 saved PSTATE를 설정합니다.

328-330mov w0, #BOOT_CPU_MODE_EL2
orr x0, x0, x2
eret

EL2 진입 mode와 선택적 E2H flag를 하나의 반환값으로 합친 뒤 EL1 호출 지점으로 exception return합니다.

331SYM_FUNC_END(init_kernel_el)

EL1·EL2 두 내부 경로를 포함하는 전체 함수의 심볼 범위를 닫습니다.

설명: 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. EL1과 EL2의 서로 다른 진입 경로가 EL1 커널 실행으로 합쳐지는 모습
EL1 EL2 초기화 경로 EL1 진입과 EL2 진입 경로가 각각 시스템 레지스터를 초기화한 후 eret을 통해 EL1 커널 경로로 합쳐지는 과정 mrs x1, CurrentEL EL1EL2 SCTLR_EL1 = INIT_SCTLR_EL1_MMU_OFFEL1 제어 레지스터를 알려진 상태로 설정 SPSR_EL1 · ELR_EL1복귀 PSTATE와 호출자 주소 eret HCR_EL2 · VBAR_EL2 · hyp stateVHE/nVHE와 stub vector 준비 SCTLR_EL2/EL1 · SPSR_EL2 · ELR_EL2EL1로 내려갈 제어 상태 완성 eret EL1 kernel path · x20 = boot mode | E2H

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)

라인별 주석

333-336/* holding pen for secondary cores */

primary CPU가 공유 자료구조와 scheduler 상태를 준비할 때까지 secondary core가 진행하지 못하도록 대기시키는 진입 경로임을 설명합니다.

337-339SYM_FUNC_START(secondary_holding_pen)
mov x0, xzr
bl init_kernel_el

holding-pen entry를 시작하고 entry MMU state를 0으로 넘겨 EL1/EL2 상태를 정리합니다. 반환 w0에는 boot mode가 있습니다.

340-342mrs x2, mpidr_el1
mov_q x1, MPIDR_HWID_BITMASK
and x2, x2, x1

현재 CPU의 MPIDR에서 affinity를 나타내는 hardware ID 비트만 남깁니다. release variable과 비교할 고유 식별값입니다.

343-348adr_l x3, secondary_holding_pen_release
pen: ldr x4, [x3]
cmp x4, x2
b.eq secondary_startup
wfe
b pen

공유 release 값이 자기 MPIDR과 같아질 때까지 읽고 비교합니다. 다르면 wfe로 저전력 대기한 뒤 event를 받으면 다시 검사하며, 일치할 때만 공통 startup으로 나갑니다.

349SYM_FUNC_END(secondary_holding_pen)

holding-pen 심볼의 끝입니다.

351-359SYM_FUNC_START(secondary_entry)
mov x0, xzr
bl init_kernel_el
b secondary_startup
SYM_FUNC_END(secondary_entry)

kernel이 CPU hotplug 등으로 core를 직접 online할 때 쓰는 짧은 entry입니다. holding pen 비교 없이 EL 상태만 정리하고 공통 startup으로 바로 갑니다.

361-365SYM_FUNC_START_LOCAL(secondary_startup)
mov x20, x0

secondary 공통 low-level 경로를 시작하고 init_kernel_el이 반환한 boot mode를 callee-saved x20에 보존합니다.

367-371#ifdef CONFIG_ARM64_VA_BITS_52
alternative_if ARM64_HAS_VA52
bl __cpu_secondary_check52bitva
alternative_else_nop_endif
#endif

52-bit VA 빌드에서 CPU capability에 따라 secondary 검사를 alternative patching으로 삽입합니다. 필요 없는 CPU 경로는 동등 길이 NOP로 대체됩니다.

373bl __cpu_setup

이 secondary CPU의 TCR, MAIR, cache/MMU 관련 system register를 CPU 모델에 맞게 준비합니다.

374-376adrp x1, swapper_pg_dir
adrp x2, idmap_pg_dir
bl __enable_mmu

최종 kernel mapping root와 identity-map root를 인자로 준비하고 secondary CPU의 MMU를 켭니다.

377-379ldr x8, =__secondary_switched
br x8
SYM_FUNC_END(secondary_startup)

MMU-on virtual address의 continuation을 literal로 읽어 절대 분기합니다. low-level startup은 돌아오지 않으므로 br을 사용합니다.

381-384.text
SYM_FUNC_START_LOCAL(__secondary_switched)
mov x0, x20
bl set_cpu_boot_mode_flag

정상 kernel text로 돌아와 secondary boot mode를 전역 slot에 기록합니다.

386-387mov x0, x20
bl finalise_el2

이 CPU의 EL2/VHE 상태를 primary와 동일한 커널 정책으로 최종 정리합니다.

389str_l xzr, __early_cpu_boot_status, x3

이 CPU가 MMU 전환을 통과했음을 나타내도록 early failure status를 0으로 지웁니다.

390-392adr_l x5, vectors
msr vbar_el1, x5
isb

secondary CPU에도 정식 EL1 exception vector를 설치하고 동기화합니다. VBAR은 CPU-local system register라 각 CPU에서 설정해야 합니다.

394-396adr_l x0, secondary_data
ldr x2, [x0, #CPU_BOOT_TASK]
cbz x2, __secondary_too_slow

primary가 준비한 secondary task pointer를 읽습니다. 아직 0이면 안전한 task/stack이 없으므로 계속 진행하지 않고 park 경로로 갑니다.

398init_cpu_task x2, x1, x3

해당 secondary의 current task, stack, frame, SCS, per-CPU base를 설정합니다.

400-402#ifdef CONFIG_ARM64_PTR_AUTH
ptrauth_keys_init_cpu x2, x3, x4, x5
#endif

pointer authentication이 활성화된 경우 이 CPU가 사용할 key register를 초기화합니다. key register도 CPU-local 상태입니다.

404-406bl secondary_start_kernel
ASM_BUG()
SYM_FUNC_END(__secondary_switched)

generic SMP C 경로로 진입합니다. 정상적으로 반환하지 않으므로 돌아오면 BUG를 발생시키고 함수 범위를 닫습니다.

설명: 보조 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. primary CPU와 secondary CPU 사이의 release 신호와 상태 전이
Secondary CPU 부팅 시퀀스 Primary CPU가 공유 page table과 secondary data를 준비하고 event를 보내 secondary CPU가 holding pen에서 빠져나와 MMU를 켜고 online 되는 흐름 Primary CPU Secondary CPU time ↓ kernel page tables공유 mapping 준비 holding penMPIDR 비교 · wfe secondary_data.taskstack과 task pointer 기록 holding pen아직 release 불일치 release MPIDR + sev대상 CPU를 깨우는 event release signal init_kernel_el → __cpu_setupCPU-local sysreg 초기화 __enable_mmusecondary_start_kernel → online CPU online observedmask · scheduler에 반영

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

라인별 주석

408-412SYM_FUNC_START_LOCAL(__secondary_too_slow)
wfe
wfi
b __secondary_too_slow
SYM_FUNC_END(__secondary_too_slow)

task 준비가 끝나기 전에 도착한 secondary를 영구 park합니다. event와 interrupt를 기다리며 전력을 줄이지만, 깨어나도 다시 같은 loop로 돌아가 잘못된 stack으로 진행하지 않습니다.

414-417/* Sets the __boot_cpu_mode flag ... */

w0로 받은 EL1/EL2 boot mode를 공유 배열의 어느 slot에 기록할지 설명하는 함수 계약입니다.

418-419SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
adr_l x1, __boot_cpu_mode

helper를 시작하고 boot-mode 저장 영역의 첫 slot 주소를 계산합니다.

420-422cmp w0, #BOOT_CPU_MODE_EL2
b.ne 1f
add x1, x1, #4

EL2로 부팅한 CPU이면 두 번째 32-bit slot을 선택하도록 주소에 4를 더합니다. EL1이면 첫 slot을 그대로 씁니다.

423-4251: str w0, [x1]
ret
SYM_FUNC_END(set_cpu_boot_mode_flag)

선택한 slot에 mode를 저장하고 반환한 뒤 심볼을 닫습니다. 여러 CPU의 관측 결과로 시스템 boot mode 일관성을 판단할 수 있습니다.

427-435/* update_early_cpu_boot_status ... */

MMU-off CPU가 실패 상태를 공유 메모리에 기록하며 두 scratch register를 파괴한다는 매크로 계약입니다.

437.macro update_early_cpu_boot_status status, tmp1, tmp2

상태 상수와 주소·값용 scratch register 두 개를 인자로 받는 매크로를 정의합니다.

438mov \tmp2, #\status

호출자가 지정한 실패 비트 조합을 실제 저장할 register에 materialize합니다.

439-440adr_l \tmp1, __early_cpu_boot_status
str \tmp2, [\tmp1]

공유 early status의 주소를 계산해 실패값을 저장합니다. 아직 MMU-off이므로 이후 cache 가시성 처리가 중요합니다.

441dmb sy

status store가 전역 관찰 순서에서 cache maintenance보다 먼저 완료되도록 full-system barrier를 둡니다.

442dc ivac, \tmp1

해당 VA/PA alias에 남아 있을 수 있는 stale cache line을 invalidate해 다른 CPU가 메모리의 새 실패값을 읽을 수 있게 합니다.

443.endm

early status 기록 매크로 정의를 끝냅니다.

설명: 부팅 성공 경로만 있으면 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. MMU-off CPU가 실패 코드를 메모리에 남기고 다른 CPU가 읽는 과정
Early CPU 실패 상태의 가시성 실패한 CPU가 상태 값을 저장하고 메모리 배리어와 캐시 무효화를 수행한 뒤 다른 CPU가 그 값을 읽는 구조 Failing CPUNO_GRAN · 52_BIT_VA · TOO_SLOW str status, [address]MMU off 상태의 physical store 공유 물리 메모리 __early_cpu_boot_statusSTUCK | REASON bits __boot_cpu_modeEL1 slot · EL2 slot 다른 CPU가 읽어야 하는 cache line dmb sy → dc ivacstore 완료 후 stale line 제거 Observer CPU실패 reason을 읽어 로그·복구 판단 wfe · wfi · park

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)

라인별 주석

445-457/* Enable the MMU ... */

입력 register 계약을 정의합니다. x0는 켤 SCTLR 값, x1은 TTBR1 kernel root, x2는 TTBR0 idmap root입니다. 복귀 주소까지 idmap에 포함되어야 하며 granule 미지원 CPU는 park합니다.

458.section ".idmap.text","a"

함수를 allocatable identity-map text 섹션에 배치합니다. MMU가 켜지는 경계에서 같은 숫자의 VA와 PA로 계속 fetch할 수 있게 합니다.

459SYM_FUNC_START(__enable_mmu)

MMU enable 함수의 ELF function symbol을 시작합니다.

460mrs x3, ID_AA64MMFR0_EL1

CPU의 memory-model feature register를 읽습니다. page granule 지원 범위가 이 register에 인코딩되어 있습니다.

461ubfx x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4

빌드가 선택한 granule에 대응하는 4-bit TGRAN field만 unsigned bitfield extract로 뽑습니다.

462-463cmp x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN
b.lt __no_granule_support

field 값이 지원 범위의 하한보다 작으면 정상 translation table을 사용할 수 없으므로 실패 park 경로로 갑니다.

464-465cmp x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX
b.gt __no_granule_support

지원 범위의 상한도 확인합니다. architecture가 예약하거나 미지원으로 정의한 encoding을 통과시키지 않습니다.

466phys_to_ttbr x2, x2

idmap root의 물리 주소를 TTBR register가 요구하는 encoding으로 변환합니다. PA bit 배치와 CnP 같은 TTBR 형식 차이를 매크로가 흡수합니다.

467msr ttbr0_el1, x2

낮은 주소의 identity mapping에 사용할 root를 TTBR0_EL1에 설치합니다. 현재 함수와 복귀 지점의 연속성을 보장하는 table입니다.

468load_ttbr1 x1, x1, x3

kernel high-VA mapping root를 TTBR1_EL1에 설치합니다. 필요한 barrier와 CPU별 TTBR 처리에 x3를 scratch로 사용합니다.

470set_sctlr_el1 x0

준비한 SCTLR 값을 기록해 M 비트를 포함한 MMU/cache 제어 상태를 활성화합니다. 매크로 내부의 동기화까지 끝난 다음부터 주소 변환이 적용됩니다.

472-473ret
SYM_FUNC_END(__enable_mmu)

idmap으로 덮인 LR 주소에 복귀하고 함수 심볼을 닫습니다. caller가 idmap 밖에 있으면 이 ret 직후 instruction abort가 납니다.

설명: 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. 낮은 VA와 높은 커널 VA가 서로 다른 TTBR을 거쳐 같은 RAM으로 가는 구조
TTBR0와 TTBR1 주소 변환 구조 낮은 identity mapped 주소는 TTBR0를 통하고 높은 커널 가상 주소는 TTBR1을 통과해 page table walk 후 물리 RAM을 가리키는 구조 CPU virtual address low VA · .idmap.textVA 숫자 == 현재 실행 PA high VA · KIMAGE_VADDR커널 text의 정상 실행 주소 TTBR0_EL1init_idmap_pg_dir TTBR1_EL1kernel pg_dir translation table walk PGD → PUD → PMD → PTE PGD → PUD → PMD → PTE physical output RAM · Kernel Image 두 VA 경로가 같은 물리 페이지를 가리킴 set_sctlr_el1 · SCTLR.M = 1 이 명령 이후부터 위의 TTBR 선택과 table walk가 적용된다. 현재 PC가 idmap에 포함되어 있어야 다음 명령을 fetch할 수 있다.

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)

라인별 주석

475-476#ifdef CONFIG_ARM64_VA_BITS_52
SYM_FUNC_START(__cpu_secondary_check52bitva)

52-bit VA 커널에서만 secondary CPU feature 검사 함수를 빌드합니다. 48-bit 이하 빌드에는 이 코드 자체가 없습니다.

477-480#ifndef CONFIG_ARM64_LPA2
mrs_s x0, SYS_ID_AA64MMFR2_EL1
and x0, x0, ID_AA64MMFR2_EL1_VARange_MASK
cbnz x0, 2f

LPA2를 쓰지 않는 52-bit VA 구성에서는 ID_AA64MMFR2_EL1.VARange를 읽습니다. 지원 비트가 0이 아니면 label 2의 성공 return으로 갑니다.

481-485#else
mrs x0, id_aa64mmfr0_el1
sbfx x0, x0, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
cmp x0, #ID_AA64MMFR0_EL1_TGRAN_LPA2
b.ge 2f

LPA2 구성에서는 선택 granule의 signed 4-bit capability encoding을 추출해 LPA2 이상인지 비교합니다. 만족하면 정상 복귀합니다.

486#endif

VARange 방식과 LPA2 granule 방식의 compile-time 분기를 닫습니다. 여기까지 fall-through했다면 CPU가 요구 기능을 지원하지 않습니다.

488-489update_early_cpu_boot_status \
CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1

공유 early status에 커널 내부 정지와 52-bit VA 미지원 이유를 함께 기록합니다. x0, x1은 매크로 scratch로 파괴됩니다.

490-4921: wfe
wfi
b 1b

실패 CPU를 event/interrupt 저전력 대기 loop에 영구 정지시킵니다. 깨어나더라도 뒤로 분기해 kernel state로 진입하지 않습니다.

494-4962: ret
SYM_FUNC_END(__cpu_secondary_check52bitva)
#endif

feature 검사를 통과한 CPU만 호출자에게 돌아갑니다. 함수와 52-bit VA 조건부 컴파일 영역을 닫습니다.

498-499SYM_FUNC_START_LOCAL(__no_granule_support)
/* CPU can't boot and is stuck */

선택된 translation granule을 지원하지 않는 CPU의 공통 local failure entry입니다. 다른 granule로 동적 fallback하지 않습니다.

500-501update_early_cpu_boot_status \
CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2

공유 status에 NO_GRAN 원인을 기록해 primary나 진단 코드가 정지 이유를 구분할 수 있게 합니다.

502-5051:
wfe
wfi
b 1b

상태 기록 뒤 CPU를 영구 park합니다. page-table format 자체가 맞지 않으므로 이후 명령을 실행하는 것보다 멈추는 것이 유일하게 안전합니다.

506SYM_FUNC_END(__no_granule_support)

granule failure 심볼 범위를 닫습니다.

설명: 지원하지 않는 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. CPU feature register에서 MMU 가능 여부를 판정하는 분기
52비트 VA와 granule 검사 결정 트리 커널 설정과 CPU feature register를 비교해 52비트 가상 주소 및 page granule 사용 가능 여부를 판단하고 실패 시 CPU를 정지시키는 흐름 Kernel build configurationVA_BITS · PAGE_SIZE · LPA2 CONFIG_ARM64_VA_BITS_52?52-bit 빌드인가 NoYes TGRAN supported?ID_AA64MMFR0_EL1 VARange/LPA2?ID_AA64MMFR2_EL1+ TGRAN check YesYes __enable_mmu검증된 TCR 설정으로 진행 NO_GRAN reasonstatus 기록 · wfe/wfi 52_BIT_VA reasonstatus 기록 · wfe/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)

라인별 주석

508SYM_FUNC_START_LOCAL(__primary_switch)

primary CPU의 MMU 전환과 relocation을 담당하는 local function을 시작합니다. 이 함수 안에서 주소 해석 체계가 물리/idmap에서 kernel high VA로 바뀝니다.

509adrp x1, reserved_pg_dir

초기 high-VA kernel mapping을 담을 reserved page-directory의 page 주소를 x1에 준비합니다. __enable_mmu의 TTBR1 인자입니다.

510adrp x2, __pi_init_idmap_pg_dir

현재 code와 return path를 identity-map할 root 주소를 x2에 준비합니다. __enable_mmu의 TTBR0 인자입니다.

511bl __enable_mmu

granule을 검증하고 TTBR0/TTBR1과 SCTLR을 설치해 MMU를 켭니다. 복귀 직후에도 아직 idmap alias에서 안전하게 실행됩니다.

513-515adrp x1, early_init_stack
mov sp, x1
mov x29, xzr

MMU-on 상태에서 사용할 early stack을 다시 잡고 frame pointer를 초기화합니다. relocation helper 호출 전에 신뢰할 stack 기준을 재설정합니다.

516mov x0, x20

보존한 전체 boot mode/E2H 상태를 __pi_early_map_kernel의 첫 번째 인자로 옮깁니다.

517mov x1, x21

FDT 물리 주소를 두 번째 인자로 전달합니다. early mapping이 FDT를 접근할 수 있는 범위를 함께 준비할 수 있습니다.

518bl __pi_early_map_kernel

kernel high-VA mapping을 완성하고 KASLR 등 필요한 relocation을 적용합니다. __pi_ 함수라 아직 position-independent 호출이 가능합니다.

520ldr x8, =__primary_switched

relocation이 반영된 정상 virtual text의 continuation 주소를 literal load로 x8에 가져옵니다.

521adrp x0, KERNEL_START

kernel 시작점의 물리 주소를 x0에 준비합니다. 다음 단계가 kimage_voffset = VA(_text) - PA(KERNEL_START)를 계산할 때 사용합니다.

522br x8

link register를 바꾸지 않고 높은 kernel virtual address의 __primary_switched로 절대 분기합니다. 이 지점이 idmap 실행을 떠나는 실제 경계입니다.

523SYM_FUNC_END(__primary_switch)

primary switch 심볼 범위를 닫습니다. 정상 control flow는 이미 br x8에서 빠져나갔으므로 여기에 실행 명령은 없습니다.

설명: 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. 물리 PC가 idmap을 건너 최종 커널 가상 주소로 점프하는 마지막 전환
primary switch 주소 공간 전환 MMU가 꺼진 물리 주소 실행에서 identity map으로 MMU를 켜고 kernel mapping과 relocation을 적용한 뒤 high virtual address의 primary switched로 분기하는 과정 물리 주소 .idmap.text__primary_switch Kernel Image PA(_text .. _end) MMU 전환 TTBR0 = init idmap현재 PC를 PA == VA로 계속 보이게 함 SCTLR.M = 1주소 해석 규칙 전환 __pi_early_map_kernelkernel map · FDT · relocationx20=mode, x21=FDT 높은 커널 VA KIMAGE_VADDR__primary_switched kernel text relocated references ldr x8br x8 PC: physical Image → TTBR0 identity alias → kernel mapping/relocation → __primary_switched high VA

원본 코드 전문

아래 전문은 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: