334 lines
7.5 KiB
C
Executable File
334 lines
7.5 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2023 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/anon_inodes.h>
|
|
#include <linux/device.h>
|
|
#include <linux/file.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <trace/events/geniezone.h>
|
|
#include <linux/soc/mediatek/gzvm_drv.h>
|
|
#include <trace/hooks/gzvm.h>
|
|
|
|
/* maximum size needed for holding an integer */
|
|
#define ITOA_MAX_LEN 12
|
|
|
|
/**
|
|
* gzvm_vcpu_wakeup_all - wakes up all vCPUs associated with the specified
|
|
* gzvm.
|
|
* @gzvm: Pointer to gzvm structure.
|
|
*/
|
|
void gzvm_vcpu_wakeup_all(struct gzvm *gzvm)
|
|
{
|
|
for (int i = 0; i < GZVM_MAX_VCPUS; i++) {
|
|
if (gzvm->vcpus[i]) {
|
|
gzvm->vcpus[i]->idle_events.virtio_irq += 1;
|
|
rcuwait_wake_up(&gzvm->vcpus[i]->wait);
|
|
}
|
|
}
|
|
}
|
|
|
|
static enum hrtimer_restart gzvm_vtimer_expire(struct hrtimer *hrt)
|
|
{
|
|
struct gzvm_vcpu *vcpu;
|
|
|
|
vcpu = container_of(hrt, struct gzvm_vcpu, gzvm_vtimer);
|
|
|
|
gzvm_vcpu_wakeup_all(vcpu->gzvm);
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static void gzvm_vtimer_init(struct gzvm_vcpu *vcpu)
|
|
{
|
|
/* gzvm_vtimer init based on hrtimer */
|
|
hrtimer_init(&vcpu->gzvm_vtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD);
|
|
vcpu->gzvm_vtimer.function = gzvm_vtimer_expire;
|
|
}
|
|
|
|
void gzvm_vtimer_set(struct gzvm_vcpu *vcpu, u64 ns)
|
|
{
|
|
hrtimer_start(&vcpu->gzvm_vtimer, ktime_add_ns(ktime_get(), ns), HRTIMER_MODE_ABS_HARD);
|
|
}
|
|
|
|
void gzvm_vtimer_release(struct gzvm_vcpu *vcpu)
|
|
{
|
|
hrtimer_cancel(&vcpu->gzvm_vtimer);
|
|
}
|
|
|
|
static long gzvm_vcpu_update_one_reg(struct gzvm_vcpu *vcpu,
|
|
void __user *argp,
|
|
bool is_write)
|
|
{
|
|
struct gzvm_one_reg reg;
|
|
void __user *reg_addr;
|
|
u64 data = 0;
|
|
u64 reg_size;
|
|
long ret;
|
|
|
|
if (copy_from_user(®, argp, sizeof(reg)))
|
|
return -EFAULT;
|
|
|
|
reg_addr = u64_to_user_ptr(reg.addr);
|
|
reg_size = (reg.id & GZVM_REG_SIZE_MASK) >> GZVM_REG_SIZE_SHIFT;
|
|
reg_size = BIT(reg_size);
|
|
|
|
if (reg_size != 1 && reg_size != 2 && reg_size != 4 && reg_size != 8)
|
|
return -EINVAL;
|
|
|
|
if (is_write) {
|
|
/* GZ hypervisor would filter out invalid vcpu register access */
|
|
if (copy_from_user(&data, reg_addr, reg_size))
|
|
return -EFAULT;
|
|
} else {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
ret = gzvm_arch_vcpu_update_one_reg(vcpu, reg.id, is_write, &data);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* gzvm_vcpu_handle_mmio() - Handle mmio in kernel space.
|
|
* @vcpu: Pointer to vcpu.
|
|
*
|
|
* Return:
|
|
* * true - This mmio exit has been processed.
|
|
* * false - This mmio exit has not been processed, require userspace.
|
|
*/
|
|
static bool gzvm_vcpu_handle_mmio(struct gzvm_vcpu *vcpu)
|
|
{
|
|
__u64 addr;
|
|
__u32 len;
|
|
const void *val_ptr;
|
|
|
|
/* So far, we don't have in-kernel mmio read handler */
|
|
if (!vcpu->run->mmio.is_write)
|
|
return false;
|
|
addr = vcpu->run->mmio.phys_addr;
|
|
len = vcpu->run->mmio.size;
|
|
val_ptr = &vcpu->run->mmio.data;
|
|
|
|
return gzvm_ioevent_write(vcpu, addr, len, val_ptr);
|
|
}
|
|
|
|
/**
|
|
* gzvm_vcpu_run() - Handle vcpu run ioctl, entry point to guest and exit
|
|
* point from guest
|
|
* @vcpu: Pointer to struct gzvm_vcpu
|
|
* @argp: Pointer to struct gzvm_vcpu_run in userspace
|
|
*
|
|
* Return:
|
|
* * 0 - Success.
|
|
* * Negative - Failure.
|
|
*/
|
|
static long gzvm_vcpu_run(struct gzvm_vcpu *vcpu, void __user *argp)
|
|
{
|
|
bool need_userspace = false;
|
|
u64 exit_reason;
|
|
|
|
if (copy_from_user(vcpu->run, argp, sizeof(struct gzvm_vcpu_run)))
|
|
return -EFAULT;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(vcpu->run->padding1); i++) {
|
|
if (vcpu->run->padding1[i])
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (vcpu->run->immediate_exit == 1)
|
|
return -EINTR;
|
|
|
|
while (!need_userspace && !signal_pending(current)) {
|
|
gzvm_arch_vcpu_run(vcpu, &exit_reason);
|
|
trace_mtk_vcpu_exit(exit_reason);
|
|
|
|
switch (exit_reason) {
|
|
case GZVM_EXIT_MMIO:
|
|
if (!gzvm_vcpu_handle_mmio(vcpu))
|
|
need_userspace = true;
|
|
break;
|
|
/**
|
|
* it's geniezone's responsibility to fill corresponding data
|
|
* structure
|
|
*/
|
|
case GZVM_EXIT_HYPERCALL:
|
|
if (!gzvm_handle_guest_hvc(vcpu))
|
|
need_userspace = true;
|
|
break;
|
|
case GZVM_EXIT_EXCEPTION:
|
|
if (!gzvm_handle_guest_exception(vcpu))
|
|
need_userspace = true;
|
|
break;
|
|
case GZVM_EXIT_DEBUG:
|
|
fallthrough;
|
|
case GZVM_EXIT_FAIL_ENTRY:
|
|
fallthrough;
|
|
case GZVM_EXIT_INTERNAL_ERROR:
|
|
fallthrough;
|
|
case GZVM_EXIT_SYSTEM_EVENT:
|
|
fallthrough;
|
|
case GZVM_EXIT_SHUTDOWN:
|
|
need_userspace = true;
|
|
break;
|
|
case GZVM_EXIT_IRQ:
|
|
fallthrough;
|
|
case GZVM_EXIT_GZ:
|
|
break;
|
|
case GZVM_EXIT_IDLE:
|
|
gzvm_handle_guest_idle(vcpu);
|
|
break;
|
|
case GZVM_EXIT_IPI:
|
|
gzvm_handle_guest_ipi(vcpu);
|
|
break;
|
|
case GZVM_EXIT_UNKNOWN:
|
|
fallthrough;
|
|
default:
|
|
pr_err("vcpu unknown exit\n");
|
|
need_userspace = true;
|
|
}
|
|
trace_android_vh_gzvm_vcpu_exit_reason(vcpu, &need_userspace);
|
|
}
|
|
|
|
if (copy_to_user(argp, vcpu->run, sizeof(struct gzvm_vcpu_run)))
|
|
return -EFAULT;
|
|
if (signal_pending(current)) {
|
|
// invoke hvc to inform gz to map memory
|
|
gzvm_arch_inform_exit(vcpu->gzvm->vm_id);
|
|
return -ERESTARTSYS;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static long gzvm_vcpu_ioctl(struct file *filp, unsigned int ioctl,
|
|
unsigned long arg)
|
|
{
|
|
int ret = -ENOTTY;
|
|
void __user *argp = (void __user *)arg;
|
|
struct gzvm_vcpu *vcpu = filp->private_data;
|
|
|
|
switch (ioctl) {
|
|
case GZVM_RUN:
|
|
ret = gzvm_vcpu_run(vcpu, argp);
|
|
break;
|
|
case GZVM_GET_ONE_REG:
|
|
/* !is_write */
|
|
ret = -EOPNOTSUPP;
|
|
break;
|
|
case GZVM_SET_ONE_REG:
|
|
/* is_write */
|
|
ret = gzvm_vcpu_update_one_reg(vcpu, argp, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations gzvm_vcpu_fops = {
|
|
.unlocked_ioctl = gzvm_vcpu_ioctl,
|
|
.llseek = noop_llseek,
|
|
};
|
|
|
|
/* caller must hold the vm lock */
|
|
static void gzvm_destroy_vcpu(struct gzvm_vcpu *vcpu)
|
|
{
|
|
if (!vcpu)
|
|
return;
|
|
|
|
hrtimer_cancel(&vcpu->gzvm_vtimer);
|
|
gzvm_arch_destroy_vcpu(vcpu->gzvm->vm_id, vcpu->vcpuid);
|
|
/* clean guest's data */
|
|
memset(vcpu->run, 0, GZVM_VCPU_RUN_MAP_SIZE);
|
|
free_pages_exact(vcpu->run, GZVM_VCPU_RUN_MAP_SIZE);
|
|
kfree(vcpu);
|
|
}
|
|
|
|
/**
|
|
* gzvm_destroy_vcpus() - Destroy all vcpus, caller has to hold the vm lock
|
|
*
|
|
* @gzvm: vm struct that owns the vcpus
|
|
*/
|
|
void gzvm_destroy_vcpus(struct gzvm *gzvm)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < GZVM_MAX_VCPUS; i++) {
|
|
gzvm_destroy_vcpu(gzvm->vcpus[i]);
|
|
gzvm->vcpus[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/* create_vcpu_fd() - Allocates an inode for the vcpu. */
|
|
static int create_vcpu_fd(struct gzvm_vcpu *vcpu)
|
|
{
|
|
/* sizeof("gzvm-vcpu:") + max(strlen(itoa(vcpuid))) + null */
|
|
char name[10 + ITOA_MAX_LEN + 1];
|
|
|
|
snprintf(name, sizeof(name), "gzvm-vcpu:%d", vcpu->vcpuid);
|
|
return anon_inode_getfd(name, &gzvm_vcpu_fops, vcpu, O_RDWR | O_CLOEXEC);
|
|
}
|
|
|
|
/**
|
|
* gzvm_vm_ioctl_create_vcpu() - for GZVM_CREATE_VCPU
|
|
* @gzvm: Pointer to struct gzvm
|
|
* @cpuid: equals arg
|
|
*
|
|
* Return: Fd of vcpu, negative errno if error occurs
|
|
*/
|
|
int gzvm_vm_ioctl_create_vcpu(struct gzvm *gzvm, u32 cpuid)
|
|
{
|
|
struct gzvm_vcpu *vcpu;
|
|
int ret;
|
|
|
|
if (cpuid >= GZVM_MAX_VCPUS)
|
|
return -EINVAL;
|
|
|
|
vcpu = kzalloc(sizeof(*vcpu), GFP_KERNEL);
|
|
if (!vcpu)
|
|
return -ENOMEM;
|
|
|
|
/**
|
|
* Allocate 2 pages for data sharing between driver and gz hypervisor
|
|
*
|
|
* |- page 0 -|- page 1 -|
|
|
* |gzvm_vcpu_run|......|hwstate|.......|
|
|
*
|
|
*/
|
|
vcpu->run = alloc_pages_exact(GZVM_VCPU_RUN_MAP_SIZE,
|
|
GFP_KERNEL_ACCOUNT | __GFP_ZERO);
|
|
if (!vcpu->run) {
|
|
ret = -ENOMEM;
|
|
goto free_vcpu;
|
|
}
|
|
vcpu->hwstate = (void *)vcpu->run + PAGE_SIZE;
|
|
vcpu->vcpuid = cpuid;
|
|
vcpu->gzvm = gzvm;
|
|
mutex_init(&vcpu->lock);
|
|
|
|
ret = gzvm_arch_create_vcpu(gzvm->vm_id, vcpu->vcpuid, vcpu->run);
|
|
if (ret < 0)
|
|
goto free_vcpu_run;
|
|
|
|
ret = create_vcpu_fd(vcpu);
|
|
if (ret < 0)
|
|
goto free_vcpu_run;
|
|
gzvm->vcpus[cpuid] = vcpu;
|
|
|
|
gzvm_vtimer_init(vcpu);
|
|
return ret;
|
|
|
|
free_vcpu_run:
|
|
free_pages_exact(vcpu->run, GZVM_VCPU_RUN_MAP_SIZE);
|
|
free_vcpu:
|
|
kfree(vcpu);
|
|
return ret;
|
|
}
|