Commit | Line | Data |
---|---|---|
220a0c60 CS |
1 | /* |
2 | * Hypervisor supplied "gpci" ("get performance counter info") performance | |
3 | * counter support | |
4 | * | |
5 | * Author: Cody P Schafer <cody@linux.vnet.ibm.com> | |
6 | * Copyright 2014 IBM Corporation. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License | |
10 | * as published by the Free Software Foundation; either version | |
11 | * 2 of the License, or (at your option) any later version. | |
12 | */ | |
13 | ||
14 | #define pr_fmt(fmt) "hv-gpci: " fmt | |
15 | ||
16 | #include <linux/init.h> | |
17 | #include <linux/perf_event.h> | |
18 | #include <asm/firmware.h> | |
19 | #include <asm/hvcall.h> | |
20 | #include <asm/io.h> | |
21 | ||
22 | #include "hv-gpci.h" | |
23 | #include "hv-common.h" | |
24 | ||
25 | /* | |
26 | * Example usage: | |
27 | * perf stat -e 'hv_gpci/counter_info_version=3,offset=0,length=8, | |
28 | * secondary_index=0,starting_index=0xffffffff,request=0x10/' ... | |
29 | */ | |
30 | ||
31 | /* u32 */ | |
32 | EVENT_DEFINE_RANGE_FORMAT(request, config, 0, 31); | |
33 | /* u32 */ | |
9e9f6010 CS |
34 | /* |
35 | * Note that starting_index, phys_processor_idx, sibling_part_id, | |
36 | * hw_chip_id, partition_id all refer to the same bit range. They | |
37 | * are basically aliases for the starting_index. The specific alias | |
38 | * used depends on the event. See REQUEST_IDX_KIND in hv-gpci-requests.h | |
39 | */ | |
220a0c60 | 40 | EVENT_DEFINE_RANGE_FORMAT(starting_index, config, 32, 63); |
9e9f6010 CS |
41 | EVENT_DEFINE_RANGE_FORMAT_LITE(phys_processor_idx, config, 32, 63); |
42 | EVENT_DEFINE_RANGE_FORMAT_LITE(sibling_part_id, config, 32, 63); | |
43 | EVENT_DEFINE_RANGE_FORMAT_LITE(hw_chip_id, config, 32, 63); | |
44 | EVENT_DEFINE_RANGE_FORMAT_LITE(partition_id, config, 32, 63); | |
45 | ||
220a0c60 CS |
46 | /* u16 */ |
47 | EVENT_DEFINE_RANGE_FORMAT(secondary_index, config1, 0, 15); | |
48 | /* u8 */ | |
49 | EVENT_DEFINE_RANGE_FORMAT(counter_info_version, config1, 16, 23); | |
50 | /* u8, bytes of data (1-8) */ | |
51 | EVENT_DEFINE_RANGE_FORMAT(length, config1, 24, 31); | |
52 | /* u32, byte offset */ | |
53 | EVENT_DEFINE_RANGE_FORMAT(offset, config1, 32, 63); | |
54 | ||
55 | static struct attribute *format_attrs[] = { | |
56 | &format_attr_request.attr, | |
57 | &format_attr_starting_index.attr, | |
9e9f6010 CS |
58 | &format_attr_phys_processor_idx.attr, |
59 | &format_attr_sibling_part_id.attr, | |
60 | &format_attr_hw_chip_id.attr, | |
61 | &format_attr_partition_id.attr, | |
220a0c60 CS |
62 | &format_attr_secondary_index.attr, |
63 | &format_attr_counter_info_version.attr, | |
64 | ||
65 | &format_attr_offset.attr, | |
66 | &format_attr_length.attr, | |
67 | NULL, | |
68 | }; | |
69 | ||
70 | static struct attribute_group format_group = { | |
71 | .name = "format", | |
72 | .attrs = format_attrs, | |
73 | }; | |
74 | ||
9e9f6010 CS |
75 | static struct attribute_group event_group = { |
76 | .name = "events", | |
77 | .attrs = hv_gpci_event_attrs, | |
78 | }; | |
79 | ||
220a0c60 CS |
80 | #define HV_CAPS_ATTR(_name, _format) \ |
81 | static ssize_t _name##_show(struct device *dev, \ | |
82 | struct device_attribute *attr, \ | |
83 | char *page) \ | |
84 | { \ | |
85 | struct hv_perf_caps caps; \ | |
86 | unsigned long hret = hv_perf_caps_get(&caps); \ | |
87 | if (hret) \ | |
88 | return -EIO; \ | |
89 | \ | |
90 | return sprintf(page, _format, caps._name); \ | |
91 | } \ | |
92 | static struct device_attribute hv_caps_attr_##_name = __ATTR_RO(_name) | |
93 | ||
94 | static ssize_t kernel_version_show(struct device *dev, | |
95 | struct device_attribute *attr, | |
96 | char *page) | |
97 | { | |
98 | return sprintf(page, "0x%x\n", COUNTER_INFO_VERSION_CURRENT); | |
99 | } | |
100 | ||
58a685c2 | 101 | static DEVICE_ATTR_RO(kernel_version); |
220a0c60 CS |
102 | HV_CAPS_ATTR(version, "0x%x\n"); |
103 | HV_CAPS_ATTR(ga, "%d\n"); | |
104 | HV_CAPS_ATTR(expanded, "%d\n"); | |
105 | HV_CAPS_ATTR(lab, "%d\n"); | |
106 | HV_CAPS_ATTR(collect_privileged, "%d\n"); | |
107 | ||
108 | static struct attribute *interface_attrs[] = { | |
109 | &dev_attr_kernel_version.attr, | |
110 | &hv_caps_attr_version.attr, | |
111 | &hv_caps_attr_ga.attr, | |
112 | &hv_caps_attr_expanded.attr, | |
113 | &hv_caps_attr_lab.attr, | |
114 | &hv_caps_attr_collect_privileged.attr, | |
115 | NULL, | |
116 | }; | |
117 | ||
118 | static struct attribute_group interface_group = { | |
119 | .name = "interface", | |
120 | .attrs = interface_attrs, | |
121 | }; | |
122 | ||
123 | static const struct attribute_group *attr_groups[] = { | |
124 | &format_group, | |
9e9f6010 | 125 | &event_group, |
220a0c60 CS |
126 | &interface_group, |
127 | NULL, | |
128 | }; | |
129 | ||
130 | #define GPCI_MAX_DATA_BYTES \ | |
131 | (1024 - sizeof(struct hv_get_perf_counter_info_params)) | |
132 | ||
133 | static unsigned long single_gpci_request(u32 req, u32 starting_index, | |
134 | u16 secondary_index, u8 version_in, u32 offset, u8 length, | |
135 | u64 *value) | |
136 | { | |
137 | unsigned long ret; | |
138 | size_t i; | |
139 | u64 count; | |
140 | ||
141 | struct { | |
142 | struct hv_get_perf_counter_info_params params; | |
143 | uint8_t bytes[GPCI_MAX_DATA_BYTES]; | |
144 | } __packed __aligned(sizeof(uint64_t)) arg = { | |
145 | .params = { | |
146 | .counter_request = cpu_to_be32(req), | |
147 | .starting_index = cpu_to_be32(starting_index), | |
148 | .secondary_index = cpu_to_be16(secondary_index), | |
149 | .counter_info_version_in = version_in, | |
150 | } | |
151 | }; | |
152 | ||
153 | ret = plpar_hcall_norets(H_GET_PERF_COUNTER_INFO, | |
154 | virt_to_phys(&arg), sizeof(arg)); | |
155 | if (ret) { | |
156 | pr_devel("hcall failed: 0x%lx\n", ret); | |
157 | return ret; | |
158 | } | |
159 | ||
160 | /* | |
161 | * we verify offset and length are within the zeroed buffer at event | |
162 | * init. | |
163 | */ | |
164 | count = 0; | |
165 | for (i = offset; i < offset + length; i++) | |
166 | count |= arg.bytes[i] << (i - offset); | |
167 | ||
168 | *value = count; | |
169 | return ret; | |
170 | } | |
171 | ||
172 | static u64 h_gpci_get_value(struct perf_event *event) | |
173 | { | |
174 | u64 count; | |
175 | unsigned long ret = single_gpci_request(event_get_request(event), | |
176 | event_get_starting_index(event), | |
177 | event_get_secondary_index(event), | |
178 | event_get_counter_info_version(event), | |
179 | event_get_offset(event), | |
180 | event_get_length(event), | |
181 | &count); | |
182 | if (ret) | |
183 | return 0; | |
184 | return count; | |
185 | } | |
186 | ||
187 | static void h_gpci_event_update(struct perf_event *event) | |
188 | { | |
189 | s64 prev; | |
190 | u64 now = h_gpci_get_value(event); | |
191 | prev = local64_xchg(&event->hw.prev_count, now); | |
192 | local64_add(now - prev, &event->count); | |
193 | } | |
194 | ||
195 | static void h_gpci_event_start(struct perf_event *event, int flags) | |
196 | { | |
197 | local64_set(&event->hw.prev_count, h_gpci_get_value(event)); | |
198 | } | |
199 | ||
200 | static void h_gpci_event_stop(struct perf_event *event, int flags) | |
201 | { | |
202 | h_gpci_event_update(event); | |
203 | } | |
204 | ||
205 | static int h_gpci_event_add(struct perf_event *event, int flags) | |
206 | { | |
207 | if (flags & PERF_EF_START) | |
208 | h_gpci_event_start(event, flags); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int h_gpci_event_init(struct perf_event *event) | |
214 | { | |
215 | u64 count; | |
216 | u8 length; | |
217 | ||
218 | /* Not our event */ | |
219 | if (event->attr.type != event->pmu->type) | |
220 | return -ENOENT; | |
221 | ||
222 | /* config2 is unused */ | |
223 | if (event->attr.config2) { | |
224 | pr_devel("config2 set when reserved\n"); | |
225 | return -EINVAL; | |
226 | } | |
227 | ||
228 | /* unsupported modes and filters */ | |
229 | if (event->attr.exclude_user || | |
230 | event->attr.exclude_kernel || | |
231 | event->attr.exclude_hv || | |
232 | event->attr.exclude_idle || | |
233 | event->attr.exclude_host || | |
cc56d673 | 234 | event->attr.exclude_guest) |
220a0c60 CS |
235 | return -EINVAL; |
236 | ||
237 | /* no branch sampling */ | |
238 | if (has_branch_stack(event)) | |
239 | return -EOPNOTSUPP; | |
240 | ||
241 | length = event_get_length(event); | |
242 | if (length < 1 || length > 8) { | |
243 | pr_devel("length invalid\n"); | |
244 | return -EINVAL; | |
245 | } | |
246 | ||
247 | /* last byte within the buffer? */ | |
248 | if ((event_get_offset(event) + length) > GPCI_MAX_DATA_BYTES) { | |
249 | pr_devel("request outside of buffer: %zu > %zu\n", | |
250 | (size_t)event_get_offset(event) + length, | |
251 | GPCI_MAX_DATA_BYTES); | |
252 | return -EINVAL; | |
253 | } | |
254 | ||
255 | /* check if the request works... */ | |
256 | if (single_gpci_request(event_get_request(event), | |
257 | event_get_starting_index(event), | |
258 | event_get_secondary_index(event), | |
259 | event_get_counter_info_version(event), | |
260 | event_get_offset(event), | |
261 | length, | |
262 | &count)) { | |
263 | pr_devel("gpci hcall failed\n"); | |
264 | return -EINVAL; | |
265 | } | |
266 | ||
267 | return 0; | |
268 | } | |
269 | ||
220a0c60 CS |
270 | static struct pmu h_gpci_pmu = { |
271 | .task_ctx_nr = perf_invalid_context, | |
272 | ||
273 | .name = "hv_gpci", | |
274 | .attr_groups = attr_groups, | |
275 | .event_init = h_gpci_event_init, | |
276 | .add = h_gpci_event_add, | |
277 | .del = h_gpci_event_stop, | |
278 | .start = h_gpci_event_start, | |
279 | .stop = h_gpci_event_stop, | |
280 | .read = h_gpci_event_update, | |
220a0c60 CS |
281 | }; |
282 | ||
283 | static int hv_gpci_init(void) | |
284 | { | |
285 | int r; | |
286 | unsigned long hret; | |
287 | struct hv_perf_caps caps; | |
288 | ||
9e9f6010 CS |
289 | hv_gpci_assert_offsets_correct(); |
290 | ||
220a0c60 | 291 | if (!firmware_has_feature(FW_FEATURE_LPAR)) { |
0a8cf9e2 | 292 | pr_debug("not a virtualized system, not enabling\n"); |
220a0c60 CS |
293 | return -ENODEV; |
294 | } | |
295 | ||
296 | hret = hv_perf_caps_get(&caps); | |
297 | if (hret) { | |
0a8cf9e2 | 298 | pr_debug("could not obtain capabilities, not enabling, rc=%ld\n", |
220a0c60 CS |
299 | hret); |
300 | return -ENODEV; | |
301 | } | |
302 | ||
cc56d673 VW |
303 | /* sampling not supported */ |
304 | h_gpci_pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT; | |
305 | ||
220a0c60 CS |
306 | r = perf_pmu_register(&h_gpci_pmu, h_gpci_pmu.name, -1); |
307 | if (r) | |
308 | return r; | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
313 | device_initcall(hv_gpci_init); |