diff options
| -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; |
