xref: /linux/tools/perf/util/kvm-stat-arch/kvm-stat-x86.c (revision c7decec2f2d2ab0366567f9e30c0e1418cece43f)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <errno.h>
3 #include <string.h>
4 #include "../kvm-stat.h"
5 #include "../evsel.h"
6 #include "../env.h"
7 #include "../../arch/x86/include/uapi/asm/svm.h"
8 #include "../../arch/x86/include/uapi/asm/vmx.h"
9 #include "../../arch/x86/include/uapi/asm/kvm.h"
10 #include <subcmd/parse-options.h>
11 
12 define_exit_reasons_table(vmx_exit_reasons, VMX_EXIT_REASONS);
13 define_exit_reasons_table(svm_exit_reasons, SVM_EXIT_REASONS);
14 
15 static const struct kvm_events_ops exit_events = {
16 	.is_begin_event = exit_event_begin,
17 	.is_end_event = exit_event_end,
18 	.decode_key = exit_event_decode_key,
19 	.name = "VM-EXIT"
20 };
21 
22 /*
23  * For the mmio events, we treat:
24  * the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry
25  * the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...).
26  */
mmio_event_get_key(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)27 static void mmio_event_get_key(struct evsel *evsel, struct perf_sample *sample,
28 			       struct event_key *key)
29 {
30 	key->key  = evsel__intval(evsel, sample, "gpa");
31 	key->info = evsel__intval(evsel, sample, "type");
32 }
33 
34 #define KVM_TRACE_MMIO_READ_UNSATISFIED 0
35 #define KVM_TRACE_MMIO_READ 1
36 #define KVM_TRACE_MMIO_WRITE 2
37 
mmio_event_begin(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)38 static bool mmio_event_begin(struct evsel *evsel,
39 			     struct perf_sample *sample, struct event_key *key)
40 {
41 	/* MMIO read begin event in kernel. */
42 	if (kvm_exit_event(evsel))
43 		return true;
44 
45 	/* MMIO write begin event in kernel. */
46 	if (evsel__name_is(evsel, "kvm:kvm_mmio") &&
47 	    evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_WRITE) {
48 		mmio_event_get_key(evsel, sample, key);
49 		return true;
50 	}
51 
52 	return false;
53 }
54 
mmio_event_end(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)55 static bool mmio_event_end(struct evsel *evsel, struct perf_sample *sample,
56 			   struct event_key *key)
57 {
58 	/* MMIO write end event in kernel. */
59 	if (kvm_entry_event(evsel))
60 		return true;
61 
62 	/* MMIO read end event in kernel.*/
63 	if (evsel__name_is(evsel, "kvm:kvm_mmio") &&
64 	    evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_READ) {
65 		mmio_event_get_key(evsel, sample, key);
66 		return true;
67 	}
68 
69 	return false;
70 }
71 
mmio_event_decode_key(struct perf_kvm_stat * kvm __maybe_unused,struct event_key * key,char * decode)72 static void mmio_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
73 				  struct event_key *key,
74 				  char *decode)
75 {
76 	scnprintf(decode, KVM_EVENT_NAME_LEN, "%#lx:%s",
77 		  (unsigned long)key->key,
78 		  key->info == KVM_TRACE_MMIO_WRITE ? "W" : "R");
79 }
80 
81 static const struct kvm_events_ops mmio_events = {
82 	.is_begin_event = mmio_event_begin,
83 	.is_end_event = mmio_event_end,
84 	.decode_key = mmio_event_decode_key,
85 	.name = "MMIO Access"
86 };
87 
88  /* The time of emulation pio access is from kvm_pio to kvm_entry. */
ioport_event_get_key(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)89 static void ioport_event_get_key(struct evsel *evsel,
90 				 struct perf_sample *sample,
91 				 struct event_key *key)
92 {
93 	key->key  = evsel__intval(evsel, sample, "port");
94 	key->info = evsel__intval(evsel, sample, "rw");
95 }
96 
ioport_event_begin(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)97 static bool ioport_event_begin(struct evsel *evsel,
98 			       struct perf_sample *sample,
99 			       struct event_key *key)
100 {
101 	if (evsel__name_is(evsel, "kvm:kvm_pio")) {
102 		ioport_event_get_key(evsel, sample, key);
103 		return true;
104 	}
105 
106 	return false;
107 }
108 
ioport_event_end(struct evsel * evsel,struct perf_sample * sample __maybe_unused,struct event_key * key __maybe_unused)109 static bool ioport_event_end(struct evsel *evsel,
110 			     struct perf_sample *sample __maybe_unused,
111 			     struct event_key *key __maybe_unused)
112 {
113 	return kvm_entry_event(evsel);
114 }
115 
ioport_event_decode_key(struct perf_kvm_stat * kvm __maybe_unused,struct event_key * key,char * decode)116 static void ioport_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
117 				    struct event_key *key,
118 				    char *decode)
119 {
120 	scnprintf(decode, KVM_EVENT_NAME_LEN, "%#llx:%s",
121 		  (unsigned long long)key->key,
122 		  key->info ? "POUT" : "PIN");
123 }
124 
125 static const struct kvm_events_ops ioport_events = {
126 	.is_begin_event = ioport_event_begin,
127 	.is_end_event = ioport_event_end,
128 	.decode_key = ioport_event_decode_key,
129 	.name = "IO Port Access"
130 };
131 
132  /* The time of emulation msr is from kvm_msr to kvm_entry. */
msr_event_get_key(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)133 static void msr_event_get_key(struct evsel *evsel,
134 				 struct perf_sample *sample,
135 				 struct event_key *key)
136 {
137 	key->key  = evsel__intval(evsel, sample, "ecx");
138 	key->info = evsel__intval(evsel, sample, "write");
139 }
140 
msr_event_begin(struct evsel * evsel,struct perf_sample * sample,struct event_key * key)141 static bool msr_event_begin(struct evsel *evsel,
142 			       struct perf_sample *sample,
143 			       struct event_key *key)
144 {
145 	if (evsel__name_is(evsel, "kvm:kvm_msr")) {
146 		msr_event_get_key(evsel, sample, key);
147 		return true;
148 	}
149 
150 	return false;
151 }
152 
msr_event_end(struct evsel * evsel,struct perf_sample * sample __maybe_unused,struct event_key * key __maybe_unused)153 static bool msr_event_end(struct evsel *evsel,
154 			     struct perf_sample *sample __maybe_unused,
155 			     struct event_key *key __maybe_unused)
156 {
157 	return kvm_entry_event(evsel);
158 }
159 
msr_event_decode_key(struct perf_kvm_stat * kvm __maybe_unused,struct event_key * key,char * decode)160 static void msr_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
161 				    struct event_key *key,
162 				    char *decode)
163 {
164 	scnprintf(decode, KVM_EVENT_NAME_LEN, "%#llx:%s",
165 		  (unsigned long long)key->key,
166 		  key->info ? "W" : "R");
167 }
168 
169 static const struct kvm_events_ops msr_events = {
170 	.is_begin_event = msr_event_begin,
171 	.is_end_event = msr_event_end,
172 	.decode_key = msr_event_decode_key,
173 	.name = "MSR Access"
174 };
175 
176 static const char * const __kvm_events_tp[] = {
177 	"kvm:kvm_entry",
178 	"kvm:kvm_exit",
179 	"kvm:kvm_mmio",
180 	"kvm:kvm_pio",
181 	"kvm:kvm_msr",
182 	NULL,
183 };
184 
185 static const struct kvm_reg_events_ops __kvm_reg_events_ops[] = {
186 	{ .name = "vmexit", .ops = &exit_events },
187 	{ .name = "mmio", .ops = &mmio_events },
188 	{ .name = "ioport", .ops = &ioport_events },
189 	{ .name = "msr", .ops = &msr_events },
190 	{ NULL, NULL },
191 };
192 
193 static const char * const __kvm_skip_events[] = {
194 	"HLT",
195 	NULL,
196 };
197 
__cpu_isa_init_x86(struct perf_kvm_stat * kvm,const char * cpuid)198 int __cpu_isa_init_x86(struct perf_kvm_stat *kvm, const char *cpuid)
199 {
200 	if (strstr(cpuid, "Intel")) {
201 		kvm->exit_reasons = vmx_exit_reasons;
202 		kvm->exit_reasons_isa = "VMX";
203 	} else if (strstr(cpuid, "AMD") || strstr(cpuid, "Hygon")) {
204 		kvm->exit_reasons = svm_exit_reasons;
205 		kvm->exit_reasons_isa = "SVM";
206 	} else
207 		return -ENOTSUP;
208 
209 	return 0;
210 }
211 
212 /*
213  * After KVM supports PEBS for guest on Intel platforms
214  * (https://lore.kernel.org/all/20220411101946.20262-1-likexu@tencent.com/),
215  * host loses the capability to sample guest with PEBS since all PEBS related
216  * MSRs are switched to guest value after vm-entry, like IA32_DS_AREA MSR is
217  * switched to guest GVA at vm-entry. This would lead to "perf kvm record"
218  * fails to sample guest on Intel platforms since "cycles:P" event is used to
219  * sample guest by default.
220  *
221  * So, to avoid this issue explicitly use "cycles" instead of "cycles:P" event
222  * by default to sample guest on Intel platforms.
223  */
__kvm_add_default_arch_event_x86(int * argc,const char ** argv)224 int __kvm_add_default_arch_event_x86(int *argc, const char **argv)
225 {
226 	const char **tmp;
227 	bool event = false;
228 	int ret = 0, i, j = *argc;
229 
230 	const struct option event_options[] = {
231 		OPT_BOOLEAN('e', "event", &event, NULL),
232 		OPT_BOOLEAN(0, "pfm-events", &event, NULL),
233 		OPT_END()
234 	};
235 
236 	if (!x86__is_intel_cpu())
237 		return 0;
238 
239 	tmp = calloc(j + 1, sizeof(char *));
240 	if (!tmp)
241 		return -ENOMEM;
242 
243 	for (i = 0; i < j; i++)
244 		tmp[i] = argv[i];
245 
246 	parse_options(j, tmp, event_options, NULL, PARSE_OPT_KEEP_UNKNOWN);
247 	if (!event) {
248 		argv[j++] = STRDUP_FAIL_EXIT("-e");
249 		argv[j++] = STRDUP_FAIL_EXIT("cycles");
250 		*argc += 2;
251 	}
252 
253 	free(tmp);
254 	return 0;
255 
256 EXIT:
257 	free(tmp);
258 	return ret;
259 }
260 
__kvm_events_tp_x86(void)261 const char * const *__kvm_events_tp_x86(void)
262 {
263 	return __kvm_events_tp;
264 }
265 
__kvm_reg_events_ops_x86(void)266 const struct kvm_reg_events_ops *__kvm_reg_events_ops_x86(void)
267 {
268 	return __kvm_reg_events_ops;
269 }
270 
__kvm_skip_events_x86(void)271 const char * const *__kvm_skip_events_x86(void)
272 {
273 	return __kvm_skip_events;
274 }
275