Skip to content

Commit df98ce7

Browse files
jsmattsonjrsean-jc
authored andcommitted
KVM: selftests: Test behavior of KVM_X86_DISABLE_EXITS_APERFMPERF
For a VCPU thread pinned to a single LPU, verify that interleaved host and guest reads of IA32_[AM]PERF return strictly increasing values when APERFMPERF exiting is disabled. Run the test in both L1 and L2 to verify that KVM passes through the APERF and MPERF MSRs when L1 doesn't want to intercept them (or any MSRs). Signed-off-by: Jim Mattson <jmattson@google.com> Link: https://lore.kernel.org/r/20250530185239.2335185-4-jmattson@google.com Co-developed-by: Sean Christopherson <seanjc@google.com> Link: https://lore.kernel.org/r/20250626001225.744268-5-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent e83ee6f commit df98ce7

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

tools/testing/selftests/kvm/Makefile.kvm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ TEST_GEN_PROGS_x86 += x86/amx_test
134134
TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
135135
TEST_GEN_PROGS_x86 += x86/triple_fault_event_test
136136
TEST_GEN_PROGS_x86 += x86/recalc_apic_map_test
137+
TEST_GEN_PROGS_x86 += x86/aperfmperf_test
137138
TEST_GEN_PROGS_x86 += access_tracking_perf_test
138139
TEST_GEN_PROGS_x86 += coalesced_io_test
139140
TEST_GEN_PROGS_x86 += dirty_log_perf_test
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Test for KVM_X86_DISABLE_EXITS_APERFMPERF
4+
*
5+
* Copyright (C) 2025, Google LLC.
6+
*
7+
* Test the ability to disable VM-exits for rdmsr of IA32_APERF and
8+
* IA32_MPERF. When these VM-exits are disabled, reads of these MSRs
9+
* return the host's values.
10+
*
11+
* Note: Requires read access to /dev/cpu/<lpu>/msr to read host MSRs.
12+
*/
13+
14+
#include <fcntl.h>
15+
#include <limits.h>
16+
#include <stdbool.h>
17+
#include <stdio.h>
18+
#include <stdint.h>
19+
#include <unistd.h>
20+
#include <asm/msr-index.h>
21+
22+
#include "kvm_util.h"
23+
#include "processor.h"
24+
#include "svm_util.h"
25+
#include "test_util.h"
26+
#include "vmx.h"
27+
28+
#define NUM_ITERATIONS 10000
29+
30+
static int open_dev_msr(int cpu)
31+
{
32+
char path[PATH_MAX];
33+
34+
snprintf(path, sizeof(path), "/dev/cpu/%d/msr", cpu);
35+
return open_path_or_exit(path, O_RDONLY);
36+
}
37+
38+
static uint64_t read_dev_msr(int msr_fd, uint32_t msr)
39+
{
40+
uint64_t data;
41+
ssize_t rc;
42+
43+
rc = pread(msr_fd, &data, sizeof(data), msr);
44+
TEST_ASSERT(rc == sizeof(data), "Read of MSR 0x%x failed", msr);
45+
46+
return data;
47+
}
48+
49+
static void guest_read_aperf_mperf(void)
50+
{
51+
int i;
52+
53+
for (i = 0; i < NUM_ITERATIONS; i++)
54+
GUEST_SYNC2(rdmsr(MSR_IA32_APERF), rdmsr(MSR_IA32_MPERF));
55+
}
56+
57+
#define L2_GUEST_STACK_SIZE 64
58+
59+
static void l2_guest_code(void)
60+
{
61+
guest_read_aperf_mperf();
62+
GUEST_DONE();
63+
}
64+
65+
static void l1_svm_code(struct svm_test_data *svm)
66+
{
67+
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
68+
struct vmcb *vmcb = svm->vmcb;
69+
70+
generic_svm_setup(svm, l2_guest_code, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
71+
run_guest(vmcb, svm->vmcb_gpa);
72+
}
73+
74+
static void l1_vmx_code(struct vmx_pages *vmx)
75+
{
76+
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
77+
78+
GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true);
79+
GUEST_ASSERT_EQ(load_vmcs(vmx), true);
80+
81+
prepare_vmcs(vmx, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
82+
83+
/*
84+
* Enable MSR bitmaps (the bitmap itself is allocated, zeroed, and set
85+
* in the VMCS by prepare_vmcs()), as MSR exiting mandatory on Intel.
86+
*/
87+
vmwrite(CPU_BASED_VM_EXEC_CONTROL,
88+
vmreadz(CPU_BASED_VM_EXEC_CONTROL) | CPU_BASED_USE_MSR_BITMAPS);
89+
90+
GUEST_ASSERT(!vmwrite(GUEST_RIP, (u64)l2_guest_code));
91+
GUEST_ASSERT(!vmlaunch());
92+
}
93+
94+
static void guest_code(void *nested_test_data)
95+
{
96+
guest_read_aperf_mperf();
97+
98+
if (this_cpu_has(X86_FEATURE_SVM))
99+
l1_svm_code(nested_test_data);
100+
else if (this_cpu_has(X86_FEATURE_VMX))
101+
l1_vmx_code(nested_test_data);
102+
else
103+
GUEST_DONE();
104+
105+
TEST_FAIL("L2 should have signaled 'done'");
106+
}
107+
108+
static void guest_no_aperfmperf(void)
109+
{
110+
uint64_t msr_val;
111+
uint8_t vector;
112+
113+
vector = rdmsr_safe(MSR_IA32_APERF, &msr_val);
114+
GUEST_ASSERT(vector == GP_VECTOR);
115+
116+
vector = rdmsr_safe(MSR_IA32_APERF, &msr_val);
117+
GUEST_ASSERT(vector == GP_VECTOR);
118+
119+
GUEST_DONE();
120+
}
121+
122+
int main(int argc, char *argv[])
123+
{
124+
const bool has_nested = kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX);
125+
uint64_t host_aperf_before, host_mperf_before;
126+
vm_vaddr_t nested_test_data_gva;
127+
struct kvm_vcpu *vcpu;
128+
struct kvm_vm *vm;
129+
int msr_fd, cpu, i;
130+
131+
/* Sanity check that APERF/MPERF are unsupported by default. */
132+
vm = vm_create_with_one_vcpu(&vcpu, guest_no_aperfmperf);
133+
vcpu_run(vcpu);
134+
TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
135+
kvm_vm_free(vm);
136+
137+
cpu = pin_self_to_any_cpu();
138+
139+
msr_fd = open_dev_msr(cpu);
140+
141+
/*
142+
* This test requires a non-standard VM initialization, because
143+
* KVM_ENABLE_CAP cannot be used on a VM file descriptor after
144+
* a VCPU has been created.
145+
*/
146+
vm = vm_create(1);
147+
148+
TEST_REQUIRE(vm_check_cap(vm, KVM_CAP_X86_DISABLE_EXITS) &
149+
KVM_X86_DISABLE_EXITS_APERFMPERF);
150+
151+
vm_enable_cap(vm, KVM_CAP_X86_DISABLE_EXITS,
152+
KVM_X86_DISABLE_EXITS_APERFMPERF);
153+
154+
vcpu = vm_vcpu_add(vm, 0, guest_code);
155+
156+
if (!has_nested)
157+
nested_test_data_gva = NONCANONICAL;
158+
else if (kvm_cpu_has(X86_FEATURE_SVM))
159+
vcpu_alloc_svm(vm, &nested_test_data_gva);
160+
else
161+
vcpu_alloc_vmx(vm, &nested_test_data_gva);
162+
163+
vcpu_args_set(vcpu, 1, nested_test_data_gva);
164+
165+
host_aperf_before = read_dev_msr(msr_fd, MSR_IA32_APERF);
166+
host_mperf_before = read_dev_msr(msr_fd, MSR_IA32_MPERF);
167+
168+
for (i = 0; i <= NUM_ITERATIONS * (1 + has_nested); i++) {
169+
uint64_t host_aperf_after, host_mperf_after;
170+
uint64_t guest_aperf, guest_mperf;
171+
struct ucall uc;
172+
173+
vcpu_run(vcpu);
174+
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
175+
176+
switch (get_ucall(vcpu, &uc)) {
177+
case UCALL_DONE:
178+
goto done;
179+
case UCALL_ABORT:
180+
REPORT_GUEST_ASSERT(uc);
181+
case UCALL_SYNC:
182+
guest_aperf = uc.args[0];
183+
guest_mperf = uc.args[1];
184+
185+
host_aperf_after = read_dev_msr(msr_fd, MSR_IA32_APERF);
186+
host_mperf_after = read_dev_msr(msr_fd, MSR_IA32_MPERF);
187+
188+
TEST_ASSERT(host_aperf_before < guest_aperf,
189+
"APERF: host_before (0x%" PRIx64 ") >= guest (0x%" PRIx64 ")",
190+
host_aperf_before, guest_aperf);
191+
TEST_ASSERT(guest_aperf < host_aperf_after,
192+
"APERF: guest (0x%" PRIx64 ") >= host_after (0x%" PRIx64 ")",
193+
guest_aperf, host_aperf_after);
194+
TEST_ASSERT(host_mperf_before < guest_mperf,
195+
"MPERF: host_before (0x%" PRIx64 ") >= guest (0x%" PRIx64 ")",
196+
host_mperf_before, guest_mperf);
197+
TEST_ASSERT(guest_mperf < host_mperf_after,
198+
"MPERF: guest (0x%" PRIx64 ") >= host_after (0x%" PRIx64 ")",
199+
guest_mperf, host_mperf_after);
200+
201+
host_aperf_before = host_aperf_after;
202+
host_mperf_before = host_mperf_after;
203+
204+
break;
205+
}
206+
}
207+
TEST_FAIL("Didn't receive UCALL_DONE\n");
208+
done:
209+
kvm_vm_free(vm);
210+
close(msr_fd);
211+
212+
return 0;
213+
}

0 commit comments

Comments
 (0)