GNU Linux-libre 6.5.10-gnu
[releases.git] / arch / powerpc / perf / 8xx-pmu.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Performance event support - PPC 8xx
4  *
5  * Copyright 2016 Christophe Leroy, CS Systemes d'Information
6  */
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/pmc.h>
14 #include <asm/machdep.h>
15 #include <asm/firmware.h>
16 #include <asm/ptrace.h>
17 #include <asm/code-patching.h>
18 #include <asm/inst.h>
19
20 #define PERF_8xx_ID_CPU_CYCLES          1
21 #define PERF_8xx_ID_HW_INSTRUCTIONS     2
22 #define PERF_8xx_ID_ITLB_LOAD_MISS      3
23 #define PERF_8xx_ID_DTLB_LOAD_MISS      4
24
25 #define C(x)    PERF_COUNT_HW_CACHE_##x
26 #define DTLB_LOAD_MISS  (C(DTLB) | (C(OP_READ) << 8) | (C(RESULT_MISS) << 16))
27 #define ITLB_LOAD_MISS  (C(ITLB) | (C(OP_READ) << 8) | (C(RESULT_MISS) << 16))
28
29 extern unsigned long itlb_miss_counter, dtlb_miss_counter;
30 extern atomic_t instruction_counter;
31
32 static atomic_t insn_ctr_ref;
33 static atomic_t itlb_miss_ref;
34 static atomic_t dtlb_miss_ref;
35
36 static s64 get_insn_ctr(void)
37 {
38         int ctr;
39         unsigned long counta;
40
41         do {
42                 ctr = atomic_read(&instruction_counter);
43                 counta = mfspr(SPRN_COUNTA);
44         } while (ctr != atomic_read(&instruction_counter));
45
46         return ((s64)ctr << 16) | (counta >> 16);
47 }
48
49 static int event_type(struct perf_event *event)
50 {
51         switch (event->attr.type) {
52         case PERF_TYPE_HARDWARE:
53                 if (event->attr.config == PERF_COUNT_HW_CPU_CYCLES)
54                         return PERF_8xx_ID_CPU_CYCLES;
55                 if (event->attr.config == PERF_COUNT_HW_INSTRUCTIONS)
56                         return PERF_8xx_ID_HW_INSTRUCTIONS;
57                 break;
58         case PERF_TYPE_HW_CACHE:
59                 if (event->attr.config == ITLB_LOAD_MISS)
60                         return PERF_8xx_ID_ITLB_LOAD_MISS;
61                 if (event->attr.config == DTLB_LOAD_MISS)
62                         return PERF_8xx_ID_DTLB_LOAD_MISS;
63                 break;
64         case PERF_TYPE_RAW:
65                 break;
66         default:
67                 return -ENOENT;
68         }
69         return -EOPNOTSUPP;
70 }
71
72 static int mpc8xx_pmu_event_init(struct perf_event *event)
73 {
74         int type = event_type(event);
75
76         if (type < 0)
77                 return type;
78         return 0;
79 }
80
81 static int mpc8xx_pmu_add(struct perf_event *event, int flags)
82 {
83         int type = event_type(event);
84         s64 val = 0;
85
86         if (type < 0)
87                 return type;
88
89         switch (type) {
90         case PERF_8xx_ID_CPU_CYCLES:
91                 val = get_tb();
92                 break;
93         case PERF_8xx_ID_HW_INSTRUCTIONS:
94                 if (atomic_inc_return(&insn_ctr_ref) == 1)
95                         mtspr(SPRN_ICTRL, 0xc0080007);
96                 val = get_insn_ctr();
97                 break;
98         case PERF_8xx_ID_ITLB_LOAD_MISS:
99                 if (atomic_inc_return(&itlb_miss_ref) == 1) {
100                         unsigned long target = patch_site_addr(&patch__itlbmiss_perf);
101
102                         patch_branch_site(&patch__itlbmiss_exit_1, target, 0);
103                 }
104                 val = itlb_miss_counter;
105                 break;
106         case PERF_8xx_ID_DTLB_LOAD_MISS:
107                 if (atomic_inc_return(&dtlb_miss_ref) == 1) {
108                         unsigned long target = patch_site_addr(&patch__dtlbmiss_perf);
109
110                         patch_branch_site(&patch__dtlbmiss_exit_1, target, 0);
111                 }
112                 val = dtlb_miss_counter;
113                 break;
114         }
115         local64_set(&event->hw.prev_count, val);
116         return 0;
117 }
118
119 static void mpc8xx_pmu_read(struct perf_event *event)
120 {
121         int type = event_type(event);
122         s64 prev, val = 0, delta = 0;
123
124         if (type < 0)
125                 return;
126
127         do {
128                 prev = local64_read(&event->hw.prev_count);
129                 switch (type) {
130                 case PERF_8xx_ID_CPU_CYCLES:
131                         val = get_tb();
132                         delta = 16 * (val - prev);
133                         break;
134                 case PERF_8xx_ID_HW_INSTRUCTIONS:
135                         val = get_insn_ctr();
136                         delta = prev - val;
137                         if (delta < 0)
138                                 delta += 0x1000000000000LL;
139                         break;
140                 case PERF_8xx_ID_ITLB_LOAD_MISS:
141                         val = itlb_miss_counter;
142                         delta = (s64)((s32)val - (s32)prev);
143                         break;
144                 case PERF_8xx_ID_DTLB_LOAD_MISS:
145                         val = dtlb_miss_counter;
146                         delta = (s64)((s32)val - (s32)prev);
147                         break;
148                 }
149         } while (local64_cmpxchg(&event->hw.prev_count, prev, val) != prev);
150
151         local64_add(delta, &event->count);
152 }
153
154 static void mpc8xx_pmu_del(struct perf_event *event, int flags)
155 {
156         ppc_inst_t insn = ppc_inst(PPC_RAW_MFSPR(10, SPRN_SPRG_SCRATCH2));
157
158         mpc8xx_pmu_read(event);
159
160         /* If it was the last user, stop counting to avoid useless overhead */
161         switch (event_type(event)) {
162         case PERF_8xx_ID_CPU_CYCLES:
163                 break;
164         case PERF_8xx_ID_HW_INSTRUCTIONS:
165                 if (atomic_dec_return(&insn_ctr_ref) == 0)
166                         mtspr(SPRN_ICTRL, 7);
167                 break;
168         case PERF_8xx_ID_ITLB_LOAD_MISS:
169                 if (atomic_dec_return(&itlb_miss_ref) == 0)
170                         patch_instruction_site(&patch__itlbmiss_exit_1, insn);
171                 break;
172         case PERF_8xx_ID_DTLB_LOAD_MISS:
173                 if (atomic_dec_return(&dtlb_miss_ref) == 0)
174                         patch_instruction_site(&patch__dtlbmiss_exit_1, insn);
175                 break;
176         }
177 }
178
179 static struct pmu mpc8xx_pmu = {
180         .event_init     = mpc8xx_pmu_event_init,
181         .add            = mpc8xx_pmu_add,
182         .del            = mpc8xx_pmu_del,
183         .read           = mpc8xx_pmu_read,
184         .capabilities   = PERF_PMU_CAP_NO_INTERRUPT |
185                           PERF_PMU_CAP_NO_NMI,
186 };
187
188 static int init_mpc8xx_pmu(void)
189 {
190         mtspr(SPRN_ICTRL, 7);
191         mtspr(SPRN_CMPA, 0);
192         mtspr(SPRN_COUNTA, 0xffff);
193
194         return perf_pmu_register(&mpc8xx_pmu, "cpu", PERF_TYPE_RAW);
195 }
196
197 early_initcall(init_mpc8xx_pmu);