Skip to contentSkip to conversation

KVM: MMU: don't drop spte if overwrite it from W to RO

PATCH In review Xiao Guangrong 3 participants 15 messages Nov 12, 2010 – Nov 17, 2010

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/4 KVM: MMU: don't drop spte if overwrite it from W to RO

We just need flush tlb if overwrite a writable spte with a read-only one

---
 arch/x86/kvm/mmu.c |   19 +++++++++----------
 1 files changed, 9 insertions(+), 10 deletions(-)
arch/x86/kvm/mmu.c +9 -10
index 4b6d54c..1a93ab4 100644
@@ -2044,6 +2044,15 @@ static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 	if (pte_access & ACC_WRITE_MASK)
 		mark_page_dirty(vcpu->kvm, gfn);
 
+	/*
+	 * If we overwrite a writable spte with a read-only one,
+	 * flush remote TLBs. Otherwise rmap_write_protect will
+	 * find a read-only spte, even though the writable spte
+	 * might be cached on a CPU's TLB.
+	 */
+	else if (is_writable_pte(*sptep))
+		ret = 1;
+
 set_pte:
 	update_spte(sptep, spte);
 done:
@@ -2084,16 +2093,6 @@ static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 				 spte_to_pfn(*sptep), pfn);
 			drop_spte(vcpu->kvm, sptep, shadow_trap_nonpresent_pte);
 			kvm_flush_remote_tlbs(vcpu->kvm);
-		/*
-		 * If we overwrite a writable spte with a read-only one,
-		 * drop it and flush remote TLBs. Otherwise rmap_write_protect
-		 * will find a read-only spte, even though the writable spte
-		 * might be cached on a CPU's TLB.
-		 */
-		} else if (is_writable_pte(*sptep) &&
-			  (!(pte_access & ACC_WRITE_MASK) || !dirty)) {
-			drop_spte(vcpu->kvm, sptep, shadow_trap_nonpresent_pte);
-			kvm_flush_remote_tlbs(vcpu->kvm);
 		} else
 			was_rmapped = 1;
 	}
Signature · 1 line
1.7.0.4
#1 lore raw

PATCH 2/4 KVM: MMU: rename 'reset_host_protection' to 'host_writeable'

From: Lai Jiangshan <[email protected]>

Rename it to fix the sense better

---
 arch/x86/kvm/mmu.c         |    8 ++++----
 arch/x86/kvm/paging_tmpl.h |   10 +++++-----
 2 files changed, 9 insertions(+), 9 deletions(-)
arch/x86/kvm/mmu.c +4 -4
index 1a93ab4..94d157f 100644
@@ -1973,7 +1973,7 @@ static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 		    unsigned pte_access, int user_fault,
 		    int write_fault, int dirty, int level,
 		    gfn_t gfn, pfn_t pfn, bool speculative,
