Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hw-breakpoints: Arbitrate access to pmu following registers constraints

Allow or refuse to build a counter using the breakpoints pmu following
given constraints.

We keep track of the pmu users by using three per cpu variables:

- nr_cpu_bp_pinned stores the number of pinned cpu breakpoints counters
in the given cpu

- nr_bp_flexible stores the number of non-pinned breakpoints counters
in the given cpu.

- task_bp_pinned stores the number of pinned task breakpoints in a cpu

The latter is not a simple counter but gathers the number of tasks that
have n pinned breakpoints.
Considering HBP_NUM the number of available breakpoint address
registers:
task_bp_pinned[0] is the number of tasks having 1 breakpoint
task_bp_pinned[1] is the number of tasks having 2 breakpoints
[...]
task_bp_pinned[HBP_NUM - 1] is the number of tasks having the
maximum number of registers (HBP_NUM).

When a breakpoint counter is created and wants an access to the pmu,
we evaluate the following constraints:

== Non-pinned counter ==

- If attached to a single cpu, check:

(per_cpu(nr_bp_flexible, cpu) || (per_cpu(nr_cpu_bp_pinned, cpu)
+ max(per_cpu(task_bp_pinned, cpu)))) < HBP_NUM

-> If there are already non-pinned counters in this cpu, it
means there is already a free slot for them.
Otherwise, we check that the maximum number of per task
breakpoints (for this cpu) plus the number of per cpu
breakpoint (for this cpu) doesn't cover every registers.

- If attached to every cpus, check:

(per_cpu(nr_bp_flexible, *) || (max(per_cpu(nr_cpu_bp_pinned, *))
+ max(per_cpu(task_bp_pinned, *)))) < HBP_NUM

-> This is roughly the same, except we check the number of per
cpu bp for every cpu and we keep the max one. Same for the
per tasks breakpoints.

== Pinned counter ==

- If attached to a single cpu, check:

((per_cpu(nr_bp_flexible, cpu) > 1)
+ per_cpu(nr_cpu_bp_pinned, cpu)
+ max(per_cpu(task_bp_pinned, cpu))) < HBP_NUM

-> Same checks as before. But now the nr_bp_flexible, if any,
must keep one register at least (or flexible breakpoints will
never be be fed).

- If attached to every cpus, check:

((per_cpu(nr_bp_flexible, *) > 1)
+ max(per_cpu(nr_cpu_bp_pinned, *))
+ max(per_cpu(task_bp_pinned, *))) < HBP_NUM

Changes in v2:

- Counter -> event rename

Changes in v5:

- Fix unreleased non-pinned task-bound-only counters. We only released
it in the first cpu. (Thanks to Paul Mackerras for reporting that)

Changes in v6:

- Currently, events scheduling are done in this order: cpu context
pinned + cpu context non-pinned + task context pinned + task context
non-pinned events. Then our current constraints are right theoretically
but not in practice, because non-pinned counters may be scheduled
before we can apply every possible pinned counters. So consider
non-pinned counters as pinned for now.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Prasad <prasad@linux.vnet.ibm.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Jan Kiszka <jan.kiszka@web.de>
Cc: Jiri Slaby <jirislaby@gmail.com>
Cc: Li Zefan <lizf@cn.fujitsu.com>
Cc: Avi Kivity <avi@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Paul Mundt <lethal@linux-sh.org>

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