diff options
Diffstat (limited to 'kernel/hw_breakpoint.c')
-rw-r--r-- | kernel/hw_breakpoint.c | 211 |
1 files changed, 205 insertions, 6 deletions
diff --git a/kernel/hw_breakpoint.c b/kernel/hw_breakpoint.c index 08f6d0163201..e662dc991c96 100644 --- a/kernel/hw_breakpoint.c +++ b/kernel/hw_breakpoint.c | |||
@@ -16,6 +16,8 @@ | |||
16 | * Copyright (C) 2007 Alan Stern | 16 | * Copyright (C) 2007 Alan Stern |
17 | * Copyright (C) IBM Corporation, 2009 | 17 | * Copyright (C) IBM Corporation, 2009 |
18 | * Copyright (C) 2009, Frederic Weisbecker <fweisbec@gmail.com> | 18 | * Copyright (C) 2009, Frederic Weisbecker <fweisbec@gmail.com> |
19 | * | ||
20 | * Thanks to Ingo Molnar for his many suggestions. | ||
19 | */ | 21 | */ |
20 | 22 | ||
21 | /* | 23 | /* |
@@ -44,24 +46,221 @@ | |||
44 | #include <asm/debugreg.h> | 46 | #include <asm/debugreg.h> |
45 | #endif | 47 | #endif |
46 | 48 | ||
47 | static atomic_t bp_slot; | 49 | /* |
50 | * Constraints data | ||
51 | */ | ||
52 | |||
53 | /* Number of pinned cpu breakpoints in a cpu */ | ||
54 | static DEFINE_PER_CPU(unsigned int, nr_cpu_bp_pinned); | ||
48 | 55 | ||
49 | int reserve_bp_slot(struct perf_event *bp) | 56 | /* Number of pinned task breakpoints in a cpu */ |
57 | static DEFINE_PER_CPU(unsigned int, task_bp_pinned[HBP_NUM]); | ||
58 | |||
59 | /* Number of non-pinned cpu/task breakpoints in a cpu */ | ||
60 | static DEFINE_PER_CPU(unsigned int, nr_bp_flexible); | ||
61 | |||
62 | /* Gather the number of total pinned and un-pinned bp in a cpuset */ | ||
63 | struct bp_busy_slots { | ||
64 | unsigned int pinned; | ||
65 | unsigned int flexible; | ||
66 | }; | ||
67 | |||
68 | /* Serialize accesses to the above constraints */ | ||
69 | static DEFINE_MUTEX(nr_bp_mutex); | ||
70 | |||
71 | /* | ||
72 | * Report the maximum number of pinned breakpoints a task | ||
73 | * have in this cpu | ||
74 | */ | ||
75 | static unsigned int max_task_bp_pinned(int cpu) | ||
50 | { | 76 | { |
51 | if (atomic_inc_return(&bp_slot) == HBP_NUM) { | 77 | int i; |
52 | atomic_dec(&bp_slot); | 78 | unsigned int *tsk_pinned = per_cpu(task_bp_pinned, cpu); |
53 | 79 | ||
54 | return -ENOSPC; | 80 | for (i = HBP_NUM -1; i >= 0; i--) { |
81 | if (tsk_pinned[i] > 0) | ||
82 | return i + 1; | ||
55 | } | 83 | } |
56 | 84 | ||
57 | return 0; | 85 | return 0; |
58 | } | 86 | } |
59 | 87 | ||
88 | /* | ||
89 | * Report the number of pinned/un-pinned breakpoints we have in | ||
90 | * a given cpu (cpu > -1) or in all of them (cpu = -1). | ||
91 | */ | ||
92 | static void fetch_bp_busy_slots(struct bp_busy_slots *slots, int cpu) | ||
93 | { | ||
94 | if (cpu >= 0) { | ||
95 | slots->pinned = per_cpu(nr_cpu_bp_pinned, cpu); | ||
96 | slots->pinned += max_task_bp_pinned(cpu); | ||
97 | slots->flexible = per_cpu(nr_bp_flexible, cpu); | ||
98 | |||
99 | return; | ||
100 | } | ||
101 | |||
102 | for_each_online_cpu(cpu) { | ||
103 | unsigned int nr; | ||
104 | |||
105 | nr = per_cpu(nr_cpu_bp_pinned, cpu); | ||
106 | nr += max_task_bp_pinned(cpu); | ||
107 | |||
108 | if (nr > slots->pinned) | ||
109 | slots->pinned = nr; | ||
110 | |||
111 | nr = per_cpu(nr_bp_flexible, cpu); | ||
112 | |||
113 | if (nr > slots->flexible) | ||
114 | slots->flexible = nr; | ||
115 | } | ||
116 | } | ||
117 | |||
118 | /* | ||
119 | * Add a pinned breakpoint for the given task in our constraint table | ||
120 | */ | ||
121 | static void toggle_bp_task_slot(struct task_struct *tsk, int cpu, bool enable) | ||
122 | { | ||
123 | int count = 0; | ||
124 | struct perf_event *bp; | ||
125 | struct perf_event_context *ctx = tsk->perf_event_ctxp; | ||
126 | unsigned int *task_bp_pinned; | ||
127 | struct list_head *list; | ||
128 | unsigned long flags; | ||
129 | |||
130 | if (WARN_ONCE(!ctx, "No perf context for this task")) | ||
131 | return; | ||
132 | |||
133 | list = &ctx->event_list; | ||
134 | |||
135 | spin_lock_irqsave(&ctx->lock, flags); | ||
136 | |||
137 | /* | ||
138 | * The current breakpoint counter is not included in the list | ||
139 | * at the open() callback time | ||
140 | */ | ||
141 | list_for_each_entry(bp, list, event_entry) { | ||
142 | if (bp->attr.type == PERF_TYPE_BREAKPOINT) | ||
143 | count++; | ||
144 | } | ||
145 | |||
146 | spin_unlock_irqrestore(&ctx->lock, flags); | ||
147 | |||
148 | if (WARN_ONCE(count < 0, "No breakpoint counter found in the counter list")) | ||
149 | return; | ||
150 | |||
151 | task_bp_pinned = per_cpu(task_bp_pinned, cpu); | ||
152 | if (enable) { | ||
153 | task_bp_pinned[count]++; | ||
154 | if (count > 0) | ||
155 | task_bp_pinned[count-1]--; | ||
156 | } else { | ||
157 | task_bp_pinned[count]--; | ||
158 | if (count > 0) | ||
159 | task_bp_pinned[count-1]++; | ||
160 | } | ||
161 | } | ||
162 | |||
163 | /* | ||
164 | * Add/remove the given breakpoint in our constraint table | ||
165 | */ | ||
166 | static void toggle_bp_slot(struct perf_event *bp, bool enable) | ||
167 | { | ||
168 | int cpu = bp->cpu; | ||
169 | struct task_struct *tsk = bp->ctx->task; | ||
170 | |||
171 | /* Pinned counter task profiling */ | ||
172 | if (tsk) { | ||
173 | if (cpu >= 0) { | ||
174 | toggle_bp_task_slot(tsk, cpu, enable); | ||
175 | return; | ||
176 | } | ||
177 | |||
178 | for_each_online_cpu(cpu) | ||
179 | toggle_bp_task_slot(tsk, cpu, enable); | ||
180 | return; | ||
181 | } | ||
182 | |||
183 | /* Pinned counter cpu profiling */ | ||
184 | if (enable) | ||
185 | per_cpu(nr_cpu_bp_pinned, bp->cpu)++; | ||
186 | else | ||
187 | per_cpu(nr_cpu_bp_pinned, bp->cpu)--; | ||
188 | } | ||
189 | |||
190 | /* | ||
191 | * Contraints to check before allowing this new breakpoint counter: | ||
192 | * | ||
193 | * == Non-pinned counter == (Considered as pinned for now) | ||
194 | * | ||
195 | * - If attached to a single cpu, check: | ||
196 | * | ||
197 | * (per_cpu(nr_bp_flexible, cpu) || (per_cpu(nr_cpu_bp_pinned, cpu) | ||
198 | * + max(per_cpu(task_bp_pinned, cpu)))) < HBP_NUM | ||
199 | * | ||
200 | * -> If there are already non-pinned counters in this cpu, it means | ||
201 | * there is already a free slot for them. | ||
202 | * Otherwise, we check that the maximum number of per task | ||
203 | * breakpoints (for this cpu) plus the number of per cpu breakpoint | ||
204 | * (for this cpu) doesn't cover every registers. | ||
205 | * | ||
206 | * - If attached to every cpus, check: | ||
207 | * | ||
208 | * (per_cpu(nr_bp_flexible, *) || (max(per_cpu(nr_cpu_bp_pinned, *)) | ||
209 | * + max(per_cpu(task_bp_pinned, *)))) < HBP_NUM | ||
210 | * | ||
211 | * -> This is roughly the same, except we check the number of per cpu | ||
212 | * bp for every cpu and we keep the max one. Same for the per tasks | ||
213 | * breakpoints. | ||
214 | * | ||
215 | * | ||
216 | * == Pinned counter == | ||
217 | * | ||
218 | * - If attached to a single cpu, check: | ||
219 | * | ||
220 | * ((per_cpu(nr_bp_flexible, cpu) > 1) + per_cpu(nr_cpu_bp_pinned, cpu) | ||
221 | * + max(per_cpu(task_bp_pinned, cpu))) < HBP_NUM | ||
222 | * | ||
223 | * -> Same checks as before. But now the nr_bp_flexible, if any, must keep | ||
224 | * one register at least (or they will never be fed). | ||
225 | * | ||
226 | * - If attached to every cpus, check: | ||
227 | * | ||
228 | * ((per_cpu(nr_bp_flexible, *) > 1) + max(per_cpu(nr_cpu_bp_pinned, *)) | ||
229 | * + max(per_cpu(task_bp_pinned, *))) < HBP_NUM | ||
230 | */ | ||
231 | int reserve_bp_slot(struct perf_event *bp) | ||
232 | { | ||
233 | struct bp_busy_slots slots = {0}; | ||
234 | int ret = 0; | ||
235 | |||
236 | mutex_lock(&nr_bp_mutex); | ||
237 | |||
238 | fetch_bp_busy_slots(&slots, bp->cpu); | ||
239 | |||
240 | /* Flexible counters need to keep at least one slot */ | ||
241 | if (slots.pinned + (!!slots.flexible) == HBP_NUM) { | ||
242 | ret = -ENOSPC; | ||
243 | goto end; | ||
244 | } | ||
245 | |||
246 | toggle_bp_slot(bp, true); | ||
247 | |||
248 | end: | ||
249 | mutex_unlock(&nr_bp_mutex); | ||
250 | |||
251 | return ret; | ||
252 | } | ||
253 | |||
60 | void release_bp_slot(struct perf_event *bp) | 254 | void release_bp_slot(struct perf_event *bp) |
61 | { | 255 | { |
62 | atomic_dec(&bp_slot); | 256 | mutex_lock(&nr_bp_mutex); |
257 | |||
258 | toggle_bp_slot(bp, false); | ||
259 | |||
260 | mutex_unlock(&nr_bp_mutex); | ||
63 | } | 261 | } |
64 | 262 | ||
263 | |||
65 | int __register_perf_hw_breakpoint(struct perf_event *bp) | 264 | int __register_perf_hw_breakpoint(struct perf_event *bp) |
66 | { | 265 | { |
67 | int ret; | 266 | int ret; |