From: Oliver Upton <oliver.upton@xxxxxxxxx> Test virtual posted interrupts under the following conditions: - vTPR[7:4] >= VECTOR[7:4]: Expect the L2 interrupt to be blocked. The bit corresponding to the posted interrupt should be set in L2's vIRR. Test with a running guest. - vTPR[7:4] < VECTOR[7:4]: Expect the interrupt to be delivered and the ISR to execute once. Test with a running and halted guest. Signed-off-by: Oliver Upton <oliver.upton@xxxxxxxxx> Co-developed-by: Jim Mattson <jmattson@xxxxxxxxxx> Signed-off-by: Jim Mattson <jmattson@xxxxxxxxxx> --- lib/x86/asm/bitops.h | 8 +++ x86/unittests.cfg | 8 +++ x86/vmx_tests.c | 133 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/lib/x86/asm/bitops.h b/lib/x86/asm/bitops.h index 13a25ec9853d..54ec9c424cd6 100644 --- a/lib/x86/asm/bitops.h +++ b/lib/x86/asm/bitops.h @@ -13,4 +13,12 @@ #define HAVE_BUILTIN_FLS 1 +static inline void test_and_set_bit(long nr, unsigned long *addr) +{ + asm volatile("lock; bts %1,%0" + : "+m" (*addr) + : "Ir" (nr) + : "memory"); +} + #endif diff --git a/x86/unittests.cfg b/x86/unittests.cfg index f307168b0e01..9598c61ef7ac 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -366,6 +366,14 @@ arch = x86_64 groups = vmx timeout = 10 +[vmx_posted_intr_test] +file = vmx.flat +smp = 2 +extra_params = -cpu max,+vmx -append "vmx_posted_interrupts_test" +arch = x86_64 +groups = vmx +timeout = 10 + [vmx_apic_passthrough_thread] file = vmx.flat smp = 2 diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index a26f77e92f72..1a3da59632dc 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -65,6 +65,11 @@ static u32 *get_vapic_page(void) return (u32 *)phys_to_virt(vmcs_read(APIC_VIRT_ADDR)); } +static u64 *get_pi_desc(void) +{ + return (u64 *)phys_to_virt(vmcs_read(POSTED_INTR_DESC_ADDR)); +} + static void basic_guest_main(void) { report_pass("Basic VMX test"); @@ -9327,6 +9332,18 @@ static void enable_vid(void) vmcs_set_bits(CPU_EXEC_CTRL1, CPU_VINTD | CPU_VIRT_X2APIC); } +#define PI_VECTOR 255 + +static void enable_posted_interrupts(void) +{ + void *pi_desc = alloc_page(); + + vmcs_set_bits(PIN_CONTROLS, PIN_POST_INTR); + vmcs_set_bits(EXI_CONTROLS, EXI_INTA); + vmcs_write(PINV, PI_VECTOR); + vmcs_write(POSTED_INTR_DESC_ADDR, (u64)pi_desc); +} + static void trigger_ioapic_scan_thread(void *data) { /* Wait until other CPU entered L2 */ @@ -10722,12 +10739,18 @@ enum Vid_op { VID_OP_SET_CR8, VID_OP_SELF_IPI, VID_OP_TERMINATE, + VID_OP_SPIN, + VID_OP_HLT, }; struct vmx_basic_vid_test_guest_args { enum Vid_op op; u8 nr; u32 isr_exec_cnt; + u32 *virtual_apic_page; + u64 *pi_desc; + u32 dest; + bool in_guest; } vmx_basic_vid_test_guest_args; /* @@ -10743,6 +10766,14 @@ static void set_virr_bit(volatile u32 *virtual_apic_page, u8 nr) virtual_apic_page[page_offset] |= mask; } +static void clear_virr_bit(volatile u32 *virtual_apic_page, u8 nr) +{ + u32 page_offset = (0x200 | ((nr & 0xE0) >> 1)) / sizeof(u32); + u32 mask = 1 << (nr & 0x1f); + + virtual_apic_page[page_offset] &= ~mask; +} + static bool get_virr_bit(volatile u32 *virtual_apic_page, u8 nr) { u32 page_offset = (0x200 | ((nr & 0xE0) >> 1)) / sizeof(u32); @@ -10783,6 +10814,24 @@ static void vmx_basic_vid_test_guest(void) case VID_OP_SELF_IPI: vmx_x2apic_write(APIC_SELF_IPI, nr); break; + case VID_OP_HLT: + cli(); + barrier(); + args->in_guest = true; + barrier(); + safe_halt(); + break; + case VID_OP_SPIN: { + u32 *virtual_apic_page = args->virtual_apic_page; + u32 prev_cnt = args->isr_exec_cnt; + u8 nr = args->nr; + + args->in_guest = true; + while (args->isr_exec_cnt == prev_cnt && + !get_virr_bit(virtual_apic_page, nr)) + pause(); + clear_virr_bit(virtual_apic_page, nr); + } default: break; } @@ -10803,6 +10852,7 @@ static void set_isrs_for_vmx_basic_vid_test(void) */ for (nr = 0x21; nr < 0x100; nr++) { vmcs_write(GUEST_INT_STATUS, 0); + args->virtual_apic_page = get_vapic_page(); args->op = VID_OP_SET_ISR; args->nr = nr; args->isr_exec_cnt = 0; @@ -10812,6 +10862,27 @@ static void set_isrs_for_vmx_basic_vid_test(void) report(true, "Set ISR for vectors 33-255."); } +static void post_interrupt(u8 vector, u32 dest) +{ + volatile struct vmx_basic_vid_test_guest_args *args = + &vmx_basic_vid_test_guest_args; + + test_and_set_bit(vector, args->pi_desc); + test_and_set_bit(256, args->pi_desc); + apic_icr_write(PI_VECTOR, dest); +} + +static void vmx_posted_interrupts_test_worker(void *data) +{ + volatile struct vmx_basic_vid_test_guest_args *args = + &vmx_basic_vid_test_guest_args; + + while (!args->in_guest) + pause(); + + post_interrupt(args->nr, args->dest); +} + /* * Test virtual interrupt delivery (VID) at VM-entry or TPR virtualization * @@ -10843,6 +10914,7 @@ static void test_basic_vid(u8 nr, u8 tpr, enum Vid_op op, u32 isr_exec_cnt_want, * delivery, sets VPPR to VTPR, when SVI is 0. */ args->isr_exec_cnt = 0; + args->virtual_apic_page = get_vapic_page(); args->op = op; switch (op) { case VID_OP_SELF_IPI: @@ -10855,6 +10927,15 @@ static void test_basic_vid(u8 nr, u8 tpr, enum Vid_op op, u32 isr_exec_cnt_want, args->nr = task_priority_class(tpr); set_vtpr(0xff); break; + case VID_OP_SPIN: + case VID_OP_HLT: + vmcs_write(GUEST_INT_STATUS, 0); + args->nr = nr; + set_vtpr(tpr); + args->in_guest = false; + barrier(); + on_cpu_async(1, vmx_posted_interrupts_test_worker, NULL); + break; default: vmcs_write(GUEST_INT_STATUS, nr); set_vtpr(tpr); @@ -10998,6 +11079,57 @@ static void vmx_eoi_virt_test(void) assert_exit_reason(VMX_VMCALL); } +static void vmx_posted_interrupts_test(void) +{ + volatile struct vmx_basic_vid_test_guest_args *args = + &vmx_basic_vid_test_guest_args; + u16 vector; + u8 class; + + if (!cpu_has_apicv()) { + report_skip("%s : Not all required APICv bits supported", __func__); + return; + } + + if (cpu_count() < 2) { + report_skip("%s : CPU count < 2", __func__); + return; + } + + enable_vid(); + enable_posted_interrupts(); + args->pi_desc = get_pi_desc(); + args->dest = apic_id(); + + test_set_guest(vmx_basic_vid_test_guest); + set_isrs_for_vmx_basic_vid_test(); + + for (class = 0; class < 16; class++) { + for (vector = 33; vector < 256; vector++) { + u32 isr_exec_cnt_want = + (task_priority_class(vector) > class) ? + 1 : 0; + + test_basic_vid(vector, class << 4, VID_OP_SPIN, + isr_exec_cnt_want, false); + + /* + * Only test posted interrupts to a halted vCPU if we + * expect the interrupt to be serviced. Otherwise, the + * vCPU could HLT indefinitely. + */ + if (isr_exec_cnt_want) + test_basic_vid(vector, class << 4, VID_OP_HLT, + isr_exec_cnt_want, false); + } + } + report(true, "Posted vectors 33-25 cross TPR classes 0-0xf, running and sometimes halted\n"); + + /* Terminate the guest */ + args->op = VID_OP_TERMINATE; + enter_guest(); +} + #define TEST(name) { #name, .v2 = name } /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */ @@ -11054,6 +11186,7 @@ struct vmx_test vmx_tests[] = { TEST(virt_x2apic_mode_test), TEST(vmx_basic_vid_test), TEST(vmx_eoi_virt_test), + TEST(vmx_posted_interrupts_test), /* APIC pass-through tests */ TEST(vmx_apic_passthrough_test), TEST(vmx_apic_passthrough_thread_test), -- 2.43.0.472.g3155946c3a-goog