# ARM64 KVM vCPU와 Stage-2 fault 소스 분석

v6.18.37 / arch/arm64/kvm/mmu.c

게스트가 만든 VA→IPA 변환 뒤에는 호스트가 관리하는 IPA→PA 변환이 있습니다. 이 두 번째 표로 게스트가 접근할 수 있는 메모리를 제한합니다. 아래는 v6.18.37 KVM이 장치 물리 범위를 게스트 IPA에 연결하는 작은 함수를 읽는 예이며 일반 RAM의 demand fault 전체를 대신하지는 않습니다.

## kvm_phys_addr_ioremap

```c

int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa,
			  phys_addr_t pa, unsigned long size, bool writable)
{
	phys_addr_t addr;
	int ret = 0;
	struct kvm_mmu_memory_cache cache = { .gfp_zero = __GFP_ZERO };
	struct kvm_s2_mmu *mmu = &kvm->arch.mmu;
	struct kvm_pgtable *pgt = mmu->pgt;
	enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_DEVICE |
				     KVM_PGTABLE_PROT_R |
				     (writable ? KVM_PGTABLE_PROT_W : 0);

	if (is_protected_kvm_enabled())
		return -EPERM;

	size += offset_in_page(guest_ipa);
	guest_ipa &= PAGE_MASK;

	for (addr = guest_ipa; addr < guest_ipa + size; addr += PAGE_SIZE) {
		ret = kvm_mmu_topup_memory_cache(&cache,
						 kvm_mmu_cache_min_pages(mmu));
		if (ret)
			break;

		write_lock(&kvm->mmu_lock);
		ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, addr, PAGE_SIZE,
				 pa, prot, &cache, 0);
		write_unlock(&kvm->mmu_lock);
		if (ret)
			break;

		pa += PAGE_SIZE;
	}

	kvm_mmu_free_memory_cache(&cache);
	return ret;
}

```

### 1144행

```c

int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa,

```

VM과 장치를 보이게 할 게스트 IPA 시작 주소를 받습니다. 사용자 VA를 받는 함수로 읽지 않습니다.

### 1145행

```c

			  phys_addr_t pa, unsigned long size, bool writable)

```

실제 물리 시작점·크기·쓰기 허용 여부를 받습니다. 호출자가 제공하는 주소 범위와 페이지 정렬 조건을 주변 경로와 함께 확인해야 합니다.

### 1147행

```c

	phys_addr_t addr;

```

한 페이지씩 처리할 게스트 주소를 보관합니다.

### 1148행

```c

	int ret = 0;

```

아직 오류가 없으므로 결과를 0으로 시작합니다.

### 1149행

```c

	struct kvm_mmu_memory_cache cache = { .gfp_zero = __GFP_ZERO };

```

Stage-2 표를 만들 때 사용할 메모리 캐시를 준비합니다. 새 표가 0으로 초기화되도록 __GFP_ZERO를 설정합니다.

### 1150행

```c

	struct kvm_s2_mmu *mmu = &kvm->arch.mmu;

```

이 VM의 Stage-2 MMU 상태를 선택합니다.

### 1151행

```c

	struct kvm_pgtable *pgt = mmu->pgt;

```

그 MMU가 사용하는 페이지 표 객체를 꺼냅니다.

### 1152행

```c

	enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_DEVICE |

```

장치용 메모리 속성을 매핑 권한에 포함합니다. 일반 cacheable RAM 매핑으로 취급하지 않습니다.

### 1153행

```c

				     KVM_PGTABLE_PROT_R |

```

장치 매핑에 읽기 권한을 포함합니다.

### 1154행

```c

				     (writable ? KVM_PGTABLE_PROT_W : 0);

```

호출자가 writable을 요청한 경우에만 쓰기 권한을 더합니다.

### 1156행

```c

	if (is_protected_kvm_enabled())

```

보호 KVM 구성이 활성화된 경우인지 확인합니다.

### 1157행

```c

		return -EPERM;

```

이 경로에서 허용하지 않는 보호 KVM 구성이면 EPERM으로 반환합니다. 모든 보호 VM 매핑을 이 함수가 지원한다고 말할 수 없는 근거입니다.

### 1159행

```c

	size += offset_in_page(guest_ipa);

```

게스트 IPA의 페이지 내부 offset을 길이에 더하여 정렬 전의 끝 범위를 포함하게 합니다.

### 1160행

```c

	guest_ipa &= PAGE_MASK;

```

게스트 시작 주소를 페이지 경계로 내립니다. 이 두 줄이 모든 잘못된 입력을 검증하는 일반 범위 검사라고 가정하지 않습니다.

### 1162행

```c

	for (addr = guest_ipa; addr < guest_ipa + size; addr += PAGE_SIZE) {

```

준비한 게스트 범위를 PAGE_SIZE 단위로 반복합니다. 반복 변수는 IPA이며 실제 물리 주소 pa도 아래에서 함께 증가합니다.

### 1163행

```c

		ret = kvm_mmu_topup_memory_cache(&cache,

```

잠금을 잡기 전에 페이지 표 구축에 필요한 캐시 메모리를 채웁니다.

### 1164행

```c

						 kvm_mmu_cache_min_pages(mmu));

```

현재 MMU에 필요한 최소 페이지 수를 계산해 준비 요청에 넘깁니다.

### 1165행

```c

		if (ret)

```

메모리 준비 중 오류가 발생했는지 확인합니다.

### 1166행

```c

			break;

```

오류면 더 매핑하지 않고 반복을 끝냅니다. 아래의 공통 캐시 정리로 이어집니다.

### 1168행

```c

		write_lock(&kvm->mmu_lock);

```

다른 경로의 Stage-2 표 변경과 충돌하지 않도록 쓰기 잠금을 잡습니다.

### 1169행

```c

		ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, addr, PAGE_SIZE,

```

Stage-2 매핑 함수를 통해 현재 IPA의 한 페이지를 연결합니다. KVM_PGT_FN은 구성에 맞는 표 함수 연결 매크로입니다.

### 1170행

```c

				 pa, prot, &cache, 0);

```

대응하는 호스트 물리 주소, 권한, 준비한 표 메모리 캐시를 매핑 함수에 전달합니다.

### 1171행

```c

		write_unlock(&kvm->mmu_lock);

```

한 페이지 설치 시도가 끝나면 MMU 잠금을 풉니다.

### 1172행

```c

		if (ret)

```

표 설치가 실패했는지 검사합니다.

### 1173행

```c

			break;

```

실패하면 다음 페이지를 계속 설치하지 않습니다. 이 함수가 이미 성공한 앞선 매핑을 여기서 전부 되돌린다고 설명하면 안 됩니다.

### 1175행

```c

		pa += PAGE_SIZE;

```

다음 IPA와 대응할 물리 주소도 한 페이지 전진시킵니다.

### 1178행

```c

	kvm_mmu_free_memory_cache(&cache);

```

남은 페이지 표 캐시 자원을 정리합니다. 이것이 앞에서 설치된 실제 Stage-2 표를 모두 해제한다는 뜻은 아닙니다.

### 1179행

```c

	return ret;

```

성공 또는 중간 오류 값을 호출자에게 반환합니다.

