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

현재 커널이 사용하던 가상 주소 체계를 끝내고 다른 코드에게 실행을 넘기는 상황입니다. 주소 변환을 끄면 다음 명령을 어디서 읽을지도 달라질 수 있습니다. 그래서 이 코드는 가상 주소와 물리 주소가 같은 곳에서 실행합니다. 인자를 보존한 뒤 제어 레지스터를 바꾸고 마지막에는 일반 함수처럼 돌아오지 않고 새 진입점으로 이동합니다.

## 처음 읽을 때
함수 주소와 함수 인자를 구분하세요. x1의 주소를 x8에 옮기는 것은 함수를 복사하는 일이 아니라, 이후 x1을 다른 인자로 사용할 준비입니다.

## 더 깊이 살펴볼 때
SCTLR 설정, ISB, idmap은 서로 다른 역할을 맡습니다. 같은 레지스터 이름이 VHE에서 가리키는 대상까지 고려하면 왜 단순히 “MMU 비트 하나를 0으로 만든다”로 끝나지 않는지 볼 수 있습니다.

## 실행 위치와 입력
원본 1–31행

### 10행
```asm
#include <linux/linkage.h>
```
어셈블리 함수의 심볼과 크기를 선언하는 linkage 정의를 가져옵니다. cpu_soft_restart를 C 코드에서 호출할 수 있는 함수로 내보냅니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 11행
```asm
#include <linux/cfi_types.h>
```
간접 호출의 함수 형식 검사에 쓰는 CFI 관련 정의를 가져옵니다. cpu_soft_restart는 함수 포인터로도 호출되므로 SYM_TYPED_FUNC_START로 형식 정보가 있는 진입점을 선언합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 12행
```asm
#include <asm/assembler.h>
```
큰 상수를 만드는 mov_q와 MMU 비활성화 직전의 CPU 문제 우회 매크로 등 공통 어셈블리 도구를 가져옵니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 13행
```asm
#include <asm/sysreg.h>
```
SCTLR 제어 비트와 MMU-off 초기값을 가져옵니다. 주소 변환을 끌 때 필요한 엔디언·제어 설정을 하드코딩한 0 대신 이 정의로 구성합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 14행
```asm
#include <asm/virt.h>
```
HVC_SOFT_RESTART 같은 EL2 stub 요청 번호를 가져옵니다. EL2에서 새 이미지로 시작해야 하는 경우 이 번호를 x0로 전달합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 16행
```asm
.text
```
기본 실행 코드 섹션을 선택합니다. 바로 뒤에서 실제 재시작 함수는 MMU 전환을 견디도록 .idmap.text에 배치합니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다.

### 17행
```asm
.pushsection    .idmap.text, "a"
```
MMU를 끄는 동안에도 실행 주소가 유효하도록 이 함수를 가상 주소와 물리 주소가 같은 idmap 영역에 둡니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다. 이전 출력 섹션을 함께 기억하므로 뒤의 .popsection으로 되돌릴 수 있습니다.

![단계별 개념 그림](../media/cpu-reset-overview.png)
E와 A·B·C는 가상 값입니다. 화살표는 인자 전달 또는 실행 이동이며 하드웨어 전체 리셋을 뜻하지 않습니다.

## 주소 변환 끄기
원본 32–40행

### 32행
```asm
SYM_TYPED_FUNC_START(cpu_soft_restart)
```
CPU를 소프트웨어로 다음 실행 이미지에 넘기는 함수입니다. x0는 EL2로 전환할지, x1은 진입 주소, x2~x4는 그 진입점에 전달할 세 인자입니다. 주변 장치 전체에 하드웨어 리셋 신호를 보내는 함수는 아닙니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. TYPED 형식은 구성에 따라 간접 호출 검사에 필요한 함수 형식 정보도 제공합니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 33행
```asm
	mov_q	x12, INIT_SCTLR_EL1_MMU_OFF
```
MMU와 캐시를 끈 상태로 사용할 SCTLR 초기값을 x12에 만듭니다. INIT_SCTLR_EL1_MMU_OFF는 단순한 0이 아니라 엔디언 설정과 필요한 제어 비트를 포함하며 M·C·I 비트는 포함하지 않습니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.
INIT_SCTLR_EL1_MMU_OFF는 단순한 정수 0이 아니라 커널이 정한 초기 제어 비트 조합입니다.

