Triển khai mô-đun nhà cung cấp pKVM

Trang này giải thích cách triển khai mô-đun của nhà cung cấp máy ảo dựa trên hạt nhân được bảo vệ (pKVM). Khi hoàn tất các bước này, bạn sẽ có một cây thư mục tương tự như sau:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Thêm mã trình điều khiển ảo EL2 (el2.c). Ít nhất, mã này phải khai báo một hàm khởi tạo chấp nhận tham chiếu đến cấu trúc pkvm_module_ops:

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

    API mô-đun nhà cung cấp pKVM là một cấu trúc đóng gói các lệnh gọi lại đến trình điều khiển ảo hoá pKVM. Cấu trúc này tuân theo các quy tắc ABI giống như giao diện GKI.

  2. Tạo hyp/Makefile để tạo mã trình điều khiển ảo hoá:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Thêm mã hạt nhân EL1 (el1.c). Phần khởi động của mã này phải chứa lệnh gọi đến pkvm_load_el2 module để tải mã trình điều khiển ảo EL2 từ bước 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. Cuối cùng, hãy tạo tệp makefile gốc để liên kết mã EL1 và EL2 với nhau:

    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
    

Tải mô-đun pKVM

Cũng như các mô-đun nhà cung cấp GKI, bạn có thể tải các mô-đun nhà cung cấp pKVM bằng modprobe. Tuy nhiên, vì lý do bảo mật, quá trình tải phải diễn ra trước khi thu hồi đặc quyền. Để tải mô-đun pKVM, bạn phải đảm bảo các mô-đun của mình có trong hệ thống tệp gốc (initramfs) và bạn phải thêm nội dung sau vào dòng lệnh hạt nhân:

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

Các mô-đun nhà cung cấp pKVM được lưu trữ trong initramfs kế thừa chữ ký và biện pháp bảo vệ của initramfs.

Nếu một trong các mô-đun nhà cung cấp pKVM không tải được, thì hệ thống sẽ được coi là không an toàn và bạn sẽ không thể khởi động máy ảo được bảo vệ.

Gọi một hàm EL2 (trình điều khiển ảo hoá) từ EL2 (mô-đun hạt nhân)

Lệnh gọi trình điều khiển ảo hoá (HVC) là một lệnh cho phép nhân gọi trình điều khiển ảo hoá. Với việc giới thiệu các mô-đun nhà cung cấp pKVM, bạn có thể sử dụng HVC để gọi một hàm chạy ở EL2 (trong mô-đun trình điều khiển ảo hoá) từ EL1 (mô-đun hạt nhân):

  1. Trong mã EL2 (el2.c), hãy khai báo trình xử lý EL2:

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. Trong mã EL1 (el1.c), hãy đăng ký trình xử lý EL2 trong mô-đun nhà cung cấp pKVM:

    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. Trong mã EL1 (el1.c), hãy gọi HVC:

    pkvm_el2_mod_call(hvc_number);