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

    pKVM ベンダー モジュール API は pKVM ハイパーバイザへのコールバックをカプセル化する構造体です。この構造体は GKI インターフェースと同じ ABI ルールに従います。

  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 の呼び出しを含め、ステップ 1 の EL2 ハイパーバイザ コードを読み込む必要があります。

    #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 ベンダー モジュールと同様に、pKVM ベンダー モジュールは modprobe を使用して読み込めます。ただし、セキュリティ上の理由から、読み込みは権限を削除する前に行う必要があります。pKVM モジュールを読み込むには、モジュールがルート ファイルシステム(initramfs)に含まれていることを確認し、カーネル コマンドラインに以下を追加する必要があります。

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

initramfs に保存されている pKVM ベンダー モジュールは initramfs の署名と保護設定を継承します。

pKVM ベンダー モジュールの 1 つが読み込みに失敗した場合、システムは安全でないと見なされ、保護された仮想マシンを開始できなくなります。

EL2(カーネル モジュール)から EL2(ハイパーバイザ)関数を呼び出す

ハイパーバイザ呼び出し(HVC)はカーネルがハイパーバイザを呼び出せるようにする手順です。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);