Implementazione di un modulo del fornitore pKVM

Questa pagina spiega come implementare un modulo del fornitore di macchine virtuali basate su kernel protette (pKVM). Al termine di questi passaggi, dovresti avere una struttura ad albero di directory simile alla seguente:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Aggiungi il codice dell'hypervisor EL2 (el2.c). Come minimo, questo codice deve dichiarare una funzione di inizializzazione che accetti un riferimento alla struttura 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;
    }
    

    L'API del modulo del fornitore pKVM è una struct che incapsula i callback all'hypervisor pKVM. Questa struttura segue le stesse regole ABI delle interfacce GKI.

  2. Crea il hyp/Makefile per compilare il codice dell'hypervisor:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Aggiungi il codice del kernel EL1 (el1.c). La sezione di inizializzazione di questo codice deve contenere una chiamata a pkvm_load_el2 module per caricare il codice dell'hypervisor EL2 del passaggio 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. Infine, crea il file makefile principale per collegare il codice EL1 ed 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
    

Caricare un modulo pKVM

Come per i moduli del fornitore GKI, i moduli del fornitore pKVM possono essere caricati utilizzando modprobe. Tuttavia, per motivi di sicurezza, il caricamento deve avvenire prima del ritiro dei privilegi. Per caricare un modulo pKVM, devi assicurarti che i moduli siano inclusi nel filesystem principale (initramfs) e devi aggiungere quanto segue alla riga di comando del kernel:

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

I moduli del fornitore di pKVM archiviati in initramfs ereditano la firma e la protezione di initramfs.

Se non riesce a caricare uno dei moduli del fornitore di pKVM, il sistema è considerato non sicuro e non sarà possibile avviare una macchina virtuale protetta.

Chiama una funzione EL2 (hypervisor) da EL2 (modulo del kernel)

Una chiamata all'hypervisor (HVC) è un'istruzione che consente al kernel di chiamare l'hypervisor. Con l'introduzione dei moduli del fornitore pKVM, un HVC può essere utilizzato per chiamare una funzione da eseguire in EL2 (nel modulo dell'hypervisor) da EL1 (il modulo del kernel):

  1. Nel codice EL2 (el2.c), dichiara il gestore 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. Nel codice EL1 (el1.c), registra l'handler EL2 nel modulo del fornitore del 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. Nel codice EL1 (el1.c), chiama l'HVC:

    pkvm_el2_mod_call(hvc_number);