KVM: PPC: Book3S HV: Make HPT reading code notice R/C bit changes
authorPaul Mackerras <paulus@samba.org>
Thu, 18 Apr 2013 19:50:24 +0000 (19:50 +0000)
committerAlexander Graf <agraf@suse.de>
Fri, 26 Apr 2013 18:27:12 +0000 (20:27 +0200)
At present, the code that determines whether a HPT entry has changed,
and thus needs to be sent to userspace when it is copying the HPT,
doesn't consider a hardware update to the reference and change bits
(R and C) in the HPT entries to constitute a change that needs to
be sent to userspace.  This adds code to check for changes in R and C
when we are scanning the HPT to find changed entries, and adds code
to set the changed flag for the HPTE when we update the R and C bits
in the guest view of the HPTE.

Since we now need to set the HPTE changed flag in book3s_64_mmu_hv.c
as well as book3s_hv_rm_mmu.c, we move the note_hpte_modification()
function into kvm_book3s_64.h.

Current Linux guest kernels don't use the hardware updates of R and C
in the HPT, so this change won't affect them.  Linux (or other) kernels
might in future want to use the R and C bits and have them correctly
transferred across when a guest is migrated, so it is better to correct
this deficiency.

Signed-off-by: Paul Mackerras <paulus@samba.org>
Signed-off-by: Alexander Graf <agraf@suse.de>
arch/powerpc/include/asm/kvm_book3s_64.h
arch/powerpc/kvm/book3s_64_mmu_hv.c
arch/powerpc/kvm/book3s_hv_rm_mmu.c

index 38bec1dc99281f682ddf13209c296be0a8da056b..9c1ff330c8053563545b9a0e7cfc72e62798365e 100644 (file)
@@ -268,4 +268,17 @@ static inline int is_vrma_hpte(unsigned long hpte_v)
                (HPTE_V_1TB_SEG | (VRMA_VSID << (40 - 16)));
 }
 
+#ifdef CONFIG_KVM_BOOK3S_64_HV
+/*
+ * Note modification of an HPTE; set the HPTE modified bit
+ * if anyone is interested.
+ */
+static inline void note_hpte_modification(struct kvm *kvm,
+                                         struct revmap_entry *rev)
+{
+       if (atomic_read(&kvm->arch.hpte_mod_interest))
+               rev->guest_rpte |= HPTE_GR_MODIFIED;
+}
+#endif /* CONFIG_KVM_BOOK3S_64_HV */
+
 #endif /* __ASM_KVM_BOOK3S_64_H__ */
index 8cc18abd6dde6b486996bf9f4719fdeb12e0c65e..d641a6634b02cc8662a547b98cf8deed9f8ee93a 100644 (file)
@@ -893,7 +893,10 @@ static int kvm_unmap_rmapp(struct kvm *kvm, unsigned long *rmapp,
                        /* Harvest R and C */
                        rcbits = hptep[1] & (HPTE_R_R | HPTE_R_C);
                        *rmapp |= rcbits << KVMPPC_RMAP_RC_SHIFT;
-                       rev[i].guest_rpte = ptel | rcbits;
+                       if (rcbits & ~rev[i].guest_rpte) {
+                               rev[i].guest_rpte = ptel | rcbits;
+                               note_hpte_modification(kvm, &rev[i]);
+                       }
                }
                unlock_rmap(rmapp);
                hptep[0] &= ~HPTE_V_HVLOCK;
@@ -976,7 +979,10 @@ static int kvm_age_rmapp(struct kvm *kvm, unsigned long *rmapp,
                /* Now check and modify the HPTE */
                if ((hptep[0] & HPTE_V_VALID) && (hptep[1] & HPTE_R_R)) {
                        kvmppc_clear_ref_hpte(kvm, hptep, i);
-                       rev[i].guest_rpte |= HPTE_R_R;
+                       if (!(rev[i].guest_rpte & HPTE_R_R)) {
+                               rev[i].guest_rpte |= HPTE_R_R;
+                               note_hpte_modification(kvm, &rev[i]);
+                       }
                        ret = 1;
                }
                hptep[0] &= ~HPTE_V_HVLOCK;
@@ -1080,7 +1086,10 @@ static int kvm_test_clear_dirty(struct kvm *kvm, unsigned long *rmapp)
                        hptep[1] &= ~HPTE_R_C;
                        eieio();
                        hptep[0] = (hptep[0] & ~HPTE_V_ABSENT) | HPTE_V_VALID;
-                       rev[i].guest_rpte |= HPTE_R_C;
+                       if (!(rev[i].guest_rpte & HPTE_R_C)) {
+                               rev[i].guest_rpte |= HPTE_R_C;
+                               note_hpte_modification(kvm, &rev[i]);
+                       }
                        ret = 1;
                }
                hptep[0] &= ~HPTE_V_HVLOCK;
