diff --git a/lib/x86/msr.h b/lib/x86/msr.h
index 2c0598c..41f75d9 100644
--- a/lib/x86/msr.h
+++ b/lib/x86/msr.h
@@ -400,6 +400,7 @@
 #define MSR_IA32_VMX_TRUE_PROC		0x0000048e
 #define MSR_IA32_VMX_TRUE_EXIT		0x0000048f
 #define MSR_IA32_VMX_TRUE_ENTRY		0x00000490
+#define MSR_IA32_VMX_VMFUNC             0x00000491
 
 #define MSR_IA32_TSCDEADLINE		0x000006e0
 
diff --git a/x86/vmx.h b/x86/vmx.h
index d12b9de..b424674 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -138,11 +138,14 @@ enum Encoding {
 	TSC_OFFSET_HI		= 0x2011ul,
 	APIC_VIRT_ADDR		= 0x2012ul,
 	APIC_ACCS_ADDR		= 0x2014ul,
+	VMFUNC_CTRL             = 0x2018ul,
+	VMFUNC_CTRL_HI          = 0x2019ul,
 	EPTP			= 0x201aul,
 	EPTP_HI			= 0x201bul,
 	PMLADDR                 = 0x200eul,
 	PMLADDR_HI              = 0x200ful,
-
+	EPTP_LIST_ADDR          = 0x2024ul,
+	EPTP_LIST_ADDR_HI       = 0x2025ul,
 
 	/* 64-Bit Readonly Data Field */
 	INFO_PHYS_ADDR		= 0x2400ul,
@@ -394,6 +397,7 @@ enum Ctrl1 {
 	CPU_URG			= 1ul << 7,
 	CPU_WBINVD		= 1ul << 6,
 	CPU_RDRAND		= 1ul << 11,
+	CPU_VMFUNC              = 1ul << 13,
 	CPU_PML                 = 1ul << 17,
 };
 
@@ -405,6 +409,8 @@ enum Intr_type {
 	VMX_INTR_TYPE_SOFT_EXCEPTION = 6,
 };
 
+#define EPTP_SWITCHING 0x1
+
 /*
  * Interruption-information format
  */
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 2522d3a..c97aad4 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -29,6 +29,9 @@ void *data_page1, *data_page2;
 void *pml_log;
 #define PML_INDEX 512
 
+void *vpage, *hpage1, *hpage2;
+void *eptp_list;
+
 static inline unsigned ffs(unsigned x)
 {
 	int pos = -1;
@@ -1332,6 +1335,148 @@ static int eptad_init()
 	return r;
 }
 
+static int vmfunc_init()
+{
+	eptp_list = alloc_page();
+	u64 *buf = eptp_list;
+
+	vpage = alloc_page();
+	hpage1 = alloc_page();
+	hpage2 = alloc_page();
+
+	*((u32 *)vpage) = 0x1;
+	*((u32 *)hpage1) = 0x2;
+	*((u32 *)hpage2) = 0x3;
+
+	ept_init_common(false);
+	buf[0] = eptp;
+	install_ept(pml4, (unsigned long)hpage1, (unsigned long)vpage,
+		    EPT_RA | EPT_WA | EPT_EA);
+
+	ept_init_common(false);
+	buf[1] = eptp;
+	install_ept(pml4, (unsigned long)hpage2, (unsigned long)vpage,
+		    EPT_RA | EPT_WA | EPT_EA);
+
+	if (!(ctrl_cpu_rev[1].clr & CPU_VMFUNC)) {
+		printf("\tVMFUNC is not supported");
+		return VMX_TEST_EXIT;
+	}
+
+	return VMX_TEST_START;
+}
+
+static void vmfunc_ud_handler(struct ex_regs *regs)
+{
+	switch(vmx_get_test_stage()) {
+	case 0:
+	case 3:
+		regs->rip += 3;
+		vmx_inc_test_stage();
+		vmx_inc_test_stage();
+		break;
+	default:
+		printf("Unexpected #UD hit!\n");
+		print_vmexit_info();
+		exit(0);
+	}
+}
+
+static void vmfunc_main()
+{
+	int nr = 0, ept = 0;
+
+	handle_exception(UD_VECTOR, vmfunc_ud_handler);
+	vmx_set_test_stage(0);
+
+	/* Call vmfunc when L1 hasn't enabled it' */
+	asm volatile("vmfunc"
+		     :
+		     : "a"(nr), "c"(ept)
+		     : "memory");
+
+	report("VMFUNC - Disabled VMFUNC in sec controls causes #UD",
+	       vmx_get_test_stage() == 2);
+
+	vmcall();
+	/* function > 64 causes #UD */
+	nr = 64;
+	asm volatile("vmfunc"
+		     :
+		     : "a"(nr), "c"(ept)
+		     : "memory");
+	report("VMFUNC - guest call with EAX > 63 causes #UD",
+	       vmx_get_test_stage() == 5);
+
+	/* invalid vm function causes vmexit */
+	nr = 1;
+	asm volatile("vmfunc"
+		     :
+		     : "a"(nr), "c"(ept)
+		     : "memory");
+	report("VMFUNC - guest call with non-existant function",
+	       vmx_get_test_stage() == 6);
+
+	/* invalid entry from the eptp list causes vmexit */
+	nr = 0;
+	ept = 512;
+	asm volatile("vmfunc"
+		     :
+		     : "a"(nr), "c"(ept)
+		     : "memory");
+	report("VMFUNC - guest call with invalid entry in eptp list",
+	       vmx_get_test_stage() == 7);
+
+	/*
+	 * vpage points to hpage2 with value 0x3
+	 * vmfunc with ecx = 0 should make vpage
+	 * point to 0x2
+	 */
+	if (*((u32 *)vpage) != 0x3) {
+		printf("Guest address points to unexpected page\n");
+		exit(0);
+	}
+	ept = 0;
+	asm volatile("vmfunc"
+		     :
+		     : "a"(nr), "c"(ept)
+		     : "memory");
+	report("VMFUNC - call to switch ept pointer without an exit",
+	       (vmx_get_test_stage() == 7) && (*((u32 *)vpage) == 0x2));
+}
+
+static int vmfunc_exit_handler(void)
+{
+	ulong reason = vmcs_read(EXI_REASON) & 0xff;
+	u64 guest_rip = vmcs_read(GUEST_RIP);
+	u32 insn_len = vmcs_read(EXI_INST_LEN);
+	u32 ctrl_cpu;
+	u64 controls;
+
+	switch (reason) {
+	case VMX_VMCALL:
+		ctrl_cpu = vmcs_read(CPU_EXEC_CTRL1) | CPU_VMFUNC;
+		vmcs_write(CPU_EXEC_CTRL1, ctrl_cpu);
+
+		controls = rdmsr(MSR_IA32_VMX_VMFUNC) & 1;
+		if (!(controls & EPTP_SWITCHING)) {
+			printf("\tEPTP switching is not supported");
+			return VMX_TEST_EXIT;
+		}
+
+		vmcs_write(EPTP_LIST_ADDR, (u64)eptp_list);
+		vmcs_write(VMFUNC_CTRL, controls);
+	case VMX_VMFUNC:
+		vmx_inc_test_stage();
+		vmcs_write(GUEST_RIP, guest_rip + insn_len);
+		return VMX_TEST_RESUME;
+	default:
+		report("Unknown exit reason, %ld", false, reason);
+		print_vmexit_info();
+	}
+	return VMX_TEST_VMEXIT;
+}
+
 static int pml_init()
 {
 	u32 ctrl_cpu;
@@ -3173,6 +3318,7 @@ struct vmx_test vmx_tests[] = {
 		disable_rdtscp_exit_handler, NULL, {0} },
 	{ "int3", int3_init, int3_guest_main, int3_exit_handler, NULL, {0} },
 	{ "into", into_init, into_guest_main, into_exit_handler, NULL, {0} },
+	{ "vmfunc", vmfunc_init, vmfunc_main, vmfunc_exit_handler, NULL, {0} },
 	{ "exit_monitor_from_l2_test", NULL, exit_monitor_from_l2_main,
 		exit_monitor_from_l2_handler, NULL, {0} },
 	/* Basic V2 tests. */
