KVM: MMU: Fix false flooding when a pte points to page table
authorAvi Kivity <avi@qumranet.com>
Thu, 15 May 2008 10:51:35 +0000 (13:51 +0300)
committerAvi Kivity <avi@qumranet.com>
Sun, 20 Jul 2008 09:40:50 +0000 (12:40 +0300)
The KVM MMU tries to detect when a speculative pte update is not actually
used by demand fault, by checking the accessed bit of the shadow pte.  If
the shadow pte has not been accessed, we deem that page table flooded and
remove the shadow page table, allowing further pte updates to proceed
without emulation.

However, if the pte itself points at a page table and only used for write
operations, the accessed bit will never be set since all access will happen
through the emulator.

This is exactly what happens with kscand on old (2.4.x) HIGHMEM kernels.
The kernel points a kmap_atomic() pte at a page table, and then
proceeds with read-modify-write operations to look at the dirty and accessed
bits.  We get a false flood trigger on the kmap ptes, which results in the
mmu spending all its time setting up and tearing down shadows.

Fix by setting the shadow accessed bit on emulated accesses.

Signed-off-by: Avi Kivity <avi@qumranet.com>
arch/x86/kvm/mmu.c
arch/x86/kvm/mmu.h
include/asm-x86/kvm_host.h

index 8e449dbcc596053830bfe59dbbaf3317052cb772..53f1ed852ca2e0478975dc27b03aac98d83f42b9 100644 (file)
@@ -1122,8 +1122,10 @@ static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *shadow_pte,
                else
                        kvm_release_pfn_clean(pfn);
        }
-       if (!ptwrite || !*ptwrite)
+       if (speculative) {
                vcpu->arch.last_pte_updated = shadow_pte;
+               vcpu->arch.last_pte_gfn = gfn;
+       }
 }
 
 static void nonpaging_new_cr3(struct kvm_vcpu *vcpu)
@@ -1671,6 +1673,18 @@ static void mmu_guess_page_from_pte_write(struct kvm_vcpu *vcpu, gpa_t gpa,
        vcpu->arch.update_pte.pfn = pfn;
 }
 
+static void kvm_mmu_access_page(struct kvm_vcpu *vcpu, gfn_t gfn)
+{
+       u64 *spte = vcpu->arch.last_pte_updated;
+
+       if (spte
+           && vcpu->arch.last_pte_gfn == gfn
+           && shadow_accessed_mask
+           && !(*spte & shadow_accessed_mask)
+           && is_shadow_present_pte(*spte))
+               set_bit(PT_ACCESSED_SHIFT, (unsigned long *)spte);
+}
+
 void kvm_mmu_pte_write(struct kvm_vcpu *vcpu, gpa_t gpa,
                       const u8 *new, int bytes)
 {
@@ -1694,6 +1708,7 @@ void kvm_mmu_pte_write(struct kvm_vcpu *vcpu, gpa_t gpa,
        pgprintk("%s: gpa %llx bytes %d\n", __func__, gpa, bytes);
        mmu_guess_page_from_pte_write(vcpu, gpa, new, bytes);
        spin_lock(&vcpu->kvm->mmu_lock);
+       kvm_mmu_access_page(vcpu, gfn);
        kvm_mmu_free_some_pages(vcpu);
        ++vcpu->kvm->stat.mmu_pte_write;
        kvm_mmu_audit(vcpu, "pre pte write");
index 1730757bbc7a88c9c26707ce12ab40c5c1a81194..258e5d56298ee953b645bef4f36f1a11594771e1 100644 (file)
@@ -15,7 +15,8 @@
 #define PT_USER_MASK (1ULL << 2)
 #define PT_PWT_MASK (1ULL << 3)
 #define PT_PCD_MASK (1ULL << 4)
-#define PT_ACCESSED_MASK (1ULL << 5)
+#define PT_ACCESSED_SHIFT 5
+#define PT_ACCESSED_MASK (1ULL << PT_ACCESSED_SHIFT)
 #define PT_DIRTY_MASK (1ULL << 6)
 #define PT_PAGE_SIZE_MASK (1ULL << 7)
 #define PT_PAT_MASK (1ULL << 7)
index 844f2a89afbcdb28a8a6272fd9826b8a24777351..c2d066e185f45131b66f07e767c13811a90161d7 100644 (file)
@@ -243,6 +243,7 @@ struct kvm_vcpu_arch {
        gfn_t last_pt_write_gfn;
        int   last_pt_write_count;
        u64  *last_pte_updated;
+       gfn_t last_pte_gfn;
 
        struct {
                gfn_t gfn;      /* presumed gfn during guest pte update */