Skip to content

Commit

Permalink
virt: geniezone: Add vcpu support
Browse files Browse the repository at this point in the history
VMM use this interface to create vcpu instance which is a fd, and this
fd will be for any vcpu operations, such as setting vcpu registers and
accepts the most important ioctl GZVM_VCPU_RUN which requests GenieZone
hypervisor to do context switch to execute VM's vcpu context.

Signed-off-by: Yingshiuan Pan <yingshiuan.pan@mediatek.com>
Signed-off-by: Jerry Wang <ze-yu.wang@mediatek.com>
Signed-off-by: Yi-De Wu <yi-de.wu@mediatek.com>
  • Loading branch information
yingshiuanpan authored and intel-lab-lkp committed Apr 28, 2023
1 parent 0e3f05a commit d4ced06
Show file tree
Hide file tree
Showing 8 changed files with 519 additions and 47 deletions.
72 changes: 72 additions & 0 deletions arch/arm64/geniezone/gzvm_arch.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,75 @@ int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm, struct gzvm_enable_cap *cap

return ret;
}

int gzvm_arch_vcpu_update_one_reg(struct gzvm_vcpu *vcpu, __u64 reg_id,
bool is_write, __u64 *data)
{
struct arm_smccc_res res;
unsigned long a1;
int ret;

/* reg id follows KVM's encoding */
switch (reg_id & GZVM_REG_ARM_COPROC_MASK) {
case GZVM_REG_ARM_CORE:
break;
default:
return -EOPNOTSUPP;
}

a1 = assemble_vm_vcpu_tuple(vcpu->gzvm->vm_id, vcpu->vcpuid);
if (!is_write) {
ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_GET_ONE_REG,
a1, reg_id, 0, 0, 0, 0, 0, &res);
if (ret == 0)
*data = res.a1;
} else {
ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_ONE_REG,
a1, reg_id, *data, 0, 0, 0, 0, &res);
}

return ret;
}

int gzvm_arch_vcpu_run(struct gzvm_vcpu *vcpu, __u64 *exit_reason)
{
struct arm_smccc_res res;
unsigned long a1;
int ret;

a1 = assemble_vm_vcpu_tuple(vcpu->gzvm->vm_id, vcpu->vcpuid);
ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_RUN, a1, 0, 0, 0, 0, 0,
0, &res);
*exit_reason = res.a1;
return ret;
}

int gzvm_arch_destroy_vcpu(gzvm_id_t vm_id, int vcpuid)
{
struct arm_smccc_res res;
unsigned long a1;

a1 = assemble_vm_vcpu_tuple(vm_id, vcpuid);
gzvm_hypcall_wrapper(MT_HVC_GZVM_DESTROY_VCPU, a1, 0, 0, 0, 0, 0, 0,
&res);

return 0;
}

