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