diff options
author | Robert Richter <robert.richter@amd.com> | 2011-12-15 11:56:38 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2012-03-08 05:35:22 -0500 |
commit | 4db2e8e6500d9ba6406f2714fa3968b39a325274 (patch) | |
tree | 2c4c3378fe8ca0d1ce379e826f82e28446a40903 | |
parent | b7074f1fbd6149eac1ec25063e4a364c39a85473 (diff) |
perf/x86: Implement IBS pmu control ops
Add code to control the IBS pmu. We need to maintain per-cpu
states. Since some states are used and changed by the nmi
handler, access to these states must be atomic.
Signed-off-by: Robert Richter <robert.richter@amd.com>
Acked-by: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/r/1323968199-9326-4-git-send-email-robert.richter@amd.com
Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r-- | arch/x86/kernel/cpu/perf_event_amd_ibs.c | 106 |
1 files changed, 103 insertions, 3 deletions
diff --git a/arch/x86/kernel/cpu/perf_event_amd_ibs.c b/arch/x86/kernel/cpu/perf_event_amd_ibs.c index a7ec6bdf0a63..40a6d9d5dd23 100644 --- a/arch/x86/kernel/cpu/perf_event_amd_ibs.c +++ b/arch/x86/kernel/cpu/perf_event_amd_ibs.c | |||
@@ -24,6 +24,19 @@ static u32 ibs_caps; | |||
24 | #define IBS_FETCH_CONFIG_MASK (IBS_FETCH_RAND_EN | IBS_FETCH_MAX_CNT) | 24 | #define IBS_FETCH_CONFIG_MASK (IBS_FETCH_RAND_EN | IBS_FETCH_MAX_CNT) |
25 | #define IBS_OP_CONFIG_MASK IBS_OP_MAX_CNT | 25 | #define IBS_OP_CONFIG_MASK IBS_OP_MAX_CNT |
26 | 26 | ||
27 | enum ibs_states { | ||
28 | IBS_ENABLED = 0, | ||
29 | IBS_STARTED = 1, | ||
30 | IBS_STOPPING = 2, | ||
31 | |||
32 | IBS_MAX_STATES, | ||
33 | }; | ||
34 | |||
35 | struct cpu_perf_ibs { | ||
36 | struct perf_event *event; | ||
37 | unsigned long state[BITS_TO_LONGS(IBS_MAX_STATES)]; | ||
38 | }; | ||
39 | |||
27 | struct perf_ibs { | 40 | struct perf_ibs { |
28 | struct pmu pmu; | 41 | struct pmu pmu; |
29 | unsigned int msr; | 42 | unsigned int msr; |
@@ -33,6 +46,7 @@ struct perf_ibs { | |||
33 | u64 valid_mask; | 46 | u64 valid_mask; |
34 | unsigned long offset_mask[1]; | 47 | unsigned long offset_mask[1]; |
35 | int offset_max; | 48 | int offset_max; |
49 | struct cpu_perf_ibs __percpu *pcpu; | ||
36 | }; | 50 | }; |
37 | 51 | ||
38 | struct perf_ibs_data { | 52 | struct perf_ibs_data { |
@@ -97,15 +111,66 @@ static int perf_ibs_init(struct perf_event *event) | |||
97 | return 0; | 111 | return 0; |
98 | } | 112 | } |
99 | 113 | ||
114 | static void perf_ibs_start(struct perf_event *event, int flags) | ||
115 | { | ||
116 | struct hw_perf_event *hwc = &event->hw; | ||
117 | struct perf_ibs *perf_ibs = container_of(event->pmu, struct perf_ibs, pmu); | ||
118 | struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu); | ||
119 | |||
120 | if (test_and_set_bit(IBS_STARTED, pcpu->state)) | ||
121 | return; | ||
122 | |||
123 | wrmsrl(hwc->config_base, hwc->config | perf_ibs->enable_mask); | ||
124 | } | ||
125 | |||
126 | static void perf_ibs_stop(struct perf_event *event, int flags) | ||
127 | { | ||
128 | struct hw_perf_event *hwc = &event->hw; | ||
129 | struct perf_ibs *perf_ibs = container_of(event->pmu, struct perf_ibs, pmu); | ||
130 | struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu); | ||
131 | u64 val; | ||
132 | |||
133 | if (!test_and_clear_bit(IBS_STARTED, pcpu->state)) | ||
134 | return; | ||
135 | |||
136 | set_bit(IBS_STOPPING, pcpu->state); | ||
137 | |||
138 | rdmsrl(hwc->config_base, val); | ||
139 | val &= ~perf_ibs->enable_mask; | ||
140 | wrmsrl(hwc->config_base, val); | ||
141 | } | ||
142 | |||
100 | static int perf_ibs_add(struct perf_event *event, int flags) | 143 | static int perf_ibs_add(struct perf_event *event, int flags) |
101 | { | 144 | { |
145 | struct perf_ibs *perf_ibs = container_of(event->pmu, struct perf_ibs, pmu); | ||
146 | struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu); | ||
147 | |||
148 | if (test_and_set_bit(IBS_ENABLED, pcpu->state)) | ||
149 | return -ENOSPC; | ||
150 | |||
151 | pcpu->event = event; | ||
152 | |||
153 | if (flags & PERF_EF_START) | ||
154 | perf_ibs_start(event, PERF_EF_RELOAD); | ||
155 | |||
102 | return 0; | 156 | return 0; |
103 | } | 157 | } |
104 | 158 | ||
105 | static void perf_ibs_del(struct perf_event *event, int flags) | 159 | static void perf_ibs_del(struct perf_event *event, int flags) |
106 | { | 160 | { |
161 | struct perf_ibs *perf_ibs = container_of(event->pmu, struct perf_ibs, pmu); | ||
162 | struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu); | ||
163 | |||
164 | if (!test_and_clear_bit(IBS_ENABLED, pcpu->state)) | ||
165 | return; | ||
166 | |||
167 | perf_ibs_stop(event, 0); | ||
168 | |||
169 | pcpu->event = NULL; | ||
107 | } | 170 | } |
108 | 171 | ||
172 | static void perf_ibs_read(struct perf_event *event) { } | ||
173 | |||
109 | static struct perf_ibs perf_ibs_fetch = { | 174 | static struct perf_ibs perf_ibs_fetch = { |
110 | .pmu = { | 175 | .pmu = { |
111 | .task_ctx_nr = perf_invalid_context, | 176 | .task_ctx_nr = perf_invalid_context, |
@@ -113,6 +178,9 @@ static struct perf_ibs perf_ibs_fetch = { | |||
113 | .event_init = perf_ibs_init, | 178 | .event_init = perf_ibs_init, |
114 | .add = perf_ibs_add, | 179 | .add = perf_ibs_add, |
115 | .del = perf_ibs_del, | 180 | .del = perf_ibs_del, |
181 | .start = perf_ibs_start, | ||
182 | .stop = perf_ibs_stop, | ||
183 | .read = perf_ibs_read, | ||
116 | }, | 184 | }, |
117 | .msr = MSR_AMD64_IBSFETCHCTL, | 185 | .msr = MSR_AMD64_IBSFETCHCTL, |
118 | .config_mask = IBS_FETCH_CONFIG_MASK, | 186 | .config_mask = IBS_FETCH_CONFIG_MASK, |
@@ -130,6 +198,9 @@ static struct perf_ibs perf_ibs_op = { | |||
130 | .event_init = perf_ibs_init, | 198 | .event_init = perf_ibs_init, |
131 | .add = perf_ibs_add, | 199 | .add = perf_ibs_add, |
132 | .del = perf_ibs_del, | 200 | .del = perf_ibs_del, |
201 | .start = perf_ibs_start, | ||
202 | .stop = perf_ibs_stop, | ||
203 | .read = perf_ibs_read, | ||
133 | }, | 204 | }, |
134 | .msr = MSR_AMD64_IBSOPCTL, | 205 | .msr = MSR_AMD64_IBSOPCTL, |
135 | .config_mask = IBS_OP_CONFIG_MASK, | 206 | .config_mask = IBS_OP_CONFIG_MASK, |
@@ -142,7 +213,8 @@ static struct perf_ibs perf_ibs_op = { | |||
142 | 213 | ||
143 | static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs) | 214 | static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs) |
144 | { | 215 | { |
145 | struct perf_event *event = NULL; | 216 | struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu); |
217 | struct perf_event *event = pcpu->event; | ||
146 | struct hw_perf_event *hwc = &event->hw; | 218 | struct hw_perf_event *hwc = &event->hw; |
147 | struct perf_sample_data data; | 219 | struct perf_sample_data data; |
148 | struct perf_raw_record raw; | 220 | struct perf_raw_record raw; |
@@ -152,6 +224,14 @@ static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs) | |||
152 | unsigned int msr; | 224 | unsigned int msr; |
153 | u64 *buf; | 225 | u64 *buf; |
154 | 226 | ||
227 | if (!test_bit(IBS_STARTED, pcpu->state)) { | ||
228 | /* Catch spurious interrupts after stopping IBS: */ | ||
229 | if (!test_and_clear_bit(IBS_STOPPING, pcpu->state)) | ||
230 | return 0; | ||
231 | rdmsrl(perf_ibs->msr, *ibs_data.regs); | ||
232 | return (*ibs_data.regs & perf_ibs->valid_mask) ? 1 : 0; | ||
233 | } | ||
234 | |||
155 | msr = hwc->config_base; | 235 | msr = hwc->config_base; |
156 | buf = ibs_data.regs; | 236 | buf = ibs_data.regs; |
157 | rdmsrl(msr, *buf); | 237 | rdmsrl(msr, *buf); |
@@ -200,13 +280,33 @@ perf_ibs_nmi_handler(unsigned int cmd, struct pt_regs *regs) | |||
200 | return handled; | 280 | return handled; |
201 | } | 281 | } |
202 | 282 | ||
283 | static __init int perf_ibs_pmu_init(struct perf_ibs *perf_ibs, char *name) | ||
284 | { | ||
285 | struct cpu_perf_ibs __percpu *pcpu; | ||
286 | int ret; | ||
287 | |||
288 | pcpu = alloc_percpu(struct cpu_perf_ibs); | ||
289 | if (!pcpu) | ||
290 | return -ENOMEM; | ||
291 | |||
292 | perf_ibs->pcpu = pcpu; | ||
293 | |||
294 | ret = perf_pmu_register(&perf_ibs->pmu, name, -1); | ||
295 | if (ret) { | ||
296 | perf_ibs->pcpu = NULL; | ||
297 | free_percpu(pcpu); | ||
298 | } | ||
299 | |||
300 | return ret; | ||
301 | } | ||
302 | |||
203 | static __init int perf_event_ibs_init(void) | 303 | static __init int perf_event_ibs_init(void) |
204 | { | 304 | { |
205 | if (!ibs_caps) | 305 | if (!ibs_caps) |
206 | return -ENODEV; /* ibs not supported by the cpu */ | 306 | return -ENODEV; /* ibs not supported by the cpu */ |
207 | 307 | ||
208 | perf_pmu_register(&perf_ibs_fetch.pmu, "ibs_fetch", -1); | 308 | perf_ibs_pmu_init(&perf_ibs_fetch, "ibs_fetch"); |
209 | perf_pmu_register(&perf_ibs_op.pmu, "ibs_op", -1); | 309 | perf_ibs_pmu_init(&perf_ibs_op, "ibs_op"); |
210 | register_nmi_handler(NMI_LOCAL, &perf_ibs_nmi_handler, 0, "perf_ibs"); | 310 | register_nmi_handler(NMI_LOCAL, &perf_ibs_nmi_handler, 0, "perf_ibs"); |
211 | printk(KERN_INFO "perf: AMD IBS detected (0x%08x)\n", ibs_caps); | 311 | printk(KERN_INFO "perf: AMD IBS detected (0x%08x)\n", ibs_caps); |
212 | 312 | ||