KVM: PPC: Book3S HV: Implement real mode H_PAGE_INIT handler
authorSuraj Jitindar Singh <sjitindarsingh@gmail.com>
Fri, 22 Mar 2019 06:05:45 +0000 (17:05 +1100)
committerPaul Mackerras <paulus@ozlabs.org>
Tue, 30 Apr 2019 04:43:12 +0000 (14:43 +1000)
Implement a real mode handler for the H_CALL H_PAGE_INIT which can be
used to zero or copy a guest page. The page is defined to be 4k and must
be 4k aligned.

The in-kernel real mode handler halves the time to handle this H_CALL
compared to handling it in userspace for a hash guest.

Signed-off-by: Suraj Jitindar Singh <sjitindarsingh@gmail.com>
Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
arch/powerpc/include/asm/kvm_ppc.h
arch/powerpc/kvm/book3s_hv_rm_mmu.c
arch/powerpc/kvm/book3s_hv_rmhandlers.S

index ac22b28ae78d4bc52223c94b478b83fc1c5ce48e..df0b173da3e0d8c064a246fd014bb345a088d457 100644 (file)
@@ -665,6 +665,8 @@ long kvmppc_h_clear_ref(struct kvm_vcpu *vcpu, unsigned long flags,
                         unsigned long pte_index);
 long kvmppc_h_clear_mod(struct kvm_vcpu *vcpu, unsigned long flags,
                         unsigned long pte_index);
+long kvmppc_rm_h_page_init(struct kvm_vcpu *vcpu, unsigned long flags,
+                          unsigned long dest, unsigned long src);
 long kvmppc_hpte_hv_fault(struct kvm_vcpu *vcpu, unsigned long addr,
                           unsigned long slb_v, unsigned int status, bool data);
 unsigned long kvmppc_rm_h_xirr(struct kvm_vcpu *vcpu);
index 3b3791ed74a6756315080be96f4085f1a9c7cd9b..8431ad1e83919051b315be36834201cbb5c01e96 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/hugetlb.h>
 #include <linux/module.h>
 #include <linux/log2.h>
+#include <linux/sizes.h>
 
 #include <asm/trace.h>
 #include <asm/kvm_ppc.h>
@@ -867,6 +868,149 @@ long kvmppc_h_clear_mod(struct kvm_vcpu *vcpu, unsigned long flags,
        return ret;
 }
 
