# head.S — 말로 풀어 읽기
Linux v6.18.37 · arch/arm64/kernel/head.S

C 함수가 실행되려면 이미 쓸 수 있는 스택과 메모리 주소 체계가 있어야 합니다. 그런데 전원을 켠 직후에는 커널이 그 준비를 직접 해야 합니다. head.S는 부트로더가 넘긴 정보를 잃지 않으면서 CPU의 실행 상태를 맞추고, 페이지 테이블과 스택을 준비해 C 초기화 코드로 연결하는 부분입니다. 파일의 위에서 아래가 실행 순서는 아닙니다. BL과 B가 어느 라벨로 향하는지 따라가야 합니다.

## 처음 읽을 때
처음에는 x0가 FDT 주소라는 점, 함수 호출 뒤에는 x0의 용도가 바뀐다는 점을 따라가세요. 주소를 저장하는 것과 그 주소의 내용을 복사하는 것도 구분합니다.

## 더 깊이 살펴볼 때
MMU 활성화 전후에 PC의 숫자는 그대로인데 해석이 달라집니다. idmap이 왜 필요한지, 최종 커널 VA로 가는 BR을 왜 별도로 수행하는지 설명해 보세요. EL2에서 시작했다는 기록과 현재 실행 EL 역시 다릅니다.

## 소스의 구성과 Image의 앞부분
원본 1–43행

### 12행
```asm
#include <linux/linkage.h>
```
SYM_FUNC_START·SYM_CODE_START·SYM_INNER_LABEL 등의 심볼 선언 규칙을 가져옵니다. 함수인지 코드 내부 라벨인지, 다른 오브젝트에 보일 이름인지와 정렬·크기 정보를 빌드 도구에 전달하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 13행
```asm
#include <linux/init.h>
```
부팅 때만 사용할 코드·데이터의 섹션 표기를 가져옵니다. 초기화가 끝난 뒤 해제 가능한 영역과 계속 남겨야 하는 영역을 링커가 구분하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 14행
```asm
#include <linux/pgtable.h>
```
페이지 표를 다루는 공통 정의와 아키텍처별 표 정의를 연결합니다. 초기 매핑을 만들 때 필요한 표 항목·속성의 정의를 일관되게 사용하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 16행
```asm
#include <asm/asm_pointer_auth.h>
```
PAC 키 설치 매크로를 가져옵니다. 사용자·커널 또는 task 사이를 전환할 때 instruction-address 인증 키가 해당 실행 문맥과 맞아야 하므로 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 17행
```asm
#include <asm/assembler.h>
```
ARM64의 큰 주소 구성, CPU별 값 조회, 시스템 레지스터 처리, 동기화 등에 쓰는 어셈블리 매크로를 가져옵니다. 각 사용 위치에서 일반 명령 여러 개로 펼쳐지는 작업입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 18행
```asm
#include <asm/boot.h>
```
부트로더에서 ARM64 커널로 진입할 때 사용하는 부팅 관련 정의를 가져옵니다. 펌웨어가 넘긴 실행 상태를 초기 커널의 규칙에 맞추는 코드가 이 공통 정의를 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 19행
```asm
#include <asm/bug.h>
```
복구를 기대하지 않는 커널 오류를 BRK와 BUG 메타데이터로 표현하는 규칙을 가져옵니다. 돌아오지 않아야 할 초기화 함수가 반환한 경우 등의 오류 경로에서 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 20행
```asm
#include <asm/ptrace.h>
```
pt_regs와 예외 전 PSTATE의 레벨·폭·마스크 비트 정의를 가져옵니다. 저장된 실행 문맥을 구성하고 32/64비트 복귀 상태를 해석하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 21행
```asm
#include <asm/asm-offsets.h>
```
C 구조체에서 계산한 필드 오프셋을 가져옵니다. 어셈블리가 task_struct·pt_regs 등에서 값을 읽고 쓸 때 C 빌드의 실제 배치와 일치하도록 생성하는 헤더입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 22행
```asm
#include <asm/cache.h>
```
캐시 라인 정렬과 유지보수 관련 크기 정의를 가져옵니다. 초기 데이터와 표를 캐시 단위로 다룰 때 사용하며 CPU 캐시를 이 include가 직접 켜는 것은 아닙니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 23행
```asm
#include <asm/cputype.h>
```
CPU 식별·기능 레지스터를 해석할 정의를 가져옵니다. 구현별 차이나 지원 주소 변환 기능을 판단할 때 숫자 비트 위치를 이름으로 사용할 수 있게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 24행
```asm
#include <asm/el2_setup.h>
```
EL2 초기화 매크로를 가져옵니다. EL2로 들어온 CPU의 타이머·트랩·가상화 관련 상태를 정리한 뒤 커널의 다음 실행 단계로 넘기는 데 쓰입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 25행
```asm
#include <asm/elf.h>
```
ARM64 ELF 실행 형식과 CPU 기능 표현에 관한 정의를 가져옵니다. ELF 관련 상수는 파일·실행 규약이며 include 자체가 ELF 로딩을 수행하지 않습니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 26행
```asm
#include <asm/image.h>
```
부트로더가 읽는 ARM64 Image 헤더의 매직 값·플래그·필드 표현을 가져옵니다. head.S 앞부분이 외부 부팅 규약에 맞는 헤더를 출력할 때 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 27행
```asm
#include <asm/kernel-pgtable.h>
```
초기 페이지 표의 크기와 루트 표 사이의 배치 차이를 가져옵니다. TTBR 전환 코드와 링커가 같은 공간·오프셋을 전제로 하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 28행
```asm
#include <asm/kvm_arm.h>
```
ARM 가상화 제어의 비트와 상수를 가져옵니다. EL2에서 HCR 등의 초기 상태를 구성하여 뒤의 EL1 커널 실행과 가상화 동작을 준비하는 데 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 29행
```asm
#include <asm/memory.h>
```
커널 가상 주소 배치, 스택 크기·정렬, 주소 변환 관련 상수를 가져옵니다. 같은 숫자가 주소·크기·오프셋 중 무엇인지 각 사용 문맥에서 구분해야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 30행
```asm
#include <asm/pgtable-hwdef.h>
```
CPU가 해석하는 페이지 표 항목과 주소 변환 제어 필드를 가져옵니다. 초기 매핑의 속성과 TCR·TTBR 처리에서 하드웨어 비트 정의를 일관되게 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 31행
```asm
#include <asm/page.h>
```
빌드가 선택한 페이지 크기와 정렬 단위를 가져옵니다. 4KiB를 항상 가정하지 않고 선택된 PAGE_SIZE로 표와 이미지 경계를 맞추는 데 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 32행
```asm
#include <asm/scs.h>
```
별도 보호 스택에 복귀 주소를 보관하는 Shadow Call Stack의 저장·복원 규칙을 가져옵니다. task나 IRQ 문맥을 바꿀 때 일반 SP와 SCS 포인터를 함께 관리해야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 33행
```asm
#include <asm/smp.h>
```
보조 CPU 시작과 초기 부팅 상태를 전달하는 SMP 정의를 가져옵니다. secondary_data와 부팅 상태값을 통해 다른 CPU와 시작 과정을 맞춥니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 34행
```asm
#include <asm/sysreg.h>
```
ARM 시스템 레지스터의 번호·필드 및 일반 이름으로 지원되지 않는 접근 인코딩을 가져옵니다. 기능 비트를 검사하거나 제어값을 구성할 때 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 35행
```asm
#include <asm/stacktrace/frame.h>
```
스택 추적기의 프레임 메타데이터 형식을 가져옵니다. 예외가 끊은 커널 프레임인지 추적의 마지막 프레임인지 표시하여 FP 연결을 안전하게 해석하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 36행
```asm
#include <asm/thread_info.h>
```
task의 저수준 실행 플래그와 스택 관련 정의를 가져옵니다. 예외 진입·복귀에서 single-step·SSBD·MTE 등 task별 처리를 고를 때 쓰는 정보입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 37행
```asm
#include <asm/virt.h>
```
EL1·EL2 부팅 모드와 가상화 상태 표시를 가져옵니다. CPU가 어디서 시작했는지와 현재 어느 EL에서 실행하는지를 구분해 기록하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 39행
```asm
#include "efi-header.S"
```
ARM64 Image 앞부분에 함께 넣을 EFI PE/COFF 헤더 생성 코드를 가져옵니다. 지원 빌드에서 펌웨어가 같은 파일을 EFI 실행 이미지로도 인식하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 41행
```asm
#if (PAGE_OFFSET & 0x1fffff) != 0
```
PAGE_OFFSET는 선형 매핑이 시작할 가상 주소입니다. 하위 21비트가 0인지 검사해 최소 2MiB 경계 정렬을 보장합니다. 맞지 않으면 다음 #error로 빌드를 중단하여 초기 페이지 표의 블록 매핑 전제가 깨진 이미지를 만들지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 42행
```asm
#error PAGE_OFFSET must be at least 2MB aligned
```
필요한 배치 조건이 성립하지 않으면 빌드를 멈춥니다. 잘못된 전제로 부팅하도록 두지 않는 검사입니다. 대상: PAGE_OFFSET must be at least 2MB aligned

