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@xxxxxxxxxx> --- x86/svm_tests.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/x86/svm_tests.c b/x86/svm_tests.c index 7a67132a..202e9271 100644 --- a/x86/svm_tests.c +++ b/x86/svm_tests.c @@ -3254,6 +3254,145 @@ static void svm_shutdown_intercept_test(void) report(vmcb->control.exit_code == SVM_EXIT_SHUTDOWN, "shutdown test passed"); } +/* + * Test that nested exceptions are delivered correctly + * when parent exception is intercepted + */ + +static void exception_merging_prepare(struct svm_test *test) +{ + 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"); +} + +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) +{ + /* intercept #GP */ + vmcb->control.intercept_exceptions |= (1ULL << GP_VECTOR); + + /* set local APIC to inject external interrupts */ + apic_setup_timer(TIMER_VECTOR, APIC_LVT_TIMER_PERIODIC); + apic_start_timer(100000); +} + +#define INTERRUPT_MERGING_DELAY 100000000ULL + +static void interrupt_merging_test(struct svm_test *test) +{ + handle_irq(TIMER_VECTOR, timer_isr); + /* break timer vector IDT entry to get #GP on interrupt delivery */ + boot_idt[TIMER_VECTOR].type = 1; + + sti(); + delay(INTERRUPT_MERGING_DELAY); +} + +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 cleanup; + } + + /* 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 cleanup; + } + + /* Original interrupt should be preserved in EXITINTINFO */ + if (!(vmcb->control.exit_int_info & SVM_EXITINTINFO_VALID)) { + report(false, "EXITINTINFO not valid"); + goto cleanup; + } + + if (type != SVM_EXITINTINFO_TYPE_INTR) { + report(false, "Incorrect event type in EXITINTINFO"); + goto cleanup; + } + + if (vec != TIMER_VECTOR) { + report(false, "Incorrect vector in EXITINTINFO"); + goto cleanup; + } + + set_test_stage(test, 1); + +cleanup: + // restore the IDT gate + boot_idt[TIMER_VECTOR].type = 14; + wmb(); + // eoi the interrupt we got #GP for + eoi(); + apic_cleanup_timer(); + 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, @@ -3346,6 +3485,15 @@ struct svm_test svm_tests[] = { { "vgif", vgif_supported, prepare_vgif_enabled, default_prepare_gif_clear, test_vgif, vgif_finished, vgif_check }, + { "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), -- 2.34.3