@@ -1886,7 +1886,7 @@ static bool reg_corruption_finished(struct svm_test *test)
"No RIP corruption detected after %d timer interrupts",
isr_cnt);
set_test_stage(test, 1);
- return true;
+ goto finished;
}
if (vmcb->control.exit_code == SVM_EXIT_INTR) {
@@ -1901,11 +1901,14 @@ static bool reg_corruption_finished(struct svm_test *test)
report(false,
"RIP corruption detected after %d timer interrupts",
isr_cnt);
- return true;
+ goto finished;
}
}
return false;
+finished:
+ apic_write(APIC_LVTT, APIC_LVT_TIMER_MASK);
+ return true;
}
static bool reg_corruption_check(struct svm_test *test)
@@ -2382,6 +2385,155 @@ static void svm_vmrun_errata_test(void)
}
}
+
+/*
+ * Test that nested exceptions are delivered correctly
+ * when parent exception is intercepted
+ */
+
+static void exception_merging_prepare(struct svm_test *test)
+{
+ default_prepare(test);
+ set_test_stage(test, 0);
+
+ vmcb->control.intercept_exceptions |= (1ULL << GP_VECTOR);
+
+ /* break UD vector idt entry to get #GP*/
+ boot_idt[UD_VECTOR].type = 1;
+}
+
+static void exception_merging_test(struct svm_test *test)
+{
+ asm volatile (
+ "ud2\n\t" :
+ /* no outputs*/ :
+ /* no inputs*/ :
+ /* no clobbers*/
+ );
+}
+
+static bool exception_merging_finished(struct svm_test *test)
+{
+ u32 vec = vmcb->control.exit_int_info & SVM_EXITINTINFO_VEC_MASK;
+ u32 type = vmcb->control.exit_int_info & SVM_EXITINTINFO_TYPE_MASK;
+
+ if (vmcb->control.exit_code != SVM_EXIT_EXCP_BASE + GP_VECTOR) {
+ report(false, "unexpected VM exit");
+ goto out;
+ }
+
+ if (! (vmcb->control.exit_int_info & SVM_EXITINTINFO_VALID)) {
+ report(false, "EXITINTINFO not valid");
+ goto out;
+ }
+
+ if (type != SVM_EXITINTINFO_TYPE_EXEPT) {
+ report(false, "Incorrect event type in EXITINTINFO");
+ goto out;
+ }
+
+ if (vec != UD_VECTOR) {
+ report(false, "Incorrect vector in EXITINTINFO");
+ goto out;
+ }
+
+ set_test_stage(test, 1);
+out:
+ boot_idt[UD_VECTOR].type = 14;
+ return true;
+}
+
+static bool exception_merging_check(struct svm_test *test)
+{
+ return get_test_stage(test) == 1;
+}
+
+
+/*
+ * Test that if exception is raised during interrupt delivery,
+ * and that exception is intercepted, the interrupt is preserved
+ * in EXITINTINFO of the exception
+ */
+
+static void interrupt_merging_prepare(struct svm_test *test)
+{
+ default_prepare(test);
+ set_test_stage(test, 0);
+
+ /* intercept #GP */
+ vmcb->control.intercept_exceptions |= (1ULL << GP_VECTOR);
+
+ /* set local APIC to inject external interrupts */
+ apic_write(APIC_TMICT, 0);
+ apic_write(APIC_TDCR, 0);
+ apic_write(APIC_LVTT, TIMER_VECTOR | APIC_LVT_TIMER_PERIODIC);
+ apic_write(APIC_TMICT, 1000);
+}
+
+static void interrupt_merging_test(struct svm_test *test)
+{
+ /* break timer vector IDT entry to get #GP on interrupt delivery */
+ boot_idt[TIMER_VECTOR].type = 1;
+
+ irq_enable();
+
+ /* just wait forever */
+ for(;;);
+}
+
+static bool interrupt_merging_finished(struct svm_test *test)
+{
+ u32 vec = vmcb->control.exit_int_info & SVM_EXITINTINFO_VEC_MASK;
+ u32 type = vmcb->control.exit_int_info & SVM_EXITINTINFO_TYPE_MASK;
+ u32 error_code = vmcb->control.exit_info_1;
+
+ /* exit on external interrupts is disabled, thus timer interrupt
+ * should be attempted to be delivered, but due to incorrect IDT entry
+ * an #GP should be raised
+ */
+ if (vmcb->control.exit_code != SVM_EXIT_EXCP_BASE + GP_VECTOR) {
+ report(false, "unexpected VM exit");
+ goto out;
+ }
+
+ /* GP error code should be about an IDT entry, and due to external event */
+ if (error_code != (TIMER_VECTOR << 3 | 3)) {
+ report(false, "Incorrect error code of the GP exception");
+ goto out;
+ }
+
+ /* Original interrupt should be preserved in EXITINTINFO */
+ if (! (vmcb->control.exit_int_info & SVM_EXITINTINFO_VALID)) {
+ report(false, "EXITINTINFO not valid");
+ goto out;
+ }
+
+ if (type != SVM_EXITINTINFO_TYPE_INTR) {
+ report(false, "Incorrect event type in EXITINTINFO");
+ goto out;
+ }
+
+ if (vec != TIMER_VECTOR) {
+ report(false, "Incorrect vector in EXITINTINFO");
+ goto out;
+ }
+
+ set_test_stage(test, 1);
+out:
+ boot_idt[TIMER_VECTOR].type = 14;
+ apic_write(APIC_LVTT, APIC_LVT_TIMER_MASK);
+ return true;
+
+}
+
+static bool interrupt_merging_check(struct svm_test *test)
+{
+ return get_test_stage(test) == 1;
+}
+
+////////////////////////////////////////////////
+
+
struct svm_test svm_tests[] = {
{ "null", default_supported, default_prepare,
default_prepare_gif_clear, null_test,
@@ -2492,6 +2644,12 @@ struct svm_test svm_tests[] = {
{ "svm_init_intercept_test", smp_supported, init_intercept_prepare,
default_prepare_gif_clear, init_intercept_test,
init_intercept_finished, init_intercept_check, .on_vcpu = 2 },
+ { "exception_merging", default_supported, exception_merging_prepare,
+ default_prepare_gif_clear, exception_merging_test,
+ exception_merging_finished, exception_merging_check },
+ { "interrupt_merging", default_supported, interrupt_merging_prepare,
+ default_prepare_gif_clear, interrupt_merging_test,
+ interrupt_merging_finished, interrupt_merging_check },
TEST(svm_cr4_osxsave_test),
TEST(svm_guest_state_test),
TEST(svm_vmrun_errata_test),
Test that exitintinfo is set correctly when exception happens during exception/interrupt delivery and that exception is intercepted. Note that those tests currently fail, due to few bugs in KVM. Also note that those bugs are in KVM's common x86 code, thus the issue exists on VMX as well and unit tests that reproduce those on VMX will be written as well. Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com> --- x86/svm_tests.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-)