### 43행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

## 64바이트 Image 헤더와 실행 전 필요한 조건
원본 44–73행

### 57행
```asm
	__HEAD
```
Image 맨 앞에 놓일 코드 영역으로 출력 위치를 바꿉니다. 부트로더가 처음 실행할 바이트의 위치를 정하는 빌드 단계의 지시입니다.

### 61행
```asm
	efi_signature_nop			// special NOP to identity as PE/COFF executable
```
EFI 로더가 알아볼 MZ 서명과 CPU가 읽을 첫 명령을 같은 바이트에 담습니다. EFI 설정이 없으면 일반 NOP가 됩니다.

### 62행
```asm
	b	primary_entry			// branch to kernel start, magic
```
primary_entry부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 63행
```asm
	.quad	0				// Image load offset from start of RAM, little-endian
```
각 값을 8바이트 크기로 파일 안에 기록합니다. 0는 CPU가 지금 계산할 식이 아니라 어셈블·링크 단계에서 채울 데이터입니다.

### 64행
```asm
	le64sym	_kernel_size_le			// Effective size of kernel image, little-endian
```
커널의 바이트 순서 설정과 별개로 부팅 규약이 요구하는 리틀 엔디언 64비트 필드를 만듭니다. 같은 숫자라도 저장하는 바이트 순서가 중요합니다.

### 65행
```asm
	le64sym	_kernel_flags_le		// Informative flags, little-endian
```
커널의 바이트 순서 설정과 별개로 부팅 규약이 요구하는 리틀 엔디언 64비트 필드를 만듭니다. 같은 숫자라도 저장하는 바이트 순서가 중요합니다.

### 66행
```asm
	.quad	0				// reserved
```
각 값을 8바이트 크기로 파일 안에 기록합니다. 0는 CPU가 지금 계산할 식이 아니라 어셈블·링크 단계에서 채울 데이터입니다.

### 67행
```asm
	.quad	0				// reserved
```
각 값을 8바이트 크기로 파일 안에 기록합니다. 0는 CPU가 지금 계산할 식이 아니라 어셈블·링크 단계에서 채울 데이터입니다.

### 68행
```asm
	.quad	0				// reserved
```
각 값을 8바이트 크기로 파일 안에 기록합니다. 0는 CPU가 지금 계산할 식이 아니라 어셈블·링크 단계에서 채울 데이터입니다.

### 69행
```asm
	.ascii	ARM64_IMAGE_MAGIC		// Magic number
```
문자열 ARM64_IMAGE_MAGIC를 이미지에 넣습니다. 자동으로 끝의 0 바이트를 붙이지 않습니다.

### 70행
```asm
	.long	.Lpe_header_offset		// Offset to the PE header.
```
각 값을 4바이트 크기로 파일 안에 기록합니다. .Lpe_header_offset는 CPU가 지금 계산할 식이 아니라 어셈블·링크 단계에서 채울 데이터입니다.

### 72행
```asm
	__EFI_PE_HEADER
```
EFI 로더에게 줄 이미지 설명서를 생성합니다. 로더가 찾는 진입 주소·크기·권한 정보이며 CPU 함수 호출이 아닙니다.

## primary_entry: 부트 CPU의 실행 순서
원본 74–132행

### 74행
```asm
	.section ".idmap.text","a"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".idmap.text","a"

### 85행
```asm
SYM_CODE_START(primary_entry)
```
이 위치부터 (primary_entry)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 86행
```asm
	bl	record_mmu_state
```
현재 EL과 SCTLR의 MMU·데이터 캐시·엔디언 상태를 확인하는 record_mmu_state를 호출합니다. 결과는 x19에 남으며, 기대한 엔디언과 다르면 MMU를 끄고 엔디언을 맞춥니다. 바로 뒤의 캐시 처리와 EL 초기화가 이 기록을 사용합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 87행
```asm
	bl	preserve_boot_args
```
부트로더의 x0~x3를 boot_args에 보존하고 FDT 물리 주소 x0를 x21에도 남깁니다. 이후 C 호출이 인수 레지스터를 바꾸어도 장치 트리와 부팅 입력을 잃지 않으려는 단계입니다. MMU를 끈 진입이면 저장 영역의 캐시 무효화도 수행합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.

### 89행
```asm
	adrp	x1, early_init_stack
```
초기 C 코드가 사용할 4KiB 스택의 상단 주소를 구합니다. early_init_stack은 링커가 스택 공간 뒤에 두는 기호이므로 뒤의 SP 설정이 낮은 주소 쪽으로 자랄 공간을 갖습니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다. ADRP가 만드는 주소는 4KiB 경계까지만입니다. 심벌의 하위 12비트가 0이면 그 자체가 심벌 주소이고, 그렇지 않으면 별도 오프셋 계산이 필요합니다.

### 90행
```asm
	mov	sp, x1
```
이제 스택으로 사용할 위치를 x1의 값으로 바꿉니다. 메모리 내용을 옮기는 작업은 아닙니다. 다음 저장·호출부터 새 스택 위치를 사용합니다.

### 91행
```asm
	mov	x29, xzr
```
x29에 0을 넣습니다. 원래 x29의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 92행
```asm
	adrp	x0, __pi_init_idmap_pg_dir
```
MMU를 켠 직후에도 현재 코드를 같은 주소로 가져올 수 있도록 만들 초기 identity mapping의 루트 저장 공간을 x0에 전달합니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다. ADRP가 만드는 주소는 4KiB 경계까지만입니다. 심벌의 하위 12비트가 0이면 그 자체가 심벌 주소이고, 그렇지 않으면 별도 오프셋 계산이 필요합니다.

### 93행
```asm
	mov	x1, xzr
```
페이지 표 속성에서 추가로 지울 비트 마스크를 0으로 전달합니다. NULL 페이지 표 포인터가 아니라 create_init_idmap의 두 번째 인수입니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 94행
```asm
	bl	__pi_create_init_idmap
```
현재 물리 주소에서 접근할 수 있는 페이지 표에 초기 identity mapping을 만듭니다. x0는 표 공간, x1은 지울 속성 마스크이며 반환 x0는 사용한 표 영역의 끝입니다. MMU를 켜도 다음 명령과 초기 데이터에 접근하도록 준비합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 101행
```asm
	cbnz	x19, 0f
```
x19가 0이 아니면 MMU와 데이터 캐시를 켠 채 들어온 경우의 clean 경로로 갑니다. 0이면 캐시를 끈 초기 표 기록 뒤에 남을 수 있는 캐시 사본을 무효화하는 경로를 사용합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 102행
```asm
	dmb     sy
```
앞뒤의 메모리 접근이 필요한 순서로 관찰되도록 제한합니다. “먼저 썼으니 다른 CPU도 먼저 볼 것”이라는 가정을 명시적인 규칙으로 만드는 줄입니다. 캐시를 직접 비우는 명령은 아닙니다.

### 103행
```asm
	mov	x1, x0				// end of used region
```
x1에 x0에 들어 있는 값을 넣습니다. 원래 x1의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 104행
```asm
	adrp    x0, __pi_init_idmap_pg_dir
```
x0에 __pi_init_idmap_pg_dir의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 105행
```asm
	adr_l	x2, dcache_inval_poc
```
x2에 dcache_inval_poc의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 106행
```asm
	blr	x2
```
x2에 넣은 dcache_inval_poc를 호출해 x0부터 x1 직전까지의 초기 페이지 표 캐시 사본을 무효화합니다. MMU·캐시가 꺼진 상태에서 작성한 표에 낡은 캐시 라인이 나중에 덮어써지는 문제를 피하기 위한 처리입니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 107행
```asm
	b	1f
```
1f (L119)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 114행
```asm
0:	adrp	x0, __idmap_text_start
```
x0에 __idmap_text_start의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 115행
```asm
	adr_l	x1, __idmap_text_end
```
x1에 __idmap_text_end의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 116행
```asm
	adr_l	x2, dcache_clean_poc
```
x2에 dcache_clean_poc의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 117행
```asm
	blr	x2
```
캐시를 켠 채 들어왔으므로 identity-mapped 코드 영역 [x0, x1)을 dcache_clean_poc로 PoC까지 정리합니다. 뒤에서 MMU·캐시 상태를 바꾸어도 필요한 코드가 메모리에서 보이게 하려는 목적입니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 119행
```asm
1:	mov	x0, x19
```
x0에 x19에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 120행
```asm
	bl	init_kernel_el			// w0=cpu_boot_mode
```
기록한 MMU 상태를 x0로 전달해 EL1 또는 EL2 진입 상태를 정리합니다. 이 함수는 필요한 제어 레지스터를 설정하고 ERET로 EL1 실행을 맞춘 뒤 x0에 출발 EL과 E2H 플래그를 남깁니다. 최종 VHE 선택은 뒤의 finalise_el2에서 합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 121행
```asm
	mov	x20, x0