-		    bool can_unsync, bool reset_host_protection)
+		    bool can_unsync, bool host_writeable)
 {
 	u64 spte;
 	int ret = 0;
@@ -2000,7 +2000,7 @@ static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 		spte |= kvm_x86_ops->get_mt_mask(vcpu, gfn,
 			kvm_is_mmio_pfn(pfn));
 
-	if (reset_host_protection)
+	if (host_writeable)
 		spte |= SPTE_HOST_WRITEABLE;
 
 	spte |= (u64)pfn << PAGE_SHIFT;
@@ -2064,7 +2064,7 @@ static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 			 int user_fault, int write_fault, int dirty,
 			 int *ptwrite, int level, gfn_t gfn,
 			 pfn_t pfn, bool speculative,
-			 bool reset_host_protection)
+			 bool host_writeable)
 {
 	int was_rmapped = 0;
 	int rmap_count;
@@ -2099,7 +2099,7 @@ static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 
 	if (set_spte(vcpu, sptep, pte_access, user_fault, write_fault,
 		      dirty, level, gfn, pfn, speculative, true,
-		      reset_host_protection)) {
+		      host_writeable)) {
 		if (write_fault)
 			*ptwrite = 1;
 		kvm_mmu_flush_tlb(vcpu);
arch/x86/kvm/paging_tmpl.h +5 -5
index ba00eef..291342d 100644
@@ -329,7 +329,7 @@ static void FNAME(update_pte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
 		return;
 	kvm_get_pfn(pfn);
 	/*
-	 * we call mmu_set_spte() with reset_host_protection = true beacuse that
+	 * we call mmu_set_spte() with host_writeable = true beacuse that
 	 * vcpu->arch.update_pte.pfn was fetched from get_user_pages(write = 1).
 	 */
 	mmu_set_spte(vcpu, spte, sp->role.access, pte_access, 0, 0,
@@ -742,7 +742,7 @@ static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
 			    bool clear_unsync)
 {
 	int i, offset, nr_present;
-	bool reset_host_protection;
+	bool host_writeable;
 	gpa_t first_pte_gpa;
 
 	offset = nr_present = 0;
@@ -788,14 +788,14 @@ static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
 		pte_access = sp->role.access & FNAME(gpte_access)(vcpu, gpte);
 		if (!(sp->spt[i] & SPTE_HOST_WRITEABLE)) {
 			pte_access &= ~ACC_WRITE_MASK;
-			reset_host_protection = 0;
+			host_writeable = 0;
 		} else {
-			reset_host_protection = 1;
+			host_writeable = 1;
 		}
 		set_spte(vcpu, &sp->spt[i], pte_access, 0, 0,
 			 is_dirty_gpte(gpte), PT_PAGE_TABLE_LEVEL, gfn,
 			 spte_to_pfn(sp->spt[i]), true, false,
-			 reset_host_protection);
+			 host_writeable);
 	}
 
 	return !nr_present;
Signature · 1 line
1.7.0.4
#2 lore raw

Re: [PATCH 2/4] KVM: MMU: rename 'reset_host_protection' to 'host_writeable'

Quoted 4 lines · Xiao Guangrong

From: Lai Jiangshan <[email protected]>

Rename it to fix the sense better

CCed to Lai Jiangshan

#3 lore raw

PATCH 3/4 KVM: MMU: notrap it if gpte's reserved is set

In reply to Xiao Guangrong

We can past the page fault to guest directly if gpte's reserved is set

---
 arch/x86/kvm/paging_tmpl.h |   10 ++++++----
 1 files changed, 6 insertions(+), 4 deletions(-)
arch/x86/kvm/paging_tmpl.h +6 -4
index 291342d..952357a 100644
@@ -760,6 +760,7 @@ static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
 		pt_element_t gpte;
 		gpa_t pte_gpa;
 		gfn_t gfn;
+		bool gpte_invalid;
 
 		if (!is_shadow_present_pte(sp->spt[i]))
 			continue;
@@ -771,12 +772,13 @@ static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
 			return -EINVAL;
 
 		gfn = gpte_to_gfn(gpte);
-		if (is_rsvd_bits_set(&vcpu->arch.mmu, gpte, PT_PAGE_TABLE_LEVEL)
-		      || gfn != sp->gfns[i] || !is_present_gpte(gpte)
-		      || !(gpte & PT_ACCESSED_MASK)) {
+		gpte_invalid = is_present_gpte(gpte) ||
+		   is_rsvd_bits_set(&vcpu->arch.mmu, gpte, PT_PAGE_TABLE_LEVEL);
+		if (gpte_invalid || gfn != sp->gfns[i] ||
+		      !(gpte & PT_ACCESSED_MASK)) {
 			u64 nonpresent;
 
-			if (is_present_gpte(gpte) || !clear_unsync)
+			if (gpte_invalid || !clear_unsync)
 				nonpresent = shadow_trap_nonpresent_pte;
 			else
 				nonpresent = shadow_notrap_nonpresent_pte;
Signature · 1 line
1.7.0.4
#4 lore raw

Re: [PATCH 3/4] KVM: MMU: notrap it if gpte's reserved is set

Quoted 3 lines · Xiao Guangrong

We can past the page fault to guest directly if gpte's reserved is set

How can that work? shadow_notrap_nonpresent_pte causes a fault with PFEC.P=PFEC.RSVD=0, while we need PFEC.P=PFEC.RSVD=1.

Quoted 18 lines
---
  arch/x86/kvm/paging_tmpl.h |   10 ++++++----
  1 files changed, 6 insertions(+), 4 deletions(-)
arch/x86/kvm/paging_tmpl.h +1 -0
index 291342d..952357a 100644
@@ -760,6 +760,7 @@ static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
  		pt_element_t gpte;
  		gpa_t pte_gpa;
  		gfn_t gfn;
+		bool gpte_invalid;
 
  		if (!is_shadow_present_pte(sp->spt[i]))
  			continue;

arch/x86/kvm/paging_tmpl.h static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp, · line 776

Quoted 8 lines
arch/x86/kvm/paging_tmpl.h +2 -3
@@ -771,12 +772,13 @@ static int FNAME(sync_page)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
  			return -EINVAL;
 
  		gfn = gpte_to_gfn(gpte);
-		if (is_rsvd_bits_set(&vcpu->arch.mmu, gpte, PT_PAGE_TABLE_LEVEL)
-		      || gfn != sp->gfns[i] || !is_present_gpte(gpte)
-		      || !(gpte&  PT_ACCESSED_MASK)) {
+		gpte_invalid = is_present_gpte(gpte) ||
+		   is_rsvd_bits_set(&vcpu->arch.mmu, gpte, PT_PAGE_TABLE_LEVEL);

Shouldn't that be &&?

Quoted 9 lines
arch/x86/kvm/paging_tmpl.h +3 -1
+		if (gpte_invalid || gfn != sp->gfns[i] ||
+		      !(gpte&  PT_ACCESSED_MASK)) {
  			u64 nonpresent;
 
-			if (is_present_gpte(gpte) || !clear_unsync)
+			if (gpte_invalid || !clear_unsync)
  				nonpresent = shadow_trap_nonpresent_pte;
  			else
  				nonpresent = shadow_notrap_nonpresent_pte;
Signature · 1 line
error compiling committee.c: too many arguments to function
#5 lore raw

Re: [PATCH 3/4] KVM: MMU: notrap it if gpte's reserved is set

Quoted 8 lines · Avi Kivity
Quoted 3 lines · Xiao Guangrong

We can past the page fault to guest directly if gpte's reserved is set

How can that work? shadow_notrap_nonpresent_pte causes a fault with PFEC.P=PFEC.RSVD=0, while we need PFEC.P=PFEC.RSVD=1.

Ah, i missed it for a long time, thanks for you point it out.

The same mistake is in 'prefetch' path, i'll fix it in the v2 version.

#6 lore raw

Re: [PATCH 3/4] KVM: MMU: notrap it if gpte's reserved is set

Quoted 6 lines · Xiao Guangrong
Quoted 5 lines · Avi Kivity
Quoted 3 lines · Xiao Guangrong

We can past the page fault to guest directly if gpte's reserved is set

Quoted 7 lines
Quoted 3 lines

How can that work? shadow_notrap_nonpresent_pte causes a fault with PFEC.P=PFEC.RSVD=0, while we need PFEC.P=PFEC.RSVD=1.

Ah, i missed it for a long time, thanks for you point it out.

The same mistake is in 'prefetch' path, i'll fix it in the v2 version.

Doesn't access.flat catch this?

Ideally we'd have a test case to catch this, but it may be hard to write.

Signature · 1 line
error compiling committee.c: too many arguments to function
#7 lore raw

Re: [PATCH 3/4] KVM: MMU: notrap it if gpte's reserved is set

Quoted 11 lines · Avi Kivity
Quoted 10 lines · Xiao Guangrong
Quoted 8 lines · Avi Kivity
Quoted 3 lines · Xiao Guangrong

We can past the page fault to guest directly if gpte's reserved is set

How can that work? shadow_notrap_nonpresent_pte causes a fault with PFEC.P=PFEC.RSVD=0, while we need PFEC.P=PFEC.RSVD=1.

Quoted 5 lines
Quoted 3 lines

Ah, i missed it for a long time, thanks for you point it out.

The same mistake is in 'prefetch' path, i'll fix it in the v2 version.

Doesn't access.flat catch this?

Unfortunately, it can't catch this.

Quoted 3 lines

Ideally we'd have a test case to catch this, but it may be hard to write.

Um. i'll do it later.

#8 lore raw

PATCH 4/4 KVM: MMU: cleanup update_pte, pte_prefetch and sync_page functions

In reply to Xiao Guangrong

Some operation of these functions is very similar, so introduce a common function to cleanup them

---
 arch/x86/kvm/mmu.c         |    3 -
 arch/x86/kvm/paging_tmpl.h |  191 ++++++++++++++++++++++++-------------------
 2 files changed, 107 insertions(+), 87 deletions(-)
arch/x86/kvm/mmu.c +0 -3
index 94d157f..d0bcca2 100644
@@ -3108,9 +3108,6 @@ static void mmu_pte_write_new_pte(struct kvm_vcpu *vcpu,
 		return;
         }
 
-	if (is_rsvd_bits_set(&vcpu->arch.mmu, *(u64 *)new, PT_PAGE_TABLE_LEVEL))
-		return;
-
 	++vcpu->kvm->stat.mmu_pte_updated;
 	if (!sp->role.cr4_pae)
 		paging32_update_pte(vcpu, sp, spte, new);
arch/x86/kvm/paging_tmpl.h +107 -84

257 lines not on this page · raw message

Signature · 1 line
1.7.0.4
#9 lore raw

Re: [PATCH 4/4] KVM: MMU: cleanup update_pte, pte_prefetch and sync_page functions

Quoted 8 lines · Xiao Guangrong

Some operation of these functions is very similar, so introduce a common function to cleanup them

---
 arch/x86/kvm/mmu.c         |    3 -
 arch/x86/kvm/paging_tmpl.h |  191 ++++++++++++++++++++++++-------------------
 2 files changed, 107 insertions(+), 87 deletions(-)

This makes the code more complicated and error prone IMO, because there are specialities of

Quoted 68 lines
arch/x86/kvm/mmu.c +0 -3
index 94d157f..d0bcca2 100644
@@ -3108,9 +3108,6 @@ static void mmu_pte_write_new_pte(struct kvm_vcpu *vcpu,
 		return;
         }
 
-	if (is_rsvd_bits_set(&vcpu->arch.mmu, *(u64 *)new, PT_PAGE_TABLE_LEVEL))
-		return;
-
 	++vcpu->kvm->stat.mmu_pte_updated;
 	if (!sp->role.cr4_pae)
 		paging32_update_pte(vcpu, sp, spte, new);
arch/x86/kvm/paging_tmpl.h +25 -15
index 952357a..1a1a0b9 100644
@@ -299,42 +299,90 @@ static int FNAME(walk_addr_nested)(struct guest_walker *walker,
 					addr, access);
 }
 
-static void FNAME(update_pte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
-			      u64 *spte, const void *pte)
+static bool FNAME(fetch_guest_pte)(struct kvm_vcpu *vcpu,
+				   struct kvm_mmu_page *sp, u64 *spte,
+				   bool clear_unsync, pt_element_t gpte,
+				   pfn_t (get_pfn)(struct kvm_vcpu *, u64 *,
+					  pt_element_t, unsigned, bool *))
 {
-	pt_element_t gpte;
 	unsigned pte_access;
+	u64 nonpresent = shadow_trap_nonpresent_pte;
+	gfn_t gfn;
 	pfn_t pfn;
-	u64 new_spte;
+	bool dirty, host_writeable;
 
-	gpte = *(const pt_element_t *)pte;
-	if (~gpte & (PT_PRESENT_MASK | PT_ACCESSED_MASK)) {
-		if (!is_present_gpte(gpte)) {
-			if (sp->unsync)
-				new_spte = shadow_trap_nonpresent_pte;
-			else
-				new_spte = shadow_notrap_nonpresent_pte;
-			__set_spte(spte, new_spte);
-		}
-		return;
+	if (!is_present_gpte(gpte) ||
+	      is_rsvd_bits_set(&vcpu->arch.mmu, gpte, PT_PAGE_TABLE_LEVEL)) {
+		if (!sp->unsync && !clear_unsync)
+			nonpresent = shadow_notrap_nonpresent_pte;
+		goto no_present;
 	}
-	pgprintk("%s: gpte %llx spte %p\n", __func__, (u64)gpte, spte);
+
+	if (!(gpte & PT_ACCESSED_MASK))
+		goto no_present;
+
 	pte_access = sp->role.access & FNAME(gpte_access)(vcpu, gpte);
+	gfn = gpte_to_gfn(gpte);
+	dirty = is_dirty_gpte(gpte);
+	pfn = get_pfn(vcpu, spte, gpte, pte_access, &host_writeable);
+
+	if (is_error_pfn(pfn))
+		goto no_present;
+
+	if (!host_writeable)

arch/x86/kvm/paging_tmpl.h static int FNAME(walk_addr_nested)(struct guest_walker *walker, · line 342

Quoted 10 lines
arch/x86/kvm/paging_tmpl.h +10 -0
+		pte_access &= ~ACC_WRITE_MASK;
+
+	if (spte_to_pfn(*spte) == pfn)
+		set_spte(vcpu, spte, pte_access, 0, 0,
+			 dirty, PT_PAGE_TABLE_LEVEL, gfn,
+			 pfn, true, false, host_writeable);
+	else
+		mmu_set_spte(vcpu, spte, sp->role.access, pte_access, 0, 0,
+			     dirty, NULL, PT_PAGE_TABLE_LEVEL, gfn,
+			     pfn, true, host_writeable);

For example, the update path should always go through mmu_set_spte to update last_pte_updated, last_pte_gfn.

Also the callbacks make it harder to read the code. Maybe the unification works if you use common functions for common parts.

#10 lore raw

Re: [PATCH 4/4] KVM: MMU: cleanup update_pte, pte_prefetch and sync_page functions

Quoted 8 lines · Marcelo Tosatti

arch/x86/kvm/paging_tmpl.h static int FNAME(walk_addr_nested)(struct guest_walker *walker, · line 342

Quoted 4 lines
arch/x86/kvm/paging_tmpl.h +4 -0
+	else
+		mmu_set_spte(vcpu, spte, sp->role.access, pte_access, 0, 0,
+			     dirty, NULL, PT_PAGE_TABLE_LEVEL, gfn,
+			     pfn, true, host_writeable);

For example, the update path should always go through mmu_set_spte to update last_pte_updated, last_pte_gfn.

Actually, the set_spte() just works for sync path ;-)

Quoted 3 lines

Also the callbacks make it harder to read the code. Maybe the unification works if you use common functions for common parts.

Um. your advice is reasonable, i'll improve it. Thanks.

#11 lore raw
In reply to Xiao Guangrong
Quoted 2 lines · Xiao Guangrong

We just need flush tlb if overwrite a writable spte with a read-only one

What are the advantages? Avoid playing with rmap, and avoid a window where the spte is missing?

(they are worth the patch, just seeing if I'm not missing something)

Signature · 1 line
error compiling committee.c: too many arguments to function
#12 lore raw
Quoted 7 lines · Avi Kivity
Quoted 2 lines · Xiao Guangrong

We just need flush tlb if overwrite a writable spte with a read-only one

What are the advantages? Avoid playing with rmap, and avoid a window where the spte is missing?

Both, but only the first was in my mind when i'm making the patch :-)

#13 lore raw
In reply to Xiao Guangrong
Quoted 14 lines · Xiao Guangrong

We just need flush tlb if overwrite a writable spte with a read-only one

---
 arch/x86/kvm/mmu.c |   19 +++++++++----------
 1 files changed, 9 insertions(+), 10 deletions(-)
arch/x86/kvm/mmu.c +0 -0
index 4b6d54c..1a93ab4 100644
@@ -2044,6 +2044,15 @@ static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 	if (pte_access & ACC_WRITE_MASK)
 		mark_page_dirty(vcpu->kvm, gfn);

arch/x86/kvm/mmu.c static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep, · line 2055

Quoted 10 lines
arch/x86/kvm/mmu.c +9 -0
 
+	/*
+	 * If we overwrite a writable spte with a read-only one,
+	 * flush remote TLBs. Otherwise rmap_write_protect will
+	 * find a read-only spte, even though the writable spte
+	 * might be cached on a CPU's TLB.
+	 */
+	else if (is_writable_pte(*sptep))
+		ret = 1;
+

The return value of set_spte indicates whether the gfn being mapped to was write protected, not if a TLB flush is necessary.

Quoted 9 lines
arch/x86/kvm/mmu.c +0 -2
 set_pte:
 	update_spte(sptep, spte);
 done:
@@ -2084,16 +2093,6 @@ static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 				 spte_to_pfn(*sptep), pfn);
 			drop_spte(vcpu->kvm, sptep, shadow_trap_nonpresent_pte);
 			kvm_flush_remote_tlbs(vcpu->kvm);
-		/*
-		 * If we overwrite a writable spte with a read-only one,

arch/x86/kvm/mmu.c static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *sptep, · line 2097

Quoted 10 lines
arch/x86/kvm/mmu.c +0 -8
-		 * drop it and flush remote TLBs. Otherwise rmap_write_protect
-		 * will find a read-only spte, even though the writable spte
-		 * might be cached on a CPU's TLB.
-		 */
-		} else if (is_writable_pte(*sptep) &&
-			  (!(pte_access & ACC_WRITE_MASK) || !dirty)) {
-			drop_spte(vcpu->kvm, sptep, shadow_trap_nonpresent_pte);
-			kvm_flush_remote_tlbs(vcpu->kvm);
 		} else
 			was_rmapped = 1;

And here, flush will only happen if overwrite is RW->RO.

#14 lore raw
Quoted 16 lines · Marcelo Tosatti
Quoted 15 lines · Xiao Guangrong

We just need flush tlb if overwrite a writable spte with a read-only one

---
 arch/x86/kvm/mmu.c |   19 +++++++++----------
 1 files changed, 9 insertions(+), 10 deletions(-)
arch/x86/kvm/mmu.c +0 -0
index 4b6d54c..1a93ab4 100644
@@ -2044,6 +2044,15 @@ static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,
 	if (pte_access & ACC_WRITE_MASK)
 		mark_page_dirty(vcpu->kvm, gfn);
 
Quoted 13 lines

arch/x86/kvm/mmu.c static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep, · line 2055

Quoted 9 lines
arch/x86/kvm/mmu.c +9 -0
+	/*
+	 * If we overwrite a writable spte with a read-only one,
+	 * flush remote TLBs. Otherwise rmap_write_protect will
+	 * find a read-only spte, even though the writable spte
+	 * might be cached on a CPU's TLB.
+	 */
+	else if (is_writable_pte(*sptep))
+		ret = 1;
+

The return value of set_spte indicates whether the gfn being mapped to was write protected, not if a TLB flush is necessary.

Yes, i also noticed this and have fixed in the v2 queue, thanks Marcelo!