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 thực hiện xong các bước này, bạn sẽ có một cây thư mục tương tự như:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Thêm mã trình điều khiển ảo hoá EL2 (el2.c). Ở mức tối thiểu, mã này phải khai báo một hàm init 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 của nhà cung cấp pKVM là một cấu trúc đóng gói các lệnh gọi lại cho Trình điều khiển ảo hoá pKVM. Cấu trúc này tuân theo các quy tắc ABI tương tự 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ã nhân EL1 (el1.c). Phần init 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 hoá 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, 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

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

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

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

Nếu một trong các mô-đun của nhà cung cấp pKVM không tải được, thì hệ thống sẽ được coi là không an toàn và 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 hạt nhân gọi trình điều khiển ảo hoá. Với sự ra mắt của các mô-đun nhà cung cấp pKVM, HVC có thể được sử dụng để 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 nhà cung cấp pKVM của bạn mô-đun:

    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);