```
x20에 x0에 들어 있는 값을 넣습니다. 원래 x20의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 129행
```asm
	bl	__cpu_setup			// initialise processor
```
MMU를 켤 CPU 환경을 준비합니다. 지역 TLB를 정리하고 MAIR·TCR 등 주소 변환 속성과 접근 제어를 설정한 뒤, 나중에 SCTLR_EL1에 쓸 값을 x0로 반환합니다. 페이지 표 자체를 이 호출 하나가 전부 만드는 것은 아닙니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 130행
```asm
	b	__primary_switch
```
__primary_switch부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 131행
```asm
SYM_CODE_END(primary_entry)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## record_mmu_state: 엔디언, M, C와 NZCV
원본 133–166행

### 133행
```asm
	__INIT
```
부팅 초기화용 코드 영역으로 옮깁니다. 이 분류는 나중에 초기화 메모리를 관리할 때 사용하며, 지금 CPU가 점프하는 명령은 아닙니다.

### 134행
```asm
SYM_CODE_START_LOCAL(record_mmu_state)
```
이 위치부터 (record_mmu_state)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 135행
```asm
	mrs	x19, CurrentEL
```
CurrentEL의 EL 필드를 읽어 지금 EL1인지 EL2인지 판별합니다. 읽어야 할 SCTLR이 어느 레벨 것인지 선택하기 위한 정보이며 CPU 번호가 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 136행
```asm
	cmp	x19, #CurrentEL_EL2
```
x19와 CurrentEL_EL2를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 137행
```asm
	mrs	x19, sctlr_el1
```
우선 EL1의 SCTLR을 읽습니다. SCTLR은 MMU 활성 M, 데이터 캐시 C, 데이터 엔디언 EE 같은 실행 환경을 담습니다. 현재 EL2라면 뒤에서 SCTLR_EL2 값으로 교체합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 138행
```asm
	b.ne	0f
```
앞서 계산한 조건 플래그가 Z=0이면 0f (L140)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 139행
```asm
	mrs	x19, sctlr_el2
```
EL2에서 진입한 경우 실제 실행에 적용 중인 SCTLR_EL2를 읽어 x19에 둡니다. EL1 레지스터만 검사하면 부트로더가 실제로 사용한 MMU 상태를 잘못 기록할 수 있습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 140행
```asm
0:
```
여기에 0라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 141행
```asm
CPU_LE( tbnz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
```
리틀 엔디언 커널에서만 생성되는 검사입니다. 현재 SCTLR의 EE가 1이면 CPU 데이터 바이트 순서가 커널과 달라 수정 경로로 분기합니다. CPU_LE 자체는 빌드 단계의 선택이고, 포함된 TBNZ는 실행 중 EE 비트를 검사합니다.

### 142행
```asm
CPU_BE( tbz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
```
빅 엔디언 커널에서만 생성되는 검사입니다. EE가 0이면 커널이 요구한 데이터 바이트 순서와 다르므로 수정 경로로 갑니다. CPU_BE 자체는 빌드 단계의 선택이고, 포함된 TBZ는 실행 중 EE 비트를 검사합니다.

### 143행
```asm
	tst	x19, #SCTLR_ELx_C		// Z := (C == 0)
```
SCTLR.C로 데이터 캐시가 활성 상태였는지 검사합니다. C가 0일 때 Z 플래그를 세워 뒤의 CSEL이 부팅 MMU 기록을 0으로 만들도록 합니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.

### 144행
```asm
	and	x19, x19, #SCTLR_ELx_M		// isolate M bit
```
SCTLR 전체 설정에서 MMU 활성 M 비트만 남깁니다. x19를 이후의 초기 캐시 처리에서 쓰는 짧은 부팅 상태 기록으로 바꾸는 단계입니다. AND는 두 피연산자의 같은 위치 비트가 모두 1인 곳만 결과에 남깁니다.

### 145행
```asm
	csel	x19, xzr, x19, eq		// clear x19 if Z
```
앞서 캐시가 꺼져 있으면 x19를 0으로, 켜져 있으면 M 비트 값을 남깁니다. 따라서 이후 x19가 0이 아닌 조건은 M과 C가 모두 켜진 진입 상태입니다. CSEL은 NZCV 조건에 따라 두 값 중 하나를 선택하며 실행 위치를 분기하거나 조건 플래그를 갱신하지 않습니다.

### 146행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 154행
```asm
1:	eor	x19, x19, #SCTLR_ELx_EE
```
마스크에서 1인 비트를 반전합니다. 엔디언 비트처럼 현재 상태를 반대로 바꿀 때 사용할 수 있습니다. 여기서 결과를 받는 레지스터는 x19입니다.

### 155행
```asm
	bic	x19, x19, #SCTLR_ELx_M
```
지정 마스크에 해당하는 비트를 지웁니다. 나머지 설정을 유지하며 특정 기능만 끌 수 있습니다. 여기서 결과를 받는 레지스터는 x19입니다.

### 156행
```asm
	b.ne	2f
```
앞서 계산한 조건 플래그가 Z=0이면 2f (L160)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 157행
```asm
	pre_disable_mmu_workaround
```
Qualcomm Falkor erratum E1041 우회 빌드에서는 MMU를 끄는 전환에 필요한 ISB를 넣습니다. 설정이 없으면 빈 매크로입니다. SCTLR의 M 비트를 1에서 0으로 바꾸는 MSR 바로 앞에서 실행 순서를 맞추는 CPU 구현상의 보완입니다.

### 158행
```asm
	msr	sctlr_el2, x19
```
EE를 커널 엔디언으로 맞추고 M을 지운 SCTLR 값을 EL2에 씁니다. EL2 진입에서 바이트 순서와 MMU 해석을 함께 정리하며 뒤의 ISB가 새 실행 환경을 반영합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 159행
```asm
	b	3f
```
3f (L162)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 160행
```asm
2:	pre_disable_mmu_workaround
```
Qualcomm Falkor erratum E1041 우회 빌드에서는 MMU를 끄는 전환에 필요한 ISB를 넣습니다. 설정이 없으면 빈 매크로입니다. SCTLR의 M 비트를 1에서 0으로 바꾸는 MSR 바로 앞에서 실행 순서를 맞추는 CPU 구현상의 보완입니다.

### 161행
```asm
	msr	sctlr_el1, x19
```
EL1에서 들어온 경우 수정한 SCTLR_EL1을 씁니다. 여기서는 커널과 맞는 엔디언으로 바꾸면서 MMU를 끈 상태를 만들고 이후 x19에도 꺼진 상태를 기록합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 162행
```asm
3:	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 163행
```asm
	mov	x19, xzr
```
x19에 0을 넣습니다. 원래 x19의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 164행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 165행
```asm
SYM_CODE_END(record_mmu_state)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## preserve_boot_args: 32바이트 저장과 tail call
원본 167–186행

### 170행
```asm
SYM_CODE_START_LOCAL(preserve_boot_args)
```
이 위치부터 (preserve_boot_args)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 171행
```asm
	mov	x21, x0				// x21=FDT
```
x21에 x0에 들어 있는 값을 넣습니다. 원래 x21의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 173행
```asm
	adr_l	x0, boot_args			// record the contents of
```
x0에 boot_args의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 174행
```asm
	stp	x21, x1, [x0]			// x0 .. x3 at kernel entry
```
x21, x1의 값을 x0가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x21, 그보다 8바이트 높은 위치에는 x1의 값이 들어갑니다.

### 175행
```asm
	stp	x2, x3, [x0, #16]
```
x2, x3의 값을 x0를 기준으로 16만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x2, 그보다 8바이트 높은 위치에는 x3의 값이 들어갑니다.

### 177행
```asm
	cbnz	x19, 0f				// skip cache invalidation if MMU is on
```
x19의 값가 0이 아니면 0f (L183)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 178행
```asm
	dmb	sy				// needed before dc ivac with
```
앞뒤의 메모리 접근이 필요한 순서로 관찰되도록 제한합니다. “먼저 썼으니 다른 CPU도 먼저 볼 것”이라는 가정을 명시적인 규칙으로 만드는 줄입니다. 캐시를 직접 비우는 명령은 아닙니다.

### 181행
```asm
	add	x1, x0, #0x20			// 4 x 8 bytes
```
x0를 기준으로 0x20을 더해 x1에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 182행
```asm
	b	dcache_inval_poc		// tail call
```
dcache_inval_poc부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 183행
```asm
0:	str_l   x19, mmu_enabled_at_boot, x0
```
진입 때 MMU와 데이터 캐시가 켜졌는지 기록한 x19를 mmu_enabled_at_boot에 보존합니다. 뒤의 초기화 코드가 부트로더가 남긴 실행 상태를 판단할 수 있도록 전역 자료로 남기며 x0는 주소 계산용입니다. str_l은 임시 레지스터로 심벌의 주소를 구하고 STR로 그 메모리에 값을 쓰는 매크로입니다.

