vmx_tests: Add Page Modification Logging tests

Verify that a gpa is logged in the pml buffer when
it's written to. Also verify that when the PML buffer
overflows, a PML FULL event occurs.

Signed-off-by: Bandan Das <bsd@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index 09b2252..5ab4667 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -308,6 +308,12 @@
 arch = x86_64
 groups = vmx
 
+[vmx_PML]
+file = vmx.flat
+extra_params = -cpu host,+vmx -append PML
+arch = x86_64
+groups = vmx
+
 [vmx_VPID]
 file = vmx.flat
 extra_params = -cpu host,+vmx -append VPID
diff --git a/x86/vmx.h b/x86/vmx.h
index 4194429..392979b 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -110,6 +110,7 @@
 	GUEST_SEL_LDTR		= 0x080cul,
 	GUEST_SEL_TR		= 0x080eul,
 	GUEST_INT_STATUS	= 0x0810ul,
+	GUEST_PML_INDEX         = 0x0812ul,
 
 	/* 16-Bit Host State Fields */
 	HOST_SEL_ES		= 0x0c00ul,
@@ -134,6 +135,9 @@
 	APIC_ACCS_ADDR		= 0x2014ul,
 	EPTP			= 0x201aul,
 	EPTP_HI			= 0x201bul,
+	PMLADDR                 = 0x200eul,
+	PMLADDR_HI              = 0x200ful,
+
 
 	/* 64-Bit Readonly Data Field */
 	INFO_PHYS_ADDR		= 0x2400ul,
@@ -385,6 +389,7 @@
 	CPU_URG			= 1ul << 7,
 	CPU_WBINVD		= 1ul << 6,
 	CPU_RDRAND		= 1ul << 11,
+	CPU_PML                 = 1ul << 17,
 };
 
 enum Intr_type {
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 197d57b..03e4ad4 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -22,6 +22,9 @@
 u64 eptp;
 void *data_page1, *data_page2;
 
+void *pml_log;
+#define PML_INDEX 512
+
 static inline void vmcall()
 {
 	asm volatile("vmcall");
@@ -1110,6 +1113,52 @@
 	return true;
 }
 
+static int pml_exit_handler(void)
+{
+	u16 index, count;
+	ulong reason = vmcs_read(EXI_REASON) & 0xff;
+	u64 *pmlbuf = pml_log;
+	u64 guest_rip = vmcs_read(GUEST_RIP);;
+	u64 guest_cr3 = vmcs_read(GUEST_CR3);
+	u32 insn_len = vmcs_read(EXI_INST_LEN);
+
+	switch (reason) {
+	case VMX_VMCALL:
+		switch (vmx_get_test_stage()) {
+		case 0:
+			index = vmcs_read(GUEST_PML_INDEX);
+			for (count = index + 1; count < PML_INDEX; count++) {
+				if (pmlbuf[count] == (u64)data_page2) {
+					vmx_inc_test_stage();
+					clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2);
+					break;
+				}
+			}
+			break;
+		case 1:
+			index = vmcs_read(GUEST_PML_INDEX);
+			/* Keep clearing the dirty bit till a overflow */
+			clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2);
+			break;
+		default:
+			printf("ERROR - unexpected stage, %d.\n",
+			       vmx_get_test_stage());
+			print_vmexit_info();
+			return VMX_TEST_VMEXIT;
+		}
+		vmcs_write(GUEST_RIP, guest_rip + insn_len);
+		return VMX_TEST_RESUME;
+	case VMX_PML_FULL:
+		vmx_inc_test_stage();
+		vmcs_write(GUEST_PML_INDEX, PML_INDEX - 1);
+		return VMX_TEST_RESUME;
+	default:
+		printf("Unknown exit reason, %ld\n", reason);
+		print_vmexit_info();
+	}
+	return VMX_TEST_VMEXIT;
+}
+
 static int ept_exit_handler_common(bool have_ad)
 {
 	u64 guest_rip;
@@ -1269,6 +1318,49 @@
 	return r;
 }
 
+static int pml_init()
+{
+	u32 ctrl_cpu;
+	int r = eptad_init();
+
+	if (r == VMX_TEST_EXIT)
+		return r;
+
+	if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
+		!(ctrl_cpu_rev[1].clr & CPU_PML)) {
+		printf("\tPML is not supported");
+		return VMX_TEST_EXIT;
+	}
+
+	pml_log = alloc_page();
+	memset(pml_log, 0x0, PAGE_SIZE);
+	vmcs_write(PMLADDR, (u64)pml_log);
+	vmcs_write(GUEST_PML_INDEX, PML_INDEX - 1);
+
+	ctrl_cpu = vmcs_read(CPU_EXEC_CTRL1) | CPU_PML;
+	vmcs_write(CPU_EXEC_CTRL1, ctrl_cpu);
+
+	return VMX_TEST_START;
+}
+
+static void pml_main()
+{
+	int count = 0;
+
+	vmx_set_test_stage(0);
+	*((u32 *)data_page2) = 0x1;
+	vmcall();
+	report("PML - Dirty GPA Logging", vmx_get_test_stage() == 1);
+
+	while (vmx_get_test_stage() == 1) {
+		*((u32 *)data_page2) = 0x1;
+		if (count++ > PML_INDEX)
+			break;
+		vmcall();
+	}
+	report("PML Full Event", vmx_get_test_stage() == 2);
+}
+
 static void eptad_main()
 {
 	ept_common();
@@ -2844,6 +2936,7 @@
 		insn_intercept_exit_handler, NULL, {0} },
 	{ "EPT A/D disabled", ept_init, ept_main, ept_exit_handler, NULL, {0} },
 	{ "EPT A/D enabled", eptad_init, eptad_main, eptad_exit_handler, NULL, {0} },
+	{ "PML", pml_init, pml_main, pml_exit_handler, NULL, {0} },
 	{ "VPID", vpid_init, vpid_main, vpid_exit_handler, NULL, {0} },
 	{ "interrupt", interrupt_init, interrupt_main,
 		interrupt_exit_handler, NULL, {0} },