mirror of
https://github.com/torvalds/linux.git
synced 2026-09-26 10:02:02 +02:00
KVM: selftests: Add svm_pmu_host_guest_test for Host-Only/Guest-Only bits
Add a selftest to verify KVM correctly virtualizes the AMD PMU Host-Only (bit 41) and Guest-Only (bit 40) event selector bits across all relevant SVM state transitions. The test programs 4 PMCs simultaneously with all combinations of the Host-Only and Guest-Only bits, then verifies correct counting behavior with EFER.SVME clear and set, as well as in host mode and guest mode. The test also verifies that updating Host-Only / Guest-Only bits for a PMC works as intended, and that event filtering is still respected. Signed-off-by: Jim Mattson <jmattson@google.com> Co-developed-by: Yosry Ahmed <yosry@kernel.org> Signed-off-by: Yosry Ahmed <yosry@kernel.org> Link: https://patch.msgid.link/20260610003030.2957261-6-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
This commit is contained in:
parent
0c277410f5
commit
70f4701c12
|
|
@ -120,6 +120,7 @@ TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test
|
|||
TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa
|
||||
TEST_GEN_PROGS_x86 += x86/svm_nested_pat_test
|
||||
TEST_GEN_PROGS_x86 += x86/svm_lbr_nested_state
|
||||
TEST_GEN_PROGS_x86 += x86/svm_pmu_host_guest_test
|
||||
TEST_GEN_PROGS_x86 += x86/tsc_scaling_sync
|
||||
TEST_GEN_PROGS_x86 += x86/sync_regs_test
|
||||
TEST_GEN_PROGS_x86 += x86/ucna_injection_test
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@
|
|||
#define ARCH_PERFMON_EVENTSEL_INV BIT_ULL(23)
|
||||
#define ARCH_PERFMON_EVENTSEL_CMASK GENMASK_ULL(31, 24)
|
||||
|
||||
/*
|
||||
* These are AMD-specific bits.
|
||||
*/
|
||||
#define AMD64_EVENTSEL_GUESTONLY BIT_ULL(40)
|
||||
#define AMD64_EVENTSEL_HOSTONLY BIT_ULL(41)
|
||||
|
||||
/* RDPMC control flags, Intel only. */
|
||||
#define INTEL_RDPMC_METRICS BIT_ULL(29)
|
||||
#define INTEL_RDPMC_FIXED BIT_ULL(30)
|
||||
|
|
|
|||
215
tools/testing/selftests/kvm/x86/svm_pmu_host_guest_test.c
Normal file
215
tools/testing/selftests/kvm/x86/svm_pmu_host_guest_test.c
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* KVM nested SVM PMU Host-Only/Guest-Only test
|
||||
*
|
||||
* Copyright (C) 2026, Google LLC.
|
||||
*
|
||||
* Test that KVM correctly virtualizes the AMD PMU Host-Only (bit 41) and
|
||||
* Guest-Only (bit 40) event selector bits across all SVM state
|
||||
* transitions.
|
||||
*
|
||||
* Programs 4 PMCs simultaneously with all combinations of Host-Only and
|
||||
* Guest-Only bits, then verifies correct counting behavior with different
|
||||
* combinations of EFER.SVME and host/guest mode -- as well as event filtering.
|
||||
*/
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "test_util.h"
|
||||
#include "kvm_util.h"
|
||||
#include "processor.h"
|
||||
#include "svm_util.h"
|
||||
#include "pmu.h"
|
||||
|
||||
#define EVENTSEL_RETIRED_INSNS (ARCH_PERFMON_EVENTSEL_OS | \
|
||||
ARCH_PERFMON_EVENTSEL_USR | \
|
||||
ARCH_PERFMON_EVENTSEL_ENABLE | \
|
||||
AMD_ZEN_INSTRUCTIONS_RETIRED)
|
||||
|
||||
/* PMC configurations: index corresponds to Host-Only | Guest-Only bits */
|
||||
#define PMC_NONE 0 /* Neither bit set */
|
||||
#define PMC_G 1 /* Guest-Only bit set */
|
||||
#define PMC_H 2 /* Host-Only bit set */
|
||||
#define PMC_HG 3 /* Both bits set */
|
||||
#define NR_PMCS 4
|
||||
|
||||
#define LOOP_INSNS 1000
|
||||
|
||||
static __always_inline void run_instruction_loop(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < LOOP_INSNS; i++)
|
||||
__asm__ __volatile__("nop");
|
||||
}
|
||||
|
||||
static __always_inline void read_counters(uint64_t *counts)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NR_PMCS; i++)
|
||||
counts[i] = rdmsr(MSR_F15H_PERF_CTR + 2 * i);
|
||||
}
|
||||
|
||||
static __always_inline void run_and_measure(uint64_t *deltas)
|
||||
{
|
||||
uint64_t before[NR_PMCS], after[NR_PMCS];
|
||||
int i;
|
||||
|
||||
read_counters(before);
|
||||
run_instruction_loop();
|
||||
read_counters(after);
|
||||
|
||||
for (i = 0; i < NR_PMCS; i++)
|
||||
deltas[i] = after[i] - before[i];
|
||||
}
|
||||
|
||||
static void assert_pmc_counts(uint64_t *deltas, unsigned int expected_counting)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NR_PMCS; i++) {
|
||||
if (expected_counting & BIT(i))
|
||||
GUEST_ASSERT_NE(deltas[i], 0);
|
||||
else
|
||||
GUEST_ASSERT_EQ(deltas[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t l2_deltas[NR_PMCS];
|
||||
|
||||
static void l2_guest_code(void)
|
||||
{
|
||||
run_and_measure(l2_deltas);
|
||||
vmmcall();
|
||||
}
|
||||
|
||||
static void l1_guest_code(struct svm_test_data *svm)
|
||||
{
|
||||
struct vmcb *vmcb = svm->vmcb;
|
||||
uint64_t deltas[NR_PMCS];
|
||||
uint64_t eventsel;
|
||||
int i;
|
||||
|
||||
/* Program 4 PMCs with all combinations of Host-Only/Guest-Only bits */
|
||||
for (i = 0; i < NR_PMCS; i++) {
|
||||
eventsel = EVENTSEL_RETIRED_INSNS;
|
||||
if (i & PMC_G)
|
||||
eventsel |= AMD64_EVENTSEL_GUESTONLY;
|
||||
if (i & PMC_H)
|
||||
eventsel |= AMD64_EVENTSEL_HOSTONLY;
|
||||
wrmsr(MSR_F15H_PERF_CTL + 2 * i, eventsel);
|
||||
wrmsr(MSR_F15H_PERF_CTR + 2 * i, 0);
|
||||
}
|
||||
|
||||
/* Step 1: SVME=0 - Only the counter with neither bits set counts */
|
||||
wrmsr(MSR_EFER, rdmsr(MSR_EFER) & ~EFER_SVME);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE));
|
||||
|
||||
/* Step 2: Set SVME=1 - In L1 "host mode"; Guest-Only stops */
|
||||
wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG));
|
||||
|
||||
/* Step 3: VMRUN to L2 - In "guest mode"; Host-Only stops */
|
||||
generic_svm_setup(svm, l2_guest_code);
|
||||
vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT);
|
||||
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
|
||||
GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL);
|
||||
assert_pmc_counts(l2_deltas, BIT(PMC_NONE) | BIT(PMC_G) | BIT(PMC_HG));
|
||||
|
||||
/* Step 4: After VMEXIT to L1 - Back in "host mode"; Guest-Only stops */
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG));
|
||||
|
||||
/* Step 5: Set KVM_PMU_EVENT_DENY - all counters stop */
|
||||
GUEST_SYNC(KVM_PMU_EVENT_DENY);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, 0);
|
||||
|
||||
/* Step 6: Set KVM_PMU_EVENT_ALLOW - back to all except Guest-only */
|
||||
GUEST_SYNC(KVM_PMU_EVENT_ALLOW);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG));
|
||||
|
||||
/* Step 7: Clear Host-Only for PMC_HG - counter stops in "host mode" */
|
||||
eventsel = rdmsr(MSR_F15H_PERF_CTL + 2 * PMC_HG);
|
||||
wrmsr(MSR_F15H_PERF_CTL + 2 * PMC_HG, eventsel & ~AMD64_EVENTSEL_HOSTONLY);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H));
|
||||
|
||||
/* Step 8: Restore Host-Only for PMC_HG - counter counts again */
|
||||
wrmsr(MSR_F15H_PERF_CTL + 2 * PMC_HG, eventsel);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG));
|
||||
|
||||
/* Step 9: Clear SVME - Only the counter with neither bits set counts */
|
||||
wrmsr(MSR_EFER, rdmsr(MSR_EFER) & ~EFER_SVME);
|
||||
run_and_measure(deltas);
|
||||
assert_pmc_counts(deltas, BIT(PMC_NONE));
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
static struct kvm_pmu_event_filter *alloc_event_filter(u64 event)
|
||||
{
|
||||
struct kvm_pmu_event_filter *filter;
|
||||
|
||||
filter = malloc(sizeof(*filter) + sizeof(event));
|
||||
TEST_ASSERT(filter != NULL, "Filter allocation failed");
|
||||
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
memcpy(filter->events, &event, sizeof(event));
|
||||
filter->nevents = 1;
|
||||
filter->action = KVM_PMU_EVENT_ALLOW;
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct kvm_pmu_event_filter *filter;
|
||||
struct kvm_vcpu *vcpu;
|
||||
struct kvm_vm *vm;
|
||||
struct ucall uc;
|
||||
gva_t svm_gva;
|
||||
|
||||
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
|
||||
TEST_REQUIRE(kvm_is_pmu_enabled());
|
||||
TEST_REQUIRE(kvm_is_mediated_pmu_enabled());
|
||||
|
||||
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
|
||||
|
||||
vcpu_alloc_svm(vm, &svm_gva);
|
||||
vcpu_args_set(vcpu, 1, svm_gva);
|
||||
|
||||
filter = alloc_event_filter(AMD_ZEN_INSTRUCTIONS_RETIRED);
|
||||
|
||||
for (;;) {
|
||||
vcpu_run(vcpu);
|
||||
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
|
||||
|
||||
switch (get_ucall(vcpu, &uc)) {
|
||||
case UCALL_ABORT:
|
||||
REPORT_GUEST_ASSERT(uc);
|
||||
goto done;
|
||||
case UCALL_DONE:
|
||||
goto done;
|
||||
case UCALL_SYNC:
|
||||
filter->action = uc.args[1];
|
||||
vm_ioctl(vm, KVM_SET_PMU_EVENT_FILTER, filter);
|
||||
break;
|
||||
default:
|
||||
TEST_FAIL("Unknown ucall %lu", uc.cmd);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
done:
|
||||
kvm_vm_free(vm);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user