437 lines
9.4 KiB
C
437 lines
9.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2025 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com/
|
|
*/
|
|
|
|
#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/preempt.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/rcuwait.h>
|
|
|
|
#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;
|
|
}
|