// SPDX-License-Identifier: GPL-2.0
/*
* Test for x86 KVM_SET_PMU_EVENT_FILTER.
*
* Copyright (C) 2022, Google LLC.
*
* This work is licensed under the terms of the GNU GPL, version 2.
*
* Verifies the expected behavior of allow lists and deny lists for
* virtual PMU events.
*/
#include "kvm_util.h"
#include "pmu.h"
#include "processor.h"
#include "test_util.h"
#define NUM_BRANCHES 42
#define MAX_TEST_EVENTS 10
#define PMU_EVENT_FILTER_INVALID_ACTION (KVM_PMU_EVENT_DENY + 1)
#define PMU_EVENT_FILTER_INVALID_FLAGS (KVM_PMU_EVENT_FLAGS_VALID_MASK << 1)
#define PMU_EVENT_FILTER_INVALID_NEVENTS (KVM_PMU_EVENT_FILTER_MAX_EVENTS + 1)
struct __kvm_pmu_event_filter {
__u32 action;
__u32 nevents;
__u32 fixed_counter_bitmap;
__u32 flags;
__u32 pad[4];
__u64 events[KVM_PMU_EVENT_FILTER_MAX_EVENTS];
};
/*
* This event list comprises Intel's known architectural events, plus AMD's
* Branch Instructions Retired for Zen CPUs. Note, AMD and Intel use the
* same encoding for Instructions Retired.
*/
kvm_static_assert(INTEL_ARCH_INSTRUCTIONS_RETIRED == AMD_ZEN_INSTRUCTIONS_RETIRED);
static const struct __kvm_pmu_event_filter base_event_filter = {
.nevents = ARRAY_SIZE(base_event_filter.events),
.events = {
INTEL_ARCH_CPU_CYCLES,
INTEL_ARCH_INSTRUCTIONS_RETIRED,
INTEL_ARCH_REFERENCE_CYCLES,
INTEL_ARCH_LLC_REFERENCES,
INTEL_ARCH_LLC_MISSES,
INTEL_ARCH_BRANCHES_RETIRED,
INTEL_ARCH_BRANCHES_MISPREDICTED,
INTEL_ARCH_TOPDOWN_SLOTS,
AMD_ZEN_BRANCHES_RETIRED,
},
};
struct {
uint64_t loads;
uint64_t stores;
uint64_t loads_stores;
uint64_t branches_retired;
uint64_t instructions_retired;
} pmc_results;
/*
* If we encounter a #GP during the guest PMU sanity check, then the guest
* PMU is not functional. Inform the hypervisor via GUEST_SYNC(0).
*/
static void guest_gp_handler(struct ex_regs *regs)
{
GUEST_SYNC(-EFAULT);
}
/*
* Check that we can write a new value to the given MSR and read it back.
* The caller should provide a non-empty set of bits that are safe to flip.
*
* Return on success. GUEST_SYNC(0) on error.
*/
static void check_msr(uint32_t msr, uint64_t bits_to_flip)
{
uint64_t v = rdmsr(msr) ^ bits_to_flip;
wrmsr(msr, v);
if (rdmsr(msr) != v)
GUEST_SYNC(-EIO);
v ^= bits_to_flip;
wrmsr(msr, v);
if (rdmsr(msr) != v)
GUEST_SYNC(-EIO);
}
static void run_and_measure_loop(uint32_t msr_base)
{
const uint64_t branches_retired = rdmsr(msr_base + 0);
const uint64_t insn_retired = rdmsr(msr_base + 1);
__asm__ __volatile__("loop ." : "+c"((int){NUM_BRANCHES}));
pmc_results.branches_retired = rdmsr(msr_base + 0) - branches_retired;
pmc_results.instructions_retired = rdmsr(msr_base + 1) - insn_retired;
}
static void intel_guest_code(void)
{
check_msr(MSR_CORE_PERF_GLOBAL_CTRL, 1);
check_msr(MSR_P6_EVNTSEL0, 0xffff);
check_msr(MSR_IA32_PMC0, 0xffff);
GUEST_SYNC(0);
for (;;) {
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0);
wrmsr(MSR_P6_EVNTSEL0, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | INTEL_ARCH_BRANCHES_RETIRED);
wrmsr(MSR_P6_EVNTSEL1, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | INTEL_ARCH_INSTRUCTIONS_RETIRED);
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0x3);
run_and_measure_loop(MSR_IA32_PMC0);
GUEST_SYNC(0);
}
}
/*
* To avoid needing a check for CPUID.80000001:ECX.PerfCtrExtCore[bit 23],
* this code uses the always-available, legacy K7 PMU MSRs, which alias to
* the first four of the six extended core PMU MSRs.
*/
static void amd_guest_code(void)
{
check_msr(MSR_K7_EVNTSEL0, 0xffff);
check_msr(MSR_K7_PERFCTR0, 0xffff);
GUEST_SYNC(0);
for (;;) {
wrmsr(MSR_K7_EVNTSEL0, 0);
wrmsr(MSR_K7_EVNTSEL0, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | AMD_ZEN_BRANCHES_RETIRED);
wrmsr(MSR_K7_EVNTSEL1, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | AMD_ZEN_INSTRUCTIONS_RETIRED);
run_and_measure_loop(MSR_K7_PERFCTR0);
GUEST_SYNC(0);
}
}
/*
* Run the VM to the next GUEST_SYNC(value), and return the value passed
* to the sync. Any other exit from the guest is fatal.
*/
static uint64_t run_vcpu_to_sync(struct kvm_vcpu *vcpu)
{
struct ucall uc;
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
get_ucall(vcpu, &uc);
TEST_ASSERT(uc.cmd == UCALL_SYNC,
"Received ucall other than UCALL_SYNC: %lu", uc.cmd);
return uc.args[1];
}
static void run_vcpu_and_sync_pmc_results(struct kvm_vcpu *vcpu)
{
uint64_t r;
memset(&pmc_results, 0, sizeof(pmc_results));
sync_global_to_guest(vcpu->vm, pmc_results);
r = run_vcpu_to_sync(vcpu);
TEST_ASSERT(!r, "Unexpected sync value: 0x%lx", r);
sync_global_from_guest(vcpu->vm, pmc_results);
}
/*
* In a nested environment or if the vPMU is disabled, the guest PMU
* might not work as architected (accessing the PMU MSRs may raise
* #GP, or writes could simply be discarded). In those situations,
* there is no point in running these tests. The guest code will perform
* a sanity check and then GUEST_SYNC(success). In the case of failure,
* the behavior of the guest on resumption is undefined.
*/
static bool sanity_check_pmu(struct kvm_vcpu *vcpu)
{
uint64_t r;
vm_install_exception_handler(vcpu->vm, GP_VECTOR, guest_gp_handler);
r = run_vcpu_to_sync(vcpu);
vm_install_exception_handler(vcpu->vm, GP_VECTOR, NULL);
return !r;
}
/*
* Remove the first occurrence of 'event' (if any) from the filter's
* event list.
*/
static void remove_event(struct __kvm_pmu_event_filter *f, uint64_t event)
{
bool found = false;
int i;
for (i = 0; i < f->nevents; i++) {
if (found)
f->events[i - 1] = f->events[i];
else
found = f->events[i] == event;
}
if (found)
f->nevents--;
}
#define ASSERT_PMC_COUNTING_INSTRUCTIONS() \
do { \
uint64_t br = pmc_results.branches_retired; \
uint64_t ir = pmc_results.instructions_retired; \
\
if (br && br != NUM_BRANCHES) \
pr_info("%s: Branch instructions retired = %lu (expected %u)\n", \
__func__, br, NUM_BRANCHES); \
TEST_ASSERT(br, "%s: Branch instructions retired = %lu (expected > 0)", \
__func__, br); \
TEST_ASSERT(ir, "%s: Instructions retired = %lu (expected > 0)", \
__func__, ir); \
} while (0)
#define ASSERT_PMC_NOT_COUNTING_INSTRUCTIONS() \
do { \
uint64_t br = pmc_results.branches_retired; \
uint64_t ir = pmc_results.instructions_retired; \
\
TEST_ASSERT(!br, "%s: Branch instructions retired = %lu (expected 0)", \
__func__, br); \
TEST_ASSERT(!ir, "%s: Instructions retired = %lu (expected 0)", \
__func__, ir); \
} while (0)
static void test_without_filter(struct kvm_vcpu *vcpu)
{
run_vcpu_and_sync_pmc_results(vcpu);
ASSERT_PMC_COUNTING_INSTRUCTIONS();
}
static void test_with_filter(struct kvm_vcpu *vcpu,
struct __kvm_pmu_event_filter *__f)
{
struct kvm_pmu_event_filter *f = (void *)__f;
vm_ioctl(vcpu->vm, KVM_SET_PMU_EVENT_FILTER, f);
run_vcpu_and_sync_pmc_results(vcpu);
}
static void test_amd_deny_list(struct kvm_vcpu *vcpu)
{
struct __kvm_pmu_event_filter f = {
.action = KVM_PMU_EVENT_DENY,
.nevents = 1,
.events = {
RAW_EVENT(0x1C2, 0),
},
};
test_with_filter(vcpu, &f);
ASSERT_PMC_COUNTING_INSTRUCTIONS();
}
static void test_member_deny_list(struct kvm_vcpu *vcpu)
{
struct __kvm_pmu_event_filter f = base_event_filter;
f.action = KVM_PMU_EVENT_DENY;
test_with_filter(vcpu, &f);
ASSERT_PMC_NOT_COUNTING_INSTRUCTIONS();
}
static void test_member_allow_list(struct kvm_vcpu *vcpu)
{
struct __kvm_pmu_event_filter f = base_event_filter;
f.action = KVM_PMU_EVENT_ALLOW;
test_with_filter(vcpu, &f);
ASSERT_PMC_COUNTING_INSTRUCTIONS();
}
static void test_not_member_deny_list(struct kvm_vcpu *vcpu)
{
struct __kvm_pmu_event_filter f = base_event_filter;
f.action = KVM_PMU_EVENT_DENY;
remove_event(&f, INTEL_ARCH_INSTRUCTIONS_RETIRED);
remove_event(&f, INTEL_ARCH_BRANCHES_RETIRED);
remove_event(&f, AMD_ZEN_BRANCHES_RETIRED);
test_with_filter(vcpu, &f);
ASSERT_PMC_COUNTING_INSTRUCTIONS();
}
static void test_not_member_allow_list(struct kvm_vcpu *vcpu)
{
struct __kvm_pmu_event_filter f = base_event_filter;
f.action = KVM_PMU_EVENT_ALLOW;
remove_event(&f, INTEL_ARCH_INSTRUCTIONS_RETIRED);
remove_event(&f, INTEL_ARCH_BRANCHES_RETIRED);
remove_event(&f, AMD_ZEN_BRANCHES_RETIRED);
test_with_filter(vcpu, &f);
ASSERT_PMC_NOT_COUNTING_INSTRUCTIONS();
}
/*
* Verify that setting KVM_PMU_CAP_DISABLE prevents the use of the PMU.
*
* Note that KVM_CAP_PMU_CAPABILITY must be invoked prior to creating VCPUs.
*/
static void test_pmu_config_disable(void (*guest_code)(void))
{
struct kvm_vcpu *vcpu;
int r;
struct kvm_vm *vm;
r = kvm_check_cap(KVM_CAP_PMU_CAPABILITY);
if (!(r & KVM_PMU_CAP_DISABLE))
return;
vm = vm_create(1);
vm_enable_cap(vm, KVM_CAP_PMU_CAPABILITY, KVM_PMU_CAP_DISABLE);
vcpu = vm_vcpu_add(vm, 0, guest_code);
TEST_ASSERT(!sanity_check_pmu(vcpu),
"Guest should not be able to use disabled PMU.");
kvm_vm_free(vm);
}
/*
* On Intel, check for a non-zero PMU version, at least one general-purpose
* counter per logical processor, and support for counting the number of branch
* instructions retired.
*/
static bool use_intel_pmu(void)
{
return host_cpu_is_intel &&
kvm_cpu_property(X86_PROPERTY_PMU_VERSION) &&
kvm_cpu_property(X86_PROPERTY_PMU_NR_GP_COUNTERS) &&
kvm_pmu_has(X86_PMU_FEATURE_BRANCH_INSNS_RETIRED);
}
/*
* On AMD, all Family 17h+ CPUs (Zen and its successors) use event encoding
* 0xc2,0 for Branch Instructions Retired.
*/
static bool use_amd_pmu(void)
{
return host_cpu_is_amd && kvm_cpu_family() >= 0x17;
}
/*
* "MEM_INST_RETIRED.ALL_LOADS", "MEM_INST_RETIRED.ALL_STORES", and
* "MEM_INST_RETIRED.ANY" from https://perfmon-events.intel.com/
* supported on Intel Xeon processors:
* - Sapphire Rapids, Ice Lake, Cascade Lake, Skylake.
*/
#define MEM_INST_RETIRED 0xD0
#define MEM_INST_RETIRED_LOAD RAW_EVENT(MEM_INST_RETIRED, 0x81)
#define MEM_INST_RETIRED_STORE RAW_EVENT(MEM_INST_RETIRED, 0x82)
#define MEM_INST_RETIRED_LOAD_STORE RAW_EVENT(MEM_INST_RETIRED, 0x83)
static bool supports_event_mem_inst_retired(void)
{
uint32_t eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
if (x86_family(eax) == 0x6) {
switch (x86_model(eax)) {
/* Sapphire Rapids */
case 0x8F:
/* Ice Lake */
case 0x6A:
/* Skylake */
/* Cascade Lake */
case 0x55:
return true;
}
}
return false;
}
/*
* "LS Dispatch", from Processor Programming Reference
* (PPR) for AMD Family 17h Model 01h, Revision B1 Processors,
* Preliminary Processor Programming Reference (PPR) for AMD Family
* 17h Model 31h, Revision B0 Processors, and Preliminary Processor
* Programming Reference (PPR) for AMD Family 19h Model 01h, Revision
* B1 Processors Volume 1 of 2.
*/
#define LS_DISPATCH 0x29
#define LS_DISPATCH_LOAD RAW_EVENT(LS_DISPATCH, BIT(0))
#define LS_DISPATCH_STORE RAW_EVENT(LS_DISPATCH, BIT(1))
#define LS_DISPATCH_LOAD_STORE RAW_EVENT(LS_DISPATCH, BIT(2))
#define INCLUDE_MASKED_ENTRY(event_select, mask, match) \
KVM_PMU_ENCODE_MASKED_ENTRY(event_select, mask, match, false)
#define EXCLUDE_MASKED_ENTRY(event_select, mask, match) \
KVM_PMU_ENCODE_MASKED_ENTRY(event_select, mask, match, true)
static void masked_events_guest_test(uint32_t msr_base)
{
/*
* The actual value of the counters don't determine the outcome of
* the test. Only that they are zero or non-zero.
*/
const uint64_t loads = rdmsr(msr_base + 0);
const uint64_t stores = rdmsr(msr_base + 1);
const uint64_t loads_stores = rdmsr(msr_base + 2);
int val;
__asm__ __volatile__("movl $0, %[v];"
"movl %[v], %%eax;"
"incl %[v];"
: [v]"+m"(val) :: "eax");
pmc_results.loads = rdmsr(msr_base + 0) - loads;
pmc_results.stores = rdmsr(msr_base + 1) - stores;
pmc_results.loads_stores = rdmsr(msr_base + 2) - loads_stores;
}
static void intel_masked_events_guest_code(void)
{
for (;;) {
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0);
wrmsr(MSR_P6_EVNTSEL0 + 0, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | MEM_INST_RETIRED_LOAD);
wrmsr(MSR_P6_EVNTSEL0 + 1, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | MEM_INST_RETIRED_STORE);
wrmsr(MSR_P6_EVNTSEL0 + 2, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | MEM_INST_RETIRED_LOAD_STORE);
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0x7);
masked_events_guest_test(MSR_IA32_PMC0);
GUEST_SYNC(0);
}
}
static void amd_masked_events_guest_code(void)
{
for (;;) {
wrmsr(MSR_K7_EVNTSEL0, 0);
wrmsr(MSR_K7_EVNTSEL1, 0);
wrmsr(MSR_K7_EVNTSEL2, 0);
wrmsr(MSR_K7_EVNTSEL0, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | LS_DISPATCH_LOAD);
wrmsr(MSR_K7_EVNTSEL1, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | LS_DISPATCH_STORE);
wrmsr(MSR_K7_EVNTSEL2, ARCH_PERFMON_EVENTSEL_ENABLE |
ARCH_PERFMON_EVENTSEL_OS | LS_DISPATCH_LOAD_STORE);
masked_events_guest_test(MSR_K7_PERFCTR0);
GUEST_SYNC(0);
}
}
static void run_masked_events_test(struct kvm_vcpu *vcpu,
const uint64_t masked_events[],
const int nmasked_events)
{
struct __kvm_pmu_event_filter f = {
.nevents = nmasked_events,
.action = KVM_PMU_EVENT_ALLOW,
.flags = KVM_PMU_EVENT_FLAG_MASKED_EVENTS,
};
memcpy(f.events, masked_events, sizeof(uint64_t) * nmasked_events);
test_with_filter(vcpu, &f);
}
#define ALLOW_LOADS BIT(0)
#define ALLOW_STORES BIT(1)
#define ALLOW_LOADS_STORES BIT(2)
struct masked_events_test {
uint64_t intel_events[MAX_TEST_EVENTS];
uint64_t intel_event_end;
uint64_t amd_events[MAX_TEST_EVENTS];
uint64_t amd_event_end;
const char *msg;
uint32_t flags;
};
/*
* These are the test cases for the masked events tests.
*
* For each test, the guest enables 3 PMU counters (loads, stores,
* loads + stores). The filter is then set in KVM with the masked events
* provided. The test then verifies that the counters agree with which
* ones should be counting and which ones should be filtered.
*/
const struct masked_events_test test_cases[] = {
{
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0xFF, 0x81),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xFF, BIT(0)),
},
.msg = "Only allow loads.",
.flags = ALLOW_LOADS,
}, {
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0xFF, 0x82),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xFF, BIT(1)),
},
.msg = "Only allow stores.",
.flags = ALLOW_STORES,
}, {
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0xFF, 0x83),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xFF, BIT(2)),
},
.msg = "Only allow loads + stores.",
.flags = ALLOW_LOADS_STORES,
}, {
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0x7C, 0),
EXCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0xFF, 0x83),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, ~(BIT(0) | BIT(1)), 0),
},
.msg = "Only allow loads and stores.",
.flags = ALLOW_LOADS | ALLOW_STORES,
}, {
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0x7C, 0),
EXCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0xFF, 0x82),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xF8, 0),
EXCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xFF, BIT(1)),
},
.msg = "Only allow loads and loads + stores.",
.flags = ALLOW_LOADS | ALLOW_LOADS_STORES
}, {
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0xFE, 0x82),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xF8, 0),
EXCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xFF, BIT(0)),
},
.msg = "Only allow stores and loads + stores.",
.flags = ALLOW_STORES | ALLOW_LOADS_STORES
}, {
.intel_events = {
INCLUDE_MASKED_ENTRY(MEM_INST_RETIRED, 0x7C, 0),
},
.amd_events = {
INCLUDE_MASKED_ENTRY(LS_DISPATCH, 0xF8, 0),
},
.msg = "Only allow loads, stores, and loads + stores.",
.flags = ALLOW_LOADS | ALLOW_STORES | ALLOW_LOADS_STORES
},
};
static int append_test_events(const struct masked_events_test *test,
uint64_t *events, int nevents)
{
const uint64_t *evts;
int i;
evts = use_intel_pmu() ? test->intel_events : test->amd_events;
for (i = 0; i < MAX_TEST_EVENTS; i++) {
if (evts[i] == 0)
break;
events[nevents + i] = evts[i];
}
return nevents + i;
}
static bool bool_eq(bool a, bool b)
{
return a == b;
}
static void run_masked_events_tests(struct kvm_vcpu *vcpu, uint64_t *events,
int nevents)
{
int ntests = ARRAY_SIZE(test_cases);
int i, n;
for (i = 0; i < ntests; i++) {
const struct masked_events_test *test = &test_cases[i];
/* Do any test case events overflow MAX_TEST_EVENTS? */
assert(test->intel_event_end == 0);
assert(test->amd_event_end == 0);
n = append_test_events(test, events, nevents);
run_masked_events_test(vcpu, events, n);
TEST_ASSERT(bool_eq(pmc_results.loads, test->flags & ALLOW_LOADS) &&
bool_eq(pmc_results.stores, test->flags & ALLOW_STORES) &&
bool_eq(pmc_results.loads_stores,
test->flags & ALLOW_LOADS_STORES),
"%s loads: %lu, stores: %lu, loads + stores: %lu",
test->msg, pmc_results.loads, pmc_results.stores,
pmc_results.loads_stores);
}
}
static void add_dummy_events(uint64_t *events, int nevents)
{
int i;
for (i = 0; i < nevents; i++) {
int event_select = i % 0xFF;
bool exclude = ((i % 4) == 0);
if (event_select == MEM_INST_RETIRED ||
event_select == LS_DISPATCH)
event_select++;
events[i] = KVM_PMU_ENCODE_MASKED_ENTRY(event_select, 0,
0, exclude);
}
}
static void test_masked_events(struct kvm_vcpu *vcpu)
{
int nevents = KVM_PMU_EVENT_FILTER_MAX_EVENTS - MAX_TEST_EVENTS;
uint64_t events[KVM_PMU_EVENT_FILTER_MAX_EVENTS];
/* Run the test cases against a sparse PMU event filter. */
run_masked_events_tests(vcpu, events, 0);
/* Run the test cases against a dense PMU event filter. */
add_dummy_events(events, KVM_PMU_EVENT_FILTER_MAX_EVENTS);
run_masked_events_tests(vcpu, events, nevents);
}
static int set_pmu_event_filter(struct kvm_vcpu *vcpu,
struct __kvm_pmu_event_filter *__f)
{
struct kvm_pmu_event_filter *f = (void *)__f;
return __vm_ioctl(vcpu->vm, KVM_SET_PMU_EVENT_FILTER, f);
}
static int set_pmu_single_event_filter(struct kvm_vcpu *vcpu, uint64_t event,
uint32_t flags, uint32_t action)
{
struct __kvm_pmu_event_filter f = {
.nevents = 1,
.flags = flags,
.action = action,
.events = {
event,
},
};
return set_pmu_event_filter(vcpu, &f);
}
static void test_filter_ioctl(struct kvm_vcpu *vcpu)
{
uint8_t nr_fixed_counters = kvm_cpu_property(X86_PROPERTY_PMU_NR_FIXED_COUNTERS);
struct __kvm_pmu_event_filter f;
uint64_t e = ~0ul;
int r;
/*
* Unfortunately having invalid bits set in event data is expected to
* pass when flags == 0 (bits other than eventsel+umask).
*/
r = set_pmu_single_event_filter(vcpu, e, 0, KVM_PMU_EVENT_ALLOW);
TEST_ASSERT(r == 0, "Valid PMU Event Filter is failing");
r = set_pmu_single_event_filter(vcpu, e,
KVM_PMU_EVENT_FLAG_MASKED_EVENTS,
KVM_PMU_EVENT_ALLOW);
TEST_ASSERT(r != 0, "Invalid PMU Event Filter is expected to fail");
e = KVM_PMU_ENCODE_MASKED_ENTRY(0xff, 0xff, 0xff, 0xf);
r = set_pmu_single_event_filter(vcpu, e,
KVM_PMU_EVENT_FLAG_MASKED_EVENTS,
KVM_PMU_EVENT_ALLOW);
TEST_ASSERT(r == 0, "Valid PMU Event Filter is failing");
f = base_event_filter;
f.action = PMU_EVENT_FILTER_INVALID_ACTION;
r = set_pmu_event_filter(vcpu, &f);
TEST_ASSERT(r, "Set invalid action is expected to fail");
f = base_event_filter;
f.flags = PMU_EVENT_FILTER_INVALID_FLAGS;
r = set_pmu_event_filter(vcpu, &f);
TEST_ASSERT(r, "Set invalid flags is expected to fail");
f = base_event_filter;
f.nevents = PMU_EVENT_FILTER_INVALID_NEVENTS;
r = set_pmu_event_filter(vcpu, &f);
TEST_ASSERT(r, "Exceeding the max number of filter events should fail");
f = base_event_filter;
f.fixed_counter_bitmap = ~GENMASK_ULL(nr_fixed_counters, 0);
r = set_pmu_event_filter(vcpu, &f);
TEST_ASSERT(!r, "Masking non-existent fixed counters should be allowed");
}
static void intel_run_fixed_counter_guest_code(uint8_t idx)
{
for (;;) {
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0);
wrmsr(MSR_CORE_PERF_FIXED_CTR0 + idx, 0);
/* Only OS_EN bit is enabled for fixed counter[idx]. */
wrmsr(MSR_CORE_PERF_FIXED_CTR_CTRL, FIXED_PMC_CTRL(idx, FIXED_PMC_KERNEL));
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, FIXED_PMC_GLOBAL_CTRL_ENABLE(idx));
__asm__ __volatile__("loop ." : "+c"((int){NUM_BRANCHES}));
wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, 0);
GUEST_SYNC(rdmsr(MSR_CORE_PERF_FIXED_CTR0 + idx));
}
}
static uint64_t test_with_fixed_counter_filter(struct kvm_vcpu *vcpu,
uint32_t action, uint32_t bitmap)
{
struct __kvm_pmu_event_filter f = {
.action = action,
.fixed_counter_bitmap = bitmap,
};
set_pmu_event_filter(vcpu, &f);
return run_vcpu_to_sync(vcpu);
}
static uint64_t test_set_gp_and_fixed_event_filter(struct kvm_vcpu *vcpu,
uint32_t action,
uint32_t bitmap)
{
struct __kvm_pmu_event_filter f = base_event_filter;
f.action = action;
f.fixed_counter_bitmap = bitmap;
set_pmu_event_filter(vcpu, &f);
return run_vcpu_to_sync(vcpu);
}
static void __test_fixed_counter_bitmap(struct kvm_vcpu *vcpu, uint8_t idx,
uint8_t nr_fixed_counters)
{
unsigned int i;
uint32_t bitmap;
uint64_t count;
TEST_ASSERT(nr_fixed_counters < sizeof(bitmap) * 8,
"Invalid nr_fixed_counters");
/*
* Check the fixed performance counter can count normally when KVM
* userspace doesn't set any pmu filter.
*/
count = run_vcpu_to_sync(vcpu);
TEST_ASSERT(count, "Unexpected count value: %ld", count);
for (i = 0; i < BIT(nr_fixed_counters); i++) {
bitmap = BIT(i);
count = test_with_fixed_counter_filter(vcpu, KVM_PMU_EVENT_ALLOW,
bitmap);
TEST_ASSERT_EQ(!!count, !!(bitmap & BIT(idx)));
count = test_with_fixed_counter_filter(vcpu, KVM_PMU_EVENT_DENY,
bitmap);
TEST_ASSERT_EQ(!!count, !(bitmap & BIT(idx)));
/*
* Check that fixed_counter_bitmap has higher priority than
* events[] when both are set.
*/
count = test_set_gp_and_fixed_event_filter(vcpu,
KVM_PMU_EVENT_ALLOW,
bitmap);
TEST_ASSERT_EQ(!!count, !!(bitmap & BIT(idx)));
count = test_set_gp_and_fixed_event_filter(vcpu,
KVM_PMU_EVENT_DENY,
bitmap);
TEST_ASSERT_EQ(!!count, !(bitmap & BIT(idx)));
}
}
static void test_fixed_counter_bitmap(void)
{
uint8_t nr_fixed_counters = kvm_cpu_property(X86_PROPERTY_PMU_NR_FIXED_COUNTERS);
struct kvm_vm *vm;
struct kvm_vcpu *vcpu;
uint8_t idx;
/*
* Check that pmu_event_filter works as expected when it's applied to
* fixed performance counters.
*/
for (idx = 0; idx < nr_fixed_counters; idx++) {
vm = vm_create_with_one_vcpu(&vcpu,
intel_run_fixed_counter_guest_code);
vcpu_args_set(vcpu, 1, idx);
__test_fixed_counter_bitmap(vcpu, idx, nr_fixed_counters);
kvm_vm_free(vm);
}
}
int main(int argc, char *argv[])
{
void (*guest_code)(void);
struct kvm_vcpu *vcpu, *vcpu2 = NULL;
struct kvm_vm *vm;
TEST_REQUIRE(kvm_is_pmu_enabled());
TEST_REQUIRE(kvm_has_cap(KVM_CAP_PMU_EVENT_FILTER));
TEST_REQUIRE(kvm_has_cap(KVM_CAP_PMU_EVENT_MASKED_EVENTS));
TEST_REQUIRE(use_intel_pmu() || use_amd_pmu());
guest_code = use_intel_pmu() ? intel_guest_code : amd_guest_code;
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
TEST_REQUIRE(sanity_check_pmu(vcpu));
if (use_amd_pmu())
test_amd_deny_list(vcpu);
test_without_filter(vcpu);
test_member_deny_list(vcpu);
test_member_allow_list(vcpu);
test_not_member_deny_list(vcpu);
test_not_member_allow_list(vcpu);
if (use_intel_pmu() &&
supports_event_mem_inst_retired() &&
kvm_cpu_property(X86_PROPERTY_PMU_NR_GP_COUNTERS) >= 3)
vcpu2 = vm_vcpu_add(vm, 2, intel_masked_events_guest_code);
else if (use_amd_pmu())
vcpu2 = vm_vcpu_add(vm, 2, amd_masked_events_guest_code);
if (vcpu2)
test_masked_events(vcpu2);
test_filter_ioctl(vcpu);
kvm_vm_free(vm);
test_pmu_config_disable(guest_code);
test_fixed_counter_bitmap();
return 0;
}