Commit | Line | Data |
---|---|---|
93115d32 TR |
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" | |
93115d32 TR |
25 | #include "session.h" |
26 | #include "evlist.h" | |
93115d32 TR |
27 | #include "color.h" |
28 | #include "sample-raw.h" | |
29 | #include "s390-cpumcf-kernel.h" | |
3e4a1c53 | 30 | #include "pmu-events/pmu-events.h" |
9823147d | 31 | #include "util/sample.h" |
93115d32 TR |
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 | if (!len) | |
55 | return false; | |
56 | while (offset < len) { | |
57 | cep = (struct cf_ctrset_entry *)(buf + offset); | |
58 | ce.def = be16_to_cpu(cep->def); | |
59 | ce.set = be16_to_cpu(cep->set); | |
60 | ce.ctr = be16_to_cpu(cep->ctr); | |
61 | ce.res1 = be16_to_cpu(cep->res1); | |
62 | ||
63 | if (!ctrset_valid(&ce) || offset + ctrset_size(&ce) > len) { | |
64 | /* Raw data for counter sets are always multiple of 8 | |
65 | * bytes. Prepending a 4 bytes size field to the | |
66 | * raw data block in the sample causes the perf tool | |
67 | * to append 4 padding bytes to make the raw data part | |
68 | * of the sample a multiple of eight bytes again. | |
69 | * | |
70 | * If the last entry (trailer) is 4 bytes off the raw | |
71 | * area data end, all is good. | |
72 | */ | |
73 | if (len - offset - sizeof(*te) == 4) | |
74 | break; | |
75 | pr_err("Invalid counter set entry at %zd\n", offset); | |
76 | return false; | |
77 | } | |
78 | offset += ctrset_size(&ce); | |
79 | } | |
80 | return true; | |
81 | } | |
82 | ||
83 | /* Dump event bc000 on screen, already tested on correctness. */ | |
84 | static void s390_cpumcfdg_dumptrail(const char *color, size_t offset, | |
85 | struct cf_trailer_entry *tep) | |
86 | { | |
87 | struct cf_trailer_entry te; | |
88 | ||
89 | te.flags = be64_to_cpu(tep->flags); | |
90 | te.cfvn = be16_to_cpu(tep->cfvn); | |
91 | te.csvn = be16_to_cpu(tep->csvn); | |
92 | te.cpu_speed = be32_to_cpu(tep->cpu_speed); | |
93 | te.timestamp = be64_to_cpu(tep->timestamp); | |
94 | te.progusage1 = be64_to_cpu(tep->progusage1); | |
95 | te.progusage2 = be64_to_cpu(tep->progusage2); | |
96 | te.progusage3 = be64_to_cpu(tep->progusage3); | |
97 | te.tod_base = be64_to_cpu(tep->tod_base); | |
98 | te.mach_type = be16_to_cpu(tep->mach_type); | |
99 | te.res1 = be16_to_cpu(tep->res1); | |
100 | te.res2 = be32_to_cpu(tep->res2); | |
101 | ||
102 | color_fprintf(stdout, color, " [%#08zx] Trailer:%c%c%c%c%c" | |
103 | " Cfvn:%d Csvn:%d Speed:%d TOD:%#llx\n", | |
104 | offset, te.clock_base ? 'T' : ' ', | |
105 | te.speed ? 'S' : ' ', te.mtda ? 'M' : ' ', | |
106 | te.caca ? 'C' : ' ', te.lcda ? 'L' : ' ', | |
107 | te.cfvn, te.csvn, te.cpu_speed, te.timestamp); | |
108 | color_fprintf(stdout, color, "\t\t1:%lx 2:%lx 3:%lx TOD-Base:%#llx" | |
109 | " Type:%x\n\n", | |
110 | te.progusage1, te.progusage2, te.progusage3, | |
111 | te.tod_base, te.mach_type); | |
112 | } | |
113 | ||
3e4a1c53 TR |
114 | /* Return starting number of a counter set */ |
115 | static int get_counterset_start(int setnr) | |
116 | { | |
117 | switch (setnr) { | |
118 | case CPUMF_CTR_SET_BASIC: /* Basic counter set */ | |
119 | return 0; | |
120 | case CPUMF_CTR_SET_USER: /* Problem state counter set */ | |
121 | return 32; | |
122 | case CPUMF_CTR_SET_CRYPTO: /* Crypto counter set */ | |
123 | return 64; | |
124 | case CPUMF_CTR_SET_EXT: /* Extended counter set */ | |
125 | return 128; | |
126 | case CPUMF_CTR_SET_MT_DIAG: /* Diagnostic counter set */ | |
127 | return 448; | |
128 | default: | |
129 | return -1; | |
130 | } | |
131 | } | |
132 | ||
660842e4 IR |
133 | struct get_counter_name_data { |
134 | int wanted; | |
135 | const char *result; | |
136 | }; | |
137 | ||
138 | static int get_counter_name_callback(const struct pmu_event *evp, | |
1ba3752a | 139 | const struct pmu_events_table *table __maybe_unused, |
660842e4 IR |
140 | void *vdata) |
141 | { | |
142 | struct get_counter_name_data *data = vdata; | |
143 | int rc, event_nr; | |
144 | ||
145 | if (evp->name == NULL || evp->event == NULL) | |
146 | return 0; | |
147 | rc = sscanf(evp->event, "event=%x", &event_nr); | |
148 | if (rc == 1 && event_nr == data->wanted) { | |
149 | data->result = evp->name; | |
150 | return 1; /* Terminate the search. */ | |
151 | } | |
152 | return 0; | |
153 | } | |
154 | ||
3e4a1c53 TR |
155 | /* Scan the PMU table and extract the logical name of a counter from the |
156 | * PMU events table. Input is the counter set and counter number with in the | |
157 | * set. Construct the event number and use this as key. If they match return | |
158 | * the name of this counter. | |
159 | * If no match is found a NULL pointer is returned. | |
160 | */ | |
1ba3752a | 161 | static const char *get_counter_name(int set, int nr, const struct pmu_events_table *table) |
3e4a1c53 | 162 | { |
660842e4 IR |
163 | struct get_counter_name_data data = { |
164 | .wanted = get_counterset_start(set) + nr, | |
165 | .result = NULL, | |
166 | }; | |
3e4a1c53 | 167 | |
660842e4 IR |
168 | if (!table) |
169 | return NULL; | |
3e4a1c53 | 170 | |
660842e4 IR |
171 | pmu_events_table_for_each_event(table, get_counter_name_callback, &data); |
172 | return data.result; | |
3e4a1c53 TR |
173 | } |
174 | ||
93115d32 TR |
175 | static void s390_cpumcfdg_dump(struct perf_sample *sample) |
176 | { | |
177 | size_t i, len = sample->raw_size, offset = 0; | |
178 | unsigned char *buf = sample->raw_data; | |
179 | const char *color = PERF_COLOR_BLUE; | |
180 | struct cf_ctrset_entry *cep, ce; | |
1ba3752a | 181 | const struct pmu_events_table *table; |
93115d32 TR |
182 | u64 *p; |
183 | ||
eeac7730 | 184 | table = pmu_events_table__find(); |
93115d32 TR |
185 | while (offset < len) { |
186 | cep = (struct cf_ctrset_entry *)(buf + offset); | |
187 | ||
188 | ce.def = be16_to_cpu(cep->def); | |
189 | ce.set = be16_to_cpu(cep->set); | |
190 | ce.ctr = be16_to_cpu(cep->ctr); | |
191 | ce.res1 = be16_to_cpu(cep->res1); | |
192 | ||
193 | if (!ctrset_valid(&ce)) { /* Print trailer */ | |
194 | s390_cpumcfdg_dumptrail(color, offset, | |
195 | (struct cf_trailer_entry *)cep); | |
196 | return; | |
197 | } | |
198 | ||
199 | color_fprintf(stdout, color, " [%#08zx] Counterset:%d" | |
200 | " Counters:%d\n", offset, ce.set, ce.ctr); | |
3e4a1c53 | 201 | for (i = 0, p = (u64 *)(cep + 1); i < ce.ctr; ++i, ++p) { |
eeac7730 | 202 | const char *ev_name = get_counter_name(ce.set, i, table); |
3e4a1c53 | 203 | |
93115d32 | 204 | color_fprintf(stdout, color, |
3e4a1c53 TR |
205 | "\tCounter:%03d %s Value:%#018lx\n", i, |
206 | ev_name ?: "<unknown>", be64_to_cpu(*p)); | |
207 | } | |
93115d32 TR |
208 | offset += ctrset_size(&ce); |
209 | } | |
210 | } | |
211 | ||
212 | /* S390 specific trace event function. Check for PERF_RECORD_SAMPLE events | |
213 | * and if the event was triggered by a counter set diagnostic event display | |
214 | * its raw data. | |
215 | * The function is only invoked when the dump flag -D is set. | |
216 | */ | |
44d2a557 | 217 | void evlist__s390_sample_raw(struct evlist *evlist, union perf_event *event, struct perf_sample *sample) |
93115d32 | 218 | { |
32dcd021 | 219 | struct evsel *ev_bc000; |
93115d32 TR |
220 | |
221 | if (event->header.type != PERF_RECORD_SAMPLE) | |
222 | return; | |
223 | ||
3ccf8a7b | 224 | ev_bc000 = evlist__event2evsel(evlist, event); |
93115d32 | 225 | if (ev_bc000 == NULL || |
1fc632ce | 226 | ev_bc000->core.attr.config != PERF_EVENT_CPUM_CF_DIAG) |
93115d32 TR |
227 | return; |
228 | ||
229 | /* Display raw data on screen */ | |
230 | if (!s390_cpumcfdg_testctr(sample)) { | |
231 | pr_err("Invalid counter set data encountered\n"); | |
232 | return; | |
233 | } | |
234 | s390_cpumcfdg_dump(sample); | |
235 | } |