KVM: PPC: Allow use of small pages to back Book3S HV guests
[pandora-kernel.git] / arch / powerpc / kvm / book3s_hv_rm_mmu.c
index bacb0cf..c086eb0 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/kvm.h>
 #include <linux/kvm_host.h>
 #include <linux/hugetlb.h>
+#include <linux/module.h>
 
 #include <asm/tlbflush.h>
 #include <asm/kvm_ppc.h>
 #include <asm/synch.h>
 #include <asm/ppc-opcode.h>
 
-/* For now use fixed-size 16MB page table */
-#define HPT_ORDER      24
-#define HPT_NPTEG      (1ul << (HPT_ORDER - 7))        /* 128B per pteg */
-#define HPT_HASH_MASK  (HPT_NPTEG - 1)
+/*
+ * Since this file is built in even if KVM is a module, we need
+ * a local copy of this function for the case where kvm_main.c is
+ * modular.
+ */
+static struct kvm_memory_slot *builtin_gfn_to_memslot(struct kvm *kvm,
+                                               gfn_t gfn)
+{
+       struct kvm_memslots *slots;
+       struct kvm_memory_slot *memslot;
 
-#define HPTE_V_HVLOCK  0x40UL
+       slots = kvm_memslots(kvm);
+       kvm_for_each_memslot(memslot, slots)
+               if (gfn >= memslot->base_gfn &&
+                     gfn < memslot->base_gfn + memslot->npages)
+                       return memslot;
+       return NULL;
+}
 
