부팅 흐름 개요
이 파일은 아직 scheduler, allocator, IRQ core가 없는 시점의 코드입니다. 그래서 일반 C 함수처럼 읽으면 안 됩니다. 먼저 레지스터가 어떤 상태를 운반하는지, 어느 범위가 idmap으로 실행되어야 하는지, MMU를 켠 뒤 어떤 지점부터 virtual address 전제가 생기는지를 표시해야 합니다.
소스 코드 위치
원본 코드:arch/arm64/kernel/head.S · Linux 6.18.37 LTS · GPL-2.0-only
아래 글은 로컬에 저장한 원문을 기준으로 line number를 붙였습니다. 외부 링크는 stable tree mirror의 같은 tag를 가리킵니다.
라인 바이 라인
1. 라이선스, include, EFI header 연결
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 <catalin.marinas@arm.com>
9: * Will Deacon <will.deacon@arm.com>
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"
라인별 주석
빈 줄은 제외하고, 하나의 의미를 이루는 주석과 연속 선언만 묶었습니다.
/* SPDX-License-Identifier: GPL-2.0-only */이 파일 전체에 적용되는 라이선스를 기계가 읽을 수 있는 형태로 선언합니다. only이므로 GPL 2.0 조건만 적용되고, 임의로 이후 버전을 선택하는 or-later 파일이 아닙니다.
/* Low-level CPU initialisation ... */32비트 ARM의 arch/arm/kernel/head.S에서 계보가 시작되었음을 밝히고 주요 저자를 기록합니다. 실행 결과에는 영향을 주지 않지만 코드의 역사와 저작권 범위를 남기는 부분입니다.
#include <linux/linkage.h>SYM_FUNC_START, SYM_CODE_START처럼 ELF 심볼의 타입·가시성·정렬을 표현하는 매크로를 가져옵니다.
#include <linux/init.h>
#include <linux/pgtable.h>초기화 전용 섹션 표식과 공통 page-table 정의를 가져옵니다. 초기 부팅 코드와 임시 page table의 수명 경계를 기술할 때 사용됩니다.
#include <asm/asm_pointer_auth.h>AArch64 pointer authentication key 초기화에 필요한 assembler 매크로를 제공합니다. secondary CPU 경로의 ptrauth_keys_init_cpu가 여기서 연결됩니다.
#include <asm/assembler.h>adr_l, mov_q, str_l처럼 명령 한 개로 표현하기 어려운 주소·상수 연산과 cache helper를 모은 핵심 헤더입니다.
#include <asm/boot.h>bootloader가 지켜야 할 레지스터·메모리 계약과 early boot 상태값을 가져옵니다. head.S와 외부 부팅 주체 사이의 ABI 경계입니다.
#include <asm/bug.h>
#include <asm/ptrace.h>ASM_BUG()와 pt_regs stack frame 배치에 필요한 정의입니다. start_kernel이 잘못 반환했을 때의 trap과 초기 stack frame 작성에 쓰입니다.
#include <asm/asm-offsets.h>C 구조체 필드를 assembly가 접근할 수 있도록 빌드 중 생성된 수치 offset을 제공합니다. TSK_STACK, TSK_TI_CPU, CPU_BOOT_TASK가 대표적입니다.
#include <asm/cache.h>
#include <asm/cputype.h>cache line 유지보수와 CPU 식별값을 위한 정의입니다. MMU 전환 전후의 PoC clean/invalidate 및 MPIDR 처리에 사용됩니다.
#include <asm/el2_setup.h>EL2의 HCR, timer, trap 상태를 커널이 기대하는 값으로 만드는 init_el2_* 매크로를 가져옵니다.
#include <asm/elf.h>
#include <asm/image.h>ELF 배치와 arm64 Image header의 magic·flags·필드 형식을 정의합니다. 아래 64-byte header가 임의 구조가 아니라 공식 부팅 규격을 따르게 합니다.
#include <asm/kernel-pgtable.h>초기 identity map과 kernel map의 레벨 수, 크기, table 속성 등 부팅용 page-table 상수를 제공합니다.
#include <asm/kvm_arm.h>
#include <asm/memory.h>
#include <asm/pgtable-hwdef.h>
#include <asm/page.h>EL2/VHE 플래그, 물리·가상 주소 관계, 하드웨어 descriptor 비트, page 크기를 정의합니다. 주소 변환을 실제 register 값과 descriptor로 내릴 때 필요한 묶음입니다.
#include <asm/scs.h>Shadow Call Stack을 현재 task에 연결하는 scs_load_current를 제공합니다.
#include <asm/smp.h>
#include <asm/sysreg.h>secondary CPU bring-up 공유 자료구조와 system register field 정의를 가져옵니다. CurrentEL, SCTLR, ID_AA64MMFR* 판독의 기반입니다.
#include <asm/stacktrace/frame.h>
#include <asm/thread_info.h>초기 stack frame metadata와 task/thread_info 관련 offset을 제공합니다. unwind가 가능한 첫 frame을 만들 때 필요합니다.
#include <asm/virt.h>BOOT_CPU_MODE_EL1, BOOT_CPU_MODE_EL2, E2H flag처럼 커널의 가상화 실행 모드를 표현하는 값을 가져옵니다.
#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만이 아니라 이미지 포맷의 첫 면이기도 하다는 뜻입니다.
2. Image header와 bootloader ABI
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
라인별 주석
#if (PAGE_OFFSET & 0x1fffff) != 0PAGE_OFFSET의 하위 21비트가 모두 0인지 검사합니다. 즉 kernel virtual base가 2 MiB 경계에 놓이는지 전처리 단계에서 확인합니다.
#error PAGE_OFFSET must be at least 2MB aligned
#endif조건이 참이면 빌드를 즉시 중단합니다. 잘못된 정렬로 early block mapping이 어긋나는 문제를 실행 시점까지 미루지 않습니다.
/* Kernel startup entry point ... */진입 ABI를 문서화합니다. MMU와 D-cache는 꺼져 있어야 하고, I-cache는 어느 상태든 허용되며, x0에는 FDT blob의 물리 주소가 들어와야 합니다. callee-saved register는 MMU가 켜질 때까지 임시 전역 변수처럼 사용됩니다.
__HEAD뒤따르는 bytes를 image 시작부의 head 섹션에 배치하고 필요한 정렬·심볼 속성을 적용합니다. bootloader가 점프하는 첫 주소와 linker 배치를 일치시킵니다.
/* DO NOT MODIFY. Image header expected by Linux boot-loaders. */아래 필드의 순서와 offset이 Linux arm64 boot protocol의 일부임을 경고합니다. 내부 구현용 구조체가 아니라 외부 bootloader가 직접 파싱하는 ABI입니다.
efi_signature_nop일반 AArch64 실행에서는 안전한 NOP 계열 명령으로 동작하면서, PE/COFF 경로에서는 EFI image 식별에 필요한 signature 위치를 만족합니다.
b primary_entryCPU가 header의 나머지 데이터 필드를 명령으로 실행하지 않도록 실제 커널 entry로 즉시 분기합니다. 이 명령 자체가 Image header의 두 번째 32-bit word입니다.
.quad 0RAM 시작점 기준 image load offset 필드입니다. 현재 형식에서는 0을 기록하며 little-endian 64-bit 값으로 공간을 정확히 8바이트 차지합니다.
le64sym _kernel_size_lelinker가 계산한 유효 kernel image 크기를 little-endian 64-bit 값으로 넣습니다. bootloader가 image가 차지할 RAM 범위를 계산할 때 사용합니다.
le64sym _kernel_flags_lepage size, 물리 배치 방식 등 arm64 Image protocol의 정보성 flag를 little-endian으로 기록합니다.
.quad 0
.quad 0
.quad 0향후 규격 확장을 위해 예약된 24바이트입니다. 지금은 반드시 0이며, 세 줄을 줄이거나 다른 데이터를 넣으면 뒤쪽 magic의 고정 offset이 깨집니다.
.ascii ARM64_IMAGE_MAGICbootloader가 파일을 arm64 Linux Image로 판별하는 4-byte magic을 그대로 배치합니다. 문자열 종단 NUL을 붙이는 .asciz가 아니라 정확한 길이의 .ascii를 씁니다.
.long .Lpe_header_offsetImage 시작점에서 PE header까지의 상대 offset을 32-bit 값으로 기록합니다. UEFI loader가 PE/COFF 구조를 찾아가는 포인터 역할을 합니다.
__EFI_PE_HEADERefi-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로 판정하는 근거입니다.
3. primary_entry: idmap을 만들고 MMU 전환 직전까지 간다
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)
라인별 주석
/* x19 ... x20 ... x21 ... */세 callee-saved register의 수명을 먼저 고정합니다. x19는 entry MMU/cache 상태, x20은 CPU boot mode, x21은 FDT 물리 주소이며 일반 scratch register로 재사용할 수 없습니다.
SYM_CODE_START(primary_entry)primary_entry 심볼의 시작을 선언합니다. 일반 C ABI 함수라기보다 특수 entry code이므로 code symbol 매크로를 사용합니다.
bl record_mmu_state현재 EL의 SCTLR을 읽어 진입 당시 MMU·D-cache·endianness 상태를 정리하고 결과를 x19에 남깁니다.
bl preserve_boot_argsbootloader가 넘긴 x0-x3를 덮어쓰기 전에 전역 boot_args에 복사하고 FDT 주소를 x21에 보존합니다.
adrp x1, early_init_stack
mov sp, x1
mov x29, xzrearly_init_stack이 있는 page 주소를 계산해 임시 stack pointer로 사용하고 frame pointer를 0으로 초기화합니다. 아직 일반 task stack이 준비되지 않은 구간입니다.
adrp x0, __pi_init_idmap_pg_dir초기 identity map의 root page-table 주소를 page-relative 방식으로 x0에 만듭니다. 현재 PC와 대상이 같은 position-independent image 안에 있다는 전제를 이용합니다.
mov x1, xzr__pi_create_init_idmap에 넘길 두 번째 인자를 0으로 설정합니다. 선택 flag가 없는 기본 idmap 생성 경로입니다.
bl __pi_create_init_idmapMMU를 켜는 순간 현재 실행 주소가 끊기지 않도록 초기 identity mapping을 작성합니다. 반환된 x0는 사용한 page-table 영역의 끝을 가리킵니다.
/* If the page tables ... invalidate ... */MMU-off 상태에서 non-cacheable store로 table을 썼더라도 speculative access가 같은 line을 cache에 올렸을 수 있음을 설명합니다. 따라서 table publish 전에 stale cache line을 제거해야 합니다.
cbnz x19, 0fx19 != 0이면 진입 당시 MMU와 D-cache가 켜진 경로이므로 label 0의 idmap text clean으로 이동합니다. 0이면 바로 아래 table invalidate 경로를 실행합니다.
dmb sypage-table store가 모든 관찰자에게 순서상 먼저 완료되도록 full-system data memory barrier를 둡니다. 이어지는 invalidate가 store보다 앞서 보이지 않게 합니다.
mov x1, x0
adrp x0, __pi_init_idmap_pg_dir
adr_l x2, dcache_inval_poc
blr x2반환받은 끝 주소를 x1에 두고 root 시작 주소를 x0에 다시 만든 뒤, 그 구간을 PoC까지 invalidate하는 helper 주소를 x2로 불러 간접 호출합니다.
b 1fMMU-off table invalidate를 마친 뒤 MMU-on 전용 text clean 블록을 건너뛰고 두 경로가 합쳐지는 label 1로 갑니다.
/* If we entered with the MMU and caches on ... */MMU-on으로 진입했다가 곧 MMU를 끌 수 있으므로, 이후 물리 fetch할 idmap code를 PoC까지 clean해야 한다는 이유를 설명합니다.
0: 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에서 실행되는 일을 막습니다.
1: mov x0, x19
bl init_kernel_el
mov x20, x0진입 상태를 인자로 EL 초기화 함수에 넘기고, 반환된 EL1/EL2 및 E2H 정보를 장기 보존 register인 x20에 저장합니다.
/* The following calls CPU setup code ... */다음 호출이 MIDR별 processor setup을 수행해 TCR과 cache/MMU 관련 register를 준비한다는 함수 계약입니다. 세부 구현은 arch/arm64/mm/proc.S에 있습니다.
bl __cpu_setup현재 CPU 구현에 맞는 TCR, MAIR 및 MMU 전제 register를 설정합니다. 이 호출 뒤에는 page table base를 설치하고 MMU를 켤 수 있는 상태가 됩니다.
b __primary_switchlink register를 새로 저장하지 않는 tail branch로 primary MMU 전환 경로에 제어를 넘깁니다. 정상적으로는 primary_entry로 돌아오지 않습니다.
SYM_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이 아닙니다.
4. record_mmu_state: 들어온 MMU/cache 상태를 x19로 압축
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)
라인별 주석
SYM_CODE_START_LOCAL(record_mmu_state)외부로 export하지 않는 local code symbol을 시작합니다. 호출 규약보다 entry state를 다루는 특수 코드라는 성격을 표시합니다.
mrs x19, CurrentEL현재 exception level을 system register에서 읽습니다. 이 순간의 x19는 최종 상태값이 아니라 EL 판정을 위한 임시값입니다.
cmp 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 제어 비트가 있습니다.
0:EL1과 EL2의 SCTLR 선택 경로가 합쳐지는 local numeric label입니다. 앞의 b.ne 0f에서 f는 현재 위치보다 앞쪽이 아니라 다음에 나오는 label 0을 뜻합니다.
CPU_LE(tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f)little-endian 커널에서 EE 비트가 1이면 현재 byte order가 기대와 반대이므로 보정 경로 1로 갑니다.
CPU_BE(tbz x19, #SCTLR_ELx_EE_SHIFT, 1f)big-endian 커널에서는 반대로 EE 비트가 0일 때 보정 경로로 갑니다. 빌드 endian에 따라 두 매크로 중 하나만 유효한 코드로 남습니다.
tst x19, #SCTLR_ELx_CD-cache enable 비트 C를 검사해 condition flag의 Z를 설정합니다. C가 0이면 Z=1입니다.
and x19, x19, #SCTLR_ELx_MSCTLR 전체에서 MMU enable 비트 M만 남깁니다. 이 명령은 ands가 아니므로 앞의 tst가 만든 condition flag는 보존됩니다.
csel x19, xzr, x19, eqD-cache가 꺼져 Z=1이면 x19=0, 켜져 있으면 앞에서 추출한 M 비트를 유지합니다. 결과적으로 뒤의 cache 경로는 하나의 값으로 판정할 수 있습니다.
retendianness가 이미 맞는 정상 경로를 호출자에게 반환합니다. x19는 진입 상태 요약값으로 남습니다.
/* Set the correct endianness early ... */byte order를 바꾸는 동안 active idmap을 반대 endian으로 해석하지 않도록 MMU도 반드시 꺼야 한다는 설계 이유를 적습니다.
1: eor x19, x19, #SCTLR_ELx_EE현재 EE 비트를 반전해 커널 빌드가 기대하는 byte order로 맞춥니다.
bic x19, x19, #SCTLR_ELx_M동시에 M 비트를 지워 MMU를 끌 값을 만듭니다. 주소 변환이 켜진 상태에서 endian만 바뀌는 위험한 중간 상태를 만들지 않습니다.
b.ne 2fline 136의 CurrentEL 비교 flag를 이용합니다. EL2가 아니었다면 label 2의 EL1 register write 경로로 분기합니다.
pre_disable_mmu_workaround
msr sctlr_el2, x19
b 3fEL2용 CPU erratum workaround를 적용한 뒤 보정된 값을 SCTLR_EL2에 쓰고 공통 동기화 지점으로 이동합니다.
2: pre_disable_mmu_workaround
msr sctlr_el1, x19EL1 진입이었다면 동일한 사전 workaround 뒤 SCTLR_EL1을 갱신합니다.
3: isbSCTLR 변경이 이후 instruction fetch에 확실히 반영되도록 instruction synchronization barrier를 실행합니다.
mov 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로 해석하는 순간 더 이상 신뢰할 코드 경로가 아닙니다.
5. preserve_boot_args: FDT와 boot args를 잃지 않는다
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)
라인별 주석
SYM_CODE_START_LOCAL(preserve_boot_args)boot argument 보존 helper의 local code symbol을 시작합니다.
mov x21, x0FDT의 물리 주소를 장기 생존 register x21에 복사합니다. 이후 함수 호출들이 x0를 인자로 사용해도 FDT를 잃지 않습니다.
adr_l x0, boot_argsposition-independent long-address 매크로로 전역 boot_args의 주소를 x0에 만듭니다.
stp x21, x1, [x0]entry 당시 x0였던 FDT 값과 x1을 첫 16바이트에 한 쌍으로 저장합니다.
stp x2, x3, [x0, #16]x2와 x3을 다음 16바이트에 저장해 총 32바이트의 boot argument snapshot을 완성합니다.
cbnz x19, 0fentry 당시 MMU/cache가 켜져 있었다면 MMU-off 전용 cache invalidate를 건너뛰고 상태 기록 경로로 갑니다.
dmb sy
/* needed before dc ivac with MMU off */방금 수행한 pair store가 완료된 뒤 invalidate가 일어나도록 순서를 강제합니다. MMU-off alias에서 stale line이 남는 문제를 피합니다.
add x1, x0, #0x20invalidate 범위의 끝을 boot_args + 32로 계산합니다. x0는 시작 주소, x1은 배타적 끝 주소가 됩니다.
b dcache_inval_poclink register를 바꾸지 않는 tail call입니다. helper가 반환하면 곧바로 원래 preserve_boot_args 호출자에게 돌아갑니다.
0: str_l x19, mmu_enabled_at_boot, x0MMU-on 진입 경로에서는 상태값을 전역 mmu_enabled_at_boot에 저장합니다. 마지막 x0는 주소 계산용 scratch register입니다.
ret
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 위반이나 특수 진입 경로를 해석할 때 중요합니다.
6. __primary_switched: virtual world에서 start_kernel 호출
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)
라인별 주석
.macro init_cpu_task tsk, tmp1, tmp2task pointer와 두 scratch register를 인자로 받는 assembly 매크로를 정의합니다. primary와 secondary CPU가 동일한 task-context 초기화 순서를 공유합니다.
msr sp_el0, \tsk현재 task pointer를 SP_EL0에 기록합니다. 커널이 EL1 stack을 쓰는 동안 이 register를 current task 보관 용도로 사용합니다.
ldr \tmp1, [\tsk, #TSK_STACK]
add sp, \tmp1, #THREAD_SIZE
sub sp, sp, #PT_REGS_SIZEtask의 stack base를 읽고 stack top으로 이동한 다음 pt_regs가 들어갈 공간을 미리 뺍니다. AArch64 stack이 낮은 주소 방향으로 자라는 점을 반영합니다.
stp 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를 가리키게 합니다.
scs_load_current현재 task에 할당된 Shadow Call Stack pointer를 CPU register에 적재합니다. SCS가 꺼진 빌드에서는 적절한 빈 매크로로 사라집니다.
adr_l \tmp1, __per_cpu_offset
ldr w\tmp2, [\tsk, #TSK_TI_CPU]
ldr \tmp1, [\tmp1, \tmp2, lsl #3]
set_this_cpu_offset \tmp1task가 속한 CPU 번호로 __per_cpu_offset[cpu]을 읽고 현재 CPU의 per-CPU base register를 설정합니다. 이후 this_cpu 접근이 올바른 영역을 가리킵니다.
.endminit_cpu_task 매크로 정의를 끝냅니다.
/* executed with the MMU enabled; x0 = __pa(KERNEL_START) */이후 코드는 이미 높은 kernel virtual address에서 실행되며, x0에는 kernel 시작점의 물리 주소가 들어온다는 진입 조건입니다.
SYM_FUNC_START_LOCAL(__primary_switched)MMU-on primary 경로의 local function symbol을 시작합니다.
adr_l x4, init_task
init_cpu_task x4, x5, x6boot CPU의 current task를 정적 init_task로 정하고, 앞에서 정의한 매크로로 stack·frame·SCS·per-CPU base를 한꺼번에 초기화합니다.
adr_l x8, vectors
msr vbar_el1, x8
isbEL1 exception vector의 가상 주소를 VBAR_EL1에 설치하고 ISB로 즉시 효력을 보장합니다. 이후 exception은 정식 커널 vector로 진입합니다.
stp x29, x30, [sp, #-16]!
mov x29, sp현재 FP/LR을 stack에 push하고 새 frame pointer를 만듭니다. 여기부터 일반 함수 호출과 stack unwinding이 가능한 형태를 갖춥니다.
str_l x21, __fdt_pointer, x5초기부터 보존한 FDT 물리 주소를 C 코드가 읽는 전역 __fdt_pointer에 저장합니다. x5는 주소 계산용 scratch입니다.
adrp x4, _text
sub x4, x4, x0
str_l x4, kimage_voffset, x5현재 보이는 kernel virtual _text 주소에서 전달받은 물리 KERNEL_START를 빼 kimage_voffset을 계산하고 전역에 저장합니다.
mov x0, x20
bl set_cpu_boot_mode_flag보존해 둔 EL1/EL2 boot mode를 인자로 넘겨 전역 __boot_cpu_mode의 해당 slot에 기록합니다.
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
bl kasan_early_init
#endifKASAN이 필요한 빌드에서만 일반 allocator보다 먼저 shadow memory의 early mapping을 초기화합니다.
mov x0, x20
bl finalise_el2boot mode를 다시 전달해 EL2 구성을 최종 확정합니다. 하드웨어가 허용하면 VHE를 우선하고, 그렇지 않으면 nVHE 상태를 정리합니다.
ldp x29, x30, [sp], #16앞에서 만든 호출 frame을 pop해 FP/LR을 복원합니다.
bl start_kernelgeneric kernel 초기화의 C entry로 들어갑니다. scheduler, memory, IRQ, initcall 등 나머지 부팅은 여기서 시작됩니다.
ASM_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를 드러냅니다.
7. init_kernel_el: EL1/EL2 실행 상태 정리
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)
라인별 주석
SYM_FUNC_START(init_kernel_el)
mrs x1, CurrentEL
cmp x1, #CurrentEL_EL2
b.eq init_el2함수를 시작하고 현재 EL을 판정합니다. EL2이면 별도 초기화 경로로 가고, 아니면 바로 아래 EL1 경로로 fall-through합니다.
SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)같은 함수 내부에 local EL1 entry label을 만듭니다. 코드 크기와 unwind 범위는 바깥 init_kernel_el에 속합니다.
mov_q x0, INIT_SCTLR_EL1_MMU_OFF
pre_disable_mmu_workaround
msr sctlr_el1, x0
isbEL1 SCTLR을 커널이 정의한 MMU-off 기준값으로 만들고 CPU workaround를 거쳐 기록한 뒤, ISB로 새 제어 상태를 동기화합니다.
mov_q x0, INIT_PSTATE_EL1
msr spsr_el1, x0
msr elr_el1, lreret 뒤 적용될 PSTATE와 복귀 PC를 준비합니다. 호출 당시 LR을 ELR에 넣어 EL 변경 뒤에도 원래 호출 흐름으로 돌아가게 합니다.
mov w0, #BOOT_CPU_MODE_EL1
eret반환값을 EL1 boot mode로 정하고 exception return을 수행합니다. 일반 ret과 달리 SPSR/ELR의 상태를 함께 복원합니다.
SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
msr elr_el2, lrEL2 내부 label을 시작하고 최종적으로 EL1로 내려간 뒤 이어갈 주소를 ELR_EL2에 저장합니다.
cbz x0, 0f
adrp x0, __hyp_idmap_text_start
adr_l x1, __hyp_text_end
adr_l x2, dcache_clean_poc
blr x2MMU/cache-on 상태로 EL2에 들어온 경우에만 HYP idmap text를 PoC까지 clean합니다. MMU를 끈 뒤 같은 bytes를 물리 alias로 실행해도 최신 명령을 보게 합니다.
mov_q x0, INIT_SCTLR_EL2_MMU_OFF
pre_disable_mmu_workaround
msr sctlr_el2, x0
isb
0:EL2 SCTLR을 MMU-off 기준값으로 바꾸고 동기화합니다. 원래 MMU가 꺼져 있던 경로도 label 0에서 합류합니다.
init_el2_hcr HCR_HOST_NVHE_FLAGS
init_el2_stateHCR을 host nVHE 기준으로 설정하고 timer, trap, translation 등 EL2 system state를 알려진 값으로 초기화합니다.
adr_l x0, __hyp_stub_vectors
msr vbar_el2, x0
isb정식 hypervisor vector가 준비되기 전 사용할 stub vector를 VBAR_EL2에 설치하고 동기화합니다.
mov_q x1, INIT_SCTLR_EL1_MMU_OFFEL1로 내려갈 때 적용할 기준 SCTLR 값을 x1에 준비합니다.
mrs x0, hcr_el2
and x0, x0, #HCR_E2H
cbz x0, 2fHCR의 E2H 비트를 읽어 VHE host mode인지 확인합니다. E2H가 0이면 label 2의 일반 EL1 register 접근으로 갑니다.
msr_s SYS_SCTLR_EL12, x1
mov x2, #BOOT_CPU_FLAG_E2H
b 3fVHE에서는 EL2에서 EL1 system register를 보는 alias인 SCTLR_EL12에 값을 쓰고, 반환 boot status에 합칠 E2H flag를 x2에 둡니다.
2: msr sctlr_el1, x1
mov x2, xzr
3:nVHE에서는 실제 SCTLR_EL1을 직접 설정하고 E2H flag를 0으로 둡니다. 두 경로는 label 3에서 합류합니다.
mov x0, #INIT_PSTATE_EL1
msr spsr_el2, x0EL2의 eret이 EL1의 원하는 interrupt mask와 stack mode로 내려가도록 saved PSTATE를 설정합니다.
mov w0, #BOOT_CPU_MODE_EL2
orr x0, x0, x2
eretEL2 진입 mode와 선택적 E2H flag를 하나의 반환값으로 합친 뒤 EL1 호출 지점으로 exception return합니다.
SYM_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에서 소비됩니다.
8. secondary CPU: holding pen부터 secondary_start_kernel까지
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)
라인별 주석
/* holding pen for secondary cores */primary CPU가 공유 자료구조와 scheduler 상태를 준비할 때까지 secondary core가 진행하지 못하도록 대기시키는 진입 경로임을 설명합니다.
SYM_FUNC_START(secondary_holding_pen)
mov x0, xzr
bl init_kernel_elholding-pen entry를 시작하고 entry MMU state를 0으로 넘겨 EL1/EL2 상태를 정리합니다. 반환 w0에는 boot mode가 있습니다.
mrs x2, mpidr_el1
mov_q x1, MPIDR_HWID_BITMASK
and x2, x2, x1현재 CPU의 MPIDR에서 affinity를 나타내는 hardware ID 비트만 남깁니다. release variable과 비교할 고유 식별값입니다.
adr_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으로 나갑니다.
SYM_FUNC_END(secondary_holding_pen)holding-pen 심볼의 끝입니다.
SYM_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으로 바로 갑니다.
SYM_FUNC_START_LOCAL(secondary_startup)
mov x20, x0secondary 공통 low-level 경로를 시작하고 init_kernel_el이 반환한 boot mode를 callee-saved x20에 보존합니다.
#ifdef CONFIG_ARM64_VA_BITS_52
alternative_if ARM64_HAS_VA52
bl __cpu_secondary_check52bitva
alternative_else_nop_endif
#endif52-bit VA 빌드에서 CPU capability에 따라 secondary 검사를 alternative patching으로 삽입합니다. 필요 없는 CPU 경로는 동등 길이 NOP로 대체됩니다.
bl __cpu_setup이 secondary CPU의 TCR, MAIR, cache/MMU 관련 system register를 CPU 모델에 맞게 준비합니다.
adrp x1, swapper_pg_dir
adrp x2, idmap_pg_dir
bl __enable_mmu최종 kernel mapping root와 identity-map root를 인자로 준비하고 secondary CPU의 MMU를 켭니다.
ldr x8, =__secondary_switched
br x8
SYM_FUNC_END(secondary_startup)MMU-on virtual address의 continuation을 literal로 읽어 절대 분기합니다. low-level startup은 돌아오지 않으므로 br을 사용합니다.
.text
SYM_FUNC_START_LOCAL(__secondary_switched)
mov x0, x20
bl set_cpu_boot_mode_flag정상 kernel text로 돌아와 secondary boot mode를 전역 slot에 기록합니다.
mov x0, x20
bl finalise_el2이 CPU의 EL2/VHE 상태를 primary와 동일한 커널 정책으로 최종 정리합니다.
str_l xzr, __early_cpu_boot_status, x3이 CPU가 MMU 전환을 통과했음을 나타내도록 early failure status를 0으로 지웁니다.
adr_l x5, vectors
msr vbar_el1, x5
isbsecondary CPU에도 정식 EL1 exception vector를 설치하고 동기화합니다. VBAR은 CPU-local system register라 각 CPU에서 설정해야 합니다.
adr_l x0, secondary_data
ldr x2, [x0, #CPU_BOOT_TASK]
cbz x2, __secondary_too_slowprimary가 준비한 secondary task pointer를 읽습니다. 아직 0이면 안전한 task/stack이 없으므로 계속 진행하지 않고 park 경로로 갑니다.
init_cpu_task x2, x1, x3해당 secondary의 current task, stack, frame, SCS, per-CPU base를 설정합니다.
#ifdef CONFIG_ARM64_PTR_AUTH
ptrauth_keys_init_cpu x2, x3, x4, x5
#endifpointer authentication이 활성화된 경우 이 CPU가 사용할 key register를 초기화합니다. key register도 CPU-local 상태입니다.
bl 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로 진입합니다.
9. boot mode flag와 early failure status
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
라인별 주석
SYM_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으로 진행하지 않습니다.
/* Sets the __boot_cpu_mode flag ... */w0로 받은 EL1/EL2 boot mode를 공유 배열의 어느 slot에 기록할지 설명하는 함수 계약입니다.
SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
adr_l x1, __boot_cpu_modehelper를 시작하고 boot-mode 저장 영역의 첫 slot 주소를 계산합니다.
cmp w0, #BOOT_CPU_MODE_EL2
b.ne 1f
add x1, x1, #4EL2로 부팅한 CPU이면 두 번째 32-bit slot을 선택하도록 주소에 4를 더합니다. EL1이면 첫 slot을 그대로 씁니다.
1: str w0, [x1]
ret
SYM_FUNC_END(set_cpu_boot_mode_flag)선택한 slot에 mode를 저장하고 반환한 뒤 심볼을 닫습니다. 여러 CPU의 관측 결과로 시스템 boot mode 일관성을 판단할 수 있습니다.
/* update_early_cpu_boot_status ... */MMU-off CPU가 실패 상태를 공유 메모리에 기록하며 두 scratch register를 파괴한다는 매크로 계약입니다.
.macro update_early_cpu_boot_status status, tmp1, tmp2상태 상수와 주소·값용 scratch register 두 개를 인자로 받는 매크로를 정의합니다.
mov \tmp2, #\status호출자가 지정한 실패 비트 조합을 실제 저장할 register에 materialize합니다.
adr_l \tmp1, __early_cpu_boot_status
str \tmp2, [\tmp1]공유 early status의 주소를 계산해 실패값을 저장합니다. 아직 MMU-off이므로 이후 cache 가시성 처리가 중요합니다.
dmb systatus store가 전역 관찰 순서에서 cache maintenance보다 먼저 완료되도록 full-system barrier를 둡니다.
dc ivac, \tmp1해당 VA/PA alias에 남아 있을 수 있는 stale cache line을 invalidate해 다른 CPU가 메모리의 새 실패값을 읽을 수 있게 합니다.
.endmearly 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만으로는 부족합니다.
10. __enable_mmu: TTBR과 SCTLR로 MMU를 켠다
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)
라인별 주석
/* Enable the MMU ... */입력 register 계약을 정의합니다. x0는 켤 SCTLR 값, x1은 TTBR1 kernel root, x2는 TTBR0 idmap root입니다. 복귀 주소까지 idmap에 포함되어야 하며 granule 미지원 CPU는 park합니다.
.section ".idmap.text","a"함수를 allocatable identity-map text 섹션에 배치합니다. MMU가 켜지는 경계에서 같은 숫자의 VA와 PA로 계속 fetch할 수 있게 합니다.
SYM_FUNC_START(__enable_mmu)MMU enable 함수의 ELF function symbol을 시작합니다.
mrs x3, ID_AA64MMFR0_EL1CPU의 memory-model feature register를 읽습니다. page granule 지원 범위가 이 register에 인코딩되어 있습니다.
ubfx x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4빌드가 선택한 granule에 대응하는 4-bit TGRAN field만 unsigned bitfield extract로 뽑습니다.
cmp x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN
b.lt __no_granule_supportfield 값이 지원 범위의 하한보다 작으면 정상 translation table을 사용할 수 없으므로 실패 park 경로로 갑니다.
cmp x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX
b.gt __no_granule_support지원 범위의 상한도 확인합니다. architecture가 예약하거나 미지원으로 정의한 encoding을 통과시키지 않습니다.
phys_to_ttbr x2, x2idmap root의 물리 주소를 TTBR register가 요구하는 encoding으로 변환합니다. PA bit 배치와 CnP 같은 TTBR 형식 차이를 매크로가 흡수합니다.
msr ttbr0_el1, x2낮은 주소의 identity mapping에 사용할 root를 TTBR0_EL1에 설치합니다. 현재 함수와 복귀 지점의 연속성을 보장하는 table입니다.
load_ttbr1 x1, x1, x3kernel high-VA mapping root를 TTBR1_EL1에 설치합니다. 필요한 barrier와 CPU별 TTBR 처리에 x3를 scratch로 사용합니다.
set_sctlr_el1 x0준비한 SCTLR 값을 기록해 M 비트를 포함한 MMU/cache 제어 상태를 활성화합니다. 매크로 내부의 동기화까지 끝난 다음부터 주소 변환이 적용됩니다.
ret
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으로 자기 자신을 덮어야 합니다.
11. 52-bit VA와 granule 실패 park path
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)
라인별 주석
#ifdef CONFIG_ARM64_VA_BITS_52
SYM_FUNC_START(__cpu_secondary_check52bitva)52-bit VA 커널에서만 secondary CPU feature 검사 함수를 빌드합니다. 48-bit 이하 빌드에는 이 코드 자체가 없습니다.
#ifndef CONFIG_ARM64_LPA2
mrs_s x0, SYS_ID_AA64MMFR2_EL1
and x0, x0, ID_AA64MMFR2_EL1_VARange_MASK
cbnz x0, 2fLPA2를 쓰지 않는 52-bit VA 구성에서는 ID_AA64MMFR2_EL1.VARange를 읽습니다. 지원 비트가 0이 아니면 label 2의 성공 return으로 갑니다.
#else
mrs x0, id_aa64mmfr0_el1
sbfx x0, x0, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
cmp x0, #ID_AA64MMFR0_EL1_TGRAN_LPA2
b.ge 2fLPA2 구성에서는 선택 granule의 signed 4-bit capability encoding을 추출해 LPA2 이상인지 비교합니다. 만족하면 정상 복귀합니다.
#endifVARange 방식과 LPA2 granule 방식의 compile-time 분기를 닫습니다. 여기까지 fall-through했다면 CPU가 요구 기능을 지원하지 않습니다.
update_early_cpu_boot_status \
CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1공유 early status에 커널 내부 정지와 52-bit VA 미지원 이유를 함께 기록합니다. x0, x1은 매크로 scratch로 파괴됩니다.
1: wfe
wfi
b 1b실패 CPU를 event/interrupt 저전력 대기 loop에 영구 정지시킵니다. 깨어나더라도 뒤로 분기해 kernel state로 진입하지 않습니다.
2: ret
SYM_FUNC_END(__cpu_secondary_check52bitva)
#endiffeature 검사를 통과한 CPU만 호출자에게 돌아갑니다. 함수와 52-bit VA 조건부 컴파일 영역을 닫습니다.
SYM_FUNC_START_LOCAL(__no_granule_support)
/* CPU can't boot and is stuck */선택된 translation granule을 지원하지 않는 CPU의 공통 local failure entry입니다. 다른 granule로 동적 fallback하지 않습니다.
update_early_cpu_boot_status \
CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2공유 status에 NO_GRAN 원인을 기록해 primary나 진단 코드가 정지 이유를 구분할 수 있게 합니다.
1:
wfe
wfi
b 1b상태 기록 뒤 CPU를 영구 park합니다. page-table format 자체가 맞지 않으므로 이후 명령을 실행하는 것보다 멈추는 것이 유일하게 안전합니다.
SYM_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입니다.
12. __primary_switch: kernel map/relocation 후 __primary_switched로 점프
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)
라인별 주석
SYM_FUNC_START_LOCAL(__primary_switch)primary CPU의 MMU 전환과 relocation을 담당하는 local function을 시작합니다. 이 함수 안에서 주소 해석 체계가 물리/idmap에서 kernel high VA로 바뀝니다.
adrp x1, reserved_pg_dir초기 high-VA kernel mapping을 담을 reserved page-directory의 page 주소를 x1에 준비합니다. __enable_mmu의 TTBR1 인자입니다.
adrp x2, __pi_init_idmap_pg_dir현재 code와 return path를 identity-map할 root 주소를 x2에 준비합니다. __enable_mmu의 TTBR0 인자입니다.
bl __enable_mmugranule을 검증하고 TTBR0/TTBR1과 SCTLR을 설치해 MMU를 켭니다. 복귀 직후에도 아직 idmap alias에서 안전하게 실행됩니다.
adrp x1, early_init_stack
mov sp, x1
mov x29, xzrMMU-on 상태에서 사용할 early stack을 다시 잡고 frame pointer를 초기화합니다. relocation helper 호출 전에 신뢰할 stack 기준을 재설정합니다.
mov x0, x20보존한 전체 boot mode/E2H 상태를 __pi_early_map_kernel의 첫 번째 인자로 옮깁니다.
mov x1, x21FDT 물리 주소를 두 번째 인자로 전달합니다. early mapping이 FDT를 접근할 수 있는 범위를 함께 준비할 수 있습니다.
bl __pi_early_map_kernelkernel high-VA mapping을 완성하고 KASLR 등 필요한 relocation을 적용합니다. __pi_ 함수라 아직 position-independent 호출이 가능합니다.
ldr x8, =__primary_switchedrelocation이 반영된 정상 virtual text의 continuation 주소를 literal load로 x8에 가져옵니다.
adrp x0, KERNEL_STARTkernel 시작점의 물리 주소를 x0에 준비합니다. 다음 단계가 kimage_voffset = VA(_text) - PA(KERNEL_START)를 계산할 때 사용합니다.
br x8link register를 바꾸지 않고 높은 kernel virtual address의 __primary_switched로 절대 분기합니다. 이 지점이 idmap 실행을 떠나는 실제 경계입니다.
SYM_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로 넘어갑니다.
원본 코드 전문
아래 전문은 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 <catalin.marinas@arm.com>
9: * Will Deacon <will.deacon@arm.com>
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: