Commit | Line | Data |
---|---|---|
212188a5 HB |
1 | /* |
2 | * Performance event support for s390x | |
3 | * | |
443e802b | 4 | * Copyright IBM Corp. 2012, 2013 |
212188a5 HB |
5 | * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com> |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License (version 2 only) | |
9 | * as published by the Free Software Foundation. | |
10 | */ | |
11 | #define KMSG_COMPONENT "perf" | |
12 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
13 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/perf_event.h> | |
b764bb1c | 16 | #include <linux/kvm_host.h> |
212188a5 HB |
17 | #include <linux/percpu.h> |
18 | #include <linux/export.h> | |
99c64b66 | 19 | #include <linux/seq_file.h> |
8c069ff4 | 20 | #include <linux/spinlock.h> |
c7168325 | 21 | #include <linux/sysfs.h> |
212188a5 HB |
22 | #include <asm/irq.h> |
23 | #include <asm/cpu_mf.h> | |
24 | #include <asm/lowcore.h> | |
25 | #include <asm/processor.h> | |
99c64b66 | 26 | #include <asm/sysinfo.h> |
212188a5 HB |
27 | |
28 | const char *perf_pmu_name(void) | |
29 | { | |
30 | if (cpum_cf_avail() || cpum_sf_avail()) | |
99c64b66 | 31 | return "CPU-Measurement Facilities (CPU-MF)"; |
212188a5 HB |
32 | return "pmu"; |
33 | } | |
34 | EXPORT_SYMBOL(perf_pmu_name); | |
35 | ||
36 | int perf_num_counters(void) | |
37 | { | |
38 | int num = 0; | |
39 | ||
40 | if (cpum_cf_avail()) | |
41 | num += PERF_CPUM_CF_MAX_CTR; | |
8c069ff4 HB |
42 | if (cpum_sf_avail()) |
43 | num += PERF_CPUM_SF_MAX_CTR; | |
212188a5 HB |
44 | |
45 | return num; | |
46 | } | |
47 | EXPORT_SYMBOL(perf_num_counters); | |
48 | ||
b764bb1c HG |
49 | static struct kvm_s390_sie_block *sie_block(struct pt_regs *regs) |
50 | { | |
51 | struct stack_frame *stack = (struct stack_frame *) regs->gprs[15]; | |
52 | ||
53 | if (!stack) | |
54 | return NULL; | |
55 | ||
56 | return (struct kvm_s390_sie_block *) stack->empty1[0]; | |
57 | } | |
58 | ||
59 | static bool is_in_guest(struct pt_regs *regs) | |
60 | { | |
b764bb1c HG |
61 | if (user_mode(regs)) |
62 | return false; | |
61aa4884 | 63 | #if IS_ENABLED(CONFIG_KVM) |
198b1bf8 HC |
64 | return instruction_pointer(regs) == (unsigned long) &sie_exit; |
65 | #else | |
66 | return false; | |
67 | #endif | |
b764bb1c HG |
68 | } |
69 | ||
70 | static unsigned long guest_is_user_mode(struct pt_regs *regs) | |
71 | { | |
72 | return sie_block(regs)->gpsw.mask & PSW_MASK_PSTATE; | |
73 | } | |
74 | ||
75 | static unsigned long instruction_pointer_guest(struct pt_regs *regs) | |
76 | { | |
9cb1ccec | 77 | return sie_block(regs)->gpsw.addr; |
b764bb1c HG |
78 | } |
79 | ||
80 | unsigned long perf_instruction_pointer(struct pt_regs *regs) | |
81 | { | |
82 | return is_in_guest(regs) ? instruction_pointer_guest(regs) | |
83 | : instruction_pointer(regs); | |
84 | } | |
85 | ||
86 | static unsigned long perf_misc_guest_flags(struct pt_regs *regs) | |
87 | { | |
88 | return guest_is_user_mode(regs) ? PERF_RECORD_MISC_GUEST_USER | |
89 | : PERF_RECORD_MISC_GUEST_KERNEL; | |
90 | } | |
91 | ||
443e802b HB |
92 | static unsigned long perf_misc_flags_sf(struct pt_regs *regs) |
93 | { | |
94 | struct perf_sf_sde_regs *sde_regs; | |
95 | unsigned long flags; | |
96 | ||
97 | sde_regs = (struct perf_sf_sde_regs *) ®s->int_parm_long; | |
98 | if (sde_regs->in_guest) | |
99 | flags = user_mode(regs) ? PERF_RECORD_MISC_GUEST_USER | |
100 | : PERF_RECORD_MISC_GUEST_KERNEL; | |
101 | else | |
102 | flags = user_mode(regs) ? PERF_RECORD_MISC_USER | |
103 | : PERF_RECORD_MISC_KERNEL; | |
104 | return flags; | |
105 | } | |
106 | ||
b764bb1c HG |
107 | unsigned long perf_misc_flags(struct pt_regs *regs) |
108 | { | |
443e802b HB |
109 | /* Check if the cpum_sf PMU has created the pt_regs structure. |
110 | * In this case, perf misc flags can be easily extracted. Otherwise, | |
111 | * do regular checks on the pt_regs content. | |
112 | */ | |
113 | if (regs->int_code == 0x1407 && regs->int_parm == CPU_MF_INT_SF_PRA) | |
114 | if (!regs->gprs[15]) | |
115 | return perf_misc_flags_sf(regs); | |
116 | ||
b764bb1c HG |
117 | if (is_in_guest(regs)) |
118 | return perf_misc_guest_flags(regs); | |
119 | ||
120 | return user_mode(regs) ? PERF_RECORD_MISC_USER | |
121 | : PERF_RECORD_MISC_KERNEL; | |
122 | } | |
123 | ||
443fc8a3 | 124 | static void print_debug_cf(void) |
212188a5 HB |
125 | { |
126 | struct cpumf_ctr_info cf_info; | |
8c069ff4 | 127 | int cpu = smp_processor_id(); |
212188a5 | 128 | |
212188a5 | 129 | memset(&cf_info, 0, sizeof(cf_info)); |
ae6834c1 | 130 | if (!qctri(&cf_info)) |
212188a5 HB |
131 | pr_info("CPU[%i] CPUM_CF: ver=%u.%u A=%04x E=%04x C=%04x\n", |
132 | cpu, cf_info.cfvn, cf_info.csvn, | |
133 | cf_info.auth_ctl, cf_info.enable_ctl, cf_info.act_ctl); | |
8c069ff4 | 134 | } |
212188a5 | 135 | |
8c069ff4 HB |
136 | static void print_debug_sf(void) |
137 | { | |
138 | struct hws_qsi_info_block si; | |
139 | int cpu = smp_processor_id(); | |
140 | ||
141 | memset(&si, 0, sizeof(si)); | |
7e75fc3f | 142 | if (qsi(&si)) |
8c069ff4 | 143 | return; |
8c069ff4 | 144 | |
f85168e4 | 145 | pr_info("CPU[%i] CPUM_SF: basic=%i diag=%i min=%lu max=%lu cpu_speed=%u\n", |
7e75fc3f HB |
146 | cpu, si.as, si.ad, si.min_sampl_rate, si.max_sampl_rate, |
147 | si.cpu_speed); | |
148 | ||
149 | if (si.as) | |
150 | pr_info("CPU[%i] CPUM_SF: Basic-sampling: a=%i e=%i c=%i" | |
f85168e4 | 151 | " bsdes=%i tear=%016lx dear=%016lx\n", cpu, |
7e75fc3f HB |
152 | si.as, si.es, si.cs, si.bsdes, si.tear, si.dear); |
153 | if (si.ad) | |
154 | pr_info("CPU[%i] CPUM_SF: Diagnostic-sampling: a=%i e=%i c=%i" | |
f85168e4 | 155 | " dsdes=%i tear=%016lx dear=%016lx\n", cpu, |
7e75fc3f | 156 | si.ad, si.ed, si.cd, si.dsdes, si.tear, si.dear); |
8c069ff4 HB |
157 | } |
158 | ||
159 | void perf_event_print_debug(void) | |
160 | { | |
161 | unsigned long flags; | |
162 | ||
163 | local_irq_save(flags); | |
164 | if (cpum_cf_avail()) | |
165 | print_debug_cf(); | |
166 | if (cpum_sf_avail()) | |
167 | print_debug_sf(); | |
212188a5 HB |
168 | local_irq_restore(flags); |
169 | } | |
170 | ||
99c64b66 HB |
171 | /* Service level infrastructure */ |
172 | static void sl_print_counter(struct seq_file *m) | |
173 | { | |
174 | struct cpumf_ctr_info ci; | |
175 | ||
176 | memset(&ci, 0, sizeof(ci)); | |
177 | if (qctri(&ci)) | |
178 | return; | |
179 | ||
180 | seq_printf(m, "CPU-MF: Counter facility: version=%u.%u " | |
181 | "authorization=%04x\n", ci.cfvn, ci.csvn, ci.auth_ctl); | |
182 | } | |
183 | ||
184 | static void sl_print_sampling(struct seq_file *m) | |
185 | { | |
186 | struct hws_qsi_info_block si; | |
187 | ||
188 | memset(&si, 0, sizeof(si)); | |
189 | if (qsi(&si)) | |
190 | return; | |
191 | ||
192 | if (!si.as && !si.ad) | |
193 | return; | |
194 | ||
195 | seq_printf(m, "CPU-MF: Sampling facility: min_rate=%lu max_rate=%lu" | |
196 | " cpu_speed=%u\n", si.min_sampl_rate, si.max_sampl_rate, | |
197 | si.cpu_speed); | |
198 | if (si.as) | |
199 | seq_printf(m, "CPU-MF: Sampling facility: mode=basic" | |
200 | " sample_size=%u\n", si.bsdes); | |
201 | if (si.ad) | |
202 | seq_printf(m, "CPU-MF: Sampling facility: mode=diagnostic" | |
203 | " sample_size=%u\n", si.dsdes); | |
204 | } | |
205 | ||
206 | static void service_level_perf_print(struct seq_file *m, | |
207 | struct service_level *sl) | |
208 | { | |
209 | if (cpum_cf_avail()) | |
210 | sl_print_counter(m); | |
211 | if (cpum_sf_avail()) | |
212 | sl_print_sampling(m); | |
213 | } | |
214 | ||
215 | static struct service_level service_level_perf = { | |
216 | .seq_print = service_level_perf_print, | |
217 | }; | |
218 | ||
219 | static int __init service_level_perf_register(void) | |
220 | { | |
221 | return register_service_level(&service_level_perf); | |
222 | } | |
223 | arch_initcall(service_level_perf_register); | |
224 | ||
212188a5 HB |
225 | /* See also arch/s390/kernel/traps.c */ |
226 | static unsigned long __store_trace(struct perf_callchain_entry *entry, | |
227 | unsigned long sp, | |
228 | unsigned long low, unsigned long high) | |
229 | { | |
230 | struct stack_frame *sf; | |
231 | struct pt_regs *regs; | |
232 | ||
233 | while (1) { | |
212188a5 HB |
234 | if (sp < low || sp > high - sizeof(*sf)) |
235 | return sp; | |
236 | sf = (struct stack_frame *) sp; | |
9cb1ccec | 237 | perf_callchain_store(entry, sf->gprs[8]); |
212188a5 HB |
238 | /* Follow the backchain. */ |
239 | while (1) { | |
240 | low = sp; | |
9cb1ccec | 241 | sp = sf->back_chain; |
212188a5 HB |
242 | if (!sp) |
243 | break; | |
244 | if (sp <= low || sp > high - sizeof(*sf)) | |
245 | return sp; | |
246 | sf = (struct stack_frame *) sp; | |
9cb1ccec | 247 | perf_callchain_store(entry, sf->gprs[8]); |
212188a5 HB |
248 | } |
249 | /* Zero backchain detected, check for interrupt frame. */ | |
250 | sp = (unsigned long) (sf + 1); | |
251 | if (sp <= low || sp > high - sizeof(*regs)) | |
252 | return sp; | |
253 | regs = (struct pt_regs *) sp; | |
9cb1ccec | 254 | perf_callchain_store(entry, sf->gprs[8]); |
212188a5 HB |
255 | low = sp; |
256 | sp = regs->gprs[15]; | |
257 | } | |
258 | } | |
259 | ||
260 | void perf_callchain_kernel(struct perf_callchain_entry *entry, | |
261 | struct pt_regs *regs) | |
262 | { | |
1f8cbb9c | 263 | unsigned long head, frame_size; |
212188a5 HB |
264 | struct stack_frame *head_sf; |
265 | ||
266 | if (user_mode(regs)) | |
267 | return; | |
268 | ||
1f8cbb9c | 269 | frame_size = STACK_FRAME_OVERHEAD + sizeof(struct pt_regs); |
212188a5 HB |
270 | head = regs->gprs[15]; |
271 | head_sf = (struct stack_frame *) head; | |
272 | ||
273 | if (!head_sf || !head_sf->back_chain) | |
274 | return; | |
275 | ||
276 | head = head_sf->back_chain; | |
1f8cbb9c HC |
277 | head = __store_trace(entry, head, |
278 | S390_lowcore.async_stack + frame_size - ASYNC_SIZE, | |
279 | S390_lowcore.async_stack + frame_size); | |
212188a5 HB |
280 | |
281 | __store_trace(entry, head, S390_lowcore.thread_info, | |
282 | S390_lowcore.thread_info + THREAD_SIZE); | |
283 | } | |
c7168325 HB |
284 | |
285 | /* Perf defintions for PMU event attributes in sysfs */ | |
286 | ssize_t cpumf_events_sysfs_show(struct device *dev, | |
287 | struct device_attribute *attr, char *page) | |
288 | { | |
289 | struct perf_pmu_events_attr *pmu_attr; | |
290 | ||
291 | pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr); | |
292 | return sprintf(page, "event=0x%04llx,name=%s\n", | |
293 | pmu_attr->id, attr->attr.name); | |
294 | } | |
e28bb79d HB |
295 | |
296 | /* Reserve/release functions for sharing perf hardware */ | |
297 | static DEFINE_SPINLOCK(perf_hw_owner_lock); | |
298 | static void *perf_sampling_owner; | |
299 | ||
300 | int perf_reserve_sampling(void) | |
301 | { | |
302 | int err; | |
303 | ||
304 | err = 0; | |
305 | spin_lock(&perf_hw_owner_lock); | |
306 | if (perf_sampling_owner) { | |
307 | pr_warn("The sampling facility is already reserved by %p\n", | |
308 | perf_sampling_owner); | |
309 | err = -EBUSY; | |
310 | } else | |
311 | perf_sampling_owner = __builtin_return_address(0); | |
312 | spin_unlock(&perf_hw_owner_lock); | |
313 | return err; | |
314 | } | |
315 | EXPORT_SYMBOL(perf_reserve_sampling); | |
316 | ||
317 | void perf_release_sampling(void) | |
318 | { | |
319 | spin_lock(&perf_hw_owner_lock); | |
320 | WARN_ON(!perf_sampling_owner); | |
321 | perf_sampling_owner = NULL; | |
322 | spin_unlock(&perf_hw_owner_lock); | |
323 | } | |
324 | EXPORT_SYMBOL(perf_release_sampling); |