# RISC-V 64 head.S의 hart·DTB 수신과 SATP 전환

v6.18.37 / arch/riscv/kernel/head.S

MMU를 켜면 같은 PC 숫자가 다른 주소로 해석될 수 있습니다. RISC-V의 relocate_enable_mmu는 복귀 주소와 trap 벡터를 먼저 맞춘 뒤 임시 표와 정식 표를 순서대로 적재합니다. v6.18.37의 이 루틴 전체를 읽어 주소 전환 도중에도 다음 명령을 가져올 수 있어야 한다는 문제를 설명합니다.

## relocate_enable_mmu

```c

	.global relocate_enable_mmu
relocate_enable_mmu:
	/* Relocate return address */
	la a1, kernel_map
	XIP_FIXUP_OFFSET a1
	REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)
	la a2, _start
	sub a1, a1, a2
	add ra, ra, a1

	/* Point stvec to virtual address of intruction after satp write */
	la a2, 1f
	add a2, a2, a1
	csrw CSR_TVEC, a2

	/* Compute satp for kernel page tables, but don't load it yet */
	srl a2, a0, PAGE_SHIFT
	la a1, satp_mode
	XIP_FIXUP_OFFSET a1
	REG_L a1, 0(a1)
	or a2, a2, a1

	/*
	 * Load trampoline page directory, which will cause us to trap to
	 * stvec if VA != PA, or simply fall through if VA == PA.  We need a
	 * full fence here because setup_vm() just wrote these PTEs and we need
	 * to ensure the new translations are in use.
	 */
	la a0, trampoline_pg_dir
	XIP_FIXUP_OFFSET a0
	srl a0, a0, PAGE_SHIFT
	or a0, a0, a1
	sfence.vma
	csrw CSR_SATP, a0
.align 2
1:
	/* Set trap vector to spin forever to help debug */
	la a0, .Lsecondary_park
	csrw CSR_TVEC, a0

	/* Reload the global pointer */
	load_global_pointer

	/*
	 * Switch to kernel page tables.  A full fence is necessary in order to
	 * avoid using the trampoline translations, which are only correct for
	 * the first superpage.  Fetching the fence is guaranteed to work
	 * because that first superpage is translated the same way.
	 */
	csrw CSR_SATP, a2
	sfence.vma

	ret

```

### 73행

```c

	.global relocate_enable_mmu

```

다른 파일에서 참조할 수 있도록 relocate_enable_mmu 기호를 공개합니다.

### 74행

```c

relocate_enable_mmu:

```

주소 전환 루틴의 시작 위치입니다. a0에는 커널 페이지 표 루트의 물리 주소가 전달됩니다.

### 76행

```c

	la a1, kernel_map

```

kernel_map 자료의 현재 접근 가능한 주소를 a1에 얻습니다.

### 77행

```c

	XIP_FIXUP_OFFSET a1

```

XIP 빌드에서 필요한 주소 보정을 매크로에 맡깁니다. 일반 RAM 실행과 주소 배치가 다를 수 있습니다.

### 78행

```c

	REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)

```

kernel_map의 커널 가상 시작 주소를 읽습니다. REG_L은 XLEN에 맞는 load를 선택합니다.

### 79행

```c

	la a2, _start

```

현재 _start 기호 주소를 a2에 가져옵니다.

### 80행

```c

	sub a1, a1, a2

```

가상 시작과 현재 시작 사이의 차이를 a1에 계산합니다. 이것이 코드 주소를 보정할 값입니다.

### 81행

```c

	add ra, ra, a1

```

RA에 주소 차이를 더해 MMU 전환 후 돌아갈 위치도 가상 주소 기준으로 맞춥니다.

### 84행

```c

	la a2, 1f

```

뒤에 나오는 1번 지역 라벨의 현재 주소를 구합니다. 1f의 f는 앞으로 나오는 라벨을 뜻합니다.

### 85행

```c

	add a2, a2, a1

```

그 라벨 주소에도 같은 차이를 더해 전환 후의 가상 목적지를 만듭니다.

### 86행

```c

	csrw CSR_TVEC, a2

```

CSR_TVEC에 그 목적지를 설치합니다. 전환 직후 VA와 PA가 다르면 trap을 통해 이 지점에 도달할 수 있게 합니다.

### 89행

```c

	srl a2, a0, PAGE_SHIFT

```

커널 루트의 물리 주소를 PAGE_SHIFT만큼 내려 SATP가 요구하는 물리 페이지 번호로 만듭니다.

### 90행

```c

	la a1, satp_mode

```

사용할 SATP 변환 모드가 들어 있는 변수의 주소를 얻습니다.

### 91행

```c

	XIP_FIXUP_OFFSET a1

```

XIP 구성의 주소 보정을 적용합니다.

### 92행

```c

	REG_L a1, 0(a1)

```

satp_mode의 실제 값을 읽습니다. 변수 주소와 그 안의 모드값을 구분합니다.

### 93행

```c

	or a2, a2, a1

```

커널 표의 PPN과 모드 비트를 합친 SATP 값을 a2에 보관합니다. 아직 이 정식 표를 설치하지 않습니다.

### 101행

```c

	la a0, trampoline_pg_dir

```

우선 설치할 임시 trampoline 페이지 표 주소를 얻습니다.

### 102행

```c

	XIP_FIXUP_OFFSET a0

```

임시 표 주소에도 XIP 보정을 적용합니다.

### 103행

```c

	srl a0, a0, PAGE_SHIFT

```

임시 표 주소를 SATP용 PPN으로 변환합니다.

### 104행

```c

	or a0, a0, a1

```

임시 PPN과 앞에서 읽은 모드 비트를 합칩니다.

### 105행

```c

	sfence.vma

```

setup_vm이 기록한 PTE와 주소 변환 접근의 순서를 맞추는 SFENCE.VMA를 수행합니다.

### 106행

```c

	csrw CSR_SATP, a0

```

임시 SATP를 설치합니다. 현재 주소가 같은 방식으로 번역되지 않으면 앞서 준비한 TVEC 경로로 이어집니다.

### 107행

```c

.align 2

```

이어질 라벨을 어셈블러의 2^2 바이트 경계에 정렬합니다. 실행 중 주소를 반올림하는 명령은 아닙니다.

### 108행

```c

1:

```

전환 후 도달할 1번 지역 라벨입니다. 위의 1f 참조와 연결됩니다.

### 110행

```c

	la a0, .Lsecondary_park

```

후속 예외에서 대기할 디버깅용 park 루프의 주소를 구합니다.

### 111행

```c

	csrw CSR_TVEC, a0

```

TVEC를 park 루프로 다시 바꿉니다. 초기 전환용 단발 목적지를 계속 사용하지 않기 위해서입니다.

### 114행

```c

	load_global_pointer

```

가상 주소 환경에 맞는 global pointer를 다시 준비합니다. 구체적인 PC 상대 로딩은 매크로 정의에 있습니다.

### 122행

```c

	csrw CSR_SATP, a2

```

a2에 준비해 둔 정식 커널 SATP를 설치합니다.

### 123행

```c

	sfence.vma

```

임시 표에서 얻은 번역이 이후 접근에 잘못 남지 않도록 SFENCE.VMA를 수행합니다.

### 125행

```c

	ret

```

처음에 가상 주소로 보정한 RA로 돌아갑니다. MMU 전환 함수의 호출자 실행이 새 주소 환경에서 이어집니다.