aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/hw_breakpoint.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/hw_breakpoint.c')
-rw-r--r--kernel/hw_breakpoint.c211
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
47static atomic_t bp_slot; 49/*
50 * Constraints data
51 */
52
53/* Number of pinned cpu breakpoints in a cpu */
54static DEFINE_PER_CPU(unsigned int, nr_cpu_bp_pinned);
48 55
49int reserve_bp_slot(struct perf_event *bp) 56/* Number of pinned task breakpoints in a cpu */
57static DEFINE_PER_CPU(unsigned int, task_bp_pinned[HBP_NUM]);
58
59/* Number of non-pinned cpu/task breakpoints in a cpu */
60static DEFINE_PER_CPU(unsigned int, nr_bp_flexible);
61
62/* Gather the number of total pinned and un-pinned bp in a cpuset */
63struct bp_busy_slots {
64 unsigned int pinned;
65 unsigned int flexible;
66};
67
68/* Serialize accesses to the above constraints */
69static DEFINE_MUTEX(nr_bp_mutex);
70
71/*
72 * Report the maximum number of pinned breakpoints a task
73 * have in this cpu
74 */
75static 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 */
92static 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 */
121static 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 */
166static 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 */
231int 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
248end:
249 mutex_unlock(&nr_bp_mutex);
250
251 return ret;
252}
253
60void release_bp_slot(struct perf_event *bp) 254void 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
65int __register_perf_hw_breakpoint(struct perf_event *bp) 264int __register_perf_hw_breakpoint(struct perf_event *bp)
66{ 265{
67 int ret; 266 int ret;