1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright IBM Corp. 2019 |
4 | * Author(s): Thomas Richter <tmricht@linux.ibm.com> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License (version 2 only) |
8 | * as published by the Free Software Foundation. |
9 | * |
10 | * Architecture specific trace_event function. Save event's bc000 raw data |
11 | * to file. File name is aux.ctr.## where ## stands for the CPU number the |
12 | * sample was taken from. |
13 | */ |
14 | |
15 | #include <unistd.h> |
16 | #include <stdio.h> |
17 | #include <string.h> |
18 | #include <inttypes.h> |
19 | |
20 | #include <sys/stat.h> |
21 | #include <linux/compiler.h> |
22 | #include <asm/byteorder.h> |
23 | |
24 | #include "debug.h" |
25 | #include "session.h" |
26 | #include "evlist.h" |
27 | #include "color.h" |
28 | #include "sample-raw.h" |
29 | #include "s390-cpumcf-kernel.h" |
30 | #include "util/pmu.h" |
31 | #include "util/sample.h" |
32 | |
33 | static size_t ctrset_size(struct cf_ctrset_entry *set) |
34 | { |
35 | return sizeof(*set) + set->ctr * sizeof(u64); |
36 | } |
37 | |
38 | static bool ctrset_valid(struct cf_ctrset_entry *set) |
39 | { |
40 | return set->def == S390_CPUMCF_DIAG_DEF; |
41 | } |
42 | |
43 | /* CPU Measurement Counter Facility raw data is a byte stream. It is 8 byte |
44 | * aligned and might have trailing padding bytes. |
45 | * Display the raw data on screen. |
46 | */ |
47 | static bool s390_cpumcfdg_testctr(struct perf_sample *sample) |
48 | { |
49 | size_t len = sample->raw_size, offset = 0; |
50 | unsigned char *buf = sample->raw_data; |
51 | struct cf_trailer_entry *te; |
52 | struct cf_ctrset_entry *cep, ce; |
53 | |
54 | while (offset < len) { |
55 | cep = (struct cf_ctrset_entry *)(buf + offset); |
56 | ce.def = be16_to_cpu(cep->def); |
57 | ce.set = be16_to_cpu(cep->set); |
58 | ce.ctr = be16_to_cpu(cep->ctr); |
59 | ce.res1 = be16_to_cpu(cep->res1); |
60 | |
61 | if (!ctrset_valid(set: &ce) || offset + ctrset_size(set: &ce) > len) { |
62 | /* Raw data for counter sets are always multiple of 8 |
63 | * bytes. Prepending a 4 bytes size field to the |
64 | * raw data block in the sample causes the perf tool |
65 | * to append 4 padding bytes to make the raw data part |
66 | * of the sample a multiple of eight bytes again. |
67 | * |
68 | * If the last entry (trailer) is 4 bytes off the raw |
69 | * area data end, all is good. |
70 | */ |
71 | if (len - offset - sizeof(*te) == 4) |
72 | break; |
73 | pr_err("Invalid counter set entry at %zd\n" , offset); |
74 | return false; |
75 | } |
76 | offset += ctrset_size(set: &ce); |
77 | } |
78 | return true; |
79 | } |
80 | |
81 | /* Dump event bc000 on screen, already tested on correctness. */ |
82 | static void s390_cpumcfdg_dumptrail(const char *color, size_t offset, |
83 | struct cf_trailer_entry *tep) |
84 | { |
85 | struct cf_trailer_entry te; |
86 | |
87 | te.flags = be64_to_cpu(tep->flags); |
88 | te.cfvn = be16_to_cpu(tep->cfvn); |
89 | te.csvn = be16_to_cpu(tep->csvn); |
90 | te.cpu_speed = be32_to_cpu(tep->cpu_speed); |
91 | te.timestamp = be64_to_cpu(tep->timestamp); |
92 | te.progusage1 = be64_to_cpu(tep->progusage1); |
93 | te.progusage2 = be64_to_cpu(tep->progusage2); |
94 | te.progusage3 = be64_to_cpu(tep->progusage3); |
95 | te.tod_base = be64_to_cpu(tep->tod_base); |
96 | te.mach_type = be16_to_cpu(tep->mach_type); |
97 | te.res1 = be16_to_cpu(tep->res1); |
98 | te.res2 = be32_to_cpu(tep->res2); |
99 | |
100 | color_fprintf(stdout, color, " [%#08zx] Trailer:%c%c%c%c%c" |
101 | " Cfvn:%d Csvn:%d Speed:%d TOD:%#llx\n" , |
102 | offset, te.clock_base ? 'T' : ' ', |
103 | te.speed ? 'S' : ' ', te.mtda ? 'M' : ' ', |
104 | te.caca ? 'C' : ' ', te.lcda ? 'L' : ' ', |
105 | te.cfvn, te.csvn, te.cpu_speed, te.timestamp); |
106 | color_fprintf(stdout, color, "\t\t1:%lx 2:%lx 3:%lx TOD-Base:%#llx" |
107 | " Type:%x\n\n" , |
108 | te.progusage1, te.progusage2, te.progusage3, |
109 | te.tod_base, te.mach_type); |
110 | } |
111 | |
112 | /* Return starting number of a counter set */ |
113 | static int get_counterset_start(int setnr) |
114 | { |
115 | switch (setnr) { |
116 | case CPUMF_CTR_SET_BASIC: /* Basic counter set */ |
117 | return 0; |
118 | case CPUMF_CTR_SET_USER: /* Problem state counter set */ |
119 | return 32; |
120 | case CPUMF_CTR_SET_CRYPTO: /* Crypto counter set */ |
121 | return 64; |
122 | case CPUMF_CTR_SET_EXT: /* Extended counter set */ |
123 | return 128; |
124 | case CPUMF_CTR_SET_MT_DIAG: /* Diagnostic counter set */ |
125 | return 448; |
126 | case PERF_EVENT_PAI_NNPA_ALL: /* PAI NNPA counter set */ |
127 | case PERF_EVENT_PAI_CRYPTO_ALL: /* PAI CRYPTO counter set */ |
128 | return setnr; |
129 | default: |
130 | return -1; |
131 | } |
132 | } |
133 | |
134 | struct get_counter_name_data { |
135 | int wanted; |
136 | char *result; |
137 | }; |
138 | |
139 | static int get_counter_name_callback(void *vdata, struct pmu_event_info *info) |
140 | { |
141 | struct get_counter_name_data *data = vdata; |
142 | int rc, event_nr; |
143 | const char *event_str; |
144 | |
145 | if (info->str == NULL) |
146 | return 0; |
147 | |
148 | event_str = strstr(info->str, "event=" ); |
149 | if (!event_str) |
150 | return 0; |
151 | |
152 | rc = sscanf(event_str, "event=%x" , &event_nr); |
153 | if (rc == 1 && event_nr == data->wanted) { |
154 | data->result = strdup(info->name); |
155 | return 1; /* Terminate the search. */ |
156 | } |
157 | return 0; |
158 | } |
159 | |
160 | /* Scan the PMU and extract the logical name of a counter from the event. Input |
161 | * is the counter set and counter number with in the set. Construct the event |
162 | * number and use this as key. If they match return the name of this counter. |
163 | * If no match is found a NULL pointer is returned. |
164 | */ |
165 | static char *get_counter_name(int set, int nr, struct perf_pmu *pmu) |
166 | { |
167 | struct get_counter_name_data data = { |
168 | .wanted = get_counterset_start(setnr: set) + nr, |
169 | .result = NULL, |
170 | }; |
171 | |
172 | if (!pmu) |
173 | return NULL; |
174 | |
175 | perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/ true, |
176 | state: &data, cb: get_counter_name_callback); |
177 | return data.result; |
178 | } |
179 | |
180 | static void s390_cpumcfdg_dump(struct perf_pmu *pmu, struct perf_sample *sample) |
181 | { |
182 | size_t i, len = sample->raw_size, offset = 0; |
183 | unsigned char *buf = sample->raw_data; |
184 | const char *color = PERF_COLOR_BLUE; |
185 | struct cf_ctrset_entry *cep, ce; |
186 | u64 *p; |
187 | |
188 | while (offset < len) { |
189 | cep = (struct cf_ctrset_entry *)(buf + offset); |
190 | |
191 | ce.def = be16_to_cpu(cep->def); |
192 | ce.set = be16_to_cpu(cep->set); |
193 | ce.ctr = be16_to_cpu(cep->ctr); |
194 | ce.res1 = be16_to_cpu(cep->res1); |
195 | |
196 | if (!ctrset_valid(set: &ce)) { /* Print trailer */ |
197 | s390_cpumcfdg_dumptrail(color, offset, |
198 | tep: (struct cf_trailer_entry *)cep); |
199 | return; |
200 | } |
201 | |
202 | color_fprintf(stdout, color, " [%#08zx] Counterset:%d" |
203 | " Counters:%d\n" , offset, ce.set, ce.ctr); |
204 | for (i = 0, p = (u64 *)(cep + 1); i < ce.ctr; ++i, ++p) { |
205 | char *ev_name = get_counter_name(set: ce.set, nr: i, pmu); |
206 | |
207 | color_fprintf(stdout, color, |
208 | "\tCounter:%03d %s Value:%#018lx\n" , i, |
209 | ev_name ?: "<unknown>" , be64_to_cpu(*p)); |
210 | free(ev_name); |
211 | } |
212 | offset += ctrset_size(set: &ce); |
213 | } |
214 | } |
215 | |
216 | #pragma GCC diagnostic push |
217 | #pragma GCC diagnostic ignored "-Wpacked" |
218 | #pragma GCC diagnostic ignored "-Wattributes" |
219 | /* |
220 | * Check for consistency of PAI_CRYPTO/PAI_NNPA raw data. |
221 | */ |
222 | struct pai_data { /* Event number and value */ |
223 | u16 event_nr; |
224 | u64 event_val; |
225 | } __packed; |
226 | |
227 | #pragma GCC diagnostic pop |
228 | |
229 | /* |
230 | * Test for valid raw data. At least one PAI event should be in the raw |
231 | * data section. |
232 | */ |
233 | static bool s390_pai_all_test(struct perf_sample *sample) |
234 | { |
235 | size_t len = sample->raw_size; |
236 | |
237 | if (len < 0xa) |
238 | return false; |
239 | return true; |
240 | } |
241 | |
242 | static void s390_pai_all_dump(struct evsel *evsel, struct perf_sample *sample) |
243 | { |
244 | size_t len = sample->raw_size, offset = 0; |
245 | unsigned char *p = sample->raw_data; |
246 | const char *color = PERF_COLOR_BLUE; |
247 | struct pai_data pai_data; |
248 | char *ev_name; |
249 | |
250 | while (offset < len) { |
251 | memcpy(&pai_data.event_nr, p, sizeof(pai_data.event_nr)); |
252 | pai_data.event_nr = be16_to_cpu(pai_data.event_nr); |
253 | p += sizeof(pai_data.event_nr); |
254 | offset += sizeof(pai_data.event_nr); |
255 | |
256 | memcpy(&pai_data.event_val, p, sizeof(pai_data.event_val)); |
257 | pai_data.event_val = be64_to_cpu(pai_data.event_val); |
258 | p += sizeof(pai_data.event_val); |
259 | offset += sizeof(pai_data.event_val); |
260 | |
261 | ev_name = get_counter_name(set: evsel->core.attr.config, |
262 | nr: pai_data.event_nr, pmu: evsel->pmu); |
263 | color_fprintf(stdout, color, "\tCounter:%03d %s Value:%#018lx\n" , |
264 | pai_data.event_nr, ev_name ?: "<unknown>" , |
265 | pai_data.event_val); |
266 | free(ev_name); |
267 | |
268 | if (offset + 0xa > len) |
269 | break; |
270 | } |
271 | color_fprintf(stdout, color, "\n" ); |
272 | } |
273 | |
274 | /* S390 specific trace event function. Check for PERF_RECORD_SAMPLE events |
275 | * and if the event was triggered by a |
276 | * - counter set diagnostic event |
277 | * - processor activity assist (PAI) crypto counter event |
278 | * - processor activity assist (PAI) neural network processor assist (NNPA) |
279 | * counter event |
280 | * display its raw data. |
281 | * The function is only invoked when the dump flag -D is set. |
282 | * |
283 | * Function evlist__s390_sample_raw() is defined as call back after it has |
284 | * been verified that the perf.data file was created on s390 platform. |
285 | */ |
286 | void evlist__s390_sample_raw(struct evlist *evlist, union perf_event *event, |
287 | struct perf_sample *sample) |
288 | { |
289 | const char *pai_name; |
290 | struct evsel *evsel; |
291 | |
292 | if (event->header.type != PERF_RECORD_SAMPLE) |
293 | return; |
294 | |
295 | evsel = evlist__event2evsel(evlist, event); |
296 | if (!evsel) |
297 | return; |
298 | |
299 | /* Check for raw data in sample */ |
300 | if (!sample->raw_size || !sample->raw_data) |
301 | return; |
302 | |
303 | /* Display raw data on screen */ |
304 | if (evsel->core.attr.config == PERF_EVENT_CPUM_CF_DIAG) { |
305 | if (!evsel->pmu) |
306 | evsel->pmu = perf_pmus__find(name: "cpum_cf" ); |
307 | if (!s390_cpumcfdg_testctr(sample)) |
308 | pr_err("Invalid counter set data encountered\n" ); |
309 | else |
310 | s390_cpumcfdg_dump(pmu: evsel->pmu, sample); |
311 | return; |
312 | } |
313 | |
314 | switch (evsel->core.attr.config) { |
315 | case PERF_EVENT_PAI_NNPA_ALL: |
316 | pai_name = "NNPA_ALL" ; |
317 | break; |
318 | case PERF_EVENT_PAI_CRYPTO_ALL: |
319 | pai_name = "CRYPTO_ALL" ; |
320 | break; |
321 | default: |
322 | return; |
323 | } |
324 | |
325 | if (!s390_pai_all_test(sample)) { |
326 | pr_err("Invalid %s raw data encountered\n" , pai_name); |
327 | } else { |
328 | if (!evsel->pmu) |
329 | evsel->pmu = perf_pmus__find_by_type(type: evsel->core.attr.type); |
330 | s390_pai_all_dump(evsel, sample); |
331 | } |
332 | } |
333 | |