-static inline long lock_hpte(unsigned long *hpte, unsigned long bits)
+/* Translate address of a vmalloc'd thing to a linear map address */
+static void *real_vmalloc_addr(void *x)
 {
-       unsigned long tmp, old;
+       unsigned long addr = (unsigned long) x;
+       pte_t *p;
 
-       asm volatile("  ldarx   %0,0,%2\n"
-                    "  and.    %1,%0,%3\n"
-                    "  bne     2f\n"
-                    "  ori     %0,%0,%4\n"
-                    "  stdcx.  %0,0,%2\n"
-                    "  beq+    2f\n"
-                    "  li      %1,%3\n"
-                    "2:        isync"
-                    : "=&r" (tmp), "=&r" (old)
-                    : "r" (hpte), "r" (bits), "i" (HPTE_V_HVLOCK)
-                    : "cc", "memory");
-       return old == 0;
+       p = find_linux_pte(swapper_pg_dir, addr);
+       if (!p || !pte_present(*p))
+               return NULL;
+       /* assume we don't have huge pages in vmalloc space... */
+       addr = (pte_pfn(*p) << PAGE_SHIFT) | (addr & ~PAGE_MASK);
+       return __va(addr);
 }
 
 long kvmppc_h_enter(struct kvm_vcpu *vcpu, unsigned long flags,
                    long pte_index, unsigned long pteh, unsigned long ptel)
 {
-       unsigned long porder;
        struct kvm *kvm = vcpu->kvm;
-       unsigned long i, lpn, pa;
+       unsigned long i, pa, gpa, gfn, psize;
+       unsigned long slot_fn;
        unsigned long *hpte;
+       struct revmap_entry *rev;
+       unsigned long g_ptel = ptel;
+       struct kvm_memory_slot *memslot;
+       unsigned long *physp, pte_size;
+       bool realmode = vcpu->arch.vcore->vcore_state == VCORE_RUNNING;
 
-       /* only handle 4k, 64k and 16M pages for now */
-       porder = 12;
-       if (pteh & HPTE_V_LARGE) {
-               if (cpu_has_feature(CPU_FTR_ARCH_206) &&
-                   (ptel & 0xf000) == 0x1000) {
-                       /* 64k page */
-                       porder = 16;
-               } else if ((ptel & 0xff000) == 0) {
-                       /* 16M page */
-                       porder = 24;
-                       /* lowest AVA bit must be 0 for 16M pages */
-                       if (pteh & 0x80)
-                               return H_PARAMETER;
-               } else
-                       return H_PARAMETER;
-       }
-       lpn = (ptel & HPTE_R_RPN) >> kvm->arch.ram_porder;
-       if (lpn >= kvm->arch.ram_npages || porder > kvm->arch.ram_porder)
+       psize = hpte_page_size(pteh, ptel);
+       if (!psize)
+               return H_PARAMETER;
+
+       /* Find the memslot (if any) for this address */
+       gpa = (ptel & HPTE_R_RPN) & ~(psize - 1);
+       gfn = gpa >> PAGE_SHIFT;
+       memslot = builtin_gfn_to_memslot(kvm, gfn);
+       if (!(memslot && !(memslot->flags & KVM_MEMSLOT_INVALID)))
+               return H_PARAMETER;
+
+       /* Check if the requested page fits entirely in the memslot. */
+       if (!slot_is_aligned(memslot, psize))
+               return H_PARAMETER;
+       slot_fn = gfn - memslot->base_gfn;
+
+       physp = kvm->arch.slot_phys[memslot->id];
+       if (!physp)
                return H_PARAMETER;
-       pa = kvm->arch.ram_pginfo[lpn].pfn << PAGE_SHIFT;
+       physp += slot_fn;
+       if (realmode)
+               physp = real_vmalloc_addr(physp);
+       pa = *physp;
        if (!pa)
+               return H_TOO_HARD;
+       pte_size = PAGE_SIZE << (pa & KVMPPC_PAGE_ORDER_MASK);
+       pa &= PAGE_MASK;
+
+       if (pte_size < psize)
                return H_PARAMETER;
+       if (pa && pte_size > psize)
+               pa |= gpa & (pte_size - 1);
+
+       ptel &= ~(HPTE_R_PP0 - psize);
+       ptel |= pa;
+
        /* Check WIMG */
        if ((ptel & HPTE_R_WIMG) != HPTE_R_M &&
            (ptel & HPTE_R_WIMG) != (HPTE_R_W | HPTE_R_I | HPTE_R_M))
                return H_PARAMETER;
        pteh &= ~0x60UL;
-       ptel &= ~(HPTE_R_PP0 - kvm->arch.ram_psize);
-       ptel |= pa;
-       if (pte_index >= (HPT_NPTEG << 3))
+       pteh |= HPTE_V_VALID;
+
+       if (pte_index >= HPT_NPTE)
                return H_PARAMETER;
        if (likely((flags & H_EXACT) == 0)) {
                pte_index &= ~7UL;
                hpte = (unsigned long *)(kvm->arch.hpt_virt + (pte_index << 4));
-               for (i = 0; ; ++i) {
-                       if (i == 8)
-                               return H_PTEG_FULL;
+               for (i = 0; i < 8; ++i) {
                        if ((*hpte & HPTE_V_VALID) == 0 &&
-                           lock_hpte(hpte, HPTE_V_HVLOCK | HPTE_V_VALID))
+                           try_lock_hpte(hpte, HPTE_V_HVLOCK | HPTE_V_VALID))
                                break;
                        hpte += 2;
                }
+               if (i == 8) {
+                       /*
+                        * Since try_lock_hpte doesn't retry (not even stdcx.
+                        * failures), it could be that there is a free slot
+                        * but we transiently failed to lock it.  Try again,
+                        * actually locking each slot and checking it.
+                        */
+                       hpte -= 16;
+                       for (i = 0; i < 8; ++i) {
+                               while (!try_lock_hpte(hpte, HPTE_V_HVLOCK))
+                                       cpu_relax();
+                               if ((*hpte & HPTE_V_VALID) == 0)
+                                       break;
+                               *hpte &= ~HPTE_V_HVLOCK;
+                               hpte += 2;
+                       }
+                       if (i == 8)
+                               return H_PTEG_FULL;
+               }
+               pte_index += i;
        } else {
-               i = 0;
                hpte = (unsigned long *)(kvm->arch.hpt_virt + (pte_index << 4));
-               if (!lock_hpte(hpte, HPTE_V_HVLOCK | HPTE_V_VALID))
-                       return H_PTEG_FULL;
+               if (!try_lock_hpte(hpte, HPTE_V_HVLOCK | HPTE_V_VALID)) {
+                       /* Lock the slot and check again */
+                       while (!try_lock_hpte(hpte, HPTE_V_HVLOCK))
+                               cpu_relax();
+                       if (*hpte & HPTE_V_VALID) {
+                               *hpte &= ~HPTE_V_HVLOCK;
+                               return H_PTEG_FULL;
+                       }
+               }
        }
+
+       /* Save away the guest's idea of the second HPTE dword */
+       rev = real_vmalloc_addr(&kvm->arch.revmap[pte_index]);
+       if (rev)
+               rev->guest_rpte = g_ptel;
        hpte[1] = ptel;
        eieio();
        hpte[0] = pteh;
        asm volatile("ptesync" : : : "memory");
-       atomic_inc(&kvm->arch.ram_pginfo[lpn].refcnt);
-       vcpu->arch.gpr[4] = pte_index + i;
+       vcpu->arch.gpr[4] = pte_index;
        return H_SUCCESS;
 }
