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"); diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index d997663471c1..9ac166c0c781 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -1,5 +1,24 @@ # 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 + 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 @@ -33,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 63baffbaba7e..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 @@ -7,3 +8,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_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"); 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/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 */ 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 */