Merge "gunyah: gh_ctrl: Porting gh_ctrl driver to Pineapple"

This commit is contained in:
qctecmdr 2022-08-17 18:29:56 -07:00 committed by Gerrit - the friendly Code Review server
commit ac1d875b86
11 changed files with 2402 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <linux/console.h>
#include <linux/init.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/spinlock.h>
#include <linux/printk.h>
#include <linux/workqueue.h>
#include <linux/gunyah/gh_msgq.h>
#include <linux/gunyah/gh_common.h>
#include <linux/gunyah/gh_rm_drv.h>
#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");

View File

@ -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

View File

@ -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

View File

@ -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 <linux/arm-smccc.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/of.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/gunyah/gh_errno.h>
#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");

View File

@ -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 <linux/anon_inodes.h>
#include <linux/miscdevice.h>
#include <linux/pagemap.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <soc/qcom/secure_buffer.h>
#include <linux/gunyah.h>
#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");

View File

@ -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 <linux/gunyah/gh_rm_drv.h>
#include <linux/gunyah/gh_vm.h>
#include <linux/refcount.h>
#include <linux/gunyah.h>
#include <linux/wait.h>
#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 */

View File

@ -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 <linux/soc/qcom/mdt_loader.h>
#include <linux/gunyah/gh_rm_drv.h>
#include <linux/platform_device.h>
#include <linux/of_reserved_mem.h>
#include <linux/dma-mapping.h>
#include <linux/dma-direct.h>
#include <linux/of_address.h>
#include <linux/qcom_scm.h>
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <linux/of.h>
#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);
}

View File

@ -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 */

338
include/uapi/linux/gunyah.h Normal file
View File

@ -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 <linux/types.h>
#include <linux/ioctl.h>
#include <linux/virtio_types.h>
#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 */