1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Performance event support - Freescale Embedded Performance Monitor |
4 | * |
5 | * Copyright 2008-2009 Paul Mackerras, IBM Corporation. |
6 | * Copyright 2010 Freescale Semiconductor, Inc. |
7 | */ |
8 | #include <linux/kernel.h> |
9 | #include <linux/sched.h> |
10 | #include <linux/perf_event.h> |
11 | #include <linux/percpu.h> |
12 | #include <linux/hardirq.h> |
13 | #include <asm/reg_fsl_emb.h> |
14 | #include <asm/pmc.h> |
15 | #include <asm/machdep.h> |
16 | #include <asm/firmware.h> |
17 | #include <asm/ptrace.h> |
18 | |
19 | struct cpu_hw_events { |
20 | int n_events; |
21 | int disabled; |
22 | u8 pmcs_enabled; |
23 | struct perf_event *event[MAX_HWEVENTS]; |
24 | }; |
25 | static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events); |
26 | |
27 | static struct fsl_emb_pmu *ppmu; |
28 | |
29 | /* Number of perf_events counting hardware events */ |
30 | static atomic_t num_events; |
31 | /* Used to avoid races in calling reserve/release_pmc_hardware */ |
32 | static DEFINE_MUTEX(pmc_reserve_mutex); |
33 | |
34 | static void perf_event_interrupt(struct pt_regs *regs); |
35 | |
36 | /* |
37 | * Read one performance monitor counter (PMC). |
38 | */ |
39 | static unsigned long read_pmc(int idx) |
40 | { |
41 | unsigned long val; |
42 | |
43 | switch (idx) { |
44 | case 0: |
45 | val = mfpmr(PMRN_PMC0); |
46 | break; |
47 | case 1: |
48 | val = mfpmr(PMRN_PMC1); |
49 | break; |
50 | case 2: |
51 | val = mfpmr(PMRN_PMC2); |
52 | break; |
53 | case 3: |
54 | val = mfpmr(PMRN_PMC3); |
55 | break; |
56 | case 4: |
57 | val = mfpmr(PMRN_PMC4); |
58 | break; |
59 | case 5: |
60 | val = mfpmr(PMRN_PMC5); |
61 | break; |
62 | default: |
63 | printk(KERN_ERR "oops trying to read PMC%d\n" , idx); |
64 | val = 0; |
65 | } |
66 | return val; |
67 | } |
68 | |
69 | /* |
70 | * Write one PMC. |
71 | */ |
72 | static void write_pmc(int idx, unsigned long val) |
73 | { |
74 | switch (idx) { |
75 | case 0: |
76 | mtpmr(PMRN_PMC0, val); |
77 | break; |
78 | case 1: |
79 | mtpmr(PMRN_PMC1, val); |
80 | break; |
81 | case 2: |
82 | mtpmr(PMRN_PMC2, val); |
83 | break; |
84 | case 3: |
85 | mtpmr(PMRN_PMC3, val); |
86 | break; |
87 | case 4: |
88 | mtpmr(PMRN_PMC4, val); |
89 | break; |
90 | case 5: |
91 | mtpmr(PMRN_PMC5, val); |
92 | break; |
93 | default: |
94 | printk(KERN_ERR "oops trying to write PMC%d\n" , idx); |
95 | } |
96 | |
97 | isync(); |
98 | } |
99 | |
100 | /* |
101 | * Write one local control A register |
102 | */ |
103 | static void write_pmlca(int idx, unsigned long val) |
104 | { |
105 | switch (idx) { |
106 | case 0: |
107 | mtpmr(PMRN_PMLCA0, val); |
108 | break; |
109 | case 1: |
110 | mtpmr(PMRN_PMLCA1, val); |
111 | break; |
112 | case 2: |
113 | mtpmr(PMRN_PMLCA2, val); |
114 | break; |
115 | case 3: |
116 | mtpmr(PMRN_PMLCA3, val); |
117 | break; |
118 | case 4: |
119 | mtpmr(PMRN_PMLCA4, val); |
120 | break; |
121 | case 5: |
122 | mtpmr(PMRN_PMLCA5, val); |
123 | break; |
124 | default: |
125 | printk(KERN_ERR "oops trying to write PMLCA%d\n" , idx); |
126 | } |
127 | |
128 | isync(); |
129 | } |
130 | |
131 | /* |
132 | * Write one local control B register |
133 | */ |
134 | static void write_pmlcb(int idx, unsigned long val) |
135 | { |
136 | switch (idx) { |
137 | case 0: |
138 | mtpmr(PMRN_PMLCB0, val); |
139 | break; |
140 | case 1: |
141 | mtpmr(PMRN_PMLCB1, val); |
142 | break; |
143 | case 2: |
144 | mtpmr(PMRN_PMLCB2, val); |
145 | break; |
146 | case 3: |
147 | mtpmr(PMRN_PMLCB3, val); |
148 | break; |
149 | case 4: |
150 | mtpmr(PMRN_PMLCB4, val); |
151 | break; |
152 | case 5: |
153 | mtpmr(PMRN_PMLCB5, val); |
154 | break; |
155 | default: |
156 | printk(KERN_ERR "oops trying to write PMLCB%d\n" , idx); |
157 | } |
158 | |
159 | isync(); |
160 | } |
161 | |
162 | static void fsl_emb_pmu_read(struct perf_event *event) |
163 | { |
164 | s64 val, delta, prev; |
165 | |
166 | if (event->hw.state & PERF_HES_STOPPED) |
167 | return; |
168 | |
169 | /* |
170 | * Performance monitor interrupts come even when interrupts |
171 | * are soft-disabled, as long as interrupts are hard-enabled. |
172 | * Therefore we treat them like NMIs. |
173 | */ |
174 | do { |
175 | prev = local64_read(&event->hw.prev_count); |
176 | barrier(); |
177 | val = read_pmc(idx: event->hw.idx); |
178 | } while (local64_cmpxchg(l: &event->hw.prev_count, old: prev, new: val) != prev); |
179 | |
180 | /* The counters are only 32 bits wide */ |
181 | delta = (val - prev) & 0xfffffffful; |
182 | local64_add(delta, &event->count); |
183 | local64_sub(delta, &event->hw.period_left); |
184 | } |
185 | |
186 | /* |
187 | * Disable all events to prevent PMU interrupts and to allow |
188 | * events to be added or removed. |
189 | */ |
190 | static void fsl_emb_pmu_disable(struct pmu *pmu) |
191 | { |
192 | struct cpu_hw_events *cpuhw; |
193 | unsigned long flags; |
194 | |
195 | local_irq_save(flags); |
196 | cpuhw = this_cpu_ptr(&cpu_hw_events); |
197 | |
198 | if (!cpuhw->disabled) { |
199 | cpuhw->disabled = 1; |
200 | |
201 | /* |
202 | * Check if we ever enabled the PMU on this cpu. |
203 | */ |
204 | if (!cpuhw->pmcs_enabled) { |
205 | ppc_enable_pmcs(); |
206 | cpuhw->pmcs_enabled = 1; |
207 | } |
208 | |
209 | if (atomic_read(v: &num_events)) { |
210 | /* |
211 | * Set the 'freeze all counters' bit, and disable |
212 | * interrupts. The barrier is to make sure the |
213 | * mtpmr has been executed and the PMU has frozen |
214 | * the events before we return. |
215 | */ |
216 | |
217 | mtpmr(PMRN_PMGC0, PMGC0_FAC); |
218 | isync(); |
219 | } |
220 | } |
221 | local_irq_restore(flags); |
222 | } |
223 | |
224 | /* |
225 | * Re-enable all events if disable == 0. |
226 | * If we were previously disabled and events were added, then |
227 | * put the new config on the PMU. |
228 | */ |
229 | static void fsl_emb_pmu_enable(struct pmu *pmu) |
230 | { |
231 | struct cpu_hw_events *cpuhw; |
232 | unsigned long flags; |
233 | |
234 | local_irq_save(flags); |
235 | cpuhw = this_cpu_ptr(&cpu_hw_events); |
236 | if (!cpuhw->disabled) |
237 | goto out; |
238 | |
239 | cpuhw->disabled = 0; |
240 | ppc_set_pmu_inuse(cpuhw->n_events != 0); |
241 | |
242 | if (cpuhw->n_events > 0) { |
243 | mtpmr(PMRN_PMGC0, PMGC0_PMIE | PMGC0_FCECE); |
244 | isync(); |
245 | } |
246 | |
247 | out: |
248 | local_irq_restore(flags); |
249 | } |
250 | |
251 | static int collect_events(struct perf_event *group, int max_count, |
252 | struct perf_event *ctrs[]) |
253 | { |
254 | int n = 0; |
255 | struct perf_event *event; |
256 | |
257 | if (!is_software_event(event: group)) { |
258 | if (n >= max_count) |
259 | return -1; |
260 | ctrs[n] = group; |
261 | n++; |
262 | } |
263 | for_each_sibling_event(event, group) { |
264 | if (!is_software_event(event) && |
265 | event->state != PERF_EVENT_STATE_OFF) { |
266 | if (n >= max_count) |
267 | return -1; |
268 | ctrs[n] = event; |
269 | n++; |
270 | } |
271 | } |
272 | return n; |
273 | } |
274 | |
275 | /* context locked on entry */ |
276 | static int fsl_emb_pmu_add(struct perf_event *event, int flags) |
277 | { |
278 | struct cpu_hw_events *cpuhw; |
279 | int ret = -EAGAIN; |
280 | int num_counters = ppmu->n_counter; |
281 | u64 val; |
282 | int i; |
283 | |
284 | perf_pmu_disable(pmu: event->pmu); |
285 | cpuhw = &get_cpu_var(cpu_hw_events); |
286 | |
287 | if (event->hw.config & FSL_EMB_EVENT_RESTRICTED) |
288 | num_counters = ppmu->n_restricted; |
289 | |
290 | /* |
291 | * Allocate counters from top-down, so that restricted-capable |
292 | * counters are kept free as long as possible. |
293 | */ |
294 | for (i = num_counters - 1; i >= 0; i--) { |
295 | if (cpuhw->event[i]) |
296 | continue; |
297 | |
298 | break; |
299 | } |
300 | |
301 | if (i < 0) |
302 | goto out; |
303 | |
304 | event->hw.idx = i; |
305 | cpuhw->event[i] = event; |
306 | ++cpuhw->n_events; |
307 | |
308 | val = 0; |
309 | if (event->hw.sample_period) { |
310 | s64 left = local64_read(&event->hw.period_left); |
311 | if (left < 0x80000000L) |
312 | val = 0x80000000L - left; |
313 | } |
314 | local64_set(&event->hw.prev_count, val); |
315 | |
316 | if (unlikely(!(flags & PERF_EF_START))) { |
317 | event->hw.state = PERF_HES_STOPPED | PERF_HES_UPTODATE; |
318 | val = 0; |
319 | } else { |
320 | event->hw.state &= ~(PERF_HES_STOPPED | PERF_HES_UPTODATE); |
321 | } |
322 | |
323 | write_pmc(idx: i, val); |
324 | perf_event_update_userpage(event); |
325 | |
326 | write_pmlcb(idx: i, val: event->hw.config >> 32); |
327 | write_pmlca(idx: i, val: event->hw.config_base); |
328 | |
329 | ret = 0; |
330 | out: |
331 | put_cpu_var(cpu_hw_events); |
332 | perf_pmu_enable(pmu: event->pmu); |
333 | return ret; |
334 | } |
335 | |
336 | /* context locked on entry */ |
337 | static void fsl_emb_pmu_del(struct perf_event *event, int flags) |
338 | { |
339 | struct cpu_hw_events *cpuhw; |
340 | int i = event->hw.idx; |
341 | |
342 | perf_pmu_disable(pmu: event->pmu); |
343 | if (i < 0) |
344 | goto out; |
345 | |
346 | fsl_emb_pmu_read(event); |
347 | |
348 | cpuhw = &get_cpu_var(cpu_hw_events); |
349 | |
350 | WARN_ON(event != cpuhw->event[event->hw.idx]); |
351 | |
352 | write_pmlca(idx: i, val: 0); |
353 | write_pmlcb(idx: i, val: 0); |
354 | write_pmc(idx: i, val: 0); |
355 | |
356 | cpuhw->event[i] = NULL; |
357 | event->hw.idx = -1; |
358 | |
359 | /* |
360 | * TODO: if at least one restricted event exists, and we |
361 | * just freed up a non-restricted-capable counter, and |
362 | * there is a restricted-capable counter occupied by |
363 | * a non-restricted event, migrate that event to the |
364 | * vacated counter. |
365 | */ |
366 | |
367 | cpuhw->n_events--; |
368 | |
369 | out: |
370 | perf_pmu_enable(pmu: event->pmu); |
371 | put_cpu_var(cpu_hw_events); |
372 | } |
373 | |
374 | static void fsl_emb_pmu_start(struct perf_event *event, int ef_flags) |
375 | { |
376 | unsigned long flags; |
377 | unsigned long val; |
378 | s64 left; |
379 | |
380 | if (event->hw.idx < 0 || !event->hw.sample_period) |
381 | return; |
382 | |
383 | if (!(event->hw.state & PERF_HES_STOPPED)) |
384 | return; |
385 | |
386 | if (ef_flags & PERF_EF_RELOAD) |
387 | WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE)); |
388 | |
389 | local_irq_save(flags); |
390 | perf_pmu_disable(pmu: event->pmu); |
391 | |
392 | event->hw.state = 0; |
393 | left = local64_read(&event->hw.period_left); |
394 | val = 0; |
395 | if (left < 0x80000000L) |
396 | val = 0x80000000L - left; |
397 | write_pmc(idx: event->hw.idx, val); |
398 | |
399 | perf_event_update_userpage(event); |
400 | perf_pmu_enable(pmu: event->pmu); |
401 | local_irq_restore(flags); |
402 | } |
403 | |
404 | static void fsl_emb_pmu_stop(struct perf_event *event, int ef_flags) |
405 | { |
406 | unsigned long flags; |
407 | |
408 | if (event->hw.idx < 0 || !event->hw.sample_period) |
409 | return; |
410 | |
411 | if (event->hw.state & PERF_HES_STOPPED) |
412 | return; |
413 | |
414 | local_irq_save(flags); |
415 | perf_pmu_disable(pmu: event->pmu); |
416 | |
417 | fsl_emb_pmu_read(event); |
418 | event->hw.state |= PERF_HES_STOPPED | PERF_HES_UPTODATE; |
419 | write_pmc(idx: event->hw.idx, val: 0); |
420 | |
421 | perf_event_update_userpage(event); |
422 | perf_pmu_enable(pmu: event->pmu); |
423 | local_irq_restore(flags); |
424 | } |
425 | |
426 | /* |
427 | * Release the PMU if this is the last perf_event. |
428 | */ |
429 | static void hw_perf_event_destroy(struct perf_event *event) |
430 | { |
431 | if (!atomic_add_unless(v: &num_events, a: -1, u: 1)) { |
432 | mutex_lock(&pmc_reserve_mutex); |
433 | if (atomic_dec_return(v: &num_events) == 0) |
434 | release_pmc_hardware(); |
435 | mutex_unlock(lock: &pmc_reserve_mutex); |
436 | } |
437 | } |
438 | |
439 | /* |
440 | * Translate a generic cache event_id config to a raw event_id code. |
441 | */ |
442 | static int hw_perf_cache_event(u64 config, u64 *eventp) |
443 | { |
444 | unsigned long type, op, result; |
445 | int ev; |
446 | |
447 | if (!ppmu->cache_events) |
448 | return -EINVAL; |
449 | |
450 | /* unpack config */ |
451 | type = config & 0xff; |
452 | op = (config >> 8) & 0xff; |
453 | result = (config >> 16) & 0xff; |
454 | |
455 | if (type >= PERF_COUNT_HW_CACHE_MAX || |
456 | op >= PERF_COUNT_HW_CACHE_OP_MAX || |
457 | result >= PERF_COUNT_HW_CACHE_RESULT_MAX) |
458 | return -EINVAL; |
459 | |
460 | ev = (*ppmu->cache_events)[type][op][result]; |
461 | if (ev == 0) |
462 | return -EOPNOTSUPP; |
463 | if (ev == -1) |
464 | return -EINVAL; |
465 | *eventp = ev; |
466 | return 0; |
467 | } |
468 | |
469 | static int fsl_emb_pmu_event_init(struct perf_event *event) |
470 | { |
471 | u64 ev; |
472 | struct perf_event *events[MAX_HWEVENTS]; |
473 | int n; |
474 | int err; |
475 | int num_restricted; |
476 | int i; |
477 | |
478 | if (ppmu->n_counter > MAX_HWEVENTS) { |
479 | WARN(1, "No. of perf counters (%d) is higher than max array size(%d)\n" , |
480 | ppmu->n_counter, MAX_HWEVENTS); |
481 | ppmu->n_counter = MAX_HWEVENTS; |
482 | } |
483 | |
484 | switch (event->attr.type) { |
485 | case PERF_TYPE_HARDWARE: |
486 | ev = event->attr.config; |
487 | if (ev >= ppmu->n_generic || ppmu->generic_events[ev] == 0) |
488 | return -EOPNOTSUPP; |
489 | ev = ppmu->generic_events[ev]; |
490 | break; |
491 | |
492 | case PERF_TYPE_HW_CACHE: |
493 | err = hw_perf_cache_event(config: event->attr.config, eventp: &ev); |
494 | if (err) |
495 | return err; |
496 | break; |
497 | |
498 | case PERF_TYPE_RAW: |
499 | ev = event->attr.config; |
500 | break; |
501 | |
502 | default: |
503 | return -ENOENT; |
504 | } |
505 | |
506 | event->hw.config = ppmu->xlate_event(ev); |
507 | if (!(event->hw.config & FSL_EMB_EVENT_VALID)) |
508 | return -EINVAL; |
509 | |
510 | /* |
511 | * If this is in a group, check if it can go on with all the |
512 | * other hardware events in the group. We assume the event |
513 | * hasn't been linked into its leader's sibling list at this point. |
514 | */ |
515 | n = 0; |
516 | if (event->group_leader != event) { |
517 | n = collect_events(group: event->group_leader, |
518 | max_count: ppmu->n_counter - 1, ctrs: events); |
519 | if (n < 0) |
520 | return -EINVAL; |
521 | } |
522 | |
523 | if (event->hw.config & FSL_EMB_EVENT_RESTRICTED) { |
524 | num_restricted = 0; |
525 | for (i = 0; i < n; i++) { |
526 | if (events[i]->hw.config & FSL_EMB_EVENT_RESTRICTED) |
527 | num_restricted++; |
528 | } |
529 | |
530 | if (num_restricted >= ppmu->n_restricted) |
531 | return -EINVAL; |
532 | } |
533 | |
534 | event->hw.idx = -1; |
535 | |
536 | event->hw.config_base = PMLCA_CE | PMLCA_FCM1 | |
537 | (u32)((ev << 16) & PMLCA_EVENT_MASK); |
538 | |
539 | if (event->attr.exclude_user) |
540 | event->hw.config_base |= PMLCA_FCU; |
541 | if (event->attr.exclude_kernel) |
542 | event->hw.config_base |= PMLCA_FCS; |
543 | if (event->attr.exclude_idle) |
544 | return -ENOTSUPP; |
545 | |
546 | event->hw.last_period = event->hw.sample_period; |
547 | local64_set(&event->hw.period_left, event->hw.last_period); |
548 | |
549 | /* |
550 | * See if we need to reserve the PMU. |
551 | * If no events are currently in use, then we have to take a |
552 | * mutex to ensure that we don't race with another task doing |
553 | * reserve_pmc_hardware or release_pmc_hardware. |
554 | */ |
555 | err = 0; |
556 | if (!atomic_inc_not_zero(v: &num_events)) { |
557 | mutex_lock(&pmc_reserve_mutex); |
558 | if (atomic_read(v: &num_events) == 0 && |
559 | reserve_pmc_hardware(perf_event_interrupt)) |
560 | err = -EBUSY; |
561 | else |
562 | atomic_inc(v: &num_events); |
563 | mutex_unlock(lock: &pmc_reserve_mutex); |
564 | |
565 | mtpmr(PMRN_PMGC0, PMGC0_FAC); |
566 | isync(); |
567 | } |
568 | event->destroy = hw_perf_event_destroy; |
569 | |
570 | return err; |
571 | } |
572 | |
573 | static struct pmu fsl_emb_pmu = { |
574 | .pmu_enable = fsl_emb_pmu_enable, |
575 | .pmu_disable = fsl_emb_pmu_disable, |
576 | .event_init = fsl_emb_pmu_event_init, |
577 | .add = fsl_emb_pmu_add, |
578 | .del = fsl_emb_pmu_del, |
579 | .start = fsl_emb_pmu_start, |
580 | .stop = fsl_emb_pmu_stop, |
581 | .read = fsl_emb_pmu_read, |
582 | }; |
583 | |
584 | /* |
585 | * A counter has overflowed; update its count and record |
586 | * things if requested. Note that interrupts are hard-disabled |
587 | * here so there is no possibility of being interrupted. |
588 | */ |
589 | static void record_and_restart(struct perf_event *event, unsigned long val, |
590 | struct pt_regs *regs) |
591 | { |
592 | u64 period = event->hw.sample_period; |
593 | s64 prev, delta, left; |
594 | int record = 0; |
595 | |
596 | if (event->hw.state & PERF_HES_STOPPED) { |
597 | write_pmc(idx: event->hw.idx, val: 0); |
598 | return; |
599 | } |
600 | |
601 | /* we don't have to worry about interrupts here */ |
602 | prev = local64_read(&event->hw.prev_count); |
603 | delta = (val - prev) & 0xfffffffful; |
604 | local64_add(delta, &event->count); |
605 | |
606 | /* |
607 | * See if the total period for this event has expired, |
608 | * and update for the next period. |
609 | */ |
610 | val = 0; |
611 | left = local64_read(&event->hw.period_left) - delta; |
612 | if (period) { |
613 | if (left <= 0) { |
614 | left += period; |
615 | if (left <= 0) |
616 | left = period; |
617 | record = 1; |
618 | event->hw.last_period = event->hw.sample_period; |
619 | } |
620 | if (left < 0x80000000LL) |
621 | val = 0x80000000LL - left; |
622 | } |
623 | |
624 | write_pmc(idx: event->hw.idx, val); |
625 | local64_set(&event->hw.prev_count, val); |
626 | local64_set(&event->hw.period_left, left); |
627 | perf_event_update_userpage(event); |
628 | |
629 | /* |
630 | * Finally record data if requested. |
631 | */ |
632 | if (record) { |
633 | struct perf_sample_data data; |
634 | |
635 | perf_sample_data_init(data: &data, addr: 0, period: event->hw.last_period); |
636 | |
637 | if (perf_event_overflow(event, data: &data, regs)) |
638 | fsl_emb_pmu_stop(event, ef_flags: 0); |
639 | } |
640 | } |
641 | |
642 | static void perf_event_interrupt(struct pt_regs *regs) |
643 | { |
644 | int i; |
645 | struct cpu_hw_events *cpuhw = this_cpu_ptr(&cpu_hw_events); |
646 | struct perf_event *event; |
647 | unsigned long val; |
648 | |
649 | for (i = 0; i < ppmu->n_counter; ++i) { |
650 | event = cpuhw->event[i]; |
651 | |
652 | val = read_pmc(idx: i); |
653 | if ((int)val < 0) { |
654 | if (event) { |
655 | /* event has overflowed */ |
656 | record_and_restart(event, val, regs); |
657 | } else { |
658 | /* |
659 | * Disabled counter is negative, |
660 | * reset it just in case. |
661 | */ |
662 | write_pmc(idx: i, val: 0); |
663 | } |
664 | } |
665 | } |
666 | |
667 | /* PMM will keep counters frozen until we return from the interrupt. */ |
668 | mtmsr(mfmsr() | MSR_PMM); |
669 | mtpmr(PMRN_PMGC0, PMGC0_PMIE | PMGC0_FCECE); |
670 | isync(); |
671 | } |
672 | |
673 | static int fsl_emb_pmu_prepare_cpu(unsigned int cpu) |
674 | { |
675 | struct cpu_hw_events *cpuhw = &per_cpu(cpu_hw_events, cpu); |
676 | |
677 | memset(cpuhw, 0, sizeof(*cpuhw)); |
678 | |
679 | return 0; |
680 | } |
681 | |
682 | int register_fsl_emb_pmu(struct fsl_emb_pmu *pmu) |
683 | { |
684 | if (ppmu) |
685 | return -EBUSY; /* something's already registered */ |
686 | |
687 | ppmu = pmu; |
688 | pr_info("%s performance monitor hardware support registered\n" , |
689 | pmu->name); |
690 | |
691 | perf_pmu_register(pmu: &fsl_emb_pmu, name: "cpu" , type: PERF_TYPE_RAW); |
692 | cpuhp_setup_state(state: CPUHP_PERF_POWER, name: "perf/powerpc:prepare" , |
693 | startup: fsl_emb_pmu_prepare_cpu, NULL); |
694 | |
695 | return 0; |
696 | } |
697 | |