ใช้โมดูลผู้ให้บริการ pKVM

หน้านี้จะอธิบายวิธีใช้โมดูลผู้ให้บริการเครื่องเสมือน (pKVM) ที่ใช้เคอร์เนลที่ได้รับการปกป้อง เมื่อคุณทำตามขั้นตอนเหล่านี้เสร็จแล้ว คุณควรมีโครงสร้างไดเรกทอรีที่คล้ายกับ

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. เพิ่มโค้ดไฮเปอร์วิซอร์ EL2 (el2.c) อย่างน้อยที่สุด โค้ดนี้ต้องประกาศฟังก์ชัน init ที่ยอมรับการอ้างอิงไปยังโครงสร้าง 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 ของโมดูลผู้ให้บริการ pKVM คือโครงสร้างที่รวมการเรียกกลับไปยัง Hypervisor ของ pKVM โครงสร้างนี้เป็นไปตามกฎ ABI เดียวกันกับอินเทอร์เฟซ GKI

  2. สร้าง hyp/Makefile เพื่อสร้างโค้ดไฮเปอร์วิซอร์

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. เพิ่มโค้ดเคอร์เนล EL1 (el1.c) ส่วน init ของโค้ดนี้ต้องมีการเรียกใช้ pkvm_load_el2 module เพื่อโหลดโค้ดไฮเปอร์วิซอร์ EL2 จากขั้นตอนที่ 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. สุดท้าย ให้สร้างไฟล์ make รูทเพื่อเชื่อมโยงโค้ด 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 คุณสามารถโหลดโมดูลผู้ให้บริการ pKVM โดยใช้ modprobe อย่างไรก็ตาม คุณต้องโหลดก่อนเพิกถอนสิทธิ์เพื่อความปลอดภัย หากต้องการโหลดโมดูล pKVM คุณต้องตรวจสอบว่าโมดูลรวมอยู่ในระบบไฟล์รูท (initramfs) และต้องเพิ่มข้อมูลต่อไปนี้ลงในบรรทัดคำสั่งเคอร์เนล

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

โมดูลของผู้ให้บริการ pKVM ที่เก็บไว้ใน initramfs จะรับลายเซ็นและการป้องกันของ initramfs

หากโมดูลผู้ให้บริการ pKVM โหลดไม่สำเร็จ ระบบจะถือว่าไม่ปลอดภัยและจะไม่สามารถเริ่มเครื่องเสมือนที่ได้รับการปกป้องได้

เรียกใช้ฟังก์ชัน EL2 (hypervisor) จาก EL2 (โมดูลเคอร์เนล)

การเรียก Hypervisor (HVC) คือคำสั่งที่ช่วยให้เคอร์เนลเรียก Hypervisor ได้ เมื่อมีการเปิดตัวโมดูลผู้ให้บริการ pKVM คุณสามารถใช้ HVC เพื่อเรียกใช้ฟังก์ชันให้ทำงานที่ EL2 (ในโมดูลไฮเปอร์วิซอร์) จาก EL1 (โมดูลเคอร์เนล) ได้โดยทำดังนี้

  1. ในโค้ด EL2 (el2.c) ให้ประกาศตัวแฮนเดิล 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. ในรหัส EL1 (el1.c) ให้ลงทะเบียนเครื่องจัดการ EL2 ในโมดูลผู้ให้บริการ 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. ในรหัส EL1 (el1.c) ให้โทรหา HVC ดังนี้

    pkvm_el2_mod_call(hvc_number);