diff mbox

[kvm-unit-tests,3/4] VMX: add tests for EPT A/D bits

Message ID 1488996440-6493-4-git-send-email-pbonzini@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Paolo Bonzini March 8, 2017, 6:07 p.m. UTC
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/vmx.c       | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 x86/vmx.h       |  5 +++
 x86/vmx_tests.c | 31 ++++++++++++++++--
 3 files changed, 132 insertions(+), 3 deletions(-)
diff mbox

Patch

diff --git a/x86/vmx.c b/x86/vmx.c
index 5200dcd..0505534 100644
--- a/x86/vmx.c
+++ b/x86/vmx.c
@@ -421,6 +421,105 @@  unsigned long get_ept_pte(unsigned long *pml4,
 	return pte;
 }
 
+static void clear_ept_ad_pte(unsigned long *pml4, unsigned long guest_addr)
+{
+	int l;
+	unsigned long *pt = pml4;
+	u64 pte;
+	unsigned offset;
+
+	for (l = EPT_PAGE_LEVEL; ; --l) {
+		offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK;
+		pt[offset] &= ~(EPT_ACCESS_FLAG|EPT_DIRTY_FLAG);
+		pte = pt[offset];
+		if (l == 1 || (l < 4 && (pte & EPT_LARGE_PAGE)))
+			break;
+		pt = (unsigned long *)(pte & EPT_ADDR_MASK);
+	}
+}
+
+/* clear_ept_ad : Clear EPT A/D bits for the page table walk and the
+   final GPA of a guest address.  */
+void clear_ept_ad(unsigned long *pml4, u64 guest_cr3,
+		  unsigned long guest_addr)
+{
+	int l;
+	unsigned long *pt = (unsigned long *)guest_cr3, gpa;
+	u64 pte, offset_in_page;
+	unsigned offset;
+
+	for (l = EPT_PAGE_LEVEL; ; --l) {
+		offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK;
+
+		clear_ept_ad_pte(pml4, (u64) &pt[offset]);
+		pte = pt[offset];
+		if (l == 1 || (l < 4 && (pte & PT_PAGE_SIZE_MASK)))
+			break;
+		if (!(pte & PT_PRESENT_MASK))
+			return;
+		pt = (unsigned long *)(pte & PT_ADDR_MASK);
+	}
+
+	offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK;
+	offset_in_page = guest_addr & ((1 << EPT_LEVEL_SHIFT(l)) - 1);
+	gpa = (pt[offset] & PT_ADDR_MASK) | (guest_addr & offset_in_page);
+	clear_ept_ad_pte(pml4, gpa);
+}
+
+/* check_ept_ad : Check the content of EPT A/D bits for the page table
+   walk and the final GPA of a guest address.  */
+void check_ept_ad(unsigned long *pml4, u64 guest_cr3,
+		  unsigned long guest_addr, int expected_gpa_ad,
+		  int expected_pt_ad)
+{
+	int l;
+	unsigned long *pt = (unsigned long *)guest_cr3, gpa;
+	u64 ept_pte, pte, offset_in_page;
+	unsigned offset;
+	bool bad_pt_ad = false;
+
+	for (l = EPT_PAGE_LEVEL; ; --l) {
+		offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK;
+
+		ept_pte = get_ept_pte(pml4, (u64) &pt[offset], 1);
+		if (ept_pte == 0)
+			return;
+
+		if (!bad_pt_ad) {
+			bad_pt_ad |= (ept_pte & (EPT_ACCESS_FLAG|EPT_DIRTY_FLAG)) != expected_pt_ad;
+			if (bad_pt_ad)
+				report("EPT - guest level %d page table A=%d/D=%d",
+				       false, l,
+				       !!(expected_pt_ad & EPT_ACCESS_FLAG),
+				       !!(expected_pt_ad & EPT_DIRTY_FLAG));
+		}
+
+		pte = pt[offset];
+		if (l == 1 || (l < 4 && (pte & PT_PAGE_SIZE_MASK)))
+			break;
+		if (!(pte & PT_PRESENT_MASK))
+			return;
+		pt = (unsigned long *)(pte & PT_ADDR_MASK);
+	}
+
+	if (!bad_pt_ad)
+		report("EPT - guest page table structures A=%d/D=%d",
+		       true,
+		       !!(expected_pt_ad & EPT_ACCESS_FLAG),
+		       !!(expected_pt_ad & EPT_DIRTY_FLAG));
+
+	offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK;
+	offset_in_page = guest_addr & ((1 << EPT_LEVEL_SHIFT(l)) - 1);
+	gpa = (pt[offset] & PT_ADDR_MASK) | (guest_addr & offset_in_page);
+
+	ept_pte = get_ept_pte(pml4, gpa, 1);
+	report("EPT - guest physical address A=%d/D=%d",
+	       (ept_pte & (EPT_ACCESS_FLAG|EPT_DIRTY_FLAG)) == expected_gpa_ad,
+	       !!(expected_gpa_ad & EPT_ACCESS_FLAG),
+	       !!(expected_gpa_ad & EPT_DIRTY_FLAG));
+}
+
+
 void ept_sync(int type, u64 eptp)
 {
 	switch (type) {
diff --git a/x86/vmx.h b/x86/vmx.h
index a2bacd3..290e6bd 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -618,5 +618,10 @@  unsigned long get_ept_pte(unsigned long *pml4,
 		unsigned long guest_addr, int level);
 int set_ept_pte(unsigned long *pml4, unsigned long guest_addr,
 		int level, u64 pte_val);
+void check_ept_ad(unsigned long *pml4, u64 guest_cr3,
+		  unsigned long guest_addr, int expected_gpa_ad,
+		  int expected_pt_ad);
+void clear_ept_ad(unsigned long *pml4, u64 guest_cr3,
+		  unsigned long guest_addr);
 
 #endif
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 9dc64fc..a61c9f2 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -969,7 +969,11 @@  static int setup_ept(bool enable_ad)
 	end_of_memory = fwcfg_get_u64(FW_CFG_RAM_SIZE);
 	if (end_of_memory < (1ul << 32))
 		end_of_memory = (1ul << 32);
-	setup_ept_range(pml4, 0, end_of_memory, 0, support_2m,
+	/* Cannot use large EPT pages if we need to track EPT
+	 * accessed/dirty bits at 4K granularity.
+	 */
+	setup_ept_range(pml4, 0, end_of_memory,
+			0, !enable_ad && support_2m,
 			EPT_WA | EPT_RA | EPT_EA);
 	return 0;
 }
@@ -1090,12 +1094,14 @@  bool invept_test(int type, u64 eptp)
 static int ept_exit_handler_common(bool have_ad)
 {
 	u64 guest_rip;
+	u64 guest_cr3;
 	ulong reason;
 	u32 insn_len;
 	u32 exit_qual;
 	static unsigned long data_page1_pte, data_page1_pte_pte;
 
 	guest_rip = vmcs_read(GUEST_RIP);
+	guest_cr3 = vmcs_read(GUEST_CR3);
 	reason = vmcs_read(EXI_REASON) & 0xff;
 	insn_len = vmcs_read(EXI_INST_LEN);
 	exit_qual = vmcs_read(EXI_QUALIFICATION);
@@ -1103,6 +1109,18 @@  static int ept_exit_handler_common(bool have_ad)
 	case VMX_VMCALL:
 		switch (vmx_get_test_stage()) {
 		case 0:
+			check_ept_ad(pml4, guest_cr3,
+				     (unsigned long)data_page1,
+				     have_ad ? EPT_ACCESS_FLAG : 0,
+				     have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0);
+			check_ept_ad(pml4, guest_cr3,
+				     (unsigned long)data_page2,
+				     have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0,
+				     have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0);
+			clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1);
+			clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2);
+			if (have_ad)
+				ept_sync(INVEPT_SINGLE, eptp);;
 			if (*((u32 *)data_page1) == MAGIC_VAL_3 &&
 					*((u32 *)data_page2) == MAGIC_VAL_2) {
 				vmx_inc_test_stage();
@@ -1125,10 +1143,11 @@  static int ept_exit_handler_common(bool have_ad)
 			ept_sync(INVEPT_SINGLE, eptp);
 			break;
 		case 3:
+			clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1);
 			data_page1_pte = get_ept_pte(pml4,
 				(unsigned long)data_page1, 1);
 			set_ept_pte(pml4, (unsigned long)data_page1, 
-				1, data_page1_pte & (~EPT_PRESENT));
+				1, data_page1_pte & ~EPT_PRESENT);
 			ept_sync(INVEPT_SINGLE, eptp);
 			break;
 		case 4:
@@ -1137,7 +1156,7 @@  static int ept_exit_handler_common(bool have_ad)
 			data_page1_pte &= PAGE_MASK;
 			data_page1_pte_pte = get_ept_pte(pml4, data_page1_pte, 2);
 			set_ept_pte(pml4, data_page1_pte, 2,
-				data_page1_pte_pte & (~EPT_PRESENT));
+				data_page1_pte_pte & ~EPT_PRESENT);
 			ept_sync(INVEPT_SINGLE, eptp);
 			break;
 		case 6:
@@ -1174,6 +1193,9 @@  static int ept_exit_handler_common(bool have_ad)
 	case VMX_EPT_VIOLATION:
 		switch(vmx_get_test_stage()) {
 		case 3:
+			check_ept_ad(pml4, guest_cr3, (unsigned long)data_page1, 0,
+				     have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0);
+			clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1);
 			if (exit_qual == (EPT_VLT_WR | EPT_VLT_LADDR_VLD |
 					EPT_VLT_PADDR))
 				vmx_inc_test_stage();
@@ -1182,6 +1204,9 @@  static int ept_exit_handler_common(bool have_ad)
 			ept_sync(INVEPT_SINGLE, eptp);
 			break;
 		case 4:
+			check_ept_ad(pml4, guest_cr3, (unsigned long)data_page1, 0,
+				     have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0);
+			clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1);
 			if (exit_qual == (EPT_VLT_RD |
 					  (have_ad ? EPT_VLT_WR : 0) |
 					  EPT_VLT_LADDR_VLD))