// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2023 MediaTek Inc. */ #include #include #include #include #include #include #include #include /* 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; }