xref: /linux/tools/perf/util/kvm-stat-arch/kvm-stat-powerpc.c (revision b2128290c29902315e632ea59e0504d6bc9e9b42)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <errno.h>
3 #include "../kvm-stat.h"
4 #include "../parse-events.h"
5 #include "../debug.h"
6 #include "../evsel.h"
7 #include "../evlist.h"
8 #include "../pmus.h"
9 
10 #include "book3s_hv_exits.h"
11 #include "book3s_hcalls.h"
12 #include <subcmd/parse-options.h>
13 
14 #define NR_TPS 4
15 
16 define_exit_reasons_table(hv_exit_reasons, kvm_trace_symbol_exit);
17 define_exit_reasons_table(hcall_reasons, kvm_trace_symbol_hcall);
18 
19 /* Tracepoints specific to ppc_book3s_hv */
20 static const char * const ppc_book3s_hv_kvm_tp[] = {
21 	"kvm_hv:kvm_guest_enter",
22 	"kvm_hv:kvm_guest_exit",
23 	"kvm_hv:kvm_hcall_enter",
24 	"kvm_hv:kvm_hcall_exit",
25 	NULL,
26 };
27 
28 /* 1 extra placeholder for NULL */
29 static const char *__kvm_events_tp[NR_TPS + 1];
30 
31 static void hcall_event_get_key(struct perf_sample *sample,
32 				struct event_key *key)
33 {
34 	key->info = 0;
35 	key->key = perf_sample__intval(sample, "req");
36 }
37 
38 static const char *get_hcall_exit_reason(u64 exit_code)
39 {
40 	struct exit_reasons_table *tbl = hcall_reasons;
41 
42 	while (tbl->reason != NULL) {
43 		if (tbl->exit_code == exit_code)
44 			return tbl->reason;
45 		tbl++;
46 	}
47 
48 	pr_debug("Unknown hcall code: %lld\n",
49 	       (unsigned long long)exit_code);
50 	return "UNKNOWN";
51 }
52 
53 static bool hcall_event_end(struct perf_sample *sample,
54 			    struct event_key *key __maybe_unused)
55 {
56 	return evsel__name_is(sample->evsel, __kvm_events_tp[3]);
57 }
58 
59 static bool hcall_event_begin(struct perf_sample *sample, struct event_key *key)
60 {
61 	if (evsel__name_is(sample->evsel, __kvm_events_tp[2])) {
62 		hcall_event_get_key(sample, key);
63 		return true;
64 	}
65 
66 	return false;
67 }
68 static void hcall_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
69 				   struct event_key *key,
70 				   char *decode)
71 {
72 	const char *hcall_reason = get_hcall_exit_reason(key->key);
73 
74 	scnprintf(decode, KVM_EVENT_NAME_LEN, "%s", hcall_reason);
75 }
76 
77 static const struct kvm_events_ops hcall_events = {
78 	.is_begin_event = hcall_event_begin,
79 	.is_end_event = hcall_event_end,
80 	.decode_key = hcall_event_decode_key,
81 	.name = "HCALL-EVENT",
82 };
83 
84 static const struct kvm_events_ops exit_events = {
85 	.is_begin_event = exit_event_begin,
86 	.is_end_event = exit_event_end,
87 	.decode_key = exit_event_decode_key,
88 	.name = "VM-EXIT"
89 };
90 
91 static const struct kvm_reg_events_ops __kvm_reg_events_ops[] = {
92 	{ .name = "vmexit", .ops = &exit_events },
93 	{ .name = "hcall", .ops = &hcall_events },
94 	{ NULL, NULL },
95 };
96 
97 static const char * const __kvm_skip_events[] = {
98 	NULL,
99 };
100 
101 
102 static int is_tracepoint_available(const char *str, struct evlist *evlist)
103 {
104 	struct parse_events_error err;
105 	int ret;
106 
107 	parse_events_error__init(&err);
108 	ret = parse_events(evlist, str, &err);
109 	if (ret)
110 		parse_events_error__print(&err, "tracepoint");
111 	parse_events_error__exit(&err);
112 	return ret;
113 }
114 
115 static int ppc__setup_book3s_hv(struct perf_kvm_stat *kvm,
116 				struct evlist *evlist)
117 {
118 	const char * const *events_ptr;
119 	int i, nr_tp = 0, err = -1;
120 
121 	/* Check for book3s_hv tracepoints */
122 	for (events_ptr = ppc_book3s_hv_kvm_tp; *events_ptr; events_ptr++) {
123 		err = is_tracepoint_available(*events_ptr, evlist);
124 		if (err)
125 			return -1;
126 		nr_tp++;
127 	}
128 
129 	for (i = 0; i < nr_tp; i++)
130 		__kvm_events_tp[i] = ppc_book3s_hv_kvm_tp[i];
131 
132 	__kvm_events_tp[i] = NULL;
133 	kvm->exit_reasons = hv_exit_reasons;
134 	kvm->exit_reasons_isa = "HV";
135 
136 	return 0;
137 }
138 
139 /* Wrapper to setup kvm tracepoints */
140 static int ppc__setup_kvm_tp(struct perf_kvm_stat *kvm)
141 {
142 	struct evlist *evlist = evlist__new();
143 
144 	if (evlist == NULL)
145 		return -ENOMEM;
146 
147 	/* Right now, only supported on book3s_hv */
148 	return ppc__setup_book3s_hv(kvm, evlist);
149 }
150 
151 int __setup_kvm_events_tp_powerpc(struct perf_kvm_stat *kvm)
152 {
153 	return ppc__setup_kvm_tp(kvm);
154 }
155 
156 int __cpu_isa_init_powerpc(struct perf_kvm_stat *kvm)
157 {
158 	int ret;
159 
160 	ret = ppc__setup_kvm_tp(kvm);
161 	if (ret) {
162 		kvm->exit_reasons = NULL;
163 		kvm->exit_reasons_isa = NULL;
164 	}
165 
166 	return ret;
167 }
168 
169 /*
170  * In case of powerpc architecture, pmu registers are programmable
171  * by guest kernel. So monitoring guest via host may not provide
172  * valid samples with default 'cycles' event. It is better to use
173  * 'trace_imc/trace_cycles' event for guest profiling, since it
174  * can track the guest instruction pointer in the trace-record.
175  *
176  * Function to parse the arguments and return appropriate values.
177  */
178 int __kvm_add_default_arch_event_powerpc(int *argc, const char **argv)
179 {
180 	const char **tmp;
181 	bool event = false;
182 	int i, j = *argc;
183 
184 	const struct option event_options[] = {
185 		OPT_BOOLEAN('e', "event", &event, NULL),
186 		OPT_END()
187 	};
188 
189 	tmp = calloc(j + 1, sizeof(char *));
190 	if (!tmp)
191 		return -EINVAL;
192 
193 	for (i = 0; i < j; i++)
194 		tmp[i] = argv[i];
195 
196 	parse_options(j, tmp, event_options, NULL, PARSE_OPT_KEEP_UNKNOWN);
197 	if (!event) {
198 		if (perf_pmus__have_event("trace_imc", "trace_cycles")) {
199 			argv[j++] = strdup("-e");
200 			argv[j++] = strdup("trace_imc/trace_cycles/");
201 			*argc += 2;
202 		} else {
203 			free(tmp);
204 			return -EINVAL;
205 		}
206 	}
207 
208 	free(tmp);
209 	return 0;
210 }
211 
212 const char * const *__kvm_events_tp_powerpc(void)
213 {
214 	return __kvm_events_tp;
215 }
216 
217 const struct kvm_reg_events_ops *__kvm_reg_events_ops_powerpc(void)
218 {
219 	return __kvm_reg_events_ops;
220 }
221 
222 const char * const *__kvm_skip_events_powerpc(void)
223 {
224 	return __kvm_skip_events;
225 }
226