Add two tests to SVM test suite to validate APIC passthrough capabilities. In the apic_passthrough test, the guest asserts irq-line to trigger a level-triggered interrupt that should be injected directly in the guest. Confirm that the remote_irr is set before the guest's EOI and cleared after guest's EOI. Include a variant that uses a separate thread to trigger the interrupt to ensure cross CPU delivery is handled correctly. The svm_apic_passthrough_tpr_threshold_test validates that a guest can directly modify the host's APIC TPR. The host queues a pending self-IPI by disabling interrupts and raising the TPR to a high value. The test then runs a guest that lowers the TPR to 0, and upon returning to the host, it confirms that the pending interrupt is delivered once interrupts are enabled. The nVMX tests already has coverage for APIC passthrough. Add a similar test for nSVM to improve test parity between nSVM and nVMX. This test uses the old V1 test framework to utilize the test stage for specific test event sequencing. Signed-off-by: Kevin Cheng --- x86/svm_tests.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/x86/svm_tests.c b/x86/svm_tests.c index 11d0e3d39f5ba..0eedad6bc3af5 100644 --- a/x86/svm_tests.c +++ b/x86/svm_tests.c @@ -12,6 +12,7 @@ #include "util.h" #include "x86/usermode.h" #include "vmalloc.h" +#include "fwcfg.h" #define SVM_EXIT_MAX_DR_INTERCEPT 0x3f @@ -3575,6 +3576,172 @@ static void svm_shutdown_intercept_test(void) report(vmcb->control.exit_code == SVM_EXIT_SHUTDOWN, "shutdown test passed"); } + +static void set_irq_line_thread(void *data) +{ + /* Wait until other CPU entered L2 */ + while (get_test_stage(data) != 1) + ; + + /* Set irq-line 0xf to raise vector 0x78 for vCPU 0 */ + ioapic_set_redir(0xf, 0x78, TRIGGER_LEVEL); + set_test_stage(data, 2); +} + +static void irq_78_handler_guest(isr_regs_t *regs) +{ + set_irq_line(0xf, 0); + vmmcall(); + eoi(); + vmmcall(); +} + +static void svm_apic_passthrough_guest(struct svm_test *test) +{ + handle_irq(0x78, irq_78_handler_guest); + sti(); + + /* If requested, wait for other CPU to trigger ioapic scan */ + if (get_test_stage(test) < 1) { + set_test_stage(test, 1); + while (get_test_stage(test) != 2) + ; + } + + set_irq_line(0xf, 1); +} + +static void svm_disable_intercept_for_x2apic_msrs(void) +{ + for (u32 msr = APIC_BASE_MSR; msr <= (APIC_BASE_MSR+0xff); ++msr) { + int bit_nr = get_msrpm_bit_nr(msr); + + __clear_bit(bit_nr, msr_bitmap); + __clear_bit(bit_nr + 1, msr_bitmap); + } +} + +static void svm_apic_passthrough_prepare(struct svm_test *test, + bool set_irq_line_from_thread) +{ + if (set_irq_line_from_thread && (cpu_count() < 2)) { + report_skip("%s : CPU count < 2", __func__); + return; + } + + /* Test device is required for generating IRQs */ + if (!test_device_enabled()) { + report_skip("%s : No test device enabled", __func__); + return; + } + + vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT); + svm_disable_intercept_for_x2apic_msrs(); + + vmcb->control.intercept &= ~(1ULL << INTERCEPT_INTR); + + if (set_irq_line_from_thread) { + on_cpu_async(1, set_irq_line_thread, test); + } else { + ioapic_set_redir(0xf, 0x78, TRIGGER_LEVEL); + set_test_stage(test, 2); + } +} + +static void svm_apic_passthrough_test_prepare(struct svm_test *test) +{ + svm_apic_passthrough_prepare(test, false); +} + +static void svm_apic_passthrough_thread_test_prepare(struct svm_test *test) +{ + svm_apic_passthrough_prepare(test, true); +} + +static bool svm_apic_passthrough_test_finished(struct svm_test *test) +{ + u32 exit_code = vmcb->control.exit_code; + + report(exit_code == SVM_EXIT_VMMCALL, "Expected VMMCALL VM-Exit, got exit reason 0x%x", + exit_code); + + switch (get_test_stage(test)) { + case 2: + /* Jump over VMMCALL instruction */ + vmcb->save.rip += 3; + + /* Before EOI remote_irr should still be set */ + report(1 == (int)ioapic_read_redir(0xf).remote_irr, + "IOAPIC pass-through: remote_irr=1 before EOI"); + set_test_stage(test, 3); + return false; + case 3: + /* Jump over VMMCALL instruction */ + vmcb->save.rip += 3; + + /* After EOI remote_irr should be cleared */ + report(0 == (int)ioapic_read_redir(0xf).remote_irr, + "IOAPIC pass-through: remote_irr=0 after EOI"); + set_test_stage(test, 4); + return false; + case 4: + break; + default: + report_fail("Unexpected stage %d", get_test_stage(test)); + } + + return true; +} + +static bool svm_apic_passthrough_test_check(struct svm_test *test) +{ + return get_test_stage(test) == 4; +} + +static void svm_apic_passthrough_tpr_threshold_guest(struct svm_test *test) +{ + cli(); + apic_set_tpr(0); +} + +static bool svm_apic_passthrough_tpr_threshold_ipi_isr_fired; +static void svm_apic_passthrough_tpr_threshold_ipi_isr(isr_regs_t *regs) +{ + svm_apic_passthrough_tpr_threshold_ipi_isr_fired = true; + eoi(); +} + +static void svm_apic_passthrough_tpr_threshold_test(void) +{ + int ipi_vector = 0xe1; + + vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT); + svm_disable_intercept_for_x2apic_msrs(); + + vmcb->control.intercept &= ~(1ULL << INTERCEPT_INTR); + vmcb->control.int_ctl &= ~V_INTR_MASKING_MASK; + + /* Raise L0 TPR-threshold by queueing vector in LAPIC IRR */ + cli(); + apic_set_tpr((ipi_vector >> 4) + 1); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | + APIC_DM_FIXED | ipi_vector, + 0); + + test_set_guest(svm_apic_passthrough_tpr_threshold_guest); + clgi(); + svm_vmrun(); + stgi(); + + report(apic_get_tpr() == 0, "TPR was zero by guest"); + + /* Clean pending self-IPI */ + svm_apic_passthrough_tpr_threshold_ipi_isr_fired = false; + handle_irq(ipi_vector, svm_apic_passthrough_tpr_threshold_ipi_isr); + sti_nop(); + report(svm_apic_passthrough_tpr_threshold_ipi_isr_fired, "self-IPI fired"); +} + struct svm_test svm_tests[] = { { "null", default_supported, default_prepare, default_prepare_gif_clear, null_test, @@ -3694,6 +3861,14 @@ struct svm_test svm_tests[] = { { "vgif", vgif_supported, prepare_vgif_enabled, default_prepare_gif_clear, test_vgif, vgif_finished, vgif_check }, + { "apic_passthrough", default_supported, svm_apic_passthrough_test_prepare, + default_prepare_gif_clear, svm_apic_passthrough_guest, + svm_apic_passthrough_test_finished, svm_apic_passthrough_test_check}, + { "apic_passthrough_thread", default_supported, + svm_apic_passthrough_thread_test_prepare, default_prepare_gif_clear, + svm_apic_passthrough_guest, svm_apic_passthrough_test_finished, + svm_apic_passthrough_test_check}, + TEST(svm_apic_passthrough_tpr_threshold_test), TEST(svm_cr4_osxsave_test), TEST(svm_guest_state_test), TEST(svm_vmrun_errata_test), -- 2.52.0.322.g1dd061c0dc-goog