/**
* gzvm_arch_create_vcpu() - Call smc to gz hypervisor to create vcpu
* @run: Virtual address of vcpu->run
*/
int gzvm_arch_create_vcpu(gzvm_id_t vm_id, int vcpuid, void *run)
{
struct arm_smccc_res res;
unsigned long a1, a2;
int ret;

a1 = assemble_vm_vcpu_tuple(vm_id, vcpuid);
a2 = (__u64)virt_to_phys(run);
ret = gzvm_hypcall_wrapper(MT_HVC_GZVM_CREATE_VCPU, a1, a2, 0, 0, 0, 0,
0, &res);

return ret;
}
24 changes: 24 additions & 0 deletions arch/arm64/geniezone/gzvm_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,28 @@ enum {
#define MT_HVC_GZVM_PROBE GZVM_HCALL_ID(GZVM_FUNC_PROBE)
#define MT_HVC_GZVM_ENABLE_CAP GZVM_HCALL_ID(GZVM_FUNC_ENABLE_CAP)

static inline gzvm_id_t get_vmid_from_tuple(unsigned int tuple)
{
return (gzvm_id_t)(tuple >> 16);
}

static inline gzvm_vcpu_id_t get_vcpuid_from_tuple(unsigned int tuple)
{
return (gzvm_vcpu_id_t)(tuple & 0xffff);
}

static inline unsigned int
assemble_vm_vcpu_tuple(gzvm_id_t vmid, gzvm_vcpu_id_t vcpuid)
{
return ((unsigned int)vmid << 16 | vcpuid);
}

static inline void
disassemble_vm_vcpu_tuple(unsigned int tuple, gzvm_id_t *vmid,
gzvm_vcpu_id_t *vcpuid)
{
*vmid = get_vmid_from_tuple(tuple);
*vcpuid = get_vcpuid_from_tuple(tuple);
}

#endif /* __GZVM_ARCH_H__ */
29 changes: 29 additions & 0 deletions arch/arm64/include/uapi/asm/gzvm_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,33 @@
#define GZVM_CAP_ARM_PVM_SET_PVMFW_IPA 0
#define GZVM_CAP_ARM_PVM_GET_PVMFW_SIZE 1

/*
* Architecture specific registers are to be defined in arch headers and
* ORed with the arch identifier.
*/
#define GZVM_REG_ARM 0x4000000000000000ULL
#define GZVM_REG_ARM64 0x6000000000000000ULL

#define GZVM_REG_SIZE_SHIFT 52
#define GZVM_REG_SIZE_MASK 0x00f0000000000000ULL
#define GZVM_REG_SIZE_U8 0x0000000000000000ULL
#define GZVM_REG_SIZE_U16 0x0010000000000000ULL
#define GZVM_REG_SIZE_U32 0x0020000000000000ULL
#define GZVM_REG_SIZE_U64 0x0030000000000000ULL
#define GZVM_REG_SIZE_U128 0x0040000000000000ULL
#define GZVM_REG_SIZE_U256 0x0050000000000000ULL
#define GZVM_REG_SIZE_U512 0x0060000000000000ULL
#define GZVM_REG_SIZE_U1024 0x0070000000000000ULL
#define GZVM_REG_SIZE_U2048 0x0080000000000000ULL

#define GZVM_REG_ARCH_MASK 0xff00000000000000ULL

/* If you need to interpret the index values, here is the key: */
#define GZVM_REG_ARM_COPROC_MASK 0x000000000FFF0000
#define GZVM_REG_ARM_COPROC_SHIFT 16

/* Normal registers are mapped as coprocessor 16. */
#define GZVM_REG_ARM_CORE (0x0010 << GZVM_REG_ARM_COPROC_SHIFT)
#define GZVM_REG_ARM_CORE_REG(name) (offsetof(struct gzvm_regs, name) / sizeof(__u32))

#endif /* __GZVM_ARCH_H__ */
3 changes: 2 additions & 1 deletion drivers/virt/geniezone/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@

GZVM_DIR ?= ../../../drivers/virt/geniezone

gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o
gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o \
$(GZVM_DIR)/gzvm_vcpu.o

234 changes: 234 additions & 0 deletions drivers/virt/geniezone/gzvm_vcpu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 MediaTek Inc.
*/

#include <asm/sysreg.h>
#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 <linux/gzvm_drv.h>

/* maximum size needed for holding an integer */
#define ITOA_MAX_LEN 12

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(&reg, argp, sizeof(reg)))
return -EFAULT;

reg_addr = (void __user *)reg.addr;
reg_size = (reg.id & GZVM_REG_SIZE_MASK) >> GZVM_REG_SIZE_SHIFT;
reg_size = BIT(reg_size);

if (is_write) {
if (copy_from_user(&data, reg_addr, reg_size))
return -EFAULT;
}

ret = gzvm_arch_vcpu_update_one_reg(vcpu, reg.id, is_write, &data);

if (ret)
return ret;

if (!is_write) {
if (copy_to_user(reg_addr, &data, reg_size))
return -EFAULT;
}

return 0;
}

/**
* gzvm_vcpu_run() - Handle vcpu run ioctl, entry point to guest and exit
* point from guest
* @argp: pointer to struct gzvm_vcpu_run in userspace
*/
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;

if (vcpu->run->immediate_exit == 1)
return -EINTR;

while (!need_userspace && !signal_pending(current)) {
gzvm_arch_vcpu_run(vcpu, &exit_reason);

switch (exit_reason) {
case GZVM_EXIT_MMIO:
need_userspace = true;
break;
/**
* it's geniezone's responsibility to fill corresponding data
* structure
*/
case GZVM_EXIT_HYPERCALL:
fallthrough;
case GZVM_EXIT_EXCEPTION:
fallthrough;
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:
break;
case GZVM_EXIT_UNKNOWN:
fallthrough;
default:
dev_err(&gzvm_debug_dev->dev, "vcpu unknown exit\n");
need_userspace = true;
goto out;
}
}

out:
if (copy_to_user(argp, vcpu->run, sizeof(struct gzvm_vcpu_run)))
return -EFAULT;
if (signal_pending(current))
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 = gzvm_vcpu_update_one_reg(vcpu, argp, false);
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 */
void gzvm_destroy_vcpu(struct gzvm_vcpu *vcpu)
{
if (!vcpu)
return;

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()
*
* @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->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;

return ret;

free_vcpu_run:
free_pages_exact(vcpu->run, GZVM_VCPU_RUN_MAP_SIZE);
free_vcpu:
kfree(vcpu);
return ret;
}
Loading

0 comments on commit d4ced06

Please sign in to comment.