1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * KVM nested SVM PMU Host-Only/Guest-Only test 4 * 5 * Copyright (C) 2026, Google LLC. 6 * 7 * Test that KVM correctly virtualizes the AMD PMU Host-Only (bit 41) and 8 * Guest-Only (bit 40) event selector bits across all SVM state 9 * transitions. 10 * 11 * Programs 4 PMCs simultaneously with all combinations of Host-Only and 12 * Guest-Only bits, then verifies correct counting behavior with different 13 * combinations of EFER.SVME and host/guest mode -- as well as event filtering. 14 */ 15 #include <fcntl.h> 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <string.h> 19 20 #include "test_util.h" 21 #include "kvm_util.h" 22 #include "processor.h" 23 #include "svm_util.h" 24 #include "pmu.h" 25 26 #define EVENTSEL_RETIRED_INSNS (ARCH_PERFMON_EVENTSEL_OS | \ 27 ARCH_PERFMON_EVENTSEL_USR | \ 28 ARCH_PERFMON_EVENTSEL_ENABLE | \ 29 AMD_ZEN_INSTRUCTIONS_RETIRED) 30 31 /* PMC configurations: index corresponds to Host-Only | Guest-Only bits */ 32 #define PMC_NONE 0 /* Neither bit set */ 33 #define PMC_G 1 /* Guest-Only bit set */ 34 #define PMC_H 2 /* Host-Only bit set */ 35 #define PMC_HG 3 /* Both bits set */ 36 #define NR_PMCS 4 37 38 #define LOOP_INSNS 1000 39 40 static __always_inline void run_instruction_loop(void) 41 { 42 unsigned int i; 43 44 for (i = 0; i < LOOP_INSNS; i++) 45 __asm__ __volatile__("nop"); 46 } 47 48 static __always_inline void read_counters(uint64_t *counts) 49 { 50 int i; 51 52 for (i = 0; i < NR_PMCS; i++) 53 counts[i] = rdmsr(MSR_F15H_PERF_CTR + 2 * i); 54 } 55 56 static __always_inline void run_and_measure(uint64_t *deltas) 57 { 58 uint64_t before[NR_PMCS], after[NR_PMCS]; 59 int i; 60 61 read_counters(before); 62 run_instruction_loop(); 63 read_counters(after); 64 65 for (i = 0; i < NR_PMCS; i++) 66 deltas[i] = after[i] - before[i]; 67 } 68 69 static void assert_pmc_counts(uint64_t *deltas, unsigned int expected_counting) 70 { 71 int i; 72 73 for (i = 0; i < NR_PMCS; i++) { 74 if (expected_counting & BIT(i)) 75 GUEST_ASSERT_NE(deltas[i], 0); 76 else 77 GUEST_ASSERT_EQ(deltas[i], 0); 78 } 79 } 80 81 static uint64_t l2_deltas[NR_PMCS]; 82 83 static void l2_guest_code(void) 84 { 85 run_and_measure(l2_deltas); 86 vmmcall(); 87 } 88 89 static void l1_guest_code(struct svm_test_data *svm) 90 { 91 struct vmcb *vmcb = svm->vmcb; 92 uint64_t deltas[NR_PMCS]; 93 uint64_t eventsel; 94 int i; 95 96 /* Program 4 PMCs with all combinations of Host-Only/Guest-Only bits */ 97 for (i = 0; i < NR_PMCS; i++) { 98 eventsel = EVENTSEL_RETIRED_INSNS; 99 if (i & PMC_G) 100 eventsel |= AMD64_EVENTSEL_GUESTONLY; 101 if (i & PMC_H) 102 eventsel |= AMD64_EVENTSEL_HOSTONLY; 103 wrmsr(MSR_F15H_PERF_CTL + 2 * i, eventsel); 104 wrmsr(MSR_F15H_PERF_CTR + 2 * i, 0); 105 } 106 107 /* Step 1: SVME=0 - Only the counter with neither bits set counts */ 108 wrmsr(MSR_EFER, rdmsr(MSR_EFER) & ~EFER_SVME); 109 run_and_measure(deltas); 110 assert_pmc_counts(deltas, BIT(PMC_NONE)); 111 112 /* Step 2: Set SVME=1 - In L1 "host mode"; Guest-Only stops */ 113 wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME); 114 run_and_measure(deltas); 115 assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG)); 116 117 /* Step 3: VMRUN to L2 - In "guest mode"; Host-Only stops */ 118 generic_svm_setup(svm, l2_guest_code); 119 vmcb->control.intercept &= ~(1ULL << INTERCEPT_MSR_PROT); 120 121 run_guest(vmcb, svm->vmcb_gpa); 122 123 GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_VMMCALL); 124 assert_pmc_counts(l2_deltas, BIT(PMC_NONE) | BIT(PMC_G) | BIT(PMC_HG)); 125 126 /* Step 4: After VMEXIT to L1 - Back in "host mode"; Guest-Only stops */ 127 run_and_measure(deltas); 128 assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG)); 129 130 /* Step 5: Set KVM_PMU_EVENT_DENY - all counters stop */ 131 GUEST_SYNC(KVM_PMU_EVENT_DENY); 132 run_and_measure(deltas); 133 assert_pmc_counts(deltas, 0); 134 135 /* Step 6: Set KVM_PMU_EVENT_ALLOW - back to all except Guest-only */ 136 GUEST_SYNC(KVM_PMU_EVENT_ALLOW); 137 run_and_measure(deltas); 138 assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG)); 139 140 /* Step 7: Clear Host-Only for PMC_HG - counter stops in "host mode" */ 141 eventsel = rdmsr(MSR_F15H_PERF_CTL + 2 * PMC_HG); 142 wrmsr(MSR_F15H_PERF_CTL + 2 * PMC_HG, eventsel & ~AMD64_EVENTSEL_HOSTONLY); 143 run_and_measure(deltas); 144 assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H)); 145 146 /* Step 8: Restore Host-Only for PMC_HG - counter counts again */ 147 wrmsr(MSR_F15H_PERF_CTL + 2 * PMC_HG, eventsel); 148 run_and_measure(deltas); 149 assert_pmc_counts(deltas, BIT(PMC_NONE) | BIT(PMC_H) | BIT(PMC_HG)); 150 151 /* Step 9: Clear SVME - Only the counter with neither bits set counts */ 152 wrmsr(MSR_EFER, rdmsr(MSR_EFER) & ~EFER_SVME); 153 run_and_measure(deltas); 154 assert_pmc_counts(deltas, BIT(PMC_NONE)); 155 156 GUEST_DONE(); 157 } 158 159 static struct kvm_pmu_event_filter *alloc_event_filter(u64 event) 160 { 161 struct kvm_pmu_event_filter *filter; 162 163 filter = malloc(sizeof(*filter) + sizeof(event)); 164 TEST_ASSERT(filter != NULL, "Filter allocation failed"); 165 166 memset(filter, 0, sizeof(*filter)); 167 memcpy(filter->events, &event, sizeof(event)); 168 filter->nevents = 1; 169 filter->action = KVM_PMU_EVENT_ALLOW; 170 171 return filter; 172 } 173 174 int main(int argc, char *argv[]) 175 { 176 struct kvm_pmu_event_filter *filter; 177 struct kvm_vcpu *vcpu; 178 struct kvm_vm *vm; 179 struct ucall uc; 180 gva_t svm_gva; 181 182 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM)); 183 TEST_REQUIRE(kvm_is_pmu_enabled()); 184 TEST_REQUIRE(kvm_is_mediated_pmu_enabled()); 185 186 vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); 187 188 vcpu_alloc_svm(vm, &svm_gva); 189 vcpu_args_set(vcpu, 1, svm_gva); 190 191 filter = alloc_event_filter(AMD_ZEN_INSTRUCTIONS_RETIRED); 192 193 for (;;) { 194 vcpu_run(vcpu); 195 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); 196 197 switch (get_ucall(vcpu, &uc)) { 198 case UCALL_ABORT: 199 REPORT_GUEST_ASSERT(uc); 200 goto done; 201 case UCALL_DONE: 202 goto done; 203 case UCALL_SYNC: 204 filter->action = uc.args[1]; 205 vm_ioctl(vm, KVM_SET_PMU_EVENT_FILTER, filter); 206 break; 207 default: 208 TEST_FAIL("Unknown ucall %lu", uc.cmd); 209 goto done; 210 } 211 } 212 done: 213 kvm_vm_free(vm); 214 return 0; 215 } 216