### 34행
```asm
	pre_disable_mmu_workaround
```
CONFIG_QCOM_FALKOR_ERRATUM_E1041이 설정된 빌드에서는 SCTLR의 M 비트를 내리기 직전에 ISB를 넣습니다. Qualcomm Falkor의 해당 CPU 문제를 우회하기 위한 순서 보장입니다.

### 39행
```asm
	msr	sctlr_el1, x12
```
준비한 값을 SCTLR_EL1에 써서 주소 변환과 캐시 사용을 중단합니다. 이 지점 뒤에도 실행할 수 있도록 호출자가 idmap을 준비해 두어야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 40행
```asm
	isb
```
SCTLR 변경을 이후 명령 실행에 반영합니다. 다음 분기는 MMU를 끈 실행 환경에서 이루어집니다.

## EL2 경유 또는 직접 이동
원본 41–53행

### 42행
```asm
	cbz	x0, 1f				// el2_switch?
```
EL2 전환 플래그가 0이면 현재 EL에서 직접 새 진입점으로 가는 경로를 택합니다. 0이 아니면 HVC를 통해 EL2 쪽 재시작 처리로 들어갑니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 43행
```asm
	mov	x0, #HVC_SOFT_RESTART
```
x0에 HVC_SOFT_RESTART 서비스 번호를 넣어 기존의 EL2 전환 플래그를 덮어씁니다. 이 MOV는 x0의 값만 바꾸며, x1~x4나 메모리의 내용을 복사하거나 수정하지 않습니다. 다음 줄의 hvc #0이 실행될 때 EL2 처리 코드가 x0의 요청 번호, x1의 진입 주소, x2~x4의 인자를 받습니다.
HVC 요청 번호를 넣기 전에 x1의 진입점과 x2–x4의 인자는 그대로 유지됩니다.

### 44행
```asm
	hvc	#0				// no return
```
EL2의 hyp stub에 소프트웨어 재시작을 요청합니다. 처리기는 x1의 주소로 분기하면서 x2~x4를 새 x0~x2로 옮기므로 성공 경로에서는 이 HVC 다음 줄로 돌아오지 않습니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.
성공하면 hyp-stub가 새 진입점으로 BR하므로 이 호출 뒤로 반환하지 않습니다.

### 46행
```asm
1:	mov	x8, x1				// entry
```
직접 분기 경로에서 진입 주소를 x8에 옮깁니다. x1을 새 이미지의 인자 레지스터로 바꾸기 전에 분기 목적지를 보존합니다. MOV는 x8의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
다음 줄에서 x1을 덮어쓰기 전에 entry를 x8에 보존합니다.

### 47행
```asm
	mov	x0, x2				// arg0
```
호출자가 넘긴 첫 번째 새 이미지 인자를 x2에서 x0로 옮깁니다. machine_kexec에서는 여기에 DTB 물리 주소를 전달합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 48행
```asm
	mov	x1, x3				// arg1
```
두 번째 새 이미지 인자를 x3에서 x1로 옮깁니다. machine_kexec의 호출에서는 0이 전달됩니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 49행
```asm
	mov	x2, x4				// arg2
```
세 번째 새 이미지 인자를 x4에서 x2로 옮깁니다. machine_kexec의 호출에서는 0이 전달됩니다. MOV는 x2의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 50행
```asm
	br	x8
```
x8의 진입 주소로 직접 분기하여 새 커널 또는 purgatory에 제어를 넘깁니다. 이전 커널의 이 함수로 돌아오기 위한 복귀 주소는 만들지 않습니다. 레지스터가 담은 주소로 실행 위치를 바꾸며, 그 주소에서 값을 읽어 다른 레지스터에 복사하는 load 명령은 아닙니다.

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

### 53행
```asm
.popsection
```
cpu_soft_restart를 idmap에 배치하는 작업을 마치고 이전 .text 섹션으로 돌아갑니다. 이는 빌드 시의 섹션 선택입니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다.

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

### 진입점을 왜 x8에 먼저 옮기나요?
다음 줄에서 x1을 새 코드의 인자로 덮어씁니다. 분기할 주소를 그 전에 보존해야 합니다.

### BR 대신 BLR을 쓰면 무엇이 달라지나요?
BLR은 LR에 복귀 주소를 새로 만듭니다. 이 경로는 새 코드로 실행을 넘기고 돌아오지 않으므로 BR을 사용합니다.

### 이 코드는 하드웨어 리셋과 완전히 같나요?
아닙니다. 필요한 CPU 제어 상태를 바꾸고 지정 주소로 이동하는 소프트웨어 경로입니다. 주변 장치 전체를 전원 초기 상태로 돌리는 코드는 아닙니다.
