|  | /* | 
|  | * 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/init.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/smp.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/kvm_host.h> | 
|  |  | 
|  | #include <asm/cpu.h> | 
|  | #include <asm/bootinfo.h> | 
|  | #include <asm/mmu_context.h> | 
|  | #include <asm/pgtable.h> | 
|  | #include <asm/cacheflush.h> | 
|  |  | 
|  | #undef CONFIG_MIPS_MT | 
|  | #include <asm/r4kcache.h> | 
|  | #define CONFIG_MIPS_MT | 
|  |  | 
|  | #define KVM_GUEST_PC_TLB    0 | 
|  | #define KVM_GUEST_SP_TLB    1 | 
|  |  | 
|  | #define PRIx64 "llx" | 
|  |  | 
|  | /* Use VZ EntryHi.EHINV to invalidate TLB entries */ | 
|  | #define UNIQUE_ENTRYHI(idx) (CKSEG0 + ((idx) << (PAGE_SHIFT + 1))) | 
|  |  | 
|  | atomic_t kvm_mips_instance; | 
|  | EXPORT_SYMBOL(kvm_mips_instance); | 
|  |  | 
|  | /* These function pointers are initialized once the KVM module is loaded */ | 
|  | pfn_t(*kvm_mips_gfn_to_pfn) (struct kvm *kvm, gfn_t gfn); | 
|  | EXPORT_SYMBOL(kvm_mips_gfn_to_pfn); | 
|  |  | 
|  | void (*kvm_mips_release_pfn_clean) (pfn_t pfn); | 
|  | EXPORT_SYMBOL(kvm_mips_release_pfn_clean); | 
|  |  | 
|  | bool(*kvm_mips_is_error_pfn) (pfn_t pfn); | 
|  | EXPORT_SYMBOL(kvm_mips_is_error_pfn); | 
|  |  | 
|  | uint32_t kvm_mips_get_kernel_asid(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | return ASID_MASK(vcpu->arch.guest_kernel_asid[smp_processor_id()]); | 
|  | } | 
|  |  | 
|  |  | 
|  | uint32_t kvm_mips_get_user_asid(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | return ASID_MASK(vcpu->arch.guest_user_asid[smp_processor_id()]); | 
|  | } | 
|  |  | 
|  | inline uint32_t kvm_mips_get_commpage_asid (struct kvm_vcpu *vcpu) | 
|  | { | 
|  | return vcpu->kvm->arch.commpage_tlb; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Structure defining an tlb entry data set. | 
|  | */ | 
|  |  | 
|  | void kvm_mips_dump_host_tlbs(void) | 
|  | { | 
|  | unsigned long old_entryhi; | 
|  | unsigned long old_pagemask; | 
|  | struct kvm_mips_tlb tlb; | 
|  | unsigned long flags; | 
|  | int i; | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  | old_pagemask = read_c0_pagemask(); | 
|  |  | 
|  | printk("HOST TLBs:\n"); | 
|  | printk("ASID: %#lx\n", ASID_MASK(read_c0_entryhi())); | 
|  |  | 
|  | for (i = 0; i < current_cpu_data.tlbsize; i++) { | 
|  | write_c0_index(i); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_read(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | tlb.tlb_hi = read_c0_entryhi(); | 
|  | tlb.tlb_lo0 = read_c0_entrylo0(); | 
|  | tlb.tlb_lo1 = read_c0_entrylo1(); | 
|  | tlb.tlb_mask = read_c0_pagemask(); | 
|  |  | 
|  | printk("TLB%c%3d Hi 0x%08lx ", | 
|  | (tlb.tlb_lo0 | tlb.tlb_lo1) & MIPS3_PG_V ? ' ' : '*', | 
|  | i, tlb.tlb_hi); | 
|  | printk("Lo0=0x%09" PRIx64 " %c%c attr %lx ", | 
|  | (uint64_t) mips3_tlbpfn_to_paddr(tlb.tlb_lo0), | 
|  | (tlb.tlb_lo0 & MIPS3_PG_D) ? 'D' : ' ', | 
|  | (tlb.tlb_lo0 & MIPS3_PG_G) ? 'G' : ' ', | 
|  | (tlb.tlb_lo0 >> 3) & 7); | 
|  | printk("Lo1=0x%09" PRIx64 " %c%c attr %lx sz=%lx\n", | 
|  | (uint64_t) mips3_tlbpfn_to_paddr(tlb.tlb_lo1), | 
|  | (tlb.tlb_lo1 & MIPS3_PG_D) ? 'D' : ' ', | 
|  | (tlb.tlb_lo1 & MIPS3_PG_G) ? 'G' : ' ', | 
|  | (tlb.tlb_lo1 >> 3) & 7, tlb.tlb_mask); | 
|  | } | 
|  | write_c0_entryhi(old_entryhi); | 
|  | write_c0_pagemask(old_pagemask); | 
|  | mtc0_tlbw_hazard(); | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  | void kvm_mips_dump_guest_tlbs(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | struct mips_coproc *cop0 = vcpu->arch.cop0; | 
|  | struct kvm_mips_tlb tlb; | 
|  | int i; | 
|  |  | 
|  | printk("Guest TLBs:\n"); | 
|  | printk("Guest EntryHi: %#lx\n", kvm_read_c0_guest_entryhi(cop0)); | 
|  |  | 
|  | for (i = 0; i < KVM_MIPS_GUEST_TLB_SIZE; i++) { | 
|  | tlb = vcpu->arch.guest_tlb[i]; | 
|  | printk("TLB%c%3d Hi 0x%08lx ", | 
|  | (tlb.tlb_lo0 | tlb.tlb_lo1) & MIPS3_PG_V ? ' ' : '*', | 
|  | i, tlb.tlb_hi); | 
|  | printk("Lo0=0x%09" PRIx64 " %c%c attr %lx ", | 
|  | (uint64_t) mips3_tlbpfn_to_paddr(tlb.tlb_lo0), | 
|  | (tlb.tlb_lo0 & MIPS3_PG_D) ? 'D' : ' ', | 
|  | (tlb.tlb_lo0 & MIPS3_PG_G) ? 'G' : ' ', | 
|  | (tlb.tlb_lo0 >> 3) & 7); | 
|  | printk("Lo1=0x%09" PRIx64 " %c%c attr %lx sz=%lx\n", | 
|  | (uint64_t) mips3_tlbpfn_to_paddr(tlb.tlb_lo1), | 
|  | (tlb.tlb_lo1 & MIPS3_PG_D) ? 'D' : ' ', | 
|  | (tlb.tlb_lo1 & MIPS3_PG_G) ? 'G' : ' ', | 
|  | (tlb.tlb_lo1 >> 3) & 7, tlb.tlb_mask); | 
|  | } | 
|  | } | 
|  |  | 
|  | void kvm_mips_dump_shadow_tlbs(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | int i; | 
|  | volatile struct kvm_mips_tlb tlb; | 
|  |  | 
|  | printk("Shadow TLBs:\n"); | 
|  | for (i = 0; i < KVM_MIPS_GUEST_TLB_SIZE; i++) { | 
|  | tlb = vcpu->arch.shadow_tlb[smp_processor_id()][i]; | 
|  | printk("TLB%c%3d Hi 0x%08lx ", | 
|  | (tlb.tlb_lo0 | tlb.tlb_lo1) & MIPS3_PG_V ? ' ' : '*', | 
|  | i, tlb.tlb_hi); | 
|  | printk("Lo0=0x%09" PRIx64 " %c%c attr %lx ", | 
|  | (uint64_t) mips3_tlbpfn_to_paddr(tlb.tlb_lo0), | 
|  | (tlb.tlb_lo0 & MIPS3_PG_D) ? 'D' : ' ', | 
|  | (tlb.tlb_lo0 & MIPS3_PG_G) ? 'G' : ' ', | 
|  | (tlb.tlb_lo0 >> 3) & 7); | 
|  | printk("Lo1=0x%09" PRIx64 " %c%c attr %lx sz=%lx\n", | 
|  | (uint64_t) mips3_tlbpfn_to_paddr(tlb.tlb_lo1), | 
|  | (tlb.tlb_lo1 & MIPS3_PG_D) ? 'D' : ' ', | 
|  | (tlb.tlb_lo1 & MIPS3_PG_G) ? 'G' : ' ', | 
|  | (tlb.tlb_lo1 >> 3) & 7, tlb.tlb_mask); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void kvm_mips_map_page(struct kvm *kvm, gfn_t gfn) | 
|  | { | 
|  | pfn_t pfn; | 
|  |  | 
|  | if (kvm->arch.guest_pmap[gfn] != KVM_INVALID_PAGE) | 
|  | return; | 
|  |  | 
|  | pfn = kvm_mips_gfn_to_pfn(kvm, gfn); | 
|  |  | 
|  | if (kvm_mips_is_error_pfn(pfn)) { | 
|  | panic("Couldn't get pfn for gfn %#" PRIx64 "!\n", gfn); | 
|  | } | 
|  |  | 
|  | kvm->arch.guest_pmap[gfn] = pfn; | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Translate guest KSEG0 addresses to Host PA */ | 
|  | unsigned long kvm_mips_translate_guest_kseg0_to_hpa(struct kvm_vcpu *vcpu, | 
|  | unsigned long gva) | 
|  | { | 
|  | gfn_t gfn; | 
|  | uint32_t offset = gva & ~PAGE_MASK; | 
|  | struct kvm *kvm = vcpu->kvm; | 
|  |  | 
|  | if (KVM_GUEST_KSEGX(gva) != KVM_GUEST_KSEG0) { | 
|  | kvm_err("%s/%p: Invalid gva: %#lx\n", __func__, | 
|  | __builtin_return_address(0), gva); | 
|  | return KVM_INVALID_PAGE; | 
|  | } | 
|  |  | 
|  | gfn = (KVM_GUEST_CPHYSADDR(gva) >> PAGE_SHIFT); | 
|  |  | 
|  | if (gfn >= kvm->arch.guest_pmap_npages) { | 
|  | kvm_err("%s: Invalid gfn: %#llx, GVA: %#lx\n", __func__, gfn, | 
|  | gva); | 
|  | return KVM_INVALID_PAGE; | 
|  | } | 
|  | kvm_mips_map_page(vcpu->kvm, gfn); | 
|  | return (kvm->arch.guest_pmap[gfn] << PAGE_SHIFT) + offset; | 
|  | } | 
|  |  | 
|  | /* XXXKYMA: Must be called with interrupts disabled */ | 
|  | /* set flush_dcache_mask == 0 if no dcache flush required */ | 
|  | int | 
|  | kvm_mips_host_tlb_write(struct kvm_vcpu *vcpu, unsigned long entryhi, | 
|  | unsigned long entrylo0, unsigned long entrylo1, int flush_dcache_mask) | 
|  | { | 
|  | unsigned long flags; | 
|  | unsigned long old_entryhi; | 
|  | volatile int idx; | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  | write_c0_entryhi(entryhi); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_probe(); | 
|  | tlb_probe_hazard(); | 
|  | idx = read_c0_index(); | 
|  |  | 
|  | if (idx > current_cpu_data.tlbsize) { | 
|  | kvm_err("%s: Invalid Index: %d\n", __func__, idx); | 
|  | kvm_mips_dump_host_tlbs(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (idx < 0) { | 
|  | idx = read_c0_random() % current_cpu_data.tlbsize; | 
|  | write_c0_index(idx); | 
|  | mtc0_tlbw_hazard(); | 
|  | } | 
|  | write_c0_entrylo0(entrylo0); | 
|  | write_c0_entrylo1(entrylo1); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_write_indexed(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | if (debug) { | 
|  | kvm_debug("@ %#lx idx: %2d [entryhi(R): %#lx] " | 
|  | "entrylo0(R): 0x%08lx, entrylo1(R): 0x%08lx\n", | 
|  | vcpu->arch.pc, idx, read_c0_entryhi(), | 
|  | read_c0_entrylo0(), read_c0_entrylo1()); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Flush D-cache */ | 
|  | if (flush_dcache_mask) { | 
|  | if (entrylo0 & MIPS3_PG_V) { | 
|  | ++vcpu->stat.flush_dcache_exits; | 
|  | flush_data_cache_page((entryhi & VPN2_MASK) & ~flush_dcache_mask); | 
|  | } | 
|  | if (entrylo1 & MIPS3_PG_V) { | 
|  | ++vcpu->stat.flush_dcache_exits; | 
|  | flush_data_cache_page(((entryhi & VPN2_MASK) & ~flush_dcache_mask) | | 
|  | (0x1 << PAGE_SHIFT)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Restore old ASID */ | 
|  | write_c0_entryhi(old_entryhi); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  | local_irq_restore(flags); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* XXXKYMA: Must be called with interrupts disabled */ | 
|  | int kvm_mips_handle_kseg0_tlb_fault(unsigned long badvaddr, | 
|  | struct kvm_vcpu *vcpu) | 
|  | { | 
|  | gfn_t gfn; | 
|  | pfn_t pfn0, pfn1; | 
|  | unsigned long vaddr = 0; | 
|  | unsigned long entryhi = 0, entrylo0 = 0, entrylo1 = 0; | 
|  | int even; | 
|  | struct kvm *kvm = vcpu->kvm; | 
|  | const int flush_dcache_mask = 0; | 
|  |  | 
|  |  | 
|  | if (KVM_GUEST_KSEGX(badvaddr) != KVM_GUEST_KSEG0) { | 
|  | kvm_err("%s: Invalid BadVaddr: %#lx\n", __func__, badvaddr); | 
|  | kvm_mips_dump_host_tlbs(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | gfn = (KVM_GUEST_CPHYSADDR(badvaddr) >> PAGE_SHIFT); | 
|  | if (gfn >= kvm->arch.guest_pmap_npages) { | 
|  | kvm_err("%s: Invalid gfn: %#llx, BadVaddr: %#lx\n", __func__, | 
|  | gfn, badvaddr); | 
|  | kvm_mips_dump_host_tlbs(); | 
|  | return -1; | 
|  | } | 
|  | even = !(gfn & 0x1); | 
|  | vaddr = badvaddr & (PAGE_MASK << 1); | 
|  |  | 
|  | kvm_mips_map_page(vcpu->kvm, gfn); | 
|  | kvm_mips_map_page(vcpu->kvm, gfn ^ 0x1); | 
|  |  | 
|  | if (even) { | 
|  | pfn0 = kvm->arch.guest_pmap[gfn]; | 
|  | pfn1 = kvm->arch.guest_pmap[gfn ^ 0x1]; | 
|  | } else { | 
|  | pfn0 = kvm->arch.guest_pmap[gfn ^ 0x1]; | 
|  | pfn1 = kvm->arch.guest_pmap[gfn]; | 
|  | } | 
|  |  | 
|  | entryhi = (vaddr | kvm_mips_get_kernel_asid(vcpu)); | 
|  | entrylo0 = mips3_paddr_to_tlbpfn(pfn0 << PAGE_SHIFT) | (0x3 << 3) | (1 << 2) | | 
|  | (0x1 << 1); | 
|  | entrylo1 = mips3_paddr_to_tlbpfn(pfn1 << PAGE_SHIFT) | (0x3 << 3) | (1 << 2) | | 
|  | (0x1 << 1); | 
|  |  | 
|  | return kvm_mips_host_tlb_write(vcpu, entryhi, entrylo0, entrylo1, | 
|  | flush_dcache_mask); | 
|  | } | 
|  |  | 
|  | int kvm_mips_handle_commpage_tlb_fault(unsigned long badvaddr, | 
|  | struct kvm_vcpu *vcpu) | 
|  | { | 
|  | pfn_t pfn0, pfn1; | 
|  | unsigned long flags, old_entryhi = 0, vaddr = 0; | 
|  | unsigned long entrylo0 = 0, entrylo1 = 0; | 
|  |  | 
|  |  | 
|  | pfn0 = CPHYSADDR(vcpu->arch.kseg0_commpage) >> PAGE_SHIFT; | 
|  | pfn1 = 0; | 
|  | entrylo0 = mips3_paddr_to_tlbpfn(pfn0 << PAGE_SHIFT) | (0x3 << 3) | (1 << 2) | | 
|  | (0x1 << 1); | 
|  | entrylo1 = 0; | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  | vaddr = badvaddr & (PAGE_MASK << 1); | 
|  | write_c0_entryhi(vaddr | kvm_mips_get_kernel_asid(vcpu)); | 
|  | mtc0_tlbw_hazard(); | 
|  | write_c0_entrylo0(entrylo0); | 
|  | mtc0_tlbw_hazard(); | 
|  | write_c0_entrylo1(entrylo1); | 
|  | mtc0_tlbw_hazard(); | 
|  | write_c0_index(kvm_mips_get_commpage_asid(vcpu)); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlb_write_indexed(); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | kvm_debug ("@ %#lx idx: %2d [entryhi(R): %#lx] entrylo0 (R): 0x%08lx, entrylo1(R): 0x%08lx\n", | 
|  | vcpu->arch.pc, read_c0_index(), read_c0_entryhi(), | 
|  | read_c0_entrylo0(), read_c0_entrylo1()); | 
|  | #endif | 
|  |  | 
|  | /* Restore old ASID */ | 
|  | write_c0_entryhi(old_entryhi); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  | local_irq_restore(flags); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | kvm_mips_handle_mapped_seg_tlb_fault(struct kvm_vcpu *vcpu, | 
|  | struct kvm_mips_tlb *tlb, unsigned long *hpa0, unsigned long *hpa1) | 
|  | { | 
|  | unsigned long entryhi = 0, entrylo0 = 0, entrylo1 = 0; | 
|  | struct kvm *kvm = vcpu->kvm; | 
|  | pfn_t pfn0, pfn1; | 
|  |  | 
|  |  | 
|  | if ((tlb->tlb_hi & VPN2_MASK) == 0) { | 
|  | pfn0 = 0; | 
|  | pfn1 = 0; | 
|  | } else { | 
|  | kvm_mips_map_page(kvm, mips3_tlbpfn_to_paddr(tlb->tlb_lo0) >> PAGE_SHIFT); | 
|  | kvm_mips_map_page(kvm, mips3_tlbpfn_to_paddr(tlb->tlb_lo1) >> PAGE_SHIFT); | 
|  |  | 
|  | pfn0 = kvm->arch.guest_pmap[mips3_tlbpfn_to_paddr(tlb->tlb_lo0) >> PAGE_SHIFT]; | 
|  | pfn1 = kvm->arch.guest_pmap[mips3_tlbpfn_to_paddr(tlb->tlb_lo1) >> PAGE_SHIFT]; | 
|  | } | 
|  |  | 
|  | if (hpa0) | 
|  | *hpa0 = pfn0 << PAGE_SHIFT; | 
|  |  | 
|  | if (hpa1) | 
|  | *hpa1 = pfn1 << PAGE_SHIFT; | 
|  |  | 
|  | /* Get attributes from the Guest TLB */ | 
|  | entryhi = (tlb->tlb_hi & VPN2_MASK) | (KVM_GUEST_KERNEL_MODE(vcpu) ? | 
|  | kvm_mips_get_kernel_asid(vcpu) : kvm_mips_get_user_asid(vcpu)); | 
|  | entrylo0 = mips3_paddr_to_tlbpfn(pfn0 << PAGE_SHIFT) | (0x3 << 3) | | 
|  | (tlb->tlb_lo0 & MIPS3_PG_D) | (tlb->tlb_lo0 & MIPS3_PG_V); | 
|  | entrylo1 = mips3_paddr_to_tlbpfn(pfn1 << PAGE_SHIFT) | (0x3 << 3) | | 
|  | (tlb->tlb_lo1 & MIPS3_PG_D) | (tlb->tlb_lo1 & MIPS3_PG_V); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | kvm_debug("@ %#lx tlb_lo0: 0x%08lx tlb_lo1: 0x%08lx\n", vcpu->arch.pc, | 
|  | tlb->tlb_lo0, tlb->tlb_lo1); | 
|  | #endif | 
|  |  | 
|  | return kvm_mips_host_tlb_write(vcpu, entryhi, entrylo0, entrylo1, | 
|  | tlb->tlb_mask); | 
|  | } | 
|  |  | 
|  | int kvm_mips_guest_tlb_lookup(struct kvm_vcpu *vcpu, unsigned long entryhi) | 
|  | { | 
|  | int i; | 
|  | int index = -1; | 
|  | struct kvm_mips_tlb *tlb = vcpu->arch.guest_tlb; | 
|  |  | 
|  |  | 
|  | for (i = 0; i < KVM_MIPS_GUEST_TLB_SIZE; i++) { | 
|  | if (((TLB_VPN2(tlb[i]) & ~tlb[i].tlb_mask) == ((entryhi & VPN2_MASK) & ~tlb[i].tlb_mask)) && | 
|  | (TLB_IS_GLOBAL(tlb[i]) || (TLB_ASID(tlb[i]) == ASID_MASK(entryhi)))) { | 
|  | index = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifdef DEBUG | 
|  | kvm_debug("%s: entryhi: %#lx, index: %d lo0: %#lx, lo1: %#lx\n", | 
|  | __func__, entryhi, index, tlb[i].tlb_lo0, tlb[i].tlb_lo1); | 
|  | #endif | 
|  |  | 
|  | return index; | 
|  | } | 
|  |  | 
|  | int kvm_mips_host_tlb_lookup(struct kvm_vcpu *vcpu, unsigned long vaddr) | 
|  | { | 
|  | unsigned long old_entryhi, flags; | 
|  | volatile int idx; | 
|  |  | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  |  | 
|  | if (KVM_GUEST_KERNEL_MODE(vcpu)) | 
|  | write_c0_entryhi((vaddr & VPN2_MASK) | kvm_mips_get_kernel_asid(vcpu)); | 
|  | else { | 
|  | write_c0_entryhi((vaddr & VPN2_MASK) | kvm_mips_get_user_asid(vcpu)); | 
|  | } | 
|  |  | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_probe(); | 
|  | tlb_probe_hazard(); | 
|  | idx = read_c0_index(); | 
|  |  | 
|  | /* Restore old ASID */ | 
|  | write_c0_entryhi(old_entryhi); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | kvm_debug("Host TLB lookup, %#lx, idx: %2d\n", vaddr, idx); | 
|  | #endif | 
|  |  | 
|  | return idx; | 
|  | } | 
|  |  | 
|  | int kvm_mips_host_tlb_inv(struct kvm_vcpu *vcpu, unsigned long va) | 
|  | { | 
|  | int idx; | 
|  | unsigned long flags, old_entryhi; | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  |  | 
|  | write_c0_entryhi((va & VPN2_MASK) | kvm_mips_get_user_asid(vcpu)); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_probe(); | 
|  | tlb_probe_hazard(); | 
|  | idx = read_c0_index(); | 
|  |  | 
|  | if (idx >= current_cpu_data.tlbsize) | 
|  | BUG(); | 
|  |  | 
|  | if (idx > 0) { | 
|  | write_c0_entryhi(UNIQUE_ENTRYHI(idx)); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | write_c0_entrylo0(0); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | write_c0_entrylo1(0); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_write_indexed(); | 
|  | mtc0_tlbw_hazard(); | 
|  | } | 
|  |  | 
|  | write_c0_entryhi(old_entryhi); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | if (idx > 0) { | 
|  | kvm_debug("%s: Invalidated entryhi %#lx @ idx %d\n", __func__, | 
|  | (va & VPN2_MASK) | (vcpu->arch.asid_map[va & ASID_MASK] & ASID_MASK), idx); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* XXXKYMA: Fix Guest USER/KERNEL no longer share the same ASID*/ | 
|  | int kvm_mips_host_tlb_inv_index(struct kvm_vcpu *vcpu, int index) | 
|  | { | 
|  | unsigned long flags, old_entryhi; | 
|  |  | 
|  | if (index >= current_cpu_data.tlbsize) | 
|  | BUG(); | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  |  | 
|  | write_c0_entryhi(UNIQUE_ENTRYHI(index)); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | write_c0_index(index); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | write_c0_entrylo0(0); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | write_c0_entrylo1(0); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_write_indexed(); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | write_c0_entryhi(old_entryhi); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void kvm_mips_flush_host_tlb(int skip_kseg0) | 
|  | { | 
|  | unsigned long flags; | 
|  | unsigned long old_entryhi, entryhi; | 
|  | unsigned long old_pagemask; | 
|  | int entry = 0; | 
|  | int maxentry = current_cpu_data.tlbsize; | 
|  |  | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  | old_pagemask = read_c0_pagemask(); | 
|  |  | 
|  | /* Blast 'em all away. */ | 
|  | for (entry = 0; entry < maxentry; entry++) { | 
|  |  | 
|  | write_c0_index(entry); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | if (skip_kseg0) { | 
|  | tlb_read(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | entryhi = read_c0_entryhi(); | 
|  |  | 
|  | /* Don't blow away guest kernel entries */ | 
|  | if (KVM_GUEST_KSEGX(entryhi) == KVM_GUEST_KSEG0) { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Make sure all entries differ. */ | 
|  | write_c0_entryhi(UNIQUE_ENTRYHI(entry)); | 
|  | mtc0_tlbw_hazard(); | 
|  | write_c0_entrylo0(0); | 
|  | mtc0_tlbw_hazard(); | 
|  | write_c0_entrylo1(0); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_write_indexed(); | 
|  | mtc0_tlbw_hazard(); | 
|  | } | 
|  |  | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | write_c0_entryhi(old_entryhi); | 
|  | write_c0_pagemask(old_pagemask); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  | void | 
|  | kvm_get_new_mmu_context(struct mm_struct *mm, unsigned long cpu, | 
|  | struct kvm_vcpu *vcpu) | 
|  | { | 
|  | unsigned long asid = asid_cache(cpu); | 
|  |  | 
|  | if (!(ASID_MASK(ASID_INC(asid)))) { | 
|  | if (cpu_has_vtag_icache) { | 
|  | flush_icache_all(); | 
|  | } | 
|  |  | 
|  | kvm_local_flush_tlb_all();      /* start new asid cycle */ | 
|  |  | 
|  | if (!asid)      /* fix version if needed */ | 
|  | asid = ASID_FIRST_VERSION; | 
|  | } | 
|  |  | 
|  | cpu_context(cpu, mm) = asid_cache(cpu) = asid; | 
|  | } | 
|  |  | 
|  | void kvm_shadow_tlb_put(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | unsigned long flags; | 
|  | unsigned long old_entryhi; | 
|  | unsigned long old_pagemask; | 
|  | int entry = 0; | 
|  | int cpu = smp_processor_id(); | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | old_entryhi = read_c0_entryhi(); | 
|  | old_pagemask = read_c0_pagemask(); | 
|  |  | 
|  | for (entry = 0; entry < current_cpu_data.tlbsize; entry++) { | 
|  | write_c0_index(entry); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlb_read(); | 
|  | tlbw_use_hazard(); | 
|  |  | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_hi = read_c0_entryhi(); | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_lo0 = read_c0_entrylo0(); | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_lo1 = read_c0_entrylo1(); | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_mask = read_c0_pagemask(); | 
|  | } | 
|  |  | 
|  | write_c0_entryhi(old_entryhi); | 
|  | write_c0_pagemask(old_pagemask); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  |  | 
|  | } | 
|  |  | 
|  | void kvm_shadow_tlb_load(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | unsigned long flags; | 
|  | unsigned long old_ctx; | 
|  | int entry; | 
|  | int cpu = smp_processor_id(); | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | old_ctx = read_c0_entryhi(); | 
|  |  | 
|  | for (entry = 0; entry < current_cpu_data.tlbsize; entry++) { | 
|  | write_c0_entryhi(vcpu->arch.shadow_tlb[cpu][entry].tlb_hi); | 
|  | mtc0_tlbw_hazard(); | 
|  | write_c0_entrylo0(vcpu->arch.shadow_tlb[cpu][entry].tlb_lo0); | 
|  | write_c0_entrylo1(vcpu->arch.shadow_tlb[cpu][entry].tlb_lo1); | 
|  |  | 
|  | write_c0_index(entry); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | tlb_write_indexed(); | 
|  | tlbw_use_hazard(); | 
|  | } | 
|  |  | 
|  | tlbw_use_hazard(); | 
|  | write_c0_entryhi(old_ctx); | 
|  | mtc0_tlbw_hazard(); | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  |  | 
|  | void kvm_local_flush_tlb_all(void) | 
|  | { | 
|  | unsigned long flags; | 
|  | unsigned long old_ctx; | 
|  | int entry = 0; | 
|  |  | 
|  | local_irq_save(flags); | 
|  | /* Save old context and create impossible VPN2 value */ | 
|  | old_ctx = read_c0_entryhi(); | 
|  | write_c0_entrylo0(0); | 
|  | write_c0_entrylo1(0); | 
|  |  | 
|  | /* Blast 'em all away. */ | 
|  | while (entry < current_cpu_data.tlbsize) { | 
|  | /* Make sure all entries differ. */ | 
|  | write_c0_entryhi(UNIQUE_ENTRYHI(entry)); | 
|  | write_c0_index(entry); | 
|  | mtc0_tlbw_hazard(); | 
|  | tlb_write_indexed(); | 
|  | entry++; | 
|  | } | 
|  | tlbw_use_hazard(); | 
|  | write_c0_entryhi(old_ctx); | 
|  | mtc0_tlbw_hazard(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  | void kvm_mips_init_shadow_tlb(struct kvm_vcpu *vcpu) | 
|  | { | 
|  | int cpu, entry; | 
|  |  | 
|  | for_each_possible_cpu(cpu) { | 
|  | for (entry = 0; entry < current_cpu_data.tlbsize; entry++) { | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_hi = | 
|  | UNIQUE_ENTRYHI(entry); | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_lo0 = 0x0; | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_lo1 = 0x0; | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_mask = | 
|  | read_c0_pagemask(); | 
|  | #ifdef DEBUG | 
|  | kvm_debug | 
|  | ("shadow_tlb[%d][%d]: tlb_hi: %#lx, lo0: %#lx, lo1: %#lx\n", | 
|  | cpu, entry, | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_hi, | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_lo0, | 
|  | vcpu->arch.shadow_tlb[cpu][entry].tlb_lo1); | 
|  | #endif | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Restore ASID once we are scheduled back after preemption */ | 
|  | void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu) | 
|  | { | 
|  | unsigned long flags; | 
|  | int newasid = 0; | 
|  |  | 
|  | #ifdef DEBUG | 
|  | kvm_debug("%s: vcpu %p, cpu: %d\n", __func__, vcpu, cpu); | 
|  | #endif | 
|  |  | 
|  | /* Alocate new kernel and user ASIDs if needed */ | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | if (((vcpu->arch. | 
|  | guest_kernel_asid[cpu] ^ asid_cache(cpu)) & ASID_VERSION_MASK)) { | 
|  | kvm_get_new_mmu_context(&vcpu->arch.guest_kernel_mm, cpu, vcpu); | 
|  | vcpu->arch.guest_kernel_asid[cpu] = | 
|  | vcpu->arch.guest_kernel_mm.context.asid[cpu]; | 
|  | kvm_get_new_mmu_context(&vcpu->arch.guest_user_mm, cpu, vcpu); | 
|  | vcpu->arch.guest_user_asid[cpu] = | 
|  | vcpu->arch.guest_user_mm.context.asid[cpu]; | 
|  | newasid++; | 
|  |  | 
|  | kvm_info("[%d]: cpu_context: %#lx\n", cpu, | 
|  | cpu_context(cpu, current->mm)); | 
|  | kvm_info("[%d]: Allocated new ASID for Guest Kernel: %#x\n", | 
|  | cpu, vcpu->arch.guest_kernel_asid[cpu]); | 
|  | kvm_info("[%d]: Allocated new ASID for Guest User: %#x\n", cpu, | 
|  | vcpu->arch.guest_user_asid[cpu]); | 
|  | } | 
|  |  | 
|  | if (vcpu->arch.last_sched_cpu != cpu) { | 
|  | kvm_info("[%d->%d]KVM VCPU[%d] switch\n", | 
|  | vcpu->arch.last_sched_cpu, cpu, vcpu->vcpu_id); | 
|  | } | 
|  |  | 
|  | /* Only reload shadow host TLB if new ASIDs haven't been allocated */ | 
|  | #if 0 | 
|  | if ((atomic_read(&kvm_mips_instance) > 1) && !newasid) { | 
|  | kvm_mips_flush_host_tlb(0); | 
|  | kvm_shadow_tlb_load(vcpu); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (!newasid) { | 
|  | /* If we preempted while the guest was executing, then reload the pre-empted ASID */ | 
|  | if (current->flags & PF_VCPU) { | 
|  | write_c0_entryhi(ASID_MASK(vcpu->arch.preempt_entryhi)); | 
|  | ehb(); | 
|  | } | 
|  | } else { | 
|  | /* New ASIDs were allocated for the VM */ | 
|  |  | 
|  | /* Were we in guest context? If so then the pre-empted ASID is no longer | 
|  | * valid, we need to set it to what it should be based on the mode of | 
|  | * the Guest (Kernel/User) | 
|  | */ | 
|  | if (current->flags & PF_VCPU) { | 
|  | if (KVM_GUEST_KERNEL_MODE(vcpu)) | 
|  | write_c0_entryhi(ASID_MASK(vcpu->arch. | 
|  | guest_kernel_asid[cpu])); | 
|  | else | 
|  | write_c0_entryhi(ASID_MASK(vcpu->arch. | 
|  | guest_user_asid[cpu])); | 
|  | ehb(); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | uint32_t cpu; | 
|  |  | 
|  | local_irq_save(flags); | 
|  |  | 
|  | cpu = smp_processor_id(); | 
|  |  | 
|  |  | 
|  | vcpu->arch.preempt_entryhi = read_c0_entryhi(); | 
|  | vcpu->arch.last_sched_cpu = cpu; | 
|  |  | 
|  | #if 0 | 
|  | if ((atomic_read(&kvm_mips_instance) > 1)) { | 
|  | kvm_shadow_tlb_put(vcpu); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (((cpu_context(cpu, current->mm) ^ asid_cache(cpu)) & | 
|  | ASID_VERSION_MASK)) { | 
|  | kvm_debug("%s: Dropping MMU Context:  %#lx\n", __func__, | 
|  | cpu_context(cpu, current->mm)); | 
|  | drop_mmu_context(current->mm, cpu); | 
|  | } | 
|  | write_c0_entryhi(cpu_asid(cpu, current->mm)); | 
|  | ehb(); | 
|  |  | 
|  | local_irq_restore(flags); | 
|  | } | 
|  |  | 
|  | uint32_t kvm_get_inst(uint32_t *opc, struct kvm_vcpu *vcpu) | 
|  | { | 
|  | struct mips_coproc *cop0 = vcpu->arch.cop0; | 
|  | unsigned long paddr, flags; | 
|  | uint32_t inst; | 
|  | int index; | 
|  |  | 
|  | if (KVM_GUEST_KSEGX((unsigned long) opc) < KVM_GUEST_KSEG0 || | 
|  | KVM_GUEST_KSEGX((unsigned long) opc) == KVM_GUEST_KSEG23) { | 
|  | local_irq_save(flags); | 
|  | index = kvm_mips_host_tlb_lookup(vcpu, (unsigned long) opc); | 
|  | if (index >= 0) { | 
|  | inst = *(opc); | 
|  | } else { | 
|  | index = | 
|  | kvm_mips_guest_tlb_lookup(vcpu, | 
|  | ((unsigned long) opc & VPN2_MASK) | 
|  | | | 
|  | ASID_MASK(kvm_read_c0_guest_entryhi(cop0))); | 
|  | if (index < 0) { | 
|  | kvm_err | 
|  | ("%s: get_user_failed for %p, vcpu: %p, ASID: %#lx\n", | 
|  | __func__, opc, vcpu, read_c0_entryhi()); | 
|  | kvm_mips_dump_host_tlbs(); | 
|  | local_irq_restore(flags); | 
|  | return KVM_INVALID_INST; | 
|  | } | 
|  | kvm_mips_handle_mapped_seg_tlb_fault(vcpu, | 
|  | &vcpu->arch. | 
|  | guest_tlb[index], | 
|  | NULL, NULL); | 
|  | inst = *(opc); | 
|  | } | 
|  | local_irq_restore(flags); | 
|  | } else if (KVM_GUEST_KSEGX(opc) == KVM_GUEST_KSEG0) { | 
|  | paddr = | 
|  | kvm_mips_translate_guest_kseg0_to_hpa(vcpu, | 
|  | (unsigned long) opc); | 
|  | inst = *(uint32_t *) CKSEG0ADDR(paddr); | 
|  | } else { | 
|  | kvm_err("%s: illegal address: %p\n", __func__, opc); | 
|  | return KVM_INVALID_INST; | 
|  | } | 
|  |  | 
|  | return inst; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL(kvm_local_flush_tlb_all); | 
|  | EXPORT_SYMBOL(kvm_shadow_tlb_put); | 
|  | EXPORT_SYMBOL(kvm_mips_handle_mapped_seg_tlb_fault); | 
|  | EXPORT_SYMBOL(kvm_mips_handle_commpage_tlb_fault); | 
|  | EXPORT_SYMBOL(kvm_mips_init_shadow_tlb); | 
|  | EXPORT_SYMBOL(kvm_mips_dump_host_tlbs); | 
|  | EXPORT_SYMBOL(kvm_mips_handle_kseg0_tlb_fault); | 
|  | EXPORT_SYMBOL(kvm_mips_host_tlb_lookup); | 
|  | EXPORT_SYMBOL(kvm_mips_flush_host_tlb); | 
|  | EXPORT_SYMBOL(kvm_mips_guest_tlb_lookup); | 
|  | EXPORT_SYMBOL(kvm_mips_host_tlb_inv); | 
|  | EXPORT_SYMBOL(kvm_mips_translate_guest_kseg0_to_hpa); | 
|  | EXPORT_SYMBOL(kvm_shadow_tlb_load); | 
|  | EXPORT_SYMBOL(kvm_mips_dump_shadow_tlbs); | 
|  | EXPORT_SYMBOL(kvm_mips_dump_guest_tlbs); | 
|  | EXPORT_SYMBOL(kvm_get_inst); | 
|  | EXPORT_SYMBOL(kvm_arch_vcpu_load); | 
|  | EXPORT_SYMBOL(kvm_arch_vcpu_put); |