This page explains how to implement a protected kernel-based virtual machine (pKVM) vendor module. When you are done with these steps, you should have a directory tree similar to:
Makefile
el1.c
hyp/
Makefile
el2.c
Add the EL2 hypervisor code (
el2.c
). At a minimum, this code must declare an init function accepting a reference to thepkvm_module_ops
struct:#include <asm/kvm_pkvm_module.h> int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops) { /* Init the EL2 code */ return 0; }
The pKVM vendor module API is a struct encapsulating callbacks to the pKVM hypervisor. This struct follows the same ABI rules as GKI interfaces.
Create the
hyp/Makefile
to build the hypervisor code:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
Add the EL1 kernel code (
el1.c
). This code's init section must contain a call topkvm_load_el2 module
to load the EL2 hypervisor code from step 1.#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <asm/kvm_pkvm_module.h> int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops); static int __init pkvm_driver_init(void) { unsigned long token; return pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init, &token); } module_init(pkvm_driver_init);
Finally, create the root makefile to tie the EL1 and EL2 code together:
ifneq ($(KERNELRELEASE),) clean-files := hyp/hyp.lds hyp/hyp-reloc.S obj-m := pkvm_module.o pkvm_module-y := el1.o hyp/kvm_nvhe.o $(PWD)/hyp/kvm_nvhe.o: FORCE $(Q)$(MAKE) $(build)=$(obj)/hyp $(obj)/hyp/kvm_nvhe.o else all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean endif
Load a pKVM module
As with GKI vendor modules, pKVM vendor modules can be loaded using modprobe.
However, for security reasons, loading must occur before deprivileging.
To load a pKVM module, you must ensure your modules are included in
the root filesystem (initramfs
) and you must add the following to your
kernel command-line:
kvm-arm.protected_modules=mod1,mod2,mod3,...
pKVM vendor modules stored in the initramfs
inherit the signature and protection of initramfs
.
If one of the pKVM vendor modules fails to load, the system is considered insecure and it won't be possible to start a protected virtual machine.
Call an EL2 (hypervisor) function from EL2 (kernel module)
An hypervisor call (HVC) is an instruction that lets the kernel to call the hypervisor. With the introduction of pKVM vendor modules, an HVC can be used to call for a function to run at EL2 (in the hypervisor module) from EL1 (the kernel module):
- In the EL2 code (
el2.c
), declare the EL2 handler:
Android-14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android-15
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
In your EL1 code (
el1.c
), register the EL2 handler in your pKVM vendor module:int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops); void __kvm_nvhe_pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx); // Android14 void __kvm_nvhe_pkvm_driver_hyp_hvc(struct user_pt_regs *regs); // Android15 static int hvc_number; static int __init pkvm_driver_init(void) { long token; int ret; ret = pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init,token); if (ret) return ret; ret = pkvm_register_el2_mod_call(__kvm_nvhe_pkvm_driver_hyp_hvc, token) if (ret < 0) return ret; hvc_number = ret; return 0; } module_init(pkvm_driver_init);
In your EL1 code (
el1.c
), call the HVC:pkvm_el2_mod_call(hvc_number);