// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2025 Samsung Electronics Co., Ltd. * http://www.samsung.com/ */ #include #include #include #include #include #include #include #include #include #include "exynos-hvc.h" #include "hvm_drv.h" /* maximum size needed for holding an integer */ #define ITOA_MAX_LEN 12 static long hvm_vcpu_update_one_reg(struct hvm_vcpu *vcpu, void __user *argp, bool is_write) { struct hvm_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 & HVM_REG_SIZE_MASK) >> HVM_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) { /* Halla hypervisor would filter out invalid vcpu register access */ if (copy_from_user(&data, reg_addr, reg_size)) return -EFAULT; ret = exynos_hvc(HVC_FID_HVM_SET_ONE_REG, vcpu->hvm->vm_id, vcpu->vcpuid, reg.id, data); } else { /* Read is not supported yet */ return -EOPNOTSUPP; } if (ret) return ret; return 0; } /** * hvm_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 hvm_vcpu_handle_mmio(struct hvm_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 hvm_ioevent_write(vcpu, addr, len, val_ptr); } /** * hvm_handle_guest_exception() - Handle guest exception * @vcpu: Pointer to struct hvm_vcpu_run in userspace * Return: * * true - This exception has been processed, no need to back to VMM. * * false - This exception has not been processed, require userspace. */ static bool hvm_handle_guest_exception(struct hvm_vcpu *vcpu) { int ret; for (int i = 0; i < ARRAY_SIZE(vcpu->run->exception.reserved); i++) { if (vcpu->run->exception.reserved[i]) return false; } switch (vcpu->run->exception.exception) { case HVM_EXCEPTION_PAGE_FAULT: ret = hvm_handle_page_fault(vcpu); break; case HVM_EXCEPTION_UNKNOWN: fallthrough; default: ret = -EFAULT; } if (!ret) return true; else return false; } static void hvm_vcpu_load(struct hvm_vcpu *vcpu) { u32 tuple = ((u32)vcpu->hvm->vm_id << 16 | (u16)vcpu->vcpuid); __this_cpu_write(*hvm_get_running_vcpus(), tuple); hvm_restore_vtimer(vcpu); } static void hvm_vcpu_put(struct hvm_vcpu *vcpu) { hvm_save_vtimer(vcpu); __this_cpu_write(*hvm_get_running_vcpus(), (u32)(~0)); } void hvm_vcpu_wakeup(struct hvm_vcpu *vcpu) { atomic_inc(&vcpu->idle_state); rcuwait_wake_up(&vcpu->wait); } static enum hrtimer_restart hvm_idle_hrtimer_expire(struct hrtimer *hrt) { struct hvm_vcpu *vcpu; vcpu = container_of(hrt, struct hvm_vcpu, idle_hrtimer); hvm_vcpu_wakeup(vcpu); return HRTIMER_NORESTART; } static void hvm_idle_hrtimer_init(struct hvm_vcpu *vcpu) { hrtimer_init(&vcpu->idle_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD); vcpu->idle_hrtimer.function = hvm_idle_hrtimer_expire; atomic_set(&vcpu->idle_state, 0); } static void hvm_idle_hrtimer_set(struct hvm_vcpu *vcpu, u64 ns) { hrtimer_start(&vcpu->idle_hrtimer, ktime_add_ns(ktime_get(), ns), HRTIMER_MODE_ABS_HARD); } static void hvm_idle_hrtimer_release(struct hvm_vcpu *vcpu) { hrtimer_cancel(&vcpu->idle_hrtimer); } static void vcpu_block_wait(struct hvm_vcpu *vcpu) { struct rcuwait *wait = &vcpu->wait; rcuwait_wait_event(wait, atomic_read(&vcpu->idle_state), TASK_INTERRUPTIBLE); atomic_set(&vcpu->idle_state, 0); } static int hvm_handle_guest_idle(struct hvm_vcpu *vcpu) { int ret = 0; u64 ns; u64 next_cval; u64 cur_val; ns = exynos_hvc(HVC_FID_HYPERVISOR_GET_VM_IDLE_TIME, ((vcpu->hvm->vm_id << 16) | vcpu->vcpuid), 0, 0, 0); next_cval = vcpu->vtimer.cntp_cval_el0; cur_val = read_sysreg(cntpct_el0); if (next_cval > cur_val) { ns = clocksource_cyc2ns(next_cval - cur_val, vcpu->hvm->clock_scale_factor.mult, vcpu->hvm->clock_scale_factor.shift); } hvm_idle_hrtimer_set(vcpu, ns); vcpu_block_wait(vcpu); hvm_idle_hrtimer_release(vcpu); return ret; } static void hvm_handle_guest_gsi(struct hvm_vcpu *vcpu) { struct hvm *hvm = vcpu->hvm; for (int i = 0; i < hvm->nr_vcpus; i++) { if (hvm->vcpus[i]) hvm_vcpu_wakeup(hvm->vcpus[i]); } } static bool handle_guest_exit(struct hvm_vcpu *vcpu, u64 exit_reason) { switch (exit_reason) { case HVM_EXIT_MMIO: if (!hvm_vcpu_handle_mmio(vcpu)) return true; break; /** * it's Halla's responsibility to fill corresponding data * structure */ case HVM_EXIT_EXCEPTION: if (!hvm_handle_guest_exception(vcpu)) return true; break; case HVM_EXIT_HALLA: fallthrough; case HVM_EXIT_IRQ: break; case HVM_EXIT_IDLE: hvm_handle_guest_idle(vcpu); break; case HVM_EXIT_SGI: hvm_handle_guest_gsi(vcpu); break; default: return true; } return false; } /** * hvm_vcpu_run() - Handle vcpu run ioctl, entry point to guest and exit * point from guest * @vcpu: Pointer to struct hvm_vcpu * @argp: Pointer to struct hvm_vcpu_run in userspace * * Return: * * 0 - Success. * * Negative - Failure. */ static long hvm_vcpu_run(struct hvm_vcpu *vcpu, void __user *argp) { bool need_userspace = false; u64 exit_reason; if (copy_from_user(vcpu->run, argp, sizeof(struct hvm_vcpu_run))) return -EFAULT; for (int i = 0; i < ARRAY_SIZE(vcpu->run->pad); i++) { if (vcpu->run->pad[i]) return -EINVAL; } if (vcpu->run->immediate_exit == 1) return -EINTR; while (!need_userspace && !signal_pending(current)) { preempt_disable(); hvm_vcpu_load(vcpu); exit_reason = exynos_hvc(HVC_FID_HVM_VCPU_RUN, vcpu->hvm->vm_id, vcpu->vcpuid, 0, 0); /** * Need to give guarantee for processing VM timer * interrupt handler. */ isb(); hvm_vcpu_put(vcpu); preempt_enable(); need_userspace = handle_guest_exit(vcpu, exit_reason); } if (copy_to_user(argp, vcpu->run, sizeof(struct hvm_vcpu_run))) return -EFAULT; if (signal_pending(current)) { exynos_hvc(HVC_FID_HVM_NOTIFY_EXIT, vcpu->hvm->vm_id, 0, 0, 0); return -ERESTARTSYS; } return 0; } static long hvm_vcpu_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) { int ret = -EOPNOTSUPP; void __user *argp = (void __user *)arg; struct hvm_vcpu *vcpu = filp->private_data; switch (ioctl) { case HVM_VCPU_RUN: ret = hvm_vcpu_run(vcpu, argp); break; case HVM_SET_ONE_REG: /* is_write */ ret = hvm_vcpu_update_one_reg(vcpu, argp, true); break; default: break; } return ret; } static const struct file_operations hvm_vcpu_fops = { .unlocked_ioctl = hvm_vcpu_ioctl, }; /* caller must hold the vm lock */ static void hvm_destroy_vcpu(struct hvm_vcpu *vcpu) { if (!vcpu) return; /* Destroy vcpu in Halla */ exynos_hvc(HVC_FID_HVM_DESTROY_VCPU, ((vcpu->hvm->vm_id << 16) | vcpu->vcpuid), 0, 0, 0); /* * Clear the shared memory region. This is done to prevent any sensitive * data from the guest or hypervisor from lingering in the memory pages * after the vCPU is destroyed, improving security and preventing * potential information leaks. */ memset(vcpu->run, 0, HVM_VCPU_RUN_MAP_SIZE); free_pages_exact(vcpu->run, HVM_VCPU_RUN_MAP_SIZE); kfree(vcpu); } /** * hvm_destroy_vcpus() - Destroy all vcpus, caller has to hold the vm lock * * @hvm: vm struct that owns the vcpus */ void hvm_destroy_vcpus(struct hvm *hvm) { int i; for (i = 0; i < hvm->nr_vcpus; i++) { hvm_destroy_vcpu(hvm->vcpus[i]); hvm->vcpus[i] = NULL; } hvm->nr_vcpus = 0; } /* create_vcpu_fd() - Allocates an inode for the vcpu. */ static int create_vcpu_fd(struct hvm_vcpu *vcpu) { /* sizeof("hvm-vcpu:") + max(strlen(itoa(vcpuid))) + null */ char name[10 + ITOA_MAX_LEN + 1]; snprintf(name, sizeof(name), "hvm-vcpu:%d", vcpu->vcpuid); return anon_inode_getfd(name, &hvm_vcpu_fops, vcpu, O_RDWR | O_CLOEXEC); } /** * hvm_vm_ioctl_create_vcpu() - for HVM_CREATE_VCPU * @hvm: Pointer to struct hvm * @cpuid: equals arg * * Return: Fd of vcpu, negative errno if error occurs */ int hvm_vm_ioctl_create_vcpu(struct hvm *hvm, u32 cpuid) { struct hvm_vcpu *vcpu; int ret; if (cpuid > HVM_MAX_VCPUS) return -EINVAL; vcpu = kzalloc(sizeof(*vcpu), GFP_KERNEL); if (!vcpu) return -ENOMEM; /** * Allocate 2 pages for data sharing between driver and halla hypervisor * * |- page 0 -|- page 1 -| * |hvm_vcpu_run|......|not_used_yet|.......| * */ vcpu->run = alloc_pages_exact(HVM_VCPU_RUN_MAP_SIZE, GFP_KERNEL_ACCOUNT | __GFP_ZERO); if (!vcpu->run) { ret = -ENOMEM; goto free_vcpu; } vcpu->vcpuid = cpuid; vcpu->hvm = hvm; mutex_init(&vcpu->lock); ret = (int)exynos_hvc(HVC_FID_HVM_CREATE_VCPU, hvm->vm_id, vcpu->vcpuid, (u64)virt_to_phys(vcpu->run), 0); if (ret < 0) goto free_vcpu_run; ret = create_vcpu_fd(vcpu); if (ret < 0) goto free_vcpu_run; hvm->vcpus[cpuid] = vcpu; hvm->nr_vcpus += 1; hvm_vtimer_init_regs(vcpu); hvm_idle_hrtimer_init(vcpu); return ret; free_vcpu_run: free_pages_exact(vcpu->run, HVM_VCPU_RUN_MAP_SIZE); free_vcpu: kfree(vcpu); return ret; }