Implement a pKVM vendor module

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
  1. Add the EL2 hypervisor code (el2.c). At a minimum, this code must declare an init function accepting a reference to the pkvm_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.

  2. Create the hyp/Makefile to build the hypervisor code:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Add the EL1 kernel code (el1.c). This code's init section must contain a call to pkvm_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);
    
  4. 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):

  1. 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;
  }
  1. 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);
    
  2. In your EL1 code (el1.c), call the HVC:

    pkvm_el2_mod_call(hvc_number);