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
+41
View File
@@ -0,0 +1,41 @@
# 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 CPU_SUPPORTS_VZ
depends on MIPS_FP_SUPPORT
select EXPORT_UASM
select KVM_COMMON
select KVM_GENERIC_DIRTYLOG_READ_PROTECT
select KVM_MMIO
select KVM_GENERIC_HARDWARE_ENABLING
select HAVE_KVM_READONLY_MEM
help
Support for hosting Guest kernels.
config KVM_MIPS_DEBUG_COP0_COUNTERS
bool "Maintain counters for COP0 accesses"
depends on KVM
help
Maintain statistics for Guest COP0 accesses.
A histogram of COP0 accesses is printed when the VM is
shutdown.
If unsure, say N.
endif # VIRTUALIZATION
+20
View File
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for KVM support for MIPS
#
include $(srctree)/virt/kvm/Makefile.kvm
ccflags-y += -Ivirt/kvm -Iarch/mips/kvm
kvm-$(CONFIG_CPU_HAS_MSA) += msa.o
kvm-y += mips.o emulate.o entry.o \
interrupt.o stats.o \
fpu.o
kvm-y += hypcall.o
kvm-y += mmu.o
kvm-$(CONFIG_CPU_LOONGSON64) += loongson_ipi.o
kvm-y += vz.o
obj-$(CONFIG_KVM) += kvm.o
obj-y += tlb.o
File diff suppressed because it is too large Load Diff
+863
View File
@@ -0,0 +1,863 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Generation of main entry point for the guest, exception handling.
*
* Copyright (C) 2012 MIPS Technologies, Inc.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*
* Copyright (C) 2016 Imagination Technologies Ltd.
*/
#include <linux/kvm_host.h>
#include <linux/log2.h>
#include <asm/mipsregs.h>
#include <asm/mmu_context.h>
#include <asm/msa.h>
#include <asm/regdef.h>
#include <asm/setup.h>
#include <asm/tlbex.h>
#include <asm/uasm.h>
#define CALLFRAME_SIZ 32
static unsigned int scratch_vcpu[2] = { C0_DDATALO };
static unsigned int scratch_tmp[2] = { C0_ERROREPC };
enum label_id {
label_fpu_1 = 1,
label_msa_1,
label_return_to_host,
label_kernel_asid,
label_exit_common,
};
UASM_L_LA(_fpu_1)
UASM_L_LA(_msa_1)
UASM_L_LA(_return_to_host)
UASM_L_LA(_kernel_asid)
UASM_L_LA(_exit_common)
static void *kvm_mips_build_enter_guest(void *addr);
static void *kvm_mips_build_ret_from_exit(void *addr);
static void *kvm_mips_build_ret_to_guest(void *addr);
static void *kvm_mips_build_ret_to_host(void *addr);
/*
* The version of this function in tlbex.c uses current_cpu_type(), but for KVM
* we assume symmetry.
*/
static int c0_kscratch(void)
{
return 31;
}
/**
* kvm_mips_entry_setup() - Perform global setup for entry code.
*
* Perform global setup for entry code, such as choosing a scratch register.
*
* Returns: 0 on success.
* -errno on failure.
*/
int kvm_mips_entry_setup(void)
{
/*
* We prefer to use KScratchN registers if they are available over the
* defaults above, which may not work on all cores.
*/
unsigned int kscratch_mask = cpu_data[0].kscratch_mask;
if (pgd_reg != -1)
kscratch_mask &= ~BIT(pgd_reg);
/* Pick a scratch register for storing VCPU */
if (kscratch_mask) {
scratch_vcpu[0] = c0_kscratch();
scratch_vcpu[1] = ffs(kscratch_mask) - 1;
kscratch_mask &= ~BIT(scratch_vcpu[1]);
}
/* Pick a scratch register to use as a temp for saving state */
if (kscratch_mask) {
scratch_tmp[0] = c0_kscratch();
scratch_tmp[1] = ffs(kscratch_mask) - 1;
kscratch_mask &= ~BIT(scratch_tmp[1]);
}
return 0;
}
static void kvm_mips_build_save_scratch(u32 **p, unsigned int tmp,
unsigned int frame)
{
/* Save the VCPU scratch register value in cp0_epc of the stack frame */
UASM_i_MFC0(p, tmp, scratch_vcpu[0], scratch_vcpu[1]);
UASM_i_SW(p, tmp, offsetof(struct pt_regs, cp0_epc), frame);
/* Save the temp scratch register value in cp0_cause of stack frame */
if (scratch_tmp[0] == c0_kscratch()) {
UASM_i_MFC0(p, tmp, scratch_tmp[0], scratch_tmp[1]);
UASM_i_SW(p, tmp, offsetof(struct pt_regs, cp0_cause), frame);
}
}
static void kvm_mips_build_restore_scratch(u32 **p, unsigned int tmp,
unsigned int frame)
{
/*
* Restore host scratch register values saved by
* kvm_mips_build_save_scratch().
*/
UASM_i_LW(p, tmp, offsetof(struct pt_regs, cp0_epc), frame);
UASM_i_MTC0(p, tmp, scratch_vcpu[0], scratch_vcpu[1]);
if (scratch_tmp[0] == c0_kscratch()) {
UASM_i_LW(p, tmp, offsetof(struct pt_regs, cp0_cause), frame);
UASM_i_MTC0(p, tmp, scratch_tmp[0], scratch_tmp[1]);
}
}
/**
* build_set_exc_base() - Assemble code to write exception base address.
* @p: Code buffer pointer.
* @reg: Source register (generated code may set WG bit in @reg).
*
* Assemble code to modify the exception base address in the EBase register,
* using the appropriately sized access and setting the WG bit if necessary.
*/
static inline void build_set_exc_base(u32 **p, unsigned int reg)
{
if (cpu_has_ebase_wg) {
/* Set WG so that all the bits get written */
uasm_i_ori(p, reg, reg, MIPS_EBASE_WG);
UASM_i_MTC0(p, reg, C0_EBASE);
} else {
uasm_i_mtc0(p, reg, C0_EBASE);
}
}
/**
* kvm_mips_build_vcpu_run() - Assemble function to start running a guest VCPU.
* @addr: Address to start writing code.
*
* Assemble the start of the vcpu_run function to run a guest VCPU. The function
* conforms to the following prototype:
*
* int vcpu_run(struct kvm_vcpu *vcpu);
*
* The exit from the guest and return to the caller is handled by the code
* generated by kvm_mips_build_ret_to_host().
*
* Returns: Next address after end of written function.
*/
void *kvm_mips_build_vcpu_run(void *addr)
{
u32 *p = addr;
unsigned int i;
/*
* GPR_A0: vcpu
*/
/* k0/k1 not being used in host kernel context */
UASM_i_ADDIU(&p, GPR_K1, GPR_SP, -(int)sizeof(struct pt_regs));
for (i = 16; i < 32; ++i) {
if (i == 24)
i = 28;
UASM_i_SW(&p, i, offsetof(struct pt_regs, regs[i]), GPR_K1);
}
/* Save host status */
uasm_i_mfc0(&p, GPR_V0, C0_STATUS);
UASM_i_SW(&p, GPR_V0, offsetof(struct pt_regs, cp0_status), GPR_K1);
/* Save scratch registers, will be used to store pointer to vcpu etc */
kvm_mips_build_save_scratch(&p, GPR_V1, GPR_K1);
/* VCPU scratch register has pointer to vcpu */
UASM_i_MTC0(&p, GPR_A0, scratch_vcpu[0], scratch_vcpu[1]);
/* Offset into vcpu->arch */
UASM_i_ADDIU(&p, GPR_K1, GPR_A0, offsetof(struct kvm_vcpu, arch));
/*
* Save the host stack to VCPU, used for exception processing
* when we exit from the Guest
*/
UASM_i_SW(&p, GPR_SP, offsetof(struct kvm_vcpu_arch, host_stack), GPR_K1);
/* Save the kernel gp as well */
UASM_i_SW(&p, GPR_GP, offsetof(struct kvm_vcpu_arch, host_gp), GPR_K1);
/*
* Setup status register for running the guest in UM, interrupts
* are disabled
*/
UASM_i_LA(&p, GPR_K0, ST0_EXL | KSU_USER | ST0_BEV | ST0_KX_IF_64);
uasm_i_mtc0(&p, GPR_K0, C0_STATUS);
uasm_i_ehb(&p);
/* load up the new EBASE */
UASM_i_LW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, guest_ebase), GPR_K1);
build_set_exc_base(&p, GPR_K0);
/*
* Now that the new EBASE has been loaded, unset BEV, set
* interrupt mask as it was but make sure that timer interrupts
* are enabled
*/
uasm_i_addiu(&p, GPR_K0, GPR_ZERO, ST0_EXL | KSU_USER | ST0_IE | ST0_KX_IF_64);
uasm_i_andi(&p, GPR_V0, GPR_V0, ST0_IM);
uasm_i_or(&p, GPR_K0, GPR_K0, GPR_V0);
uasm_i_mtc0(&p, GPR_K0, C0_STATUS);
uasm_i_ehb(&p);
p = kvm_mips_build_enter_guest(p);
return p;
}
/**
* kvm_mips_build_enter_guest() - Assemble code to resume guest execution.
* @addr: Address to start writing code.
*
* Assemble the code to resume guest execution. This code is common between the
* initial entry into the guest from the host, and returning from the exit
* handler back to the guest.
*
* Returns: Next address after end of written function.
*/
static void *kvm_mips_build_enter_guest(void *addr)
{
u32 *p = addr;
unsigned int i;
struct uasm_label labels[2];
struct uasm_reloc relocs[2];
struct uasm_label __maybe_unused *l = labels;
struct uasm_reloc __maybe_unused *r = relocs;
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
/* Set Guest EPC */
UASM_i_LW(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, pc), GPR_K1);
UASM_i_MTC0(&p, GPR_T0, C0_EPC);
/* Save normal linux process pgd (VZ guarantees pgd_reg is set) */
if (cpu_has_ldpte)
UASM_i_MFC0(&p, GPR_K0, C0_PWBASE);
else
UASM_i_MFC0(&p, GPR_K0, c0_kscratch(), pgd_reg);
UASM_i_SW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, host_pgd), GPR_K1);
/*
* Set up KVM GPA pgd.
* This does roughly the same as TLBMISS_HANDLER_SETUP_PGD():
* - call tlbmiss_handler_setup_pgd(mm->pgd)
* - write mm->pgd into CP0_PWBase
*
* We keep GPR_S0 pointing at struct kvm so we can load the ASID below.
*/
UASM_i_LW(&p, GPR_S0, (int)offsetof(struct kvm_vcpu, kvm) -
(int)offsetof(struct kvm_vcpu, arch), GPR_K1);
UASM_i_LW(&p, GPR_A0, offsetof(struct kvm, arch.gpa_mm.pgd), GPR_S0);
UASM_i_LA(&p, GPR_T9, (unsigned long)tlbmiss_handler_setup_pgd);
uasm_i_jalr(&p, GPR_RA, GPR_T9);
/* delay slot */
if (cpu_has_htw)
UASM_i_MTC0(&p, GPR_A0, C0_PWBASE);
else
uasm_i_nop(&p);
/* Set GM bit to setup eret to VZ guest context */
uasm_i_addiu(&p, GPR_V1, GPR_ZERO, 1);
uasm_i_mfc0(&p, GPR_K0, C0_GUESTCTL0);
uasm_i_ins(&p, GPR_K0, GPR_V1, MIPS_GCTL0_GM_SHIFT, 1);
uasm_i_mtc0(&p, GPR_K0, C0_GUESTCTL0);
if (cpu_has_guestid) {
/*
* Set root mode GuestID, so that root TLB refill handler can
* use the correct GuestID in the root TLB.
*/
/* Get current GuestID */
uasm_i_mfc0(&p, GPR_T0, C0_GUESTCTL1);
/* Set GuestCtl1.RID = GuestCtl1.ID */
uasm_i_ext(&p, GPR_T1, GPR_T0, MIPS_GCTL1_ID_SHIFT,
MIPS_GCTL1_ID_WIDTH);
uasm_i_ins(&p, GPR_T0, GPR_T1, MIPS_GCTL1_RID_SHIFT,
MIPS_GCTL1_RID_WIDTH);
uasm_i_mtc0(&p, GPR_T0, C0_GUESTCTL1);
/* GuestID handles dealiasing so we don't need to touch ASID */
goto skip_asid_restore;
}
/* Root ASID Dealias (RAD) */
/* Save host ASID */
UASM_i_MFC0(&p, GPR_K0, C0_ENTRYHI);
UASM_i_SW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, host_entryhi),
GPR_K1);
/* Set the root ASID for the Guest */
UASM_i_ADDIU(&p, GPR_T1, GPR_S0,
offsetof(struct kvm, arch.gpa_mm.context.asid));
/* t1: contains the base of the ASID array, need to get the cpu id */
/* smp_processor_id */
uasm_i_lw(&p, GPR_T2, offsetof(struct thread_info, cpu), GPR_GP);
/* index the ASID array */
uasm_i_sll(&p, GPR_T2, GPR_T2, ilog2(sizeof(long)));
UASM_i_ADDU(&p, GPR_T3, GPR_T1, GPR_T2);
UASM_i_LW(&p, GPR_K0, 0, GPR_T3);
#ifdef CONFIG_MIPS_ASID_BITS_VARIABLE
/*
* reuse ASID array offset
* cpuinfo_mips is a multiple of sizeof(long)
*/
uasm_i_addiu(&p, GPR_T3, GPR_ZERO, sizeof(struct cpuinfo_mips)/sizeof(long));
uasm_i_mul(&p, GPR_T2, GPR_T2, GPR_T3);
UASM_i_LA_mostly(&p, GPR_AT, (long)&cpu_data[0].asid_mask);
UASM_i_ADDU(&p, GPR_AT, GPR_AT, GPR_T2);
UASM_i_LW(&p, GPR_T2, uasm_rel_lo((long)&cpu_data[0].asid_mask), GPR_AT);
uasm_i_and(&p, GPR_K0, GPR_K0, GPR_T2);
#else
uasm_i_andi(&p, GPR_K0, GPR_K0, MIPS_ENTRYHI_ASID);
#endif
/* Set up KVM VZ root ASID (!guestid) */
uasm_i_mtc0(&p, GPR_K0, C0_ENTRYHI);
skip_asid_restore:
uasm_i_ehb(&p);
/* Disable RDHWR access */
uasm_i_mtc0(&p, GPR_ZERO, C0_HWRENA);
/* load the guest context from VCPU and return */
for (i = 1; i < 32; ++i) {
/* Guest k0/k1 loaded later */
if (i == GPR_K0 || i == GPR_K1)
continue;
UASM_i_LW(&p, i, offsetof(struct kvm_vcpu_arch, gprs[i]), GPR_K1);
}
#ifndef CONFIG_CPU_MIPSR6
/* Restore hi/lo */
UASM_i_LW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, hi), GPR_K1);
uasm_i_mthi(&p, GPR_K0);
UASM_i_LW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, lo), GPR_K1);
uasm_i_mtlo(&p, GPR_K0);
#endif
/* Restore the guest's k0/k1 registers */
UASM_i_LW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, gprs[GPR_K0]), GPR_K1);
UASM_i_LW(&p, GPR_K1, offsetof(struct kvm_vcpu_arch, gprs[GPR_K1]), GPR_K1);
/* Jump to guest */
uasm_i_eret(&p);
uasm_resolve_relocs(relocs, labels);
return p;
}
/**
* kvm_mips_build_tlb_refill_exception() - Assemble TLB refill handler.
* @addr: Address to start writing code.
* @handler: Address of common handler (within range of @addr).
*
* Assemble TLB refill exception fast path handler for guest execution.
*
* Returns: Next address after end of written function.
*/
void *kvm_mips_build_tlb_refill_exception(void *addr, void *handler)
{
u32 *p = addr;
struct uasm_label labels[2];
struct uasm_reloc relocs[2];
#ifndef CONFIG_CPU_LOONGSON64
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
#endif
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
/* Save guest k1 into scratch register */
UASM_i_MTC0(&p, GPR_K1, scratch_tmp[0], scratch_tmp[1]);
/* Get the VCPU pointer from the VCPU scratch register */
UASM_i_MFC0(&p, GPR_K1, scratch_vcpu[0], scratch_vcpu[1]);
/* Save guest k0 into VCPU structure */
UASM_i_SW(&p, GPR_K0, offsetof(struct kvm_vcpu, arch.gprs[GPR_K0]), GPR_K1);
/*
* Some of the common tlbex code uses current_cpu_type(). For KVM we
* assume symmetry and just disable preemption to silence the warning.
*/
preempt_disable();
#ifdef CONFIG_CPU_LOONGSON64
UASM_i_MFC0(&p, GPR_K1, C0_PGD);
uasm_i_lddir(&p, GPR_K0, GPR_K1, 3); /* global page dir */
#ifndef __PAGETABLE_PMD_FOLDED
uasm_i_lddir(&p, GPR_K1, GPR_K0, 1); /* middle page dir */
#endif
uasm_i_ldpte(&p, GPR_K1, 0); /* even */
uasm_i_ldpte(&p, GPR_K1, 1); /* odd */
uasm_i_tlbwr(&p);
#else
/*
* Now for the actual refill bit. A lot of this can be common with the
* Linux TLB refill handler, however we don't need to handle so many
* cases. We only need to handle user mode refills, and user mode runs
* with 32-bit addressing.
*
* Therefore the branch to label_vmalloc generated by build_get_pmde64()
* that isn't resolved should never actually get taken and is harmless
* to leave in place for now.
*/
#ifdef CONFIG_64BIT
build_get_pmde64(&p, &l, &r, GPR_K0, GPR_K1); /* get pmd in GPR_K1 */
#else
build_get_pgde32(&p, GPR_K0, GPR_K1); /* get pgd in GPR_K1 */
#endif
/* we don't support huge pages yet */
build_get_ptep(&p, GPR_K0, GPR_K1);
build_update_entries(&p, GPR_K0, GPR_K1);
build_tlb_write_entry(&p, &l, &r, tlb_random);
#endif
preempt_enable();
/* Get the VCPU pointer from the VCPU scratch register again */
UASM_i_MFC0(&p, GPR_K1, scratch_vcpu[0], scratch_vcpu[1]);
/* Restore the guest's k0/k1 registers */
UASM_i_LW(&p, GPR_K0, offsetof(struct kvm_vcpu, arch.gprs[GPR_K0]), GPR_K1);
uasm_i_ehb(&p);
UASM_i_MFC0(&p, GPR_K1, scratch_tmp[0], scratch_tmp[1]);
/* Jump to guest */
uasm_i_eret(&p);
return p;
}
/**
* kvm_mips_build_exception() - Assemble first level guest exception handler.
* @addr: Address to start writing code.
* @handler: Address of common handler (within range of @addr).
*
* Assemble exception vector code for guest execution. The generated vector will
* branch to the common exception handler generated by kvm_mips_build_exit().
*
* Returns: Next address after end of written function.
*/
void *kvm_mips_build_exception(void *addr, void *handler)
{
u32 *p = addr;
struct uasm_label labels[2];
struct uasm_reloc relocs[2];
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
/* Save guest k1 into scratch register */
UASM_i_MTC0(&p, GPR_K1, scratch_tmp[0], scratch_tmp[1]);
/* Get the VCPU pointer from the VCPU scratch register */
UASM_i_MFC0(&p, GPR_K1, scratch_vcpu[0], scratch_vcpu[1]);
UASM_i_ADDIU(&p, GPR_K1, GPR_K1, offsetof(struct kvm_vcpu, arch));
/* Save guest k0 into VCPU structure */
UASM_i_SW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, gprs[GPR_K0]), GPR_K1);
/* Branch to the common handler */
uasm_il_b(&p, &r, label_exit_common);
uasm_i_nop(&p);
uasm_l_exit_common(&l, handler);
uasm_resolve_relocs(relocs, labels);
return p;
}
/**
* kvm_mips_build_exit() - Assemble common guest exit handler.
* @addr: Address to start writing code.
*
* Assemble the generic guest exit handling code. This is called by the
* exception vectors (generated by kvm_mips_build_exception()), and calls
* kvm_mips_handle_exit(), then either resumes the guest or returns to the host
* depending on the return value.
*
* Returns: Next address after end of written function.
*/
void *kvm_mips_build_exit(void *addr)
{
u32 *p = addr;
unsigned int i;
struct uasm_label labels[3];
struct uasm_reloc relocs[3];
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
/*
* Generic Guest exception handler. We end up here when the guest
* does something that causes a trap to kernel mode.
*
* Both k0/k1 registers will have already been saved (k0 into the vcpu
* structure, and k1 into the scratch_tmp register).
*
* The k1 register will already contain the kvm_vcpu_arch pointer.
*/
/* Start saving Guest context to VCPU */
for (i = 0; i < 32; ++i) {
/* Guest k0/k1 saved later */
if (i == GPR_K0 || i == GPR_K1)
continue;
UASM_i_SW(&p, i, offsetof(struct kvm_vcpu_arch, gprs[i]), GPR_K1);
}
#ifndef CONFIG_CPU_MIPSR6
/* We need to save hi/lo and restore them on the way out */
uasm_i_mfhi(&p, GPR_T0);
UASM_i_SW(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, hi), GPR_K1);
uasm_i_mflo(&p, GPR_T0);
UASM_i_SW(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, lo), GPR_K1);
#endif
/* Finally save guest k1 to VCPU */
uasm_i_ehb(&p);
UASM_i_MFC0(&p, GPR_T0, scratch_tmp[0], scratch_tmp[1]);
UASM_i_SW(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, gprs[GPR_K1]), GPR_K1);
/* Now that context has been saved, we can use other registers */
/* Restore vcpu */
UASM_i_MFC0(&p, GPR_S0, scratch_vcpu[0], scratch_vcpu[1]);
/*
* Save Host level EPC, BadVaddr and Cause to VCPU, useful to process
* the exception
*/
UASM_i_MFC0(&p, GPR_K0, C0_EPC);
UASM_i_SW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, pc), GPR_K1);
UASM_i_MFC0(&p, GPR_K0, C0_BADVADDR);
UASM_i_SW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, host_cp0_badvaddr),
GPR_K1);
uasm_i_mfc0(&p, GPR_K0, C0_CAUSE);
uasm_i_sw(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, host_cp0_cause), GPR_K1);
if (cpu_has_badinstr) {
uasm_i_mfc0(&p, GPR_K0, C0_BADINSTR);
uasm_i_sw(&p, GPR_K0, offsetof(struct kvm_vcpu_arch,
host_cp0_badinstr), GPR_K1);
}
if (cpu_has_badinstrp) {
uasm_i_mfc0(&p, GPR_K0, C0_BADINSTRP);
uasm_i_sw(&p, GPR_K0, offsetof(struct kvm_vcpu_arch,
host_cp0_badinstrp), GPR_K1);
}
/* Now restore the host state just enough to run the handlers */
/* Switch EBASE to the one used by Linux */
/* load up the host EBASE */
uasm_i_mfc0(&p, GPR_V0, C0_STATUS);
uasm_i_lui(&p, GPR_AT, ST0_BEV >> 16);
uasm_i_or(&p, GPR_K0, GPR_V0, GPR_AT);
uasm_i_mtc0(&p, GPR_K0, C0_STATUS);
uasm_i_ehb(&p);
UASM_i_LA_mostly(&p, GPR_K0, (long)&ebase);
UASM_i_LW(&p, GPR_K0, uasm_rel_lo((long)&ebase), GPR_K0);
build_set_exc_base(&p, GPR_K0);
if (raw_cpu_has_fpu) {
/*
* If FPU is enabled, save FCR31 and clear it so that later
* ctc1's don't trigger FPE for pending exceptions.
*/
uasm_i_lui(&p, GPR_AT, ST0_CU1 >> 16);
uasm_i_and(&p, GPR_V1, GPR_V0, GPR_AT);
uasm_il_beqz(&p, &r, GPR_V1, label_fpu_1);
uasm_i_nop(&p);
uasm_i_cfc1(&p, GPR_T0, 31);
uasm_i_sw(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, fpu.fcr31),
GPR_K1);
uasm_i_ctc1(&p, GPR_ZERO, 31);
uasm_l_fpu_1(&l, p);
}
if (cpu_has_msa) {
/*
* If MSA is enabled, save MSACSR and clear it so that later
* instructions don't trigger MSAFPE for pending exceptions.
*/
uasm_i_mfc0(&p, GPR_T0, C0_CONFIG5);
uasm_i_ext(&p, GPR_T0, GPR_T0, 27, 1); /* MIPS_CONF5_MSAEN */
uasm_il_beqz(&p, &r, GPR_T0, label_msa_1);
uasm_i_nop(&p);
uasm_i_cfcmsa(&p, GPR_T0, MSA_CSR);
uasm_i_sw(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, fpu.msacsr),
GPR_K1);
uasm_i_ctcmsa(&p, MSA_CSR, GPR_ZERO);
uasm_l_msa_1(&l, p);
}
/* Restore host ASID */
if (!cpu_has_guestid) {
UASM_i_LW(&p, GPR_K0, offsetof(struct kvm_vcpu_arch, host_entryhi),
GPR_K1);
UASM_i_MTC0(&p, GPR_K0, C0_ENTRYHI);
}
/*
* Set up normal Linux process pgd.
* This does roughly the same as TLBMISS_HANDLER_SETUP_PGD():
* - call tlbmiss_handler_setup_pgd(mm->pgd)
* - write mm->pgd into CP0_PWBase
*/
UASM_i_LW(&p, GPR_A0,
offsetof(struct kvm_vcpu_arch, host_pgd), GPR_K1);
UASM_i_LA(&p, GPR_T9, (unsigned long)tlbmiss_handler_setup_pgd);
uasm_i_jalr(&p, GPR_RA, GPR_T9);
/* delay slot */
if (cpu_has_htw)
UASM_i_MTC0(&p, GPR_A0, C0_PWBASE);
else
uasm_i_nop(&p);
/* Clear GM bit so we don't enter guest mode when EXL is cleared */
uasm_i_mfc0(&p, GPR_K0, C0_GUESTCTL0);
uasm_i_ins(&p, GPR_K0, GPR_ZERO, MIPS_GCTL0_GM_SHIFT, 1);
uasm_i_mtc0(&p, GPR_K0, C0_GUESTCTL0);
/* Save GuestCtl0 so we can access GExcCode after CPU migration */
uasm_i_sw(&p, GPR_K0,
offsetof(struct kvm_vcpu_arch, host_cp0_guestctl0), GPR_K1);
if (cpu_has_guestid) {
/*
* Clear root mode GuestID, so that root TLB operations use the
* root GuestID in the root TLB.
*/
uasm_i_mfc0(&p, GPR_T0, C0_GUESTCTL1);
/* Set GuestCtl1.RID = MIPS_GCTL1_ROOT_GUESTID (i.e. 0) */
uasm_i_ins(&p, GPR_T0, GPR_ZERO, MIPS_GCTL1_RID_SHIFT,
MIPS_GCTL1_RID_WIDTH);
uasm_i_mtc0(&p, GPR_T0, C0_GUESTCTL1);
}
/* Now that the new EBASE has been loaded, unset BEV and KSU_USER */
uasm_i_addiu(&p, GPR_AT, GPR_ZERO, ~(ST0_EXL | KSU_USER | ST0_IE));
uasm_i_and(&p, GPR_V0, GPR_V0, GPR_AT);
uasm_i_lui(&p, GPR_AT, ST0_CU0 >> 16);
uasm_i_or(&p, GPR_V0, GPR_V0, GPR_AT);
#ifdef CONFIG_64BIT
uasm_i_ori(&p, GPR_V0, GPR_V0, ST0_SX | ST0_UX);
#endif
uasm_i_mtc0(&p, GPR_V0, C0_STATUS);
uasm_i_ehb(&p);
/* Load up host GPR_GP */
UASM_i_LW(&p, GPR_GP, offsetof(struct kvm_vcpu_arch, host_gp), GPR_K1);
/* Need a stack before we can jump to "C" */
UASM_i_LW(&p, GPR_SP, offsetof(struct kvm_vcpu_arch, host_stack), GPR_K1);
/* Saved host state */
UASM_i_ADDIU(&p, GPR_SP, GPR_SP, -(int)sizeof(struct pt_regs));
/*
* XXXKYMA do we need to load the host ASID, maybe not because the
* kernel entries are marked GLOBAL, need to verify
*/
/* Restore host scratch registers, as we'll have clobbered them */
kvm_mips_build_restore_scratch(&p, GPR_K0, GPR_SP);
/* Restore RDHWR access */
UASM_i_LA_mostly(&p, GPR_K0, (long)&hwrena);
uasm_i_lw(&p, GPR_K0, uasm_rel_lo((long)&hwrena), GPR_K0);
uasm_i_mtc0(&p, GPR_K0, C0_HWRENA);
/* Jump to handler */
/*
* XXXKYMA: not sure if this is safe, how large is the stack??
* Now jump to the kvm_mips_handle_exit() to see if we can deal
* with this in the kernel
*/
uasm_i_move(&p, GPR_A0, GPR_S0);
UASM_i_LA(&p, GPR_T9, (unsigned long)kvm_mips_handle_exit);
uasm_i_jalr(&p, GPR_RA, GPR_T9);
UASM_i_ADDIU(&p, GPR_SP, GPR_SP, -CALLFRAME_SIZ);
uasm_resolve_relocs(relocs, labels);
p = kvm_mips_build_ret_from_exit(p);
return p;
}
/**
* kvm_mips_build_ret_from_exit() - Assemble guest exit return handler.
* @addr: Address to start writing code.
*
* Assemble the code to handle the return from kvm_mips_handle_exit(), either
* resuming the guest or returning to the host depending on the return value.
*
* Returns: Next address after end of written function.
*/
static void *kvm_mips_build_ret_from_exit(void *addr)
{
u32 *p = addr;
struct uasm_label labels[2];
struct uasm_reloc relocs[2];
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
/* Return from handler Make sure interrupts are disabled */
uasm_i_di(&p, GPR_ZERO);
uasm_i_ehb(&p);
/*
* XXXKYMA: k0/k1 could have been blown away if we processed
* an exception while we were handling the exception from the
* guest, reload k1
*/
uasm_i_move(&p, GPR_K1, GPR_S0);
UASM_i_ADDIU(&p, GPR_K1, GPR_K1, offsetof(struct kvm_vcpu, arch));
/*
* Check return value, should tell us if we are returning to the
* host (handle I/O etc)or resuming the guest
*/
uasm_i_andi(&p, GPR_T0, GPR_V0, RESUME_HOST);
uasm_il_bnez(&p, &r, GPR_T0, label_return_to_host);
uasm_i_nop(&p);
p = kvm_mips_build_ret_to_guest(p);
uasm_l_return_to_host(&l, p);
p = kvm_mips_build_ret_to_host(p);
uasm_resolve_relocs(relocs, labels);
return p;
}
/**
* kvm_mips_build_ret_to_guest() - Assemble code to return to the guest.
* @addr: Address to start writing code.
*
* Assemble the code to handle return from the guest exit handler
* (kvm_mips_handle_exit()) back to the guest.
*
* Returns: Next address after end of written function.
*/
static void *kvm_mips_build_ret_to_guest(void *addr)
{
u32 *p = addr;
/* Put the saved pointer to vcpu (s0) back into the scratch register */
UASM_i_MTC0(&p, GPR_S0, scratch_vcpu[0], scratch_vcpu[1]);
/* Load up the Guest EBASE to minimize the window where BEV is set */
UASM_i_LW(&p, GPR_T0, offsetof(struct kvm_vcpu_arch, guest_ebase), GPR_K1);
/* Switch EBASE back to the one used by KVM */
uasm_i_mfc0(&p, GPR_V1, C0_STATUS);
uasm_i_lui(&p, GPR_AT, ST0_BEV >> 16);
uasm_i_or(&p, GPR_K0, GPR_V1, GPR_AT);
uasm_i_mtc0(&p, GPR_K0, C0_STATUS);
uasm_i_ehb(&p);
build_set_exc_base(&p, GPR_T0);
/* Setup status register for running guest in UM */
uasm_i_ori(&p, GPR_V1, GPR_V1, ST0_EXL | KSU_USER | ST0_IE);
UASM_i_LA(&p, GPR_AT, ~(ST0_CU0 | ST0_MX | ST0_SX | ST0_UX));
uasm_i_and(&p, GPR_V1, GPR_V1, GPR_AT);
uasm_i_mtc0(&p, GPR_V1, C0_STATUS);
uasm_i_ehb(&p);
p = kvm_mips_build_enter_guest(p);
return p;
}
/**
* kvm_mips_build_ret_to_host() - Assemble code to return to the host.
* @addr: Address to start writing code.
*
* Assemble the code to handle return from the guest exit handler
* (kvm_mips_handle_exit()) back to the host, i.e. to the caller of the vcpu_run
* function generated by kvm_mips_build_vcpu_run().
*
* Returns: Next address after end of written function.
*/
static void *kvm_mips_build_ret_to_host(void *addr)
{
u32 *p = addr;
unsigned int i;
/* EBASE is already pointing to Linux */
UASM_i_LW(&p, GPR_K1, offsetof(struct kvm_vcpu_arch, host_stack), GPR_K1);
UASM_i_ADDIU(&p, GPR_K1, GPR_K1, -(int)sizeof(struct pt_regs));
/*
* r2/v0 is the return code, shift it down by 2 (arithmetic)
* to recover the err code
*/
uasm_i_sra(&p, GPR_K0, GPR_V0, 2);
uasm_i_move(&p, GPR_V0, GPR_K0);
/* Load context saved on the host stack */
for (i = 16; i < 31; ++i) {
if (i == 24)
i = 28;
UASM_i_LW(&p, i, offsetof(struct pt_regs, regs[i]), GPR_K1);
}
/* Restore RDHWR access */
UASM_i_LA_mostly(&p, GPR_K0, (long)&hwrena);
uasm_i_lw(&p, GPR_K0, uasm_rel_lo((long)&hwrena), GPR_K0);
uasm_i_mtc0(&p, GPR_K0, C0_HWRENA);
/* Restore GPR_RA, which is the address we will return to */
UASM_i_LW(&p, GPR_RA, offsetof(struct pt_regs, regs[GPR_RA]), GPR_K1);
uasm_i_jr(&p, GPR_RA);
uasm_i_nop(&p);
return p;
}
+125
View File
@@ -0,0 +1,125 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* FPU context handling code for KVM.
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*/
#include <asm/asm.h>
#include <asm/asm-offsets.h>
#include <asm/fpregdef.h>
#include <asm/mipsregs.h>
#include <asm/regdef.h>
/* preprocessor replaces the fp in ".set fp=64" with $30 otherwise */
#undef fp
.set noreorder
.set noat
LEAF(__kvm_save_fpu)
.set push
.set hardfloat
.set fp=64
mfc0 t0, CP0_STATUS
sll t0, t0, 5 # is Status.FR set?
bgez t0, 1f # no: skip odd doubles
nop
sdc1 $f1, VCPU_FPR1(a0)
sdc1 $f3, VCPU_FPR3(a0)
sdc1 $f5, VCPU_FPR5(a0)
sdc1 $f7, VCPU_FPR7(a0)
sdc1 $f9, VCPU_FPR9(a0)
sdc1 $f11, VCPU_FPR11(a0)
sdc1 $f13, VCPU_FPR13(a0)
sdc1 $f15, VCPU_FPR15(a0)
sdc1 $f17, VCPU_FPR17(a0)
sdc1 $f19, VCPU_FPR19(a0)
sdc1 $f21, VCPU_FPR21(a0)
sdc1 $f23, VCPU_FPR23(a0)
sdc1 $f25, VCPU_FPR25(a0)
sdc1 $f27, VCPU_FPR27(a0)
sdc1 $f29, VCPU_FPR29(a0)
sdc1 $f31, VCPU_FPR31(a0)
1: sdc1 $f0, VCPU_FPR0(a0)
sdc1 $f2, VCPU_FPR2(a0)
sdc1 $f4, VCPU_FPR4(a0)
sdc1 $f6, VCPU_FPR6(a0)
sdc1 $f8, VCPU_FPR8(a0)
sdc1 $f10, VCPU_FPR10(a0)
sdc1 $f12, VCPU_FPR12(a0)
sdc1 $f14, VCPU_FPR14(a0)
sdc1 $f16, VCPU_FPR16(a0)
sdc1 $f18, VCPU_FPR18(a0)
sdc1 $f20, VCPU_FPR20(a0)
sdc1 $f22, VCPU_FPR22(a0)
sdc1 $f24, VCPU_FPR24(a0)
sdc1 $f26, VCPU_FPR26(a0)
sdc1 $f28, VCPU_FPR28(a0)
jr ra
sdc1 $f30, VCPU_FPR30(a0)
.set pop
END(__kvm_save_fpu)
LEAF(__kvm_restore_fpu)
.set push
.set hardfloat
.set fp=64
mfc0 t0, CP0_STATUS
sll t0, t0, 5 # is Status.FR set?
bgez t0, 1f # no: skip odd doubles
nop
ldc1 $f1, VCPU_FPR1(a0)
ldc1 $f3, VCPU_FPR3(a0)
ldc1 $f5, VCPU_FPR5(a0)
ldc1 $f7, VCPU_FPR7(a0)
ldc1 $f9, VCPU_FPR9(a0)
ldc1 $f11, VCPU_FPR11(a0)
ldc1 $f13, VCPU_FPR13(a0)
ldc1 $f15, VCPU_FPR15(a0)
ldc1 $f17, VCPU_FPR17(a0)
ldc1 $f19, VCPU_FPR19(a0)
ldc1 $f21, VCPU_FPR21(a0)
ldc1 $f23, VCPU_FPR23(a0)
ldc1 $f25, VCPU_FPR25(a0)
ldc1 $f27, VCPU_FPR27(a0)
ldc1 $f29, VCPU_FPR29(a0)
ldc1 $f31, VCPU_FPR31(a0)
1: ldc1 $f0, VCPU_FPR0(a0)
ldc1 $f2, VCPU_FPR2(a0)
ldc1 $f4, VCPU_FPR4(a0)
ldc1 $f6, VCPU_FPR6(a0)
ldc1 $f8, VCPU_FPR8(a0)
ldc1 $f10, VCPU_FPR10(a0)
ldc1 $f12, VCPU_FPR12(a0)
ldc1 $f14, VCPU_FPR14(a0)
ldc1 $f16, VCPU_FPR16(a0)
ldc1 $f18, VCPU_FPR18(a0)
ldc1 $f20, VCPU_FPR20(a0)
ldc1 $f22, VCPU_FPR22(a0)
ldc1 $f24, VCPU_FPR24(a0)
ldc1 $f26, VCPU_FPR26(a0)
ldc1 $f28, VCPU_FPR28(a0)
jr ra
ldc1 $f30, VCPU_FPR30(a0)
.set pop
END(__kvm_restore_fpu)
LEAF(__kvm_restore_fcsr)
.set push
.set hardfloat
lw t0, VCPU_FCR31(a0)
/*
* The ctc1 must stay at this offset in __kvm_restore_fcsr.
* See kvm_mips_csr_die_notify() which handles t0 containing a value
* which triggers an FP Exception, which must be stepped over and
* ignored since the set cause bits must remain there for the guest.
*/
ctc1 t0, fcr31
jr ra
nop
.set pop
END(__kvm_restore_fcsr)
+53
View File
@@ -0,0 +1,53 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS: Hypercall handling.
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*/
#include <linux/kernel.h>
#include <linux/kvm_host.h>
#include <linux/kvm_para.h>
#define MAX_HYPCALL_ARGS 4
enum emulation_result kvm_mips_emul_hypcall(struct kvm_vcpu *vcpu,
union mips_instruction inst)
{
unsigned int code = (inst.co_format.code >> 5) & 0x3ff;
kvm_debug("[%#lx] HYPCALL %#03x\n", vcpu->arch.pc, code);
switch (code) {
case 0:
return EMULATE_HYPERCALL;
default:
return EMULATE_FAIL;
};
}
static int kvm_mips_hypercall(struct kvm_vcpu *vcpu, unsigned long num,
const unsigned long *args, unsigned long *hret)
{
/* Report unimplemented hypercall to guest */
*hret = -KVM_ENOSYS;
return RESUME_GUEST;
}
int kvm_mips_handle_hypcall(struct kvm_vcpu *vcpu)
{
unsigned long num, args[MAX_HYPCALL_ARGS];
/* read hypcall number and arguments */
num = vcpu->arch.gprs[2]; /* v0 */
args[0] = vcpu->arch.gprs[4]; /* a0 */
args[1] = vcpu->arch.gprs[5]; /* a1 */
args[2] = vcpu->arch.gprs[6]; /* a2 */
args[3] = vcpu->arch.gprs[7]; /* a3 */
return kvm_mips_hypercall(vcpu, num,
args, &vcpu->arch.gprs[2] /* v0 */);
}
+40
View File
@@ -0,0 +1,40 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS: Interrupt delivery
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/memblock.h>
#include <asm/page.h>
#include <asm/cacheflush.h>
#include <linux/kvm_host.h>
#include "interrupt.h"
void kvm_mips_deliver_interrupts(struct kvm_vcpu *vcpu, u32 cause)
{
unsigned long *pending = &vcpu->arch.pending_exceptions;
unsigned long *pending_clr = &vcpu->arch.pending_exceptions_clr;
unsigned int priority;
for_each_set_bit(priority, pending_clr, MIPS_EXC_MAX + 1)
kvm_mips_callbacks->irq_clear(vcpu, priority, cause);
for_each_set_bit(priority, pending, MIPS_EXC_MAX + 1)
kvm_mips_callbacks->irq_deliver(vcpu, priority, cause);
}
int kvm_mips_pending_timer(struct kvm_vcpu *vcpu)
{
return test_bit(MIPS_EXC_INT_TIMER, &vcpu->arch.pending_exceptions);
}
+43
View File
@@ -0,0 +1,43 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS: Interrupts
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
/*
* MIPS Exception Priorities, exceptions (including interrupts) are queued up
* for the guest in the order specified by their priorities
*/
#define MIPS_EXC_RESET 0
#define MIPS_EXC_SRESET 1
#define MIPS_EXC_DEBUG_ST 2
#define MIPS_EXC_DEBUG 3
#define MIPS_EXC_DDB 4
#define MIPS_EXC_NMI 5
#define MIPS_EXC_MCHK 6
#define MIPS_EXC_INT_TIMER 7
#define MIPS_EXC_INT_IO_1 8
#define MIPS_EXC_INT_IO_2 9
#define MIPS_EXC_EXECUTE 10
#define MIPS_EXC_INT_IPI_1 11
#define MIPS_EXC_INT_IPI_2 12
#define MIPS_EXC_MAX 13
/* XXXSL More to follow */
#define C_TI (_ULCAST_(1) << 30)
extern u32 *kvm_priority_to_irq;
u32 kvm_irq_to_priority(u32 irq);
int kvm_mips_pending_timer(struct kvm_vcpu *vcpu);
void kvm_mips_deliver_interrupts(struct kvm_vcpu *vcpu, u32 cause);
#ifdef CONFIG_CPU_LOONGSON64
extern void kvm_init_loongson_ipi(struct kvm *kvm);
#endif
+216
View File
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Loongson-3 Virtual IPI interrupt support.
*
* Copyright (C) 2019 Loongson Technologies, Inc. All rights reserved.
*
* Authors: Chen Zhu <zhuchen@loongson.cn>
* Authors: Huacai Chen <chenhc@lemote.com>
*/
#include <linux/kvm_host.h>
#include "interrupt.h"
#define IPI_BASE 0x3ff01000ULL
#define CORE0_STATUS_OFF 0x000
#define CORE0_EN_OFF 0x004
#define CORE0_SET_OFF 0x008
#define CORE0_CLEAR_OFF 0x00c
#define CORE0_BUF_20 0x020
#define CORE0_BUF_28 0x028
#define CORE0_BUF_30 0x030
#define CORE0_BUF_38 0x038
#define CORE1_STATUS_OFF 0x100
#define CORE1_EN_OFF 0x104
#define CORE1_SET_OFF 0x108
#define CORE1_CLEAR_OFF 0x10c
#define CORE1_BUF_20 0x120
#define CORE1_BUF_28 0x128
#define CORE1_BUF_30 0x130
#define CORE1_BUF_38 0x138
#define CORE2_STATUS_OFF 0x200
#define CORE2_EN_OFF 0x204
#define CORE2_SET_OFF 0x208
#define CORE2_CLEAR_OFF 0x20c
#define CORE2_BUF_20 0x220
#define CORE2_BUF_28 0x228
#define CORE2_BUF_30 0x230
#define CORE2_BUF_38 0x238
#define CORE3_STATUS_OFF 0x300
#define CORE3_EN_OFF 0x304
#define CORE3_SET_OFF 0x308
#define CORE3_CLEAR_OFF 0x30c
#define CORE3_BUF_20 0x320
#define CORE3_BUF_28 0x328
#define CORE3_BUF_30 0x330
#define CORE3_BUF_38 0x338
static int loongson_vipi_read(struct loongson_kvm_ipi *ipi,
gpa_t addr, int len, void *val)
{
uint32_t core = (addr >> 8) & 3;
uint32_t node = (addr >> 44) & 3;
uint32_t id = core + node * 4;
uint64_t offset = addr & 0xff;
void *pbuf;
struct ipi_state *s = &(ipi->ipistate[id]);
BUG_ON(offset & (len - 1));
switch (offset) {
case CORE0_STATUS_OFF:
*(uint64_t *)val = s->status;
break;
case CORE0_EN_OFF:
*(uint64_t *)val = s->en;
break;
case CORE0_SET_OFF:
*(uint64_t *)val = 0;
break;
case CORE0_CLEAR_OFF:
*(uint64_t *)val = 0;
break;
case CORE0_BUF_20 ... CORE0_BUF_38:
pbuf = (void *)s->buf + (offset - 0x20);
if (len == 8)
*(uint64_t *)val = *(uint64_t *)pbuf;
else /* Assume len == 4 */
*(uint32_t *)val = *(uint32_t *)pbuf;
break;
default:
pr_notice("%s with unknown addr %llx\n", __func__, addr);
break;
}
return 0;
}
static int loongson_vipi_write(struct loongson_kvm_ipi *ipi,
gpa_t addr, int len, const void *val)
{
uint32_t core = (addr >> 8) & 3;
uint32_t node = (addr >> 44) & 3;
uint32_t id = core + node * 4;
uint64_t data, offset = addr & 0xff;
void *pbuf;
struct kvm *kvm = ipi->kvm;
struct kvm_mips_interrupt irq;
struct ipi_state *s = &(ipi->ipistate[id]);
data = *(uint64_t *)val;
BUG_ON(offset & (len - 1));
switch (offset) {
case CORE0_STATUS_OFF:
break;
case CORE0_EN_OFF:
s->en = data;
break;
case CORE0_SET_OFF:
s->status |= data;
irq.cpu = id;
irq.irq = 6;
kvm_vcpu_ioctl_interrupt(kvm_get_vcpu(kvm, id), &irq);
break;
case CORE0_CLEAR_OFF:
s->status &= ~data;
if (!s->status) {
irq.cpu = id;
irq.irq = -6;
kvm_vcpu_ioctl_interrupt(kvm_get_vcpu(kvm, id), &irq);
}
break;
case CORE0_BUF_20 ... CORE0_BUF_38:
pbuf = (void *)s->buf + (offset - 0x20);
if (len == 8)
*(uint64_t *)pbuf = (uint64_t)data;
else /* Assume len == 4 */
*(uint32_t *)pbuf = (uint32_t)data;
break;
default:
pr_notice("%s with 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)
{
unsigned long flags;
struct loongson_kvm_ipi *ipi;
struct ipi_io_device *ipi_device;
ipi_device = container_of(dev, struct ipi_io_device, device);
ipi = ipi_device->ipi;
spin_lock_irqsave(&ipi->lock, flags);
loongson_vipi_read(ipi, addr, len, val);
spin_unlock_irqrestore(&ipi->lock, flags);
return 0;
}
static int kvm_ipi_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
unsigned long flags;
struct loongson_kvm_ipi *ipi;
struct ipi_io_device *ipi_device;
ipi_device = container_of(dev, struct ipi_io_device, device);
ipi = ipi_device->ipi;
spin_lock_irqsave(&ipi->lock, flags);
loongson_vipi_write(ipi, addr, len, val);
spin_unlock_irqrestore(&ipi->lock, flags);
return 0;
}
static const struct kvm_io_device_ops kvm_ipi_ops = {
.read = kvm_ipi_read,
.write = kvm_ipi_write,
};
void kvm_init_loongson_ipi(struct kvm *kvm)
{
int i;
unsigned long addr;
struct loongson_kvm_ipi *s;
struct kvm_io_device *device;
s = &kvm->arch.ipi;
s->kvm = kvm;
spin_lock_init(&s->lock);
/*
* Initialize IPI device
*/
for (i = 0; i < 4; i++) {
device = &s->dev_ipi[i].device;
kvm_iodevice_init(device, &kvm_ipi_ops);
addr = (((unsigned long)i) << 44) + IPI_BASE;
mutex_lock(&kvm->slots_lock);
kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, addr, 0x400, device);
mutex_unlock(&kvm->slots_lock);
s->dev_ipi[i].ipi = s;
s->dev_ipi[i].node_id = i;
}
}
File diff suppressed because it is too large Load Diff
+717
View File
@@ -0,0 +1,717 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS MMU handling in the KVM module.
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
#include <linux/highmem.h>
#include <linux/kvm_host.h>
#include <linux/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/pgalloc.h>
/*
* KVM_MMU_CACHE_MIN_PAGES is the number of GPA page table translation levels
* for which pages need to be cached.
*/
#if defined(__PAGETABLE_PMD_FOLDED)
#define KVM_MMU_CACHE_MIN_PAGES 1
#else
#define KVM_MMU_CACHE_MIN_PAGES 2
#endif
void kvm_mmu_free_memory_caches(struct kvm_vcpu *vcpu)
{
kvm_mmu_free_memory_cache(&vcpu->arch.mmu_page_cache);
}
/**
* kvm_pgd_init() - Initialise KVM GPA page directory.
* @page: Pointer to page directory (PGD) for KVM GPA.
*
* Initialise a KVM GPA page directory with pointers to the invalid table, i.e.
* representing no mappings. This is similar to pgd_init(), however it
* initialises all the page directory pointers, not just the ones corresponding
* to the userland address space (since it is for the guest physical address
* space rather than a virtual address space).
*/
static void kvm_pgd_init(void *page)
{
unsigned long *p, *end;
unsigned long entry;
#ifdef __PAGETABLE_PMD_FOLDED
entry = (unsigned long)invalid_pte_table;
#else
entry = (unsigned long)invalid_pmd_table;
#endif
p = (unsigned long *)page;
end = p + PTRS_PER_PGD;
do {
p[0] = entry;
p[1] = entry;
p[2] = entry;
p[3] = entry;
p[4] = entry;
p += 8;
p[-3] = entry;
p[-2] = entry;
p[-1] = entry;
} while (p != end);
}
/**
* 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.
*/
pgd_t *kvm_pgd_alloc(void)
{
pgd_t *ret;
ret = (pgd_t *)__get_free_pages(GFP_KERNEL, PGD_TABLE_ORDER);
if (ret)
kvm_pgd_init(ret);
return ret;
}
/**
* kvm_mips_walk_pgd() - Walk page table with optional allocation.
* @pgd: Page directory pointer.
* @addr: Address to index page table using.
* @cache: MMU page cache to allocate new page tables from, or NULL.
*
* Walk the page tables pointed to by @pgd 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.
*
* Returns: Pointer to pte_t corresponding to @addr.
* NULL if a page table doesn't exist for @addr and !@cache.
* NULL if a page table allocation failed.
*/
static pte_t *kvm_mips_walk_pgd(pgd_t *pgd, struct kvm_mmu_memory_cache *cache,
unsigned long addr)
{
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pgd += pgd_index(addr);
if (pgd_none(*pgd)) {
/* Not used on MIPS yet */
BUG();
return NULL;
}
p4d = p4d_offset(pgd, addr);
pud = pud_offset(p4d, addr);
if (pud_none(*pud)) {
pmd_t *new_pmd;
if (!cache)
return NULL;
new_pmd = kvm_mmu_memory_cache_alloc(cache);
pmd_init(new_pmd);
pud_populate(NULL, pud, new_pmd);
}
pmd = pmd_offset(pud, addr);
if (pmd_none(*pmd)) {
pte_t *new_pte;
if (!cache)
return NULL;
new_pte = kvm_mmu_memory_cache_alloc(cache);
clear_page(new_pte);
pmd_populate_kernel(NULL, pmd, new_pte);
}
return pte_offset_kernel(pmd, addr);
}
/* Caller must hold kvm->mm_lock */
static pte_t *kvm_mips_pte_for_gpa(struct kvm *kvm,
struct kvm_mmu_memory_cache *cache,
unsigned long addr)
{
return kvm_mips_walk_pgd(kvm->arch.gpa_mm.pgd, cache, addr);
}
/*
* kvm_mips_flush_gpa_{pte,pmd,pud,pgd,pt}.
* Flush a range of guest physical address space from the VM's GPA page tables.
*/
static bool kvm_mips_flush_gpa_pte(pte_t *pte, unsigned long start_gpa,
unsigned long end_gpa)
{
int i_min = pte_index(start_gpa);
int i_max = pte_index(end_gpa);
bool safe_to_remove = (i_min == 0 && i_max == PTRS_PER_PTE - 1);
int i;
for (i = i_min; i <= i_max; ++i) {
if (!pte_present(pte[i]))
continue;
set_pte(pte + i, __pte(0));
}
return safe_to_remove;
}
static bool kvm_mips_flush_gpa_pmd(pmd_t *pmd, unsigned long start_gpa,
unsigned long end_gpa)
{
pte_t *pte;
unsigned long end = ~0ul;
int i_min = pmd_index(start_gpa);
int i_max = pmd_index(end_gpa);
bool safe_to_remove = (i_min == 0 && i_max == PTRS_PER_PMD - 1);
int i;
for (i = i_min; i <= i_max; ++i, start_gpa = 0) {
if (!pmd_present(pmd[i]))
continue;
pte = pte_offset_kernel(pmd + i, 0);
if (i == i_max)
end = end_gpa;
if (kvm_mips_flush_gpa_pte(pte, start_gpa, end)) {
pmd_clear(pmd + i);
pte_free_kernel(NULL, pte);
} else {
safe_to_remove = false;
}
}
return safe_to_remove;
}
static bool kvm_mips_flush_gpa_pud(pud_t *pud, unsigned long start_gpa,
unsigned long end_gpa)
{
pmd_t *pmd;
unsigned long end = ~0ul;
int i_min = pud_index(start_gpa);
int i_max = pud_index(end_gpa);
bool safe_to_remove = (i_min == 0 && i_max == PTRS_PER_PUD - 1);
int i;
for (i = i_min; i <= i_max; ++i, start_gpa = 0) {
if (!pud_present(pud[i]))
continue;
pmd = pmd_offset(pud + i, 0);
if (i == i_max)
end = end_gpa;
if (kvm_mips_flush_gpa_pmd(pmd, start_gpa, end)) {
pud_clear(pud + i);
pmd_free(NULL, pmd);
} else {
safe_to_remove = false;
}
}
return safe_to_remove;
}
static bool kvm_mips_flush_gpa_pgd(pgd_t *pgd, unsigned long start_gpa,
unsigned long end_gpa)
{
p4d_t *p4d;
pud_t *pud;
unsigned long end = ~0ul;
int i_min = pgd_index(start_gpa);
int i_max = pgd_index(end_gpa);
bool safe_to_remove = (i_min == 0 && i_max == PTRS_PER_PGD - 1);
int i;
for (i = i_min; i <= i_max; ++i, start_gpa = 0) {
if (!pgd_present(pgd[i]))
continue;
p4d = p4d_offset(pgd, 0);
pud = pud_offset(p4d + i, 0);
if (i == i_max)
end = end_gpa;
if (kvm_mips_flush_gpa_pud(pud, start_gpa, end)) {
pgd_clear(pgd + i);
pud_free(NULL, pud);
} else {
safe_to_remove = false;
}
}
return safe_to_remove;
}
/**
* kvm_mips_flush_gpa_pt() - 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.
*
* Flushes a range of GPA mappings from the GPA page tables.
*
* The caller must hold the @kvm->mmu_lock spinlock.
*
* Returns: Whether its safe to remove the top level page directory because
* all lower levels have been removed.
*/
bool kvm_mips_flush_gpa_pt(struct kvm *kvm, gfn_t start_gfn, gfn_t end_gfn)
{
return kvm_mips_flush_gpa_pgd(kvm->arch.gpa_mm.pgd,
start_gfn << PAGE_SHIFT,
end_gfn << PAGE_SHIFT);
}
#define BUILD_PTE_RANGE_OP(name, op) \
static int kvm_mips_##name##_pte(pte_t *pte, unsigned long start, \
unsigned long end) \
{ \
int ret = 0; \
int i_min = pte_index(start); \
int i_max = pte_index(end); \
int i; \
pte_t old, new; \
\
for (i = i_min; i <= i_max; ++i) { \
if (!pte_present(pte[i])) \
continue; \
\
old = pte[i]; \
new = op(old); \
if (pte_val(new) == pte_val(old)) \
continue; \
set_pte(pte + i, new); \
ret = 1; \
} \
return ret; \
} \
\
/* returns true if anything was done */ \
static int kvm_mips_##name##_pmd(pmd_t *pmd, unsigned long start, \
unsigned long end) \
{ \
int ret = 0; \
pte_t *pte; \
unsigned long cur_end = ~0ul; \
int i_min = pmd_index(start); \
int i_max = pmd_index(end); \
int i; \
\
for (i = i_min; i <= i_max; ++i, start = 0) { \
if (!pmd_present(pmd[i])) \
continue; \
\
pte = pte_offset_kernel(pmd + i, 0); \
if (i == i_max) \
cur_end = end; \
\
ret |= kvm_mips_##name##_pte(pte, start, cur_end); \
} \
return ret; \
} \
\
static int kvm_mips_##name##_pud(pud_t *pud, unsigned long start, \
unsigned long end) \
{ \
int ret = 0; \
pmd_t *pmd; \
unsigned long cur_end = ~0ul; \
int i_min = pud_index(start); \
int i_max = pud_index(end); \
int i; \
\
for (i = i_min; i <= i_max; ++i, start = 0) { \
if (!pud_present(pud[i])) \
continue; \
\
pmd = pmd_offset(pud + i, 0); \
if (i == i_max) \
cur_end = end; \
\
ret |= kvm_mips_##name##_pmd(pmd, start, cur_end); \
} \
return ret; \
} \
\
static int kvm_mips_##name##_pgd(pgd_t *pgd, unsigned long start, \
unsigned long end) \
{ \
int ret = 0; \
p4d_t *p4d; \
pud_t *pud; \
unsigned long cur_end = ~0ul; \
int i_min = pgd_index(start); \
int i_max = pgd_index(end); \
int i; \
\
for (i = i_min; i <= i_max; ++i, start = 0) { \
if (!pgd_present(pgd[i])) \
continue; \
\
p4d = p4d_offset(pgd, 0); \
pud = pud_offset(p4d + i, 0); \
if (i == i_max) \
cur_end = end; \
\
ret |= kvm_mips_##name##_pud(pud, start, cur_end); \
} \
return ret; \
}
/*
* kvm_mips_mkclean_gpa_pt.
* Mark a range of guest physical address space clean (writes fault) in the VM's
* GPA page table to allow dirty page tracking.
*/
BUILD_PTE_RANGE_OP(mkclean, pte_mkclean)
/**
* kvm_mips_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.
*/
int kvm_mips_mkclean_gpa_pt(struct kvm *kvm, gfn_t start_gfn, gfn_t end_gfn)
{
return kvm_mips_mkclean_pgd(kvm->arch.gpa_mm.pgd,
start_gfn << PAGE_SHIFT,
end_gfn << PAGE_SHIFT);
}
/**
* 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)
{
gfn_t base_gfn = slot->base_gfn + gfn_offset;
gfn_t start = base_gfn + __ffs(mask);
gfn_t end = base_gfn + __fls(mask);
kvm_mips_mkclean_gpa_pt(kvm, start, end);
}
/*
* kvm_mips_mkold_gpa_pt.
* 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.
*/
BUILD_PTE_RANGE_OP(mkold, pte_mkold)
static int kvm_mips_mkold_gpa_pt(struct kvm *kvm, gfn_t start_gfn,
gfn_t end_gfn)
{
return kvm_mips_mkold_pgd(kvm->arch.gpa_mm.pgd,
start_gfn << PAGE_SHIFT,
end_gfn << PAGE_SHIFT);
}
bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
{
kvm_mips_flush_gpa_pt(kvm, range->start, range->end);
return true;
}
bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
{
return kvm_mips_mkold_gpa_pt(kvm, range->start, range->end);
}
bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
{
gpa_t gpa = range->start << PAGE_SHIFT;
pte_t *gpa_pte = kvm_mips_pte_for_gpa(kvm, NULL, gpa);
if (!gpa_pte)
return false;
return pte_young(*gpa_pte);
}
/**
* _kvm_mips_map_page_fast() - Fast path GPA fault handler.
* @vcpu: VCPU pointer.
* @gpa: Guest physical address of fault.
* @write_fault: Whether the fault was due to a write.
* @out_entry: New PTE for @gpa (written on success unless NULL).
* @out_buddy: New PTE for @gpa's buddy (written on success unless
* NULL).
*
* 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_mips_map_page_fast(struct kvm_vcpu *vcpu, unsigned long gpa,
bool write_fault,
pte_t *out_entry, pte_t *out_buddy)
{
struct kvm *kvm = vcpu->kvm;
gfn_t gfn = gpa >> PAGE_SHIFT;
pte_t *ptep;
int ret = 0;
spin_lock(&kvm->mmu_lock);
/* Fast path - just check GPA page table for an existing entry */
ptep = kvm_mips_pte_for_gpa(kvm, NULL, gpa);
if (!ptep || !pte_present(*ptep)) {
ret = -EFAULT;
goto out;
}
/* Track access to pages marked old */
if (!pte_young(*ptep))
set_pte(ptep, pte_mkyoung(*ptep));
if (write_fault && !pte_dirty(*ptep)) {
if (!pte_write(*ptep)) {
ret = -EFAULT;
goto out;
}
/* Track dirtying of writeable pages */
set_pte(ptep, pte_mkdirty(*ptep));
mark_page_dirty(kvm, gfn);
}
if (out_entry)
*out_entry = *ptep;
if (out_buddy)
*out_buddy = *ptep_buddy(ptep);
out:
spin_unlock(&kvm->mmu_lock);
return ret;
}
/**
* kvm_mips_map_page() - Map a guest physical page.
* @vcpu: VCPU pointer.
* @gpa: Guest physical address of fault.
* @write_fault: Whether the fault was due to a write.
* @out_entry: New PTE for @gpa (written on success unless NULL).
* @out_buddy: New PTE for @gpa's buddy (written on success unless
* NULL).
*
* 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, in which case the caller may use the @out_entry
* and @out_buddy PTEs to update derived mappings and resume guest
* execution.
* -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_mips_map_page(struct kvm_vcpu *vcpu, unsigned long gpa,
bool write_fault,
pte_t *out_entry, pte_t *out_buddy)
{
struct kvm *kvm = vcpu->kvm;
struct kvm_mmu_memory_cache *memcache = &vcpu->arch.mmu_page_cache;
gfn_t gfn = gpa >> PAGE_SHIFT;
int srcu_idx, err;
kvm_pfn_t pfn;
pte_t *ptep, entry;
bool writeable;
unsigned long prot_bits;
unsigned long mmu_seq;
struct page *page;
/* Try the fast path to handle old / clean pages */
srcu_idx = srcu_read_lock(&kvm->srcu);
err = _kvm_mips_map_page_fast(vcpu, gpa, write_fault, out_entry,
out_buddy);
if (!err)
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_notifier_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_fault, &writeable, &page);
if (is_error_noslot_pfn(pfn)) {
err = -EFAULT;
goto out;
}
spin_lock(&kvm->mmu_lock);
/* Check if an invalidation has taken place since we got pfn */
if (mmu_invalidate_retry(kvm, mmu_seq)) {
/*
* 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);
goto retry;
}
/* Ensure page tables are allocated */
ptep = kvm_mips_pte_for_gpa(kvm, memcache, gpa);
/* Set up the PTE */
prot_bits = _PAGE_PRESENT | __READABLE | _page_cachable_default;
if (writeable) {
prot_bits |= _PAGE_WRITE;
if (write_fault) {
prot_bits |= __WRITEABLE;
mark_page_dirty(kvm, gfn);
}
}
entry = pfn_pte(pfn, __pgprot(prot_bits));
/* Write the PTE */
set_pte(ptep, entry);
err = 0;
if (out_entry)
*out_entry = *ptep;
if (out_buddy)
*out_buddy = *ptep_buddy(ptep);
kvm_release_faultin_page(kvm, page, false, writeable);
spin_unlock(&kvm->mmu_lock);
out:
srcu_read_unlock(&kvm->srcu, srcu_idx);
return err;
}
int kvm_mips_handle_vz_root_tlb_fault(unsigned long badvaddr,
struct kvm_vcpu *vcpu,
bool write_fault)
{
int ret;
ret = kvm_mips_map_page(vcpu, badvaddr, write_fault, NULL, NULL);
if (ret)
return ret;
/* Invalidate this entry in the TLB */
return kvm_vz_host_tlb_inv(vcpu, badvaddr);
}
/**
* kvm_mips_migrate_count() - Migrate timer.
* @vcpu: Virtual CPU.
*
* Migrate CP0_Count hrtimer to the current CPU by cancelling and restarting it
* if it was running prior to being cancelled.
*
* Must be called when the VCPU is migrated to a different CPU to ensure that
* timer expiry during guest execution interrupts the guest and causes the
* interrupt to be delivered in a timely manner.
*/
static void kvm_mips_migrate_count(struct kvm_vcpu *vcpu)
{
if (hrtimer_cancel(&vcpu->arch.comparecount_timer))
hrtimer_restart(&vcpu->arch.comparecount_timer);
}
/* Restore ASID once we are scheduled back after preemption */
void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
{
unsigned long flags;
kvm_debug("%s: vcpu %p, cpu: %d\n", __func__, vcpu, cpu);
local_irq_save(flags);
vcpu->cpu = cpu;
if (vcpu->arch.last_sched_cpu != cpu) {
kvm_debug("[%d->%d]KVM VCPU[%d] switch\n",
vcpu->arch.last_sched_cpu, cpu, vcpu->vcpu_id);
/*
* Migrate the timer interrupt to the current CPU so that it
* always interrupts the guest and synchronously triggers a
* guest timer interrupt.
*/
kvm_mips_migrate_count(vcpu);
}
/* restore guest state to registers */
kvm_mips_callbacks->vcpu_load(vcpu, cpu);
local_irq_restore(flags);
}
/* ASID can change if another task is scheduled during preemption */
void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
{
unsigned long flags;
int cpu;
local_irq_save(flags);
cpu = smp_processor_id();
vcpu->arch.last_sched_cpu = cpu;
vcpu->cpu = -1;
/* save guest state in registers */
kvm_mips_callbacks->vcpu_put(vcpu, cpu);
local_irq_restore(flags);
}
+161
View File
@@ -0,0 +1,161 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* MIPS SIMD Architecture (MSA) context handling code for KVM.
*
* Copyright (C) 2015 Imagination Technologies Ltd.
*/
#include <asm/asm.h>
#include <asm/asm-offsets.h>
#include <asm/asmmacro.h>
#include <asm/regdef.h>
.set noreorder
.set noat
LEAF(__kvm_save_msa)
st_d 0, VCPU_FPR0, a0
st_d 1, VCPU_FPR1, a0
st_d 2, VCPU_FPR2, a0
st_d 3, VCPU_FPR3, a0
st_d 4, VCPU_FPR4, a0
st_d 5, VCPU_FPR5, a0
st_d 6, VCPU_FPR6, a0
st_d 7, VCPU_FPR7, a0
st_d 8, VCPU_FPR8, a0
st_d 9, VCPU_FPR9, a0
st_d 10, VCPU_FPR10, a0
st_d 11, VCPU_FPR11, a0
st_d 12, VCPU_FPR12, a0
st_d 13, VCPU_FPR13, a0
st_d 14, VCPU_FPR14, a0
st_d 15, VCPU_FPR15, a0
st_d 16, VCPU_FPR16, a0
st_d 17, VCPU_FPR17, a0
st_d 18, VCPU_FPR18, a0
st_d 19, VCPU_FPR19, a0
st_d 20, VCPU_FPR20, a0
st_d 21, VCPU_FPR21, a0
st_d 22, VCPU_FPR22, a0
st_d 23, VCPU_FPR23, a0
st_d 24, VCPU_FPR24, a0
st_d 25, VCPU_FPR25, a0
st_d 26, VCPU_FPR26, a0
st_d 27, VCPU_FPR27, a0
st_d 28, VCPU_FPR28, a0
st_d 29, VCPU_FPR29, a0
st_d 30, VCPU_FPR30, a0
st_d 31, VCPU_FPR31, a0
jr ra
nop
END(__kvm_save_msa)
LEAF(__kvm_restore_msa)
ld_d 0, VCPU_FPR0, a0
ld_d 1, VCPU_FPR1, a0
ld_d 2, VCPU_FPR2, a0
ld_d 3, VCPU_FPR3, a0
ld_d 4, VCPU_FPR4, a0
ld_d 5, VCPU_FPR5, a0
ld_d 6, VCPU_FPR6, a0
ld_d 7, VCPU_FPR7, a0
ld_d 8, VCPU_FPR8, a0
ld_d 9, VCPU_FPR9, a0
ld_d 10, VCPU_FPR10, a0
ld_d 11, VCPU_FPR11, a0
ld_d 12, VCPU_FPR12, a0
ld_d 13, VCPU_FPR13, a0
ld_d 14, VCPU_FPR14, a0
ld_d 15, VCPU_FPR15, a0
ld_d 16, VCPU_FPR16, a0
ld_d 17, VCPU_FPR17, a0
ld_d 18, VCPU_FPR18, a0
ld_d 19, VCPU_FPR19, a0
ld_d 20, VCPU_FPR20, a0
ld_d 21, VCPU_FPR21, a0
ld_d 22, VCPU_FPR22, a0
ld_d 23, VCPU_FPR23, a0
ld_d 24, VCPU_FPR24, a0
ld_d 25, VCPU_FPR25, a0
ld_d 26, VCPU_FPR26, a0
ld_d 27, VCPU_FPR27, a0
ld_d 28, VCPU_FPR28, a0
ld_d 29, VCPU_FPR29, a0
ld_d 30, VCPU_FPR30, a0
ld_d 31, VCPU_FPR31, a0
jr ra
nop
END(__kvm_restore_msa)
.macro kvm_restore_msa_upper wr, off, base
.set push
.set noat
#ifdef CONFIG_64BIT
ld $1, \off(\base)
insert_d \wr, 1
#elif defined(CONFIG_CPU_LITTLE_ENDIAN)
lw $1, \off(\base)
insert_w \wr, 2
lw $1, (\off+4)(\base)
insert_w \wr, 3
#else /* CONFIG_CPU_BIG_ENDIAN */
lw $1, (\off+4)(\base)
insert_w \wr, 2
lw $1, \off(\base)
insert_w \wr, 3
#endif
.set pop
.endm
LEAF(__kvm_restore_msa_upper)
kvm_restore_msa_upper 0, VCPU_FPR0 +8, a0
kvm_restore_msa_upper 1, VCPU_FPR1 +8, a0
kvm_restore_msa_upper 2, VCPU_FPR2 +8, a0
kvm_restore_msa_upper 3, VCPU_FPR3 +8, a0
kvm_restore_msa_upper 4, VCPU_FPR4 +8, a0
kvm_restore_msa_upper 5, VCPU_FPR5 +8, a0
kvm_restore_msa_upper 6, VCPU_FPR6 +8, a0
kvm_restore_msa_upper 7, VCPU_FPR7 +8, a0
kvm_restore_msa_upper 8, VCPU_FPR8 +8, a0
kvm_restore_msa_upper 9, VCPU_FPR9 +8, a0
kvm_restore_msa_upper 10, VCPU_FPR10+8, a0
kvm_restore_msa_upper 11, VCPU_FPR11+8, a0
kvm_restore_msa_upper 12, VCPU_FPR12+8, a0
kvm_restore_msa_upper 13, VCPU_FPR13+8, a0
kvm_restore_msa_upper 14, VCPU_FPR14+8, a0
kvm_restore_msa_upper 15, VCPU_FPR15+8, a0
kvm_restore_msa_upper 16, VCPU_FPR16+8, a0
kvm_restore_msa_upper 17, VCPU_FPR17+8, a0
kvm_restore_msa_upper 18, VCPU_FPR18+8, a0
kvm_restore_msa_upper 19, VCPU_FPR19+8, a0
kvm_restore_msa_upper 20, VCPU_FPR20+8, a0
kvm_restore_msa_upper 21, VCPU_FPR21+8, a0
kvm_restore_msa_upper 22, VCPU_FPR22+8, a0
kvm_restore_msa_upper 23, VCPU_FPR23+8, a0
kvm_restore_msa_upper 24, VCPU_FPR24+8, a0
kvm_restore_msa_upper 25, VCPU_FPR25+8, a0
kvm_restore_msa_upper 26, VCPU_FPR26+8, a0
kvm_restore_msa_upper 27, VCPU_FPR27+8, a0
kvm_restore_msa_upper 28, VCPU_FPR28+8, a0
kvm_restore_msa_upper 29, VCPU_FPR29+8, a0
kvm_restore_msa_upper 30, VCPU_FPR30+8, a0
kvm_restore_msa_upper 31, VCPU_FPR31+8, a0
jr ra
nop
END(__kvm_restore_msa_upper)
LEAF(__kvm_restore_msacsr)
lw t0, VCPU_MSA_CSR(a0)
/*
* The ctcmsa must stay at this offset in __kvm_restore_msacsr.
* See kvm_mips_csr_die_notify() which handles t0 containing a value
* which triggers an MSA FP Exception, which must be stepped over and
* ignored since the set cause bits must remain there for the guest.
*/
_ctcmsa MSA_CSR, t0
jr ra
nop
END(__kvm_restore_msacsr)
+63
View File
@@ -0,0 +1,63 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS: COP0 access histogram
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
#include <linux/kvm_host.h>
char *kvm_cop0_str[N_MIPS_COPROC_REGS] = {
"Index",
"Random",
"EntryLo0",
"EntryLo1",
"Context",
"PG Mask",
"Wired",
"HWREna",
"BadVAddr",
"Count",
"EntryHI",
"Compare",
"Status",
"Cause",
"EXC PC",
"PRID",
"Config",
"LLAddr",
"Watch Lo",
"Watch Hi",
"X Context",
"Reserved",
"Impl Dep",
"Debug",
"DEPC",
"PerfCnt",
"ErrCtl",
"CacheErr",
"TagLo",
"TagHi",
"ErrorEPC",
"DESAVE"
};
void kvm_mips_dump_stats(struct kvm_vcpu *vcpu)
{
#ifdef CONFIG_KVM_MIPS_DEBUG_COP0_COUNTERS
int i, j;
kvm_info("\nKVM VCPU[%d] COP0 Access Profile:\n", vcpu->vcpu_id);
for (i = 0; i < N_MIPS_COPROC_REGS; i++) {
for (j = 0; j < N_MIPS_COPROC_SEL; j++) {
if (vcpu->arch.cop0.stat[i][j])
kvm_info("%s[%d]: %lu\n", kvm_cop0_str[i], j,
vcpu->arch.cop0.stat[i][j]);
}
}
#endif
}
+525
View File
@@ -0,0 +1,525 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS TLB handling, this file is part of the Linux host kernel so that
* TLB handlers run from KSEG0
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/kvm_host.h>
#include <linux/srcu.h>
#include <asm/cpu.h>
#include <asm/bootinfo.h>
#include <asm/mipsregs.h>
#include <asm/mmu_context.h>
#include <asm/cacheflush.h>
#include <asm/tlb.h>
#include <asm/tlbdebug.h>
#undef CONFIG_MIPS_MT
#include <asm/r4kcache.h>
#define CONFIG_MIPS_MT
unsigned long GUESTID_MASK;
EXPORT_SYMBOL_GPL(GUESTID_MASK);
unsigned long GUESTID_FIRST_VERSION;
EXPORT_SYMBOL_GPL(GUESTID_FIRST_VERSION);
unsigned long GUESTID_VERSION_MASK;
EXPORT_SYMBOL_GPL(GUESTID_VERSION_MASK);
static u32 kvm_mips_get_root_asid(struct kvm_vcpu *vcpu)
{
struct mm_struct *gpa_mm = &vcpu->kvm->arch.gpa_mm;
if (cpu_has_guestid)
return 0;
else
return cpu_asid(smp_processor_id(), gpa_mm);
}
static int _kvm_mips_host_tlb_inv(unsigned long entryhi)
{
int idx;
write_c0_entryhi(entryhi);
mtc0_tlbw_hazard();
tlb_probe();
tlb_probe_hazard();
idx = read_c0_index();
BUG_ON(idx >= current_cpu_data.tlbsize);
if (idx >= 0) {
write_c0_entryhi(UNIQUE_ENTRYHI(idx));
write_c0_entrylo0(0);
write_c0_entrylo1(0);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
}
return idx;
}
/* GuestID management */
/**
* clear_root_gid() - Set GuestCtl1.RID for normal root operation.
*/
static inline void clear_root_gid(void)
{
if (cpu_has_guestid) {
clear_c0_guestctl1(MIPS_GCTL1_RID);
mtc0_tlbw_hazard();
}
}
/**
* set_root_gid_to_guest_gid() - Set GuestCtl1.RID to match GuestCtl1.ID.
*
* Sets the root GuestID to match the current guest GuestID, for TLB operation
* on the GPA->RPA mappings in the root TLB.
*
* The caller must be sure to disable HTW while the root GID is set, and
* possibly longer if TLB registers are modified.
*/
static inline void set_root_gid_to_guest_gid(void)
{
unsigned int guestctl1;
if (cpu_has_guestid) {
back_to_back_c0_hazard();
guestctl1 = read_c0_guestctl1();
guestctl1 = (guestctl1 & ~MIPS_GCTL1_RID) |
((guestctl1 & MIPS_GCTL1_ID) >> MIPS_GCTL1_ID_SHIFT)
<< MIPS_GCTL1_RID_SHIFT;
write_c0_guestctl1(guestctl1);
mtc0_tlbw_hazard();
}
}
int kvm_vz_host_tlb_inv(struct kvm_vcpu *vcpu, unsigned long va)
{
int idx;
unsigned long flags, old_entryhi;
local_irq_save(flags);
htw_stop();
/* Set root GuestID for root probe and write of guest TLB entry */
set_root_gid_to_guest_gid();
old_entryhi = read_c0_entryhi();
idx = _kvm_mips_host_tlb_inv((va & VPN2_MASK) |
kvm_mips_get_root_asid(vcpu));
write_c0_entryhi(old_entryhi);
clear_root_gid();
mtc0_tlbw_hazard();
htw_start();
local_irq_restore(flags);
/*
* We don't want to get reserved instruction exceptions for missing tlb
* entries.
*/
if (cpu_has_vtag_icache)
flush_icache_all();
if (idx > 0)
kvm_debug("%s: Invalidated root entryhi %#lx @ idx %d\n",
__func__, (va & VPN2_MASK) |
kvm_mips_get_root_asid(vcpu), idx);
return 0;
}
EXPORT_SYMBOL_GPL(kvm_vz_host_tlb_inv);
/**
* kvm_vz_guest_tlb_lookup() - Lookup a guest VZ TLB mapping.
* @vcpu: KVM VCPU pointer.
* @gpa: Guest virtual address in a TLB mapped guest segment.
* @gpa: Pointer to output guest physical address it maps to.
*
* Converts a guest virtual address in a guest TLB mapped segment to a guest
* physical address, by probing the guest TLB.
*
* Returns: 0 if guest TLB mapping exists for @gva. *@gpa will have been
* written.
* -EFAULT if no guest TLB mapping exists for @gva. *@gpa may not
* have been written.
*/
int kvm_vz_guest_tlb_lookup(struct kvm_vcpu *vcpu, unsigned long gva,
unsigned long *gpa)
{
unsigned long o_entryhi, o_entrylo[2], o_pagemask;
unsigned int o_index;
unsigned long entrylo[2], pagemask, pagemaskbit, pa;
unsigned long flags;
int index;
/* Probe the guest TLB for a mapping */
local_irq_save(flags);
/* Set root GuestID for root probe of guest TLB entry */
htw_stop();
set_root_gid_to_guest_gid();
o_entryhi = read_gc0_entryhi();
o_index = read_gc0_index();
write_gc0_entryhi((o_entryhi & 0x3ff) | (gva & ~0xfffl));
mtc0_tlbw_hazard();
guest_tlb_probe();
tlb_probe_hazard();
index = read_gc0_index();
if (index < 0) {
/* No match, fail */
write_gc0_entryhi(o_entryhi);
write_gc0_index(o_index);
clear_root_gid();
htw_start();
local_irq_restore(flags);
return -EFAULT;
}
/* Match! read the TLB entry */
o_entrylo[0] = read_gc0_entrylo0();
o_entrylo[1] = read_gc0_entrylo1();
o_pagemask = read_gc0_pagemask();
mtc0_tlbr_hazard();
guest_tlb_read();
tlb_read_hazard();
entrylo[0] = read_gc0_entrylo0();
entrylo[1] = read_gc0_entrylo1();
pagemask = ~read_gc0_pagemask() & ~0x1fffl;
write_gc0_entryhi(o_entryhi);
write_gc0_index(o_index);
write_gc0_entrylo0(o_entrylo[0]);
write_gc0_entrylo1(o_entrylo[1]);
write_gc0_pagemask(o_pagemask);
clear_root_gid();
htw_start();
local_irq_restore(flags);
/* Select one of the EntryLo values and interpret the GPA */
pagemaskbit = (pagemask ^ (pagemask & (pagemask - 1))) >> 1;
pa = entrylo[!!(gva & pagemaskbit)];
/*
* TLB entry may have become invalid since TLB probe if physical FTLB
* entries are shared between threads (e.g. I6400).
*/
if (!(pa & ENTRYLO_V))
return -EFAULT;
/*
* Note, this doesn't take guest MIPS32 XPA into account, where PFN is
* split with XI/RI in the middle.
*/
pa = (pa << 6) & ~0xfffl;
pa |= gva & ~(pagemask | pagemaskbit);
*gpa = pa;
return 0;
}
EXPORT_SYMBOL_GPL(kvm_vz_guest_tlb_lookup);
/**
* kvm_vz_local_flush_roottlb_all_guests() - Flush all root TLB entries for
* guests.
*
* Invalidate all entries in root tlb which are GPA mappings.
*/
void kvm_vz_local_flush_roottlb_all_guests(void)
{
unsigned long flags;
unsigned long old_entryhi, old_pagemask, old_guestctl1;
int entry;
if (WARN_ON(!cpu_has_guestid))
return;
local_irq_save(flags);
htw_stop();
/* TLBR may clobber EntryHi.ASID, PageMask, and GuestCtl1.RID */
old_entryhi = read_c0_entryhi();
old_pagemask = read_c0_pagemask();
old_guestctl1 = read_c0_guestctl1();
/*
* Invalidate guest entries in root TLB while leaving root entries
* intact when possible.
*/
for (entry = 0; entry < current_cpu_data.tlbsize; entry++) {
write_c0_index(entry);
mtc0_tlbw_hazard();
tlb_read();
tlb_read_hazard();
/* Don't invalidate non-guest (RVA) mappings in the root TLB */
if (!(read_c0_guestctl1() & MIPS_GCTL1_RID))
continue;
/* Make sure all entries differ. */
write_c0_entryhi(UNIQUE_ENTRYHI(entry));
write_c0_entrylo0(0);
write_c0_entrylo1(0);
write_c0_guestctl1(0);
mtc0_tlbw_hazard();
tlb_write_indexed();
}
write_c0_entryhi(old_entryhi);
write_c0_pagemask(old_pagemask);
write_c0_guestctl1(old_guestctl1);
tlbw_use_hazard();
htw_start();
local_irq_restore(flags);
}
EXPORT_SYMBOL_GPL(kvm_vz_local_flush_roottlb_all_guests);
/**
* kvm_vz_local_flush_guesttlb_all() - Flush all guest TLB entries.
*
* Invalidate all entries in guest tlb irrespective of guestid.
*/
void kvm_vz_local_flush_guesttlb_all(void)
{
unsigned long flags;
unsigned long old_index;
unsigned long old_entryhi;
unsigned long old_entrylo[2];
unsigned long old_pagemask;
int entry;
u64 cvmmemctl2 = 0;
local_irq_save(flags);
/* Preserve all clobbered guest registers */
old_index = read_gc0_index();
old_entryhi = read_gc0_entryhi();
old_entrylo[0] = read_gc0_entrylo0();
old_entrylo[1] = read_gc0_entrylo1();
old_pagemask = read_gc0_pagemask();
switch (current_cpu_type()) {
case CPU_CAVIUM_OCTEON3:
/* Inhibit machine check due to multiple matching TLB entries */
cvmmemctl2 = read_c0_cvmmemctl2();
cvmmemctl2 |= CVMMEMCTL2_INHIBITTS;
write_c0_cvmmemctl2(cvmmemctl2);
break;
}
/* Invalidate guest entries in guest TLB */
write_gc0_entrylo0(0);
write_gc0_entrylo1(0);
write_gc0_pagemask(0);
for (entry = 0; entry < current_cpu_data.guest.tlbsize; entry++) {
/* Make sure all entries differ. */
write_gc0_index(entry);
write_gc0_entryhi(UNIQUE_GUEST_ENTRYHI(entry));
mtc0_tlbw_hazard();
guest_tlb_write_indexed();
}
if (cvmmemctl2) {
cvmmemctl2 &= ~CVMMEMCTL2_INHIBITTS;
write_c0_cvmmemctl2(cvmmemctl2);
}
write_gc0_index(old_index);
write_gc0_entryhi(old_entryhi);
write_gc0_entrylo0(old_entrylo[0]);
write_gc0_entrylo1(old_entrylo[1]);
write_gc0_pagemask(old_pagemask);
tlbw_use_hazard();
local_irq_restore(flags);
}
EXPORT_SYMBOL_GPL(kvm_vz_local_flush_guesttlb_all);
/**
* kvm_vz_save_guesttlb() - Save a range of guest TLB entries.
* @buf: Buffer to write TLB entries into.
* @index: Start index.
* @count: Number of entries to save.
*
* Save a range of guest TLB entries. The caller must ensure interrupts are
* disabled.
*/
void kvm_vz_save_guesttlb(struct kvm_mips_tlb *buf, unsigned int index,
unsigned int count)
{
unsigned int end = index + count;
unsigned long old_entryhi, old_entrylo0, old_entrylo1, old_pagemask;
unsigned int guestctl1 = 0;
int old_index, i;
/* Save registers we're about to clobber */
old_index = read_gc0_index();
old_entryhi = read_gc0_entryhi();
old_entrylo0 = read_gc0_entrylo0();
old_entrylo1 = read_gc0_entrylo1();
old_pagemask = read_gc0_pagemask();
/* Set root GuestID for root probe */
htw_stop();
set_root_gid_to_guest_gid();
if (cpu_has_guestid)
guestctl1 = read_c0_guestctl1();
/* Read each entry from guest TLB */
for (i = index; i < end; ++i, ++buf) {
write_gc0_index(i);
mtc0_tlbr_hazard();
guest_tlb_read();
tlb_read_hazard();
if (cpu_has_guestid &&
(read_c0_guestctl1() ^ guestctl1) & MIPS_GCTL1_RID) {
/* Entry invalid or belongs to another guest */
buf->tlb_hi = UNIQUE_GUEST_ENTRYHI(i);
buf->tlb_lo[0] = 0;
buf->tlb_lo[1] = 0;
buf->tlb_mask = 0;
} else {
/* Entry belongs to the right guest */
buf->tlb_hi = read_gc0_entryhi();
buf->tlb_lo[0] = read_gc0_entrylo0();
buf->tlb_lo[1] = read_gc0_entrylo1();
buf->tlb_mask = read_gc0_pagemask();
}
}
/* Clear root GuestID again */
clear_root_gid();
htw_start();
/* Restore clobbered registers */
write_gc0_index(old_index);
write_gc0_entryhi(old_entryhi);
write_gc0_entrylo0(old_entrylo0);
write_gc0_entrylo1(old_entrylo1);
write_gc0_pagemask(old_pagemask);
tlbw_use_hazard();
}
EXPORT_SYMBOL_GPL(kvm_vz_save_guesttlb);
/**
* kvm_vz_load_guesttlb() - Save a range of guest TLB entries.
* @buf: Buffer to read TLB entries from.
* @index: Start index.
* @count: Number of entries to load.
*
* Load a range of guest TLB entries. The caller must ensure interrupts are
* disabled.
*/
void kvm_vz_load_guesttlb(const struct kvm_mips_tlb *buf, unsigned int index,
unsigned int count)
{
unsigned int end = index + count;
unsigned long old_entryhi, old_entrylo0, old_entrylo1, old_pagemask;
int old_index, i;
/* Save registers we're about to clobber */
old_index = read_gc0_index();
old_entryhi = read_gc0_entryhi();
old_entrylo0 = read_gc0_entrylo0();
old_entrylo1 = read_gc0_entrylo1();
old_pagemask = read_gc0_pagemask();
/* Set root GuestID for root probe */
htw_stop();
set_root_gid_to_guest_gid();
/* Write each entry to guest TLB */
for (i = index; i < end; ++i, ++buf) {
write_gc0_index(i);
write_gc0_entryhi(buf->tlb_hi);
write_gc0_entrylo0(buf->tlb_lo[0]);
write_gc0_entrylo1(buf->tlb_lo[1]);
write_gc0_pagemask(buf->tlb_mask);
mtc0_tlbw_hazard();
guest_tlb_write_indexed();
}
/* Clear root GuestID again */
clear_root_gid();
htw_start();
/* Restore clobbered registers */
write_gc0_index(old_index);
write_gc0_entryhi(old_entryhi);
write_gc0_entrylo0(old_entrylo0);
write_gc0_entrylo1(old_entrylo1);
write_gc0_pagemask(old_pagemask);
tlbw_use_hazard();
}
EXPORT_SYMBOL_GPL(kvm_vz_load_guesttlb);
#ifdef CONFIG_CPU_LOONGSON64
void kvm_loongson_clear_guest_vtlb(void)
{
int idx = read_gc0_index();
/* Set root GuestID for root probe and write of guest TLB entry */
set_root_gid_to_guest_gid();
write_gc0_index(0);
guest_tlbinvf();
write_gc0_index(idx);
clear_root_gid();
set_c0_diag(LOONGSON_DIAG_ITLB | LOONGSON_DIAG_DTLB);
}
EXPORT_SYMBOL_GPL(kvm_loongson_clear_guest_vtlb);
void kvm_loongson_clear_guest_ftlb(void)
{
int i;
int idx = read_gc0_index();
/* Set root GuestID for root probe and write of guest TLB entry */
set_root_gid_to_guest_gid();
for (i = current_cpu_data.tlbsizevtlb;
i < (current_cpu_data.tlbsizevtlb +
current_cpu_data.tlbsizeftlbsets);
i++) {
write_gc0_index(i);
guest_tlbinvf();
}
write_gc0_index(idx);
clear_root_gid();
set_c0_diag(LOONGSON_DIAG_ITLB | LOONGSON_DIAG_DTLB);
}
EXPORT_SYMBOL_GPL(kvm_loongson_clear_guest_ftlb);
#endif
+346
View File
@@ -0,0 +1,346 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
#if !defined(_TRACE_KVM_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_KVM_H
#include <linux/tracepoint.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM kvm
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE trace
/*
* arch/mips/kvm/mips.c
*/
extern bool kvm_trace_guest_mode_change;
int kvm_guest_mode_change_trace_reg(void);
void kvm_guest_mode_change_trace_unreg(void);
/*
* Tracepoints for VM enters
*/
DECLARE_EVENT_CLASS(kvm_transition,
TP_PROTO(struct kvm_vcpu *vcpu),
TP_ARGS(vcpu),
TP_STRUCT__entry(
__field(unsigned long, pc)
),
TP_fast_assign(
__entry->pc = vcpu->arch.pc;
),
TP_printk("PC: 0x%08lx",
__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));
/* The first 32 exit reasons correspond to Cause.ExcCode */
#define KVM_TRACE_EXIT_INT 0
#define KVM_TRACE_EXIT_TLBMOD 1
#define KVM_TRACE_EXIT_TLBMISS_LD 2
#define KVM_TRACE_EXIT_TLBMISS_ST 3
#define KVM_TRACE_EXIT_ADDRERR_LD 4
#define KVM_TRACE_EXIT_ADDRERR_ST 5
#define KVM_TRACE_EXIT_SYSCALL 8
#define KVM_TRACE_EXIT_BREAK_INST 9
#define KVM_TRACE_EXIT_RESVD_INST 10
#define KVM_TRACE_EXIT_COP_UNUSABLE 11
#define KVM_TRACE_EXIT_TRAP_INST 13
#define KVM_TRACE_EXIT_MSA_FPE 14
#define KVM_TRACE_EXIT_FPE 15
#define KVM_TRACE_EXIT_MSA_DISABLED 21
#define KVM_TRACE_EXIT_GUEST_EXIT 27
/* Further exit reasons */
#define KVM_TRACE_EXIT_WAIT 32
#define KVM_TRACE_EXIT_CACHE 33
#define KVM_TRACE_EXIT_SIGNAL 34
/* 32 exit reasons correspond to GuestCtl0.GExcCode (VZ) */
#define KVM_TRACE_EXIT_GEXCCODE_BASE 64
#define KVM_TRACE_EXIT_GPSI 64 /* 0 */
#define KVM_TRACE_EXIT_GSFC 65 /* 1 */
#define KVM_TRACE_EXIT_HC 66 /* 2 */
#define KVM_TRACE_EXIT_GRR 67 /* 3 */
#define KVM_TRACE_EXIT_GVA 72 /* 8 */
#define KVM_TRACE_EXIT_GHFC 73 /* 9 */
#define KVM_TRACE_EXIT_GPA 74 /* 10 */
/* Tracepoints for VM exits */
#define kvm_trace_symbol_exit_types \
{ KVM_TRACE_EXIT_INT, "Interrupt" }, \
{ KVM_TRACE_EXIT_TLBMOD, "TLB Mod" }, \
{ KVM_TRACE_EXIT_TLBMISS_LD, "TLB Miss (LD)" }, \
{ KVM_TRACE_EXIT_TLBMISS_ST, "TLB Miss (ST)" }, \
{ KVM_TRACE_EXIT_ADDRERR_LD, "Address Error (LD)" }, \
{ KVM_TRACE_EXIT_ADDRERR_ST, "Address Err (ST)" }, \
{ KVM_TRACE_EXIT_SYSCALL, "System Call" }, \
{ KVM_TRACE_EXIT_BREAK_INST, "Break Inst" }, \
{ KVM_TRACE_EXIT_RESVD_INST, "Reserved Inst" }, \
{ KVM_TRACE_EXIT_COP_UNUSABLE, "COP0/1 Unusable" }, \
{ KVM_TRACE_EXIT_TRAP_INST, "Trap Inst" }, \
{ KVM_TRACE_EXIT_MSA_FPE, "MSA FPE" }, \
{ KVM_TRACE_EXIT_FPE, "FPE" }, \
{ KVM_TRACE_EXIT_MSA_DISABLED, "MSA Disabled" }, \
{ KVM_TRACE_EXIT_GUEST_EXIT, "Guest Exit" }, \
{ KVM_TRACE_EXIT_WAIT, "WAIT" }, \
{ KVM_TRACE_EXIT_CACHE, "CACHE" }, \
{ KVM_TRACE_EXIT_SIGNAL, "Signal" }, \
{ KVM_TRACE_EXIT_GPSI, "GPSI" }, \
{ KVM_TRACE_EXIT_GSFC, "GSFC" }, \
{ KVM_TRACE_EXIT_HC, "HC" }, \
{ KVM_TRACE_EXIT_GRR, "GRR" }, \
{ KVM_TRACE_EXIT_GVA, "GVA" }, \
{ KVM_TRACE_EXIT_GHFC, "GHFC" }, \
{ KVM_TRACE_EXIT_GPA, "GPA" }
TRACE_EVENT(kvm_exit,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int reason),
TP_ARGS(vcpu, reason),
TP_STRUCT__entry(
__field(unsigned long, pc)
__field(unsigned int, reason)
),
TP_fast_assign(
__entry->pc = vcpu->arch.pc;
__entry->reason = reason;
),
TP_printk("[%s]PC: 0x%08lx",
__print_symbolic(__entry->reason,
kvm_trace_symbol_exit_types),
__entry->pc)
);
#define KVM_TRACE_MFC0 0
#define KVM_TRACE_MTC0 1
#define KVM_TRACE_DMFC0 2
#define KVM_TRACE_DMTC0 3
#define KVM_TRACE_RDHWR 4
#define KVM_TRACE_HWR_COP0 0
#define KVM_TRACE_HWR_HWR 1
#define KVM_TRACE_COP0(REG, SEL) ((KVM_TRACE_HWR_COP0 << 8) | \
((REG) << 3) | (SEL))
#define KVM_TRACE_HWR(REG, SEL) ((KVM_TRACE_HWR_HWR << 8) | \
((REG) << 3) | (SEL))
#define kvm_trace_symbol_hwr_ops \
{ KVM_TRACE_MFC0, "MFC0" }, \
{ KVM_TRACE_MTC0, "MTC0" }, \
{ KVM_TRACE_DMFC0, "DMFC0" }, \
{ KVM_TRACE_DMTC0, "DMTC0" }, \
{ KVM_TRACE_RDHWR, "RDHWR" }
#define kvm_trace_symbol_hwr_cop \
{ KVM_TRACE_HWR_COP0, "COP0" }, \
{ KVM_TRACE_HWR_HWR, "HWR" }
#define kvm_trace_symbol_hwr_regs \
{ KVM_TRACE_COP0( 0, 0), "Index" }, \
{ KVM_TRACE_COP0( 2, 0), "EntryLo0" }, \
{ KVM_TRACE_COP0( 3, 0), "EntryLo1" }, \
{ KVM_TRACE_COP0( 4, 0), "Context" }, \
{ KVM_TRACE_COP0( 4, 2), "UserLocal" }, \
{ KVM_TRACE_COP0( 5, 0), "PageMask" }, \
{ KVM_TRACE_COP0( 6, 0), "Wired" }, \
{ KVM_TRACE_COP0( 7, 0), "HWREna" }, \
{ KVM_TRACE_COP0( 8, 0), "BadVAddr" }, \
{ KVM_TRACE_COP0( 9, 0), "Count" }, \
{ KVM_TRACE_COP0(10, 0), "EntryHi" }, \
{ KVM_TRACE_COP0(11, 0), "Compare" }, \
{ KVM_TRACE_COP0(12, 0), "Status" }, \
{ KVM_TRACE_COP0(12, 1), "IntCtl" }, \
{ KVM_TRACE_COP0(12, 2), "SRSCtl" }, \
{ KVM_TRACE_COP0(13, 0), "Cause" }, \
{ KVM_TRACE_COP0(14, 0), "EPC" }, \
{ KVM_TRACE_COP0(15, 0), "PRId" }, \
{ KVM_TRACE_COP0(15, 1), "EBase" }, \
{ KVM_TRACE_COP0(16, 0), "Config" }, \
{ KVM_TRACE_COP0(16, 1), "Config1" }, \
{ KVM_TRACE_COP0(16, 2), "Config2" }, \
{ KVM_TRACE_COP0(16, 3), "Config3" }, \
{ KVM_TRACE_COP0(16, 4), "Config4" }, \
{ KVM_TRACE_COP0(16, 5), "Config5" }, \
{ KVM_TRACE_COP0(16, 7), "Config7" }, \
{ KVM_TRACE_COP0(17, 1), "MAAR" }, \
{ KVM_TRACE_COP0(17, 2), "MAARI" }, \
{ KVM_TRACE_COP0(26, 0), "ECC" }, \
{ KVM_TRACE_COP0(30, 0), "ErrorEPC" }, \
{ KVM_TRACE_COP0(31, 2), "KScratch1" }, \
{ KVM_TRACE_COP0(31, 3), "KScratch2" }, \
{ KVM_TRACE_COP0(31, 4), "KScratch3" }, \
{ KVM_TRACE_COP0(31, 5), "KScratch4" }, \
{ KVM_TRACE_COP0(31, 6), "KScratch5" }, \
{ KVM_TRACE_COP0(31, 7), "KScratch6" }, \
{ KVM_TRACE_HWR( 0, 0), "CPUNum" }, \
{ KVM_TRACE_HWR( 1, 0), "SYNCI_Step" }, \
{ KVM_TRACE_HWR( 2, 0), "CC" }, \
{ KVM_TRACE_HWR( 3, 0), "CCRes" }, \
{ KVM_TRACE_HWR(29, 0), "ULR" }
TRACE_EVENT(kvm_hwr,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int op, unsigned int reg,
unsigned long val),
TP_ARGS(vcpu, op, reg, val),
TP_STRUCT__entry(
__field(unsigned long, val)
__field(u16, reg)
__field(u8, op)
),
TP_fast_assign(
__entry->val = val;
__entry->reg = reg;
__entry->op = op;
),
TP_printk("%s %s (%s:%u:%u) 0x%08lx",
__print_symbolic(__entry->op,
kvm_trace_symbol_hwr_ops),
__print_symbolic(__entry->reg,
kvm_trace_symbol_hwr_regs),
__print_symbolic(__entry->reg >> 8,
kvm_trace_symbol_hwr_cop),
(__entry->reg >> 3) & 0x1f,
__entry->reg & 0x7,
__entry->val)
);
#define KVM_TRACE_AUX_RESTORE 0
#define KVM_TRACE_AUX_SAVE 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_MSA 2
#define KVM_TRACE_AUX_FPU_MSA 3
#define kvm_trace_symbol_aux_op \
{ KVM_TRACE_AUX_RESTORE, "restore" }, \
{ KVM_TRACE_AUX_SAVE, "save" }, \
{ 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_MSA, "MSA" }, \
{ KVM_TRACE_AUX_FPU_MSA, "FPU & MSA" }
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)
);
TRACE_EVENT(kvm_asid_change,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int old_asid,
unsigned int new_asid),
TP_ARGS(vcpu, old_asid, new_asid),
TP_STRUCT__entry(
__field(unsigned long, pc)
__field(u8, old_asid)
__field(u8, new_asid)
),
TP_fast_assign(
__entry->pc = vcpu->arch.pc;
__entry->old_asid = old_asid;
__entry->new_asid = new_asid;
),
TP_printk("PC: 0x%08lx old: 0x%02x new: 0x%02x",
__entry->pc,
__entry->old_asid,
__entry->new_asid)
);
TRACE_EVENT(kvm_guestid_change,
TP_PROTO(struct kvm_vcpu *vcpu, unsigned int guestid),
TP_ARGS(vcpu, guestid),
TP_STRUCT__entry(
__field(unsigned int, guestid)
),
TP_fast_assign(
__entry->guestid = guestid;
),
TP_printk("GuestID: 0x%02x",
__entry->guestid)
);
TRACE_EVENT_FN(kvm_guest_mode_change,
TP_PROTO(struct kvm_vcpu *vcpu),
TP_ARGS(vcpu),
TP_STRUCT__entry(
__field(unsigned long, epc)
__field(unsigned long, pc)
__field(unsigned long, badvaddr)
__field(unsigned int, status)
__field(unsigned int, cause)
),
TP_fast_assign(
__entry->epc = kvm_read_c0_guest_epc(&vcpu->arch.cop0);
__entry->pc = vcpu->arch.pc;
__entry->badvaddr = kvm_read_c0_guest_badvaddr(&vcpu->arch.cop0);
__entry->status = kvm_read_c0_guest_status(&vcpu->arch.cop0);
__entry->cause = kvm_read_c0_guest_cause(&vcpu->arch.cop0);
),
TP_printk("EPC: 0x%08lx PC: 0x%08lx Status: 0x%08x Cause: 0x%08x BadVAddr: 0x%08lx",
__entry->epc,
__entry->pc,
__entry->status,
__entry->cause,
__entry->badvaddr),
kvm_guest_mode_change_trace_reg,
kvm_guest_mode_change_trace_unreg
);
#endif /* _TRACE_KVM_H */
/* This part must be outside protection */
#include <trace/define_trace.h>
File diff suppressed because it is too large Load Diff