+EXPORT_SYMBOL_GPL(kvmppc_h_enter);
 
 #define LOCK_TOKEN     (*(u32 *)(&get_paca()->lock_token))
 
@@ -138,10 +195,10 @@ long kvmppc_h_remove(struct kvm_vcpu *vcpu, unsigned long flags,
        unsigned long *hpte;
        unsigned long v, r, rb;
 
-       if (pte_index >= (HPT_NPTEG << 3))
+       if (pte_index >= HPT_NPTE)
                return H_PARAMETER;
        hpte = (unsigned long *)(kvm->arch.hpt_virt + (pte_index << 4));
-       while (!lock_hpte(hpte, HPTE_V_HVLOCK))
+       while (!try_lock_hpte(hpte, HPTE_V_HVLOCK))
                cpu_relax();
        if ((hpte[0] & HPTE_V_VALID) == 0 ||
            ((flags & H_AVPN) && (hpte[0] & ~0x7fUL) != avpn) ||
@@ -193,14 +250,14 @@ long kvmppc_h_bulk_remove(struct kvm_vcpu *vcpu)
                if (req == 3)
                        break;
                if (req != 1 || flags == 3 ||
-                   pte_index >= (HPT_NPTEG << 3)) {
+                   pte_index >= HPT_NPTE) {
                        /* parameter error */
                        args[i * 2] = ((0xa0 | flags) << 56) + pte_index;
                        ret = H_PARAMETER;
                        break;
                }
                hp = (unsigned long *)(kvm->arch.hpt_virt + (pte_index << 4));
-               while (!lock_hpte(hp, HPTE_V_HVLOCK))
+               while (!try_lock_hpte(hp, HPTE_V_HVLOCK))
                        cpu_relax();
                found = 0;
                if (hp[0] & HPTE_V_VALID) {
@@ -256,12 +313,13 @@ long kvmppc_h_protect(struct kvm_vcpu *vcpu, unsigned long flags,
 {
        struct kvm *kvm = vcpu->kvm;
        unsigned long *hpte;
-       unsigned long v, r, rb;
+       struct revmap_entry *rev;
+       unsigned long v, r, rb, mask, bits;
 
-       if (pte_index >= (HPT_NPTEG << 3))
+       if (pte_index >= HPT_NPTE)
                return H_PARAMETER;
        hpte = (unsigned long *)(kvm->arch.hpt_virt + (pte_index << 4));
-       while (!lock_hpte(hpte, HPTE_V_HVLOCK))
+       while (!try_lock_hpte(hpte, HPTE_V_HVLOCK))
                cpu_relax();
        if ((hpte[0] & HPTE_V_VALID) == 0 ||
            ((flags & H_AVPN) && (hpte[0] & ~0x7fUL) != avpn)) {
@@ -271,11 +329,21 @@ long kvmppc_h_protect(struct kvm_vcpu *vcpu, unsigned long flags,
        if (atomic_read(&kvm->online_vcpus) == 1)
                flags |= H_LOCAL;
        v = hpte[0];
-       r = hpte[1] & ~(HPTE_R_PP0 | HPTE_R_PP | HPTE_R_N |
-                       HPTE_R_KEY_HI | HPTE_R_KEY_LO);
-       r |= (flags << 55) & HPTE_R_PP0;
-       r |= (flags << 48) & HPTE_R_KEY_HI;
-       r |= flags & (HPTE_R_PP | HPTE_R_N | HPTE_R_KEY_LO);
+       bits = (flags << 55) & HPTE_R_PP0;
+       bits |= (flags << 48) & HPTE_R_KEY_HI;
+       bits |= flags & (HPTE_R_PP | HPTE_R_N | HPTE_R_KEY_LO);
+
+       /* Update guest view of 2nd HPTE dword */
+       mask = HPTE_R_PP0 | HPTE_R_PP | HPTE_R_N |
+               HPTE_R_KEY_HI | HPTE_R_KEY_LO;
+       rev = real_vmalloc_addr(&kvm->arch.revmap[pte_index]);
+       if (rev) {
+               r = (rev->guest_rpte & ~mask) | bits;
+               rev->guest_rpte = r;
+       }
+       r = (hpte[1] & ~mask) | bits;
+
+       /* Update HPTE */
        rb = compute_tlbie_rb(v, r, pte_index);
        hpte[0] = v & ~HPTE_V_VALID;
        if (!(flags & H_LOCAL)) {
@@ -298,38 +366,31 @@ long kvmppc_h_protect(struct kvm_vcpu *vcpu, unsigned long flags,
        return H_SUCCESS;
 }
 
-static unsigned long reverse_xlate(struct kvm *kvm, unsigned long realaddr)
-{
-       long int i;
-       unsigned long offset, rpn;
-
-       offset = realaddr & (kvm->arch.ram_psize - 1);
-       rpn = (realaddr - offset) >> PAGE_SHIFT;
-       for (i = 0; i < kvm->arch.ram_npages; ++i)
-               if (rpn == kvm->arch.ram_pginfo[i].pfn)
-                       return (i << PAGE_SHIFT) + offset;
-       return HPTE_R_RPN;      /* all 1s in the RPN field */
-}
-
 long kvmppc_h_read(struct kvm_vcpu *vcpu, unsigned long flags,
                   unsigned long pte_index)
 {
        struct kvm *kvm = vcpu->kvm;
        unsigned long *hpte, r;
        int i, n = 1;
+       struct revmap_entry *rev = NULL;
 
-       if (pte_index >= (HPT_NPTEG << 3))
+       if (pte_index >= HPT_NPTE)
                return H_PARAMETER;
        if (flags & H_READ_4) {
                pte_index &= ~3;
                n = 4;
        }
+       if (flags & H_R_XLATE)
+               rev = real_vmalloc_addr(&kvm->arch.revmap[pte_index]);
        for (i = 0; i < n; ++i, ++pte_index) {
                hpte = (unsigned long *)(kvm->arch.hpt_virt + (pte_index << 4));
                r = hpte[1];
-               if ((flags & H_R_XLATE) && (hpte[0] & HPTE_V_VALID))
-                       r = reverse_xlate(kvm, r & HPTE_R_RPN) |
-                               (r & ~HPTE_R_RPN);
+               if (hpte[0] & HPTE_V_VALID) {
+                       if (rev)
+                               r = rev[i].guest_rpte;
+                       else
+                               r = hpte[1] | HPTE_R_RPN;
+               }
                vcpu->arch.gpr[4 + i * 2] = hpte[0];
                vcpu->arch.gpr[5 + i * 2] = r;
        }