MIPS: KVM: Fix mapped fault broken commpage handling
[linux-2.6-block.git] / arch / mips / kvm / mmu.c
index 2f494ec5c939c0332487c6ea830908ffa797e846..96e0b24cfe5cb6bfdaca0b950c0476bf84e5a1fb 100644 (file)
@@ -9,6 +9,7 @@
  * Authors: Sanjay Lal <sanjayl@kymasys.com>
  */
 
+#include <linux/highmem.h>
 #include <linux/kvm_host.h>
 #include <asm/mmu_context.h>
 
@@ -116,9 +117,11 @@ int kvm_mips_handle_kseg0_tlb_fault(unsigned long badvaddr,
        pfn1 = kvm->arch.guest_pmap[gfn | 0x1];
 
        entrylo0 = mips3_paddr_to_tlbpfn(pfn0 << PAGE_SHIFT) |
-                  (0x3 << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V;
+               ((_page_cachable_default >> _CACHE_SHIFT) << ENTRYLO_C_SHIFT) |
+               ENTRYLO_D | ENTRYLO_V;
        entrylo1 = mips3_paddr_to_tlbpfn(pfn1 << PAGE_SHIFT) |
-                  (0x3 << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V;
+               ((_page_cachable_default >> _CACHE_SHIFT) << ENTRYLO_C_SHIFT) |
+               ENTRYLO_D | ENTRYLO_V;
 
        preempt_disable();
        entryhi = (vaddr | kvm_mips_get_kernel_asid(vcpu));
@@ -135,35 +138,42 @@ int kvm_mips_handle_mapped_seg_tlb_fault(struct kvm_vcpu *vcpu,
        unsigned long entryhi = 0, entrylo0 = 0, entrylo1 = 0;
        struct kvm *kvm = vcpu->kvm;
        kvm_pfn_t pfn0, pfn1;
+       long tlb_lo[2];
        int ret;
 
-       if ((tlb->tlb_hi & VPN2_MASK) == 0) {
-               pfn0 = 0;
-               pfn1 = 0;
-       } else {
-               if (kvm_mips_map_page(kvm, mips3_tlbpfn_to_paddr(tlb->tlb_lo[0])
-                                          >> PAGE_SHIFT) < 0)
-                       return -1;
-
-               if (kvm_mips_map_page(kvm, mips3_tlbpfn_to_paddr(tlb->tlb_lo[1])
-                                          >> PAGE_SHIFT) < 0)
-                       return -1;
-
-               pfn0 = kvm->arch.guest_pmap[
-                       mips3_tlbpfn_to_paddr(tlb->tlb_lo[0]) >> PAGE_SHIFT];
-               pfn1 = kvm->arch.guest_pmap[
-                       mips3_tlbpfn_to_paddr(tlb->tlb_lo[1]) >> PAGE_SHIFT];
-       }
+       tlb_lo[0] = tlb->tlb_lo[0];
+       tlb_lo[1] = tlb->tlb_lo[1];
+
+       /*
+        * The commpage address must not be mapped to anything else if the guest
+        * TLB contains entries nearby, or commpage accesses will break.
+        */
+       if (!((tlb->tlb_hi ^ KVM_GUEST_COMMPAGE_ADDR) &
+                       VPN2_MASK & (PAGE_MASK << 1)))
+               tlb_lo[(KVM_GUEST_COMMPAGE_ADDR >> PAGE_SHIFT) & 1] = 0;
+
+       if (kvm_mips_map_page(kvm, mips3_tlbpfn_to_paddr(tlb_lo[0])
+                                  >> PAGE_SHIFT) < 0)
+               return -1;
+
+       if (kvm_mips_map_page(kvm, mips3_tlbpfn_to_paddr(tlb_lo[1])
+                                  >> PAGE_SHIFT) < 0)
+               return -1;
+
+       pfn0 = kvm->arch.guest_pmap[
+               mips3_tlbpfn_to_paddr(tlb_lo[0]) >> PAGE_SHIFT];
+       pfn1 = kvm->arch.guest_pmap[
+               mips3_tlbpfn_to_paddr(tlb_lo[1]) >> PAGE_SHIFT];
 
        /* Get attributes from the Guest TLB */
        entrylo0 = mips3_paddr_to_tlbpfn(pfn0 << PAGE_SHIFT) |
-                  (0x3 << ENTRYLO_C_SHIFT) |
-                  (tlb->tlb_lo[0] & ENTRYLO_D) |
-                  (tlb->tlb_lo[0] & ENTRYLO_V);
+               ((_page_cachable_default >> _CACHE_SHIFT) << ENTRYLO_C_SHIFT) |
+               (tlb_lo[0] & ENTRYLO_D) |
+               (tlb_lo[0] & ENTRYLO_V);
        entrylo1 = mips3_paddr_to_tlbpfn(pfn1 << PAGE_SHIFT) |
-                  (0x3 << ENTRYLO_C_SHIFT) |
-                  (tlb->tlb_lo[1] & ENTRYLO_D) |
-                  (tlb->tlb_lo[1] & ENTRYLO_V);
+               ((_page_cachable_default >> _CACHE_SHIFT) << ENTRYLO_C_SHIFT) |
+               (tlb_lo[1] & ENTRYLO_D) |
+               (tlb_lo[1] & ENTRYLO_V);
 
        kvm_debug("@ %#lx tlb_lo0: 0x%08lx tlb_lo1: 0x%08lx\n", vcpu->arch.pc,
                  tlb->tlb_lo[0], tlb->tlb_lo[1]);
@@ -328,6 +338,7 @@ u32 kvm_get_inst(u32 *opc, struct kvm_vcpu *vcpu)
        struct mips_coproc *cop0 = vcpu->arch.cop0;
        unsigned long paddr, flags, vpn2, asid;
        unsigned long va = (unsigned long)opc;
+       void *vaddr;
        u32 inst;
        int index;
 
@@ -358,7 +369,10 @@ u32 kvm_get_inst(u32 *opc, struct kvm_vcpu *vcpu)
                local_irq_restore(flags);
        } else if (KVM_GUEST_KSEGX(va) == KVM_GUEST_KSEG0) {
                paddr = kvm_mips_translate_guest_kseg0_to_hpa(vcpu, va);
-               inst = *(u32 *) CKSEG0ADDR(paddr);
+               vaddr = kmap_atomic(pfn_to_page(PHYS_PFN(paddr)));
+               vaddr += paddr & ~PAGE_MASK;
+               inst = *(u32 *)vaddr;
+               kunmap_atomic(vaddr);
        } else {
                kvm_err("%s: illegal address: %p\n", __func__, opc);
                return KVM_INVALID_INST;