### 184행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 185행
```asm
SYM_CODE_END(preserve_boot_args)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## init_cpu_task: current, 스택, 프레임 끝, per-CPU
원본 187–214행

### 195행
```asm
	.macro	init_cpu_task tsk, tmp1, tmp2
```
task 하나를 이 CPU의 실행 주체로 설치하는 매크로를 정의합니다. tsk는 task_struct 주소이고 tmp1·tmp2는 임시 레지스터입니다. current 식별, 일반 스택, unwind 종료 표식, SCS, per-CPU 기준을 한 묶음으로 준비합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 196행
```asm
	msr	sp_el0, \tsk
```
SP_EL0에 task_struct 주소를 기록하여 커널 실행 중 current를 찾을 수 있게 합니다. 이 시점의 SP_EL0는 사용자 스택을 복원하는 용도가 아니며 실제 커널 SP는 다음 명령들에서 별도로 정합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 198행
```asm
	ldr	\tmp1, [\tsk, #TSK_STACK]
```
task_struct의 stack 포인터를 읽습니다. TSK_STACK은 asm-offsets가 C 구조체 배치에서 생성한 바이트 offset이므로 고정 숫자를 손으로 가정하지 않습니다. 이 주소 형식은 기준 레지스터 \tsk 자체를 갱신하지 않습니다.

### 199행
```asm
	add	sp, \tmp1, #THREAD_SIZE
```
할당된 커널 스택 시작에 THREAD_SIZE를 더해 높은 주소의 끝을 SP로 잡습니다. AArch64 스택은 이후 저장 때 낮은 주소 방향으로 내려갑니다. 이 명령은 SP의 주소값을 조정하며, 확보하거나 반환하는 스택 공간의 데이터를 직접 저장하거나 지우지는 않습니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 200행
```asm
	sub	sp, sp, #PT_REGS_SIZE
```
최상단에서 pt_regs 크기를 미리 빼 예외 상태와 종료 프레임 메타데이터가 들어갈 공간을 확보합니다. 이 명령은 SP의 주소값을 조정하며, 확보하거나 반환하는 스택 공간의 데이터를 직접 저장하거나 지우지는 않습니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 202행
```asm
	stp	xzr, xzr, [sp, #S_STACKFRAME]
```
xzr, xzr의 값을 sp를 기준으로 S_STACKFRAME만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 xzr, 그보다 8바이트 높은 위치에는 xzr의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 203행
```asm
	mov	\tmp1, #FRAME_META_TYPE_FINAL
```
FRAME_META_TYPE_FINAL은 스택 추적의 끝임을 표시하는 메타데이터 종류입니다. 일반적인 이전 FP·LR 연결을 계속 따라가면 안 되는 초기 task 스택이기 때문에 이 값을 사용합니다. MOV는 \tmp1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 204행
```asm
	str	\tmp1, [sp, #S_STACKFRAME_TYPE]
```
\tmp1의 값을 sp를 기준으로 S_STACKFRAME_TYPE만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 205행
```asm
	add	x29, sp, #S_STACKFRAME
```
sp를 기준으로 S_STACKFRAME을 더해 x29에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 207행
```asm
	scs_load_current
```
태스크에 저장해 둔 반환 주소 보호용 스택 위치를 되살립니다. 일반 SP를 읽는 것과는 다른 상태입니다. 여기의 인자는 없음입니다.

### 209행
```asm
	adr_l	\tmp1, __per_cpu_offset
```
각 논리 CPU가 사용할 per-CPU 영역까지의 거리 표 __per_cpu_offset을 구합니다. 모든 CPU가 같은 전역 이름으로 자기 자료를 찾게 해 주는 기준 표입니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다. adr_l 매크로는 ADRP와 ADD로 주소의 페이지 부분과 하위 오프셋을 합칩니다.

### 210행
```asm
	ldr	w\tmp2, [\tsk, #TSK_TI_CPU]
```
task의 thread_info에 기록된 논리 CPU 번호를 읽습니다. MPIDR 하드웨어 ID와 같은 값이라고 가정하지 않습니다. 실제로 \tsk + (TSK_TI_CPU) 주소의 메모리 4바이트를 w\tmp2에 읽습니다. 이 주소 형식은 기준 레지스터 \tsk 자체를 갱신하지 않습니다.

### 211행
```asm
	ldr	\tmp1, [\tmp1, \tmp2, lsl #3]
```
CPU 번호에 8을 곱해 unsigned long 배열에서 이 CPU의 per-CPU offset을 읽습니다. 그 결과를 다음 set_this_cpu_offset이 접근 기준으로 설치합니다. 실제로 \tmp1 + (\tmp2 × 8) 주소의 메모리 8바이트를 \tmp1에 읽습니다.

### 212행
```asm
	set_this_cpu_offset \tmp1
```
현재 CPU에 속한 변수들에 접근할 기준 오프셋을 기록합니다. 여러 CPU가 같은 변수 이름을 사용해도 서로 다른 저장 공간을 찾게 합니다.

### 213행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## __primary_switched: 최종 가상 주소에서 start_kernel까지
원본 215–249행

### 220행
```asm
SYM_FUNC_START_LOCAL(__primary_switched)
```
이 위치부터 (__primary_switched)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 221행
```asm
	adr_l	x4, init_task
```
x4에 init_task의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 222행
```asm
	init_cpu_task x4, x5, x6
```
CPU가 사용할 태스크, 커널 스택, 호출 프레임 끝, CPU별 변수 접근 기준을 준비합니다. C 코드를 실행하기 위한 주변 상태를 맞추는 단계입니다.

### 224행
```asm
	adr_l	x8, vectors			// load VBAR_EL1 with virtual
```
x8에 vectors의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 225행
```asm
	msr	vbar_el1, x8			// vector table address
```
예외가 발생할 때 CPU가 사용할 커널 벡터 표의 가상 주소를 VBAR_EL1에 설치합니다. 현재는 최종 커널 매핑으로 옮겨 왔으므로 물리 주소가 아니라 정상 커널 주소의 vectors를 사용합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 226행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 228행
```asm
	stp	x29, x30, [sp, #-16]!
```
x29, x30의 값을 sp를 기준으로 -16만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x29, 그보다 8바이트 높은 위치에는 x30의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다. 쓰기 전에 기준 주소를 갱신합니다. SP의 음수 오프셋이면 낮은 주소 쪽에 저장 공간을 확보하는 뜻입니다.

### 229행
```asm
	mov	x29, sp
```
현재 SP의 숫자를 x29에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 231행
```asm
	str_l	x21, __fdt_pointer, x5		// Save FDT pointer
```
보존해 둔 FDT 물리 주소 x21을 __fdt_pointer에 기록합니다. 뒤의 setup_arch가 장치 트리를 매핑하고 해석할 입력입니다. str_l은 임시 레지스터로 심벌의 주소를 구하고 STR로 그 메모리에 값을 쓰는 매크로입니다.

### 233행
```asm
	adrp	x4, _text			// Save the offset between
```
x4에 _text의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 234행
```asm
	sub	x4, x4, x0			// the kernel virtual and
```
x4를 기준으로 x0을 빼서 x4에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 235행
```asm
	str_l	x4, kimage_voffset, x5		// physical mappings
```
커널 기호의 가상 주소와 물리 주소 사이의 차이를 kimage_voffset에 보관합니다. 커널 이미지의 주소 변환에 쓸 값이며 일반 선형 매핑의 모든 주소를 같은 식으로 바꾼다는 뜻은 아닙니다. str_l은 임시 레지스터로 심벌의 주소를 구하고 STR로 그 메모리에 값을 쓰는 매크로입니다.

### 237행
```asm
	mov	x0, x20
```
x0에 x20에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 238행
```asm
	bl	set_cpu_boot_mode_flag
```
x20에서 가져온 출발 EL 기록을 전역 __boot_cpu_mode에 남깁니다. 여러 CPU가 일관된 권한 상태로 부팅되었는지 이후 코드가 확인할 수 있게 합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 240행
```asm
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
```
KASAN은 커널 메모리의 범위 밖 접근이나 해제 후 사용 등을 찾는 검사 기능입니다. GENERIC과 SW_TAGS는 소프트웨어가 참조할 shadow 메모리가 필요하므로 이 두 설정 중 하나가 켜졌을 때만 다음 초기 매핑 호출을 포함합니다. HW_TAGS는 MTE 기반의 다른 방식이어서 이 조건에는 들어가지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 241행
```asm
	bl	kasan_early_init
```
kasan_early_init은 전체 shadow 영역을 초기 shadow 페이지로 연결하여 초기 C 코드의 KASAN 계측이 아직 준비되지 않은 주소를 읽지 않도록 합니다. 실제 메모리 구간별 최종 shadow 구성은 뒤의 KASAN 초기화에서 이루어집니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 242행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 243행
```asm
	mov	x0, x20
```
x0에 x20에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 244행
```asm
	bl	finalise_el2			// Prefer VHE if possible
```
출발 EL이 EL2이고 현재 EL1이라면 HVC로 hyp stub에 EL2 마무리를 요청합니다. CPU의 VHE 지원과 override 정책이 허용하면 호스트를 EL2h로 옮길 수 있고, 조건이 맞지 않으면 EL1에서 계속합니다. EL2 출발 기록만으로 이미 VHE라고 판단하면 안 됩니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 245행
```asm
	ldp	x29, x30, [sp], #16
```
sp가 가리키는 메모리 위치에 보관된 값을 x29, x30로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x29, 그보다 8바이트 높은 위치에서 x30의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 246행
```asm
	bl	start_kernel
```
준비된 커널 스택·주소 변환·예외 벡터를 바탕으로 공통 C 초기화 start_kernel로 넘깁니다. 메모리·스케줄러 등 일반 커널 초기화가 여기서 이어지며 정상적으로 이 어셈블리로 반환하지 않습니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 247행
```asm
	ASM_BUG()
```
start_kernel은 정상적으로 돌아오지 않아야 합니다. 만약 반환했다면 이어서 다른 초기 코드로 흘러가지 않도록 BRK와 BUG 정보를 만들어 오류를 드러냅니다.

### 248행
```asm
SYM_FUNC_END(__primary_switched)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## init_kernel_el의 EL1 경로: RET 대신 ERET
원본 250–285행

### 254행
```asm
	.section ".idmap.text","a"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".idmap.text","a"

### 270행
```asm
SYM_FUNC_START(init_kernel_el)
```
이 위치부터 (init_kernel_el)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 271행
```asm
	mrs	x1, CurrentEL
```
실제 현재 EL을 읽어 EL1 초기화와 EL2 초기화 중 올바른 경로를 고릅니다. x0의 부팅 MMU 기록과 별도 정보입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 272행
```asm
	cmp	x1, #CurrentEL_EL2
```
x1와 CurrentEL_EL2를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 273행
```asm
	b.eq	init_el2
```
앞서 계산한 조건 플래그가 Z=1이면 init_el2로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 275행
```asm
SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)
```
기존 함수 안의 중간 위치에 이름을 붙입니다. 다른 분기에서 찾아올 수 있지만 새 함수를 호출하거나 스택 프레임을 만드는 동작은 아닙니다.

### 276행
```asm
	mov_q	x0, INIT_SCTLR_EL1_MMU_OFF
```
INIT_SCTLR_EL1_MMU_OFF는 EL1에서 쓸 기본 제어값이되 MMU를 꺼 둔 설정입니다. 부트로더의 임의 SCTLR 상태를 그대로 이어 쓰지 않도록 준비합니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 277행
```asm
	pre_disable_mmu_workaround
```
Qualcomm Falkor erratum E1041 우회 빌드에서는 MMU를 끄는 전환에 필요한 ISB를 넣습니다. 설정이 없으면 빈 매크로입니다. SCTLR의 M 비트를 1에서 0으로 바꾸는 MSR 바로 앞에서 실행 순서를 맞추는 CPU 구현상의 보완입니다.

### 278행
```asm
	msr	sctlr_el1, x0
```
기본 EL1 제어값을 설치하여 MMU가 꺼진 일관된 초기 실행 환경을 만듭니다. 뒤에서 __cpu_setup과 __enable_mmu가 정식 주소 변환 설정을 완성합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 279행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 280행
```asm
	mov_q	x0, INIT_PSTATE_EL1
```
INIT_PSTATE_EL1은 ERET 후 사용할 EL1h와 예외 마스크 등의 초기 PSTATE입니다. 단순한 숫자 반환값이 아닙니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 281행
```asm
	msr	spsr_el1, x0
```
ERET가 복원할 PSTATE를 SPSR_EL1에 넣습니다. RET와 달리 ERET를 사용해 상태까지 정규화하려는 준비입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 282행
```asm
	msr	elr_el1, lr
```
BL이 LR에 남긴 다음 명령 주소를 ELR_EL1에 넣습니다. ERET가 이 주소로 돌아가면서 SPSR_EL1의 상태도 적용하도록 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 283행
```asm
	mov	w0, #BOOT_CPU_MODE_EL1
```
w0에 BOOT_CPU_MODE_EL1라는 값을 넣습니다. 원래 w0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다. W 레지스터에 쓰므로 대응 X 레지스터의 상위 절반도 0이 됩니다.

### 284행
```asm
	eret
```
예외가 끝났으므로 저장해 둔 PC와 실행 상태를 함께 되살립니다. 일반 RET와 달리 어느 예외 레벨과 스택 모드로 돌아갈지도 SPSR의 내용에 따라 정해집니다.

## init_kernel_el의 EL2 경로: HCR, EL12, boot flags
원본 286–332행

### 286행
```asm
SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
```
기존 함수 안의 중간 위치에 이름을 붙입니다. 다른 분기에서 찾아올 수 있지만 새 함수를 호출하거나 스택 프레임을 만드는 동작은 아닙니다.

### 287행
```asm
	msr	elr_el2, lr
```
EL2 경로 역시 호출자의 복귀 주소를 ELR_EL2에 보관합니다. 뒤의 ERET가 EL1으로 내려오면서 이 위치부터 이어 실행하게 됩니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 290행
```asm
	cbz	x0, 0f
```
x0의 값가 0이면 0f (L300)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 291행
```asm
	adrp	x0, __hyp_idmap_text_start
```
x0에 __hyp_idmap_text_start의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 292행
```asm
	adr_l	x1, __hyp_text_end
```
x1에 __hyp_text_end의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 293행
```asm
	adr_l	x2, dcache_clean_poc
```
x2에 dcache_clean_poc의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 294행
```asm
	blr	x2
```
EL2를 MMU·캐시가 켜진 상태로 시작했다면 hyp 코드 [x0,x1)을 PoC까지 clean합니다. EL2 MMU를 끈 뒤에도 메모리에서 초기 hyp 코드를 안정적으로 가져오기 위한 처리입니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 296행
```asm
	mov_q	x0, INIT_SCTLR_EL2_MMU_OFF
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. 즉시값 조각과 재배치 표현을 사용하며, 메모리에서 그 주소의 본문을 복사하는 작업은 아닙니다.

### 297행
```asm
	pre_disable_mmu_workaround
```
Qualcomm Falkor erratum E1041 우회 빌드에서는 MMU를 끄는 전환에 필요한 ISB를 넣습니다. 설정이 없으면 빈 매크로입니다. SCTLR의 M 비트를 1에서 0으로 바꾸는 MSR 바로 앞에서 실행 순서를 맞추는 CPU 구현상의 보완입니다.

### 298행
```asm
	msr	sctlr_el2, x0
```
EL2의 SCTLR을 MMU off 기본값으로 바꿉니다. 이 명령 전후의 캐시 clean과 ISB를 함께 봐야 기존 변환을 중단하는 순서가 설명됩니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 299행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 300행
```asm
0:
```
여기에 0라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 302행
```asm
	init_el2_hcr	HCR_HOST_NVHE_FLAGS
```
EL2가 하위 실행 환경을 어떻게 제어할지 기본값을 정합니다. 일부 CPU는 VHE 상태를 끌 수 없어 실제 기능에 맞춘 처리가 필요합니다.

### 303행
```asm
	init_el2_state
```
EL1 실행에 영향을 주는 상위 EL2의 타이머·인터럽트·접근 제어를 준비합니다. 하위 레벨만 초기화해도 상위 제약은 남을 수 있기 때문입니다.

### 306행
```asm
	adr_l	x0, __hyp_stub_vectors
```
x0에 __hyp_stub_vectors의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 307행
```asm
	msr	vbar_el2, x0
```
EL2 예외를 초기 hyp stub의 벡터로 받도록 VBAR_EL2에 설치합니다. EL1에서 HVC로 finalise_el2 등을 요청할 최소 처리 경로를 마련합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 308행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 310행
```asm
	mov_q	x1, INIT_SCTLR_EL1_MMU_OFF
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. 즉시값 조각과 재배치 표현을 사용하며, 메모리에서 그 주소의 본문을 복사하는 작업은 아닙니다.

### 312행
```asm
	mrs	x0, hcr_el2
```
HCR_EL2를 읽어 E2H가 실제로 켜져 있는지 확인합니다. HCR은 EL2가 EL1 실행을 제어하는 레지스터이며 E2H에 따라 EL1 이름의 일부 레지스터 접근 의미가 달라집니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 313행
```asm
	and	x0, x0, #HCR_E2H
```
필요한 비트만 남깁니다. 다른 정보가 섞인 값에서 관심 있는 부분을 고르는 작업입니다. 여기서 결과를 받는 레지스터는 x0입니다.

### 314행
```asm
	cbz	x0, 2f
```
x0의 값가 0이면 2f (L321)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 317행
```asm
	msr_s	SYS_SCTLR_EL12, x1
```
E2H가 켜진 EL2에서는 SCTLR_EL1 이름이 호스트 EL2 쪽에 연결될 수 있으므로, EL12 별칭으로 실제 하위 EL1의 SCTLR을 초기화합니다. 잘못된 레벨의 MMU를 설정하지 않기 위한 구분입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 318행
```asm
	mov	x2, #BOOT_CPU_FLAG_E2H
```
BOOT_CPU_FLAG_E2H는 EL2 시작 당시 E2H 상태를 함께 전달할 상위 플래그입니다. 하위 w0의 BOOT_CPU_MODE_EL2와 별도 정보입니다. MOV는 x2의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 319행
```asm
	b	3f
```
3f (L324)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 321행
```asm
2:
```
여기에 2라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 322행
```asm
	msr	sctlr_el1, x1
```
E2H가 꺼져 있으면 SCTLR_EL1 이름으로 실제 하위 EL1 제어 레지스터를 설정할 수 있습니다. 이 경로는 EL12 별칭이 필요 없습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 323행
```asm
	mov	x2, xzr
```
x2에 0을 넣습니다. 원래 x2의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 324행
```asm
3:
```
여기에 3라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 325행
```asm
	mov	x0, #INIT_PSTATE_EL1
```
x0에 INIT_PSTATE_EL1라는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 326행
```asm
	msr	spsr_el2, x0
```
EL2에서 ERET한 뒤 실행할 EL1h 및 마스크 상태를 SPSR_EL2에 준비합니다. 출발은 EL2지만 이 ERET 직후의 실행은 EL1입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 328행
```asm
	mov	w0, #BOOT_CPU_MODE_EL2
```
w0에 BOOT_CPU_MODE_EL2라는 값을 넣습니다. 원래 w0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다. W 레지스터에 쓰므로 대응 X 레지스터의 상위 절반도 0이 됩니다.

### 329행
```asm
	orr	x0, x0, x2
```
여러 비트의 설정을 합칩니다. 이미 1인 비트를 유지하면서 지정한 설정을 추가할 때 사용합니다. 여기서 결과를 받는 레지스터는 x0입니다.

### 330행
```asm
	eret
```
예외가 끝났으므로 저장해 둔 PC와 실행 상태를 함께 되살립니다. 일반 RET와 달리 어느 예외 레벨과 스택 모드로 돌아갈지도 SPSR의 내용에 따라 정해집니다.

### 331행
```asm
SYM_FUNC_END(init_kernel_el)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 보조 CPU의 두 진입점과 holding pen
원본 333–360행

### 337행
```asm
SYM_FUNC_START(secondary_holding_pen)
```
이 위치부터 (secondary_holding_pen)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 338행
```asm
	mov	x0, xzr
```
x0에 0을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 339행
```asm
	bl	init_kernel_el			// w0=cpu_boot_mode
```
holding pen에서 대기할 보조 CPU의 EL 상태를 정규화합니다. x0=0으로 MMU off 진입임을 전달하며, 반환값은 나중에 보조 CPU 초기화에서 사용할 출발 EL 기록입니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.

### 340행
```asm
	mrs	x2, mpidr_el1
```
MPIDR_EL1에서 이 CPU의 하드웨어 affinity ID를 읽습니다. release 변수에 기록된 대상 CPU ID와 비교해 자기 차례인지 확인할 값입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 341행
```asm
	mov_q	x1, MPIDR_HWID_BITMASK
```
MPIDR에서 하드웨어 CPU 식별에 쓰는 affinity 비트만 남길 마스크를 준비합니다. unrelated 상태 비트 때문에 release ID 비교가 틀리지 않게 합니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 342행
```asm
	and	x2, x2, x1
```
필요한 비트만 남깁니다. 다른 정보가 섞인 값에서 관심 있는 부분을 고르는 작업입니다. 여기서 결과를 받는 레지스터는 x2입니다.

### 343행
```asm
	adr_l	x3, secondary_holding_pen_release
```
x3에 secondary_holding_pen_release의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 344행
```asm
pen:	ldr	x4, [x3]
```
x3가 가리키는 메모리 위치에 보관된 값을 x4로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 345행
```asm
	cmp	x4, x2
```
x4와 x2를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 346행
```asm
	b.eq	secondary_startup
```
앞서 계산한 조건 플래그가 Z=1이면 secondary_startup로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 347행
```asm
	wfe
```
이벤트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 348행
```asm
	b	pen
```
pen부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 349행
```asm
SYM_FUNC_END(secondary_holding_pen)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 355행
```asm
SYM_FUNC_START(secondary_entry)
```
이 위치부터 (secondary_entry)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 356행
```asm
	mov	x0, xzr
```
x0에 0을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 357행
```asm
	bl	init_kernel_el			// w0=cpu_boot_mode
```
펌웨어가 바로 secondary_entry로 보내온 보조 CPU도 EL1 실행 준비와 출발 EL 기록을 수행합니다. holding pen 경로와 합류하기 전에 같은 초기 조건을 만드는 단계입니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 358행
```asm
	b	secondary_startup
```
secondary_startup부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 359행
```asm
SYM_FUNC_END(secondary_entry)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

![단계별 개념 그림](../media/head-overview.png)
화살표는 테이블 준비, 주소 변환, 실행 이동을 각각 나타냅니다. PC 주소의 실제 숫자와 세부 초기화는 생략했습니다.

## secondary_startup: 기존 테이블로 MMU 켜기
원본 361–380행

### 361행
```asm
SYM_FUNC_START_LOCAL(secondary_startup)
```
이 위치부터 (secondary_startup)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 365행
```asm
	mov	x20, x0				// preserve boot mode
```
x20에 x0에 들어 있는 값을 넣습니다. 원래 x20의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 367행
```asm
#ifdef CONFIG_ARM64_VA_BITS_52
```
52비트 VA를 지원하도록 빌드한 커널에만 보조 CPU의 VA 폭 호환성 검사를 포함합니다. 커널이 실제로 넓은 VA를 사용한다면 이를 지원하지 않는 보조 CPU를 그대로 online시키면 주소 변환에 실패합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 368행
```asm
alternative_if ARM64_HAS_VA52
```
ARM64_HAS_VA52는 이 시스템에서 52비트 VA 기능을 사용하는 조건입니다. 그 조건이 활성인 경우 검사 호출을 유지하고, 필요 없는 시스템에서는 대안 NOP 경로로 교체하도록 만듭니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 369행
```asm
	bl	__cpu_secondary_check52bitva
```
현재 보조 CPU가 시스템에서 사용할 52비트 주소 방식을 지원하는지 읽습니다. 지원하지 않으면 실패 이유를 기록하고 대기하며, 지원하면 돌아와 정상 MMU 준비를 계속합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 370행
```asm
alternative_else_nop_endif
```
앞의 52비트 VA 검사가 필요하지 않은 대안은 같은 크기의 NOP입니다. 빈 조건을 실행하는 문장이 아니라 부팅 시 코드 교체를 위한 분기 정의의 끝입니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 371행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 373행
```asm
	bl	__cpu_setup			// initialise processor
```
이 보조 CPU의 TLB와 주소 변환 속성을 초기화하고 SCTLR 값 x0를 준비합니다. 부트 CPU에서 했더라도 시스템 레지스터는 CPU마다 별도로 설정해야 합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 374행
```asm
	adrp	x1, swapper_pg_dir
```
x1에 swapper_pg_dir의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 375행
```asm
	adrp	x2, idmap_pg_dir
```
x2에 idmap_pg_dir의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 376행
```asm
	bl	__enable_mmu
```
x1=swapper_pg_dir, x2=idmap_pg_dir, x0=SCTLR 설정으로 보조 CPU의 MMU를 켭니다. 페이지 granule 지원을 검사하고 TTBR0·TTBR1을 설치하므로 뒤의 가상 주소 분기가 유효해집니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 377행
```asm
	ldr	x8, =__secondary_switched
```
x8에 __secondary_switched의 주소 상수를 준비합니다. 그 함수나 변수의 본문을 복사하는 것이 아니라 이후 이동하거나 접근할 위치를 얻습니다.

### 378행
```asm
	br	x8
```
x8에 준비한 주소부터 실행합니다. 주소가 가리키는 값을 메모리에서 읽는 것이 아니라 CPU의 다음 실행 위치를 바꿉니다. 새 복귀 주소는 만들지 않습니다.

### 379행
```asm
SYM_FUNC_END(secondary_startup)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## __secondary_switched와 시작할 태스크가 없는 보조 CPU
원본 381–413행

### 381행
```asm
	.text
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

### 382행
```asm
SYM_FUNC_START_LOCAL(__secondary_switched)
```
이 위치부터 (__secondary_switched)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 383행
```asm
	mov	x0, x20
```
x0에 x20에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 384행
```asm
	bl	set_cpu_boot_mode_flag
```
보조 CPU가 어떤 EL에서 출발했는지를 공유 부팅 기록에 남깁니다. 현재 EL을 읽는 동작이 아니라 앞에서 x20에 보존한 입력을 기록합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.

### 386행
```asm
	mov	x0, x20
```
x0에 x20에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 387행
```asm
	bl	finalise_el2
```
보조 CPU도 출발 EL 및 VHE 조건에 맞춰 EL2 상태를 마무리합니다. 부트 CPU와 실행 레벨 정책을 맞추는 과정이며 언제나 EL2로 올라간다고 단정하지 않습니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 389행
```asm
	str_l	xzr, __early_cpu_boot_status, x3
```
보조 CPU의 초기 부팅 상태를 0으로 지웁니다. 이 위치까지 정상적으로 CPU 초기화·MMU 전환을 마쳤다는 흐름을 다른 CPU가 읽을 상태 변수에 반영합니다. str_l은 임시 레지스터로 심벌의 주소를 구하고 STR로 그 메모리에 값을 쓰는 매크로입니다.

### 390행
```asm
	adr_l	x5, vectors
```
x5에 vectors의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 391행
```asm
	msr	vbar_el1, x5
```
보조 CPU의 VBAR_EL1에 정상 커널 벡터 주소를 설치합니다. 같은 vectors를 사용하더라도 레지스터는 각 CPU에 설정해야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 392행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 394행
```asm
	adr_l	x0, secondary_data
```
x0에 secondary_data의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 395행
```asm
	ldr	x2, [x0, #CPU_BOOT_TASK]
```
x0를 기준으로 CPU_BOOT_TASK만큼 계산한 메모리 위치에 보관된 값을 x2로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 396행
```asm
	cbz	x2, __secondary_too_slow
```
x2의 값가 0이면 __secondary_too_slow로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 398행
```asm
	init_cpu_task x2, x1, x3
```
CPU가 사용할 태스크, 커널 스택, 호출 프레임 끝, CPU별 변수 접근 기준을 준비합니다. C 코드를 실행하기 위한 주변 상태를 맞추는 단계입니다.

### 400행
```asm
#ifdef CONFIG_ARM64_PTR_AUTH
```
포인터 인증 지원을 빌드한 경우에 보조 CPU의 키·제어 상태 초기화를 포함합니다. PAC는 비밀 키로 포인터를 인증하는 기능이며, 꺼진 빌드는 이 준비를 생략합니다. 실제 CPU 지원 여부는 매크로 내부 조건도 따릅니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 401행
```asm
	ptrauth_keys_init_cpu x2, x3, x4, x5
```
현재 태스크와 CPU 설정에 맞게 포인터 인증 상태를 준비합니다. 키와 제어 설정이 일치해야 이후 인증된 복귀가 동작합니다.

### 402행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 404행
```asm
	bl	secondary_start_kernel
```
준비된 idle task와 스택을 사용해 보조 CPU의 C 초기화 secondary_start_kernel로 들어갑니다. CPU 기능·인터럽트·스케줄러 준비 후 online 및 idle 실행으로 이어지며 정상 반환을 기대하지 않습니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 405행
```asm
	ASM_BUG()
```
secondary_start_kernel은 정상적으로 반환하지 않습니다. 뜻하지 않게 돌아오면 잘못된 복귀 주소로 계속 실행하지 않고 BUG 예외로 멈춥니다. 어셈블 단계에는 진단용 BUG 메타데이터와 BRK 인코딩을 배치하고, 실행이 이 BRK에 도달하면 예외가 발생합니다.

### 406행
```asm
SYM_FUNC_END(__secondary_switched)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 408행
```asm
SYM_FUNC_START_LOCAL(__secondary_too_slow)
```
이 위치부터 (__secondary_too_slow)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 409행
```asm
	wfe
```
이벤트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 410행
```asm
	wfi
```
인터럽트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 411행
```asm
	b	__secondary_too_slow
```
__secondary_too_slow부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 412행
```asm
SYM_FUNC_END(__secondary_too_slow)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## set_cpu_boot_mode_flag: 32비트 슬롯 두 개
원본 414–426행

### 418행
```asm
SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
```
이 위치부터 (set_cpu_boot_mode_flag)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 419행
```asm
	adr_l	x1, __boot_cpu_mode
```
x1에 __boot_cpu_mode의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 420행
```asm
	cmp	w0, #BOOT_CPU_MODE_EL2
```
w0와 BOOT_CPU_MODE_EL2를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 421행
```asm
	b.ne	1f
```
앞서 계산한 조건 플래그가 Z=0이면 1f (L423)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 422행
```asm
	add	x1, x1, #4
```
x1를 기준으로 4을 더해 x1에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 423행
```asm
1:	str	w0, [x1]			// Save CPU boot mode
```
w0의 값을 x1가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다.

### 424행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 425행
```asm
SYM_FUNC_END(set_cpu_boot_mode_flag)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## MMU off 실패 상태를 외부에 남기는 매크로
원본 427–444행

### 437행
```asm
	.macro	update_early_cpu_boot_status status, tmp1, tmp2
```
초기 CPU가 더 진행하지 못한 이유를 __early_cpu_boot_status에 쓰는 매크로입니다. status는 실패 종류, tmp1·tmp2는 임시 레지스터이며 MMU off 상태에서도 다른 CPU가 볼 수 있도록 장벽과 캐시 무효화를 포함합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 438행
```asm
	mov	\tmp2, #\status
```
\tmp2에 \status라는 값을 넣습니다. 원래 \tmp2의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 439행
```asm
	adr_l	\tmp1, __early_cpu_boot_status
```
\tmp1에 __early_cpu_boot_status의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 440행
```asm
	str	\tmp2, [\tmp1]
```
\tmp2의 값을 \tmp1가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다.

### 441행
```asm
	dmb	sy
```
앞뒤의 메모리 접근이 필요한 순서로 관찰되도록 제한합니다. “먼저 썼으니 다른 CPU도 먼저 볼 것”이라는 가정을 명시적인 규칙으로 만드는 줄입니다. 캐시를 직접 비우는 명령은 아닙니다.

### 442행
```asm
	dc	ivac, \tmp1			// Invalidate potentially stale cache line
```
데이터 캐시의 해당 주소 라인에 ivac, \tmp1 작업을 적용합니다. 일부 바이트를 지정해도 관리 단위는 캐시 라인이므로 이웃 데이터와 정렬을 함께 고려해야 합니다.

### 443행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## __enable_mmu: TTBR 설치와 MMU를 켠 뒤에도 실행이 이어지는 이유
원본 445–474행

### 458행
```asm
	.section ".idmap.text","a"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".idmap.text","a"

### 459행
```asm
SYM_FUNC_START(__enable_mmu)
```
이 위치부터 (__enable_mmu)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 460행
```asm
	mrs	x3, ID_AA64MMFR0_EL1
```
메모리 모델 기능 ID_AA64MMFR0_EL1을 읽습니다. 바로 뒤에서 현재 빌드의 4KiB·16KiB·64KiB 중 선택한 translation granule을 이 CPU가 지원하는지 판별합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 461행
```asm
	ubfx	x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
```
빌드한 PAGE_SIZE에 대응하는 TGRAN 필드 4비트를 추출합니다. 임의의 가상 주소 비트를 자르는 것이 아니라 하드웨어 지원 범위를 읽는 동작입니다. 추출한 필드는 결과의 하위 비트에 놓이고 나머지 상위 비트는 0으로 채워집니다.

### 462행
```asm
	cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN
```
x3와 ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 463행
```asm
	b.lt    __no_granule_support
```
앞서 계산한 조건 플래그가 N≠V (부호 있는 미만)이면 __no_granule_support로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 464행
```asm
	cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX
```
x3와 ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 465행
```asm
	b.gt    __no_granule_support
```
앞서 계산한 조건 플래그가 Z=0이고 N=V이면 __no_granule_support로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 466행
```asm
	phys_to_ttbr x2, x2
```
페이지 테이블의 물리 주소를 TTBR이 요구하는 비트 배치로 바꿉니다. 주소 숫자를 무조건 그대로 쓰는 것과는 다릅니다.

### 467행
```asm
	msr	ttbr0_el1, x2			// load TTBR0
```
MMU on 전후 같은 코드 주소를 사용할 idmap 루트 x2를 TTBR0_EL1에 넣습니다. TTBR1의 커널 가상 매핑은 다음 load_ttbr1이 맡습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 468행
```asm
	load_ttbr1 x1, x1, x3
```
x1의 커널 페이지 표 주소를 TTBR1 형식으로 바꾸고 선택된 주소 폭·페이지 크기에 필요한 보정을 거쳐 설치합니다. 이어지는 ISB까지 포함하여 새 커널 매핑이 뒤의 명령 실행에 적용되도록 합니다.

### 470행
```asm
	set_sctlr_el1	x0
```
__cpu_setup이 x0에 준비한 SCTLR 값을 설치하여 MMU·캐시 설정을 적용합니다. set_sctlr_el1 매크로가 요구되는 실행 동기화와 보완도 포함합니다. 내부에서는 SCTLR_EL1을 쓴 뒤 ISB를 수행하고, I-cache 무효화와 DSB NSH·ISB로 명령 실행 상태를 동기화합니다.

### 472행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 473행
```asm
SYM_FUNC_END(__enable_mmu)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 지원하지 않는 VA 폭·페이지 크기의 실패 경로
원본 475–507행

### 475행
```asm
#ifdef CONFIG_ARM64_VA_BITS_52
```
52비트 VA 지원 빌드에서만 아래 보조 CPU 기능 검사 함수 자체를 만듭니다. 일반 VA 폭 빌드에는 이 함수가 필요 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 476행
```asm
SYM_FUNC_START(__cpu_secondary_check52bitva)
```
이 위치부터 (__cpu_secondary_check52bitva)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 477행
```asm
#ifndef CONFIG_ARM64_LPA2
```
LPA2를 사용하지 않는 52비트 VA 구성에서는 MMFR2의 VARange로 지원 여부를 검사합니다. LPA2 구성은 뒤의 #else에서 translation granule의 LPA2 인코딩을 검사하므로 두 방식의 기능 레지스터가 다릅니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 478행
```asm
	mrs_s	x0, SYS_ID_AA64MMFR2_EL1
```
ID_AA64MMFR2_EL1의 가상 주소 범위 기능을 읽습니다. VARange가 넓은 VA를 지원하는지를 판단하여 이 보조 CPU가 시스템 주소 체계에 참여할 수 있는지 확인합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 479행
```asm
	and	x0, x0, ID_AA64MMFR2_EL1_VARange_MASK
```
필요한 비트만 남깁니다. 다른 정보가 섞인 값에서 관심 있는 부분을 고르는 작업입니다. 여기서 결과를 받는 레지스터는 x0입니다.

### 480행
```asm
	cbnz	x0, 2f
```
x0의 값가 0이 아니면 2f (L494)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 481행
```asm
#else
```
LPA2가 포함된 빌드에서는 MMFR2 VARange 대신 MMFR0의 선택 granule 필드로 LPA2 지원을 검사합니다. 넓은 주소 지원이라는 목적은 같지만 하드웨어 기능 인코딩이 다릅니다.

### 482행
```asm
	mrs	x0, id_aa64mmfr0_el1
```
MMFR0를 읽어 현재 페이지 granule의 LPA2 지원 수준을 확인합니다. 다음 SBFX가 4비트 필드를 부호 있는 값으로 꺼냅니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 483행
```asm
	sbfx	x0, x0, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4
```
선택한 TGRAN 필드를 부호 확장해 꺼냅니다. 미지원 인코딩까지 포함한 비교를 올바르게 수행하기 위해 UBFX와 구분합니다. 추출한 필드의 최상위 비트를 결과의 상위 자리까지 반복해 부호를 유지합니다.

### 484행
```asm
	cmp	x0, #ID_AA64MMFR0_EL1_TGRAN_LPA2
```
x0와 ID_AA64MMFR0_EL1_TGRAN_LPA2를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 485행
```asm
	b.ge	2f
```
앞서 계산한 조건 플래그가 N=V이면 2f (L494)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 486행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 488행
```asm
	update_early_cpu_boot_status \
```
이 CPU가 어느 초기화 상태에 있는지 공유 메모리에 기록합니다. 다른 CPU가 실패 원인을 확인할 수 있도록 남기는 정보입니다.

### 489행
```asm
		CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1
```
일반적인 커널 초기화 정지 표식에 52비트 VA 미지원 원인을 OR해 기록합니다. x0·x1은 이 매크로의 임시 레지스터이며 함수 반환값이 아닙니다.

### 490행
```asm
1:	wfe
```
이벤트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 491행
```asm
	wfi
```
인터럽트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 492행
```asm
	b	1b
```
1b (L490)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 494행
```asm
2:	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 495행
```asm
SYM_FUNC_END(__cpu_secondary_check52bitva)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 496행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 498행
```asm
SYM_FUNC_START_LOCAL(__no_granule_support)
```
이 위치부터 (__no_granule_support)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 500행
```asm
	update_early_cpu_boot_status \
```
이 CPU가 어느 초기화 상태에 있는지 공유 메모리에 기록합니다. 다른 CPU가 실패 원인을 확인할 수 있도록 남기는 정보입니다.

### 501행
```asm
		CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2
```
초기화 정지 표식에 선택한 페이지 granule 미지원 원인을 합칩니다. 부트 CPU가 보조 CPU의 실패를 기다리다가 원인을 진단할 수 있게 합니다. 앞줄에서 시작한 식이나 매크로 인자에 이어지는 부분입니다. |로 연결한 비트들은 하나의 값으로 합쳐집니다.

### 502행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 503행
```asm
	wfe
```
이벤트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 504행
```asm
	wfi
```
인터럽트를 기다릴 수 있도록 CPU에 힌트를 줍니다. 함수가 끝나는 명령은 아니며 깨어나면 다음 줄이 실행될 수 있으므로, 반복 분기나 조건 재검사와 함께 읽습니다.

### 505행
```asm
	b	1b
```
1b (L502)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 506행
```asm
SYM_FUNC_END(__no_granule_support)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## __primary_switch: MMU on에서 최종 VA로 넘어가기
원본 508–523행

### 508행
```asm
SYM_FUNC_START_LOCAL(__primary_switch)
```
이 위치부터 (__primary_switch)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 509행
```asm
	adrp	x1, reserved_pg_dir
```
x1에 reserved_pg_dir의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 510행
```asm
	adrp	x2, __pi_init_idmap_pg_dir
```
x2에 __pi_init_idmap_pg_dir의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 511행
```asm
	bl	__enable_mmu
```
부트 CPU의 TTBR1에는 빈 reserved_pg_dir, TTBR0에는 초기 idmap을 넣어 우선 MMU를 켭니다. 정식 커널 가상 매핑이 아직 준비되지 않았으므로 idmap 위에서 다음 C 초기화를 수행합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 513행
```asm
	adrp	x1, early_init_stack
```
x1에 early_init_stack의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 514행
```asm
	mov	sp, x1
```
이제 스택으로 사용할 위치를 x1의 값으로 바꿉니다. 메모리 내용을 옮기는 작업은 아닙니다. 다음 저장·호출부터 새 스택 위치를 사용합니다.

### 515행
```asm
	mov	x29, xzr
```
x29에 0을 넣습니다. 원래 x29의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 516행
```asm
	mov	x0, x20				// pass the full boot status
```
x0에 x20에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 517행
```asm
	mov	x1, x21				// pass the FDT
```
x1에 x21에 들어 있는 값을 넣습니다. 원래 x1의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 518행
```asm
	bl	__pi_early_map_kernel		// Map and relocate the kernel
```
x0의 부팅 상태와 x1의 FDT 물리 주소를 받아 초기 명령행 기능 override·주소 폭·KASLR 등을 고려한 커널 매핑과 재배치를 준비합니다. 반환 뒤에는 __primary_switched의 최종 가상 주소로 점프할 수 있습니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 520행
```asm
	ldr	x8, =__primary_switched
```
x8에 __primary_switched의 주소 상수를 준비합니다. 그 함수나 변수의 본문을 복사하는 것이 아니라 이후 이동하거나 접근할 위치를 얻습니다.

### 521행
```asm
	adrp	x0, KERNEL_START		// __pa(KERNEL_START)
```
x0에 KERNEL_START의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. ADRP는 4 KiB 페이지의 시작만 계산하므로 세부 위치는 다음 오프셋 연산에서 더합니다.

### 522행
```asm
	br	x8
```
x8에 준비한 주소부터 실행합니다. 주소가 가리키는 값을 메모리에서 읽는 것이 아니라 CPU의 다음 실행 위치를 바꿉니다. 새 복귀 주소는 만들지 않습니다.

### 523행
```asm
SYM_FUNC_END(__primary_switch)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 설명한 뒤 함께 생각해 볼 질문

### MMU를 켜면 PC가 자동으로 높은 커널 주소가 되나요?
아닙니다. 현재 숫자를 가상 주소로 해석하기 시작합니다. idmap으로 같은 물리 코드를 계속 읽고, 이후 BR로 최종 VA에 이동합니다.

### FDT 주소를 x21에 복사하는 이유는 무엇인가요?
x0는 함수의 인자와 반환값으로 계속 재사용됩니다. 초기 DTB/FDT 포인터를 나중에도 쓰기 위해 다른 레지스터와 boot_args에 보존합니다.

### EL2에서 들어왔으면 start_kernel도 EL2인가요?
init_kernel_el은 EL1으로 복귀할 수 있습니다. 나중에 finalise_el2에서 조건을 만족해야 host VHE로 전환합니다.
