xref: /linux/tools/testing/selftests/kvm/x86/svm_pmu_host_guest_test.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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