diff mbox

[kvm-unit-tests,2/2] Add basic invvpid test

Message ID 20170629175211.66170-2-jmattson@google.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jim Mattson June 29, 2017, 5:52 p.m. UTC
Tests only for success/failure of invvpid. Does not test actual
invvpid functionality.

Signed-off-by: Jim Mattson <jmattson@google.com>
---
 lib/x86/processor.h |   5 ++
 x86/unittests.cfg   |   6 ++
 x86/vmx.c           |   6 +-
 x86/vmx.h           |  24 ++++---
 x86/vmx_tests.c     | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 219 insertions(+), 16 deletions(-)
diff mbox

Patch

diff --git a/lib/x86/processor.h b/lib/x86/processor.h
index 895d992a6943..8839923e9207 100644
--- a/lib/x86/processor.h
+++ b/lib/x86/processor.h
@@ -430,4 +430,9 @@  static inline void write_pkru(u32 pkru)
         : : "a" (eax), "c" (ecx), "d" (edx));
 }
 
+static inline bool is_canonical(u64 addr)
+{
+	return (s64)(addr << 16) >> 16 == addr;
+}
+
 #endif
diff --git a/x86/unittests.cfg b/x86/unittests.cfg
index 5ab46671d631..c9858159c657 100644
--- a/x86/unittests.cfg
+++ b/x86/unittests.cfg
@@ -494,6 +494,12 @@  extra_params = -cpu host,+vmx -m 2048 -append ept_access_test_force_2m_page
 arch = x86_64
 groups = vmx
 
+[vmx_invvpid]
+file = vmx.flat
+extra_params = -cpu host,+vmx -m 2048 -append invvpid_test_v2
+arch = x86_64
+groups = vmx
+
 [debug]
 file = debug.flat
 arch = x86_64
