mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 04:38:03 +00:00
cdc118f802
Export kernel paravirt features to user space, so that VMM can control each single paravirt feature. By default paravirt features will be the same with kvm supported features if VMM does not set it. Also a new feature KVM_FEATURE_VIRT_EXTIOI is added which can be set from user space. This feature indicates that the virt EIOINTC can route interrupts to 256 vCPUs, rather than 4 vCPUs like with real HW. Signed-off-by: Bibo Mao <maobibo@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
890 lines
20 KiB
C
890 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kvm_host.h>
|
|
#include <linux/module.h>
|
|
#include <linux/preempt.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <trace/events/kvm.h>
|
|
#include <asm/fpu.h>
|
|
#include <asm/inst.h>
|
|
#include <asm/loongarch.h>
|
|
#include <asm/mmzone.h>
|
|
#include <asm/numa.h>
|
|
#include <asm/time.h>
|
|
#include <asm/tlb.h>
|
|
#include <asm/kvm_csr.h>
|
|
#include <asm/kvm_vcpu.h>
|
|
#include "trace.h"
|
|
|
|
static int kvm_emu_cpucfg(struct kvm_vcpu *vcpu, larch_inst inst)
|
|
{
|
|
int rd, rj;
|
|
unsigned int index, ret;
|
|
|
|
if (inst.reg2_format.opcode != cpucfg_op)
|
|
return EMULATE_FAIL;
|
|
|
|
rd = inst.reg2_format.rd;
|
|
rj = inst.reg2_format.rj;
|
|
++vcpu->stat.cpucfg_exits;
|
|
index = vcpu->arch.gprs[rj];
|
|
|
|
/*
|
|
* By LoongArch Reference Manual 2.2.10.5
|
|
* Return value is 0 for undefined CPUCFG index
|
|
*
|
|
* Disable preemption since hw gcsr is accessed
|
|
*/
|
|
preempt_disable();
|
|
switch (index) {
|
|
case 0 ... (KVM_MAX_CPUCFG_REGS - 1):
|
|
vcpu->arch.gprs[rd] = vcpu->arch.cpucfg[index];
|
|
break;
|
|
case CPUCFG_KVM_SIG:
|
|
/* CPUCFG emulation between 0x40000000 -- 0x400000ff */
|
|
vcpu->arch.gprs[rd] = *(unsigned int *)KVM_SIGNATURE;
|
|
break;
|
|
case CPUCFG_KVM_FEATURE:
|
|
ret = vcpu->kvm->arch.pv_features & LOONGARCH_PV_FEAT_MASK;
|
|
vcpu->arch.gprs[rd] = ret;
|
|
break;
|
|
default:
|
|
vcpu->arch.gprs[rd] = 0;
|
|
break;
|
|
}
|
|
preempt_enable();
|
|
|
|
return EMULATE_DONE;
|
|
}
|
|
|
|
static unsigned long kvm_emu_read_csr(struct kvm_vcpu *vcpu, int csrid)
|
|
{
|
|
unsigned long val = 0;
|
|
struct loongarch_csrs *csr = vcpu->arch.csr;
|
|
|
|
/*
|
|
* From LoongArch Reference Manual Volume 1 Chapter 4.2.1
|
|
* For undefined CSR id, return value is 0
|
|
*/
|
|
if (get_gcsr_flag(csrid) & SW_GCSR)
|
|
val = kvm_read_sw_gcsr(csr, csrid);
|
|
else
|
|
pr_warn_once("Unsupported csrrd 0x%x with pc %lx\n", csrid, vcpu->arch.pc);
|
|
|
|
return val;
|
|
}
|
|
|
|
static unsigned long kvm_emu_write_csr(struct kvm_vcpu *vcpu, int csrid, unsigned long val)
|
|
{
|
|
unsigned long old = 0;
|
|
struct loongarch_csrs *csr = vcpu->arch.csr;
|
|
|
|
if (get_gcsr_flag(csrid) & SW_GCSR) {
|
|
old = kvm_read_sw_gcsr(csr, csrid);
|
|
kvm_write_sw_gcsr(csr, csrid, val);
|
|
} else
|
|
pr_warn_once("Unsupported csrwr 0x%x with pc %lx\n", csrid, vcpu->arch.pc);
|
|
|
|
return old;
|
|
}
|
|
|
|
static unsigned long kvm_emu_xchg_csr(struct kvm_vcpu *vcpu, int csrid,
|
|
unsigned long csr_mask, unsigned long val)
|
|
{
|
|
unsigned long old = 0;
|
|
struct loongarch_csrs *csr = vcpu->arch.csr;
|
|
|
|
if (get_gcsr_flag(csrid) & SW_GCSR) {
|
|
old = kvm_read_sw_gcsr(csr, csrid);
|
|
val = (old & ~csr_mask) | (val & csr_mask);
|
|
kvm_write_sw_gcsr(csr, csrid, val);
|
|
old = old & csr_mask;
|
|
} else
|
|
pr_warn_once("Unsupported csrxchg 0x%x with pc %lx\n", csrid, vcpu->arch.pc);
|
|
|
|
return old;
|
|
}
|
|
|
|
static int kvm_handle_csr(struct kvm_vcpu *vcpu, larch_inst inst)
|
|
{
|
|
unsigned int rd, rj, csrid;
|
|
unsigned long csr_mask, val = 0;
|
|
|
|
/*
|
|
* CSR value mask imm
|
|
* rj = 0 means csrrd
|
|
* rj = 1 means csrwr
|
|
* rj != 0,1 means csrxchg
|
|
*/
|
|
rd = inst.reg2csr_format.rd;
|
|
rj = inst.reg2csr_format.rj;
|
|
csrid = inst.reg2csr_format.csr;
|
|
|
|
if (csrid >= LOONGARCH_CSR_PERFCTRL0 && csrid <= vcpu->arch.max_pmu_csrid) {
|
|
if (kvm_guest_has_pmu(&vcpu->arch)) {
|
|
vcpu->arch.pc -= 4;
|
|
kvm_make_request(KVM_REQ_PMU, vcpu);
|
|
return EMULATE_DONE;
|
|
}
|
|
}
|
|
|
|
/* Process CSR ops */
|
|
switch (rj) {
|
|
case 0: /* process csrrd */
|
|
val = kvm_emu_read_csr(vcpu, csrid);
|
|
vcpu->arch.gprs[rd] = val;
|
|
break;
|
|
case 1: /* process csrwr */
|
|
val = vcpu->arch.gprs[rd];
|
|
val = kvm_emu_write_csr(vcpu, csrid, val);
|
|
vcpu->arch.gprs[rd] = val;
|
|
break;
|
|
default: /* process csrxchg */
|
|
val = vcpu->arch.gprs[rd];
|
|
csr_mask = vcpu->arch.gprs[rj];
|
|
val = kvm_emu_xchg_csr(vcpu, csrid, csr_mask, val);
|
|
vcpu->arch.gprs[rd] = val;
|
|
}
|
|
|
|
return EMULATE_DONE;
|
|
}
|
|
|
|
int kvm_emu_iocsr(larch_inst inst, struct kvm_run *run, struct kvm_vcpu *vcpu)
|
|
{
|
|
int ret;
|
|
unsigned long val;
|
|
u32 addr, rd, rj, opcode;
|
|
|
|
/*
|
|
* Each IOCSR with different opcode
|
|
*/
|
|
rd = inst.reg2_format.rd;
|
|
rj = inst.reg2_format.rj;
|
|
opcode = inst.reg2_format.opcode;
|
|
addr = vcpu->arch.gprs[rj];
|
|
ret = EMULATE_DO_IOCSR;
|
|
run->iocsr_io.phys_addr = addr;
|
|
run->iocsr_io.is_write = 0;
|
|
|
|
/* LoongArch is Little endian */
|
|
switch (opcode) {
|
|
case iocsrrdb_op:
|
|
run->iocsr_io.len = 1;
|
|
break;
|
|
case iocsrrdh_op:
|
|
run->iocsr_io.len = 2;
|
|
break;
|
|
case iocsrrdw_op:
|
|
run->iocsr_io.len = 4;
|
|
break;
|
|
case iocsrrdd_op:
|
|
run->iocsr_io.len = 8;
|
|
break;
|
|
case iocsrwrb_op:
|
|
run->iocsr_io.len = 1;
|
|
run->iocsr_io.is_write = 1;
|
|
break;
|
|
case iocsrwrh_op:
|
|
run->iocsr_io.len = 2;
|
|
run->iocsr_io.is_write = 1;
|
|
break;
|
|
case iocsrwrw_op:
|
|
run->iocsr_io.len = 4;
|
|
run->iocsr_io.is_write = 1;
|
|
break;
|
|
case iocsrwrd_op:
|
|
run->iocsr_io.len = 8;
|
|
run->iocsr_io.is_write = 1;
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (ret == EMULATE_DO_IOCSR) {
|
|
if (run->iocsr_io.is_write) {
|
|
val = vcpu->arch.gprs[rd];
|
|
memcpy(run->iocsr_io.data, &val, run->iocsr_io.len);
|
|
}
|
|
vcpu->arch.io_gpr = rd;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int kvm_complete_iocsr_read(struct kvm_vcpu *vcpu, struct kvm_run *run)
|
|
{
|
|
enum emulation_result er = EMULATE_DONE;
|
|
unsigned long *gpr = &vcpu->arch.gprs[vcpu->arch.io_gpr];
|
|
|
|
switch (run->iocsr_io.len) {
|
|
case 1:
|
|
*gpr = *(s8 *)run->iocsr_io.data;
|
|
break;
|
|
case 2:
|
|
*gpr = *(s16 *)run->iocsr_io.data;
|
|
break;
|
|
case 4:
|
|
*gpr = *(s32 *)run->iocsr_io.data;
|
|
break;
|
|
case 8:
|
|
*gpr = *(s64 *)run->iocsr_io.data;
|
|
break;
|
|
default:
|
|
kvm_err("Bad IOCSR length: %d, addr is 0x%lx\n",
|
|
run->iocsr_io.len, vcpu->arch.badv);
|
|
er = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
|
|
return er;
|
|
}
|
|
|
|
int kvm_emu_idle(struct kvm_vcpu *vcpu)
|
|
{
|
|
++vcpu->stat.idle_exits;
|
|
trace_kvm_exit_idle(vcpu, KVM_TRACE_EXIT_IDLE);
|
|
|
|
if (!kvm_arch_vcpu_runnable(vcpu))
|
|
kvm_vcpu_halt(vcpu);
|
|
|
|
return EMULATE_DONE;
|
|
}
|
|
|
|
static int kvm_trap_handle_gspr(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned long curr_pc;
|
|
larch_inst inst;
|
|
enum emulation_result er = EMULATE_DONE;
|
|
struct kvm_run *run = vcpu->run;
|
|
|
|
/* Fetch the instruction */
|
|
inst.word = vcpu->arch.badi;
|
|
curr_pc = vcpu->arch.pc;
|
|
update_pc(&vcpu->arch);
|
|
|
|
trace_kvm_exit_gspr(vcpu, inst.word);
|
|
er = EMULATE_FAIL;
|
|
switch (((inst.word >> 24) & 0xff)) {
|
|
case 0x0: /* CPUCFG GSPR */
|
|
er = kvm_emu_cpucfg(vcpu, inst);
|
|
break;
|
|
case 0x4: /* CSR{RD,WR,XCHG} GSPR */
|
|
er = kvm_handle_csr(vcpu, inst);
|
|
break;
|
|
case 0x6: /* Cache, Idle and IOCSR GSPR */
|
|
switch (((inst.word >> 22) & 0x3ff)) {
|
|
case 0x18: /* Cache GSPR */
|
|
er = EMULATE_DONE;
|
|
trace_kvm_exit_cache(vcpu, KVM_TRACE_EXIT_CACHE);
|
|
break;
|
|
case 0x19: /* Idle/IOCSR GSPR */
|
|
switch (((inst.word >> 15) & 0x1ffff)) {
|
|
case 0xc90: /* IOCSR GSPR */
|
|
er = kvm_emu_iocsr(inst, run, vcpu);
|
|
break;
|
|
case 0xc91: /* Idle GSPR */
|
|
er = kvm_emu_idle(vcpu);
|
|
break;
|
|
default:
|
|
er = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
er = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
er = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
|
|
/* Rollback PC only if emulation was unsuccessful */
|
|
if (er == EMULATE_FAIL) {
|
|
kvm_err("[%#lx]%s: unsupported gspr instruction 0x%08x\n",
|
|
curr_pc, __func__, inst.word);
|
|
|
|
kvm_arch_vcpu_dump_regs(vcpu);
|
|
vcpu->arch.pc = curr_pc;
|
|
}
|
|
|
|
return er;
|
|
}
|
|
|
|
/*
|
|
* Trigger GSPR:
|
|
* 1) Execute CPUCFG instruction;
|
|
* 2) Execute CACOP/IDLE instructions;
|
|
* 3) Access to unimplemented CSRs/IOCSRs.
|
|
*/
|
|
static int kvm_handle_gspr(struct kvm_vcpu *vcpu)
|
|
{
|
|
int ret = RESUME_GUEST;
|
|
enum emulation_result er = EMULATE_DONE;
|
|
|
|
er = kvm_trap_handle_gspr(vcpu);
|
|
|
|
if (er == EMULATE_DONE) {
|
|
ret = RESUME_GUEST;
|
|
} else if (er == EMULATE_DO_MMIO) {
|
|
vcpu->run->exit_reason = KVM_EXIT_MMIO;
|
|
ret = RESUME_HOST;
|
|
} else if (er == EMULATE_DO_IOCSR) {
|
|
vcpu->run->exit_reason = KVM_EXIT_LOONGARCH_IOCSR;
|
|
ret = RESUME_HOST;
|
|
} else {
|
|
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
|
|
ret = RESUME_GUEST;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int kvm_emu_mmio_read(struct kvm_vcpu *vcpu, larch_inst inst)
|
|
{
|
|
int ret;
|
|
unsigned int op8, opcode, rd;
|
|
struct kvm_run *run = vcpu->run;
|
|
|
|
run->mmio.phys_addr = vcpu->arch.badv;
|
|
vcpu->mmio_needed = 2; /* signed */
|
|
op8 = (inst.word >> 24) & 0xff;
|
|
ret = EMULATE_DO_MMIO;
|
|
|
|
switch (op8) {
|
|
case 0x24 ... 0x27: /* ldptr.w/d process */
|
|
rd = inst.reg2i14_format.rd;
|
|
opcode = inst.reg2i14_format.opcode;
|
|
|
|
switch (opcode) {
|
|
case ldptrw_op:
|
|
run->mmio.len = 4;
|
|
break;
|
|
case ldptrd_op:
|
|
run->mmio.len = 8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case 0x28 ... 0x2e: /* ld.b/h/w/d, ld.bu/hu/wu process */
|
|
rd = inst.reg2i12_format.rd;
|
|
opcode = inst.reg2i12_format.opcode;
|
|
|
|
switch (opcode) {
|
|
case ldb_op:
|
|
run->mmio.len = 1;
|
|
break;
|
|
case ldbu_op:
|
|
vcpu->mmio_needed = 1; /* unsigned */
|
|
run->mmio.len = 1;
|
|
break;
|
|
case ldh_op:
|
|
run->mmio.len = 2;
|
|
break;
|
|
case ldhu_op:
|
|
vcpu->mmio_needed = 1; /* unsigned */
|
|
run->mmio.len = 2;
|
|
break;
|
|
case ldw_op:
|
|
run->mmio.len = 4;
|
|
break;
|
|
case ldwu_op:
|
|
vcpu->mmio_needed = 1; /* unsigned */
|
|
run->mmio.len = 4;
|
|
break;
|
|
case ldd_op:
|
|
run->mmio.len = 8;
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
case 0x38: /* ldx.b/h/w/d, ldx.bu/hu/wu process */
|
|
rd = inst.reg3_format.rd;
|
|
opcode = inst.reg3_format.opcode;
|
|
|
|
switch (opcode) {
|
|
case ldxb_op:
|
|
run->mmio.len = 1;
|
|
break;
|
|
case ldxbu_op:
|
|
run->mmio.len = 1;
|
|
vcpu->mmio_needed = 1; /* unsigned */
|
|
break;
|
|
case ldxh_op:
|
|
run->mmio.len = 2;
|
|
break;
|
|
case ldxhu_op:
|
|
run->mmio.len = 2;
|
|
vcpu->mmio_needed = 1; /* unsigned */
|
|
break;
|
|
case ldxw_op:
|
|
run->mmio.len = 4;
|
|
break;
|
|
case ldxwu_op:
|
|
run->mmio.len = 4;
|
|
vcpu->mmio_needed = 1; /* unsigned */
|
|
break;
|
|
case ldxd_op:
|
|
run->mmio.len = 8;
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
}
|
|
|
|
if (ret == EMULATE_DO_MMIO) {
|
|
/* Set for kvm_complete_mmio_read() use */
|
|
vcpu->arch.io_gpr = rd;
|
|
run->mmio.is_write = 0;
|
|
vcpu->mmio_is_write = 0;
|
|
trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, run->mmio.len,
|
|
run->mmio.phys_addr, NULL);
|
|
} else {
|
|
kvm_err("Read not supported Inst=0x%08x @%lx BadVaddr:%#lx\n",
|
|
inst.word, vcpu->arch.pc, vcpu->arch.badv);
|
|
kvm_arch_vcpu_dump_regs(vcpu);
|
|
vcpu->mmio_needed = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int kvm_complete_mmio_read(struct kvm_vcpu *vcpu, struct kvm_run *run)
|
|
{
|
|
enum emulation_result er = EMULATE_DONE;
|
|
unsigned long *gpr = &vcpu->arch.gprs[vcpu->arch.io_gpr];
|
|
|
|
/* Update with new PC */
|
|
update_pc(&vcpu->arch);
|
|
switch (run->mmio.len) {
|
|
case 1:
|
|
if (vcpu->mmio_needed == 2)
|
|
*gpr = *(s8 *)run->mmio.data;
|
|
else
|
|
*gpr = *(u8 *)run->mmio.data;
|
|
break;
|
|
case 2:
|
|
if (vcpu->mmio_needed == 2)
|
|
*gpr = *(s16 *)run->mmio.data;
|
|
else
|
|
*gpr = *(u16 *)run->mmio.data;
|
|
break;
|
|
case 4:
|
|
if (vcpu->mmio_needed == 2)
|
|
*gpr = *(s32 *)run->mmio.data;
|
|
else
|
|
*gpr = *(u32 *)run->mmio.data;
|
|
break;
|
|
case 8:
|
|
*gpr = *(s64 *)run->mmio.data;
|
|
break;
|
|
default:
|
|
kvm_err("Bad MMIO length: %d, addr is 0x%lx\n",
|
|
run->mmio.len, vcpu->arch.badv);
|
|
er = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
|
|
trace_kvm_mmio(KVM_TRACE_MMIO_READ, run->mmio.len,
|
|
run->mmio.phys_addr, run->mmio.data);
|
|
|
|
return er;
|
|
}
|
|
|
|
int kvm_emu_mmio_write(struct kvm_vcpu *vcpu, larch_inst inst)
|
|
{
|
|
int ret;
|
|
unsigned int rd, op8, opcode;
|
|
unsigned long curr_pc, rd_val = 0;
|
|
struct kvm_run *run = vcpu->run;
|
|
void *data = run->mmio.data;
|
|
|
|
/*
|
|
* Update PC and hold onto current PC in case there is
|
|
* an error and we want to rollback the PC
|
|
*/
|
|
curr_pc = vcpu->arch.pc;
|
|
update_pc(&vcpu->arch);
|
|
|
|
op8 = (inst.word >> 24) & 0xff;
|
|
run->mmio.phys_addr = vcpu->arch.badv;
|
|
ret = EMULATE_DO_MMIO;
|
|
switch (op8) {
|
|
case 0x24 ... 0x27: /* stptr.w/d process */
|
|
rd = inst.reg2i14_format.rd;
|
|
opcode = inst.reg2i14_format.opcode;
|
|
|
|
switch (opcode) {
|
|
case stptrw_op:
|
|
run->mmio.len = 4;
|
|
*(unsigned int *)data = vcpu->arch.gprs[rd];
|
|
break;
|
|
case stptrd_op:
|
|
run->mmio.len = 8;
|
|
*(unsigned long *)data = vcpu->arch.gprs[rd];
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
case 0x28 ... 0x2e: /* st.b/h/w/d process */
|
|
rd = inst.reg2i12_format.rd;
|
|
opcode = inst.reg2i12_format.opcode;
|
|
rd_val = vcpu->arch.gprs[rd];
|
|
|
|
switch (opcode) {
|
|
case stb_op:
|
|
run->mmio.len = 1;
|
|
*(unsigned char *)data = rd_val;
|
|
break;
|
|
case sth_op:
|
|
run->mmio.len = 2;
|
|
*(unsigned short *)data = rd_val;
|
|
break;
|
|
case stw_op:
|
|
run->mmio.len = 4;
|
|
*(unsigned int *)data = rd_val;
|
|
break;
|
|
case std_op:
|
|
run->mmio.len = 8;
|
|
*(unsigned long *)data = rd_val;
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
case 0x38: /* stx.b/h/w/d process */
|
|
rd = inst.reg3_format.rd;
|
|
opcode = inst.reg3_format.opcode;
|
|
|
|
switch (opcode) {
|
|
case stxb_op:
|
|
run->mmio.len = 1;
|
|
*(unsigned char *)data = vcpu->arch.gprs[rd];
|
|
break;
|
|
case stxh_op:
|
|
run->mmio.len = 2;
|
|
*(unsigned short *)data = vcpu->arch.gprs[rd];
|
|
break;
|
|
case stxw_op:
|
|
run->mmio.len = 4;
|
|
*(unsigned int *)data = vcpu->arch.gprs[rd];
|
|
break;
|
|
case stxd_op:
|
|
run->mmio.len = 8;
|
|
*(unsigned long *)data = vcpu->arch.gprs[rd];
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
ret = EMULATE_FAIL;
|
|
}
|
|
|
|
if (ret == EMULATE_DO_MMIO) {
|
|
run->mmio.is_write = 1;
|
|
vcpu->mmio_needed = 1;
|
|
vcpu->mmio_is_write = 1;
|
|
trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, run->mmio.len,
|
|
run->mmio.phys_addr, data);
|
|
} else {
|
|
vcpu->arch.pc = curr_pc;
|
|
kvm_err("Write not supported Inst=0x%08x @%lx BadVaddr:%#lx\n",
|
|
inst.word, vcpu->arch.pc, vcpu->arch.badv);
|
|
kvm_arch_vcpu_dump_regs(vcpu);
|
|
/* Rollback PC if emulation was unsuccessful */
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int kvm_handle_rdwr_fault(struct kvm_vcpu *vcpu, bool write)
|
|
{
|
|
int ret;
|
|
larch_inst inst;
|
|
enum emulation_result er = EMULATE_DONE;
|
|
struct kvm_run *run = vcpu->run;
|
|
unsigned long badv = vcpu->arch.badv;
|
|
|
|
ret = kvm_handle_mm_fault(vcpu, badv, write);
|
|
if (ret) {
|
|
/* Treat as MMIO */
|
|
inst.word = vcpu->arch.badi;
|
|
if (write) {
|
|
er = kvm_emu_mmio_write(vcpu, inst);
|
|
} else {
|
|
/* A code fetch fault doesn't count as an MMIO */
|
|
if (kvm_is_ifetch_fault(&vcpu->arch)) {
|
|
kvm_queue_exception(vcpu, EXCCODE_ADE, EXSUBCODE_ADEF);
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
er = kvm_emu_mmio_read(vcpu, inst);
|
|
}
|
|
}
|
|
|
|
if (er == EMULATE_DONE) {
|
|
ret = RESUME_GUEST;
|
|
} else if (er == EMULATE_DO_MMIO) {
|
|
run->exit_reason = KVM_EXIT_MMIO;
|
|
ret = RESUME_HOST;
|
|
} else {
|
|
kvm_queue_exception(vcpu, EXCCODE_ADE, EXSUBCODE_ADEM);
|
|
ret = RESUME_GUEST;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int kvm_handle_read_fault(struct kvm_vcpu *vcpu)
|
|
{
|
|
return kvm_handle_rdwr_fault(vcpu, false);
|
|
}
|
|
|
|
static int kvm_handle_write_fault(struct kvm_vcpu *vcpu)
|
|
{
|
|
return kvm_handle_rdwr_fault(vcpu, true);
|
|
}
|
|
|
|
/**
|
|
* kvm_handle_fpu_disabled() - Guest used fpu however it is disabled at host
|
|
* @vcpu: Virtual CPU context.
|
|
*
|
|
* Handle when the guest attempts to use fpu which hasn't been allowed
|
|
* by the root context.
|
|
*/
|
|
static int kvm_handle_fpu_disabled(struct kvm_vcpu *vcpu)
|
|
{
|
|
struct kvm_run *run = vcpu->run;
|
|
|
|
if (!kvm_guest_has_fpu(&vcpu->arch)) {
|
|
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
/*
|
|
* If guest FPU not present, the FPU operation should have been
|
|
* treated as a reserved instruction!
|
|
* If FPU already in use, we shouldn't get this at all.
|
|
*/
|
|
if (WARN_ON(vcpu->arch.aux_inuse & KVM_LARCH_FPU)) {
|
|
kvm_err("%s internal error\n", __func__);
|
|
run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
|
|
return RESUME_HOST;
|
|
}
|
|
|
|
kvm_own_fpu(vcpu);
|
|
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
static long kvm_save_notify(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned long id, data;
|
|
|
|
id = kvm_read_reg(vcpu, LOONGARCH_GPR_A1);
|
|
data = kvm_read_reg(vcpu, LOONGARCH_GPR_A2);
|
|
switch (id) {
|
|
case BIT(KVM_FEATURE_STEAL_TIME):
|
|
if (data & ~(KVM_STEAL_PHYS_MASK | KVM_STEAL_PHYS_VALID))
|
|
return KVM_HCALL_INVALID_PARAMETER;
|
|
|
|
vcpu->arch.st.guest_addr = data;
|
|
if (!(data & KVM_STEAL_PHYS_VALID))
|
|
return 0;
|
|
|
|
vcpu->arch.st.last_steal = current->sched_info.run_delay;
|
|
kvm_make_request(KVM_REQ_STEAL_UPDATE, vcpu);
|
|
return 0;
|
|
default:
|
|
return KVM_HCALL_INVALID_CODE;
|
|
};
|
|
|
|
return KVM_HCALL_INVALID_CODE;
|
|
};
|
|
|
|
/*
|
|
* kvm_handle_lsx_disabled() - Guest used LSX while disabled in root.
|
|
* @vcpu: Virtual CPU context.
|
|
*
|
|
* Handle when the guest attempts to use LSX when it is disabled in the root
|
|
* context.
|
|
*/
|
|
static int kvm_handle_lsx_disabled(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (kvm_own_lsx(vcpu))
|
|
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
|
|
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
/*
|
|
* kvm_handle_lasx_disabled() - Guest used LASX while disabled in root.
|
|
* @vcpu: Virtual CPU context.
|
|
*
|
|
* Handle when the guest attempts to use LASX when it is disabled in the root
|
|
* context.
|
|
*/
|
|
static int kvm_handle_lasx_disabled(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (kvm_own_lasx(vcpu))
|
|
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
|
|
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
static int kvm_handle_lbt_disabled(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (kvm_own_lbt(vcpu))
|
|
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
|
|
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
static int kvm_send_pv_ipi(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned int min, cpu, i;
|
|
unsigned long ipi_bitmap;
|
|
struct kvm_vcpu *dest;
|
|
|
|
min = kvm_read_reg(vcpu, LOONGARCH_GPR_A3);
|
|
for (i = 0; i < 2; i++, min += BITS_PER_LONG) {
|
|
ipi_bitmap = kvm_read_reg(vcpu, LOONGARCH_GPR_A1 + i);
|
|
if (!ipi_bitmap)
|
|
continue;
|
|
|
|
cpu = find_first_bit((void *)&ipi_bitmap, BITS_PER_LONG);
|
|
while (cpu < BITS_PER_LONG) {
|
|
dest = kvm_get_vcpu_by_cpuid(vcpu->kvm, cpu + min);
|
|
cpu = find_next_bit((void *)&ipi_bitmap, BITS_PER_LONG, cpu + 1);
|
|
if (!dest)
|
|
continue;
|
|
|
|
/* Send SWI0 to dest vcpu to emulate IPI interrupt */
|
|
kvm_queue_irq(dest, INT_SWI0);
|
|
kvm_vcpu_kick(dest);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Hypercall emulation always return to guest, Caller should check retval.
|
|
*/
|
|
static void kvm_handle_service(struct kvm_vcpu *vcpu)
|
|
{
|
|
long ret = KVM_HCALL_INVALID_CODE;
|
|
unsigned long func = kvm_read_reg(vcpu, LOONGARCH_GPR_A0);
|
|
|
|
switch (func) {
|
|
case KVM_HCALL_FUNC_IPI:
|
|
if (kvm_guest_has_pv_feature(vcpu, KVM_FEATURE_IPI)) {
|
|
kvm_send_pv_ipi(vcpu);
|
|
ret = KVM_HCALL_SUCCESS;
|
|
}
|
|
break;
|
|
case KVM_HCALL_FUNC_NOTIFY:
|
|
if (kvm_guest_has_pv_feature(vcpu, KVM_FEATURE_STEAL_TIME))
|
|
ret = kvm_save_notify(vcpu);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
kvm_write_reg(vcpu, LOONGARCH_GPR_A0, ret);
|
|
}
|
|
|
|
static int kvm_handle_hypercall(struct kvm_vcpu *vcpu)
|
|
{
|
|
int ret;
|
|
larch_inst inst;
|
|
unsigned int code;
|
|
|
|
inst.word = vcpu->arch.badi;
|
|
code = inst.reg0i15_format.immediate;
|
|
ret = RESUME_GUEST;
|
|
|
|
switch (code) {
|
|
case KVM_HCALL_SERVICE:
|
|
vcpu->stat.hypercall_exits++;
|
|
kvm_handle_service(vcpu);
|
|
break;
|
|
case KVM_HCALL_SWDBG:
|
|
/* KVM_HCALL_SWDBG only in effective when SW_BP is enabled */
|
|
if (vcpu->guest_debug & KVM_GUESTDBG_SW_BP_MASK) {
|
|
vcpu->run->exit_reason = KVM_EXIT_DEBUG;
|
|
ret = RESUME_HOST;
|
|
break;
|
|
}
|
|
fallthrough;
|
|
default:
|
|
/* Treat it as noop intruction, only set return value */
|
|
kvm_write_reg(vcpu, LOONGARCH_GPR_A0, KVM_HCALL_INVALID_CODE);
|
|
break;
|
|
}
|
|
|
|
if (ret == RESUME_GUEST)
|
|
update_pc(&vcpu->arch);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* LoongArch KVM callback handling for unimplemented guest exiting
|
|
*/
|
|
static int kvm_fault_ni(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned int ecode, inst;
|
|
unsigned long estat, badv;
|
|
|
|
/* Fetch the instruction */
|
|
inst = vcpu->arch.badi;
|
|
badv = vcpu->arch.badv;
|
|
estat = vcpu->arch.host_estat;
|
|
ecode = (estat & CSR_ESTAT_EXC) >> CSR_ESTAT_EXC_SHIFT;
|
|
kvm_err("ECode: %d PC=%#lx Inst=0x%08x BadVaddr=%#lx ESTAT=%#lx\n",
|
|
ecode, vcpu->arch.pc, inst, badv, read_gcsr_estat());
|
|
kvm_arch_vcpu_dump_regs(vcpu);
|
|
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
|
|
|
|
return RESUME_GUEST;
|
|
}
|
|
|
|
static exit_handle_fn kvm_fault_tables[EXCCODE_INT_START] = {
|
|
[0 ... EXCCODE_INT_START - 1] = kvm_fault_ni,
|
|
[EXCCODE_TLBI] = kvm_handle_read_fault,
|
|
[EXCCODE_TLBL] = kvm_handle_read_fault,
|
|
[EXCCODE_TLBS] = kvm_handle_write_fault,
|
|
[EXCCODE_TLBM] = kvm_handle_write_fault,
|
|
[EXCCODE_FPDIS] = kvm_handle_fpu_disabled,
|
|
[EXCCODE_LSXDIS] = kvm_handle_lsx_disabled,
|
|
[EXCCODE_LASXDIS] = kvm_handle_lasx_disabled,
|
|
[EXCCODE_BTDIS] = kvm_handle_lbt_disabled,
|
|
[EXCCODE_GSPR] = kvm_handle_gspr,
|
|
[EXCCODE_HVC] = kvm_handle_hypercall,
|
|
};
|
|
|
|
int kvm_handle_fault(struct kvm_vcpu *vcpu, int fault)
|
|
{
|
|
return kvm_fault_tables[fault](vcpu);
|
|
}
|