Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0
#
# KVM configuration
#
source "virt/kvm/Kconfig"
menuconfig VIRTUALIZATION
bool "Virtualization"
help
Say Y here to get to see options for using your Linux host to run
other operating systems inside virtual machines (guests).
This option alone does not add any kernel code.
If you say N, all options in this submenu will be skipped and
disabled.
if VIRTUALIZATION
config KVM
tristate "Kernel-based Virtual Machine (KVM) support"
depends on AS_HAS_LVZ_EXTENSION && 64BIT
select HAVE_KVM_DIRTY_RING_ACQ_REL
select HAVE_KVM_IRQ_ROUTING
select HAVE_KVM_IRQCHIP
select HAVE_KVM_MSI
select HAVE_KVM_READONLY_MEM
select KVM_COMMON
select KVM_GENERIC_DIRTYLOG_READ_PROTECT
select KVM_GENERIC_HARDWARE_ENABLING
select KVM_MMIO
select VIRT_XFER_TO_GUEST_WORK
select SCHED_INFO
select GUEST_PERF_EVENTS if PERF_EVENTS
help
Support hosting virtualized guest machines using
hardware virtualization extensions. You will need
a processor equipped with virtualization extensions.
If unsure, say N.
endif # VIRTUALIZATION
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for LoongArch KVM support
#
include $(srctree)/virt/kvm/Makefile.kvm
obj-$(CONFIG_KVM) += kvm.o
obj-y += switch.o
kvm-y += exit.o
kvm-y += interrupt.o
kvm-y += main.o
kvm-y += mmu.o
kvm-y += timer.o
kvm-y += tlb.o
kvm-y += vcpu.o
kvm-y += vm.o
kvm-y += intc/ipi.o
kvm-y += intc/dmsintc.o
kvm-y += intc/eiointc.o
kvm-y += intc/pch_pic.o
kvm-y += irqfd.o
CFLAGS_exit.o += $(call cc-disable-warning, override-init)
+971
View File
@@ -0,0 +1,971 @@
// 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);
} 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 {
vcpu->arch.io_gpr = rd; /* Set register id for iocsr read completion */
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_read(vcpu, KVM_IOCSR_BUS, addr,
run->iocsr_io.len, run->iocsr_io.data);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (ret == 0) {
kvm_complete_iocsr_read(vcpu, run);
ret = EMULATE_DONE;
} else
ret = EMULATE_DO_IOCSR;
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 */
trace_kvm_exit_cpucfg(vcpu, KVM_TRACE_EXIT_CPUCFG);
er = kvm_emu_cpucfg(vcpu, inst);
break;
case 0x4: /* CSR{RD,WR,XCHG} GSPR */
trace_kvm_exit_csr(vcpu, KVM_TRACE_EXIT_CSR);
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 ecode)
{
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:
ret = EMULATE_FAIL;
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);
vcpu->arch.io_gpr = rd; /* Set for kvm_complete_mmio_read() use */
/*
* 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, run->mmio.data);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (!ret) {
kvm_complete_mmio_read(vcpu, run);
update_pc(&vcpu->arch);
vcpu->mmio_needed = 0;
return EMULATE_DONE;
}
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 ecode)
{
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, ecode);
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, int ecode)
{
return kvm_handle_rdwr_fault(vcpu, false, ecode);
}
static int kvm_handle_write_fault(struct kvm_vcpu *vcpu, int ecode)
{
return kvm_handle_rdwr_fault(vcpu, true, ecode);
}
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.
* @ecode: Exception code.
*
* 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, int ecode)
{
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_make_request(KVM_REQ_FPU_LOAD, 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;
}
}
/*
* kvm_handle_lsx_disabled() - Guest used LSX while disabled in root.
* @vcpu: Virtual CPU context.
* @ecode: Exception code.
*
* 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, int ecode)
{
if (!kvm_guest_has_lsx(&vcpu->arch))
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
else
kvm_make_request(KVM_REQ_FPU_LOAD, vcpu);
return RESUME_GUEST;
}
/*
* kvm_handle_lasx_disabled() - Guest used LASX while disabled in root.
* @vcpu: Virtual CPU context.
* @ecode: Exception code.
*
* 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, int ecode)
{
if (!kvm_guest_has_lasx(&vcpu->arch))
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
else
kvm_make_request(KVM_REQ_FPU_LOAD, vcpu);
return RESUME_GUEST;
}
static int kvm_handle_lbt_disabled(struct kvm_vcpu *vcpu, int ecode)
{
if (!kvm_guest_has_lbt(&vcpu->arch))
kvm_queue_exception(vcpu, EXCCODE_INE, 0);
else
kvm_make_request(KVM_REQ_LBT_LOAD, vcpu);
return RESUME_GUEST;
}
static void kvm_send_pv_ipi(struct kvm_vcpu *vcpu)
{
unsigned int min, cpu;
struct kvm_vcpu *dest;
DECLARE_BITMAP(ipi_bitmap, BITS_PER_LONG * 2) = {
kvm_read_reg(vcpu, LOONGARCH_GPR_A1),
kvm_read_reg(vcpu, LOONGARCH_GPR_A2)
};
min = kvm_read_reg(vcpu, LOONGARCH_GPR_A3);
for_each_set_bit(cpu, ipi_bitmap, BITS_PER_LONG * 2) {
dest = kvm_get_vcpu_by_cpuid(vcpu->kvm, cpu + min);
if (!dest)
continue;
/* Send SWI0 to dest vcpu to emulate IPI interrupt */
kvm_queue_irq(dest, INT_SWI0);
kvm_vcpu_kick(dest);
}
}
/*
* 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 ecode)
{
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, int ecode)
{
unsigned int inst;
unsigned long badv;
/* Fetch the instruction */
inst = vcpu->arch.badi;
badv = vcpu->arch.badv;
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, fault);
}
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 Loongson Technology Corporation Limited
*/
#include <linux/kvm_host.h>
#include <asm/kvm_csr.h>
#include <asm/kvm_dmsintc.h>
#include <asm/kvm_vcpu.h>
void dmsintc_inject_irq(struct kvm_vcpu *vcpu)
{
unsigned int i;
unsigned long vector[4], old;
struct dmsintc_state *ds = &vcpu->arch.dmsintc_state;
if (!ds)
return;
for (i = 0; i < 4; i++) {
old = atomic64_read(&(ds->vector_map[i]));
if (old)
vector[i] = atomic64_xchg(&(ds->vector_map[i]), 0);
}
if (vector[0]) {
old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR0);
kvm_write_hw_gcsr(LOONGARCH_CSR_ISR0, vector[0] | old);
}
if (vector[1]) {
old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR1);
kvm_write_hw_gcsr(LOONGARCH_CSR_ISR1, vector[1] | old);
}
if (vector[2]) {
old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR2);
kvm_write_hw_gcsr(LOONGARCH_CSR_ISR2, vector[2] | old);
}
if (vector[3]) {
old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR3);
kvm_write_hw_gcsr(LOONGARCH_CSR_ISR3, vector[3] | old);
}
}
int dmsintc_deliver_msi_to_vcpu(struct kvm *kvm,
struct kvm_vcpu *vcpu, u32 vector, int level)
{
struct kvm_interrupt vcpu_irq;
struct dmsintc_state *ds = &vcpu->arch.dmsintc_state;
if (!level)
return 0;
if (!vcpu || vector >= 256)
return -EINVAL;
if (!ds)
return -ENODEV;
vcpu_irq.irq = INT_AVEC;
set_bit(vector, (unsigned long *)&ds->vector_map);
kvm_vcpu_ioctl_interrupt(vcpu, &vcpu_irq);
kvm_vcpu_kick(vcpu);
return 0;
}
int dmsintc_set_irq(struct kvm *kvm, u64 addr, int data, int level)
{
unsigned int irq, cpu;
struct kvm_vcpu *vcpu;
irq = (addr >> AVEC_IRQ_SHIFT) & AVEC_IRQ_MASK;
cpu = (addr >> AVEC_CPU_SHIFT) & kvm->arch.dmsintc->cpu_mask;
if (cpu >= KVM_MAX_VCPUS)
return -EINVAL;
vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu);
if (!vcpu)
return -EINVAL;
return dmsintc_deliver_msi_to_vcpu(kvm, vcpu, irq, level);
}
static int kvm_dmsintc_ctrl_access(struct kvm_device *dev,
struct kvm_device_attr *attr, bool is_write)
{
int addr = attr->attr;
unsigned long cpu_bit, val;
void __user *data = (void __user *)attr->addr;
struct loongarch_dmsintc *s = dev->kvm->arch.dmsintc;
switch (addr) {
case KVM_DEV_LOONGARCH_DMSINTC_MSG_ADDR_BASE:
if (is_write) {
if (copy_from_user(&val, data, sizeof(s->msg_addr_base)))
return -EFAULT;
if (s->msg_addr_base)
return -EFAULT; /* Duplicate setting are not allowed. */
if ((val & (BIT(AVEC_CPU_SHIFT) - 1)) != 0)
return -EINVAL;
s->msg_addr_base = val;
cpu_bit = find_first_bit((unsigned long *)&(s->msg_addr_base), 64) - AVEC_CPU_SHIFT;
cpu_bit = min(cpu_bit, AVEC_CPU_BIT);
s->cpu_mask = GENMASK(cpu_bit - 1, 0) & AVEC_CPU_MASK;
}
break;
case KVM_DEV_LOONGARCH_DMSINTC_MSG_ADDR_SIZE:
if (is_write) {
if (copy_from_user(&val, data, sizeof(s->msg_addr_size)))
return -EFAULT;
if (s->msg_addr_size)
return -EFAULT; /*Duplicate setting are not allowed. */
s->msg_addr_size = val;
}
break;
default:
kvm_err("%s: unknown dmsintc register, addr = %d\n", __func__, addr);
return -ENXIO;
}
return 0;
}
static int kvm_dmsintc_set_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
switch (attr->group) {
case KVM_DEV_LOONGARCH_DMSINTC_GRP_CTRL:
return kvm_dmsintc_ctrl_access(dev, attr, true);
default:
kvm_err("%s: unknown group (%d)\n", __func__, attr->group);
return -EINVAL;
}
}
static int kvm_dmsintc_create(struct kvm_device *dev, u32 type)
{
struct kvm *kvm;
struct loongarch_dmsintc *s;
if (!dev) {
kvm_err("%s: kvm_device ptr is invalid!\n", __func__);
return -EINVAL;
}
kvm = dev->kvm;
if (kvm->arch.dmsintc) {
kvm_err("%s: LoongArch DMSINTC has already been created!\n", __func__);
return -EINVAL;
}
s = kzalloc(sizeof(struct loongarch_dmsintc), GFP_KERNEL);
if (!s)
return -ENOMEM;
s->kvm = kvm;
kvm->arch.dmsintc = s;
return 0;
}
static void kvm_dmsintc_destroy(struct kvm_device *dev)
{
if (!dev || !dev->kvm || !dev->kvm->arch.dmsintc)
return;
kfree(dev->kvm->arch.dmsintc);
kfree(dev);
}
static struct kvm_device_ops kvm_dmsintc_dev_ops = {
.name = "kvm-loongarch-dmsintc",
.create = kvm_dmsintc_create,
.destroy = kvm_dmsintc_destroy,
.set_attr = kvm_dmsintc_set_attr,
};
int kvm_loongarch_register_dmsintc_device(void)
{
return kvm_register_device_ops(&kvm_dmsintc_dev_ops, KVM_DEV_TYPE_LOONGARCH_DMSINTC);
}
@@ -0,0 +1,693 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Loongson Technology Corporation Limited
*/
#include <asm/kvm_eiointc.h>
#include <asm/kvm_vcpu.h>
#include <linux/count_zeros.h>
static void eiointc_set_sw_coreisr(struct loongarch_eiointc *s)
{
int ipnum, cpu, cpuid, irq;
struct kvm_vcpu *vcpu;
for (irq = 0; irq < EIOINTC_IRQS; irq++) {
ipnum = (s->ipmap >> (irq / 32 * 8)) & 0xff;
if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
ipnum = count_trailing_zeros(ipnum);
ipnum = ipnum < 4 ? ipnum : 0;
}
cpuid = ((u8 *)s->coremap)[irq];
vcpu = kvm_get_vcpu_by_cpuid(s->kvm, cpuid);
if (!vcpu)
continue;
cpu = vcpu->vcpu_id;
if (test_bit(irq, (unsigned long *)s->coreisr[cpu]))
__set_bit(irq, s->sw_coreisr[cpu][ipnum]);
else
__clear_bit(irq, s->sw_coreisr[cpu][ipnum]);
}
}
static void eiointc_update_irq(struct loongarch_eiointc *s, int irq, int level)
{
int ipnum, cpu, found;
struct kvm_vcpu *vcpu;
struct kvm_interrupt vcpu_irq;
ipnum = (s->ipmap >> (irq / 32 * 8)) & 0xff;
if (!(s->status & BIT(EIOINTC_ENABLE_INT_ENCODE))) {
ipnum = count_trailing_zeros(ipnum);
ipnum = ipnum < 4 ? ipnum : 0;
}
cpu = s->sw_coremap[irq];
vcpu = kvm_get_vcpu_by_id(s->kvm, cpu);
if (unlikely(vcpu == NULL)) {
kvm_err("%s: invalid target cpu: %d\n", __func__, cpu);
return;
}
if (level) {
/* if not enable return false */
if (!test_bit(irq, (unsigned long *)s->enable))
return;
__set_bit(irq, (unsigned long *)s->coreisr[cpu]);
found = find_first_bit(s->sw_coreisr[cpu][ipnum], EIOINTC_IRQS);
__set_bit(irq, s->sw_coreisr[cpu][ipnum]);
} else {
__clear_bit(irq, (unsigned long *)s->coreisr[cpu]);
__clear_bit(irq, s->sw_coreisr[cpu][ipnum]);
found = find_first_bit(s->sw_coreisr[cpu][ipnum], EIOINTC_IRQS);
}
if (found < EIOINTC_IRQS)
return; /* other irq is handling, needn't update parent irq */
vcpu_irq.irq = level ? (INT_HWI0 + ipnum) : -(INT_HWI0 + ipnum);
kvm_vcpu_ioctl_interrupt(vcpu, &vcpu_irq);
}
static inline void eiointc_update_sw_coremap(struct loongarch_eiointc *s,
int irq, u64 val, u32 len, bool notify)
{
int i, cpu, cpuid;
struct kvm_vcpu *vcpu;
for (i = 0; i < len; i++) {
cpuid = val & 0xff;
val = val >> 8;
if (!(s->status & BIT(EIOINTC_ENABLE_CPU_ENCODE))) {
cpuid = ffs(cpuid) - 1;
cpuid = ((cpuid < 0) || (cpuid >= 4)) ? 0 : cpuid;
}
vcpu = kvm_get_vcpu_by_cpuid(s->kvm, cpuid);
if (!vcpu)
continue;
cpu = vcpu->vcpu_id;
if (s->sw_coremap[irq + i] == cpu)
continue;
if (notify && test_bit(irq + i, (unsigned long *)s->isr)) {
/* lower irq at old cpu and raise irq at new cpu */
eiointc_update_irq(s, irq + i, 0);
s->sw_coremap[irq + i] = cpu;
eiointc_update_irq(s, irq + i, 1);
} else {
s->sw_coremap[irq + i] = cpu;
}
}
}
void eiointc_set_irq(struct loongarch_eiointc *s, int irq, int level)
{
unsigned long flags;
unsigned long *isr = (unsigned long *)s->isr;
spin_lock_irqsave(&s->lock, flags);
level ? __set_bit(irq, isr) : __clear_bit(irq, isr);
eiointc_update_irq(s, irq, level);
spin_unlock_irqrestore(&s->lock, flags);
}
static int loongarch_eiointc_read(struct kvm_vcpu *vcpu, struct loongarch_eiointc *s,
gpa_t addr, unsigned long *val)
{
int index;
u64 data = 0;
gpa_t offset;
offset = addr - EIOINTC_BASE;
switch (offset) {
case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END:
index = (offset - EIOINTC_NODETYPE_START) >> 3;
data = s->nodetype[index];
break;
case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END:
index = (offset - EIOINTC_IPMAP_START) >> 3;
data = s->ipmap;
break;
case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END:
index = (offset - EIOINTC_ENABLE_START) >> 3;
data = s->enable[index];
break;
case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END:
index = (offset - EIOINTC_BOUNCE_START) >> 3;
data = s->bounce[index];
break;
case EIOINTC_COREISR_START ... EIOINTC_COREISR_END:
index = (offset - EIOINTC_COREISR_START) >> 3;
data = s->coreisr[vcpu->vcpu_id][index];
break;
case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END:
index = (offset - EIOINTC_COREMAP_START) >> 3;
data = s->coremap[index];
break;
default:
break;
}
*val = data;
return 0;
}
static int kvm_eiointc_read(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, void *val)
{
unsigned long flags, data, offset;
struct loongarch_eiointc *eiointc = vcpu->kvm->arch.eiointc;
if (!eiointc) {
kvm_err("%s: eiointc irqchip not valid!\n", __func__);
return 0;
}
if (addr & (len - 1)) {
kvm_err("%s: eiointc not aligned addr %llx len %d\n", __func__, addr, len);
return 0;
}
offset = addr & 0x7;
addr -= offset;
vcpu->stat.eiointc_read_exits++;
spin_lock_irqsave(&eiointc->lock, flags);
loongarch_eiointc_read(vcpu, eiointc, addr, &data);
spin_unlock_irqrestore(&eiointc->lock, flags);
data = data >> (offset * 8);
switch (len) {
case 1:
*(long *)val = (s8)data;
break;
case 2:
*(long *)val = (s16)data;
break;
case 4:
*(long *)val = (s32)data;
break;
default:
*(long *)val = (long)data;
break;
}
return 0;
}
static int loongarch_eiointc_write(struct kvm_vcpu *vcpu,
struct loongarch_eiointc *s,
gpa_t addr, u64 value, u64 field_mask)
{
int index, irq;
u8 cpu;
u64 data, old, mask;
gpa_t offset;
offset = addr & 7;
mask = field_mask << (offset * 8);
data = (value & field_mask) << (offset * 8);
addr -= offset;
offset = addr - EIOINTC_BASE;
switch (offset) {
case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END:
index = (offset - EIOINTC_NODETYPE_START) >> 3;
old = s->nodetype[index];
s->nodetype[index] = (old & ~mask) | data;
break;
case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END:
/*
* ipmap cannot be set at runtime, can be set only at the beginning
* of irqchip driver, need not update upper irq level
*/
old = s->ipmap;
s->ipmap = (old & ~mask) | data;
break;
case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END:
index = (offset - EIOINTC_ENABLE_START) >> 3;
old = s->enable[index];
s->enable[index] = (old & ~mask) | data;
/*
* 1: enable irq.
* update irq when isr is set.
*/
data = s->enable[index] & ~old & s->isr[index];
while (data) {
irq = __ffs(data);
eiointc_update_irq(s, irq + index * 64, 1);
data &= ~BIT_ULL(irq);
}
/*
* 0: disable irq.
* update irq when isr is set.
*/
data = ~s->enable[index] & old & s->isr[index];
while (data) {
irq = __ffs(data);
eiointc_update_irq(s, irq + index * 64, 0);
data &= ~BIT_ULL(irq);
}
break;
case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END:
/* do not emulate hw bounced irq routing */
index = (offset - EIOINTC_BOUNCE_START) >> 3;
old = s->bounce[index];
s->bounce[index] = (old & ~mask) | data;
break;
case EIOINTC_COREISR_START ... EIOINTC_COREISR_END:
index = (offset - EIOINTC_COREISR_START) >> 3;
/* use attrs to get current cpu index */
cpu = vcpu->vcpu_id;
old = s->coreisr[cpu][index];
/* write 1 to clear interrupt */
s->coreisr[cpu][index] = old & ~data;
data &= old;
while (data) {
irq = __ffs(data);
eiointc_update_irq(s, irq + index * 64, 0);
data &= ~BIT_ULL(irq);
}
break;
case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END:
index = (offset - EIOINTC_COREMAP_START) >> 3;
old = s->coremap[index];
s->coremap[index] = (old & ~mask) | data;
data = s->coremap[index];
eiointc_update_sw_coremap(s, index * 8, data, sizeof(data), true);
break;
default:
break;
}
return 0;
}
static int kvm_eiointc_write(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
unsigned long flags, value;
struct loongarch_eiointc *eiointc = vcpu->kvm->arch.eiointc;
if (!eiointc) {
kvm_err("%s: eiointc irqchip not valid!\n", __func__);
return 0;
}
if (addr & (len - 1)) {
kvm_err("%s: eiointc not aligned addr %llx len %d\n", __func__, addr, len);
return 0;
}
vcpu->stat.eiointc_write_exits++;
spin_lock_irqsave(&eiointc->lock, flags);
switch (len) {
case 1:
value = *(unsigned char *)val;
loongarch_eiointc_write(vcpu, eiointc, addr, value, 0xFF);
break;
case 2:
value = *(unsigned short *)val;
loongarch_eiointc_write(vcpu, eiointc, addr, value, USHRT_MAX);
break;
case 4:
value = *(unsigned int *)val;
loongarch_eiointc_write(vcpu, eiointc, addr, value, UINT_MAX);
break;
default:
value = *(unsigned long *)val;
loongarch_eiointc_write(vcpu, eiointc, addr, value, ULONG_MAX);
break;
}
spin_unlock_irqrestore(&eiointc->lock, flags);
return 0;
}
static const struct kvm_io_device_ops kvm_eiointc_ops = {
.read = kvm_eiointc_read,
.write = kvm_eiointc_write,
};
static int kvm_eiointc_virt_read(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, void *val)
{
unsigned long flags;
u32 *data = val;
struct loongarch_eiointc *eiointc = vcpu->kvm->arch.eiointc;
if (!eiointc) {
kvm_err("%s: eiointc irqchip not valid!\n", __func__);
return 0;
}
addr -= EIOINTC_VIRT_BASE;
spin_lock_irqsave(&eiointc->lock, flags);
switch (addr) {
case EIOINTC_VIRT_FEATURES:
*data = eiointc->features;
break;
case EIOINTC_VIRT_CONFIG:
*data = eiointc->status;
break;
default:
break;
}
spin_unlock_irqrestore(&eiointc->lock, flags);
return 0;
}
static int kvm_eiointc_virt_write(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
unsigned long flags;
u32 value = *(u32 *)val;
struct loongarch_eiointc *eiointc = vcpu->kvm->arch.eiointc;
if (!eiointc) {
kvm_err("%s: eiointc irqchip not valid!\n", __func__);
return 0;
}
addr -= EIOINTC_VIRT_BASE;
spin_lock_irqsave(&eiointc->lock, flags);
switch (addr) {
case EIOINTC_VIRT_FEATURES:
break;
case EIOINTC_VIRT_CONFIG:
/*
* eiointc features can only be set at disabled status
*/
if ((eiointc->status & BIT(EIOINTC_ENABLE)) && value) {
break;
}
eiointc->status = value & eiointc->features;
break;
default:
break;
}
spin_unlock_irqrestore(&eiointc->lock, flags);
return 0;
}
static const struct kvm_io_device_ops kvm_eiointc_virt_ops = {
.read = kvm_eiointc_virt_read,
.write = kvm_eiointc_virt_write,
};
static int kvm_eiointc_ctrl_access(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
int ret = 0;
unsigned long flags;
unsigned long type = (unsigned long)attr->attr;
u32 i, start_irq, val;
void __user *data;
struct loongarch_eiointc *s = dev->kvm->arch.eiointc;
data = (void __user *)attr->addr;
switch (type) {
case KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_NUM_CPU:
case KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_FEATURE:
if (copy_from_user(&val, data, 4))
return -EFAULT;
break;
default:
break;
}
spin_lock_irqsave(&s->lock, flags);
switch (type) {
case KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_NUM_CPU:
if (val > EIOINTC_ROUTE_MAX_VCPUS)
ret = -EINVAL;
else
s->num_cpu = val;
break;
case KVM_DEV_LOONGARCH_EXTIOI_CTRL_INIT_FEATURE:
s->features = val;
if (!(s->features & BIT(EIOINTC_HAS_VIRT_EXTENSION)))
s->status |= BIT(EIOINTC_ENABLE);
break;
case KVM_DEV_LOONGARCH_EXTIOI_CTRL_LOAD_FINISHED:
eiointc_set_sw_coreisr(s);
for (i = 0; i < (EIOINTC_IRQS / 8); i++) {
start_irq = i * 8;
eiointc_update_sw_coremap(s, start_irq,
s->coremap[i], sizeof(u64), false);
}
break;
default:
break;
}
spin_unlock_irqrestore(&s->lock, flags);
return ret;
}
static int kvm_eiointc_regs_access(struct kvm_device *dev,
struct kvm_device_attr *attr,
bool is_write, int *data)
{
int addr, cpu, offset, ret = 0;
unsigned long flags;
void *p = NULL;
struct loongarch_eiointc *s;
s = dev->kvm->arch.eiointc;
addr = attr->attr;
cpu = addr >> 16;
addr &= 0xffff;
switch (addr) {
case EIOINTC_NODETYPE_START ... EIOINTC_NODETYPE_END:
offset = (addr - EIOINTC_NODETYPE_START) / 4;
p = (void *)s->nodetype + offset * 4;
break;
case EIOINTC_IPMAP_START ... EIOINTC_IPMAP_END:
offset = (addr - EIOINTC_IPMAP_START) / 4;
p = (void *)&s->ipmap + offset * 4;
break;
case EIOINTC_ENABLE_START ... EIOINTC_ENABLE_END:
offset = (addr - EIOINTC_ENABLE_START) / 4;
p = (void *)s->enable + offset * 4;
break;
case EIOINTC_BOUNCE_START ... EIOINTC_BOUNCE_END:
offset = (addr - EIOINTC_BOUNCE_START) / 4;
p = (void *)s->bounce + offset * 4;
break;
case EIOINTC_ISR_START ... EIOINTC_ISR_END:
offset = (addr - EIOINTC_ISR_START) / 4;
p = (void *)s->isr + offset * 4;
break;
case EIOINTC_COREISR_START ... EIOINTC_COREISR_END:
if (cpu >= s->num_cpu)
return -EINVAL;
offset = (addr - EIOINTC_COREISR_START) / 4;
p = (void *)s->coreisr[cpu] + offset * 4;
break;
case EIOINTC_COREMAP_START ... EIOINTC_COREMAP_END:
offset = (addr - EIOINTC_COREMAP_START) / 4;
p = (void *)s->coremap + offset * 4;
break;
default:
kvm_err("%s: unknown eiointc register, addr = %d\n", __func__, addr);
return -EINVAL;
}
spin_lock_irqsave(&s->lock, flags);
if (is_write)
memcpy(p, data, 4);
else
memcpy(data, p, 4);
spin_unlock_irqrestore(&s->lock, flags);
return ret;
}
static int kvm_eiointc_sw_status_access(struct kvm_device *dev,
struct kvm_device_attr *attr,
bool is_write, int *data)
{
int addr, ret = 0;
unsigned long flags;
void *p = NULL;
struct loongarch_eiointc *s;
s = dev->kvm->arch.eiointc;
addr = attr->attr;
addr &= 0xffff;
switch (addr) {
case KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_NUM_CPU:
if (is_write)
return ret;
p = &s->num_cpu;
break;
case KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_FEATURE:
if (is_write)
return ret;
p = &s->features;
break;
case KVM_DEV_LOONGARCH_EXTIOI_SW_STATUS_STATE:
p = &s->status;
break;
default:
kvm_err("%s: unknown eiointc register, addr = %d\n", __func__, addr);
return -EINVAL;
}
spin_lock_irqsave(&s->lock, flags);
if (is_write)
memcpy(p, data, 4);
else
memcpy(data, p, 4);
spin_unlock_irqrestore(&s->lock, flags);
return ret;
}
static int kvm_eiointc_get_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
int ret, data;
switch (attr->group) {
case KVM_DEV_LOONGARCH_EXTIOI_GRP_REGS:
ret = kvm_eiointc_regs_access(dev, attr, false, &data);
if (ret)
return ret;
if (copy_to_user((void __user *)attr->addr, &data, 4))
ret = -EFAULT;
return ret;
case KVM_DEV_LOONGARCH_EXTIOI_GRP_SW_STATUS:
ret = kvm_eiointc_sw_status_access(dev, attr, false, &data);
if (ret)
return ret;
if (copy_to_user((void __user *)attr->addr, &data, 4))
ret = -EFAULT;
return ret;
default:
return -EINVAL;
}
}
static int kvm_eiointc_set_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
int data;
switch (attr->group) {
case KVM_DEV_LOONGARCH_EXTIOI_GRP_CTRL:
return kvm_eiointc_ctrl_access(dev, attr);
case KVM_DEV_LOONGARCH_EXTIOI_GRP_REGS:
if (copy_from_user(&data, (void __user *)attr->addr, 4))
return -EFAULT;
return kvm_eiointc_regs_access(dev, attr, true, &data);
case KVM_DEV_LOONGARCH_EXTIOI_GRP_SW_STATUS:
if (copy_from_user(&data, (void __user *)attr->addr, 4))
return -EFAULT;
return kvm_eiointc_sw_status_access(dev, attr, true, &data);
default:
return -EINVAL;
}
}
static int kvm_eiointc_create(struct kvm_device *dev, u32 type)
{
int ret;
struct loongarch_eiointc *s;
struct kvm_io_device *device;
struct kvm *kvm = dev->kvm;
/* eiointc has been created */
if (kvm->arch.eiointc)
return -EINVAL;
s = kzalloc_obj(struct loongarch_eiointc);
if (!s)
return -ENOMEM;
spin_lock_init(&s->lock);
s->kvm = kvm;
/*
* Initialize IOCSR device
*/
device = &s->device;
kvm_iodevice_init(device, &kvm_eiointc_ops);
mutex_lock(&kvm->slots_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_IOCSR_BUS,
EIOINTC_BASE, EIOINTC_SIZE, device);
mutex_unlock(&kvm->slots_lock);
if (ret < 0) {
kfree(s);
return ret;
}
device = &s->device_vext;
kvm_iodevice_init(device, &kvm_eiointc_virt_ops);
mutex_lock(&kvm->slots_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_IOCSR_BUS,
EIOINTC_VIRT_BASE, EIOINTC_VIRT_SIZE, device);
mutex_unlock(&kvm->slots_lock);
if (ret < 0) {
mutex_lock(&kvm->slots_lock);
kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &s->device);
mutex_unlock(&kvm->slots_lock);
kfree(s);
return ret;
}
kvm->arch.eiointc = s;
return 0;
}
static void kvm_eiointc_destroy(struct kvm_device *dev)
{
struct kvm *kvm;
struct loongarch_eiointc *eiointc;
if (!dev || !dev->kvm || !dev->kvm->arch.eiointc)
return;
kvm = dev->kvm;
eiointc = kvm->arch.eiointc;
mutex_lock(&kvm->slots_lock);
kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &eiointc->device);
kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &eiointc->device_vext);
mutex_unlock(&kvm->slots_lock);
kfree(eiointc);
kfree(dev);
}
static struct kvm_device_ops kvm_eiointc_dev_ops = {
.name = "kvm-loongarch-eiointc",
.create = kvm_eiointc_create,
.destroy = kvm_eiointc_destroy,
.set_attr = kvm_eiointc_set_attr,
.get_attr = kvm_eiointc_get_attr,
};
int kvm_loongarch_register_eiointc_device(void)
{
return kvm_register_device_ops(&kvm_eiointc_dev_ops, KVM_DEV_TYPE_LOONGARCH_EIOINTC);
}
+468
View File
@@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Loongson Technology Corporation Limited
*/
#include <linux/kvm_host.h>
#include <asm/kvm_ipi.h>
#include <asm/kvm_vcpu.h>
static void ipi_set(struct kvm_vcpu *vcpu, uint32_t data)
{
uint32_t status;
struct kvm_interrupt irq;
spin_lock(&vcpu->arch.ipi_state.lock);
status = vcpu->arch.ipi_state.status;
vcpu->arch.ipi_state.status |= data;
spin_unlock(&vcpu->arch.ipi_state.lock);
if ((status == 0) && data) {
irq.irq = LARCH_INT_IPI;
kvm_vcpu_ioctl_interrupt(vcpu, &irq);
}
}
static void ipi_send(struct kvm *kvm, uint64_t data)
{
int cpu;
struct kvm_vcpu *vcpu;
cpu = ((data & 0xffffffff) >> 16) & 0x3ff;
vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu);
if (unlikely(vcpu == NULL)) {
kvm_err("%s: invalid target cpu: %d\n", __func__, cpu);
return;
}
ipi_set(vcpu, BIT(data & 0x1f));
}
static void ipi_clear(struct kvm_vcpu *vcpu, uint64_t data)
{
uint32_t status;
struct kvm_interrupt irq;
spin_lock(&vcpu->arch.ipi_state.lock);
vcpu->arch.ipi_state.status &= ~data;
status = vcpu->arch.ipi_state.status;
spin_unlock(&vcpu->arch.ipi_state.lock);
if (status == 0) {
irq.irq = -LARCH_INT_IPI;
kvm_vcpu_ioctl_interrupt(vcpu, &irq);
}
}
static uint64_t read_mailbox(struct kvm_vcpu *vcpu, int offset, int len)
{
uint64_t data = 0;
spin_lock(&vcpu->arch.ipi_state.lock);
data = *(ulong *)((void *)vcpu->arch.ipi_state.buf + (offset - 0x20));
spin_unlock(&vcpu->arch.ipi_state.lock);
switch (len) {
case 1:
return data & 0xff;
case 2:
return data & 0xffff;
case 4:
return data & 0xffffffff;
case 8:
return data;
default:
kvm_err("%s: unknown data len: %d\n", __func__, len);
return 0;
}
}
static void write_mailbox(struct kvm_vcpu *vcpu, int offset, uint64_t data, int len)
{
void *pbuf;
spin_lock(&vcpu->arch.ipi_state.lock);
pbuf = (void *)vcpu->arch.ipi_state.buf + (offset - 0x20);
switch (len) {
case 1:
*(unsigned char *)pbuf = (unsigned char)data;
break;
case 2:
*(unsigned short *)pbuf = (unsigned short)data;
break;
case 4:
*(unsigned int *)pbuf = (unsigned int)data;
break;
case 8:
*(unsigned long *)pbuf = (unsigned long)data;
break;
default:
kvm_err("%s: unknown data len: %d\n", __func__, len);
}
spin_unlock(&vcpu->arch.ipi_state.lock);
}
static int mail_send(struct kvm *kvm, uint64_t data)
{
int i, cpu, mailbox, offset;
uint32_t val = 0, mask = 0;
struct kvm_vcpu *vcpu;
cpu = ((data & 0xffffffff) >> 16) & 0x3ff;
vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu);
if (unlikely(vcpu == NULL)) {
kvm_err("%s: invalid target cpu: %d\n", __func__, cpu);
return 0;
}
mailbox = ((data & 0xffffffff) >> 2) & 0x7;
offset = IOCSR_IPI_BUF_20 + mailbox * 4;
if ((data >> 27) & 0xf) {
val = read_mailbox(vcpu, offset, 4);
for (i = 0; i < 4; i++)
if (data & (BIT(27 + i)))
mask |= (0xff << (i * 8));
val &= mask;
}
val |= ((uint32_t)(data >> 32) & ~mask);
write_mailbox(vcpu, offset, val, 4);
return 0;
}
static int send_ipi_data(struct kvm_vcpu *vcpu, gpa_t addr, uint64_t data)
{
int i, idx, ret;
uint64_t val = 0, mask = 0;
/*
* Bit 27-30 is mask for byte writing.
* If the mask is 0, we need not to do anything.
*/
if ((data >> 27) & 0xf) {
/* Read the old val */
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_read(vcpu, KVM_IOCSR_BUS, addr, 4, &val);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (unlikely(ret)) {
kvm_err("%s: : read data from addr %llx failed\n", __func__, addr);
return 0;
}
/* Construct the mask by scanning the bit 27-30 */
for (i = 0; i < 4; i++) {
if (data & (BIT(27 + i)))
mask |= (0xff << (i * 8));
}
/* Save the old part of val */
val &= mask;
}
val |= ((uint32_t)(data >> 32) & ~mask);
idx = srcu_read_lock(&vcpu->kvm->srcu);
ret = kvm_io_bus_write(vcpu, KVM_IOCSR_BUS, addr, 4, &val);
srcu_read_unlock(&vcpu->kvm->srcu, idx);
if (unlikely(ret))
kvm_err("%s: : write data to addr %llx failed\n", __func__, addr);
return 0;
}
static int any_send(struct kvm *kvm, uint64_t data)
{
int cpu, offset;
struct kvm_vcpu *vcpu;
cpu = ((data & 0xffffffff) >> 16) & 0x3ff;
vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu);
if (unlikely(vcpu == NULL)) {
kvm_err("%s: invalid target cpu: %d\n", __func__, cpu);
return 0;
}
offset = data & 0xffff;
return send_ipi_data(vcpu, offset, data);
}
static int loongarch_ipi_readl(struct kvm_vcpu *vcpu, gpa_t addr, int len, void *val)
{
uint32_t offset;
uint64_t res = 0;
offset = (uint32_t)(addr & 0x1ff);
WARN_ON_ONCE(offset & (len - 1));
switch (offset) {
case IOCSR_IPI_STATUS:
spin_lock(&vcpu->arch.ipi_state.lock);
res = vcpu->arch.ipi_state.status;
spin_unlock(&vcpu->arch.ipi_state.lock);
break;
case IOCSR_IPI_EN:
spin_lock(&vcpu->arch.ipi_state.lock);
res = vcpu->arch.ipi_state.en;
spin_unlock(&vcpu->arch.ipi_state.lock);
break;
case IOCSR_IPI_SET:
case IOCSR_IPI_CLEAR:
break;
case IOCSR_IPI_BUF_20 ... IOCSR_IPI_BUF_38 + 7:
if (offset + len > IOCSR_IPI_BUF_38 + 8) {
kvm_err("%s: invalid offset or len: offset = %d, len = %d\n",
__func__, offset, len);
break;
}
res = read_mailbox(vcpu, offset, len);
break;
default:
kvm_err("%s: unknown addr: %llx\n", __func__, addr);
break;
}
*(uint64_t *)val = res;
return 0;
}
static int loongarch_ipi_writel(struct kvm_vcpu *vcpu, gpa_t addr, int len, const void *val)
{
uint64_t data;
uint32_t offset;
data = *(uint64_t *)val;
offset = (uint32_t)(addr & 0x1ff);
WARN_ON_ONCE(offset & (len - 1));
switch (offset) {
case IOCSR_IPI_STATUS:
break;
case IOCSR_IPI_EN:
spin_lock(&vcpu->arch.ipi_state.lock);
vcpu->arch.ipi_state.en = data;
spin_unlock(&vcpu->arch.ipi_state.lock);
break;
case IOCSR_IPI_SET:
ipi_set(vcpu, data);
break;
case IOCSR_IPI_CLEAR:
/* Just clear the status of the current vcpu */
ipi_clear(vcpu, data);
break;
case IOCSR_IPI_BUF_20 ... IOCSR_IPI_BUF_38 + 7:
if (offset + len > IOCSR_IPI_BUF_38 + 8) {
kvm_err("%s: invalid offset or len: offset = %d, len = %d\n",
__func__, offset, len);
break;
}
write_mailbox(vcpu, offset, data, len);
break;
case IOCSR_IPI_SEND:
ipi_send(vcpu->kvm, data);
break;
case IOCSR_MAIL_SEND:
mail_send(vcpu->kvm, data);
break;
case IOCSR_ANY_SEND:
any_send(vcpu->kvm, data);
break;
default:
kvm_err("%s: unknown addr: %llx\n", __func__, addr);
break;
}
return 0;
}
static int kvm_ipi_read(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, void *val)
{
vcpu->stat.ipi_read_exits++;
return loongarch_ipi_readl(vcpu, addr, len, val);
}
static int kvm_ipi_write(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
vcpu->stat.ipi_write_exits++;
return loongarch_ipi_writel(vcpu, addr, len, val);
}
static const struct kvm_io_device_ops kvm_ipi_ops = {
.read = kvm_ipi_read,
.write = kvm_ipi_write,
};
static int kvm_ipi_regs_access(struct kvm_device *dev,
struct kvm_device_attr *attr,
bool is_write)
{
int len = 4;
int cpu, addr;
uint64_t val;
void *p = NULL;
struct kvm_vcpu *vcpu;
cpu = (attr->attr >> 16) & 0x3ff;
addr = attr->attr & 0xff;
vcpu = kvm_get_vcpu_by_id(dev->kvm, cpu);
if (unlikely(vcpu == NULL)) {
kvm_err("%s: invalid target cpu: %d\n", __func__, cpu);
return -EINVAL;
}
switch (addr) {
case IOCSR_IPI_STATUS:
p = &vcpu->arch.ipi_state.status;
break;
case IOCSR_IPI_EN:
p = &vcpu->arch.ipi_state.en;
break;
case IOCSR_IPI_SET:
p = &vcpu->arch.ipi_state.set;
break;
case IOCSR_IPI_CLEAR:
p = &vcpu->arch.ipi_state.clear;
break;
case IOCSR_IPI_BUF_20:
p = &vcpu->arch.ipi_state.buf[0];
len = 8;
break;
case IOCSR_IPI_BUF_28:
p = &vcpu->arch.ipi_state.buf[1];
len = 8;
break;
case IOCSR_IPI_BUF_30:
p = &vcpu->arch.ipi_state.buf[2];
len = 8;
break;
case IOCSR_IPI_BUF_38:
p = &vcpu->arch.ipi_state.buf[3];
len = 8;
break;
default:
kvm_err("%s: unknown ipi register, addr = %d\n", __func__, addr);
return -EINVAL;
}
if (is_write) {
if (len == 4) {
if (get_user(val, (uint32_t __user *)attr->addr))
return -EFAULT;
*(uint32_t *)p = (uint32_t)val;
} else if (len == 8) {
if (get_user(val, (uint64_t __user *)attr->addr))
return -EFAULT;
*(uint64_t *)p = val;
}
} else {
if (len == 4) {
val = *(uint32_t *)p;
return put_user(val, (uint32_t __user *)attr->addr);
} else if (len == 8) {
val = *(uint64_t *)p;
return put_user(val, (uint64_t __user *)attr->addr);
}
}
return 0;
}
static int kvm_ipi_get_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
switch (attr->group) {
case KVM_DEV_LOONGARCH_IPI_GRP_REGS:
return kvm_ipi_regs_access(dev, attr, false);
default:
kvm_err("%s: unknown group (%d)\n", __func__, attr->group);
return -EINVAL;
}
}
static int kvm_ipi_set_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
switch (attr->group) {
case KVM_DEV_LOONGARCH_IPI_GRP_REGS:
return kvm_ipi_regs_access(dev, attr, true);
default:
kvm_err("%s: unknown group (%d)\n", __func__, attr->group);
return -EINVAL;
}
}
static int kvm_ipi_create(struct kvm_device *dev, u32 type)
{
int ret;
struct kvm *kvm;
struct kvm_io_device *device;
struct loongarch_ipi *s;
if (!dev) {
kvm_err("%s: kvm_device ptr is invalid!\n", __func__);
return -EINVAL;
}
kvm = dev->kvm;
if (kvm->arch.ipi) {
kvm_err("%s: LoongArch IPI has already been created!\n", __func__);
return -EINVAL;
}
s = kzalloc_obj(struct loongarch_ipi);
if (!s)
return -ENOMEM;
spin_lock_init(&s->lock);
s->kvm = kvm;
/*
* Initialize IOCSR device
*/
device = &s->device;
kvm_iodevice_init(device, &kvm_ipi_ops);
mutex_lock(&kvm->slots_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_IOCSR_BUS, IOCSR_IPI_BASE, IOCSR_IPI_SIZE, device);
mutex_unlock(&kvm->slots_lock);
if (ret < 0) {
kvm_err("%s: Initialize IOCSR dev failed, ret = %d\n", __func__, ret);
goto err;
}
kvm->arch.ipi = s;
return 0;
err:
kfree(s);
return -EFAULT;
}
static void kvm_ipi_destroy(struct kvm_device *dev)
{
struct kvm *kvm;
struct loongarch_ipi *ipi;
if (!dev || !dev->kvm || !dev->kvm->arch.ipi)
return;
kvm = dev->kvm;
ipi = kvm->arch.ipi;
mutex_lock(&kvm->slots_lock);
kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &ipi->device);
mutex_unlock(&kvm->slots_lock);
kfree(ipi);
kfree(dev);
}
static struct kvm_device_ops kvm_ipi_dev_ops = {
.name = "kvm-loongarch-ipi",
.create = kvm_ipi_create,
.destroy = kvm_ipi_destroy,
.set_attr = kvm_ipi_set_attr,
.get_attr = kvm_ipi_get_attr,
};
int kvm_loongarch_register_ipi_device(void)
{
return kvm_register_device_ops(&kvm_ipi_dev_ops, KVM_DEV_TYPE_LOONGARCH_IPI);
}
@@ -0,0 +1,502 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Loongson Technology Corporation Limited
*/
#include <asm/kvm_dmsintc.h>
#include <asm/kvm_eiointc.h>
#include <asm/kvm_pch_pic.h>
#include <asm/kvm_vcpu.h>
#include <linux/count_zeros.h>
/* update the isr according to irq level and route irq to eiointc */
static void pch_pic_update_irq(struct loongarch_pch_pic *s, int irq, int level)
{
u64 mask = BIT(irq);
/*
* set isr and route irq to eiointc and
* the route table is in htmsi_vector[]
*/
if (level) {
if (mask & s->irr & ~s->mask) {
s->isr |= mask;
irq = s->htmsi_vector[irq];
eiointc_set_irq(s->kvm->arch.eiointc, irq, level);
}
} else {
if (mask & s->isr & ~s->irr) {
s->isr &= ~mask;
irq = s->htmsi_vector[irq];
eiointc_set_irq(s->kvm->arch.eiointc, irq, level);
}
}
}
/* update batch irqs, the irq_mask is a bitmap of irqs */
static void pch_pic_update_batch_irqs(struct loongarch_pch_pic *s, u64 irq_mask, int level)
{
unsigned int irq;
DECLARE_BITMAP(irqs, 64) = { BITMAP_FROM_U64(irq_mask) };
for_each_set_bit(irq, irqs, 64)
pch_pic_update_irq(s, irq, level);
}
/* called when a irq is triggered in pch pic */
void pch_pic_set_irq(struct loongarch_pch_pic *s, int irq, int level)
{
u64 mask = BIT(irq);
spin_lock(&s->lock);
if (level)
s->irr |= mask; /* set irr */
else {
/*
* In edge triggered mode, 0 does not mean to clear irq
* The irr register variable is cleared when cpu writes to the
* PCH_PIC_CLEAR_START address area
*/
if (s->edge & mask) {
spin_unlock(&s->lock);
return;
}
s->irr &= ~mask;
}
pch_pic_update_irq(s, irq, level);
spin_unlock(&s->lock);
}
/* msi irq handler */
int pch_msi_set_irq(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *e, int level)
{
u64 msg_addr = (((u64)e->msi.address_hi) << 32) | e->msi.address_lo;
if (cpu_has_msgint && kvm->arch.dmsintc &&
msg_addr >= kvm->arch.dmsintc->msg_addr_base &&
msg_addr < (kvm->arch.dmsintc->msg_addr_base + kvm->arch.dmsintc->msg_addr_size)) {
return dmsintc_set_irq(kvm, msg_addr, e->msi.data, level);
}
eiointc_set_irq(kvm->arch.eiointc, e->msi.data, level);
return 0;
}
static int loongarch_pch_pic_read(struct loongarch_pch_pic *s, gpa_t addr, int len, void *val)
{
int offset;
u64 data = 0;
void *ptemp;
offset = addr - s->pch_pic_base;
offset -= offset & 7;
spin_lock(&s->lock);
switch (offset) {
case PCH_PIC_INT_ID_START ... PCH_PIC_INT_ID_END:
data = s->id.data;
break;
case PCH_PIC_MASK_START ... PCH_PIC_MASK_END:
data = s->mask;
break;
case PCH_PIC_HTMSI_EN_START ... PCH_PIC_HTMSI_EN_END:
/* read htmsi enable reg */
data = s->htmsi_en;
break;
case PCH_PIC_EDGE_START ... PCH_PIC_EDGE_END:
/* read edge enable reg */
data = s->edge;
break;
case PCH_PIC_AUTO_CTRL0_START ... PCH_PIC_AUTO_CTRL0_END:
case PCH_PIC_AUTO_CTRL1_START ... PCH_PIC_AUTO_CTRL1_END:
/* we only use default mode: fixed interrupt distribution mode */
break;
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
/* only route to int0: eiointc */
ptemp = s->route_entry + (offset - PCH_PIC_ROUTE_ENTRY_START);
data = *(u64 *)ptemp;
break;
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
/* read htmsi vector */
ptemp = s->htmsi_vector + (offset - PCH_PIC_HTMSI_VEC_START);
data = *(u64 *)ptemp;
break;
case PCH_PIC_POLARITY_START ... PCH_PIC_POLARITY_END:
data = s->polarity;
break;
case PCH_PIC_INT_IRR_START:
data = s->irr;
break;
case PCH_PIC_INT_ISR_START:
data = s->isr;
break;
default:
break;
}
spin_unlock(&s->lock);
offset = (addr - s->pch_pic_base) & 7;
data = data >> (offset * 8);
memcpy(val, &data, len);
return 0;
}
static int kvm_pch_pic_read(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, void *val)
{
int ret = 0;
struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
if (!s) {
kvm_err("%s: pch pic irqchip not valid!\n", __func__);
return ret;
}
if (addr & (len - 1)) {
kvm_err("%s: pch pic not aligned addr %llx len %d\n", __func__, addr, len);
return ret;
}
/* statistics of pch pic reading */
vcpu->stat.pch_pic_read_exits++;
ret = loongarch_pch_pic_read(s, addr, len, val);
return ret;
}
static int loongarch_pch_pic_write(struct loongarch_pch_pic *s, gpa_t addr,
int len, const void *val)
{
int offset;
u64 old, data, mask;
void *ptemp;
switch (len) {
case 1:
data = *(u8 *)val;
mask = 0xFF;
break;
case 2:
data = *(u16 *)val;
mask = USHRT_MAX;
break;
case 4:
data = *(u32 *)val;
mask = UINT_MAX;
break;
case 8:
default:
data = *(u64 *)val;
mask = ULONG_MAX;
break;
}
offset = (addr - s->pch_pic_base) & 7;
mask = mask << (offset * 8);
data = data << (offset * 8);
offset = (addr - s->pch_pic_base) - offset;
spin_lock(&s->lock);
switch (offset) {
case PCH_PIC_MASK_START:
old = s->mask;
s->mask = (old & ~mask) | data;
if (old & ~data)
pch_pic_update_batch_irqs(s, old & ~data, 1);
if (~old & data)
pch_pic_update_batch_irqs(s, ~old & data, 0);
break;
case PCH_PIC_HTMSI_EN_START:
s->htmsi_en = (s->htmsi_en & ~mask) | data;
break;
case PCH_PIC_EDGE_START:
s->edge = (s->edge & ~mask) | data;
break;
case PCH_PIC_POLARITY_START:
s->polarity = (s->polarity & ~mask) | data;
break;
case PCH_PIC_CLEAR_START:
old = s->irr & s->edge & data;
if (old) {
s->irr &= ~old;
pch_pic_update_batch_irqs(s, old, 0);
}
break;
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
ptemp = s->htmsi_vector + (offset - PCH_PIC_HTMSI_VEC_START);
*(u64 *)ptemp = (*(u64 *)ptemp & ~mask) | data;
break;
/* Not implemented */
case PCH_PIC_AUTO_CTRL0_START:
case PCH_PIC_AUTO_CTRL1_START:
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
break;
default:
break;
}
spin_unlock(&s->lock);
return 0;
}
static int kvm_pch_pic_write(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
int ret = 0;
struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
if (!s) {
kvm_err("%s: pch pic irqchip not valid!\n", __func__);
return ret;
}
if (addr & (len - 1)) {
kvm_err("%s: pch pic not aligned addr %llx len %d\n", __func__, addr, len);
return ret;
}
/* statistics of pch pic writing */
vcpu->stat.pch_pic_write_exits++;
ret = loongarch_pch_pic_write(s, addr, len, val);
return ret;
}
static const struct kvm_io_device_ops kvm_pch_pic_ops = {
.read = kvm_pch_pic_read,
.write = kvm_pch_pic_write,
};
static int kvm_pch_pic_init(struct kvm_device *dev, u64 addr)
{
int ret;
struct kvm *kvm = dev->kvm;
struct kvm_io_device *device;
struct loongarch_pch_pic *s = dev->kvm->arch.pch_pic;
s->pch_pic_base = addr;
device = &s->device;
/* init device by pch pic writing and reading ops */
kvm_iodevice_init(device, &kvm_pch_pic_ops);
mutex_lock(&kvm->slots_lock);
/* register pch pic device */
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, addr, PCH_PIC_SIZE, device);
mutex_unlock(&kvm->slots_lock);
return (ret < 0) ? -EFAULT : 0;
}
/* used by user space to get or set pch pic registers */
static int kvm_pch_pic_regs_access(struct kvm_device *dev,
struct kvm_device_attr *attr,
bool is_write)
{
char buf[8];
int addr, offset, len = 8, ret = 0;
void __user *data;
void *p = NULL;
struct loongarch_pch_pic *s;
s = dev->kvm->arch.pch_pic;
addr = attr->attr;
data = (void __user *)attr->addr;
/* get pointer to pch pic register by addr */
switch (addr) {
case PCH_PIC_MASK_START:
p = &s->mask;
break;
case PCH_PIC_HTMSI_EN_START:
p = &s->htmsi_en;
break;
case PCH_PIC_EDGE_START:
p = &s->edge;
break;
case PCH_PIC_AUTO_CTRL0_START:
p = &s->auto_ctrl0;
break;
case PCH_PIC_AUTO_CTRL1_START:
p = &s->auto_ctrl1;
break;
case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
offset = addr - PCH_PIC_ROUTE_ENTRY_START;
p = &s->route_entry[offset];
len = 1;
break;
case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
offset = addr - PCH_PIC_HTMSI_VEC_START;
p = &s->htmsi_vector[offset];
len = 1;
break;
case PCH_PIC_INT_IRR_START:
p = &s->irr;
break;
case PCH_PIC_INT_ISR_START:
p = &s->isr;
break;
case PCH_PIC_POLARITY_START:
p = &s->polarity;
break;
default:
return -EINVAL;
}
if (is_write) {
if (copy_from_user(buf, data, len))
return -EFAULT;
}
spin_lock(&s->lock);
if (is_write)
memcpy(p, buf, len);
else
memcpy(buf, p, len);
spin_unlock(&s->lock);
if (!is_write) {
if (copy_to_user(data, buf, len))
return -EFAULT;
}
return ret;
}
static int kvm_pch_pic_get_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
switch (attr->group) {
case KVM_DEV_LOONGARCH_PCH_PIC_GRP_REGS:
return kvm_pch_pic_regs_access(dev, attr, false);
default:
return -EINVAL;
}
}
static int kvm_pch_pic_set_attr(struct kvm_device *dev,
struct kvm_device_attr *attr)
{
u64 addr;
void __user *uaddr = (void __user *)(long)attr->addr;
switch (attr->group) {
case KVM_DEV_LOONGARCH_PCH_PIC_GRP_CTRL:
switch (attr->attr) {
case KVM_DEV_LOONGARCH_PCH_PIC_CTRL_INIT:
if (copy_from_user(&addr, uaddr, sizeof(addr)))
return -EFAULT;
if (!dev->kvm->arch.pch_pic) {
kvm_err("%s: please create pch_pic irqchip first!\n", __func__);
return -ENODEV;
}
return kvm_pch_pic_init(dev, addr);
default:
kvm_err("%s: unknown group (%d) attr (%lld)\n", __func__, attr->group,
attr->attr);
return -EINVAL;
}
case KVM_DEV_LOONGARCH_PCH_PIC_GRP_REGS:
return kvm_pch_pic_regs_access(dev, attr, true);
default:
return -EINVAL;
}
}
static int kvm_setup_default_irq_routing(struct kvm *kvm)
{
int i, ret;
u32 nr = KVM_IRQCHIP_NUM_PINS;
struct kvm_irq_routing_entry *entries;
entries = kzalloc_objs(*entries, nr);
if (!entries)
return -ENOMEM;
for (i = 0; i < nr; i++) {
entries[i].gsi = i;
entries[i].type = KVM_IRQ_ROUTING_IRQCHIP;
entries[i].u.irqchip.irqchip = 0;
entries[i].u.irqchip.pin = i;
}
ret = kvm_set_irq_routing(kvm, entries, nr, 0);
kfree(entries);
return ret;
}
static int kvm_pch_pic_create(struct kvm_device *dev, u32 type)
{
int i, ret, irq_num;
struct kvm *kvm = dev->kvm;
struct loongarch_pch_pic *s;
/* pch pic should not has been created */
if (kvm->arch.pch_pic)
return -EINVAL;
ret = kvm_setup_default_irq_routing(kvm);
if (ret)
return -ENOMEM;
s = kzalloc_obj(struct loongarch_pch_pic);
if (!s)
return -ENOMEM;
/*
* Interrupt controller identification register 1
* Bit 24-31 Interrupt Controller ID
* Interrupt controller identification register 2
* Bit 0-7 Interrupt Controller version number
* Bit 16-23 The number of interrupt sources supported
*/
irq_num = 32;
s->mask = -1UL;
s->id.desc.id = PCH_PIC_INT_ID_VAL;
s->id.desc.version = PCH_PIC_INT_ID_VER;
s->id.desc.irq_num = irq_num - 1;
for (i = 0; i < irq_num; i++) {
s->route_entry[i] = 1;
s->htmsi_vector[i] = i;
}
spin_lock_init(&s->lock);
s->kvm = kvm;
kvm->arch.pch_pic = s;
return 0;
}
static void kvm_pch_pic_destroy(struct kvm_device *dev)
{
struct kvm *kvm;
struct loongarch_pch_pic *s;
if (!dev || !dev->kvm || !dev->kvm->arch.pch_pic)
return;
kvm = dev->kvm;
s = kvm->arch.pch_pic;
/* unregister pch pic device and free it's memory */
mutex_lock(&kvm->slots_lock);
kvm_io_bus_unregister_dev(kvm, KVM_MMIO_BUS, &s->device);
mutex_unlock(&kvm->slots_lock);
kfree(s);
kfree(dev);
}
static struct kvm_device_ops kvm_pch_pic_dev_ops = {
.name = "kvm-loongarch-pch-pic",
.create = kvm_pch_pic_create,
.destroy = kvm_pch_pic_destroy,
.set_attr = kvm_pch_pic_set_attr,
.get_attr = kvm_pch_pic_get_attr,
};
int kvm_loongarch_register_pch_pic_device(void)
{
return kvm_register_device_ops(&kvm_pch_pic_dev_ops, KVM_DEV_TYPE_LOONGARCH_PCHPIC);
}
+147
View File
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/err.h>
#include <linux/errno.h>
#include <asm/kvm_csr.h>
#include <asm/kvm_vcpu.h>
#include <asm/kvm_dmsintc.h>
static void kvm_irq_deliver(struct kvm_vcpu *vcpu, unsigned long mask)
{
unsigned long irq;
unsigned long old, new;
if (mask & CPU_AVEC)
dmsintc_inject_irq(vcpu);
irq = mask & KVM_ESTAT_INTI_MASK;
if (irq) {
old = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
set_gcsr_estat(irq);
new = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
/* Inject TI if TVAL inverted */
if (new > old)
set_gcsr_estat(CPU_TIMER);
}
irq = (mask >> VIP_DELTA) & KVM_GINTC_IRQ_MASK;
if (irq)
set_csr_gintc(irq);
}
static void kvm_irq_clear(struct kvm_vcpu *vcpu, unsigned long mask)
{
unsigned long irq;
unsigned long old, new;
irq = mask & KVM_ESTAT_INTI_MASK;
if (irq) {
old = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
clear_gcsr_estat(irq);
new = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
/* Inject TI if TVAL inverted */
if (new > old)
set_gcsr_estat(CPU_TIMER);
}
irq = (mask >> VIP_DELTA) & KVM_GINTC_IRQ_MASK;
if (irq)
clear_csr_gintc(irq);
}
void kvm_deliver_intr(struct kvm_vcpu *vcpu)
{
unsigned long mask;
mask = READ_ONCE(vcpu->arch.irq_clear);
if (mask) {
mask = xchg_relaxed(&vcpu->arch.irq_clear, 0);
kvm_irq_clear(vcpu, mask);
}
mask = READ_ONCE(vcpu->arch.irq_pending);
if (mask) {
mask = xchg_relaxed(&vcpu->arch.irq_pending, 0);
kvm_irq_deliver(vcpu, mask);
}
}
int kvm_pending_timer(struct kvm_vcpu *vcpu)
{
return test_bit(INT_TI, &vcpu->arch.irq_pending);
}
/*
* Only support illegal instruction or illegal Address Error exception,
* Other exceptions are injected by hardware in kvm mode
*/
static void _kvm_deliver_exception(struct kvm_vcpu *vcpu,
unsigned int code, unsigned int subcode)
{
unsigned long val, vec_size;
/*
* BADV is added for EXCCODE_ADE exception
* Use PC register (GVA address) if it is instruction exeception
* Else use BADV from host side (GPA address) for data exeception
*/
if (code == EXCCODE_ADE) {
if (subcode == EXSUBCODE_ADEF)
val = vcpu->arch.pc;
else
val = vcpu->arch.badv;
kvm_write_hw_gcsr(LOONGARCH_CSR_BADV, val);
}
/* Set exception instruction */
kvm_write_hw_gcsr(LOONGARCH_CSR_BADI, vcpu->arch.badi);
/*
* Save CRMD in PRMD
* Set IRQ disabled and PLV0 with CRMD
*/
val = kvm_read_hw_gcsr(LOONGARCH_CSR_CRMD);
kvm_write_hw_gcsr(LOONGARCH_CSR_PRMD, val);
val = val & ~(CSR_CRMD_PLV | CSR_CRMD_IE);
kvm_write_hw_gcsr(LOONGARCH_CSR_CRMD, val);
/* Set exception PC address */
kvm_write_hw_gcsr(LOONGARCH_CSR_ERA, vcpu->arch.pc);
/*
* Set exception code
* Exception and interrupt can be inject at the same time
* Hardware will handle exception first and then extern interrupt
* Exception code is Ecode in ESTAT[16:21]
* Interrupt code in ESTAT[0:12]
*/
val = kvm_read_hw_gcsr(LOONGARCH_CSR_ESTAT);
val = (val & ~CSR_ESTAT_EXC) | code;
kvm_write_hw_gcsr(LOONGARCH_CSR_ESTAT, val);
/* Calculate expcetion entry address */
val = kvm_read_hw_gcsr(LOONGARCH_CSR_ECFG);
vec_size = (val & CSR_ECFG_VS) >> CSR_ECFG_VS_SHIFT;
if (vec_size)
vec_size = (1 << vec_size) * 4;
val = kvm_read_hw_gcsr(LOONGARCH_CSR_EENTRY);
vcpu->arch.pc = val + code * vec_size;
}
void kvm_deliver_exception(struct kvm_vcpu *vcpu)
{
unsigned int code;
unsigned long *pending = &vcpu->arch.exception_pending;
if (*pending) {
code = __ffs(*pending);
_kvm_deliver_exception(vcpu, code, vcpu->arch.esubcode);
*pending = 0;
vcpu->arch.esubcode = 0;
}
}
+90
View File
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 Loongson Technology Corporation Limited
*/
#include <linux/kvm_host.h>
#include <trace/events/kvm.h>
#include <asm/kvm_pch_pic.h>
static int kvm_set_pic_irq(struct kvm_kernel_irq_routing_entry *e,
struct kvm *kvm, int irq_source_id, int level, bool line_status)
{
/* PCH-PIC pin (0 ~ 64) <---> GSI (0 ~ 64) */
pch_pic_set_irq(kvm->arch.pch_pic, e->irqchip.pin, level);
return 0;
}
/*
* kvm_set_msi: inject the MSI corresponding to the
* MSI routing entry
*
* This is the entry point for irqfd MSI injection
* and userspace MSI injection.
*/
int kvm_set_msi(struct kvm_kernel_irq_routing_entry *e,
struct kvm *kvm, int irq_source_id, int level, bool line_status)
{
if (!level)
return -1;
return pch_msi_set_irq(kvm, e, level);
}
/*
* kvm_set_routing_entry: populate a kvm routing entry
* from a user routing entry
*
* @kvm: the VM this entry is applied to
* @e: kvm kernel routing entry handle
* @ue: user api routing entry handle
* return 0 on success, -EINVAL on errors.
*/
int kvm_set_routing_entry(struct kvm *kvm,
struct kvm_kernel_irq_routing_entry *e,
const struct kvm_irq_routing_entry *ue)
{
switch (ue->type) {
case KVM_IRQ_ROUTING_IRQCHIP:
e->set = kvm_set_pic_irq;
e->irqchip.irqchip = ue->u.irqchip.irqchip;
e->irqchip.pin = ue->u.irqchip.pin;
if (e->irqchip.pin >= KVM_IRQCHIP_NUM_PINS ||
e->irqchip.irqchip >= KVM_NR_IRQCHIPS)
return -EINVAL;
return 0;
case KVM_IRQ_ROUTING_MSI:
e->set = kvm_set_msi;
e->msi.address_lo = ue->u.msi.address_lo;
e->msi.address_hi = ue->u.msi.address_hi;
e->msi.data = ue->u.msi.data;
return 0;
default:
return -EINVAL;
}
}
int kvm_arch_set_irq_inatomic(struct kvm_kernel_irq_routing_entry *e,
struct kvm *kvm, int irq_source_id, int level, bool line_status)
{
if (!level)
return -EWOULDBLOCK;
switch (e->type) {
case KVM_IRQ_ROUTING_IRQCHIP:
pch_pic_set_irq(kvm->arch.pch_pic, e->irqchip.pin, level);
return 0;
case KVM_IRQ_ROUTING_MSI:
return pch_msi_set_irq(kvm, e, level);
default:
return -EWOULDBLOCK;
}
}
bool kvm_arch_intc_initialized(struct kvm *kvm)
{
return kvm_arch_irqchip_in_kernel(kvm);
}
+449
View File
@@ -0,0 +1,449 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/kvm_host.h>
#include <asm/cacheflush.h>
#include <asm/cpufeature.h>
#include <asm/kvm_csr.h>
#include <asm/kvm_eiointc.h>
#include <asm/kvm_pch_pic.h>
#include "trace.h"
unsigned long vpid_mask;
struct kvm_world_switch *kvm_loongarch_ops;
static int gcsr_flag[CSR_MAX_NUMS];
static struct kvm_context __percpu *vmcs;
int get_gcsr_flag(int csr)
{
if (csr < CSR_MAX_NUMS)
return gcsr_flag[csr];
return INVALID_GCSR;
}
static inline void set_gcsr_sw_flag(int csr)
{
if (csr < CSR_MAX_NUMS)
gcsr_flag[csr] |= SW_GCSR;
}
static inline void set_gcsr_hw_flag(int csr)
{
if (csr < CSR_MAX_NUMS)
gcsr_flag[csr] |= HW_GCSR;
}
/*
* The default value of gcsr_flag[CSR] is 0, and we use this
* function to set the flag to 1 (SW_GCSR) or 2 (HW_GCSR) if the
* gcsr is software or hardware. It will be used by get/set_gcsr,
* if gcsr_flag is HW we should use gcsrrd/gcsrwr to access it,
* else use software csr to emulate it.
*/
static void kvm_init_gcsr_flag(void)
{
set_gcsr_hw_flag(LOONGARCH_CSR_CRMD);
set_gcsr_hw_flag(LOONGARCH_CSR_PRMD);
set_gcsr_hw_flag(LOONGARCH_CSR_EUEN);
set_gcsr_hw_flag(LOONGARCH_CSR_MISC);
set_gcsr_hw_flag(LOONGARCH_CSR_ECFG);
set_gcsr_hw_flag(LOONGARCH_CSR_ESTAT);
set_gcsr_hw_flag(LOONGARCH_CSR_ERA);
set_gcsr_hw_flag(LOONGARCH_CSR_BADV);
set_gcsr_hw_flag(LOONGARCH_CSR_BADI);
set_gcsr_hw_flag(LOONGARCH_CSR_EENTRY);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBIDX);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBEHI);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBELO0);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBELO1);
set_gcsr_hw_flag(LOONGARCH_CSR_ASID);
set_gcsr_hw_flag(LOONGARCH_CSR_PGDL);
set_gcsr_hw_flag(LOONGARCH_CSR_PGDH);
set_gcsr_hw_flag(LOONGARCH_CSR_PGD);
set_gcsr_hw_flag(LOONGARCH_CSR_PWCTL0);
set_gcsr_hw_flag(LOONGARCH_CSR_PWCTL1);
set_gcsr_hw_flag(LOONGARCH_CSR_STLBPGSIZE);
set_gcsr_hw_flag(LOONGARCH_CSR_RVACFG);
set_gcsr_hw_flag(LOONGARCH_CSR_CPUID);
set_gcsr_hw_flag(LOONGARCH_CSR_PRCFG1);
set_gcsr_hw_flag(LOONGARCH_CSR_PRCFG2);
set_gcsr_hw_flag(LOONGARCH_CSR_PRCFG3);
set_gcsr_hw_flag(LOONGARCH_CSR_KS0);
set_gcsr_hw_flag(LOONGARCH_CSR_KS1);
set_gcsr_hw_flag(LOONGARCH_CSR_KS2);
set_gcsr_hw_flag(LOONGARCH_CSR_KS3);
set_gcsr_hw_flag(LOONGARCH_CSR_KS4);
set_gcsr_hw_flag(LOONGARCH_CSR_KS5);
set_gcsr_hw_flag(LOONGARCH_CSR_KS6);
set_gcsr_hw_flag(LOONGARCH_CSR_KS7);
set_gcsr_hw_flag(LOONGARCH_CSR_TMID);
set_gcsr_hw_flag(LOONGARCH_CSR_TCFG);
set_gcsr_hw_flag(LOONGARCH_CSR_TVAL);
set_gcsr_hw_flag(LOONGARCH_CSR_TINTCLR);
set_gcsr_hw_flag(LOONGARCH_CSR_CNTC);
set_gcsr_hw_flag(LOONGARCH_CSR_LLBCTL);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRENTRY);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRBADV);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRERA);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRSAVE);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRELO0);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRELO1);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBREHI);
set_gcsr_hw_flag(LOONGARCH_CSR_TLBRPRMD);
set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN0);
set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN1);
set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN2);
set_gcsr_hw_flag(LOONGARCH_CSR_DMWIN3);
set_gcsr_sw_flag(LOONGARCH_CSR_IMPCTL1);
set_gcsr_sw_flag(LOONGARCH_CSR_IMPCTL2);
set_gcsr_sw_flag(LOONGARCH_CSR_MERRCTL);
set_gcsr_sw_flag(LOONGARCH_CSR_MERRINFO1);
set_gcsr_sw_flag(LOONGARCH_CSR_MERRINFO2);
set_gcsr_sw_flag(LOONGARCH_CSR_MERRENTRY);
set_gcsr_sw_flag(LOONGARCH_CSR_MERRERA);
set_gcsr_sw_flag(LOONGARCH_CSR_MERRSAVE);
set_gcsr_sw_flag(LOONGARCH_CSR_CTAG);
set_gcsr_sw_flag(LOONGARCH_CSR_DEBUG);
set_gcsr_sw_flag(LOONGARCH_CSR_DERA);
set_gcsr_sw_flag(LOONGARCH_CSR_DESAVE);
set_gcsr_sw_flag(LOONGARCH_CSR_FWPC);
set_gcsr_sw_flag(LOONGARCH_CSR_FWPS);
set_gcsr_sw_flag(LOONGARCH_CSR_MWPC);
set_gcsr_sw_flag(LOONGARCH_CSR_MWPS);
set_gcsr_sw_flag(LOONGARCH_CSR_DB0ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB0MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB0CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB0ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB1ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB1MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB1CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB1ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB2ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB2MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB2CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB2ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB3ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB3MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB3CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB3ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB4ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB4MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB4CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB4ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB5ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB5MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB5CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB5ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB6ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB6MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB6CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB6ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_DB7ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_DB7MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_DB7CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_DB7ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB0ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB0MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB0CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB0ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB1ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB1MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB1CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB1ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB2ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB2MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB2CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB2ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB3ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB3MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB3CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB3ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB4ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB4MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB4CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB4ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB5ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB5MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB5CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB5ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB6ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB6MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB6CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB6ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_IB7ADDR);
set_gcsr_sw_flag(LOONGARCH_CSR_IB7MASK);
set_gcsr_sw_flag(LOONGARCH_CSR_IB7CTRL);
set_gcsr_sw_flag(LOONGARCH_CSR_IB7ASID);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL0);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR0);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL1);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR1);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL2);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR2);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCTRL3);
set_gcsr_sw_flag(LOONGARCH_CSR_PERFCNTR3);
if (cpu_has_msgint) {
set_gcsr_hw_flag(LOONGARCH_CSR_IPR);
set_gcsr_hw_flag(LOONGARCH_CSR_ISR0);
set_gcsr_hw_flag(LOONGARCH_CSR_ISR1);
set_gcsr_hw_flag(LOONGARCH_CSR_ISR2);
set_gcsr_hw_flag(LOONGARCH_CSR_ISR3);
}
}
static void kvm_update_vpid(struct kvm_vcpu *vcpu, int cpu)
{
unsigned long vpid;
struct kvm_context *context;
context = per_cpu_ptr(vcpu->kvm->arch.vmcs, cpu);
vpid = context->vpid_cache + 1;
if (!(vpid & vpid_mask)) {
/* finish round of vpid loop */
if (unlikely(!vpid))
vpid = vpid_mask + 1;
++vpid; /* vpid 0 reserved for root */
/* start new vpid cycle */
kvm_flush_tlb_all();
}
context->vpid_cache = vpid;
vcpu->arch.vpid = vpid;
}
void kvm_check_vpid(struct kvm_vcpu *vcpu)
{
int cpu;
bool migrated;
unsigned long ver, old, vpid;
struct kvm_context *context;
cpu = smp_processor_id();
/*
* Are we entering guest context on a different CPU to last time?
* If so, the vCPU's guest TLB state on this CPU may be stale.
*/
context = per_cpu_ptr(vcpu->kvm->arch.vmcs, cpu);
migrated = (vcpu->cpu != cpu);
/*
* Check if our vpid is of an older version
*
* We also discard the stored vpid if we've executed on
* another CPU, as the guest mappings may have changed without
* hypervisor knowledge.
*/
ver = vcpu->arch.vpid & ~vpid_mask;
old = context->vpid_cache & ~vpid_mask;
if (migrated || (ver != old)) {
kvm_update_vpid(vcpu, cpu);
trace_kvm_vpid_change(vcpu, vcpu->arch.vpid);
vcpu->cpu = cpu;
kvm_clear_request(KVM_REQ_TLB_FLUSH_GPA, vcpu);
/*
* LLBCTL is a separated guest CSR register from host, a general
* exception ERET instruction clears the host LLBCTL register in
* host mode, and clears the guest LLBCTL register in guest mode.
* ERET in tlb refill exception does not clear LLBCTL register.
*
* When secondary mmu mapping is changed, guest OS does not know
* even if the content is changed after mapping is changed.
*
* Here clear WCLLB of the guest LLBCTL register when mapping is
* changed. Otherwise, if mmu mapping is changed while guest is
* executing LL/SC pair, LL loads with the old address and set
* the LLBCTL flag, SC checks the LLBCTL flag and will store the
* new address successfully since LLBCTL_WCLLB is on, even if
* memory with new address is changed on other VCPUs.
*/
set_gcsr_llbctl(CSR_LLBCTL_WCLLB);
/* Restore GSTAT(0x50).vpid */
vpid = (vcpu->arch.vpid & vpid_mask) << CSR_GSTAT_GID_SHIFT;
change_csr_gstat(vpid_mask << CSR_GSTAT_GID_SHIFT, vpid);
}
}
void kvm_init_vmcs(struct kvm *kvm)
{
kvm->arch.vmcs = vmcs;
}
long kvm_arch_dev_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
return -ENOIOCTLCMD;
}
int kvm_arch_enable_virtualization_cpu(void)
{
unsigned long env, gcfg = 0;
env = read_csr_gcfg();
/* First init gcfg, gstat, gintc, gtlbc. All guest use the same config */
write_csr_gcfg(0);
write_csr_gstat(0);
write_csr_gintc(0);
clear_csr_gtlbc(CSR_GTLBC_USETGID | CSR_GTLBC_TOTI);
/*
* Enable virtualization features granting guest direct control of
* certain features:
* GCI=2: Trap on init or unimplemented cache instruction.
* TORU=0: Trap on Root Unimplement.
* CACTRL=1: Root control cache.
* TOP=0: Trap on Privilege.
* TOE=0: Trap on Exception.
* TIT=0: Trap on Timer.
*/
if (env & CSR_GCFG_GCIP_SECURE)
gcfg |= CSR_GCFG_GCI_SECURE;
if (env & CSR_GCFG_MATP_ROOT)
gcfg |= CSR_GCFG_MATC_ROOT;
write_csr_gcfg(gcfg);
kvm_flush_tlb_all();
/* Enable using TGID */
set_csr_gtlbc(CSR_GTLBC_USETGID);
kvm_debug("GCFG:%lx GSTAT:%lx GINTC:%lx GTLBC:%lx",
read_csr_gcfg(), read_csr_gstat(), read_csr_gintc(), read_csr_gtlbc());
/*
* HW Guest CSR registers are lost after CPU suspend and resume.
* Clear last_vcpu so that Guest CSR registers forced to reload
* from vCPU SW state.
*/
this_cpu_ptr(vmcs)->last_vcpu = NULL;
return 0;
}
void kvm_arch_disable_virtualization_cpu(void)
{
write_csr_gcfg(0);
write_csr_gstat(0);
write_csr_gintc(0);
clear_csr_gtlbc(CSR_GTLBC_USETGID | CSR_GTLBC_TOTI);
/* Flush any remaining guest TLB entries */
kvm_flush_tlb_all();
}
static int kvm_loongarch_env_init(void)
{
int cpu, ret;
struct kvm_context *context;
vmcs = alloc_percpu(struct kvm_context);
if (!vmcs) {
pr_err("kvm: failed to allocate percpu kvm_context\n");
return -ENOMEM;
}
kvm_loongarch_ops = kzalloc_obj(*kvm_loongarch_ops);
if (!kvm_loongarch_ops) {
free_percpu(vmcs);
vmcs = NULL;
return -ENOMEM;
}
kvm_loongarch_ops->exc_entry = (void *)kvm_exc_entry;
kvm_loongarch_ops->enter_guest = (void *)kvm_enter_guest;
vpid_mask = read_csr_gstat();
vpid_mask = (vpid_mask & CSR_GSTAT_GIDBIT) >> CSR_GSTAT_GIDBIT_SHIFT;
if (vpid_mask)
vpid_mask = GENMASK(vpid_mask - 1, 0);
for_each_possible_cpu(cpu) {
context = per_cpu_ptr(vmcs, cpu);
context->vpid_cache = vpid_mask + 1;
context->last_vcpu = NULL;
}
kvm_init_gcsr_flag();
kvm_register_perf_callbacks();
/* Register LoongArch IPI interrupt controller interface. */
ret = kvm_loongarch_register_ipi_device();
if (ret)
return ret;
/* Register LoongArch EIOINTC interrupt controller interface. */
ret = kvm_loongarch_register_eiointc_device();
if (ret)
return ret;
/* Register LoongArch PCH-PIC interrupt controller interface. */
ret = kvm_loongarch_register_pch_pic_device();
if (ret)
return ret;
/* Register LoongArch DMSINTC interrupt contrroller interface */
if (cpu_has_msgint)
ret = kvm_loongarch_register_dmsintc_device();
return ret;
}
static void kvm_loongarch_env_exit(void)
{
if (vmcs)
free_percpu(vmcs);
if (kvm_loongarch_ops) {
kfree(kvm_loongarch_ops);
}
kvm_unregister_perf_callbacks();
}
static int kvm_loongarch_init(void)
{
int r;
if (!cpu_has_lvz) {
kvm_info("Hardware virtualization not available\n");
return -ENODEV;
}
r = kvm_loongarch_env_init();
if (r)
return r;
return kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
}
static void kvm_loongarch_exit(void)
{
kvm_exit();
kvm_loongarch_env_exit();
}
module_init(kvm_loongarch_init);
module_exit(kvm_loongarch_exit);
#ifdef MODULE
static const struct cpu_feature kvm_feature[] = {
{ .feature = cpu_feature(LOONGARCH_LVZ) },
{},
};
MODULE_DEVICE_TABLE(cpu, kvm_feature);
#endif
+947
View File
@@ -0,0 +1,947 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/highmem.h>
#include <linux/hugetlb.h>
#include <linux/kvm_host.h>
#include <linux/page-flags.h>
#include <linux/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/pgalloc.h>
#include <asm/tlb.h>
#include <asm/kvm_mmu.h>
static inline bool kvm_hugepage_capable(struct kvm_memory_slot *slot)
{
return slot->arch.flags & KVM_MEM_HUGEPAGE_CAPABLE;
}
static inline bool kvm_hugepage_incapable(struct kvm_memory_slot *slot)
{
return slot->arch.flags & KVM_MEM_HUGEPAGE_INCAPABLE;
}
static inline void kvm_ptw_prepare(struct kvm *kvm, kvm_ptw_ctx *ctx)
{
ctx->level = kvm->arch.root_level;
/* pte table */
ctx->invalid_ptes = kvm->arch.invalid_ptes;
ctx->pte_shifts = kvm->arch.pte_shifts;
ctx->pgtable_shift = ctx->pte_shifts[ctx->level];
ctx->invalid_entry = ctx->invalid_ptes[ctx->level];
ctx->opaque = kvm;
}
/*
* Mark a range of guest physical address space old (all accesses fault) in the
* VM's GPA page table to allow detection of commonly used pages.
*/
static int kvm_mkold_pte(kvm_pte_t *pte, phys_addr_t addr, kvm_ptw_ctx *ctx)
{
if (kvm_pte_young(*pte)) {
*pte = kvm_pte_mkold(*pte);
return 1;
}
return 0;
}
/*
* Mark a range of guest physical address space clean (writes fault) in the VM's
* GPA page table to allow dirty page tracking.
*/
static int kvm_mkclean_pte(kvm_pte_t *pte, phys_addr_t addr, kvm_ptw_ctx *ctx)
{
gfn_t offset;
kvm_pte_t val;
val = *pte;
/*
* For kvm_arch_mmu_enable_log_dirty_pt_masked with mask, start and end
* may cross hugepage, for first huge page parameter addr is equal to
* start, however for the second huge page addr is base address of
* this huge page, rather than start or end address
*/
if ((ctx->flag & _KVM_HAS_PGMASK) && !kvm_pte_huge(val)) {
offset = (addr >> PAGE_SHIFT) - ctx->gfn;
if (!(BIT(offset) & ctx->mask))
return 0;
}
/*
* Need not split huge page now, just set write-proect pte bit
* Split huge page until next write fault
*/
if (kvm_pte_dirty(val)) {
*pte = kvm_pte_mkclean(val);
return 1;
}
return 0;
}
/*
* Clear pte entry
*/
static int kvm_flush_pte(kvm_pte_t *pte, phys_addr_t addr, kvm_ptw_ctx *ctx)
{
struct kvm *kvm;
kvm = ctx->opaque;
if (ctx->level)
kvm->stat.hugepages--;
else
kvm->stat.pages--;
kvm_set_pte(pte, ctx->invalid_entry);
return 1;
}
/*
* kvm_pgd_alloc() - Allocate and initialise a KVM GPA page directory.
*
* Allocate a blank KVM GPA page directory (PGD) for representing guest physical
* to host physical page mappings.
*
* Returns: Pointer to new KVM GPA page directory.
* NULL on allocation failure.
*/
kvm_pte_t *kvm_pgd_alloc(void)
{
kvm_pte_t *pgd;
pgd = (kvm_pte_t *)__get_free_pages(GFP_KERNEL, 0);
if (pgd)
pgd_init((void *)pgd);
return pgd;
}
static void _kvm_pte_init(void *addr, unsigned long val)
{
unsigned long *p, *end;
p = (unsigned long *)addr;
end = p + PTRS_PER_PTE;
do {
p[0] = val;
p[1] = val;
p[2] = val;
p[3] = val;
p[4] = val;
p += 8;
p[-3] = val;
p[-2] = val;
p[-1] = val;
} while (p != end);
}
/*
* Caller must hold kvm->mm_lock
*
* Walk the page tables of kvm to find the PTE corresponding to the
* address @addr. If page tables don't exist for @addr, they will be created
* from the MMU cache if @cache is not NULL.
*/
static kvm_pte_t *kvm_populate_gpa(struct kvm *kvm,
struct kvm_mmu_memory_cache *cache,
unsigned long addr, int level)
{
kvm_ptw_ctx ctx;
kvm_pte_t *entry, *child;
kvm_ptw_prepare(kvm, &ctx);
child = kvm->arch.pgd;
while (ctx.level > level) {
entry = kvm_pgtable_offset(&ctx, child, addr);
if (kvm_pte_none(&ctx, entry)) {
if (!cache)
return NULL;
child = kvm_mmu_memory_cache_alloc(cache);
_kvm_pte_init(child, ctx.invalid_ptes[ctx.level - 1]);
smp_wmb(); /* Make pte visible before pmd */
kvm_set_pte(entry, __pa(child));
} else if (kvm_pte_huge(*entry)) {
return entry;
} else
child = (kvm_pte_t *)__va(PHYSADDR(*entry));
kvm_ptw_enter(&ctx);
}
entry = kvm_pgtable_offset(&ctx, child, addr);
return entry;
}
/*
* Page walker for VM shadow mmu at last level
* The last level is small pte page or huge pmd page
*/
static int kvm_ptw_leaf(kvm_pte_t *dir, phys_addr_t addr, phys_addr_t end, kvm_ptw_ctx *ctx)
{
int ret;
phys_addr_t next, start, size;
struct list_head *list;
kvm_pte_t *entry, *child;
ret = 0;
start = addr;
child = (kvm_pte_t *)__va(PHYSADDR(*dir));
entry = kvm_pgtable_offset(ctx, child, addr);
do {
next = addr + (0x1UL << ctx->pgtable_shift);
if (!kvm_pte_present(ctx, entry))
continue;
ret |= ctx->ops(entry, addr, ctx);
} while (entry++, addr = next, addr < end);
if (kvm_need_flush(ctx)) {
size = 0x1UL << (ctx->pgtable_shift + PAGE_SHIFT - 3);
if (start + size == end) {
list = (struct list_head *)child;
list_add_tail(list, &ctx->list);
*dir = ctx->invalid_ptes[ctx->level + 1];
}
}
return ret;
}
/*
* Page walker for VM shadow mmu at page table dir level
*/
static int kvm_ptw_dir(kvm_pte_t *dir, phys_addr_t addr, phys_addr_t end, kvm_ptw_ctx *ctx)
{
int ret;
phys_addr_t next, start, size;
struct list_head *list;
kvm_pte_t *entry, *child;
ret = 0;
start = addr;
child = (kvm_pte_t *)__va(PHYSADDR(*dir));
entry = kvm_pgtable_offset(ctx, child, addr);
do {
next = kvm_pgtable_addr_end(ctx, addr, end);
if (!kvm_pte_present(ctx, entry))
continue;
if (kvm_pte_huge(*entry)) {
ret |= ctx->ops(entry, addr, ctx);
continue;
}
kvm_ptw_enter(ctx);
if (ctx->level == 0)
ret |= kvm_ptw_leaf(entry, addr, next, ctx);
else
ret |= kvm_ptw_dir(entry, addr, next, ctx);
kvm_ptw_exit(ctx);
} while (entry++, addr = next, addr < end);
if (kvm_need_flush(ctx)) {
size = 0x1UL << (ctx->pgtable_shift + PAGE_SHIFT - 3);
if (start + size == end) {
list = (struct list_head *)child;
list_add_tail(list, &ctx->list);
*dir = ctx->invalid_ptes[ctx->level + 1];
}
}
return ret;
}
/*
* Page walker for VM shadow mmu at page root table
*/
static int kvm_ptw_top(kvm_pte_t *dir, phys_addr_t addr, phys_addr_t end, kvm_ptw_ctx *ctx)
{
int ret;
phys_addr_t next;
kvm_pte_t *entry;
ret = 0;
entry = kvm_pgtable_offset(ctx, dir, addr);
do {
next = kvm_pgtable_addr_end(ctx, addr, end);
if (!kvm_pte_present(ctx, entry))
continue;
kvm_ptw_enter(ctx);
ret |= kvm_ptw_dir(entry, addr, next, ctx);
kvm_ptw_exit(ctx);
} while (entry++, addr = next, addr < end);
return ret;
}
/*
* kvm_flush_range() - Flush a range of guest physical addresses.
* @kvm: KVM pointer.
* @start_gfn: Guest frame number of first page in GPA range to flush.
* @end_gfn: Guest frame number of last page in GPA range to flush.
* @lock: Whether to hold mmu_lock or not
*
* Flushes a range of GPA mappings from the GPA page tables.
*/
static void kvm_flush_range(struct kvm *kvm, gfn_t start_gfn, gfn_t end_gfn, int lock)
{
int ret;
kvm_ptw_ctx ctx;
struct list_head *pos, *temp;
ctx.ops = kvm_flush_pte;
ctx.flag = _KVM_FLUSH_PGTABLE;
kvm_ptw_prepare(kvm, &ctx);
INIT_LIST_HEAD(&ctx.list);
if (lock) {
spin_lock(&kvm->mmu_lock);
ret = kvm_ptw_top(kvm->arch.pgd, start_gfn << PAGE_SHIFT,
end_gfn << PAGE_SHIFT, &ctx);
spin_unlock(&kvm->mmu_lock);
} else
ret = kvm_ptw_top(kvm->arch.pgd, start_gfn << PAGE_SHIFT,
end_gfn << PAGE_SHIFT, &ctx);
/* Flush vpid for each vCPU individually */
if (ret)
kvm_flush_remote_tlbs(kvm);
/*
* free pte table page after mmu_lock
* the pte table page is linked together with ctx.list
*/
list_for_each_safe(pos, temp, &ctx.list) {
list_del(pos);
free_page((unsigned long)pos);
}
}
/*
* kvm_mkclean_gpa_pt() - Make a range of guest physical addresses clean.
* @kvm: KVM pointer.
* @start_gfn: Guest frame number of first page in GPA range to flush.
* @end_gfn: Guest frame number of last page in GPA range to flush.
*
* Make a range of GPA mappings clean so that guest writes will fault and
* trigger dirty page logging.
*
* The caller must hold the @kvm->mmu_lock spinlock.
*
* Returns: Whether any GPA mappings were modified, which would require
* derived mappings (GVA page tables & TLB enties) to be
* invalidated.
*/
static int kvm_mkclean_gpa_pt(struct kvm *kvm, gfn_t start_gfn, gfn_t end_gfn)
{
kvm_ptw_ctx ctx;
ctx.ops = kvm_mkclean_pte;
ctx.flag = 0;
kvm_ptw_prepare(kvm, &ctx);
return kvm_ptw_top(kvm->arch.pgd, start_gfn << PAGE_SHIFT, end_gfn << PAGE_SHIFT, &ctx);
}
/*
* kvm_arch_mmu_enable_log_dirty_pt_masked() - write protect dirty pages
* @kvm: The KVM pointer
* @slot: The memory slot associated with mask
* @gfn_offset: The gfn offset in memory slot
* @mask: The mask of dirty pages at offset 'gfn_offset' in this memory
* slot to be write protected
*
* Walks bits set in mask write protects the associated pte's. Caller must
* acquire @kvm->mmu_lock.
*/
void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
struct kvm_memory_slot *slot, gfn_t gfn_offset, unsigned long mask)
{
kvm_ptw_ctx ctx;
gfn_t base_gfn = slot->base_gfn + gfn_offset;
gfn_t start = base_gfn + __ffs(mask);
gfn_t end = base_gfn + __fls(mask) + 1;
ctx.ops = kvm_mkclean_pte;
ctx.flag = _KVM_HAS_PGMASK;
ctx.mask = mask;
ctx.gfn = base_gfn;
kvm_ptw_prepare(kvm, &ctx);
kvm_ptw_top(kvm->arch.pgd, start << PAGE_SHIFT, end << PAGE_SHIFT, &ctx);
}
int kvm_arch_prepare_memory_region(struct kvm *kvm, const struct kvm_memory_slot *old,
struct kvm_memory_slot *new, enum kvm_mr_change change)
{
gpa_t gpa_start;
hva_t hva_start;
size_t size, gpa_offset, hva_offset;
if ((change != KVM_MR_MOVE) && (change != KVM_MR_CREATE))
return 0;
/*
* Prevent userspace from creating a memory region outside of the
* VM GPA address space
*/
if ((new->base_gfn + new->npages) > (kvm->arch.gpa_size >> PAGE_SHIFT))
return -ENOMEM;
new->arch.flags = 0;
size = new->npages * PAGE_SIZE;
gpa_start = new->base_gfn << PAGE_SHIFT;
hva_start = new->userspace_addr;
if (IS_ALIGNED(size, PMD_SIZE) && IS_ALIGNED(gpa_start, PMD_SIZE)
&& IS_ALIGNED(hva_start, PMD_SIZE))
new->arch.flags |= KVM_MEM_HUGEPAGE_CAPABLE;
else {
/*
* Pages belonging to memslots that don't have the same
* alignment within a PMD for userspace and GPA cannot be
* mapped with PMD entries, because we'll end up mapping
* the wrong pages.
*
* Consider a layout like the following:
*
* memslot->userspace_addr:
* +-----+--------------------+--------------------+---+
* |abcde|fgh Stage-1 block | Stage-1 block tv|xyz|
* +-----+--------------------+--------------------+---+
*
* memslot->base_gfn << PAGE_SIZE:
* +---+--------------------+--------------------+-----+
* |abc|def Stage-2 block | Stage-2 block |tvxyz|
* +---+--------------------+--------------------+-----+
*
* If we create those stage-2 blocks, we'll end up with this
* incorrect mapping:
* d -> f
* e -> g
* f -> h
*/
gpa_offset = gpa_start & (PMD_SIZE - 1);
hva_offset = hva_start & (PMD_SIZE - 1);
if (gpa_offset != hva_offset) {
new->arch.flags |= KVM_MEM_HUGEPAGE_INCAPABLE;
} else {
if (gpa_offset == 0)
gpa_offset = PMD_SIZE;
if ((size + gpa_offset) < (PMD_SIZE * 2))
new->arch.flags |= KVM_MEM_HUGEPAGE_INCAPABLE;
}
}
return 0;
}
void kvm_arch_commit_memory_region(struct kvm *kvm,
struct kvm_memory_slot *old,
const struct kvm_memory_slot *new,
enum kvm_mr_change change)
{
int needs_flush;
u32 old_flags = old ? old->flags : 0;
u32 new_flags = new ? new->flags : 0;
bool log_dirty_pages = new_flags & KVM_MEM_LOG_DIRTY_PAGES;
/* Only track memslot flags changed */
if (change != KVM_MR_FLAGS_ONLY)
return;
/* Discard dirty page tracking on readonly memslot */
if ((old_flags & new_flags) & KVM_MEM_READONLY)
return;
/*
* If dirty page logging is enabled, write protect all pages in the slot
* ready for dirty logging.
*
* There is no need to do this in any of the following cases:
* CREATE: No dirty mappings will already exist.
* MOVE/DELETE: The old mappings will already have been cleaned up by
* kvm_arch_flush_shadow_memslot()
*/
if (!(old_flags & KVM_MEM_LOG_DIRTY_PAGES) && log_dirty_pages) {
/*
* Initially-all-set does not require write protecting any page
* because they're all assumed to be dirty.
*/
if (kvm_dirty_log_manual_protect_and_init_set(kvm))
return;
spin_lock(&kvm->mmu_lock);
/* Write protect GPA page table entries */
needs_flush = kvm_mkclean_gpa_pt(kvm, new->base_gfn,
new->base_gfn + new->npages);
spin_unlock(&kvm->mmu_lock);
if (needs_flush)
kvm_flush_remote_tlbs(kvm);
}
}
void kvm_arch_flush_shadow_all(struct kvm *kvm)
{
kvm_flush_range(kvm, 0, kvm->arch.gpa_size >> PAGE_SHIFT, 0);
}
void kvm_arch_flush_shadow_memslot(struct kvm *kvm, struct kvm_memory_slot *slot)
{
/*
* The slot has been made invalid (ready for moving or deletion), so we
* need to ensure that it can no longer be accessed by any guest vCPUs.
*/
kvm_flush_range(kvm, slot->base_gfn, slot->base_gfn + slot->npages, 1);
}
bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
{
kvm_ptw_ctx ctx;
ctx.flag = 0;
ctx.ops = kvm_flush_pte;
kvm_ptw_prepare(kvm, &ctx);
INIT_LIST_HEAD(&ctx.list);
return kvm_ptw_top(kvm->arch.pgd, range->start << PAGE_SHIFT,
range->end << PAGE_SHIFT, &ctx);
}
bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
{
kvm_ptw_ctx ctx;
ctx.flag = 0;
ctx.ops = kvm_mkold_pte;
kvm_ptw_prepare(kvm, &ctx);
return kvm_ptw_top(kvm->arch.pgd, range->start << PAGE_SHIFT,
range->end << PAGE_SHIFT, &ctx);
}
bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
{
gpa_t gpa = range->start << PAGE_SHIFT;
kvm_pte_t *ptep = kvm_populate_gpa(kvm, NULL, gpa, 0);
if (ptep && kvm_pte_present(NULL, ptep) && kvm_pte_young(*ptep))
return true;
return false;
}
/*
* kvm_map_page_fast() - Fast path GPA fault handler.
* @vcpu: vCPU pointer.
* @gpa: Guest physical address of fault.
* @write: Whether the fault was due to a write.
*
* Perform fast path GPA fault handling, doing all that can be done without
* calling into KVM. This handles marking old pages young (for idle page
* tracking), and dirtying of clean pages (for dirty page logging).
*
* Returns: 0 on success, in which case we can update derived mappings and
* resume guest execution.
* -EFAULT on failure due to absent GPA mapping or write to
* read-only page, in which case KVM must be consulted.
*/
static int kvm_map_page_fast(struct kvm_vcpu *vcpu, unsigned long gpa, bool write)
{
int ret = 0;
kvm_pte_t *ptep, changed, new;
gfn_t gfn = gpa >> PAGE_SHIFT;
struct kvm *kvm = vcpu->kvm;
struct kvm_memory_slot *slot;
spin_lock(&kvm->mmu_lock);
/* Fast path - just check GPA page table for an existing entry */
ptep = kvm_populate_gpa(kvm, NULL, gpa, 0);
if (!ptep || !kvm_pte_present(NULL, ptep)) {
ret = -EFAULT;
goto out;
}
/* Track access to pages marked old */
new = kvm_pte_mkyoung(*ptep);
if (write && !kvm_pte_dirty(new)) {
if (!kvm_pte_writeable(new)) {
ret = -EFAULT;
goto out;
}
if (kvm_pte_huge(new)) {
/*
* Do not set write permission when dirty logging is
* enabled for HugePages
*/
slot = gfn_to_memslot(kvm, gfn);
if (kvm_slot_dirty_track_enabled(slot)) {
ret = -EFAULT;
goto out;
}
}
/* Track dirtying of writeable pages */
new = kvm_pte_mkdirty(new);
}
changed = new ^ (*ptep);
if (changed)
kvm_set_pte(ptep, new);
spin_unlock(&kvm->mmu_lock);
if (kvm_pte_dirty(changed))
mark_page_dirty(kvm, gfn);
return ret;
out:
spin_unlock(&kvm->mmu_lock);
return ret;
}
static bool fault_supports_huge_mapping(struct kvm_memory_slot *memslot,
unsigned long hva, bool write)
{
hva_t start, end;
/* Disable dirty logging on HugePages */
if (kvm_slot_dirty_track_enabled(memslot) && write)
return false;
if (kvm_hugepage_capable(memslot))
return true;
if (kvm_hugepage_incapable(memslot))
return false;
start = memslot->userspace_addr;
end = start + memslot->npages * PAGE_SIZE;
/*
* Next, let's make sure we're not trying to map anything not covered
* by the memslot. This means we have to prohibit block size mappings
* for the beginning and end of a non-block aligned and non-block sized
* memory slot (illustrated by the head and tail parts of the
* userspace view above containing pages 'abcde' and 'xyz',
* respectively).
*
* Note that it doesn't matter if we do the check using the
* userspace_addr or the base_gfn, as both are equally aligned (per
* the check above) and equally sized.
*/
return (hva >= ALIGN(start, PMD_SIZE)) && (hva < ALIGN_DOWN(end, PMD_SIZE));
}
/*
* Lookup the mapping level for @gfn in the current mm.
*
* WARNING! Use of host_pfn_mapping_level() requires the caller and the end
* consumer to be tied into KVM's handlers for MMU notifier events!
*
* There are several ways to safely use this helper:
*
* - Check mmu_invalidate_retry_gfn() after grabbing the mapping level, before
* consuming it. In this case, mmu_lock doesn't need to be held during the
* lookup, but it does need to be held while checking the MMU notifier.
*
* - Hold mmu_lock AND ensure there is no in-progress MMU notifier invalidation
* event for the hva. This can be done by explicit checking the MMU notifier
* or by ensuring that KVM already has a valid mapping that covers the hva.
*
* - Do not use the result to install new mappings, e.g. use the host mapping
* level only to decide whether or not to zap an entry. In this case, it's
* not required to hold mmu_lock (though it's highly likely the caller will
* want to hold mmu_lock anyways, e.g. to modify SPTEs).
*
* Note! The lookup can still race with modifications to host page tables, but
* the above "rules" ensure KVM will not _consume_ the result of the walk if a
* race with the primary MMU occurs.
*/
static int host_pfn_mapping_level(struct kvm *kvm, gfn_t gfn,
const struct kvm_memory_slot *slot)
{
int level = 0;
unsigned long hva;
unsigned long flags;
pgd_t pgd;
p4d_t p4d;
pud_t pud;
pmd_t pmd;
/*
* Note, using the already-retrieved memslot and __gfn_to_hva_memslot()
* is not solely for performance, it's also necessary to avoid the
* "writable" check in __gfn_to_hva_many(), which will always fail on
* read-only memslots due to gfn_to_hva() assuming writes. Earlier
* page fault steps have already verified the guest isn't writing a
* read-only memslot.
*/
hva = __gfn_to_hva_memslot(slot, gfn);
/*
* Disable IRQs to prevent concurrent tear down of host page tables,
* e.g. if the primary MMU promotes a P*D to a huge page and then frees
* the original page table.
*/
local_irq_save(flags);
/*
* Read each entry once. As above, a non-leaf entry can be promoted to
* a huge page _during_ this walk. Re-reading the entry could send the
* walk into the weeks, e.g. p*d_leaf() returns false (sees the old
* value) and then p*d_offset() walks into the target huge page instead
* of the old page table (sees the new value).
*/
pgd = pgdp_get(pgd_offset(kvm->mm, hva));
if (pgd_none(pgd))
goto out;
p4d = p4dp_get(p4d_offset(&pgd, hva));
if (p4d_none(p4d) || !p4d_present(p4d))
goto out;
pud = pudp_get(pud_offset(&p4d, hva));
if (pud_none(pud) || !pud_present(pud))
goto out;
pmd = pmdp_get(pmd_offset(&pud, hva));
if (pmd_none(pmd) || !pmd_present(pmd))
goto out;
if (kvm_pte_huge(pmd_val(pmd)))
level = 1;
out:
local_irq_restore(flags);
return level;
}
/*
* Split huge page
*/
static kvm_pte_t *kvm_split_huge(struct kvm_vcpu *vcpu, kvm_pte_t *ptep, gfn_t gfn)
{
int i;
kvm_pte_t val, *child;
struct kvm *kvm = vcpu->kvm;
struct kvm_mmu_memory_cache *memcache;
memcache = &vcpu->arch.mmu_page_cache;
child = kvm_mmu_memory_cache_alloc(memcache);
val = kvm_pte_mksmall(*ptep);
for (i = 0; i < PTRS_PER_PTE; i++) {
kvm_set_pte(child + i, val);
val += PAGE_SIZE;
}
smp_wmb(); /* Make pte visible before pmd */
/* The later kvm_flush_tlb_gpa() will flush hugepage tlb */
kvm_set_pte(ptep, __pa(child));
kvm->stat.hugepages--;
kvm->stat.pages += PTRS_PER_PTE;
return child + (gfn & (PTRS_PER_PTE - 1));
}
/*
* kvm_map_page() - Map a guest physical page.
* @vcpu: vCPU pointer.
* @gpa: Guest physical address of fault.
* @write: Whether the fault was due to a write.
*
* Handle GPA faults by creating a new GPA mapping (or updating an existing
* one).
*
* This takes care of marking pages young or dirty (idle/dirty page tracking),
* asking KVM for the corresponding PFN, and creating a mapping in the GPA page
* tables. Derived mappings (GVA page tables and TLBs) must be handled by the
* caller.
*
* Returns: 0 on success
* -EFAULT if there is no memory region at @gpa or a write was
* attempted to a read-only memory region. This is usually handled
* as an MMIO access.
*/
static int kvm_map_page(struct kvm_vcpu *vcpu, unsigned long gpa, bool write)
{
bool writeable;
int srcu_idx, err, retry_no = 0, level;
unsigned long hva, mmu_seq, prot_bits;
kvm_pfn_t pfn;
kvm_pte_t *ptep, new_pte;
gfn_t gfn = gpa >> PAGE_SHIFT;
struct kvm *kvm = vcpu->kvm;
struct kvm_memory_slot *memslot;
struct kvm_mmu_memory_cache *memcache = &vcpu->arch.mmu_page_cache;
struct page *page;
/* Try the fast path to handle old / clean pages */
srcu_idx = srcu_read_lock(&kvm->srcu);
err = kvm_map_page_fast(vcpu, gpa, write);
if (!err)
goto out;
memslot = gfn_to_memslot(kvm, gfn);
hva = gfn_to_hva_memslot_prot(memslot, gfn, &writeable);
if (kvm_is_error_hva(hva) || (write && !writeable)) {
err = -EFAULT;
goto out;
}
/* We need a minimum of cached pages ready for page table creation */
err = kvm_mmu_topup_memory_cache(memcache, KVM_MMU_CACHE_MIN_PAGES);
if (err)
goto out;
retry:
/*
* Used to check for invalidations in progress, of the pfn that is
* returned by pfn_to_pfn_prot below.
*/
mmu_seq = kvm->mmu_invalidate_seq;
/*
* Ensure the read of mmu_invalidate_seq isn't reordered with PTE reads in
* kvm_faultin_pfn() (which calls get_user_pages()), so that we don't
* risk the page we get a reference to getting unmapped before we have a
* chance to grab the mmu_lock without mmu_invalidate_retry() noticing.
*
* This smp_rmb() pairs with the effective smp_wmb() of the combination
* of the pte_unmap_unlock() after the PTE is zapped, and the
* spin_lock() in kvm_mmu_invalidate_invalidate_<page|range_end>() before
* mmu_invalidate_seq is incremented.
*/
smp_rmb();
/* Slow path - ask KVM core whether we can access this GPA */
pfn = kvm_faultin_pfn(vcpu, gfn, write, &writeable, &page);
if (is_error_noslot_pfn(pfn)) {
err = -EFAULT;
goto out;
}
/* Check if an invalidation has taken place since we got pfn */
spin_lock(&kvm->mmu_lock);
if (mmu_invalidate_retry_gfn(kvm, mmu_seq, gfn)) {
/*
* This can happen when mappings are changed asynchronously, but
* also synchronously if a COW is triggered by
* kvm_faultin_pfn().
*/
spin_unlock(&kvm->mmu_lock);
kvm_release_page_unused(page);
if (retry_no > 100) {
retry_no = 0;
schedule();
}
retry_no++;
goto retry;
}
/*
* For emulated devices such virtio device, actual cache attribute is
* determined by physical machine.
* For pass through physical device, it should be uncachable
*/
prot_bits = _PAGE_PRESENT | __READABLE;
if (pfn_valid(pfn))
prot_bits |= _CACHE_CC;
else
prot_bits |= _CACHE_SUC;
if (writeable) {
prot_bits = kvm_pte_mkwriteable(prot_bits);
if (write || !kvm_slot_dirty_track_enabled(memslot))
prot_bits = kvm_pte_mkdirty(prot_bits);
}
/* Disable dirty logging on HugePages */
level = 0;
if (fault_supports_huge_mapping(memslot, hva, write)) {
/* Check page level about host mmu*/
level = host_pfn_mapping_level(kvm, gfn, memslot);
if (level == 1) {
/*
* Check page level about secondary mmu
* Disable hugepage if it is normal page on
* secondary mmu already
*/
ptep = kvm_populate_gpa(kvm, NULL, gpa, 0);
if (ptep && !kvm_pte_huge(*ptep))
level = 0;
}
if (level == 1) {
gfn = gfn & ~(PTRS_PER_PTE - 1);
pfn = pfn & ~(PTRS_PER_PTE - 1);
}
}
/* Ensure page tables are allocated */
ptep = kvm_populate_gpa(kvm, memcache, gpa, level);
new_pte = kvm_pfn_pte(pfn, __pgprot(prot_bits));
if (level == 1) {
new_pte = kvm_pte_mkhuge(new_pte);
/*
* previous pmd entry is invalid_pte_table
* there is invalid tlb with small page
* need flush these invalid tlbs for current vcpu
*/
kvm_make_request(KVM_REQ_TLB_FLUSH, vcpu);
++kvm->stat.hugepages;
} else if (kvm_pte_huge(*ptep) && write)
ptep = kvm_split_huge(vcpu, ptep, gfn);
else
++kvm->stat.pages;
kvm_set_pte(ptep, new_pte);
kvm_release_faultin_page(kvm, page, false, writeable);
spin_unlock(&kvm->mmu_lock);
if (kvm_pte_dirty(prot_bits))
mark_page_dirty_in_slot(kvm, memslot, gfn);
out:
srcu_read_unlock(&kvm->srcu, srcu_idx);
return err;
}
int kvm_handle_mm_fault(struct kvm_vcpu *vcpu, unsigned long gpa, bool write, int ecode)
{
int ret;
ret = kvm_map_page(vcpu, gpa, write);
if (ret)
return ret;
/* Invalidate this entry in the TLB */
if (!cpu_has_ptw || (ecode == EXCCODE_TLBM)) {
/*
* With HW PTW, invalid TLB is not added when page fault. But
* for EXCCODE_TLBM exception, stale TLB may exist because of
* the last read access.
*
* With SW PTW, invalid TLB is added in TLB refill exception.
*/
vcpu->arch.flush_gpa = gpa;
kvm_make_request(KVM_REQ_TLB_FLUSH_GPA, vcpu);
}
return 0;
}
void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
{
}
void kvm_arch_flush_remote_tlbs_memslot(struct kvm *kvm,
const struct kvm_memory_slot *memslot)
{
kvm_flush_remote_tlbs(kvm);
}
+288
View File
@@ -0,0 +1,288 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/linkage.h>
#include <linux/kvm_types.h>
#include <asm/asm.h>
#include <asm/asmmacro.h>
#include <asm/loongarch.h>
#include <asm/page.h>
#include <asm/regdef.h>
#include <asm/unwind_hints.h>
#define HGPR_OFFSET(x) (PT_R0 + 8*x)
#define GGPR_OFFSET(x) (KVM_ARCH_GGPR + 8*x)
.macro kvm_save_host_gpr base
.irp n,1,2,3,22,23,24,25,26,27,28,29,30,31
st.d $r\n, \base, HGPR_OFFSET(\n)
.endr
.endm
.macro kvm_restore_host_gpr base
.irp n,1,2,3,22,23,24,25,26,27,28,29,30,31
ld.d $r\n, \base, HGPR_OFFSET(\n)
.endr
.endm
/*
* Save and restore all GPRs except base register,
* and default value of base register is a2.
*/
.macro kvm_save_guest_gprs base
.irp n,1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
st.d $r\n, \base, GGPR_OFFSET(\n)
.endr
.endm
.macro kvm_restore_guest_gprs base
.irp n,1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
ld.d $r\n, \base, GGPR_OFFSET(\n)
.endr
.endm
/*
* Prepare switch to guest, save host regs and restore guest regs.
* a2: kvm_vcpu_arch, don't touch it until 'ertn'
* t0, t1: temp register
*/
.macro kvm_switch_to_guest
/* Set host ECFG.VS=0, all exceptions share one exception entry */
csrrd t0, LOONGARCH_CSR_ECFG
bstrins.w t0, zero, CSR_ECFG_VS_SHIFT_END, CSR_ECFG_VS_SHIFT
csrwr t0, LOONGARCH_CSR_ECFG
/* Load up the new EENTRY */
ld.d t0, a2, KVM_ARCH_GEENTRY
csrwr t0, LOONGARCH_CSR_EENTRY
/* Set Guest ERA */
ld.d t0, a2, KVM_ARCH_GPC
csrwr t0, LOONGARCH_CSR_ERA
/* Load PGD for KVM hypervisor */
ld.d t0, a2, KVM_ARCH_KVMPGD
csrwr t0, LOONGARCH_CSR_PGDL
/* Mix GID and RID */
csrrd t1, LOONGARCH_CSR_GSTAT
bstrpick.w t1, t1, CSR_GSTAT_GID_SHIFT_END, CSR_GSTAT_GID_SHIFT
csrrd t0, LOONGARCH_CSR_GTLBC
bstrins.w t0, t1, CSR_GTLBC_TGID_SHIFT_END, CSR_GTLBC_TGID_SHIFT
csrwr t0, LOONGARCH_CSR_GTLBC
/*
* Enable intr in root mode with future ertn so that host interrupt
* can be responsed during VM runs
* Guest CRMD comes from separate GCSR_CRMD register
*/
ori t0, zero, CSR_PRMD_PIE
csrwr t0, LOONGARCH_CSR_PRMD
/* Set PVM bit to setup ertn to guest context */
ori t0, zero, CSR_GSTAT_PVM
csrxchg t0, t0, LOONGARCH_CSR_GSTAT
/* Load Guest GPRs */
kvm_restore_guest_gprs a2
/* Load KVM_ARCH register */
ld.d a2, a2, (KVM_ARCH_GGPR + 8 * REG_A2)
ertn /* Switch to guest: GSTAT.PGM = 1, ERRCTL.ISERR = 0, TLBRPRMD.ISTLBR = 0 */
.endm
/*
* Exception entry for general exception from guest mode
* - IRQ is disabled
* - kernel privilege in root mode
* - page mode keep unchanged from previous PRMD in root mode
* - Fixme: tlb exception cannot happen since registers relative with TLB
* - is still in guest mode, such as pgd table/vmid registers etc,
* - will fix with hw page walk enabled in future
* load kvm_vcpu from reserved CSR KVM_VCPU_KS, and save a2 to KVM_TEMP_KS
*
* PGD register is shared between root kernel and kvm hypervisor.
* So world switch entry should be in DMW area rather than TLB area
* to avoid page fault re-enter.
*/
.text
.p2align PAGE_SHIFT
.cfi_sections .debug_frame
SYM_CODE_START(kvm_exc_entry)
UNWIND_HINT_END_OF_STACK
csrwr a2, KVM_TEMP_KS
csrrd a2, KVM_VCPU_KS
addi.d a2, a2, KVM_VCPU_ARCH
/* After save GPRs, free to use any GPR */
kvm_save_guest_gprs a2
/* Save guest A2 */
csrrd t0, KVM_TEMP_KS
st.d t0, a2, (KVM_ARCH_GGPR + 8 * REG_A2)
/* A2 is kvm_vcpu_arch, A1 is free to use */
csrrd s1, KVM_VCPU_KS
ld.d s0, s1, KVM_VCPU_RUN
csrrd t0, LOONGARCH_CSR_ESTAT
st.d t0, a2, KVM_ARCH_HESTAT
csrrd t0, LOONGARCH_CSR_ERA
st.d t0, a2, KVM_ARCH_GPC
csrrd t0, LOONGARCH_CSR_BADV
st.d t0, a2, KVM_ARCH_HBADV
csrrd t0, LOONGARCH_CSR_BADI
st.d t0, a2, KVM_ARCH_HBADI
/* Restore host ECFG.VS */
csrrd t0, LOONGARCH_CSR_ECFG
ld.d t1, a2, KVM_ARCH_HECFG
or t0, t0, t1
csrwr t0, LOONGARCH_CSR_ECFG
/* Restore host EENTRY */
ld.d t0, a2, KVM_ARCH_HEENTRY
csrwr t0, LOONGARCH_CSR_EENTRY
/* Restore host pgd table */
ld.d t0, a2, KVM_ARCH_HPGD
csrwr t0, LOONGARCH_CSR_PGDL
/*
* Disable PGM bit to enter root mode by default with next ertn
*/
ori t0, zero, CSR_GSTAT_PVM
csrxchg zero, t0, LOONGARCH_CSR_GSTAT
/*
* Clear GTLBC.TGID field
* 0: for root tlb update in future tlb instr
* others: for guest tlb update like gpa to hpa in future tlb instr
*/
csrrd t0, LOONGARCH_CSR_GTLBC
bstrins.w t0, zero, CSR_GTLBC_TGID_SHIFT_END, CSR_GTLBC_TGID_SHIFT
csrwr t0, LOONGARCH_CSR_GTLBC
ld.d tp, a2, KVM_ARCH_HTP
ld.d sp, a2, KVM_ARCH_HSP
/* restore per cpu register */
ld.d u0, a2, KVM_ARCH_HPERCPU
addi.d sp, sp, -PT_SIZE
/* Prepare handle exception */
or a0, s0, zero
or a1, s1, zero
ld.d t8, a2, KVM_ARCH_HANDLE_EXIT
jirl ra, t8, 0
or a2, s1, zero
addi.d a2, a2, KVM_VCPU_ARCH
/* Resume host when ret <= 0 */
blez a0, ret_to_host
/*
* Return to guest
* Save per cpu register again, maybe switched to another cpu
*/
st.d u0, a2, KVM_ARCH_HPERCPU
/* Save kvm_vcpu to kscratch */
csrwr s1, KVM_VCPU_KS
kvm_switch_to_guest
ret_to_host:
ld.d a2, a2, KVM_ARCH_HSP
addi.d a2, a2, -PT_SIZE
kvm_restore_host_gpr a2
jr ra
SYM_CODE_END(kvm_exc_entry)
EXPORT_SYMBOL_FOR_KVM(kvm_exc_entry)
/*
* int kvm_enter_guest(struct kvm_run *run, struct kvm_vcpu *vcpu)
*
* @register_param:
* a0: kvm_run* run
* a1: kvm_vcpu* vcpu
*/
SYM_FUNC_START(kvm_enter_guest)
/* Allocate space in stack bottom */
addi.d a2, sp, -PT_SIZE
/* Save host GPRs */
kvm_save_host_gpr a2
addi.d a2, a1, KVM_VCPU_ARCH
st.d sp, a2, KVM_ARCH_HSP
st.d tp, a2, KVM_ARCH_HTP
/* Save per cpu register */
st.d u0, a2, KVM_ARCH_HPERCPU
/* Save kvm_vcpu to kscratch */
csrwr a1, KVM_VCPU_KS
kvm_switch_to_guest
SYM_FUNC_END(kvm_enter_guest)
EXPORT_SYMBOL_FOR_KVM(kvm_enter_guest)
SYM_FUNC_START(kvm_save_fpu)
fpu_save_csr a0 t1
fpu_save_double a0 t1
fpu_save_cc a0 t1 t2
jr ra
SYM_FUNC_END(kvm_save_fpu)
EXPORT_SYMBOL_FOR_KVM(kvm_save_fpu)
SYM_FUNC_START(kvm_restore_fpu)
fpu_restore_double a0 t1
fpu_restore_csr a0 t1 t2
fpu_restore_cc a0 t1 t2
jr ra
SYM_FUNC_END(kvm_restore_fpu)
EXPORT_SYMBOL_FOR_KVM(kvm_restore_fpu)
#ifdef CONFIG_CPU_HAS_LSX
SYM_FUNC_START(kvm_save_lsx)
fpu_save_csr a0 t1
fpu_save_cc a0 t1 t2
lsx_save_data a0 t1
jr ra
SYM_FUNC_END(kvm_save_lsx)
EXPORT_SYMBOL_FOR_KVM(kvm_save_lsx)
SYM_FUNC_START(kvm_restore_lsx)
lsx_restore_data a0 t1
fpu_restore_cc a0 t1 t2
fpu_restore_csr a0 t1 t2
jr ra
SYM_FUNC_END(kvm_restore_lsx)
EXPORT_SYMBOL_FOR_KVM(kvm_restore_lsx)
#endif
#ifdef CONFIG_CPU_HAS_LASX
SYM_FUNC_START(kvm_save_lasx)
fpu_save_csr a0 t1
fpu_save_cc a0 t1 t2
lasx_save_data a0 t1
jr ra
SYM_FUNC_END(kvm_save_lasx)
EXPORT_SYMBOL_FOR_KVM(kvm_save_lasx)
SYM_FUNC_START(kvm_restore_lasx)
lasx_restore_data a0 t1
fpu_restore_cc a0 t1 t2
fpu_restore_csr a0 t1 t2
jr ra
SYM_FUNC_END(kvm_restore_lasx)
EXPORT_SYMBOL_FOR_KVM(kvm_restore_lasx)
#endif
#ifdef CONFIG_CPU_HAS_LBT
STACK_FRAME_NON_STANDARD kvm_restore_fpu
#ifdef CONFIG_CPU_HAS_LSX
STACK_FRAME_NON_STANDARD kvm_restore_lsx
#endif
#ifdef CONFIG_CPU_HAS_LASX
STACK_FRAME_NON_STANDARD kvm_restore_lasx
#endif
#endif
+198
View File
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/kvm_host.h>
#include <asm/delay.h>
#include <asm/kvm_csr.h>
#include <asm/kvm_vcpu.h>
/*
* ktime_to_tick() - Scale ktime_t to timer tick value.
*/
static inline u64 ktime_to_tick(struct kvm_vcpu *vcpu, ktime_t now)
{
u64 delta;
delta = ktime_to_ns(now);
return div_u64(delta * vcpu->arch.timer_mhz, MNSEC_PER_SEC);
}
static inline u64 tick_to_ns(struct kvm_vcpu *vcpu, u64 tick)
{
return div_u64(tick * MNSEC_PER_SEC, vcpu->arch.timer_mhz);
}
/* Low level hrtimer wake routine */
enum hrtimer_restart kvm_swtimer_wakeup(struct hrtimer *timer)
{
struct kvm_vcpu *vcpu;
vcpu = container_of(timer, struct kvm_vcpu, arch.swtimer);
kvm_vcpu_wake_up(vcpu);
return HRTIMER_NORESTART;
}
/*
* Initialise the timer to the specified frequency, zero it
*/
void kvm_init_timer(struct kvm_vcpu *vcpu, unsigned long timer_hz)
{
vcpu->arch.timer_mhz = timer_hz >> 20;
/* Starting at 0 */
kvm_write_sw_gcsr(vcpu->arch.csr, LOONGARCH_CSR_TVAL, 0);
}
/*
* Restore soft timer state from saved context.
*/
void kvm_restore_timer(struct kvm_vcpu *vcpu)
{
unsigned long cfg, estat;
unsigned long ticks, delta, period;
ktime_t expire, now;
struct loongarch_csrs *csr = vcpu->arch.csr;
/*
* Set guest stable timer cfg csr
* Disable timer before restore estat CSR register, avoid to
* get invalid timer interrupt for old timer cfg
*/
cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG);
write_gcsr_timercfg(0);
kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_ESTAT);
kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TCFG);
if (!(cfg & CSR_TCFG_EN)) {
/* Guest timer is disabled, just restore timer registers */
kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TVAL);
return;
}
/*
* Freeze the soft-timer and sync the guest stable timer with it.
*/
if (kvm_vcpu_is_blocking(vcpu))
hrtimer_cancel(&vcpu->arch.swtimer);
/*
* From LoongArch Reference Manual Volume 1 Chapter 7.6.2
* If oneshot timer is fired, CSR TVAL will be -1, there are two
* conditions:
* 1) timer is fired during exiting to host
* 2) timer is fired and vm is doing timer irq, and then exiting to
* host. Host should not inject timer irq to avoid spurious
* timer interrupt again
*/
ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL);
estat = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_ESTAT);
if (!(cfg & CSR_TCFG_PERIOD) && (ticks > cfg)) {
/*
* Writing 0 to LOONGARCH_CSR_TVAL will inject timer irq
* and set CSR TVAL with -1
*/
write_gcsr_timertick(0);
/*
* Writing CSR_TINTCLR_TI to LOONGARCH_CSR_TINTCLR will clear
* timer interrupt, and CSR TVAL keeps unchanged with -1, it
* avoids spurious timer interrupt
*/
if (!(estat & CPU_TIMER)) {
__delay(2); /* Wait cycles until timer interrupt injected */
/* Write TVAL with max value if no TI shot */
estat = kvm_read_hw_gcsr(LOONGARCH_CSR_ESTAT);
if (!(estat & CPU_TIMER))
write_gcsr_timertick(CSR_TCFG_VAL);
gcsr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR);
}
return;
}
/*
* Set remainder tick value if not expired
*/
delta = 0;
now = ktime_get();
expire = vcpu->arch.expire;
if (ktime_before(now, expire))
delta = ktime_to_tick(vcpu, ktime_sub(expire, now));
else if (cfg & CSR_TCFG_PERIOD) {
period = cfg & CSR_TCFG_VAL;
delta = ktime_to_tick(vcpu, ktime_sub(now, expire));
delta = period - (delta % period);
/*
* Inject timer here though sw timer should inject timer
* interrupt async already, since sw timer may be cancelled
* during injecting intr async
*/
kvm_queue_irq(vcpu, INT_TI);
}
write_gcsr_timertick(delta);
}
/*
* Save guest timer state and switch to software emulation of guest
* timer. The hard timer must already be in use, so preemption should be
* disabled.
*/
static void _kvm_save_timer(struct kvm_vcpu *vcpu)
{
unsigned long ticks, delta, cfg;
ktime_t expire;
struct loongarch_csrs *csr = vcpu->arch.csr;
cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG);
ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL);
/*
* From LoongArch Reference Manual Volume 1 Chapter 7.6.2
* If period timer is fired, CSR TVAL will be reloaded from CSR TCFG
* If oneshot timer is fired, CSR TVAL will be -1
* Here judge one-shot timer fired by checking whether TVAL is larger
* than TCFG
*/
if (ticks < cfg)
delta = tick_to_ns(vcpu, ticks);
else
delta = 0;
expire = ktime_add_ns(ktime_get(), delta);
vcpu->arch.expire = expire;
if (kvm_vcpu_is_blocking(vcpu)) {
/*
* HRTIMER_MODE_PINNED_HARD is suggested since vcpu may run in
* the same physical cpu in next time, and the timer should run
* in hardirq context even in the PREEMPT_RT case.
*/
hrtimer_start(&vcpu->arch.swtimer, expire, HRTIMER_MODE_ABS_PINNED_HARD);
}
}
/*
* Save guest timer state and switch to soft guest timer if hard timer was in
* use.
*/
void kvm_save_timer(struct kvm_vcpu *vcpu)
{
struct loongarch_csrs *csr = vcpu->arch.csr;
preempt_disable();
/* Save hard timer state */
kvm_save_hw_gcsr(csr, LOONGARCH_CSR_TCFG);
kvm_save_hw_gcsr(csr, LOONGARCH_CSR_TVAL);
if (kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG) & CSR_TCFG_EN)
_kvm_save_timer(vcpu);
/* Save timer-related state to vCPU context */
kvm_save_hw_gcsr(csr, LOONGARCH_CSR_ESTAT);
preempt_enable();
}
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/kvm_host.h>
#include <asm/tlb.h>
#include <asm/kvm_csr.h>
/*
* kvm_flush_tlb_all() - Flush all root TLB entries for guests.
*
* Invalidate all entries including GVA-->GPA and GPA-->HPA mappings.
*/
void kvm_flush_tlb_all(void)
{
unsigned long flags;
local_irq_save(flags);
invtlb_all(INVTLB_ALLGID, 0, 0);
local_irq_restore(flags);
}
void kvm_flush_tlb_gpa(struct kvm_vcpu *vcpu, unsigned long gpa)
{
lockdep_assert_irqs_disabled();
gpa &= (PAGE_MASK << 1);
invtlb(INVTLB_GID_ADDR, read_csr_gstat() & CSR_GSTAT_GID, gpa);
}
+221
View File
@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#if !defined(_TRACE_KVM_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_KVM_H
#include <linux/tracepoint.h>
#include <asm/kvm_csr.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM kvm
/*
* Tracepoints for VM enters
*/
DECLARE_EVENT_CLASS(kvm_transition,
TP_PROTO(struct kvm_vcpu *vcpu),
TP_ARGS(vcpu),
TP_STRUCT__entry(
__field(unsigned int, vcpu_id)
__field(unsigned long, pc)
),
TP_fast_assign(
__entry->vcpu_id = vcpu->vcpu_id;
__entry->pc = vcpu->arch.pc;
),
TP_printk("vcpu %u PC: 0x%08lx", __entry->vcpu_id, __entry->pc)
);
DEFINE_EVENT(kvm_transition, kvm_enter,
TP_PROTO(struct kvm_vcpu *vcpu),
TP_ARGS(vcpu));
DEFINE_EVENT(kvm_transition, kvm_reenter,
TP_PROTO(struct kvm_vcpu *vcpu),
TP_ARGS(vcpu));
DEFINE_EVENT(kvm_transition, kvm_out,
TP_PROTO(struct kvm_vcpu *vcpu),
TP_ARGS(vcpu));
/* Further exit reasons */
#define KVM_TRACE_EXIT_IDLE 64
#define KVM_TRACE_EXIT_CACHE 65
#define KVM_TRACE_EXIT_CPUCFG 66
#define KVM_TRACE_EXIT_CSR 67
/* Tracepoints for VM exits */
#define kvm_trace_symbol_exit_types \
{ KVM_TRACE_EXIT_IDLE, "IDLE" }, \
{ KVM_TRACE_EXIT_CACHE, "CACHE" }, \
{ KVM_TRACE_EXIT_CPUCFG, "CPUCFG" }, \
{ KVM_TRACE_EXIT_CSR, "CSR" }
DECLARE_EVENT_CLASS(kvm_exit,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason),
TP_STRUCT__entry(
__field(unsigned int, vcpu_id)
__field(unsigned long, pc)
__field(unsigned int, reason)
),
TP_fast_assign(
__entry->vcpu_id = vcpu->vcpu_id;
__entry->pc = vcpu->arch.pc;
__entry->reason = reason;
),
TP_printk("vcpu %u [%s] PC: 0x%08lx",
__entry->vcpu_id,
__print_symbolic(__entry->reason,
kvm_trace_symbol_exit_types),
__entry->pc)
);
DEFINE_EVENT(kvm_exit, kvm_exit_idle,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason));
DEFINE_EVENT(kvm_exit, kvm_exit_cache,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason));
DEFINE_EVENT(kvm_exit, kvm_exit_cpucfg,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason));
DEFINE_EVENT(kvm_exit, kvm_exit_csr,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason));
DEFINE_EVENT(kvm_exit, kvm_exit,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason));
TRACE_EVENT(kvm_exit_gspr,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int inst_word),
TP_ARGS(vcpu, inst_word),
TP_STRUCT__entry(
__field(unsigned int, vcpu_id)
__field(unsigned int, inst_word)
),
TP_fast_assign(
__entry->vcpu_id = vcpu->vcpu_id;
__entry->inst_word = inst_word;
),
TP_printk("vcpu %u Inst word: 0x%08x", __entry->vcpu_id,
__entry->inst_word)
);
#define KVM_TRACE_AUX_SAVE 0
#define KVM_TRACE_AUX_RESTORE 1
#define KVM_TRACE_AUX_ENABLE 2
#define KVM_TRACE_AUX_DISABLE 3
#define KVM_TRACE_AUX_DISCARD 4
#define KVM_TRACE_AUX_FPU 1
#define KVM_TRACE_AUX_LSX 2
#define KVM_TRACE_AUX_LASX 3
#define kvm_trace_symbol_aux_op \
{ KVM_TRACE_AUX_SAVE, "save" }, \
{ KVM_TRACE_AUX_RESTORE, "restore" }, \
{ KVM_TRACE_AUX_ENABLE, "enable" }, \
{ KVM_TRACE_AUX_DISABLE, "disable" }, \
{ KVM_TRACE_AUX_DISCARD, "discard" }
#define kvm_trace_symbol_aux_state \
{ KVM_TRACE_AUX_FPU, "FPU" }, \
{ KVM_TRACE_AUX_LSX, "LSX" }, \
{ KVM_TRACE_AUX_LASX, "LASX" }
TRACE_EVENT(kvm_aux,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int op,
unsigned int state),
TP_ARGS(vcpu, op, state),
TP_STRUCT__entry(
__field(unsigned long, pc)
__field(u8, op)
__field(u8, state)
),
TP_fast_assign(
__entry->pc = vcpu->arch.pc;
__entry->op = op;
__entry->state = state;
),
TP_printk("%s %s PC: 0x%08lx",
__print_symbolic(__entry->op,
kvm_trace_symbol_aux_op),
__print_symbolic(__entry->state,
kvm_trace_symbol_aux_state),
__entry->pc)
);
#define KVM_TRACE_IOCSR_READ_UNSATISFIED 0
#define KVM_TRACE_IOCSR_READ 1
#define KVM_TRACE_IOCSR_WRITE 2
#define kvm_trace_symbol_iocsr \
{ KVM_TRACE_IOCSR_READ_UNSATISFIED, "unsatisfied-read" }, \
{ KVM_TRACE_IOCSR_READ, "read" }, \
{ KVM_TRACE_IOCSR_WRITE, "write" }
TRACE_EVENT(kvm_iocsr,
TP_PROTO(int type, int len, u64 gpa, void *val),
TP_ARGS(type, len, gpa, val),
TP_STRUCT__entry(
__field( u32, type )
__field( u32, len )
__field( u64, gpa )
__field( u64, val )
),
TP_fast_assign(
__entry->type = type;
__entry->len = len;
__entry->gpa = gpa;
__entry->val = 0;
if (val)
memcpy(&__entry->val, val,
min_t(u32, sizeof(__entry->val), len));
),
TP_printk("iocsr %s len %u gpa 0x%llx val 0x%llx",
__print_symbolic(__entry->type, kvm_trace_symbol_iocsr),
__entry->len, __entry->gpa, __entry->val)
);
TRACE_EVENT(kvm_vpid_change,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned long vpid),
TP_ARGS(vcpu, vpid),
TP_STRUCT__entry(
__field(unsigned long, vpid)
),
TP_fast_assign(
__entry->vpid = vpid;
),
TP_printk("VPID: 0x%08lx", __entry->vpid)
);
#endif /* _TRACE_KVM_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../arch/loongarch/kvm
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff
+212
View File
@@ -0,0 +1,212 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <linux/kvm_host.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_vcpu.h>
#include <asm/kvm_csr.h>
#include <asm/kvm_eiointc.h>
#include <asm/kvm_pch_pic.h>
const struct kvm_stats_desc kvm_vm_stats_desc[] = {
KVM_GENERIC_VM_STATS(),
STATS_DESC_ICOUNTER(VM, pages),
STATS_DESC_ICOUNTER(VM, hugepages),
};
const struct kvm_stats_header kvm_vm_stats_header = {
.name_size = KVM_STATS_NAME_SIZE,
.num_desc = ARRAY_SIZE(kvm_vm_stats_desc),
.id_offset = sizeof(struct kvm_stats_header),
.desc_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE,
.data_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE +
sizeof(kvm_vm_stats_desc),
};
static void kvm_vm_init_features(struct kvm *kvm)
{
unsigned long val;
if (cpu_has_lsx)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_LSX);
if (cpu_has_lasx)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_LASX);
if (cpu_has_lbt_x86)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_X86BT);
if (cpu_has_lbt_arm)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_ARMBT);
if (cpu_has_lbt_mips)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_MIPSBT);
if (cpu_has_ptw)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_PTW);
if (cpu_has_msgint)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_MSGINT);
val = read_csr_gcfg();
if (val & CSR_GCFG_GPMP)
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_PMU);
/* Enable all PV features by default */
kvm->arch.pv_features |= BIT(KVM_FEATURE_IPI);
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_PV_IPI);
if (kvm_pvtime_supported()) {
kvm->arch.pv_features |= BIT(KVM_FEATURE_PREEMPT);
kvm->arch.pv_features |= BIT(KVM_FEATURE_STEAL_TIME);
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_PV_PREEMPT);
kvm->arch.kvm_features |= BIT(KVM_LOONGARCH_VM_FEAT_PV_STEALTIME);
}
}
int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
{
int i;
/* Allocate page table to map GPA -> RPA */
kvm->arch.pgd = kvm_pgd_alloc();
if (!kvm->arch.pgd)
return -ENOMEM;
kvm->arch.phyid_map = kvzalloc_obj(struct kvm_phyid_map,
GFP_KERNEL_ACCOUNT);
if (!kvm->arch.phyid_map) {
free_page((unsigned long)kvm->arch.pgd);
kvm->arch.pgd = NULL;
return -ENOMEM;
}
spin_lock_init(&kvm->arch.phyid_map_lock);
kvm_init_vmcs(kvm);
kvm_vm_init_features(kvm);
/*
* cpu_vabits means user address space only (a half of total).
* GPA size of VM is the same with the size of user address space.
*/
kvm->arch.gpa_size = BIT(cpu_vabits);
kvm->arch.root_level = CONFIG_PGTABLE_LEVELS - 1;
kvm->arch.invalid_ptes[0] = 0;
kvm->arch.invalid_ptes[1] = (unsigned long)invalid_pte_table;
#if CONFIG_PGTABLE_LEVELS > 2
kvm->arch.invalid_ptes[2] = (unsigned long)invalid_pmd_table;
#endif
#if CONFIG_PGTABLE_LEVELS > 3
kvm->arch.invalid_ptes[3] = (unsigned long)invalid_pud_table;
#endif
for (i = 0; i <= kvm->arch.root_level; i++)
kvm->arch.pte_shifts[i] = PAGE_SHIFT + i * (PAGE_SHIFT - 3);
return 0;
}
void kvm_arch_destroy_vm(struct kvm *kvm)
{
kvm_destroy_vcpus(kvm);
free_page((unsigned long)kvm->arch.pgd);
kvm->arch.pgd = NULL;
kvfree(kvm->arch.phyid_map);
kvm->arch.phyid_map = NULL;
}
int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
{
int r;
switch (ext) {
case KVM_CAP_IRQCHIP:
case KVM_CAP_ONE_REG:
case KVM_CAP_ENABLE_CAP:
case KVM_CAP_READONLY_MEM:
case KVM_CAP_IMMEDIATE_EXIT:
case KVM_CAP_IOEVENTFD:
case KVM_CAP_MP_STATE:
case KVM_CAP_SET_GUEST_DEBUG:
r = 1;
break;
case KVM_CAP_NR_VCPUS:
r = min_t(unsigned int, num_online_cpus(), KVM_MAX_VCPUS);
break;
case KVM_CAP_MAX_VCPUS:
r = KVM_MAX_VCPUS;
break;
case KVM_CAP_MAX_VCPU_ID:
r = KVM_MAX_VCPU_IDS;
break;
case KVM_CAP_NR_MEMSLOTS:
r = KVM_USER_MEM_SLOTS;
break;
default:
r = 0;
break;
}
return r;
}
static int kvm_vm_feature_has_attr(struct kvm *kvm, struct kvm_device_attr *attr)
{
switch (attr->attr) {
case KVM_LOONGARCH_VM_FEAT_LSX:
case KVM_LOONGARCH_VM_FEAT_LASX:
case KVM_LOONGARCH_VM_FEAT_X86BT:
case KVM_LOONGARCH_VM_FEAT_ARMBT:
case KVM_LOONGARCH_VM_FEAT_MIPSBT:
case KVM_LOONGARCH_VM_FEAT_PTW:
case KVM_LOONGARCH_VM_FEAT_MSGINT:
case KVM_LOONGARCH_VM_FEAT_PMU:
case KVM_LOONGARCH_VM_FEAT_PV_IPI:
case KVM_LOONGARCH_VM_FEAT_PV_PREEMPT:
case KVM_LOONGARCH_VM_FEAT_PV_STEALTIME:
if (kvm_vm_support(&kvm->arch, attr->attr))
return 0;
return -ENXIO;
default:
return -ENXIO;
}
}
static int kvm_vm_has_attr(struct kvm *kvm, struct kvm_device_attr *attr)
{
switch (attr->group) {
case KVM_LOONGARCH_VM_FEAT_CTRL:
return kvm_vm_feature_has_attr(kvm, attr);
default:
return -ENXIO;
}
}
int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct kvm *kvm = filp->private_data;
struct kvm_device_attr attr;
switch (ioctl) {
case KVM_CREATE_IRQCHIP:
return 0;
case KVM_HAS_DEVICE_ATTR:
if (copy_from_user(&attr, argp, sizeof(attr)))
return -EFAULT;
return kvm_vm_has_attr(kvm, &attr);
default:
return -ENOIOCTLCMD;
}
}
int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_event, bool line_status)
{
if (!kvm_arch_irqchip_in_kernel(kvm))
return -ENXIO;
irq_event->status = kvm_set_irq(kvm, KVM_USERSPACE_IRQ_SOURCE_ID,
irq_event->irq, irq_event->level, line_status);
return 0;
}
bool kvm_arch_irqchip_in_kernel(struct kvm *kvm)
{
return (kvm->arch.ipi && kvm->arch.eiointc && kvm->arch.pch_pic);
}