mirror of
https://github.com/torvalds/linux.git
synced 2026-08-01 03:59:40 +02:00
Merge "gunyah: gh_ctrl: Porting gh_ctrl driver to Pineapple"
This commit is contained in:
commit
ac1d875b86
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
315
drivers/tty/hvc/hvc_gunyah.c
Normal file
315
drivers/tty/hvc/hvc_gunyah.c
Normal 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");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
268
drivers/virt/gunyah/gh_ctrl.c
Normal file
268
drivers/virt/gunyah/gh_ctrl.c
Normal 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");
|
||||
798
drivers/virt/gunyah/gh_main.c
Normal file
798
drivers/virt/gunyah/gh_main.c
Normal 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");
|
||||
53
drivers/virt/gunyah/gh_private.h
Normal file
53
drivers/virt/gunyah/gh_private.h
Normal 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 */
|
||||
532
drivers/virt/gunyah/gh_secure_vm_loader.c
Normal file
532
drivers/virt/gunyah/gh_secure_vm_loader.c
Normal 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);
|
||||
}
|
||||
43
drivers/virt/gunyah/gh_secure_vm_loader.h
Normal file
43
drivers/virt/gunyah/gh_secure_vm_loader.h
Normal 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
338
include/uapi/linux/gunyah.h
Normal 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 */
|
||||
Loading…
Reference in New Issue
Block a user