123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- // 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(®, 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 (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 = 0;
- 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);
- 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_UNKNOWN:
- fallthrough;
- default:
- pr_err("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)) {
- // 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;
- 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;
- return ret;
- free_vcpu_run:
- free_pages_exact(vcpu->run, GZVM_VCPU_RUN_MAP_SIZE);
- free_vcpu:
- kfree(vcpu);
- return ret;
- }
|