422 lines
10 KiB
C
422 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 Western Digital Corporation or its affiliates.
|
|
*
|
|
* Authors:
|
|
* Atish Patra <atish.patra@wdc.com>
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/kvm_host.h>
|
|
#include <asm/sbi.h>
|
|
#include <asm/kvm_vcpu_sbi.h>
|
|
|
|
#ifndef CONFIG_RISCV_SBI_V01
|
|
static const struct kvm_vcpu_sbi_extension vcpu_sbi_ext_v01 = {
|
|
.extid_start = -1UL,
|
|
.extid_end = -1UL,
|
|
.handler = NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifndef CONFIG_RISCV_PMU_SBI
|
|
static const struct kvm_vcpu_sbi_extension vcpu_sbi_ext_pmu = {
|
|
.extid_start = -1UL,
|
|
.extid_end = -1UL,
|
|
.handler = NULL,
|
|
};
|
|
#endif
|
|
|
|
struct kvm_riscv_sbi_extension_entry {
|
|
enum KVM_RISCV_SBI_EXT_ID ext_idx;
|
|
const struct kvm_vcpu_sbi_extension *ext_ptr;
|
|
};
|
|
|
|
static const struct kvm_riscv_sbi_extension_entry sbi_ext[] = {
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_V01,
|
|
.ext_ptr = &vcpu_sbi_ext_v01,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_MAX, /* Can't be disabled */
|
|
.ext_ptr = &vcpu_sbi_ext_base,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_TIME,
|
|
.ext_ptr = &vcpu_sbi_ext_time,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_IPI,
|
|
.ext_ptr = &vcpu_sbi_ext_ipi,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_RFENCE,
|
|
.ext_ptr = &vcpu_sbi_ext_rfence,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_SRST,
|
|
.ext_ptr = &vcpu_sbi_ext_srst,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_HSM,
|
|
.ext_ptr = &vcpu_sbi_ext_hsm,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_PMU,
|
|
.ext_ptr = &vcpu_sbi_ext_pmu,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_EXPERIMENTAL,
|
|
.ext_ptr = &vcpu_sbi_ext_experimental,
|
|
},
|
|
{
|
|
.ext_idx = KVM_RISCV_SBI_EXT_VENDOR,
|
|
.ext_ptr = &vcpu_sbi_ext_vendor,
|
|
},
|
|
};
|
|
|
|
void kvm_riscv_vcpu_sbi_forward(struct kvm_vcpu *vcpu, struct kvm_run *run)
|
|
{
|
|
struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
|
|
|
|
vcpu->arch.sbi_context.return_handled = 0;
|
|
vcpu->stat.ecall_exit_stat++;
|
|
run->exit_reason = KVM_EXIT_RISCV_SBI;
|
|
run->riscv_sbi.extension_id = cp->a7;
|
|
run->riscv_sbi.function_id = cp->a6;
|
|
run->riscv_sbi.args[0] = cp->a0;
|
|
run->riscv_sbi.args[1] = cp->a1;
|
|
run->riscv_sbi.args[2] = cp->a2;
|
|
run->riscv_sbi.args[3] = cp->a3;
|
|
run->riscv_sbi.args[4] = cp->a4;
|
|
run->riscv_sbi.args[5] = cp->a5;
|
|
run->riscv_sbi.ret[0] = cp->a0;
|
|
run->riscv_sbi.ret[1] = cp->a1;
|
|
}
|
|
|
|
void kvm_riscv_vcpu_sbi_system_reset(struct kvm_vcpu *vcpu,
|
|
struct kvm_run *run,
|
|
u32 type, u64 reason)
|
|
{
|
|
unsigned long i;
|
|
struct kvm_vcpu *tmp;
|
|
|
|
kvm_for_each_vcpu(i, tmp, vcpu->kvm)
|
|
tmp->arch.power_off = true;
|
|
kvm_make_all_cpus_request(vcpu->kvm, KVM_REQ_SLEEP);
|
|
|
|
memset(&run->system_event, 0, sizeof(run->system_event));
|
|
run->system_event.type = type;
|
|
run->system_event.ndata = 1;
|
|
run->system_event.data[0] = reason;
|
|
run->exit_reason = KVM_EXIT_SYSTEM_EVENT;
|
|
}
|
|
|
|
int kvm_riscv_vcpu_sbi_return(struct kvm_vcpu *vcpu, struct kvm_run *run)
|
|
{
|
|
struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
|
|
|
|
/* Handle SBI return only once */
|
|
if (vcpu->arch.sbi_context.return_handled)
|
|
return 0;
|
|
vcpu->arch.sbi_context.return_handled = 1;
|
|
|
|
/* Update return values */
|
|
cp->a0 = run->riscv_sbi.ret[0];
|
|
cp->a1 = run->riscv_sbi.ret[1];
|
|
|
|
/* Move to next instruction */
|
|
vcpu->arch.guest_context.sepc += 4;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int riscv_vcpu_set_sbi_ext_single(struct kvm_vcpu *vcpu,
|
|
unsigned long reg_num,
|
|
unsigned long reg_val)
|
|
{
|
|
unsigned long i;
|
|
const struct kvm_riscv_sbi_extension_entry *sext = NULL;
|
|
struct kvm_vcpu_sbi_context *scontext = &vcpu->arch.sbi_context;
|
|
|
|
if (reg_num >= KVM_RISCV_SBI_EXT_MAX)
|
|
return -ENOENT;
|
|
|
|
if (reg_val != 1 && reg_val != 0)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sbi_ext); i++) {
|
|
if (sbi_ext[i].ext_idx == reg_num) {
|
|
sext = &sbi_ext[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!sext)
|
|
return -ENOENT;
|
|
|
|
/*
|
|
* We can't set the extension status to available here, since it may
|
|
* have a probe() function which needs to confirm availability first,
|
|
* but it may be too early to call that here. We can set the status to
|
|
* unavailable, though.
|
|
*/
|
|
if (!reg_val)
|
|
scontext->ext_status[sext->ext_idx] =
|
|
KVM_RISCV_SBI_EXT_UNAVAILABLE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int riscv_vcpu_get_sbi_ext_single(struct kvm_vcpu *vcpu,
|
|
unsigned long reg_num,
|
|
unsigned long *reg_val)
|
|
{
|
|
unsigned long i;
|
|
const struct kvm_riscv_sbi_extension_entry *sext = NULL;
|
|
struct kvm_vcpu_sbi_context *scontext = &vcpu->arch.sbi_context;
|
|
|
|
if (reg_num >= KVM_RISCV_SBI_EXT_MAX)
|
|
return -ENOENT;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sbi_ext); i++) {
|
|
if (sbi_ext[i].ext_idx == reg_num) {
|
|
sext = &sbi_ext[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!sext)
|
|
return -ENOENT;
|
|
|
|
/*
|
|
* If the extension status is still uninitialized, then we should probe
|
|
* to determine if it's available, but it may be too early to do that
|
|
* here. The best we can do is report that the extension has not been
|
|
* disabled, i.e. we return 1 when the extension is available and also
|
|
* when it only may be available.
|
|
*/
|
|
*reg_val = scontext->ext_status[sext->ext_idx] !=
|
|
KVM_RISCV_SBI_EXT_UNAVAILABLE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int riscv_vcpu_set_sbi_ext_multi(struct kvm_vcpu *vcpu,
|
|
unsigned long reg_num,
|
|
unsigned long reg_val, bool enable)
|
|
{
|
|
unsigned long i, ext_id;
|
|
|
|
if (reg_num > KVM_REG_RISCV_SBI_MULTI_REG_LAST)
|
|
return -ENOENT;
|
|
|
|
for_each_set_bit(i, ®_val, BITS_PER_LONG) {
|
|
ext_id = i + reg_num * BITS_PER_LONG;
|
|
if (ext_id >= KVM_RISCV_SBI_EXT_MAX)
|
|
break;
|
|
|
|
riscv_vcpu_set_sbi_ext_single(vcpu, ext_id, enable);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int riscv_vcpu_get_sbi_ext_multi(struct kvm_vcpu *vcpu,
|
|
unsigned long reg_num,
|
|
unsigned long *reg_val)
|
|
{
|
|
unsigned long i, ext_id, ext_val;
|
|
|
|
if (reg_num > KVM_REG_RISCV_SBI_MULTI_REG_LAST)
|
|
return -ENOENT;
|
|
|
|
for (i = 0; i < BITS_PER_LONG; i++) {
|
|
ext_id = i + reg_num * BITS_PER_LONG;
|
|
if (ext_id >= KVM_RISCV_SBI_EXT_MAX)
|
|
break;
|
|
|
|
ext_val = 0;
|
|
riscv_vcpu_get_sbi_ext_single(vcpu, ext_id, &ext_val);
|
|
if (ext_val)
|
|
*reg_val |= KVM_REG_RISCV_SBI_MULTI_MASK(ext_id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kvm_riscv_vcpu_set_reg_sbi_ext(struct kvm_vcpu *vcpu,
|
|
const struct kvm_one_reg *reg)
|
|
{
|
|
unsigned long __user *uaddr =
|
|
(unsigned long __user *)(unsigned long)reg->addr;
|
|
unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK |
|
|
KVM_REG_SIZE_MASK |
|
|
KVM_REG_RISCV_SBI_EXT);
|
|
unsigned long reg_val, reg_subtype;
|
|
|
|
if (KVM_REG_SIZE(reg->id) != sizeof(unsigned long))
|
|
return -EINVAL;
|
|
|
|
if (vcpu->arch.ran_atleast_once)
|
|
return -EBUSY;
|
|
|
|
reg_subtype = reg_num & KVM_REG_RISCV_SUBTYPE_MASK;
|
|
reg_num &= ~KVM_REG_RISCV_SUBTYPE_MASK;
|
|
|
|
if (copy_from_user(®_val, uaddr, KVM_REG_SIZE(reg->id)))
|
|
return -EFAULT;
|
|
|
|
switch (reg_subtype) {
|
|
case KVM_REG_RISCV_SBI_SINGLE:
|
|
return riscv_vcpu_set_sbi_ext_single(vcpu, reg_num, reg_val);
|
|
case KVM_REG_RISCV_SBI_MULTI_EN:
|
|
return riscv_vcpu_set_sbi_ext_multi(vcpu, reg_num, reg_val, true);
|
|
case KVM_REG_RISCV_SBI_MULTI_DIS:
|
|
return riscv_vcpu_set_sbi_ext_multi(vcpu, reg_num, reg_val, false);
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kvm_riscv_vcpu_get_reg_sbi_ext(struct kvm_vcpu *vcpu,
|
|
const struct kvm_one_reg *reg)
|
|
{
|
|
int rc;
|
|
unsigned long __user *uaddr =
|
|
(unsigned long __user *)(unsigned long)reg->addr;
|
|
unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK |
|
|
KVM_REG_SIZE_MASK |
|
|
KVM_REG_RISCV_SBI_EXT);
|
|
unsigned long reg_val, reg_subtype;
|
|
|
|
if (KVM_REG_SIZE(reg->id) != sizeof(unsigned long))
|
|
return -EINVAL;
|
|
|
|
reg_subtype = reg_num & KVM_REG_RISCV_SUBTYPE_MASK;
|
|
reg_num &= ~KVM_REG_RISCV_SUBTYPE_MASK;
|
|
|
|
reg_val = 0;
|
|
switch (reg_subtype) {
|
|
case KVM_REG_RISCV_SBI_SINGLE:
|
|
rc = riscv_vcpu_get_sbi_ext_single(vcpu, reg_num, ®_val);
|
|
break;
|
|
case KVM_REG_RISCV_SBI_MULTI_EN:
|
|
case KVM_REG_RISCV_SBI_MULTI_DIS:
|
|
rc = riscv_vcpu_get_sbi_ext_multi(vcpu, reg_num, ®_val);
|
|
if (!rc && reg_subtype == KVM_REG_RISCV_SBI_MULTI_DIS)
|
|
reg_val = ~reg_val;
|
|
break;
|
|
default:
|
|
rc = -ENOENT;
|
|
}
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (copy_to_user(uaddr, ®_val, KVM_REG_SIZE(reg->id)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct kvm_vcpu_sbi_extension *kvm_vcpu_sbi_find_ext(
|
|
struct kvm_vcpu *vcpu, unsigned long extid)
|
|
{
|
|
struct kvm_vcpu_sbi_context *scontext = &vcpu->arch.sbi_context;
|
|
const struct kvm_riscv_sbi_extension_entry *entry;
|
|
const struct kvm_vcpu_sbi_extension *ext;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sbi_ext); i++) {
|
|
entry = &sbi_ext[i];
|
|
ext = entry->ext_ptr;
|
|
|
|
if (ext->extid_start <= extid && ext->extid_end >= extid) {
|
|
if (entry->ext_idx >= KVM_RISCV_SBI_EXT_MAX ||
|
|
scontext->ext_status[entry->ext_idx] ==
|
|
KVM_RISCV_SBI_EXT_AVAILABLE)
|
|
return ext;
|
|
if (scontext->ext_status[entry->ext_idx] ==
|
|
KVM_RISCV_SBI_EXT_UNAVAILABLE)
|
|
return NULL;
|
|
if (ext->probe && !ext->probe(vcpu)) {
|
|
scontext->ext_status[entry->ext_idx] =
|
|
KVM_RISCV_SBI_EXT_UNAVAILABLE;
|
|
return NULL;
|
|
}
|
|
|
|
scontext->ext_status[entry->ext_idx] =
|
|
KVM_RISCV_SBI_EXT_AVAILABLE;
|
|
return ext;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int kvm_riscv_vcpu_sbi_ecall(struct kvm_vcpu *vcpu, struct kvm_run *run)
|
|
{
|
|
int ret = 1;
|
|
bool next_sepc = true;
|
|
struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
|
|
const struct kvm_vcpu_sbi_extension *sbi_ext;
|
|
struct kvm_cpu_trap utrap = {0};
|
|
struct kvm_vcpu_sbi_return sbi_ret = {
|
|
.out_val = 0,
|
|
.err_val = 0,
|
|
.utrap = &utrap,
|
|
};
|
|
bool ext_is_v01 = false;
|
|
|
|
sbi_ext = kvm_vcpu_sbi_find_ext(vcpu, cp->a7);
|
|
if (sbi_ext && sbi_ext->handler) {
|
|
#ifdef CONFIG_RISCV_SBI_V01
|
|
if (cp->a7 >= SBI_EXT_0_1_SET_TIMER &&
|
|
cp->a7 <= SBI_EXT_0_1_SHUTDOWN)
|
|
ext_is_v01 = true;
|
|
#endif
|
|
ret = sbi_ext->handler(vcpu, run, &sbi_ret);
|
|
} else {
|
|
/* Return error for unsupported SBI calls */
|
|
cp->a0 = SBI_ERR_NOT_SUPPORTED;
|
|
goto ecall_done;
|
|
}
|
|
|
|
/*
|
|
* When the SBI extension returns a Linux error code, it exits the ioctl
|
|
* loop and forwards the error to userspace.
|
|
*/
|
|
if (ret < 0) {
|
|
next_sepc = false;
|
|
goto ecall_done;
|
|
}
|
|
|
|
/* Handle special error cases i.e trap, exit or userspace forward */
|
|
if (sbi_ret.utrap->scause) {
|
|
/* No need to increment sepc or exit ioctl loop */
|
|
ret = 1;
|
|
sbi_ret.utrap->sepc = cp->sepc;
|
|
kvm_riscv_vcpu_trap_redirect(vcpu, sbi_ret.utrap);
|
|
next_sepc = false;
|
|
goto ecall_done;
|
|
}
|
|
|
|
/* Exit ioctl loop or Propagate the error code the guest */
|
|
if (sbi_ret.uexit) {
|
|
next_sepc = false;
|
|
ret = 0;
|
|
} else {
|
|
cp->a0 = sbi_ret.err_val;
|
|
ret = 1;
|
|
}
|
|
ecall_done:
|
|
if (next_sepc)
|
|
cp->sepc += 4;
|
|
/* a1 should only be updated when we continue the ioctl loop */
|
|
if (!ext_is_v01 && ret == 1)
|
|
cp->a1 = sbi_ret.out_val;
|
|
|
|
return ret;
|
|
}
|