+static int kvmppc_get_hpa(struct kvm_vcpu *vcpu, unsigned long gpa,
+                         int writing, unsigned long *hpa,
+                         struct kvm_memory_slot **memslot_p)
+{
+       struct kvm *kvm = vcpu->kvm;
+       struct kvm_memory_slot *memslot;
+       unsigned long gfn, hva, pa, psize = PAGE_SHIFT;
+       unsigned int shift;
+       pte_t *ptep, pte;
+
+       /* Find the memslot for this address */
+       gfn = gpa >> PAGE_SHIFT;
+       memslot = __gfn_to_memslot(kvm_memslots_raw(kvm), gfn);
+       if (!memslot || (memslot->flags & KVM_MEMSLOT_INVALID))
+               return H_PARAMETER;
+
+       /* Translate to host virtual address */
+       hva = __gfn_to_hva_memslot(memslot, gfn);
+
+       /* Try to find the host pte for that virtual address */
+       ptep = __find_linux_pte(vcpu->arch.pgdir, hva, NULL, &shift);
+       if (!ptep)
+               return H_TOO_HARD;
+       pte = kvmppc_read_update_linux_pte(ptep, writing);
+       if (!pte_present(pte))
+               return H_TOO_HARD;
+
+       /* Convert to a physical address */
+       if (shift)
+               psize = 1UL << shift;
+       pa = pte_pfn(pte) << PAGE_SHIFT;
+       pa |= hva & (psize - 1);
+       pa |= gpa & ~PAGE_MASK;
+
+       if (hpa)
+               *hpa = pa;
+       if (memslot_p)
+               *memslot_p = memslot;
+
+       return H_SUCCESS;
+}
+
+static long kvmppc_do_h_page_init_zero(struct kvm_vcpu *vcpu,
+                                      unsigned long dest)
+{
+       struct kvm_memory_slot *memslot;
+       struct kvm *kvm = vcpu->kvm;
+       unsigned long pa, mmu_seq;
+       long ret = H_SUCCESS;
+       int i;
+
+       /* Used later to detect if we might have been invalidated */
+       mmu_seq = kvm->mmu_notifier_seq;
+       smp_rmb();
+
+       ret = kvmppc_get_hpa(vcpu, dest, 1, &pa, &memslot);
+       if (ret != H_SUCCESS)
+               return ret;
+
+       /* Check if we've been invalidated */
+       raw_spin_lock(&kvm->mmu_lock.rlock);
+       if (mmu_notifier_retry(kvm, mmu_seq)) {
+               ret = H_TOO_HARD;
+               goto out_unlock;
+       }
+
+       /* Zero the page */
+       for (i = 0; i < SZ_4K; i += L1_CACHE_BYTES, pa += L1_CACHE_BYTES)
+               dcbz((void *)pa);
+       kvmppc_update_dirty_map(memslot, dest >> PAGE_SHIFT, PAGE_SIZE);
+
+out_unlock:
+       raw_spin_unlock(&kvm->mmu_lock.rlock);
+       return ret;
+}
+
+static long kvmppc_do_h_page_init_copy(struct kvm_vcpu *vcpu,
+                                      unsigned long dest, unsigned long src)
+{
+       unsigned long dest_pa, src_pa, mmu_seq;
+       struct kvm_memory_slot *dest_memslot;
+       struct kvm *kvm = vcpu->kvm;
+       long ret = H_SUCCESS;
+
+       /* Used later to detect if we might have been invalidated */
+       mmu_seq = kvm->mmu_notifier_seq;
+       smp_rmb();
+
+       ret = kvmppc_get_hpa(vcpu, dest, 1, &dest_pa, &dest_memslot);
+       if (ret != H_SUCCESS)
+               return ret;
+       ret = kvmppc_get_hpa(vcpu, src, 0, &src_pa, NULL);
+       if (ret != H_SUCCESS)
+               return ret;
+
+       /* Check if we've been invalidated */
+       raw_spin_lock(&kvm->mmu_lock.rlock);
+       if (mmu_notifier_retry(kvm, mmu_seq)) {
+               ret = H_TOO_HARD;
+               goto out_unlock;
+       }
+
+       /* Copy the page */
+       memcpy((void *)dest_pa, (void *)src_pa, SZ_4K);
+
+       kvmppc_update_dirty_map(dest_memslot, dest >> PAGE_SHIFT, PAGE_SIZE);
+
+out_unlock:
+       raw_spin_unlock(&kvm->mmu_lock.rlock);
+       return ret;
+}
+
+long kvmppc_rm_h_page_init(struct kvm_vcpu *vcpu, unsigned long flags,
+                          unsigned long dest, unsigned long src)
+{
+       struct kvm *kvm = vcpu->kvm;
+       u64 pg_mask = SZ_4K - 1;        /* 4K page size */
+       long ret = H_SUCCESS;
+
+       /* Don't handle radix mode here, go up to the virtual mode handler */
+       if (kvm_is_radix(kvm))
+               return H_TOO_HARD;
+
+       /* Check for invalid flags (H_PAGE_SET_LOANED covers all CMO flags) */
+       if (flags & ~(H_ICACHE_INVALIDATE | H_ICACHE_SYNCHRONIZE |
+                     H_ZERO_PAGE | H_COPY_PAGE | H_PAGE_SET_LOANED))
+               return H_PARAMETER;
+
+       /* dest (and src if copy_page flag set) must be page aligned */
+       if ((dest & pg_mask) || ((flags & H_COPY_PAGE) && (src & pg_mask)))
+               return H_PARAMETER;
+
+       /* zero and/or copy the page as determined by the flags */
+       if (flags & H_COPY_PAGE)
+               ret = kvmppc_do_h_page_init_copy(vcpu, dest, src);
+       else if (flags & H_ZERO_PAGE)
+               ret = kvmppc_do_h_page_init_zero(vcpu, dest);
+
+       /* We can ignore the other flags */
+
+       return ret;
+}
+
 void kvmppc_invalidate_hpte(struct kvm *kvm, __be64 *hptep,
                        unsigned long pte_index)
 {
index 3a5e719ef032bcdc7097f840b1932c47408bafc3..4e8be4d9e11436d33fb05b300ab792735c36a2b4 100644 (file)
@@ -2281,7 +2281,7 @@ hcall_real_table:
 #endif
        .long   0               /* 0x24 - H_SET_SPRG0 */
        .long   DOTSYM(kvmppc_h_set_dabr) - hcall_real_table
-       .long   0               /* 0x2c */
+       .long   DOTSYM(kvmppc_rm_h_page_init) - hcall_real_table
        .long   0               /* 0x30 */
        .long   0               /* 0x34 */
        .long   0               /* 0x38 */