实现 pKVM 供应商模块

本页介绍了如何实现受保护的基于内核的虚拟机 (pKVM) 供应商模块。完成这些步骤后,您应该会看到类似于下方所示的目录树:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. 添加 EL2 Hypervisor 代码 (el2.c)。此代码必须至少声明一个接受对 pkvm_module_ops 结构体引用的 init 函数:

    #include <asm/kvm_pkvm_module.h>
    
    int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops)
    {
      /* Init the EL2 code */
    
      return 0;
    }
    

    pKVM 供应商模块 API 是一个结构体,封装了对 pKVM Hypervisor 的回调。此结构体遵循与 GKI 接口相同的 ABI 规则。

  2. 创建 hyp/Makefile 以构建 Hypervisor 代码:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. 添加 EL1 内核代码 (el1.c)。此代码的 init 部分必须包含对 pkvm_load_el2 module 的调用,以加载第 1 步中的 EL2 Hypervisor 代码。

    #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. 最后,创建根 makefile,将 EL1 和 EL2 代码关联起来:

    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
    

加载 pKVM 模块

与 GKI 供应商模块一样,可以使用 modprobe 加载 pKVM 供应商模块。不过,出于安全原因,加载操作必须在撤消特权之前进行。如需加载 pKVM 模块,您必须确保自己的模块包含在根文件系统 (initramfs) 中,并且必须将以下内容添加到内核命令行:

kvm-arm.protected_modules=mod1,mod2,mod3,...

存储在 initramfs 中的 pKVM 供应商模块会继承 initramfs 的签名和保护机制。

如果有一个 pKVM 供应商模块加载失败,系统就会被视为不安全,并且无法启动受保护的虚拟机。

通过 EL2(内核模块)调用 EL2 (Hypervisor) 函数

Hypervisor 调用 (HVC) 是一条可让内核调用 Hypervisor 的指令。引入 pKVM 供应商模块后,HVC 可用于从 EL1(内核模块)调用会在 EL2(Hypervisor 模块)运行的函数:

  1. 在 EL2 代码 (el2.c) 中,声明 EL2 处理程序:

    void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
    {
      /* Handle the call */
    
      cpu_reg(ctx, 1) = 0;
    }
    
  2. 在 EL1 代码 (el1.c) 中,向 pKVM 供应商模块注册 EL2 处理程序:

    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);
    
    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);
    
  3. 在 EL1 代码 (el1.c) 中,调用 HVC:

    pkvm_el2_mod_call(hvc_number);