diff --git a/x86/vmx.c b/x86/vmx.c
index aff13b430a67..f71d71f64f46 100644
--- a/x86/vmx.c
+++ b/x86/vmx.c
@@ -987,9 +987,9 @@  bool ept_ad_bits_supported(void)
 void vpid_sync(int type, u16 vpid)
 {
 	switch(type) {
-	case INVVPID_SINGLE:
-		if (ept_vpid.val & VPID_CAP_INVVPID_SINGLE) {
-			invvpid(INVVPID_SINGLE, vpid, 0);
+	case INVVPID_CONTEXT_GLOBAL:
+		if (ept_vpid.val & VPID_CAP_INVVPID_CXTGLB) {
+			invvpid(INVVPID_CONTEXT_GLOBAL, vpid, 0);
 			break;
 		}
 	case INVVPID_ALL:
diff --git a/x86/vmx.h b/x86/vmx.h
index cbba62e46959..42d7f0a4e5e3 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -13,6 +13,11 @@  struct vmcs {
 	char data[0];
 };
 
+struct invvpid_operand {
+	u64 vpid;
+	u64 gla;
+};
+
 struct regs {
 	u64 rax;
 	u64 rcx;
@@ -537,8 +542,10 @@  enum vm_instruction_error_number {
 #define EPT_CAP_INVEPT_ALL	(1ull << 26)
 #define EPT_CAP_AD_FLAG		(1ull << 21)
 #define VPID_CAP_INVVPID	(1ull << 32)
-#define VPID_CAP_INVVPID_SINGLE	(1ull << 41)
-#define VPID_CAP_INVVPID_ALL	(1ull << 42)
+#define VPID_CAP_INVVPID_ADDR   (1ull << 40)
+#define VPID_CAP_INVVPID_CXTGLB (1ull << 41)
+#define VPID_CAP_INVVPID_ALL    (1ull << 42)
+#define VPID_CAP_INVVPID_CXTLOC	(1ull << 43)
 
 #define PAGE_SIZE_2M		(512 * PAGE_SIZE)
 #define PAGE_SIZE_1G		(512 * PAGE_SIZE_2M)
@@ -569,9 +576,10 @@  enum vm_instruction_error_number {
 #define INVEPT_SINGLE		1
 #define INVEPT_GLOBAL		2
 
-#define INVVPID_SINGLE_ADDRESS	0
-#define INVVPID_SINGLE		1
+#define INVVPID_ADDR            0
+#define INVVPID_CONTEXT_GLOBAL	1
 #define INVVPID_ALL		2
+#define INVVPID_CONTEXT_LOCAL	3
 
 #define ACTV_ACTIVE		0
 #define ACTV_HLT		1
@@ -687,16 +695,12 @@  static inline bool invept(unsigned long type, u64 eptp)
 	return ret;
 }
 
-static inline bool invvpid(unsigned long type, u16 vpid, u64 gva)
+static inline bool invvpid(unsigned long type, u64 vpid, u64 gla)
 {
 	bool ret;
 	u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF;
 
-	struct {
-		u64 vpid : 16;
-		u64 rsvd : 48;
-		u64 gva;
-	} operand = {vpid, 0, gva};
+	struct invvpid_operand operand = {vpid, gla};
 	asm volatile("push %1; popf; invvpid %2, %3; setbe %0"
 		     : "=q" (ret) : "r" (rflags), "m"(operand),"r"(type) : "cc");
 	return ret;
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 03e4ad43eba3..5043652291c6 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -13,6 +13,10 @@ 
 #include "apic.h"
 #include "types.h"
 
+#define NONCANONICAL            0xaaaaaaaaaaaaaaaaull
+
+#define VPID_CAP_INVVPID_TYPES_SHIFT 40
+
 u64 ia32_pat;
 u64 ia32_efer;
 void *io_bitmap_a, *io_bitmap_b;
@@ -25,6 +29,15 @@  void *data_page1, *data_page2;
 void *pml_log;
 #define PML_INDEX 512
 
+static inline unsigned ffs(unsigned x)
+{
+	int pos = -1;
+
+	__asm__ __volatile__("bsf %1, %%eax; cmovnz %%eax, %0"
+			     : "+r"(pos) : "rm"(x) : "eax");
+	return pos + 1;
+}
+
 static inline void vmcall()
 {
 	asm volatile("vmcall");
@@ -1375,7 +1388,8 @@  bool invvpid_test(int type, u16 vpid)
 {
 	bool ret, supported;
 
-	supported = ept_vpid.val & (VPID_CAP_INVVPID_SINGLE >> INVVPID_SINGLE << type);
+	supported = ept_vpid.val &
+		(VPID_CAP_INVVPID_ADDR >> INVVPID_ADDR << type);
 	ret = invvpid(type, vpid, 0);
 
 	if (ret == !supported)
@@ -1432,11 +1446,11 @@  static int vpid_exit_handler()
 	case VMX_VMCALL:
 		switch(vmx_get_test_stage()) {
 		case 0:
-			if (!invvpid_test(INVVPID_SINGLE_ADDRESS, 1))
+			if (!invvpid_test(INVVPID_ADDR, 1))
 				vmx_inc_test_stage();
 			break;
 		case 2:
-			if (!invvpid_test(INVVPID_SINGLE, 1))
+			if (!invvpid_test(INVVPID_CONTEXT_GLOBAL, 1))
 				vmx_inc_test_stage();
 			break;
 		case 4:
@@ -2916,6 +2930,178 @@  static void ept_access_test_force_2m_page(void)
 	ept_misconfig_at_level_mkhuge(true, 2, EPT_PRESENT, EPT_WA);
 }
 
+static bool invvpid_valid(u64 type, u64 vpid, u64 gla)
+{
+	u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+
+	TEST_ASSERT(msr & VPID_CAP_INVVPID);
+
+	if (type < INVVPID_ADDR || type > INVVPID_CONTEXT_LOCAL)
+		return false;
+
+	if (!(msr & (1ull << (type + VPID_CAP_INVVPID_TYPES_SHIFT))))
+		return false;
+
+	if (vpid >> 16)
+		return false;
+
+	if (type != INVVPID_ALL && !vpid)
+		return false;
+
+	if (type == INVVPID_ADDR && !is_canonical(gla))
+		return false;
+
+	return true;
+}
+
+static void try_invvpid(u64 type, u64 vpid, u64 gla)
+{
+	int rc;
+	bool valid = invvpid_valid(type, vpid, gla);
+	u64 expected = valid ? VMXERR_UNSUPPORTED_VMCS_COMPONENT
+		: VMXERR_INVALID_OPERAND_TO_INVEPT_INVVPID;
+	/*
+	 * Set VMX_INST_ERROR to VMXERR_UNVALID_VMCS_COMPONENT, so
+	 * that we can tell if it is updated by INVVPID.
+	 */
+	vmcs_read(~0);
+	rc = invvpid(type, vpid, gla);
+	report("INVVPID type %ld VPID %lx GLA %lx %s",
+	       !rc == valid, type, vpid, gla,
+	       valid ? "passes" : "fails");
+	report("After %s INVVPID, VMX_INST_ERR is %d (actual %d)",
+	       vmcs_read(VMX_INST_ERROR) == expected,
+	       rc ? "failed" : "successful",
+	       expected, vmcs_read(VMX_INST_ERROR));
+}
+
+static void ds_invvpid(void *data)
+{
+	u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+	u64 type = ffs(msr >> VPID_CAP_INVVPID_TYPES_SHIFT) - 1;
+
+	TEST_ASSERT(type >= INVVPID_ADDR && type <= INVVPID_CONTEXT_LOCAL);
+	asm volatile("invvpid %0, %1"
+		     :
+		     : "m"(*(struct invvpid_operand *)data),
+		       "r"(type));
+}
+
+/*
+ * The SS override is ignored in 64-bit mode, so we use an addressing
+ * mode with %rsp as the base register to generate an implicit SS
+ * reference.
+ */
+static void ss_invvpid(void *data)
+{
+	u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+	u64 type = ffs(msr >> VPID_CAP_INVVPID_TYPES_SHIFT) - 1;
+
+	TEST_ASSERT(type >= INVVPID_ADDR && type <= INVVPID_CONTEXT_LOCAL);
+	asm volatile("sub %%rsp,%0; invvpid (%%rsp,%0,1), %1"
+		     : "+r"(data)
+		     : "r"(type));
+}
+
+static void invvpid_test_gp(void)
+{
+	bool fault;
+
+	fault = test_for_exception(GP_VECTOR, &ds_invvpid,
+				   (void *)NONCANONICAL);
+	report("INVVPID with non-canonical DS operand raises #GP", fault);
+}
+
+static void invvpid_test_ss(void)
+{
+	bool fault;
+
+	fault = test_for_exception(SS_VECTOR, &ss_invvpid,
+				   (void *)NONCANONICAL);
+	report("INVVPID with non-canonical SS operand raises #SS", fault);
+}
+
+static void invvpid_test_pf(void)
+{
+	void *vpage = alloc_vpage();
+	bool fault;
+
+	fault = test_for_exception(PF_VECTOR, &ds_invvpid, vpage);
+	report("INVVPID with unmapped operand raises #PF", fault);
+}
+
+static void invvpid_test_not_in_vmx_operation(void)
+{
+	bool fault;
+
+	TEST_ASSERT(!vmx_off());
+	fault = test_for_exception(UD_VECTOR, &ds_invvpid, NULL);
+	report("INVVPID outside of VMX operation raises #UD", fault);
+	TEST_ASSERT(!vmx_on());
+}
+
+/*
+ * This does not test real-address mode, virtual-8086 mode, protected mode,
+ * compatibility mode, or CPL > 0.
+ */
+static void invvpid_test_v2(void)
+{
+	u64 msr;
+	int i;
+	unsigned types = 0;
+	unsigned type;
+
+	if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
+	    !(ctrl_cpu_rev[1].clr & CPU_VPID))
+		test_skip("VPID not supported");
+
+	msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+
+	if (!(msr & VPID_CAP_INVVPID))
+		test_skip("INVVPID not supported.\n");
+
+	if (msr & VPID_CAP_INVVPID_ADDR)
+		types |= 1u << INVVPID_ADDR;
+	if (msr & VPID_CAP_INVVPID_CXTGLB)
+		types |= 1u << INVVPID_CONTEXT_GLOBAL;
+	if (msr & VPID_CAP_INVVPID_ALL)
+		types |= 1u << INVVPID_ALL;
+	if (msr & VPID_CAP_INVVPID_CXTLOC)
+		types |= 1u << INVVPID_CONTEXT_LOCAL;
+
+	if (!types)
+		test_skip("No INVVPID types supported.\n");
+
+	for (i = -127; i < 128; i++)
+		try_invvpid(i, 0xffff, 0);
+
+	/*
+	 * VPID must not be more than 16 bits.
+	 */
+	for (i = 0; i < 64; i++)
+		for (type = 0; type < 4; type++)
+			if (types & (1u << type))
+				try_invvpid(type, 1ul << i, 0);
+
+	/*
+	 * VPID must not be zero, except for "all contexts."
+	 */
+	for (type = 0; type < 4; type++)
+		if (types & (1u << type))
+			try_invvpid(type, 0, 0);
+
+	/*
+	 * The gla operand is only validated for single-address INVVPID.
+	 */
+	if (types & (1u << INVVPID_ADDR))
+		try_invvpid(INVVPID_ADDR, 0xffff, NONCANONICAL);
+
+	invvpid_test_gp();
+	invvpid_test_ss();
+	invvpid_test_pf();
+	invvpid_test_not_in_vmx_operation();
+}
+
 #define TEST(name) { #name, .v2 = name }
 
 /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */
@@ -2977,5 +3163,7 @@  struct vmx_test vmx_tests[] = {
 	TEST(ept_access_test_paddr_read_execute_ad_enabled),
 	TEST(ept_access_test_paddr_not_present_page_fault),
 	TEST(ept_access_test_force_2m_page),
+	/* Opcode tests. */
+	TEST(invvpid_test_v2),
 	{ NULL, NULL, NULL, NULL, NULL, {0} },
 };