From c4f9bb20a9385f88ff538c958f29143463fea50a Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Sun, 3 Jul 2022 19:41:27 -0700 Subject: [PATCH 1/4] gunyah: loader: Porting Gunyah Secure VM Laoder driver to Pineapple Add gunyah secure vm loader driver for Pineapple. This is just snapshot of kalama's secure vm loader driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: I3ec9446c85e0d8fcef66c4d4a6736b62d1d7a219 Signed-off-by: Peng Yang --- drivers/virt/gunyah/Kconfig | 10 + drivers/virt/gunyah/gh_secure_vm_loader.c | 532 ++++++++++++++++++++++ drivers/virt/gunyah/gh_secure_vm_loader.h | 43 ++ 3 files changed, 585 insertions(+) create mode 100644 drivers/virt/gunyah/gh_secure_vm_loader.c create mode 100644 drivers/virt/gunyah/gh_secure_vm_loader.h diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index d997663471c1..935cb802ffa4 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -1,5 +1,15 @@ # SPDX-License-Identifier: GPL-2.0-only +config GH_SECURE_VM_LOADER + tristate "Gunyah Secure Virtual Machine Loader Driver" + depends on GUNYAH + select QCOM_MDT_LOADER + help + This driver invokes mdt Loader to load images of + any secure guest Virtual Machine (VM). The images are loaded + in the carveout designated for the VM once the firmware name + is validated. + config GH_PROXY_SCHED tristate "PROXY Scheduling for Secondary VMs" depends on GUNYAH diff --git a/drivers/virt/gunyah/gh_secure_vm_loader.c b/drivers/virt/gunyah/gh_secure_vm_loader.c new file mode 100644 index 000000000000..701e1207ba8c --- /dev/null +++ b/drivers/virt/gunyah/gh_secure_vm_loader.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gh_private.h" +#include "gh_secure_vm_virtio_backend.h" + +#define PAGE_ROUND_UP(x) ((((u64)(x) + (PAGE_SIZE - 1)) / PAGE_SIZE) * PAGE_SIZE) + +struct gh_sec_vm_dev { + struct list_head list; + const char *vm_name; + struct device *dev; + bool system_vm; + phys_addr_t fw_phys; + void *fw_virt; + ssize_t fw_size; + int pas_id; + int vmid; + bool is_static; +}; + +const static struct { + enum gh_vm_names val; + const char *str; +} fw_name_to_vm_name[] = { + {GH_PRIMARY_VM, "pvm"}, + {GH_TRUSTED_VM, "trustedvm"}, + {GH_CPUSYS_VM, "cpusys_vm"}, + {GH_OEM_VM, "oemvm"}, +}; + +static DEFINE_SPINLOCK(gh_sec_vm_lock); +static LIST_HEAD(gh_sec_vm_list); + +static inline enum gh_vm_names get_gh_vm_name(const char *str) +{ + int vmid; + + for (vmid = 0; vmid < ARRAY_SIZE(fw_name_to_vm_name); ++vmid) { + if (!strcmp(str, fw_name_to_vm_name[vmid].str)) + return fw_name_to_vm_name[vmid].val; + } + return GH_VM_MAX; +} + +static struct gh_sec_vm_dev *get_sec_vm_dev_by_name(const char *vm_name) +{ + struct gh_sec_vm_dev *sec_vm_dev; + + spin_lock(&gh_sec_vm_lock); + + list_for_each_entry(sec_vm_dev, &gh_sec_vm_list, list) { + if (!strcmp(sec_vm_dev->vm_name, vm_name)) { + spin_unlock(&gh_sec_vm_lock); + return sec_vm_dev; + } + } + + spin_unlock(&gh_sec_vm_lock); + + return NULL; +} + +static u64 gh_sec_load_metadata(struct gh_sec_vm_dev *vm_dev, + void *mdata, size_t mdata_size) +{ + struct device *dev = vm_dev->dev; + const struct elf32_phdr *phdrs; + const struct elf32_phdr *phdr; + const struct elf32_hdr *ehdr; + bool relocatable = false; + void *metadata_start; + u64 image_start_addr = 0; + u64 image_end_addr; + u64 image_size = 0; + u32 max_paddr = 0; + u64 moffset = 0; + int i; + + ehdr = (struct elf32_hdr *)mdata; + phdrs = (struct elf32_phdr *)(ehdr + 1); + + mdata_size = PAGE_ROUND_UP(mdata_size); + + /* Calculate total image size */ + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + if (phdr->p_flags & QCOM_MDT_RELOCATABLE) + relocatable = true; + + if (phdr->p_paddr > max_paddr) { + if (phdr->p_memsz > (U64_MAX - phdr->p_paddr)) { + dev_err(dev, "Overflow detected while calculating metadata offset\"%s\"\n", + vm_dev->vm_name); + return 0; + } + image_end_addr = phdr->p_paddr + phdr->p_memsz; + max_paddr = phdr->p_paddr; + } + image_size += phdr->p_memsz; + } + + if ((image_size > (U64_MAX - mdata_size)) || + (vm_dev->fw_size < (image_size + mdata_size))) { + dev_err(dev, "Metadata cannot fit in mem_region \"%s\"\n", + vm_dev->vm_name); + return 0; + } + + if (!relocatable) + image_start_addr = vm_dev->fw_phys; + + /* Calculate suitable metadata offset */ + moffset = vm_dev->fw_size - mdata_size; + + if (moffset > vm_dev->fw_size || + (image_start_addr > (U64_MAX - moffset)) || + ((u64) vm_dev->fw_virt > (U64_MAX - moffset))) { + dev_err(dev, "Overflow detected while calculating metadata offset\"%s\"\n", + vm_dev->vm_name); + return 0; + } + + if (image_end_addr <= (image_start_addr + moffset)) { + metadata_start = vm_dev->fw_virt + moffset; + memcpy(metadata_start, mdata, mdata_size); + return moffset; + } + + dev_err(dev, "Metadata cannot fit in mem_region %s\n", + vm_dev->vm_name); + return 0; +} + +static int gh_vm_loader_sec_load(struct gh_sec_vm_dev *vm_dev, + struct gh_vm *vm) +{ + struct device *dev = vm_dev->dev; + const struct firmware *fw; + char fw_name[GH_VM_FW_NAME_MAX]; + size_t metadata_size; + u64 metadata_offset; + void *metadata; + int ret; + + scnprintf(fw_name, ARRAY_SIZE(fw_name), "%s.mdt", vm_dev->vm_name); + + ret = request_firmware(&fw, fw_name, dev); + if (ret) { + dev_err(dev, "Error requesting fw \"%s\": %d\n", fw_name, ret); + return ret; + } + + metadata = qcom_mdt_read_metadata(dev, fw, fw_name, &metadata_size, false, NULL); + if (IS_ERR(metadata)) { + release_firmware(fw); + return PTR_ERR(metadata); + } + + metadata_offset = gh_sec_load_metadata(vm_dev, metadata, metadata_size); + if (!metadata_offset) { + dev_err(dev, "Failed to load metadata \"%s\": %d\n", fw_name, ret); + goto release_fw; + } + + ret = qcom_mdt_load_no_init(dev, fw, fw_name, vm_dev->pas_id, vm_dev->fw_virt, + vm_dev->fw_phys, vm_dev->fw_size, NULL); + if (ret) { + dev_err(dev, "Failed to load fw \"%s\": %d\n", fw_name, ret); + goto release_fw; + } + + ret = gh_provide_mem(vm, vm_dev->fw_phys, + vm_dev->fw_size, vm_dev->system_vm); + + if (ret) { + dev_err(dev, "Failed to provide memory for %s, %d\n", + vm_dev->vm_name, ret); + goto release_fw; + } + + vm->is_secure_vm = true; + + ret = gh_vm_configure(GH_VM_AUTH_PIL_ELF, metadata_offset, + metadata_size, 0, 0, vm_dev->pas_id, + vm_dev->vm_name, vm); + if (ret) + dev_err(dev, "Configuring secure VM %s to memory failed %ld\n", + vm_dev->vm_name, ret); + +release_fw: + kfree(metadata); + release_firmware(fw); + return ret; +} + +static int gh_sec_vm_loader_load_fw(struct gh_sec_vm_dev *vm_dev, + struct gh_vm *vm) +{ + enum gh_vm_names vm_name; + dma_addr_t dma_handle; + struct device *dev; + int ret = 0; + void *virt; + + dev = vm_dev->dev; + + vm_name = get_gh_vm_name(vm_dev->vm_name); + + ret = gh_rm_vm_alloc_vmid(vm_name, &vm_dev->vmid); + if (ret < 0) { + dev_err(dev, "Couldn't allocate VMID for %s %d\n", + vm_dev->vm_name, ret); + return ret; + } + + vm->status.vm_status = GH_RM_VM_STATUS_LOAD; + vm->vmid = vm_dev->vmid; + if (!vm_dev->is_static) { + virt = dma_alloc_coherent(dev, vm_dev->fw_size, &dma_handle, + GFP_KERNEL); + if (!virt) { + ret = -ENOMEM; + dev_err(dev, "Couldn't allocate cma memory for %s %d\n", + vm_dev->vm_name, ret); + return ret; + } + + vm_dev->fw_virt = virt; + vm_dev->fw_phys = dma_to_phys(dev, dma_handle); + } + + ret = gh_vm_loader_sec_load(vm_dev, vm); + if (ret) { + dev_err(dev, "Loading Secure VM %s failed %d\n", + vm_dev->vm_name, ret); + if (!vm_dev->is_static) + dma_free_coherent(dev, vm_dev->fw_size, virt, dma_handle); + return ret; + } + + return ret; +} + +long gh_vm_ioctl_set_fw_name(struct gh_vm *vm, unsigned long arg) +{ + struct gh_sec_vm_dev *sec_vm_dev; + struct gh_fw_name vm_fw_name; + struct device *dev; + long ret = -EINVAL; + + if (copy_from_user(&vm_fw_name, (void __user *)arg, sizeof(vm_fw_name))) + return -EFAULT; + + mutex_lock(&vm->vm_lock); + if (strlen(vm->fw_name)) { + pr_err("Secure VM %s already loaded %ld\n", + vm->fw_name, ret); + ret = -EEXIST; + goto err_fw_name; + } + + sec_vm_dev = get_sec_vm_dev_by_name(vm_fw_name.name); + if (!sec_vm_dev) { + pr_err("Requested Secure VM %s not supported\n", + vm_fw_name.name); + ret = -EINVAL; + goto err_fw_name; + } + + dev = sec_vm_dev->dev; + + ret = gh_sec_vm_loader_load_fw(sec_vm_dev, vm); + if (ret) { + dev_err(dev, "Loading secure VM %s to memory failed %ld\n", + sec_vm_dev->vm_name, ret); + goto err_fw_name; + } + + scnprintf(vm->fw_name, ARRAY_SIZE(vm->fw_name), + "%s", vm_fw_name.name); + + mutex_unlock(&vm->vm_lock); + gh_uevent_notify_change(GH_EVENT_CREATE_VM, vm); + return ret; + +err_fw_name: + mutex_unlock(&vm->vm_lock); + return ret; +} + +long gh_vm_ioctl_get_fw_name(struct gh_vm *vm, unsigned long arg) +{ + struct gh_fw_name vm_fw_name; + + mutex_lock(&vm->vm_lock); + scnprintf(vm_fw_name.name, ARRAY_SIZE(vm_fw_name.name), + "%s", vm->fw_name); + mutex_unlock(&vm->vm_lock); + + if (copy_to_user((void __user *)arg, &vm_fw_name, sizeof(vm_fw_name))) + return -EFAULT; + + return 0; +} + +int gh_secure_vm_loader_reclaim_fw(struct gh_vm *vm) +{ + struct gh_sec_vm_dev *sec_vm_dev; + struct device *dev; + char *fw_name; + int ret = 0; + + fw_name = vm->fw_name; + sec_vm_dev = get_sec_vm_dev_by_name(fw_name); + if (!sec_vm_dev) { + pr_err("Requested Secure VM %s not supported\n", fw_name); + return -EINVAL; + } + + dev = sec_vm_dev->dev; + + ret = gh_reclaim_mem(vm, sec_vm_dev->fw_phys, + sec_vm_dev->fw_size, sec_vm_dev->system_vm); + if (!sec_vm_dev->is_static) { + dma_free_coherent(dev, sec_vm_dev->fw_size, sec_vm_dev->fw_virt, + phys_to_dma(dev, sec_vm_dev->fw_phys)); + } + + return ret; +} + +static int gh_vm_loader_mem_probe(struct gh_sec_vm_dev *sec_vm_dev) +{ + struct device *dev = sec_vm_dev->dev; + struct reserved_mem *rmem; + struct device_node *node; + struct resource res; + phys_addr_t phys; + ssize_t size; + void *virt; + int ret; + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "DT error getting \"memory-region\"\n"); + return -EINVAL; + } + + if (!of_property_read_bool(node, "no-map")) { + sec_vm_dev->is_static = false; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (ret) { + pr_err("%s: dma_set_mask_and_coherent failed\n", __func__); + goto err_of_node_put; + } + + ret = of_reserved_mem_device_init_by_idx(dev, dev->of_node, 0); + if (ret) { + pr_err("%s: Failed to initialize CMA mem, ret %d\n", __func__, ret); + goto err_of_node_put; + } + + rmem = of_reserved_mem_lookup(node); + if (!rmem) { + ret = -EINVAL; + pr_err("%s: failed to acquire memory region for %s\n", + __func__, node->name); + goto err_of_node_put; + } + + sec_vm_dev->fw_size = rmem->size; + } else { + sec_vm_dev->is_static = true; + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(dev, "error %d getting \"memory-region\" resource\n", + ret); + goto err_of_node_put; + } + + phys = res.start; + size = (size_t)resource_size(&res); + virt = memremap(phys, size, MEMREMAP_WC); + if (!virt) { + dev_err(dev, "Unable to remap firmware memory\n"); + ret = -ENOMEM; + goto err_of_node_put; + } + + sec_vm_dev->fw_phys = phys; + sec_vm_dev->fw_virt = virt; + sec_vm_dev->fw_size = size; + } + +err_of_node_put: + of_node_put(node); + return ret; +} + +static int gh_secure_vm_loader_probe(struct platform_device *pdev) +{ + struct gh_sec_vm_dev *sec_vm_dev; + struct device *dev = &pdev->dev; + enum gh_vm_names vm_name; + int ret; + + sec_vm_dev = devm_kzalloc(dev, sizeof(*sec_vm_dev), GFP_KERNEL); + if (!sec_vm_dev) + return -ENOMEM; + + sec_vm_dev->dev = dev; + platform_set_drvdata(pdev, sec_vm_dev); + + ret = of_property_read_u32(dev->of_node, + "qcom,pas-id", &sec_vm_dev->pas_id); + if (ret) { + dev_err(dev, "DT error getting \"qcom,pas-id\": %d\n", ret); + return ret; + } + + sec_vm_dev->system_vm = of_property_read_bool(dev->of_node, "qcom,no-shutdown"); + if (sec_vm_dev->system_vm) + dev_info(dev, "Vm with no shutdown attribute added\n"); + + + ret = of_property_read_u32(dev->of_node, + "qcom,vmid", &sec_vm_dev->vmid); + if (ret) { + dev_err(dev, "DT error getting \"qcom,vmid\": %d\n", ret); + return ret; + } + + ret = gh_vm_loader_mem_probe(sec_vm_dev); + if (ret) + return ret; + + ret = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name", + &sec_vm_dev->vm_name); + if (ret) + goto err_unmap_fw; + + vm_name = get_gh_vm_name(sec_vm_dev->vm_name); + if (vm_name == GH_VM_MAX) { + dev_err(dev, "Requested Secure VM %d not supported\n", vm_name); + ret = -EINVAL; + goto err_unmap_fw; + } + + if (get_sec_vm_dev_by_name(sec_vm_dev->vm_name)) { + dev_err(dev, "Requested Secure VM %s already present\n", sec_vm_dev->vm_name); + ret = -EINVAL; + goto err_unmap_fw; + } + + ret = gh_parse_virtio_properties(dev, sec_vm_dev->vm_name); + if (ret) + goto err_unmap_fw; + + spin_lock(&gh_sec_vm_lock); + list_add(&sec_vm_dev->list, &gh_sec_vm_list); + spin_unlock(&gh_sec_vm_lock); + + return 0; + +err_unmap_fw: + memunmap(sec_vm_dev->fw_virt); + return ret; +} + +static int gh_secure_vm_loader_remove(struct platform_device *pdev) +{ + struct gh_sec_vm_dev *sec_vm_dev; + + sec_vm_dev = platform_get_drvdata(pdev); + + spin_lock(&gh_sec_vm_lock); + list_del(&sec_vm_dev->list); + spin_unlock(&gh_sec_vm_lock); + + if (sec_vm_dev->is_static) + memunmap(sec_vm_dev->fw_virt); + else + of_reserved_mem_device_release(&pdev->dev); + + return gh_virtio_backend_remove(sec_vm_dev->vm_name); +} + +static const struct of_device_id gh_secure_vm_loader_match_table[] = { + { .compatible = "qcom,gh-secure-vm-loader" }, + {}, +}; + +static struct platform_driver gh_secure_vm_loader_drv = { + .probe = gh_secure_vm_loader_probe, + .remove = gh_secure_vm_loader_remove, + .driver = { + .name = "gh_secure_vm_loader", + .of_match_table = gh_secure_vm_loader_match_table, + }, +}; + +int gh_secure_vm_loader_init(void) +{ + return platform_driver_register(&gh_secure_vm_loader_drv); +} + +void gh_secure_vm_loader_exit(void) +{ + platform_driver_unregister(&gh_secure_vm_loader_drv); +} diff --git a/drivers/virt/gunyah/gh_secure_vm_loader.h b/drivers/virt/gunyah/gh_secure_vm_loader.h new file mode 100644 index 000000000000..5748833c2d01 --- /dev/null +++ b/drivers/virt/gunyah/gh_secure_vm_loader.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _GH_SECURE_VM_LOADER_H +#define _GH_SECURE_VM_LOADER_H + +#include "gh_private.h" +/* + * secure vm loader APIs + */ +#if IS_ENABLED(CONFIG_GH_SECURE_VM_LOADER) +int gh_secure_vm_loader_init(void); +void gh_secure_vm_loader_exit(void); +long gh_vm_ioctl_set_fw_name(struct gh_vm *vm, unsigned long arg); +long gh_vm_ioctl_get_fw_name(struct gh_vm *vm, unsigned long arg); +int gh_secure_vm_loader_reclaim_fw(struct gh_vm *vm); +#else +static int gh_secure_vm_loader_init(void) +{ + return -EINVAL; +} +static void gh_secure_vm_loader_exit(void) +{ +} +static inline long gh_vm_ioctl_set_fw_name(struct gh_vm *vm, + unsigned long arg) +{ + return -EINVAL; +} +static inline long gh_vm_ioctl_get_fw_name(struct gh_vm *vm, + unsigned long arg) +{ + return -EINVAL; +} +static inline int gh_secure_vm_loader_reclaim_fw(struct gh_vm *vm) +{ + return -EINVAL; +} +#endif + +#endif /* _GH_SECURE_VM_LOADER_H */ From b9a522e8bafdabe08684221c2b63ccb83745a9d0 Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Mon, 4 Jul 2022 20:19:27 -0700 Subject: [PATCH 2/4] gunyah: loader: Porting Gunyah Main driver to Pineapple Add gunyah main driver for Pineapple. This is just snapshot of kalama's gunyah main driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: Id8ff3aecad9051fc1fb1613f49a15f57235d4992 Signed-off-by: Peng Yang --- drivers/virt/gunyah/Kconfig | 9 + drivers/virt/gunyah/Makefile | 5 + drivers/virt/gunyah/gh_main.c | 798 +++++++++++++++++++++++++++++++ drivers/virt/gunyah/gh_private.h | 53 ++ include/uapi/linux/gunyah.h | 338 +++++++++++++ 5 files changed, 1203 insertions(+) create mode 100644 drivers/virt/gunyah/gh_main.c create mode 100644 drivers/virt/gunyah/gh_private.h create mode 100644 include/uapi/linux/gunyah.h diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index 935cb802ffa4..35be95fc6480 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -1,5 +1,14 @@ # SPDX-License-Identifier: GPL-2.0-only +config GUNYAH + tristate "Gunyah Virtualization support" + help + The Gunyah driver adds support for creating and launching + virtual machines from userspace. This driver exposes various + IOCTLs to allow userspace interface to manage virtual machines. + The userspace requests are relayed to Gunyah hypervisor via + this driver. + config GH_SECURE_VM_LOADER tristate "Gunyah Secure Virtual Machine Loader Driver" depends on GUNYAH diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index 63baffbaba7e..04696ba5a1ba 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -7,3 +7,8 @@ gh_rm_drv-y += gh_rm_core.o gh_rm_iface.o obj-$(CONFIG_GH_IRQ_LEND) += gh_irq_lend.o obj-$(CONFIG_GH_MEM_NOTIFIER) += gh_mem_notifier.o obj-$(CONFIG_GH_GUEST_POPS) += gh_guest_pops.o +obj-$(CONFIG_GUNYAH) += gunyah.o +gunyah-y := gh_main.o gh_secure_vm_virtio_backend.o +gunyah-$(CONFIG_GH_SECURE_VM_LOADER) += gh_secure_vm_loader.o +gunyah-$(CONFIG_GH_PROXY_SCHED) += gh_proxy_sched.o +CFLAGS_gh_secure_vm_virtio_backend.o = -DDYNAMIC_DEBUG_MODULE diff --git a/drivers/virt/gunyah/gh_main.c b/drivers/virt/gunyah/gh_main.c new file mode 100644 index 000000000000..858b21b48b5f --- /dev/null +++ b/drivers/virt/gunyah/gh_main.c @@ -0,0 +1,798 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gh_secure_vm_virtio_backend.h" +#include "gh_secure_vm_loader.h" +#include "gh_proxy_sched.h" +#include "gh_private.h" + +#define MAX_VCPU_NAME 20 /* gh-vcpu:u32_max +1 */ + +SRCU_NOTIFIER_HEAD_STATIC(gh_vm_notifier); + +/* + * Support for RM calls and the wait for change of status + */ +#define gh_rm_call_and_set_status(name) \ +static int gh_##name(struct gh_vm *vm, int vm_status) \ +{ \ + int ret = 0; \ + ret = gh_rm_##name(vm->vmid); \ + if (!ret) \ + vm->status.vm_status = vm_status; \ + return ret; \ +} + +gh_rm_call_and_set_status(vm_start); + +int gh_register_vm_notifier(struct notifier_block *nb) +{ + return srcu_notifier_chain_register(&gh_vm_notifier, nb); +} +EXPORT_SYMBOL(gh_register_vm_notifier); + +int gh_unregister_vm_notifier(struct notifier_block *nb) +{ + return srcu_notifier_chain_unregister(&gh_vm_notifier, nb); +} +EXPORT_SYMBOL(gh_unregister_vm_notifier); + +static void gh_notify_clients(struct gh_vm *vm, unsigned long val) +{ + srcu_notifier_call_chain(&gh_vm_notifier, val, &vm->vmid); +} + +static void gh_notif_vm_status(struct gh_vm *vm, + struct gh_rm_notif_vm_status_payload *status) +{ + if (vm->vmid != status->vmid) + return; + + /* Wake up the waiters only if there's a change in any of the states */ + if (status->vm_status != vm->status.vm_status && + (status->vm_status == GH_RM_VM_STATUS_RESET || + status->vm_status == GH_RM_VM_STATUS_READY)) { + pr_info("VM: %d status %d complete\n", vm->vmid, + status->vm_status); + vm->status.vm_status = status->vm_status; + wake_up_interruptible(&vm->vm_status_wait); + } +} + +static void gh_notif_vm_exited(struct gh_vm *vm, + struct gh_rm_notif_vm_exited_payload *vm_exited) +{ + if (vm->vmid != vm_exited->vmid) + return; + + mutex_lock(&vm->vm_lock); + vm->exit_type = vm_exited->exit_type; + vm->status.vm_status = GH_RM_VM_STATUS_EXITED; + gh_wakeup_all_vcpus(vm->vmid); + wake_up_interruptible(&vm->vm_status_wait); + mutex_unlock(&vm->vm_lock); +} + +int gh_wait_for_vm_status(struct gh_vm *vm, int wait_status) +{ + int ret = 0; + + ret = wait_event_interruptible(vm->vm_status_wait, + vm->status.vm_status == wait_status); + if (ret < 0) + pr_err("Wait for VM_STATUS %d interrupted\n", wait_status); + + return ret; +} + +static int gh_vm_rm_notifier_fn(struct notifier_block *nb, + unsigned long cmd, void *data) +{ + struct gh_vm *vm; + + vm = container_of(nb, struct gh_vm, rm_nb); + + switch (cmd) { + case GH_RM_NOTIF_VM_STATUS: + gh_notif_vm_status(vm, data); + break; + case GH_RM_NOTIF_VM_EXITED: + gh_notif_vm_exited(vm, data); + break; + } + + return NOTIFY_DONE; +} + +static void gh_vm_cleanup(struct gh_vm *vm) +{ + gh_vmid_t vmid = vm->vmid; + int vm_status = vm->status.vm_status; + int ret; + + switch (vm_status) { + case GH_RM_VM_STATUS_EXITED: + case GH_RM_VM_STATUS_RUNNING: + case GH_RM_VM_STATUS_READY: + ret = gh_rm_unpopulate_hyp_res(vmid, vm->fw_name); + if (ret) + pr_warn("Failed to unpopulate hyp resources: %d\n", ret); + ret = gh_virtio_mmio_exit(vmid, vm->fw_name); + if (ret) + pr_warn("Failed to free virtio resources : %d\n", ret); + fallthrough; + case GH_RM_VM_STATUS_INIT: + case GH_RM_VM_STATUS_AUTH: + ret = gh_rm_vm_reset(vmid); + if (!ret) { + ret = gh_wait_for_vm_status(vm, GH_RM_VM_STATUS_RESET); + if (ret < 0) + pr_err("wait for VM_STATUS_RESET interrupted %d\n", ret); + } else + pr_warn("Reset is unsuccessful for VM:%d\n", vmid); + + if (vm->is_secure_vm) { + ret = gh_secure_vm_loader_reclaim_fw(vm); + if (ret) + pr_warn("Failed to reclaim mem VMID: %d: %d\n", vmid, ret); + } + fallthrough; + case GH_RM_VM_STATUS_LOAD: + ret = gh_rm_vm_dealloc_vmid(vmid); + if (ret) + pr_warn("Failed to dealloc VMID: %d: %d\n", vmid, ret); + vm->vmid = 0; + } + + vm->status.vm_status = GH_RM_VM_STATUS_NO_STATE; +} + +static int gh_exit_vm(struct gh_vm *vm, u32 stop_reason, u8 stop_flags) +{ + gh_vmid_t vmid = vm->vmid; + int ret = -EINVAL; + + if (!vmid) + return -ENODEV; + + mutex_lock(&vm->vm_lock); + if (vm->status.vm_status != GH_RM_VM_STATUS_RUNNING) { + pr_err("VM:%d is not running\n", vmid); + mutex_unlock(&vm->vm_lock); + return -ENODEV; + } + + ret = gh_rm_vm_stop(vmid, stop_reason, stop_flags); + if (ret) { + pr_err("Failed to stop the VM:%d ret %d\n", vmid, ret); + mutex_unlock(&vm->vm_lock); + return ret; + } + mutex_unlock(&vm->vm_lock); + + ret = gh_wait_for_vm_status(vm, GH_RM_VM_STATUS_EXITED); + if (ret) + pr_err("VM:%d stop operation is interrupted\n", vmid); + + return ret; +} + +static int gh_stop_vm(struct gh_vm *vm) +{ + gh_vmid_t vmid = vm->vmid; + int ret = -EINVAL; + + ret = gh_exit_vm(vm, GH_VM_STOP_RESTART, 0); + if (ret && ret != -ENODEV) + goto err_vm_force_stop; + + return ret; + +err_vm_force_stop: + ret = gh_exit_vm(vm, GH_VM_STOP_FORCE_STOP, + GH_RM_VM_STOP_FLAG_FORCE_STOP); + if (ret) + pr_err("VM:%d force stop has failed\n", vmid); + return ret; +} + +void gh_destroy_vcpu(struct gh_vcpu *vcpu) +{ + struct gh_vm *vm = vcpu->vm; + u32 id = vcpu->vcpu_id; + + kfree(vcpu); + vm->vcpus[id] = NULL; + vm->created_vcpus--; +} + +void gh_destroy_vm(struct gh_vm *vm) +{ + int vcpu_id = 0; + + if (vm->status.vm_status == GH_RM_VM_STATUS_NO_STATE) + goto clean_vm; + + gh_stop_vm(vm); + + while (vm->created_vcpus && vcpu_id < GH_MAX_VCPUS) { + if (!vm->vcpus[vcpu_id]) + continue; + gh_destroy_vcpu(vm->vcpus[vcpu_id]); + vcpu_id++; + } + + gh_notify_clients(vm, GH_VM_EARLY_POWEROFF); + gh_vm_cleanup(vm); + + gh_uevent_notify_change(GH_EVENT_DESTROY_VM, vm); + gh_notify_clients(vm, GH_VM_POWEROFF); + memset(vm->fw_name, 0, GH_VM_FW_NAME_MAX); + +clean_vm: + gh_rm_unregister_notifier(&vm->rm_nb); + mutex_destroy(&vm->vm_lock); + kfree(vm); +} + +static void gh_get_vm(struct gh_vm *vm) +{ + refcount_inc(&vm->users_count); +} + +static void gh_put_vm(struct gh_vm *vm) +{ + if (refcount_dec_and_test(&vm->users_count)) + gh_destroy_vm(vm); +} + +static int gh_vcpu_release(struct inode *inode, struct file *filp) +{ + struct gh_vcpu *vcpu = filp->private_data; + + gh_put_vm(vcpu->vm); + return 0; +} + +static int gh_vcpu_ioctl_run(struct gh_vcpu *vcpu) +{ + struct gh_hcall_vcpu_run_resp vcpu_run; + struct gh_vm *vm = vcpu->vm; + int ret = 0; + + mutex_lock(&vm->vm_lock); + + if (vm->status.vm_status == GH_RM_VM_STATUS_RUNNING) { + mutex_unlock(&vm->vm_lock); + goto start_vcpu_run; + } + + if (vm->vm_run_once && + vm->status.vm_status != GH_RM_VM_STATUS_RUNNING) { + pr_err("VM:%d has failed to run before\n", vm->vmid); + mutex_unlock(&vm->vm_lock); + return -EINVAL; + } + + vm->vm_run_once = true; + + if (vm->is_secure_vm && + vm->created_vcpus != vm->allowed_vcpus) { + pr_err("VCPUs created %d doesn't match with allowed %d for VM %d\n", + vm->created_vcpus, vm->allowed_vcpus, + vm->vmid); + ret = -EINVAL; + mutex_unlock(&vm->vm_lock); + return ret; + } + + if (vm->status.vm_status != GH_RM_VM_STATUS_READY) { + pr_err("VM:%d not ready to start\n", vm->vmid); + ret = -EINVAL; + mutex_unlock(&vm->vm_lock); + return ret; + } + + gh_notify_clients(vm, GH_VM_BEFORE_POWERUP); + + ret = gh_vm_start(vm, GH_RM_VM_STATUS_RUNNING); + if (ret) { + pr_err("Failed to start VM:%d %d\n", vm->vmid, ret); + mutex_unlock(&vm->vm_lock); + goto err_powerup; + } + pr_info("VM:%d started running\n", vm->vmid); + + mutex_unlock(&vm->vm_lock); + +start_vcpu_run: + /* + * proxy scheduling APIs + */ + if (gh_vm_supports_proxy_sched(vm->vmid)) { + ret = gh_vcpu_run(vm->vmid, vcpu->vcpu_id, + 0, 0, 0, &vcpu_run); + if (ret < 0) { + pr_err("Failed vcpu_run %d\n", ret); + return ret; + } + } + + ret = gh_wait_for_vm_status(vm, GH_RM_VM_STATUS_EXITED); + if (ret) + return ret; + + ret = vm->exit_type; + return ret; + +err_powerup: + gh_notify_clients(vm, GH_VM_POWERUP_FAIL); + return ret; +} + +static long gh_vcpu_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct gh_vcpu *vcpu = filp->private_data; + int ret = -EINVAL; + + switch (cmd) { + case GH_VCPU_RUN: + ret = gh_vcpu_ioctl_run(vcpu); + break; + default: + pr_err("Invalid gunyah VCPU ioctl 0x%lx\n", cmd); + break; + } + return ret; +} + +static const struct file_operations gh_vcpu_fops = { + .unlocked_ioctl = gh_vcpu_ioctl, + .release = gh_vcpu_release, + .llseek = noop_llseek, +}; + +static int gh_vm_ioctl_get_vcpu_count(struct gh_vm *vm) +{ + if (!vm->is_secure_vm) + return -EINVAL; + + if (vm->status.vm_status != GH_RM_VM_STATUS_READY) + return -EAGAIN; + + return vm->allowed_vcpus; +} + +static long gh_vm_ioctl_create_vcpu(struct gh_vm *vm, u32 id) +{ + struct gh_vcpu *vcpu; + struct file *file; + char name[MAX_VCPU_NAME]; + int fd, err = 0; + + if (id > GH_MAX_VCPUS) + return -EINVAL; + + mutex_lock(&vm->vm_lock); + if (vm->vcpus[id]) { + err = -EEXIST; + mutex_unlock(&vm->vm_lock); + return err; + } + + vcpu = kzalloc(sizeof(*vcpu), GFP_KERNEL); + if (!vcpu) { + err = -ENOMEM; + mutex_unlock(&vm->vm_lock); + return err; + } + + vcpu->vcpu_id = id; + vcpu->vm = vm; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + err = fd; + goto err_destroy_vcpu; + } + + snprintf(name, sizeof(name), "gh-vcpu:%d", id); + file = anon_inode_getfile(name, &gh_vcpu_fops, vcpu, O_RDWR); + if (IS_ERR(file)) { + err = PTR_ERR(file); + goto err_put_fd; + } + + fd_install(fd, file); + gh_get_vm(vm); + + vm->vcpus[id] = vcpu; + vm->created_vcpus++; + mutex_unlock(&vm->vm_lock); + return fd; + +err_put_fd: + put_unused_fd(fd); +err_destroy_vcpu: + kfree(vcpu); + mutex_unlock(&vm->vm_lock); + return err; +} + +int gh_reclaim_mem(struct gh_vm *vm, phys_addr_t phys, + ssize_t size, bool is_system_vm) +{ + int destVMperm[1] = {PERM_READ | PERM_WRITE | PERM_EXEC}; + int vmid = vm->vmid; + int destVM[1] = {VMID_HLOS}; + int srcVM[1] = {vmid}; + int ret = 0; + + if (!is_system_vm) { + ret = gh_rm_mem_reclaim(vm->mem_handle, 0); + + if (ret) + pr_err("Failed to reclaim memory for %d, %d\n", + vm->vmid, ret); + } + + ret = hyp_assign_phys(phys, size, srcVM, 1, destVM, destVMperm, 1); + + return ret; +} + +int gh_provide_mem(struct gh_vm *vm, phys_addr_t phys, + ssize_t size, bool is_system_vm) +{ + int destVMperm[1] = {PERM_READ | PERM_WRITE | PERM_EXEC}; + gh_vmid_t vmid = vm->vmid; + struct gh_acl_desc *acl_desc; + struct gh_sgl_desc *sgl_desc; + int srcVM[1] = {VMID_HLOS}; + int destVM[1] = {vmid}; + int ret = 0; + + acl_desc = kzalloc(offsetof(struct gh_acl_desc, acl_entries[1]), + GFP_KERNEL); + if (!acl_desc) + return -ENOMEM; + + acl_desc->n_acl_entries = 1; + acl_desc->acl_entries[0].vmid = vmid; + acl_desc->acl_entries[0].perms = + GH_RM_ACL_X | GH_RM_ACL_R | GH_RM_ACL_W; + + sgl_desc = kzalloc(offsetof(struct gh_sgl_desc, sgl_entries[1]), + GFP_KERNEL); + if (!sgl_desc) { + kfree(acl_desc); + return -ENOMEM; + } + + sgl_desc->n_sgl_entries = 1; + sgl_desc->sgl_entries[0].ipa_base = phys; + sgl_desc->sgl_entries[0].size = size; + + ret = hyp_assign_phys(phys, size, srcVM, 1, destVM, destVMperm, 1); + if (ret) { + pr_err("failed hyp_assign for %pa address of size %zx - subsys VMid %d rc:%d\n", + phys, size, vmid, ret); + goto err_hyp_assign; + } + + /* + * A system VM is deemed critical for the functioning of the + * system. The memory donated to this VM can't be reclaimed + * by host OS at any point in time after donating it. + * Whereas any memory lent to a non system VM, can be reclaimed + * when VM terminates. + */ + if (is_system_vm) + ret = gh_rm_mem_donate(GH_RM_MEM_TYPE_NORMAL, 0, 0, + acl_desc, sgl_desc, NULL, &vm->mem_handle); + else + ret = gh_rm_mem_lend(GH_RM_MEM_TYPE_NORMAL, 0, 0, acl_desc, + sgl_desc, NULL, &vm->mem_handle); + + if (ret) + ret = hyp_assign_phys(phys, size, destVM, 1, + srcVM, destVMperm, 1); + +err_hyp_assign: + kfree(acl_desc); + kfree(sgl_desc); + return ret; +} + +long gh_vm_configure(u16 auth_mech, u64 image_offset, + u64 image_size, u64 dtb_offset, u64 dtb_size, + u32 pas_id, const char *fw_name, struct gh_vm *vm) +{ + struct gh_vm_auth_param_entry entry; + long ret = -EINVAL; + int nr_vcpus = 0; + + switch (auth_mech) { + case GH_VM_AUTH_PIL_ELF: + ret = gh_rm_vm_config_image(vm->vmid, auth_mech, + vm->mem_handle, image_offset, + image_size, dtb_offset, dtb_size); + if (ret) { + pr_err("VM_CONFIG failed for VM:%d %d\n", + vm->vmid, ret); + return ret; + } + vm->status.vm_status = GH_RM_VM_STATUS_AUTH; + if (!pas_id) { + pr_err("Incorrect pas_id %d for VM:%d\n", pas_id, + vm->vmid); + return -EINVAL; + } + entry.auth_param_type = GH_VM_AUTH_PARAM_PAS_ID; + entry.auth_param = pas_id; + + ret = gh_rm_vm_auth_image(vm->vmid, 1, &entry); + if (ret) { + pr_err("VM_AUTH_IMAGE failed for VM:%d %d\n", + vm->vmid, ret); + return ret; + } + vm->status.vm_status = GH_RM_VM_STATUS_INIT; + break; + default: + pr_err("Invalid auth mechanism for VM\n"); + return ret; + } + + ret = gh_rm_vm_init(vm->vmid); + if (ret) { + pr_err("VM_INIT_IMAGE failed for VM:%d %d\n", + vm->vmid, ret); + return ret; + } + + ret = gh_wait_for_vm_status(vm, GH_RM_VM_STATUS_READY); + if (ret < 0) + pr_err("wait for VM_STATUS_RESET interrupted %d\n", ret); + + ret = gh_rm_populate_hyp_res(vm->vmid, fw_name); + if (ret < 0) { + pr_err("Failed to populate resources %d\n", ret); + return ret; + } + + if (vm->is_secure_vm) { + nr_vcpus = gh_get_nr_vcpus(vm->vmid); + + if (nr_vcpus < 0) { + pr_err("Failed to get vcpu count for vm %d ret%d\n", + vm->vmid, nr_vcpus); + ret = nr_vcpus; + return ret; + } else if (!nr_vcpus) /* Hypervisor scheduled case when at least 1 vcpu is needed */ + nr_vcpus = 1; + + vm->allowed_vcpus = nr_vcpus; + } + + return ret; +} + +static long gh_vm_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct gh_vm *vm = filp->private_data; + long ret = -EINVAL; + + switch (cmd) { + case GH_CREATE_VCPU: + ret = gh_vm_ioctl_create_vcpu(vm, arg); + break; + case GH_VM_SET_FW_NAME: + ret = gh_vm_ioctl_set_fw_name(vm, arg); + break; + case GH_VM_GET_FW_NAME: + ret = gh_vm_ioctl_get_fw_name(vm, arg); + break; + case GH_VM_GET_VCPU_COUNT: + ret = gh_vm_ioctl_get_vcpu_count(vm); + break; + default: + ret = gh_virtio_backend_ioctl(vm->fw_name, cmd, arg); + break; + } + return ret; +} + +static int gh_vm_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct gh_vm *vm = file->private_data; + int ret = -EINVAL; + + ret = gh_virtio_backend_mmap(vm->fw_name, vma); + + return ret; +} + +static int gh_vm_release(struct inode *inode, struct file *filp) +{ + struct gh_vm *vm = filp->private_data; + + gh_put_vm(vm); + return 0; +} + +static const struct file_operations gh_vm_fops = { + .unlocked_ioctl = gh_vm_ioctl, + .mmap = gh_vm_mmap, + .release = gh_vm_release, + .llseek = noop_llseek, +}; + +static struct gh_vm *gh_create_vm(void) +{ + struct gh_vm *vm; + int ret; + + vm = kzalloc(sizeof(*vm), GFP_KERNEL); + if (!vm) + return ERR_PTR(-ENOMEM); + + mutex_init(&vm->vm_lock); + vm->rm_nb.priority = 1; + vm->rm_nb.notifier_call = gh_vm_rm_notifier_fn; + ret = gh_rm_register_notifier(&vm->rm_nb); + if (ret) { + mutex_destroy(&vm->vm_lock); + kfree(vm); + return ERR_PTR(ret); + } + refcount_set(&vm->users_count, 1); + init_waitqueue_head(&vm->vm_status_wait); + vm->status.vm_status = GH_RM_VM_STATUS_NO_STATE; + vm->exit_type = -EINVAL; + + return vm; +} + +static long gh_dev_ioctl_create_vm(unsigned long arg) +{ + struct gh_vm *vm; + struct file *file; + int fd, err; + + vm = gh_create_vm(); + if (IS_ERR_OR_NULL(vm)) + return PTR_ERR(vm); + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + err = fd; + goto err_destroy_vm; + } + + file = anon_inode_getfile("gunyah-vm", &gh_vm_fops, vm, O_RDWR); + if (IS_ERR(file)) { + err = PTR_ERR(file); + goto err_put_fd; + } + + fd_install(fd, file); + + return fd; + +err_put_fd: + put_unused_fd(fd); +err_destroy_vm: + gh_put_vm(vm); + return err; +} + +static long gh_dev_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + long ret = -EINVAL; + + switch (cmd) { + case GH_CREATE_VM: + ret = gh_dev_ioctl_create_vm(arg); + break; + default: + pr_err("Invalid gunyah dev ioctl 0x%lx\n", cmd); + break; + } + + return ret; +} + +static const struct file_operations gh_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = gh_dev_ioctl, + .llseek = noop_llseek, +}; + +static struct miscdevice gh_dev = { + .name = "gunyah", + .minor = MISC_DYNAMIC_MINOR, + .fops = &gh_dev_fops, +}; + +void gh_uevent_notify_change(unsigned int type, struct gh_vm *vm) +{ + struct kobj_uevent_env *env; + + env = kzalloc(sizeof(*env), GFP_KERNEL_ACCOUNT); + if (!env) + return; + + if (type == GH_EVENT_CREATE_VM) + add_uevent_var(env, "EVENT=create"); + else if (type == GH_EVENT_DESTROY_VM) { + add_uevent_var(env, "EVENT=destroy"); + add_uevent_var(env, "vm_exit=%d", vm->exit_type); + } + + add_uevent_var(env, "vm_name=%s", vm->fw_name); + env->envp[env->envp_idx++] = NULL; + kobject_uevent_env(&gh_dev.this_device->kobj, KOBJ_CHANGE, env->envp); + kfree(env); +} + +static int __init gh_init(void) +{ + int ret; + + ret = gh_secure_vm_loader_init(); + if (ret) + pr_err("gunyah: secure loader init failed %d\n", ret); + + ret = gh_proxy_sched_init(); + if (ret) + pr_err("gunyah: proxy scheduler init failed %d\n", ret); + + ret = misc_register(&gh_dev); + if (ret) { + pr_err("gunyah: misc device register failed %d\n", ret); + goto err_gh_init; + } + + ret = gh_virtio_backend_init(); + if (ret) + pr_err("gunyah: virtio backend init failed %d\n", ret); + + return ret; + +err_gh_init: + gh_proxy_sched_exit(); + gh_secure_vm_loader_exit(); + return 0; +} +module_init(gh_init); + +static void __exit gh_exit(void) +{ + misc_deregister(&gh_dev); + gh_proxy_sched_exit(); + gh_secure_vm_loader_exit(); + gh_virtio_backend_exit(); +} +module_exit(gh_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/virt/gunyah/gh_private.h b/drivers/virt/gunyah/gh_private.h new file mode 100644 index 000000000000..05d8e9f81e65 --- /dev/null +++ b/drivers/virt/gunyah/gh_private.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _GH_PRIVATE_H +#define _GH_PRIVATE_H + +#include +#include +#include +#include +#include + +#define GH_EVENT_CREATE_VM 0 +#define GH_EVENT_DESTROY_VM 1 +#define GH_MAX_VCPUS 8 + +struct gh_vcpu { + u32 vcpu_id; + struct gh_vm *vm; +}; + +struct gh_vm { + bool is_secure_vm; /* is true for Qcom authenticated secure VMs */ + bool vm_run_once; + u32 created_vcpus; + u32 allowed_vcpus; + gh_vmid_t vmid; + struct gh_vcpu *vcpus[GH_MAX_VCPUS]; + char fw_name[GH_VM_FW_NAME_MAX]; + struct notifier_block rm_nb; + struct gh_vm_status status; + wait_queue_head_t vm_status_wait; + int exit_type; + refcount_t users_count; + gh_memparcel_handle_t mem_handle; + struct mutex vm_lock; +}; + +/* + * memory lending/donating and reclaiming APIs + */ +int gh_provide_mem(struct gh_vm *vm, phys_addr_t phys, + ssize_t size, bool is_system_vm); +int gh_reclaim_mem(struct gh_vm *vm, phys_addr_t phys, + ssize_t size, bool is_system_vm); +long gh_vm_configure(u16 auth_mech, u64 image_offset, + u64 image_size, u64 dtb_offset, u64 dtb_size, + u32 pas_id, const char *fw_name, struct gh_vm *vm); +void gh_uevent_notify_change(unsigned int type, struct gh_vm *vm); + +#endif /* _GH_PRIVATE_H */ diff --git a/include/uapi/linux/gunyah.h b/include/uapi/linux/gunyah.h new file mode 100644 index 000000000000..2a258838fd62 --- /dev/null +++ b/include/uapi/linux/gunyah.h @@ -0,0 +1,338 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _UAPI_LINUX_GUNYAH +#define _UAPI_LINUX_GUNYAH + +/* + * Userspace interface for /dev/gunyah - gunyah based virtual machine + * + * Note: this interface is considered experimental and may change without + * notice. + */ + +#include +#include +#include + +#define GH_IOCTL_TYPE 0xB2 + +/* + * fw_name is used to find the secure VM image by name to be loaded. + */ +#define GH_VM_FW_NAME_MAX 16 + +/** @struct gh_fw_name + * A structure to be passed to GH_VM_SET_FM_NAME ioctl + * @name - name of the secure VM image + */ +struct gh_fw_name { + char name[GH_VM_FW_NAME_MAX]; +}; + +#define VBE_ASSIGN_IOEVENTFD 1 +#define VBE_DEASSIGN_IOEVENTFD 2 + +#define VBE_ASSIGN_IRQFD 1 +#define VBE_DEASSIGN_IRQFD 2 + +#define EVENT_NEW_BUFFER 1 +#define EVENT_RESET_RQST 2 +#define EVENT_INTERRUPT_ACK 4 +#define EVENT_DRIVER_OK 8 +#define EVENT_DRIVER_FAILED 0x10 +#define EVENT_MODULE_EXIT 0x20 +#define EVENT_VM_EXIT 0x40 +#define EVENT_APP_EXIT 0x100 + +/* + * gh_vm_exit_reasons specifies the various reasons why + * the secondary VM ended its execution. VCPU_RUN returns these values + * to userspace. + */ +#define GH_VM_EXIT_REASON_UNKNOWN 0 +#define GH_VM_EXIT_REASON_SHUTDOWN 1 +#define GH_VM_EXIT_REASON_RESTART 2 +#define GH_VM_EXIT_REASON_PANIC 3 +#define GH_VM_EXIT_REASON_NSWD 4 +#define GH_VM_EXIT_REASON_HYP_ERROR 5 +#define GH_VM_EXIT_REASON_ASYNC_EXT_ABORT 6 +#define GH_VM_EXIT_REASON_FORCE_STOPPED 7 +#define GH_VM_EXIT_REASONS_MAX 8 + +/* + * ioctls for /dev/gunyah fds: + */ +/** + * GH_CREATE_VM - Driver creates a VM sepecific structure. An anon file is + * also created per VM. This would be the first IOCTL made + * on /dev/gunyah node to obtain a per VM fd for futher + * VM specific operations like VCPU creation, memory etc. + * + * Return: an fd for the per VM file created, -errno on failure + */ +#define GH_CREATE_VM _IO(GH_IOCTL_TYPE, 0x01) + +/* + * ioctls for VM fd. + */ +/** + * GH_CREATE_VCPU - Driver creates a VCPU sepecific structure. It takes + * vcpu id as the input. This also creates an anon file + * per vcpu which is used for further vcpu specific + * operations. + * + * Return: an fd for the per VCPU file created, -errno on failure + */ +#define GH_CREATE_VCPU _IO(GH_IOCTL_TYPE, 0x40) +/* + * ioctls for VM properties + */ +/** + * GH_VM_SET_FW_NAME - Userspace will specify the name of the firmware + * image that needs to be loaded into VM's memory + * after authentication. The loaded VM memory details + * are forwarded to Gunyah Hypervisor underneath. + * + * Input: gh_fw_name structure with Secure VM name as name attribute of + * the struct. + * Return: 0 if success, -errno on failure + */ +#define GH_VM_SET_FW_NAME _IOW(GH_IOCTL_TYPE, 0x41, struct gh_fw_name) +/** + * GH_VM_GET_FW_NAME - Userspace can use this IOCTL to query the name of + * the secure VM image that was loaded. + * + * Input: gh_fw_name structure to be filled with Secure VM name as the + * name attribute of the struct. + * Return: 0 if success and firmware name in struct fw_name that + * represents the firmware image name currently associated with + * the VM if a call to GH_VM_SET_FW_NAME ioctl previously was + * successful, -errno on failure + */ +#define GH_VM_GET_FW_NAME _IOR(GH_IOCTL_TYPE, 0x42, struct gh_fw_name) +/** + * GH_VM_GET_VCPU_COUNT - Userspace can use this IOCTL to query the number + * of vcpus that are supported for the VM. Userspace + * can further use this count to create VCPUs. + * + * Return: nr_vcpus for proxy scheduled VMs, 1 for hypervisor scheduled VMs, + * -errno on failure + */ +#define GH_VM_GET_VCPU_COUNT _IO(GH_IOCTL_TYPE, 0x43) +/* + * IOCTLs supported by virtio backend driver + */ +/** + * GH_GET_SHARED_MEMORY_SIZE - Userspace can use this IOCTL to query the virtio + * shared memory size of the VM. Userpsace can use + * it for mmap. + * + * Input: 64 bit unsigned integer variable to be filled with shared memory size. + * + * Return: 0 if success with shared memory size as u64 in the third argument, + * -errno on failure + */ +#define GH_GET_SHARED_MEMORY_SIZE _IOR(GH_IOCTL_TYPE, 0x61, __u64) +/** + * GH_IOEVENTFD - Eventfd created in userspace is passed to kernel using this + * ioctl. Userspace is signalled by virtio backend driver through + * this fd when data is available in the ring. + * + * Input: virtio_eventfd structure with required attributes. + * + * Return: 0 if success, -errno on failure + */ +#define GH_IOEVENTFD _IOW(GH_IOCTL_TYPE, 0x62, \ + struct virtio_eventfd) +/** + * GH_IRQFD - Eventfd created in userspace is passed to kernel using this ioctl. + * Virtio backned driver is signalled by userspace using this fd when + * the ring is serviced. + * + * Input: virtio_irqfd structure with required attributes. + * + * Return: 0 if success, -errno on failure + */ +#define GH_IRQFD _IOW(GH_IOCTL_TYPE, 0x63, \ + struct virtio_irqfd) +/** + * GH_WAIT_FOR_EVENT - Userspace waits for events from the virtio backend driver + * for indefinite time. For example when hypervisor detects + * a DRIVER_OK event, it is passed to userspace using this + * ioctl. + * + * Input: virtio_event structure with required attributes. + * + * Return: 0 if success, with the event data in struct virtio_event + * -errno on failure + */ +#define GH_WAIT_FOR_EVENT _IOWR(GH_IOCTL_TYPE, 0x64, \ + struct virtio_event) + +/** + * GH_SET_DEVICE_FEATURES - This ioctl writes virtio device features supported + * by the userspace to a page that is shared with + * guest VM. + * + * Input: virtio_dev_features structure with required attributes. + * + * Return: 0 if success, -errno on failure + */ +#define GH_SET_DEVICE_FEATURES _IOW(GH_IOCTL_TYPE, 0x65, \ + struct virtio_dev_features) +/** + * GH_SET_QUEUE_NUM_MAX - This ioctl writes max virtio queue size supported by + * the userspace to a page that is shared with guest VM. + * + * Input: virtio_queue_max structure with required attributes. + * + * Return: 0 if success, -errno on failure + */ +#define GH_SET_QUEUE_NUM_MAX _IOW(GH_IOCTL_TYPE, 0x66, \ + struct virtio_queue_max) +/** + * GH_SET_DEVICE_CONFIG_DATA - This ioctl writes device configuration data + * to a page that is shared with guest VM. + * + * Input: virtio_config_data structure with required attributes. + * + * Return: 0 if success, -errno on failure + */ +#define GH_SET_DEVICE_CONFIG_DATA _IOW(GH_IOCTL_TYPE, 0x67, \ + struct virtio_config_data) +/** + * GH_GET_DRIVER_CONFIG_DATA - This ioctl reads the driver supported virtio + * device configuration data from a page that is + * shared with guest VM. + * + * Input: virtio_config_data structure with required attributes. + * + * Return: 0 if success with driver config data in struct virtio_config_data, + * -errno on failure + */ +#define GH_GET_DRIVER_CONFIG_DATA _IOWR(GH_IOCTL_TYPE, 0x68, \ + struct virtio_config_data) +/** + * GH_GET_QUEUE_INFO - This ioctl reads the driver supported virtqueue info from + * a page that is shared with guest VM. + * + * Input: virtio_queue_info structure with required attributes. + * + * Return: 0 if success with virtqueue info in struct virtio_queue_info, + * -errno on failure + */ +#define GH_GET_QUEUE_INFO _IOWR(GH_IOCTL_TYPE, 0x69, \ + struct virtio_queue_info) +/** + * GH_GET_DRIVER_FEATURES - This ioctl reads the driver supported features from + * a page that is shared with guest VM. + * + * Input: virtio_driver_features structure with required attributes. + * + * Return: 0 if success with driver features in struct virtio_driver_features, + * -errno on failure + */ +#define GH_GET_DRIVER_FEATURES _IOWR(GH_IOCTL_TYPE, 0x6a, \ + struct virtio_driver_features) +/** + * GH_ACK_DRIVER_OK - This ioctl acknowledges the DRIVER_OK event from virtio + * backend driver. + * + * Input: 32 bit unsigned integer virtio device label. + * + * Return: 0 if success, -errno on failure + */ +#define GH_ACK_DRIVER_OK _IOWR(GH_IOCTL_TYPE, 0x6b, __u32) +/** + * GH_ACK_RESET - This ioctl acknowledges the RESET event from virtio + * backend driver. + * + * Input: virtio_ack_reset structure with required attributes. + * + * Return: 0 if success, -errno on failure + */ +#define GH_ACK_RESET _IOW(GH_IOCTL_TYPE, 0x6d, struct virtio_ack_reset) + +/* + * ioctls for vcpu fd. + */ +/** + * GH_VCPU_RUN - This command is used to run the vcpus created. VCPU_RUN + * is called on vcpu fd created previously. VCPUs are + * started individually if proxy scheduling is chosen as the + * scheduling policy and vcpus are started simultaneously + * in case of VMs whose scheduling is controlled by the + * hypervisor. In the latter case, VCPU_RUN is blocked + * until the VM terminates. + * + * Return: Reason for vm termination, -errno on failure + */ +#define GH_VCPU_RUN _IO(GH_IOCTL_TYPE, 0x80) + +struct virtio_ack_reset { + __u32 label; + __u32 reserved; +}; + +struct virtio_driver_features { + __u32 label; + __u32 reserved; + __u32 features_sel; + __u32 features; +}; + +struct virtio_queue_info { + __u32 label; + __u32 queue_sel; + __u32 queue_num; + __u32 queue_ready; + __u64 queue_desc; + __u64 queue_driver; + __u64 queue_device; +}; + +struct virtio_config_data { + __u32 label; + __u32 config_size; + __u64 config_data; +}; + +struct virtio_dev_features { + __u32 label; + __u32 reserved; + __u32 features_sel; + __u32 features; +}; + +struct virtio_queue_max { + __u32 label; + __u32 reserved; + __u32 queue_sel; + __u32 queue_num_max; +}; + +struct virtio_event { + __u32 label; + __u32 event; + __u32 event_data; + __u32 reserved; +}; + +struct virtio_eventfd { + __u32 label; + __u32 flags; + __u32 queue_num; + __s32 fd; +}; + +struct virtio_irqfd { + __u32 label; + __u32 flags; + __s32 fd; + __u32 reserved; +}; + +#endif /* _UAPI_LINUX_GUNYAH */ From 233918f39e4fcdcd92d14a8e8c50a675126fde34 Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Tue, 5 Jul 2022 00:13:55 -0700 Subject: [PATCH 3/4] hvc: gunyah: Porting hvc console for gunyah hypervisor Add hvc console driver for gunyah hypervisor. This is just snapshot of kalama's hvc console driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: I20ff6c457af005ed8781807a4e9472cfe94d54e8 Signed-off-by: Peng Yang --- drivers/tty/hvc/Kconfig | 19 +++ drivers/tty/hvc/Makefile | 1 + drivers/tty/hvc/hvc_gunyah.c | 315 +++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 drivers/tty/hvc/hvc_gunyah.c diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig index 4f9264d005c0..8076c3d1cc56 100644 --- a/drivers/tty/hvc/Kconfig +++ b/drivers/tty/hvc/Kconfig @@ -106,6 +106,25 @@ config HVC_DCC_SERIALIZE_SMP CPU hotplug to work. For example, during early chipset bringups without debug serial console support. If unsure, say N. +config HVC_GUNYAH + tristate "Gunyah tty support" + depends on GH_RM_DRV + select HVC_DRIVER + help + This console exposes communication with other + virtual machines in the Gunyah hypervisor. This + option may also be used as an early console + to another VM. + +config HVC_GUNYAH_CONSOLE + bool "Gunyah console support" + depends on HVC_GUNYAH + help + Select this option to allow Gunyah tty + as an boot console communicating with + the primary VM. Still need to specify + earlycon and console parameters. + config HVC_RISCV_SBI bool "RISC-V SBI console support" depends on RISCV_SBI_V01 diff --git a/drivers/tty/hvc/Makefile b/drivers/tty/hvc/Makefile index 98880e357941..2e02505e3913 100644 --- a/drivers/tty/hvc/Makefile +++ b/drivers/tty/hvc/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_HVC_OPAL) += hvc_opal.o hvsi_lib.o obj-$(CONFIG_HVC_OLD_HVSI) += hvsi.o obj-$(CONFIG_HVC_RTAS) += hvc_rtas.o obj-$(CONFIG_HVC_DCC) += hvc_dcc.o +obj-$(CONFIG_HVC_GUNYAH) += hvc_gunyah.o obj-$(CONFIG_HVC_DRIVER) += hvc_console.o obj-$(CONFIG_HVC_IRQ) += hvc_irq.o obj-$(CONFIG_HVC_XEN) += hvc_xen.o diff --git a/drivers/tty/hvc/hvc_gunyah.c b/drivers/tty/hvc/hvc_gunyah.c new file mode 100644 index 000000000000..3261a2ae039f --- /dev/null +++ b/drivers/tty/hvc/hvc_gunyah.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "hvc_gunyah: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hvc_console.h" + +/* + * Note: hvc_alloc follows first-come, first-served for assigning + * numbers to registered hvc instances. Thus, the following assignments occur + * when both DCC and GUNYAH consoles are compiled: + * | DCC connected | DCC not connected + * (dcc) | hvc0 | (not present) + * SELF | hvc1 | hvc0 + * PRIMARY_VM | hvc2 | hvc1 + * TRUSTED_VM | hvc3 | hvc2 + * "DCC connected" means a DCC terminal is open with device + */ + +#define HVC_GH_VTERM_COOKIE 0x474E5948 +/* # of payload bytes that can fit in a 1-fragment CONSOLE_WRITE message */ +#define GH_HVC_WRITE_MSG_SIZE ((1 * (GH_MSGQ_MAX_MSG_SIZE_BYTES - 8)) - 4) + +struct gh_hvc_prv { + struct hvc_struct *hvc; + enum gh_vm_names vm_name; + DECLARE_KFIFO(get_fifo, char, 1024); + DECLARE_KFIFO(put_fifo, char, 1024); + struct work_struct put_work; +}; + +static DEFINE_SPINLOCK(fifo_lock); +static struct gh_hvc_prv gh_hvc_data[GH_VM_MAX]; + +static inline int gh_vm_name_to_vtermno(enum gh_vm_names vmname) +{ + return vmname + HVC_GH_VTERM_COOKIE; +} + +static inline int vtermno_to_gh_vm_name(int vtermno) +{ + return vtermno - HVC_GH_VTERM_COOKIE; +} + +static int gh_hvc_notify_console_chars(struct notifier_block *this, + unsigned long cmd, void *data) +{ + struct gh_rm_notif_vm_console_chars *msg = data; + enum gh_vm_names vm_name; + int ret; + + if (cmd != GH_RM_NOTIF_VM_CONSOLE_CHARS) + return NOTIFY_DONE; + + ret = gh_rm_get_vm_name(msg->vmid, &vm_name); + if (ret) { + pr_warn_ratelimited("don't know VMID %d ret: %d\n", msg->vmid, + ret); + return NOTIFY_OK; + } + + ret = kfifo_in_spinlocked(&gh_hvc_data[vm_name].get_fifo, + msg->bytes, msg->num_bytes, + &fifo_lock); + + if (ret < 0) + pr_warn_ratelimited("dropped %d bytes from VM%d - error %d\n", + msg->num_bytes, vm_name, ret); + else if (ret < msg->num_bytes) + pr_warn_ratelimited("dropped %d bytes from VM%d - full fifo\n", + msg->num_bytes - ret, vm_name); + + if (hvc_poll(gh_hvc_data[vm_name].hvc)) + hvc_kick(); + + return NOTIFY_OK; +} + +static void gh_hvc_put_work_fn(struct work_struct *ws) +{ + gh_vmid_t vmid; + char buf[GH_HVC_WRITE_MSG_SIZE]; + int count, ret; + struct gh_hvc_prv *prv = container_of(ws, struct gh_hvc_prv, put_work); + + ret = gh_rm_get_vmid(prv->vm_name, &vmid); + if (ret) { + pr_warn_once("%s: gh_rm_get_vmid failed for %d: %d\n", + __func__, prv->vm_name, ret); + return; + } + + while (!kfifo_is_empty(&prv->put_fifo)) { + count = kfifo_out_spinlocked(&prv->put_fifo, buf, sizeof(buf), + &fifo_lock); + if (count <= 0) + continue; + + ret = gh_rm_console_write(vmid, buf, count); + if (ret) { + pr_warn_once("%s gh_rm_console_write failed for %d: %d\n", + __func__, prv->vm_name, ret); + break; + } + } +} + +static int gh_hvc_get_chars(uint32_t vtermno, char *buf, int count) +{ + int vm_name = vtermno_to_gh_vm_name(vtermno); + + if (vm_name < 0 || vm_name >= GH_VM_MAX) + return -EINVAL; + + return kfifo_out_spinlocked(&gh_hvc_data[vm_name].get_fifo, + buf, count, &fifo_lock); +} + +static int gh_hvc_put_chars(uint32_t vtermno, const char *buf, int count) +{ + int ret, vm_name = vtermno_to_gh_vm_name(vtermno); + + if (vm_name < 0 || vm_name >= GH_VM_MAX) + return -EINVAL; + + ret = kfifo_in_spinlocked(&gh_hvc_data[vm_name].put_fifo, + buf, count, &fifo_lock); + if (ret > 0) + schedule_work(&gh_hvc_data[vm_name].put_work); + return ret; +} + +static int gh_hvc_flush(uint32_t vtermno, bool wait) +{ + int ret, vm_name = vtermno_to_gh_vm_name(vtermno); + gh_vmid_t vmid; + + /* RM calls will all sleep. A flush without waiting isn't possible */ + if (!wait) + return 0; + might_sleep(); + + if (vm_name < 0 || vm_name >= GH_VM_MAX) + return -EINVAL; + + ret = gh_rm_get_vmid(vm_name, &vmid); + if (ret) + return ret; + + if (cancel_work_sync(&gh_hvc_data[vm_name].put_work)) { + /* flush the fifo */ + gh_hvc_put_work_fn(&gh_hvc_data[vm_name].put_work); + } + + return gh_rm_console_flush(vmid); +} + +static int gh_hvc_notify_add(struct hvc_struct *hp, int vm_name) +{ + int ret; + gh_vmid_t vmid; + +#ifdef CONFIG_HVC_GUNYAH_CONSOLE + /* tty layer is opening, but kernel has already opened for printk */ + if (vm_name == GH_SELF_VM) + return 0; +#endif /* CONFIG_HVC_GUNYAH_CONSOLE */ + + ret = gh_rm_get_vmid(vm_name, &vmid); + if (ret) { + pr_err("%s: gh_rm_get_vmid failed for %d: %d\n", __func__, + vm_name, ret); + return ret; + } + + return gh_rm_console_open(vmid); +} + +static void gh_hvc_notify_del(struct hvc_struct *hp, int vm_name) +{ + int ret; + gh_vmid_t vmid; + + if (vm_name < 0 || vm_name >= GH_VM_MAX) + return; + +#ifdef CONFIG_HVC_GUNYAH_CONSOLE + /* tty layer is closing, but kernel is still using for printk. */ + if (vm_name == GH_SELF_VM) + return; +#endif /* CONFIG_HVC_GUNYAH_CONSOLE */ + + if (cancel_work_sync(&gh_hvc_data[vm_name].put_work)) { + /* flush the fifo */ + gh_hvc_put_work_fn(&gh_hvc_data[vm_name].put_work); + } + + ret = gh_rm_get_vmid(vm_name, &vmid); + if (ret) + return; + + ret = gh_rm_console_close(vmid); + + if (ret) + pr_err("%s: failed close VM%d console - %d\n", __func__, + vm_name, ret); + + kfifo_reset(&gh_hvc_data[vm_name].get_fifo); +} + +static struct notifier_block gh_hvc_nb = { + .notifier_call = gh_hvc_notify_console_chars, +}; + +static const struct hv_ops gh_hv_ops = { + .get_chars = gh_hvc_get_chars, + .put_chars = gh_hvc_put_chars, + .flush = gh_hvc_flush, + .notifier_add = gh_hvc_notify_add, + .notifier_del = gh_hvc_notify_del, +}; + +#ifdef CONFIG_HVC_GUNYAH_CONSOLE +static int __init hvc_gh_console_init(void) +{ + int ret; + + /* Need to call RM CONSOLE_OPEN before console can be used */ + ret = gh_rm_console_open(0); + if (ret) + return ret; + + ret = hvc_instantiate(gh_vm_name_to_vtermno(GH_SELF_VM), 0, + &gh_hv_ops); + + return ret < 0 ? -ENODEV : 0; +} +#else +static int __init hvc_gh_console_init(void) +{ + return 0; +} +#endif /* CONFIG_HVC_GUNYAH_CONSOLE */ + +static int __init hvc_gh_init(void) +{ + int i, ret = 0; + struct gh_hvc_prv *prv; + + /* Must initialize fifos and work before calling hvc_gh_console_init */ + for (i = 0; i < GH_VM_MAX; i++) { + prv = &gh_hvc_data[i]; + prv->vm_name = i; + INIT_KFIFO(prv->get_fifo); + INIT_KFIFO(prv->put_fifo); + INIT_WORK(&prv->put_work, gh_hvc_put_work_fn); + } + + /* Must instantiate console before calling hvc_alloc */ + hvc_gh_console_init(); + + for (i = 0; i < GH_VM_MAX; i++) { + prv = &gh_hvc_data[i]; + prv->hvc = hvc_alloc(gh_vm_name_to_vtermno(i), i, &gh_hv_ops, + 256); + ret = PTR_ERR_OR_ZERO(prv->hvc); + if (ret) + goto bail; + } + + ret = gh_rm_register_notifier(&gh_hvc_nb); + if (ret) + goto bail; + + return 0; +bail: + for (--i; i >= 0; i--) { + hvc_remove(gh_hvc_data[i].hvc); + gh_hvc_data[i].hvc = NULL; + } + return ret; +} +late_initcall(hvc_gh_init); + +static __exit void hvc_gh_exit(void) +{ + int i; + + gh_rm_unregister_notifier(&gh_hvc_nb); + + for (i = 0; i < GH_VM_MAX; i++) + if (gh_hvc_data[i].hvc) { + hvc_remove(gh_hvc_data[i].hvc); + gh_hvc_data[i].hvc = NULL; + } +} +module_exit(hvc_gh_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Hypervisor Console Driver"); From 8a2ba9a2c0a38682e79dda8a5465841f6132b1ac Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Tue, 5 Jul 2022 01:30:31 -0700 Subject: [PATCH 4/4] gunyah: gh_ctrl: Porting gh_ctrl driver to Pineapple Add gh_ctrl driver for Pineapple. This is just snapshot of kalama's gh_ctrl driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: Ia3aeefb7abaf6bed1d7057ce4866d4cf92d3c8cd Signed-off-by: Peng Yang --- drivers/virt/gunyah/Kconfig | 10 ++ drivers/virt/gunyah/Makefile | 1 + drivers/virt/gunyah/gh_ctrl.c | 268 ++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 drivers/virt/gunyah/gh_ctrl.c diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index 35be95fc6480..9ac166c0c781 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -52,6 +52,16 @@ config GH_VIRT_WATCHDOG actions such as setting the bark/bite time and also petting the watchdog in the hypervisor. +config GH_CTRL + tristate "Create Gunyah entries under /sys/hypervisor" + depends on SYSFS + select SYS_HYPERVISOR + help + Create entries under /sys/hypervisor for the Gunyah hypervisor. + The driver also provides a facility for controlling + hypervisor debug features. + See Documentation/ABI/testing/sysfs-hypervisor-gunyah for more details. + config GH_DBL tristate "Gunyah Doorbell driver" help diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index 04696ba5a1ba..cb88c5d4249b 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_GH_VIRT_WATCHDOG)+= gh_virt_wdt.o +obj-$(CONFIG_GH_CTRL) += gh_ctrl.o obj-$(CONFIG_GH_DBL) += gh_dbl.o obj-$(CONFIG_GH_MSGQ) += gh_msgq.o obj-$(CONFIG_GH_RM_DRV) += gh_rm_drv.o diff --git a/drivers/virt/gunyah/gh_ctrl.c b/drivers/virt/gunyah/gh_ctrl.c new file mode 100644 index 000000000000..40e2304658bb --- /dev/null +++ b/drivers/virt/gunyah/gh_ctrl.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "gunyah: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hcall_ctrl.h" + +#define QC_HYP_SMCCC_CALL_UID \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, 0xff01) +#define QC_HYP_SMCCC_REVISION \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, 0xff03) + +#define QC_HYP_UID0 0x19bd54bd +#define QC_HYP_UID1 0x0b37571b +#define QC_HYP_UID2 0x946f609b +#define QC_HYP_UID3 0x54539de6 + +#define GH_API_INFO_API_VERSION(x) (((x) >> 0) & 0x3fff) +#define GH_API_INFO_BIG_ENDIAN(x) (((x) >> 14) & 1) +#define GH_API_INFO_IS_64BIT(x) (((x) >> 15) & 1) +#define GH_API_INFO_VARIANT(x) (((x) >> 56) & 0xff) + +#define GH_IDENTIFY_PARTITION_CSPACE(x) (((x) >> 0) & 1) +#define GH_IDENTIFY_DOORBELL(x) (((x) >> 1) & 1) +#define GH_IDENTIFY_MSGQUEUE(x) (((x) >> 2) & 1) +#define GH_IDENTIFY_VIC(x) (((x) >> 3) & 1) +#define GH_IDENTIFY_VPM(x) (((x) >> 4) & 1) +#define GH_IDENTIFY_VCPU(x) (((x) >> 5) & 1) +#define GH_IDENTIFY_MEMEXTENT(x) (((x) >> 6) & 1) +#define GH_IDENTIFY_TRACE_CTRL(x) (((x) >> 7) & 1) +#define GH_IDENTIFY_ROOTVM_CHANNEL(x) (((x) >> 16) & 1) +#define GH_IDENTIFY_SCHEDULER(x) (((x) >> 28) & 0xf) + +static bool qc_hyp_calls; +static struct gh_hcall_hyp_identify_resp gunyah_api; + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buffer) +{ + return scnprintf(buffer, PAGE_SIZE, "gunyah\n"); +} +static struct kobj_attribute type_attr = __ATTR_RO(type); + +static ssize_t api_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buffer) +{ + return scnprintf(buffer, PAGE_SIZE, "%d\n", + (int)GH_API_INFO_API_VERSION(gunyah_api.api_info)); +} +static struct kobj_attribute api_attr = __ATTR_RO(api); + +static ssize_t variant_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buffer) +{ + return scnprintf(buffer, PAGE_SIZE, "%d\n", + (int)GH_API_INFO_VARIANT(gunyah_api.api_info)); +} +static struct kobj_attribute variant_attr = __ATTR_RO(variant); + +static struct attribute *version_attrs[] = { &api_attr.attr, + &variant_attr.attr, NULL }; + +static const struct attribute_group version_group = { + .name = "version", + .attrs = version_attrs, +}; + +static int __init gh_sysfs_register(void) +{ + int ret; + + ret = sysfs_create_file(hypervisor_kobj, &type_attr.attr); + if (ret) + return ret; + + return sysfs_create_group(hypervisor_kobj, &version_group); +} + +static void __exit gh_sysfs_unregister(void) +{ + sysfs_remove_file(hypervisor_kobj, &type_attr.attr); + sysfs_remove_group(hypervisor_kobj, &version_group); +} + +#if defined(CONFIG_DEBUG_FS) + +#define QC_HYP_SMCCC_UART_DISABLE \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, 0x0) +#define QC_HYP_SMCCC_UART_ENABLE \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_VENDOR_HYP, 0xc) +#define ENABLE 1 +#define DISABLE 0 + +static struct dentry *gh_dbgfs_dir; +static int hyp_uart_enable; + +static void gh_control_hyp_uart(int val) +{ + switch (val) { + case ENABLE: + if (!hyp_uart_enable) { + hyp_uart_enable = val; + pr_info("Gunyah: enabling HYP UART\n"); + arm_smccc_1_1_smc(QC_HYP_SMCCC_UART_ENABLE, NULL); + } else { + pr_info("Gunyah: HYP UART already enabled\n"); + } + break; + case DISABLE: + if (hyp_uart_enable) { + hyp_uart_enable = val; + pr_info("Gunyah: disabling HYP UART\n"); + arm_smccc_1_1_smc(QC_HYP_SMCCC_UART_DISABLE, NULL); + } else { + pr_info("Gunyah: HYP UART already disabled\n"); + } + break; + default: + pr_info("Gunyah: supported values disable(0)/enable(1)\n"); + } +} + +static int gh_dbgfs_trace_class_set(void *data, u64 val) +{ + return gh_remap_error(gh_hcall_trace_update_class_flags(val, 0, NULL)); +} + +static int gh_dbgfs_trace_class_clear(void *data, u64 val) +{ + return gh_remap_error(gh_hcall_trace_update_class_flags(0, val, NULL)); +} + +static int gh_dbgfs_trace_class_get(void *data, u64 *val) +{ + *val = 0; + return gh_remap_error(gh_hcall_trace_update_class_flags(0, 0, val)); +} + +static int gh_dbgfs_hyp_uart_set(void *data, u64 val) +{ + gh_control_hyp_uart(val); + return 0; +} + +static int gh_dbgfs_hyp_uart_get(void *data, u64 *val) +{ + *val = hyp_uart_enable; + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(gh_dbgfs_trace_class_set_fops, + gh_dbgfs_trace_class_get, + gh_dbgfs_trace_class_set, + "0x%llx\n"); + +DEFINE_DEBUGFS_ATTRIBUTE(gh_dbgfs_trace_class_clear_fops, + gh_dbgfs_trace_class_get, + gh_dbgfs_trace_class_clear, + "0x%llx\n"); + +DEFINE_DEBUGFS_ATTRIBUTE(gh_dbgfs_hyp_uart_ctrl_fops, + gh_dbgfs_hyp_uart_get, + gh_dbgfs_hyp_uart_set, + "0x%llx\n"); + +static int __init gh_dbgfs_register(void) +{ + struct dentry *dentry; + + gh_dbgfs_dir = debugfs_create_dir("gunyah", NULL); + if (IS_ERR_OR_NULL(gh_dbgfs_dir)) + return PTR_ERR(gh_dbgfs_dir); + + if (GH_IDENTIFY_TRACE_CTRL(gunyah_api.flags[0])) { + dentry = debugfs_create_file("trace_set", 0600, gh_dbgfs_dir, + NULL, &gh_dbgfs_trace_class_set_fops); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + dentry = debugfs_create_file("trace_clear", 0600, gh_dbgfs_dir, + NULL, &gh_dbgfs_trace_class_clear_fops); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + dentry = debugfs_create_file("hyp_uart_ctrl", 0600, gh_dbgfs_dir, + NULL, &gh_dbgfs_hyp_uart_ctrl_fops); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + } + + return 0; +} + +static void __exit gh_dbgfs_unregister(void) +{ + debugfs_remove_recursive(gh_dbgfs_dir); +} +#else /* !defined (CONFIG_DEBUG_FS) */ +static inline int gh_dbgfs_register(void) { return 0; } +static inline int gh_dbgfs_unregister(void) { return 0; } +#endif + +static int __init gh_ctrl_init(void) +{ + int ret; + struct device_node *hyp; + struct arm_smccc_res res; + + hyp = of_find_node_by_path("/hypervisor"); + + if (!hyp || (!of_device_is_compatible(hyp, "qcom,gunyah-hypervisor") && + !of_device_is_compatible(hyp, "qcom,haven-hypervisor"))) { + pr_err("gunyah-hypervisor or haven-hypervisor node not present\n"); + return 0; + } + + (void)gh_hcall_hyp_identify(&gunyah_api); + + if (GH_API_INFO_API_VERSION(gunyah_api.api_info) != 1) { + pr_err("unknown version\n"); + return 0; + } + + /* Check for ARM SMCCC VENDOR_HYP service calls by UID. */ + arm_smccc_1_1_smc(QC_HYP_SMCCC_CALL_UID, &res); + if ((res.a0 == QC_HYP_UID0) && (res.a1 == QC_HYP_UID1) && + (res.a2 == QC_HYP_UID2) && (res.a3 == QC_HYP_UID3)) + qc_hyp_calls = true; + + if (qc_hyp_calls) { + ret = gh_sysfs_register(); + if (ret) + return ret; + + ret = gh_dbgfs_register(); + if (ret) + pr_warn("failed to register dbgfs: %d\n", ret); + } else { + pr_info("Gunyah: no QC HYP interface detected\n"); + } + + return 0; +} +module_init(gh_ctrl_init); + +static void __exit gh_ctrl_exit(void) +{ + gh_sysfs_unregister(); + gh_dbgfs_unregister(); +} +module_exit(gh_ctrl_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Hypervisor Control Driver");