| 1 | |
| 2 | * Xtensa hardware breakpoints/watchpoints handling functions
|
| 3 | *
|
| 4 | * This file is subject to the terms and conditions of the GNU General Public
|
| 5 | * License. See the file "COPYING" in the main directory of this archive
|
| 6 | * for more details.
|
| 7 | *
|
| 8 | * Copyright (C) 2016 Cadence Design Systems Inc.
|
| 9 |
|
| 10 |
|
| 11 | #include <linux/hw_breakpoint.h>
|
| 12 | #include <linux/log2.h>
|
| 13 | #include <linux/percpu.h>
|
| 14 | #include <linux/perf_event.h>
|
| 15 | #include <asm/core.h>
|
| 16 | #include <asm/hw_breakpoint.h>
|
| 17 |
|
| 18 |
|
| 19 | static DEFINE_PER_CPU(struct perf_event *, bp_on_reg[XCHAL_NUM_IBREAK]);
|
| 20 |
|
| 21 |
|
| 22 | static DEFINE_PER_CPU(struct perf_event *, wp_on_reg[XCHAL_NUM_DBREAK]);
|
| 23 |
|
| 24 | int hw_breakpoint_slots(int type)
|
| 25 | {
|
| 26 | switch (type) {
|
| 27 | case TYPE_INST:
|
| 28 | return XCHAL_NUM_IBREAK;
|
| 29 | case TYPE_DATA:
|
| 30 | return XCHAL_NUM_DBREAK;
|
| 31 | default:
|
| 32 | pr_warn("unknown slot type: %d\n", type);
|
| 33 | return 0;
|
| 34 | }
|
| 35 | }
|
| 36 |
|
| 37 | int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw)
|
| 38 | {
|
| 39 | unsigned int len;
|
| 40 | unsigned long va;
|
| 41 |
|
| 42 | va = hw->address;
|
| 43 | len = hw->len;
|
| 44 |
|
| 45 | return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);
|
| 46 | }
|
| 47 |
|
| 48 | |
| 49 | * Construct an arch_hw_breakpoint from a perf_event.
|
| 50 |
|
| 51 | int hw_breakpoint_arch_parse(struct perf_event *bp,
|
| 52 | const struct perf_event_attr *attr,
|
| 53 | struct arch_hw_breakpoint *hw)
|
| 54 | {
|
| 55 |
|
| 56 | switch (attr->bp_type) {
|
| 57 | case HW_BREAKPOINT_X:
|
| 58 | hw->type = XTENSA_BREAKPOINT_EXECUTE;
|
| 59 | break;
|
| 60 | case HW_BREAKPOINT_R:
|
| 61 | hw->type = XTENSA_BREAKPOINT_LOAD;
|
| 62 | break;
|
| 63 | case HW_BREAKPOINT_W:
|
| 64 | hw->type = XTENSA_BREAKPOINT_STORE;
|
| 65 | break;
|
| 66 | case HW_BREAKPOINT_RW:
|
| 67 | hw->type = XTENSA_BREAKPOINT_LOAD | XTENSA_BREAKPOINT_STORE;
|
| 68 | break;
|
| 69 | default:
|
| 70 | return -EINVAL;
|
| 71 | }
|
| 72 |
|
| 73 |
|
| 74 | hw->len = attr->bp_len;
|
| 75 | if (hw->len < 1 || hw->len > 64 || !is_power_of_2(hw->len))
|
| 76 | return -EINVAL;
|
| 77 |
|
| 78 |
|
| 79 | hw->address = attr->bp_addr;
|
| 80 | if (hw->address & (hw->len - 1))
|
| 81 | return -EINVAL;
|
| 82 |
|
| 83 | return 0;
|
| 84 | }
|
| 85 |
|
| 86 | int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
|
| 87 | unsigned long val, void *data)
|
| 88 | {
|
| 89 | return NOTIFY_DONE;
|
| 90 | }
|
| 91 |
|
| 92 | static void xtensa_wsr(unsigned long v, u8 sr)
|
| 93 | {
|
| 94 | |
| 95 | * doesn't seem worth it given how small XCHAL_NUM_IBREAK and
|
| 96 | * XCHAL_NUM_DBREAK are. Thus the switch. In case build breaks here
|
| 97 | * the switch below needs to be extended.
|
| 98 |
|
| 99 | BUILD_BUG_ON(XCHAL_NUM_IBREAK > 2);
|
| 100 | BUILD_BUG_ON(XCHAL_NUM_DBREAK > 2);
|
| 101 |
|
| 102 | switch (sr) {
|
| 103 | #if XCHAL_NUM_IBREAK > 0
|
| 104 | case SREG_IBREAKA + 0:
|
| 105 | xtensa_set_sr(v, SREG_IBREAKA + 0);
|
| 106 | break;
|
| 107 | #endif
|
| 108 | #if XCHAL_NUM_IBREAK > 1
|
| 109 | case SREG_IBREAKA + 1:
|
| 110 | xtensa_set_sr(v, SREG_IBREAKA + 1);
|
| 111 | break;
|
| 112 | #endif
|
| 113 |
|
| 114 | #if XCHAL_NUM_DBREAK > 0
|
| 115 | case SREG_DBREAKA + 0:
|
| 116 | xtensa_set_sr(v, SREG_DBREAKA + 0);
|
| 117 | break;
|
| 118 | case SREG_DBREAKC + 0:
|
| 119 | xtensa_set_sr(v, SREG_DBREAKC + 0);
|
| 120 | break;
|
| 121 | #endif
|
| 122 | #if XCHAL_NUM_DBREAK > 1
|
| 123 | case SREG_DBREAKA + 1:
|
| 124 | xtensa_set_sr(v, SREG_DBREAKA + 1);
|
| 125 | break;
|
| 126 |
|
| 127 | case SREG_DBREAKC + 1:
|
| 128 | xtensa_set_sr(v, SREG_DBREAKC + 1);
|
| 129 | break;
|
| 130 | #endif
|
| 131 | }
|
| 132 | }
|
| 133 |
|
| 134 | static int alloc_slot(struct perf_event **slot, size_t n,
|
| 135 | struct perf_event *bp)
|
| 136 | {
|
| 137 | size_t i;
|
| 138 |
|
| 139 | for (i = 0; i < n; ++i) {
|
| 140 | if (!slot[i]) {
|
| 141 | slot[i] = bp;
|
| 142 | return i;
|
| 143 | }
|
| 144 | }
|
| 145 | return -EBUSY;
|
| 146 | }
|
| 147 |
|
| 148 | static void set_ibreak_regs(int reg, struct perf_event *bp)
|
| 149 | {
|
| 150 | struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
| 151 | unsigned long ibreakenable;
|
| 152 |
|
| 153 | xtensa_wsr(info->address, SREG_IBREAKA + reg);
|
| 154 | ibreakenable = xtensa_get_sr(SREG_IBREAKENABLE);
|
| 155 | xtensa_set_sr(ibreakenable | (1 << reg), SREG_IBREAKENABLE);
|
| 156 | }
|
| 157 |
|
| 158 | static void set_dbreak_regs(int reg, struct perf_event *bp)
|
| 159 | {
|
| 160 | struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
| 161 | unsigned long dbreakc = DBREAKC_MASK_MASK & -info->len;
|
| 162 |
|
| 163 | if (info->type & XTENSA_BREAKPOINT_LOAD)
|
| 164 | dbreakc |= DBREAKC_LOAD_MASK;
|
| 165 | if (info->type & XTENSA_BREAKPOINT_STORE)
|
| 166 | dbreakc |= DBREAKC_STOR_MASK;
|
| 167 |
|
| 168 | xtensa_wsr(info->address, SREG_DBREAKA + reg);
|
| 169 | xtensa_wsr(dbreakc, SREG_DBREAKC + reg);
|
| 170 | }
|
| 171 |
|
| 172 | int arch_install_hw_breakpoint(struct perf_event *bp)
|
| 173 | {
|
| 174 | int i;
|
| 175 |
|
| 176 | if (counter_arch_bp(bp)->type == XTENSA_BREAKPOINT_EXECUTE) {
|
| 177 |
|
| 178 | i = alloc_slot(this_cpu_ptr(bp_on_reg), XCHAL_NUM_IBREAK, bp);
|
| 179 | if (i < 0)
|
| 180 | return i;
|
| 181 | set_ibreak_regs(i, bp);
|
| 182 |
|
| 183 | } else {
|
| 184 |
|
| 185 | i = alloc_slot(this_cpu_ptr(wp_on_reg), XCHAL_NUM_DBREAK, bp);
|
| 186 | if (i < 0)
|
| 187 | return i;
|
| 188 | set_dbreak_regs(i, bp);
|
| 189 | }
|
| 190 | return 0;
|
| 191 | }
|
| 192 |
|
| 193 | static int free_slot(struct perf_event **slot, size_t n,
|
| 194 | struct perf_event *bp)
|
| 195 | {
|
| 196 | size_t i;
|
| 197 |
|
| 198 | for (i = 0; i < n; ++i) {
|
| 199 | if (slot[i] == bp) {
|
| 200 | slot[i] = NULL;
|
| 201 | return i;
|
| 202 | }
|
| 203 | }
|
| 204 | return -EBUSY;
|
| 205 | }
|
| 206 |
|
| 207 | void arch_uninstall_hw_breakpoint(struct perf_event *bp)
|
| 208 | {
|
| 209 | struct arch_hw_breakpoint *info = counter_arch_bp(bp);
|
| 210 | int i;
|
| 211 |
|
| 212 | if (info->type == XTENSA_BREAKPOINT_EXECUTE) {
|
| 213 | unsigned long ibreakenable;
|
| 214 |
|
| 215 |
|
| 216 | i = free_slot(this_cpu_ptr(bp_on_reg), XCHAL_NUM_IBREAK, bp);
|
| 217 | if (i >= 0) {
|
| 218 | ibreakenable = xtensa_get_sr(SREG_IBREAKENABLE);
|
| 219 | xtensa_set_sr(ibreakenable & ~(1 << i),
|
| 220 | SREG_IBREAKENABLE);
|
| 221 | }
|
| 222 | } else {
|
| 223 |
|
| 224 | i = free_slot(this_cpu_ptr(wp_on_reg), XCHAL_NUM_DBREAK, bp);
|
| 225 | if (i >= 0)
|
| 226 | xtensa_wsr(0, SREG_DBREAKC + i);
|
| 227 | }
|
| 228 | }
|
| 229 |
|
| 230 | void hw_breakpoint_pmu_read(struct perf_event *bp)
|
| 231 | {
|
| 232 | }
|
| 233 |
|
| 234 | void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
|
| 235 | {
|
| 236 | int i;
|
| 237 | struct thread_struct *t = &tsk->thread;
|
| 238 |
|
| 239 | for (i = 0; i < XCHAL_NUM_IBREAK; ++i) {
|
| 240 | if (t->ptrace_bp[i]) {
|
| 241 | unregister_hw_breakpoint(t->ptrace_bp[i]);
|
| 242 | t->ptrace_bp[i] = NULL;
|
| 243 | }
|
| 244 | }
|
| 245 | for (i = 0; i < XCHAL_NUM_DBREAK; ++i) {
|
| 246 | if (t->ptrace_wp[i]) {
|
| 247 | unregister_hw_breakpoint(t->ptrace_wp[i]);
|
| 248 | t->ptrace_wp[i] = NULL;
|
| 249 | }
|
| 250 | }
|
| 251 | }
|
| 252 |
|
| 253 | |
| 254 | * Set ptrace breakpoint pointers to zero for this task.
|
| 255 | * This is required in order to prevent child processes from unregistering
|
| 256 | * breakpoints held by their parent.
|
| 257 |
|
| 258 | void clear_ptrace_hw_breakpoint(struct task_struct *tsk)
|
| 259 | {
|
| 260 | memset(tsk->thread.ptrace_bp, 0, sizeof(tsk->thread.ptrace_bp));
|
| 261 | memset(tsk->thread.ptrace_wp, 0, sizeof(tsk->thread.ptrace_wp));
|
| 262 | }
|
| 263 |
|
| 264 | void restore_dbreak(void)
|
| 265 | {
|
| 266 | int i;
|
| 267 |
|
| 268 | for (i = 0; i < XCHAL_NUM_DBREAK; ++i) {
|
| 269 | struct perf_event *bp = this_cpu_ptr(wp_on_reg)[i];
|
| 270 |
|
| 271 | if (bp)
|
| 272 | set_dbreak_regs(i, bp);
|
| 273 | }
|
| 274 | clear_thread_flag(TIF_DB_DISABLED);
|
| 275 | }
|
| 276 |
|
| 277 | int check_hw_breakpoint(struct pt_regs *regs)
|
| 278 | {
|
| 279 | if (regs->debugcause & BIT(DEBUGCAUSE_IBREAK_BIT)) {
|
| 280 | int i;
|
| 281 | struct perf_event **bp = this_cpu_ptr(bp_on_reg);
|
| 282 |
|
| 283 | for (i = 0; i < XCHAL_NUM_IBREAK; ++i) {
|
| 284 | if (bp[i] && !bp[i]->attr.disabled &&
|
| 285 | regs->pc == bp[i]->attr.bp_addr)
|
| 286 | perf_bp_event(bp[i], regs);
|
| 287 | }
|
| 288 | return 0;
|
| 289 | } else if (regs->debugcause & BIT(DEBUGCAUSE_DBREAK_BIT)) {
|
| 290 | struct perf_event **bp = this_cpu_ptr(wp_on_reg);
|
| 291 | int dbnum = (regs->debugcause & DEBUGCAUSE_DBNUM_MASK) >>
|
| 292 | DEBUGCAUSE_DBNUM_SHIFT;
|
| 293 |
|
| 294 | if (dbnum < XCHAL_NUM_DBREAK && bp[dbnum]) {
|
| 295 | if (user_mode(regs)) {
|
| 296 | perf_bp_event(bp[dbnum], regs);
|
| 297 | } else {
|
| 298 | set_thread_flag(TIF_DB_DISABLED);
|
| 299 | xtensa_wsr(0, SREG_DBREAKC + dbnum);
|
| 300 | }
|
| 301 | } else {
|
| 302 | WARN_ONCE(1,
|
| 303 | "Wrong/unconfigured DBNUM reported in DEBUGCAUSE: %d\n",
|
| 304 | dbnum);
|
| 305 | }
|
| 306 | return 0;
|
| 307 | }
|
| 308 | return -ENOENT;
|
| 309 | }
|