@@ -1193,16 +1202,36 @@ struct kvm_htab_ctx {
 
 #define HPTE_SIZE      (2 * sizeof(unsigned long))
 
+/*
+ * Returns 1 if this HPT entry has been modified or has pending
+ * R/C bit changes.
+ */
+static int hpte_dirty(struct revmap_entry *revp, unsigned long *hptp)
+{
+       unsigned long rcbits_unset;
+
+       if (revp->guest_rpte & HPTE_GR_MODIFIED)
+               return 1;
+
+       /* Also need to consider changes in reference and changed bits */
+       rcbits_unset = ~revp->guest_rpte & (HPTE_R_R | HPTE_R_C);
+       if ((hptp[0] & HPTE_V_VALID) && (hptp[1] & rcbits_unset))
+               return 1;
+
+       return 0;
+}
+
 static long record_hpte(unsigned long flags, unsigned long *hptp,
                        unsigned long *hpte, struct revmap_entry *revp,
                        int want_valid, int first_pass)
 {
        unsigned long v, r;
+       unsigned long rcbits_unset;
        int ok = 1;
        int valid, dirty;
 
        /* Unmodified entries are uninteresting except on the first pass */
-       dirty = !!(revp->guest_rpte & HPTE_GR_MODIFIED);
+       dirty = hpte_dirty(revp, hptp);
        if (!first_pass && !dirty)
                return 0;
 
@@ -1223,16 +1252,28 @@ static long record_hpte(unsigned long flags, unsigned long *hptp,
                while (!try_lock_hpte(hptp, HPTE_V_HVLOCK))
                        cpu_relax();
                v = hptp[0];
+
+               /* re-evaluate valid and dirty from synchronized HPTE value */
+               valid = !!(v & HPTE_V_VALID);
+               dirty = !!(revp->guest_rpte & HPTE_GR_MODIFIED);
+
+               /* Harvest R and C into guest view if necessary */
+               rcbits_unset = ~revp->guest_rpte & (HPTE_R_R | HPTE_R_C);
+               if (valid && (rcbits_unset & hptp[1])) {
+                       revp->guest_rpte |= (hptp[1] & (HPTE_R_R | HPTE_R_C)) |
+                               HPTE_GR_MODIFIED;
+                       dirty = 1;
+               }
+
                if (v & HPTE_V_ABSENT) {
                        v &= ~HPTE_V_ABSENT;
                        v |= HPTE_V_VALID;
+                       valid = 1;
                }
-               /* re-evaluate valid and dirty from synchronized HPTE value */
-               valid = !!(v & HPTE_V_VALID);
                if ((flags & KVM_GET_HTAB_BOLTED_ONLY) && !(v & HPTE_V_BOLTED))
                        valid = 0;
-               r = revp->guest_rpte | (hptp[1] & (HPTE_R_R | HPTE_R_C));
-               dirty = !!(revp->guest_rpte & HPTE_GR_MODIFIED);
+
+               r = revp->guest_rpte;
                /* only clear modified if this is the right sort of entry */
                if (valid == want_valid && dirty) {
                        r &= ~HPTE_GR_MODIFIED;
@@ -1288,7 +1329,7 @@ static ssize_t kvm_htab_read(struct file *file, char __user *buf,
                /* Skip uninteresting entries, i.e. clean on not-first pass */
                if (!first_pass) {
                        while (i < kvm->arch.hpt_npte &&
-                              !(revp->guest_rpte & HPTE_GR_MODIFIED)) {
+                              !hpte_dirty(revp, hptp)) {
                                ++i;
                                hptp += 2;
                                ++revp;
index 19c93bae1aea5781a421957c3ff941d39427f7f5..6dcbb49105a4667353ee745d4a3ea6c23db72082 100644 (file)
@@ -97,17 +97,6 @@ void kvmppc_add_revmap_chain(struct kvm *kvm, struct revmap_entry *rev,
 }
 EXPORT_SYMBOL_GPL(kvmppc_add_revmap_chain);
 
-/*
- * Note modification of an HPTE; set the HPTE modified bit
- * if anyone is interested.
- */
-static inline void note_hpte_modification(struct kvm *kvm,
-                                         struct revmap_entry *rev)
-{
-       if (atomic_read(&kvm->arch.hpte_mod_interest))
-               rev->guest_rpte |= HPTE_GR_MODIFIED;
-}
-
 /* Remove this HPTE from the chain for a real page */
 static void remove_revmap_chain(struct kvm *kvm, long pte_index,
                                struct revmap_entry *rev,