Skip to content

Commit c3a9a27

Browse files
committed
KVM: selftests: Add a test to verify APICv updates (while L2 is active)
Add a test to verify KVM correctly handles a variety of edge cases related to APICv updates, and in particular updates that are triggered while L2 is actively running. Reviewed-by: Chao Gao <chao.gao@intel.com> Link: https://patch.msgid.link/20260109034532.1012993-2-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent fd09d25 commit c3a9a27

3 files changed

Lines changed: 160 additions & 0 deletions

File tree

tools/testing/selftests/kvm/Makefile.kvm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ TEST_GEN_PROGS_x86 += x86/ucna_injection_test
115115
TEST_GEN_PROGS_x86 += x86/userspace_io_test
116116
TEST_GEN_PROGS_x86 += x86/userspace_msr_exit_test
117117
TEST_GEN_PROGS_x86 += x86/vmx_apic_access_test
118+
TEST_GEN_PROGS_x86 += x86/vmx_apicv_updates_test
118119
TEST_GEN_PROGS_x86 += x86/vmx_dirty_log_test
119120
TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state
120121
TEST_GEN_PROGS_x86 += x86/vmx_msrs_test

tools/testing/selftests/kvm/include/x86/apic.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#define APIC_SPIV 0xF0
3333
#define APIC_SPIV_FOCUS_DISABLED (1 << 9)
3434
#define APIC_SPIV_APIC_ENABLED (1 << 8)
35+
#define APIC_ISR 0x100
3536
#define APIC_IRR 0x200
3637
#define APIC_ICR 0x300
3738
#define APIC_LVTCMCI 0x2f0
@@ -68,6 +69,9 @@
6869
#define APIC_TMCCT 0x390
6970
#define APIC_TDCR 0x3E0
7071

