From ca44b02c025461b2039da396e49eaae12d63aa0c Mon Sep 17 00:00:00 2001 From: Prakruthi Deepak Heragu Date: Mon, 24 Feb 2025 14:43:08 -0800 Subject: [PATCH] ANDROID: gunyah: qtvm: Support for Qualcomm Trusted Virtual Machines Qualcomm Trusted Virtual Machine (QTVM) are VMs that are authenticated by Qualcomm firmware before the VM starts. The firmware also defines the VM's image layout. Additionally, each QTVM comes with a reserved VM ID. Introduce an IOCTL to allow VMMs to configure a QTVM and this driver will handle the setup for QTVMs. Bug: 399219478 Change-Id: Ia3fe74a46c0e53134b9b4b10fa59bcaa8f376c87 Signed-off-by: Prakruthi Deepak Heragu --- drivers/virt/gunyah/Kconfig | 10 ++ drivers/virt/gunyah/Makefile | 1 + drivers/virt/gunyah/gunyah_qtvm.c | 193 ++++++++++++++++++++++++++++++ drivers/virt/gunyah/rsc_mgr.h | 2 +- 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 drivers/virt/gunyah/gunyah_qtvm.c diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index fdc546145e26..5b729bff33ea 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -50,3 +50,13 @@ config GUNYAH_IOEVENTFD when a Gunyah virtual machine accesses a memory address. Say Y/M here if unsure and you want to support Gunyah VMMs. + +config GUNYAH_QCOM_TRUSTED_VM + tristate "Gunyah Qualcomm Trusted VM Support" + depends on GUNYAH + help + Enable kernel support for creating Qualcomm Trusted VMs. + They are a kind of protected VM which is authenticated by the + firmware. + + Say Y/M here if unsure and you want to support QTVMs. diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index d2034dd81361..a1dee0e223ae 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_GUNYAH_HYPERCALLS) += gunyah.o gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o vm_mgr.o vm_mgr_mem.o gunyah_rsc_mgr-$(CONFIG_DMA_CMA) += vm_mgr_cma_mem.o obj-$(CONFIG_GUNYAH) += gunyah_rsc_mgr.o gunyah_vcpu.o +obj-$(CONFIG_GUNYAH_QCOM_TRUSTED_VM) += gunyah_qtvm.o obj-$(CONFIG_GUNYAH_PLATFORM_HOOKS) += gunyah_platform_hooks.o obj-$(CONFIG_GUNYAH_QCOM_PLATFORM) += gunyah_qcom.o obj-$(CONFIG_GUNYAH_IRQFD) += gunyah_irqfd.o diff --git a/drivers/virt/gunyah/gunyah_qtvm.c b/drivers/virt/gunyah/gunyah_qtvm.c new file mode 100644 index 000000000000..e8a6aa77107e --- /dev/null +++ b/drivers/virt/gunyah/gunyah_qtvm.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include "vm_mgr.h" + +#define PAS_VM_METADATA_SZ 8192 + +static DEFINE_MUTEX(gunyah_qtvm_lock); +static LIST_HEAD(gunyah_qtvm_list); +SRCU_NOTIFIER_HEAD_STATIC(gunyah_vm_notifier); + +struct gunyah_qtvm { + struct gunyah_vm *ghvm; + struct gunyah_vm_parcel *parcel_list; + struct list_head list; + u64 vm_image_addr; + u64 vm_image_size; + u32 pas_id; + u16 vmid; +}; + +static u16 gunyah_qtvm_pre_alloc_vmid(struct gunyah_vm *ghvm) +{ + struct gunyah_qtvm *vm = ghvm->auth_vm_mgr_data; + + return vm->vmid; +} + +static int gunyah_qtvm_pre_vm_configure(struct gunyah_vm *ghvm) +{ + struct gunyah_qtvm *vm = ghvm->auth_vm_mgr_data; + u64 start_gfn; + int ret; + + /* + * For QTVMs, the metadata is always placed at the beginning of the + * main VM memory and will always be of fixed size decided at the + * build time while signing the VM image. The metadata contains the + * signing information needed by firmware to authenticate the VM image. + * VM image once loaded into the memory looks like this: + * + * start |----------------------| + * | MDT header + hashes | + * |----------------------| + * | Kernel | + * |----------------------| + * | DTB | + * |----------------------| + * | CPIO/Ramdisk | + * |----------------------| + */ + + ghvm->config_image.parcel.start = gunyah_gpa_to_gfn(vm->vm_image_addr); + ghvm->config_image.parcel.pages = gunyah_gpa_to_gfn(vm->vm_image_size); + + ghvm->config_image.image_offset = 0; + ghvm->config_image.image_size = PAS_VM_METADATA_SZ; + + ghvm->config_image.dtb_offset = ghvm->dtb.config.guest_phys_addr - + gunyah_gfn_to_gpa(ghvm->config_image.parcel.start); + ghvm->config_image.dtb_size = ghvm->dtb.config.size; + + if ((ghvm->dtb.config.guest_phys_addr + ghvm->config_image.dtb_size) > + (gunyah_gfn_to_gpa(ghvm->config_image.parcel.start) + + gunyah_gfn_to_gpa(ghvm->config_image.parcel.pages))) { + /* + * DTB is out of the config image bounds. + * This is should not happen! + */ + dev_err(ghvm->parent, "DTB is outside the image parcel\n"); + return -EINVAL; + } + + /* + * RM would expect to have all the memory mentioned + * in the VM DT to be shared/lent before the VM starts. + * We will lend the primary memory parcel as + * part of the vm_configure operation. So, share the rest + * of the VM memory here. + */ + start_gfn = gunyah_gpa_to_gfn(vm->vm_image_addr + vm->vm_image_size); + ret = gunyah_share_range_as_parcels(ghvm, start_gfn, ULONG_MAX, &vm->parcel_list); + if (ret) { + dev_err(ghvm->parent, "Failed to share non primary parcel(s) before VM start\n"); + return ret; + } + + return 0; +} + +static int gunyah_qtvm_authenticate(struct gunyah_vm *ghvm) +{ + struct gunyah_qtvm *vm = ghvm->auth_vm_mgr_data; + struct gunyah_rm_vm_authenticate_param_entry entry; + int ret; + + entry.param_type = GUNYAH_VM_AUTH_PARAM_PAS_ID; + entry.param = vm->pas_id; + + ret = gunyah_rm_vm_authenticate(ghvm->rm, vm->vmid, 1, &entry); + if (ret) { + dev_err(ghvm->parent, "Failed to Authenticate VM: %d\n", ret); + return ret; + } + + return 0; +} + +static struct gunyah_auth_vm_mgr_ops vm_ops = { + .pre_alloc_vmid = gunyah_qtvm_pre_alloc_vmid, + .pre_vm_configure = gunyah_qtvm_pre_vm_configure, + .vm_authenticate = gunyah_qtvm_authenticate, +}; + +static long gunyah_qtvm_attach(struct gunyah_vm *ghvm, struct gunyah_auth_desc *desc) +{ + struct gunyah_qtvm_auth_arg arg; + struct gunyah_qtvm *vm; + void __user *argp; + + if (desc->arg_size > sizeof(struct gunyah_qtvm_auth_arg)) + return -EINVAL; + + argp = u64_to_user_ptr(desc->arg); + if (copy_from_user(&arg, argp, desc->arg_size)) + return -EFAULT; + + if (overflows_type(arg.guest_phys_addr + arg.size, + u64)) + return -EOVERFLOW; + + mutex_lock(&gunyah_qtvm_lock); + vm = kzalloc(sizeof(*vm), GFP_KERNEL_ACCOUNT); + if (!vm) { + mutex_unlock(&gunyah_qtvm_lock); + return -ENOMEM; + } + + vm->vmid = arg.vm_id; + vm->pas_id = arg.peripheral_id; + + /* This would be the primary Image parcel */ + vm->vm_image_addr = arg.guest_phys_addr; + vm->vm_image_size = arg.size; + vm->ghvm = ghvm; + + ghvm->auth = GUNYAH_RM_VM_AUTH_QCOM_TRUSTED_VM; + ghvm->auth_vm_mgr_ops = &vm_ops; + ghvm->auth_vm_mgr_data = vm; + + list_add(&vm->list, &gunyah_qtvm_list); + mutex_unlock(&gunyah_qtvm_lock); + return -EINVAL; +} + +static void gunyah_qtvm_detach(struct gunyah_vm *ghvm) +{ + struct gunyah_qtvm *vm = ghvm->auth_vm_mgr_data; + + kfree(vm->parcel_list); + list_del(&vm->list); + kfree(vm); + ghvm->auth_vm_mgr_ops = NULL; + ghvm->auth_vm_mgr_data = NULL; +} + +static struct gunyah_auth_vm_mgr auth_vm = { + .type = GUNYAH_QCOM_TRUSTED_VM_TYPE, + .name = "gunyah_qtvm", + .mod = THIS_MODULE, + .vm_attach = gunyah_qtvm_attach, + .vm_detach = gunyah_qtvm_detach, +}; + +static int __init gunyah_qtvm_init(void) +{ + mutex_init(&gunyah_qtvm_lock); + return gunyah_auth_vm_mgr_register(&auth_vm); +} + +static void __exit gunyah_qtvm_exit(void) +{ + gunyah_auth_vm_mgr_unregister(&auth_vm); +} + +module_init(gunyah_qtvm_init); +module_exit(gunyah_qtvm_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Gunyah Qualcomm Trusted VM Driver"); diff --git a/drivers/virt/gunyah/rsc_mgr.h b/drivers/virt/gunyah/rsc_mgr.h index ddc6e67c3e10..4eedf462d63e 100644 --- a/drivers/virt/gunyah/rsc_mgr.h +++ b/drivers/virt/gunyah/rsc_mgr.h @@ -74,7 +74,7 @@ int gunyah_rm_vm_stop(struct gunyah_rm *rm, u16 vmid); enum gunyah_rm_vm_auth_mechanism { /* clang-format off */ GUNYAH_RM_VM_AUTH_NONE = 0, - GUNYAH_RM_VM_AUTH_QCOM_PIL_ELF = 1, + GUNYAH_RM_VM_AUTH_QCOM_TRUSTED_VM = 1, GUNYAH_RM_VM_AUTH_QCOM_ANDROID_PVM = 2, /* clang-format on */ };