Galaxy Note 10+ kvm 도전 실패기 - 3 (Arm Exception Level 확인하기)
이전 포스트 에서 프로세서는 EL2를 지원한다는 사실을 확인했다. 프로세서가 EL2를 지원하고, kernel에서 kvm option도 켰다. 그럼에도 /dev/kvm
파일이 나타나지 않는 이유에 대해 확인할 차례다. kvm 커널 코드를 확인해보자.
kvm_arm_init
kvm 활성화는 arm_init()
-> kvm_init()
-> kvm_arch_init()
-> is_hyp_mode_available()
순으로 호출된다. is_hyp_mode_available()
함수가 true를 리턴하면 kvm 관련 초기화가 진행되고 /dev/kvm
파일이 나타나게 된다. is_hyp_mode_available()
함수가 무조건 true를 리턴하도록 수정해서 테스트해 본다.
1
2
3
4
5
6
7
/* Reports the availability of HYP mode */
static inline bool is_hyp_mode_available(void)
{
// return (__boot_cpu_mode[0] == BOOT_CPU_MODE_EL2 &&
// __boot_cpu_mode[1] == BOOT_CPU_MODE_EL2);
return 1;
}
이렇게 해서 빌드하니 /dev/kvm
이 나타났다…!
kvm 동작 test
/dev/kvm
이 정상 동작하는지 테스트해 보자. kvm guest가 간단히 hello world를 출력하도록 하는 코드를 이용했다. ioctl을 이용하도록 구현되어 있었고, 쉽게 빌드할 수 있었다.
android에서 정상적으로 동작하게 하려면 Makefile에 -static 옵션을 추가해서 빌드해 주어야 한다.
안타깝게도 결과는 Segmentation fault가 출력되었다. 이유를 살펴보자.
__boot_cpu_mode
is_hyp_mode_available()
가 정상적으로 true를 리턴하려면 __boot_cpu_mode
가 BOOT_CPU_MODE_EL2
를 가지고 있어야 한다. __boot_cpu_mode
는 부트로더가 커널을 불러올 때 설정된다는 사실을 확인했다.
1
2
3
4
5
6
7
8
9
10
/*
* __boot_cpu_mode records what mode CPUs were booted in.
* A correctly-implemented bootloader must start all CPUs in the same mode:
* In this case, both 32bit halves of __boot_cpu_mode will contain the
* same value (either 0 if booted in EL1, BOOT_CPU_MODE_EL2 if booted in EL2).
*
* Should the bootloader fail to do this, the two values will be different.
* This allows the kernel to flag an error when the secondaries have come up.
*/
extern u32 __boot_cpu_mode[2];
__boot_cpu_mode
가 중요한 이유는, Linux kernel이 프로세서의 hypervisor 기능을 초기화할 때 EL2 레벨이 필요하기 때문이다.
linux kernel의 첫 실행 지점은 arch/arm64/kernel/head.S
에 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* Kernel startup entry point.
* ---------------------------
*
...
*/
__HEAD
_head:
...
__INIT
ENTRY(stext)
bl preserve_boot_args
bl el2_setup // Drop to EL1, w0=cpu_boot_mode
adrp x23, __PHYS_OFFSET
...
kernel startup 시에 el2_setup
이 호출되게 되고, el2_setup
은 CurrentEL이 EL2인지 확인한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ENTRY(el2_setup)
msr SPsel, #1 // We want to use SP_EL{1,2}
mrs x0, CurrentEL
cmp x0, #CurrentEL_EL2
b.eq 1f
mov_q x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)
msr sctlr_el1, x0
mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1
isb
ret
1: mov_q x0, (SCTLR_EL2_RES1 | ENDIAN_SET_EL2)
msr sctlr_el2, x0
...
CurrentEL이 EL1일 경우 el2_setup은 BOOT_CPU_MODE_EL1을 설정해 놓고 바로 끝나버린다. 코드를 수정해서 억지로 진행한다고 하더라도, 뒤의 instruction들은 EL2레벨이 필요한 instruction들이다.
즉, Linux Kernel의 초기화 시점에 무조건 프로세서가 EL2 레벨 이어야 한다.
우회방법..?
스마트폰의 부트로더를 수정하는 것은 쉽지가 않다. 단순히 커널을 바꾸는 것과 달리 부트로더는 한 번만 실수해도 바로 벽돌이 되어 버리기 때문이다. 그리고 부트로더 수정할 수 있을 정도면 왜 kvm 쓰냐. Native로 쓰지 (부트로더를 수정하는 대범한 프로젝트들도 있다.. )
어떻게든 Kernel에서 해결할방법이 없을까 찾던중에 다음 자료를 발견했다.
https://github.com/sleirsgoevy/exynos-kvm-patch
Galaxy A6 (exynos7870) 에서 kvm을 활성화하는데 성공했다고 한다. 어떻게 했는지 살펴보니, samsung bootloader(SBOOT)의 백도어를 이용했다고 한다. 삼성 커널이 RKP hypervisor를 Load하기 위해 EL3 (Trustzone)으로 갈 수 있는 백도어를 SBOOT에 설치했고, init/vmm.c
에 있는 함수들을 통해 이 백도어를 이용할 수 있다고 한다. EL3로 갈수만 있다면 하위 레벨인 EL2로 가기는 쉬우니, 그 상태에서 커널 초기화를 다시 수행하면 된다는 것이다.
후다닥 Samsung OpenSource 홈페이지에 가서 note10+ 커널 소스를 받아보았다. 하지만 note10+ 커널 소스에는 init/vmm.c
파일이 존재하지 않았다… (Galaxy A6 커널 소스에는 진짜로 init/vmm.c
파일이 존재했다.) 아무래도 백도어가 막힌 듯하다…
부트로더를 수정하는 건 너무나도 위험해 보여서, 안타깝지만 “note10+ kvm 활성하기”는 일단 실패로 마무리하기로 했다.