This repository contains a patch for Samsung's official kernel sources that provides KVM support on exynos boards. This can probably be applied to other Samsung exynos kernels with minor changes.
- Download official kernel sources from opensource.samsung.com.
- Apply the patch
- Copy
/proc/config.gz
from your device, and unzip to.config
- Install and configure a cross-compiler (not documented here).
- In
make menuconfig
disable everything about TIMA (?) and RKP under "Boot Options" (they are incompatible with KVM), and enable KVM under "Virtualization". - Build and flash your shiny KVM-enabled kernel!
- Older versions of Samsung's TrustZone do not play well with this. They probably implemented big.LITTLE scheduling outside of the kernel, which breaks KVM's assumption that VM's do not suddenly jump from one core to another. This seems to have been fixed in the firmware update based on Android 10, open an issue if this persists. The symptom is that the phone sometimes instantly reboots when a VM is launched.
- The register
cntfrq_el0
, which the bootloader should have configured, is set to zero. ARM documentation states that is's only writeable from the highest privilege level (i.e.TrustZone), but readable from any privilege level (impossible to trap accesses), and guest OSes will expect it to hold the architected timer frequency (26.0 MHz). There also does not seem to be ansmc
call to set this value. This means that guest OSes will need to be patched until a TrustZone exploit is found to initialize the register properly. - Timer handling is somehow broken. Linux boots (with a custom DTB that specifies timer frequency explicitly), but OVMF hangs on the first sleep due to interrupts not arriving. UPD: KVM bug, fixed upstream.
Normally Linux needs to be booted in EL2 (HYP mode in ARM terminology) to be able to utilize the virtualization extensions. SBOOT boots the Linux kernel in EL1, but fortunately for us they implemented a backdoor in TrustZone to load and execute custom code in EL2. This interface is utilized by init/vmm.c
in Samsung's kernel to load the proprietary RKP hypervisor, and looks as follows:
#define VMM_64BIT_SMC_CALL_MAGIC 0xC2000400
#define VMM_STACK_OFFSET 4096
#define VMM_MODE_AARCH64 1
status = _vmm_goto_EL2(VMM_64BIT_SMC_CALL_MAGIC, entry_point, VMM_STACK_OFFSET, VMM_MODE_AARCH64, vmm, vmm_size);
Here _vmm_goto_EL2
is a simple wrapper around smc #0
, entry_point
is a physical address of the initialization routine, and the last two parameters are passed to it in x0
and x1
registers. To return, the initialization routine calls smc #0
with x0=0xc2000401, x1=status
(the only piece of information that was obtained by disassembling the proprietary hypervisor).
The semantics of this interface are as follows:
x1
(status
) is passed to the kernel as the return value of_vmm_goto_EL2
. If it is nonzero, the firmware resets the HYP state to default, and furtherhvc
calls result in an exception.- EL2 initialization code only runs on the boot CPU. When further CPUs are brought up, it's EL2 state is copied from the one established by the initialization routine, with one exception:
sp
is set tobootcore.sp + VMM_STACK_OFFSET * core_index
. The numbering used is the same as in Linux kernel. - The firmware saves the value of
vbar_el2
at exit from the initialization routine, and restores it to this value at some random (unknown) points. This means that the address of the exception vector can not be changed later by EL2 code.
Normal KVM/ARM bootstrap process:
- The code in
head.S
detects being booted in EL2 and sets the EL2 exception vector to a so-called "HYP stub" (basically a backdoor), and drops to EL1 to continue booting. - When the KVM subsystem begins initialization, it calls the backdoor to run its initialization code, and changes
vbar_el2
to point to the real exception vector.
KVM/ARM bootstrap process with this patch:
- After
mm_init()
is called fromstart_kernel
, a new functionpreinit_hyp_mode()
is called, that calls the KVM initialization code via the TrustZone backdoor (initialization code itself was also changed to exit viasmc #0
instead oferet
). This point is chosen because before that that code would fail on a memory allocation, and if done too late other cores could be already running. - When the normal ("late") KVM initialization routine starts, it initializes everything except the EL2 state.
- The check for EL2 boot in
arch/arm64/include/asm/virt.h
is replaced with a simplereturn 1;
What did not work:
- In the early bootup code, use the backdoor to enter EL2, and continue booting from there, imitating normal EL2 boot. This probably fails later when secondary cores boot up, causing a sanity check in
arch/arm64/include/asm/virt.h
to fail (boot CPU booted in EL2, others in EL1). - Use the backdoor early to bootstrap a valid-looking HYP stub, then let KVM boot normally. Does not work due to the custom
vbar_el2
handling, see above.