72+
#define APIC_VECTOR_TO_BIT_NUMBER(v) ((unsigned int)(v) % 32)
73+
#define APIC_VECTOR_TO_REG_OFFSET(v) ((unsigned int)(v) / 32 * 0x10)
74+
7175
void apic_disable(void);
7276
void xapic_enable(void);
7377
void x2apic_enable(void);
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
#include "test_util.h"
3+
#include "kvm_util.h"
4+
#include "processor.h"
5+
#include "vmx.h"
6+
7+
#define GOOD_IPI_VECTOR 0xe0
8+
#define BAD_IPI_VECTOR 0xf0
9+
10+
static volatile int good_ipis_received;
11+
12+
static void good_ipi_handler(struct ex_regs *regs)
13+
{
14+
good_ipis_received++;
15+
}
16+
17+
static void bad_ipi_handler(struct ex_regs *regs)
18+
{
19+
GUEST_FAIL("Received \"bad\" IPI; ICR MMIO write should have been ignored");
20+
}
21+
22+
static void l2_guest_code(void)
23+
{
24+
x2apic_enable();
25+
vmcall();
26+
27+
xapic_enable();
28+
xapic_write_reg(APIC_ID, 1 << 24);
29+
vmcall();
30+
}
31+
32+
static void l1_guest_code(struct vmx_pages *vmx_pages)
33+
{
34+
#define L2_GUEST_STACK_SIZE 64
35+
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
36+
uint32_t control;
37+
38+
GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
39+
GUEST_ASSERT(load_vmcs(vmx_pages));
40+
41+
/* Prepare the VMCS for L2 execution. */
42+
prepare_vmcs(vmx_pages, l2_guest_code, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
43+
control = vmreadz(CPU_BASED_VM_EXEC_CONTROL);
44+
control |= CPU_BASED_USE_MSR_BITMAPS;
45+
vmwrite(CPU_BASED_VM_EXEC_CONTROL, control);
46+
47+
/* Modify APIC ID to coerce KVM into inhibiting APICv. */
48+
xapic_enable();
49+
xapic_write_reg(APIC_ID, 1 << 24);
50+
51+
/*
52+
* Generate+receive an IRQ without doing EOI to get an IRQ set in vISR
53+
* but not SVI. APICv should be inhibited due to running with a
54+
* modified APIC ID.
55+
*/
56+
xapic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_DM_FIXED | GOOD_IPI_VECTOR);
57+
GUEST_ASSERT_EQ(xapic_read_reg(APIC_ID), 1 << 24);
58+
59+
/* Enable IRQs and verify the IRQ was received. */
60+
sti_nop();
61+
GUEST_ASSERT_EQ(good_ipis_received, 1);
62+
63+
/*
64+
* Run L2 to switch to x2APIC mode, which in turn will uninhibit APICv,
65+
* as KVM should force the APIC ID back to its default.
66+
*/
67+
GUEST_ASSERT(!vmlaunch());
68+
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
69+
vmwrite(GUEST_RIP, vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN));
70+
GUEST_ASSERT(rdmsr(MSR_IA32_APICBASE) & MSR_IA32_APICBASE_EXTD);
71+
72+
/*
73+
* Scribble the APIC access page to verify KVM disabled xAPIC
74+
* virtualization in vmcs01, and to verify that KVM flushes L1's TLB
75+
* when L2 switches back to accelerated xAPIC mode.
76+
*/
77+
xapic_write_reg(APIC_ICR2, 0xdeadbeefu);
78+
xapic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_DM_FIXED | BAD_IPI_VECTOR);
79+
80+
/*
81+
* Verify the IRQ is still in-service and emit an EOI to verify KVM
82+
* propagates the highest vISR vector to SVI when APICv is activated
83+
* (and does so even if APICv was uninhibited while L2 was active).
84+
*/
85+
GUEST_ASSERT_EQ(x2apic_read_reg(APIC_ISR + APIC_VECTOR_TO_REG_OFFSET(GOOD_IPI_VECTOR)),
86+
BIT(APIC_VECTOR_TO_BIT_NUMBER(GOOD_IPI_VECTOR)));
87+
x2apic_write_reg(APIC_EOI, 0);
88+
GUEST_ASSERT_EQ(x2apic_read_reg(APIC_ISR + APIC_VECTOR_TO_REG_OFFSET(GOOD_IPI_VECTOR)), 0);
89+
90+
/*
91+
* Run L2 one more time to switch back to xAPIC mode to verify that KVM
92+
* handles the x2APIC => xAPIC transition and inhibits APICv while L2
93+
* is active.
94+
*/
95+
GUEST_ASSERT(!vmresume());
96+
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
97+
GUEST_ASSERT(!(rdmsr(MSR_IA32_APICBASE) & MSR_IA32_APICBASE_EXTD));
98+
99+
xapic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_DM_FIXED | GOOD_IPI_VECTOR);
100+
/* Re-enable IRQs, as VM-Exit clears RFLAGS.IF. */
101+
sti_nop();
102+
GUEST_ASSERT_EQ(good_ipis_received, 2);
103+
104+
GUEST_ASSERT_EQ(xapic_read_reg(APIC_ISR + APIC_VECTOR_TO_REG_OFFSET(GOOD_IPI_VECTOR)),
105+
BIT(APIC_VECTOR_TO_BIT_NUMBER(GOOD_IPI_VECTOR)));
106+
xapic_write_reg(APIC_EOI, 0);
107+
GUEST_ASSERT_EQ(xapic_read_reg(APIC_ISR + APIC_VECTOR_TO_REG_OFFSET(GOOD_IPI_VECTOR)), 0);
108+
GUEST_DONE();
109+
}
110+
111+
int main(int argc, char *argv[])
112+
{
113+
vm_vaddr_t vmx_pages_gva;
114+
struct vmx_pages *vmx;
115+
struct kvm_vcpu *vcpu;
116+
struct kvm_vm *vm;
117+
struct ucall uc;
118+
119+
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
120+
121+
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
122+
123+
vmx = vcpu_alloc_vmx(vm, &vmx_pages_gva);
124+
prepare_virtualize_apic_accesses(vmx, vm);
125+
vcpu_args_set(vcpu, 1, vmx_pages_gva);
126+
127+
virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
128+
vm_install_exception_handler(vm, BAD_IPI_VECTOR, bad_ipi_handler);
129+
vm_install_exception_handler(vm, GOOD_IPI_VECTOR, good_ipi_handler);
130+
131+
vcpu_run(vcpu);
132+
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
133+
134+
switch (get_ucall(vcpu, &uc)) {
135+
case UCALL_ABORT:
136+
REPORT_GUEST_ASSERT(uc);
137+
/* NOT REACHED */
138+
case UCALL_DONE:
139+
break;
140+
default:
141+
TEST_FAIL("Unexpected ucall %lu", uc.cmd);
142+
}
143+
144+
/*
145+
* Verify at least two IRQs were injected. Unfortunately, KVM counts
146+
* re-injected IRQs (e.g. if delivering the IRQ hits an EPT violation),
147+
* so being more precise isn't possible given the current stats.
148+
*/
149+
TEST_ASSERT(vcpu_get_stat(vcpu, irq_injections) >= 2,
150+
"Wanted at least 2 IRQ injections, got %lu\n",
151+
vcpu_get_stat(vcpu, irq_injections));
152+
153+
kvm_vm_free(vm);
154+
return 0;
155+
}

0 commit comments

Comments
 (0)