큰 지도
이 파일은 아직 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"
설명: 첫 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
설명: 이 부분은 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)
설명: 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)
설명: 이 함수는 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)
설명: 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)
설명: 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)
설명: 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)
설명: 보조 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
설명: 부팅 성공 경로만 있으면 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)
설명: 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)
설명: 지원하지 않는 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)
설명: 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: