Test cases for preemption timer in nested VMX. Two aspects are tested:
1. Save preemption timer on VMEXIT if relevant bit set in EXIT_CONTROL
2. Test a relevant bug of KVM. The bug will not save preemption timer
value if exit L2->L0 for some reason and enter L0->L2. Thus preemption
timer will never trigger if the value is large enough.
3. Some other aspects are tested, e.g. preempt without save, preempt
when value is 0.

Signed-off-by: Arthur Chunqi Li <[email protected]>
---
ChangeLog to v1:
1. Add test of EXI_SAVE_PREEMPT enable and PIN_PREEMPT disable
2. Add test of PIN_PREEMPT enable and EXI_SAVE_PREEMPT enable/disable
3. Add test of preemption value is 0

 x86/vmx.h       |    3 +
 x86/vmx_tests.c |  175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 178 insertions(+)

diff --git a/x86/vmx.h b/x86/vmx.h
index 28595d8..ebc8cfd 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -210,6 +210,7 @@ enum Encoding {
        GUEST_ACTV_STATE        = 0x4826ul,
        GUEST_SMBASE            = 0x4828ul,
        GUEST_SYSENTER_CS       = 0x482aul,
+       PREEMPT_TIMER_VALUE     = 0x482eul,
 
        /* 32-Bit Host State Fields */
        HOST_SYSENTER_CS        = 0x4c00ul,
@@ -331,6 +332,7 @@ enum Ctrl_exi {
        EXI_LOAD_PERF           = 1UL << 12,
        EXI_INTA                = 1UL << 15,
        EXI_LOAD_EFER           = 1UL << 21,
+       EXI_SAVE_PREEMPT        = 1UL << 22,
 };
 
 enum Ctrl_ent {
@@ -342,6 +344,7 @@ enum Ctrl_pin {
        PIN_EXTINT              = 1ul << 0,
        PIN_NMI                 = 1ul << 3,
        PIN_VIRT_NMI            = 1ul << 5,
+       PIN_PREEMPT             = 1ul << 6,
 };
 
 enum Ctrl0 {
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index c1b39f4..2e32031 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -1,4 +1,30 @@
 #include "vmx.h"
+#include "msr.h"
+#include "processor.h"
+
+volatile u32 stage;
+
+static inline void vmcall()
+{
+       asm volatile("vmcall");
+}
+ 
+static inline void set_stage(u32 s)
+{
+       barrier();
+       stage = s;
+       barrier();
+}
+
+static inline u32 get_stage()
+{
+       u32 s;
+
+       barrier();
+       s = stage;
+       barrier();
+       return s;
+}
 
 void basic_init()
 {
@@ -76,6 +102,153 @@ int vmenter_exit_handler()
        return VMX_TEST_VMEXIT;
 }
 
+u32 preempt_scale;
+volatile unsigned long long tsc_val;
+volatile u32 preempt_val;
+
+void preemption_timer_init()
+{
+       u32 ctrl_exit;
+
+       // Enable EXI_SAVE_PREEMPT with PIN_PREEMPT dieabled
+       ctrl_exit = (vmcs_read(EXI_CONTROLS) |
+                       EXI_SAVE_PREEMPT) & ctrl_exit_rev.clr;
+       vmcs_write(EXI_CONTROLS, ctrl_exit);
+       preempt_val = 10000000;
+       vmcs_write(PREEMPT_TIMER_VALUE, preempt_val);
+       set_stage(0);
+       preempt_scale = rdmsr(MSR_IA32_VMX_MISC) & 0x1F;
+}
+
+void preemption_timer_main()
+{
+       int i, j;
+
+       if (!(ctrl_pin_rev.clr & PIN_PREEMPT)) {
+               printf("\tPreemption timer is not supported\n");
+               return;
+       }
+       if (!(ctrl_exit_rev.clr & EXI_SAVE_PREEMPT))
+               printf("\tSave preemption value is not supported\n");
+       else {
+               // Test EXI_SAVE_PREEMPT enable and PIN_PREEMPT disable
+               set_stage(0);
+               // Consume enough time to let L2->L0->L2 occurs
+               for(i = 0; i < 100000; i++)
+                       for (j = 0; j < 10000; j++);
+               vmcall();
+               // Test PIN_PREEMPT enable and EXI_SAVE_PREEMPT enable/disable
+               set_stage(1);
+               vmcall();
+               // Test both enable
+               if (get_stage() == 2)
+                       vmcall();
+       }
+       // Test the bug of reseting preempt value when L2->L0->L2
+       set_stage(3);
+       vmcall();
+       tsc_val = rdtsc();
+       while (1) {
+               if (((rdtsc() - tsc_val) >> preempt_scale)
+                               > 10 * preempt_val) {
+                       report("Preemption timer timeout", 0);
+                       break;
+               }
+               if (get_stage() == 4)
+                       break;
+       }
+       // Test preempt val is 0
+       set_stage(4);
+       report("Preemption timer, val=0", 0);
+}
+
+int preemption_timer_exit_handler()
+{
+       u64 guest_rip;
+       ulong reason;
+       u32 insn_len;
+       u32 ctrl_exit;
+       u32 ctrl_pin;
+
+       guest_rip = vmcs_read(GUEST_RIP);
+       reason = vmcs_read(EXI_REASON) & 0xff;
+       insn_len = vmcs_read(EXI_INST_LEN);
+       switch (reason) {
+       case VMX_PREEMPT:
+               switch (get_stage()) {
+               case 3:
+                       if (((rdtsc() - tsc_val) >> preempt_scale) < 
preempt_val)
+                               report("Preemption timer timeout", 0);
+                       else
+                               report("Preemption timer timeout", 1);
+                       set_stage(get_stage() + 1);
+                       break;
+               case 4:
+                       if (vmcs_read(PREEMPT_TIMER_VALUE) == 0)
+                               report("Preemption timer, val=0", 1);
+                       else
+                               report("Preemption timer, val=0", 0);
+                       return VMX_TEST_VMEXIT;
+               }
+               return VMX_TEST_RESUME;
+       case VMX_VMCALL:
+               switch (get_stage()) {
+               case 0:
+                       if (vmcs_read(PREEMPT_TIMER_VALUE) != preempt_val)
+                               report("Preemption timer off", 0);
+                       else
+                               report("Preemption timer off", 1);
+                       // Enable PIN_PREEMPT and disable EXI_SAVE_PREEMPT
+                       ctrl_pin = (vmcs_read(PIN_CONTROLS) | PIN_PREEMPT) &
+                                       ctrl_pin_rev.clr;
+                       vmcs_write(PIN_CONTROLS, ctrl_pin);
+                       ctrl_exit = (vmcs_read(EXI_CONTROLS) &
+                                       ~EXI_SAVE_PREEMPT) & ctrl_exit_rev.clr;
+                       vmcs_write(EXI_CONTROLS, ctrl_exit);
+                       vmcs_write(PREEMPT_TIMER_VALUE, preempt_val);
+                       break;
+               case 1:
+                       if (vmcs_read(PREEMPT_TIMER_VALUE) != preempt_val)
+                               report("Save preemption value", 0);
+                       else {
+                               set_stage(get_stage() + 1);
+                               // Enable EXI_SAVE_PREEMPT with PIN_PREEMPT
+                               // already enabled
+                               ctrl_exit = (vmcs_read(EXI_CONTROLS) |
+                                       EXI_SAVE_PREEMPT) & ctrl_exit_rev.clr;
+                               vmcs_write(EXI_CONTROLS, ctrl_exit);
+                       }
+                       vmcs_write(PREEMPT_TIMER_VALUE, preempt_val);
+                       break;
+               case 2:
+                       if (vmcs_read(PREEMPT_TIMER_VALUE) >= preempt_val)
+                               report("Save preemption value", 0);
+                       else
+                               report("Save preemption value", 1);
+                       break;
+               case 3:
+                       vmcs_write(PREEMPT_TIMER_VALUE, preempt_val);
+                       ctrl_pin = (vmcs_read(PIN_CONTROLS) | PIN_PREEMPT) &
+                                       ctrl_pin_rev.clr;
+                       vmcs_write(PIN_CONTROLS, ctrl_pin);
+                       ctrl_exit = (vmcs_read(EXI_CONTROLS) |
+                                       EXI_SAVE_PREEMPT) & ctrl_exit_rev.clr;
+                       vmcs_write(EXI_CONTROLS, ctrl_exit);
+                       break;
+               default:
+                       printf("Invalid stage.\n");
+                       print_vmexit_info();
+                       return VMX_TEST_VMEXIT;
+               }
+               vmcs_write(GUEST_RIP, guest_rip + insn_len);
+               return VMX_TEST_RESUME;
+       default:
+               printf("Unknown exit reason, %d\n", reason);
+               print_vmexit_info();
+       }
+       return VMX_TEST_VMEXIT;
+}
+
 /* name/init/guest_main/exit_handler/syscall_handler/guest_regs
    basic_* just implement some basic functions */
 struct vmx_test vmx_tests[] = {
@@ -83,5 +256,7 @@ struct vmx_test vmx_tests[] = {
                basic_syscall_handler, {0} },
        { "vmenter", basic_init, vmenter_main, vmenter_exit_handler,
                basic_syscall_handler, {0} },
+       { "preemption timer", preemption_timer_init, preemption_timer_main,
+               preemption_timer_exit_handler, basic_syscall_handler, {0} },
        { NULL, NULL, NULL, NULL, NULL, {0} },
 };
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to