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