Bibo Mao 6bdbb73dc8 LoongArch: KVM: Fix GPA size issue about VM
Physical address space is 48 bit on Loongson-3A5000 physical machine,
however it is 47 bit for VM on Loongson-3A5000 system. Size of physical
address space of VM is the same with the size of virtual user space (a
half) of physical machine.

Variable cpu_vabits represents user address space, kernel address space
is not included (user space and kernel space are both a half of total).
Here cpu_vabits, rather than cpu_vabits - 1, is to represent the size of
guest physical address space.

Also there is strict checking about page fault GPA address, inject error
if it is larger than maximum GPA address of VM.

Cc: stable@vger.kernel.org
Signed-off-by: Bibo Mao <maobibo@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
2025-03-08 13:52:04 +08:00

971 lines
22 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 idx, 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];
run->iocsr_io.phys_addr = addr;
run->iocsr_io.is_write = 0;
val = &vcpu->arch.gprs[rd];
/* 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:
return EMULATE_FAIL;
}
if (run->iocsr_io.is_write) {
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_write(vcpu, KVM_IOCSR_BUS, addr, run->iocsr_io.len, val);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (ret == 0)
ret = EMULATE_DONE;
else {
ret = EMULATE_DO_IOCSR;
/* Save data and let user space to write it */
memcpy(run->iocsr_io.data, val, run->iocsr_io.len);
}
trace_kvm_iocsr(KVM_TRACE_IOCSR_WRITE, run->iocsr_io.len, addr, val);
} else {
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_read(vcpu, KVM_IOCSR_BUS, addr, run->iocsr_io.len, val);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (ret == 0)
ret = EMULATE_DONE;
else {
ret = EMULATE_DO_IOCSR;
/* Save register id for iocsr read completion */
vcpu->arch.io_gpr = rd;
}
trace_kvm_iocsr(KVM_TRACE_IOCSR_READ, run->iocsr_io.len, addr, NULL);
}
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 idx, 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) {
trace_kvm_mmio(KVM_TRACE_MMIO_READ, run->mmio.len, run->mmio.phys_addr, NULL);
/*
* If mmio device such as PCH-PIC is emulated in KVM,
* it need not return to user space to handle the mmio
* exception.
*/
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_read(vcpu, KVM_MMIO_BUS, vcpu->arch.badv,
run->mmio.len, &vcpu->arch.gprs[rd]);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (!ret) {
update_pc(&vcpu->arch);
vcpu->mmio_needed = 0;
return EMULATE_DONE;
}
/* Set for kvm_complete_mmio_read() use */
vcpu->arch.io_gpr = rd;
run->mmio.is_write = 0;
vcpu->mmio_is_write = 0;
return EMULATE_DO_MMIO;
}
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 idx, 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) {
trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, run->mmio.len, run->mmio.phys_addr, data);
/*
* If mmio device such as PCH-PIC is emulated in KVM,
* it need not return to user space to handle the mmio
* exception.
*/
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_write(vcpu, KVM_MMIO_BUS, vcpu->arch.badv, run->mmio.len, data);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (!ret)
return EMULATE_DONE;
run->mmio.is_write = 1;
vcpu->mmio_needed = 1;
vcpu->mmio_is_write = 1;
return EMULATE_DO_MMIO;
}
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;
/* Inject ADE exception if exceed max GPA size */
if (unlikely(badv >= vcpu->kvm->arch.gpa_size)) {
kvm_queue_exception(vcpu, EXCCODE_ADE, EXSUBCODE_ADEM);
return RESUME_GUEST;
}
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);
}
int kvm_complete_user_service(struct kvm_vcpu *vcpu, struct kvm_run *run)
{
update_pc(&vcpu->arch);
kvm_write_reg(vcpu, LOONGARCH_GPR_A0, run->hypercall.ret);
return 0;
}
/**
* 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_USER_SERVICE:
if (!kvm_guest_has_pv_feature(vcpu, KVM_FEATURE_USER_HCALL)) {
kvm_write_reg(vcpu, LOONGARCH_GPR_A0, KVM_HCALL_INVALID_CODE);
break;
}
vcpu->stat.hypercall_exits++;
vcpu->run->exit_reason = KVM_EXIT_HYPERCALL;
vcpu->run->hypercall.nr = KVM_HCALL_USER_SERVICE;
vcpu->run->hypercall.args[0] = kvm_read_reg(vcpu, LOONGARCH_GPR_A0);
vcpu->run->hypercall.args[1] = kvm_read_reg(vcpu, LOONGARCH_GPR_A1);
vcpu->run->hypercall.args[2] = kvm_read_reg(vcpu, LOONGARCH_GPR_A2);
vcpu->run->hypercall.args[3] = kvm_read_reg(vcpu, LOONGARCH_GPR_A3);
vcpu->run->hypercall.args[4] = kvm_read_reg(vcpu, LOONGARCH_GPR_A4);
vcpu->run->hypercall.args[5] = kvm_read_reg(vcpu, LOONGARCH_GPR_A5);
vcpu->run->hypercall.flags = 0;
/*
* Set invalid return value by default, let user-mode VMM modify it.
*/
vcpu->run->hypercall.ret = KVM_HCALL_INVALID_CODE;
ret = RESUME_HOST;
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);
}