gunyah: loader: Porting Gunyah Main driver to Pineapple

Add gunyah main driver for Pineapple.

This is just snapshot of kalama's gunyah main driver from
'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix
IBI for invalid device OR handler"")'from msm-5.15 branch.

Change-Id: Id8ff3aecad9051fc1fb1613f49a15f57235d4992
Signed-off-by: Peng Yang <quic_penyan@quicinc.com>
This commit is contained in:
Peng Yang 2022-07-04 20:19:27 -07:00 committed by Gerrit - the friendly Code Review server
parent c4f9bb20a9
commit b9a522e8ba
5 changed files with 1203 additions and 0 deletions

View File

@ -1,5 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
config GUNYAH
tristate "Gunyah Virtualization support"
help
The Gunyah driver adds support for creating and launching
virtual machines from userspace. This driver exposes various
IOCTLs to allow userspace interface to manage virtual machines.
The userspace requests are relayed to Gunyah hypervisor via
this driver.
config GH_SECURE_VM_LOADER
tristate "Gunyah Secure Virtual Machine Loader Driver"
depends on GUNYAH

View File

@ -7,3 +7,8 @@ gh_rm_drv-y += gh_rm_core.o gh_rm_iface.o
obj-$(CONFIG_GH_IRQ_LEND) += gh_irq_lend.o
obj-$(CONFIG_GH_MEM_NOTIFIER) += gh_mem_notifier.o
obj-$(CONFIG_GH_GUEST_POPS) += gh_guest_pops.o
obj-$(CONFIG_GUNYAH) += gunyah.o
gunyah-y := gh_main.o gh_secure_vm_virtio_backend.o
gunyah-$(CONFIG_GH_SECURE_VM_LOADER) += gh_secure_vm_loader.o
gunyah-$(CONFIG_GH_PROXY_SCHED) += gh_proxy_sched.o
CFLAGS_gh_secure_vm_virtio_backend.o = -DDYNAMIC_DEBUG_MODULE

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

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