Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/xtensa/kernel/hw_breakpoint.c
26424 views
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
/* Breakpoint currently in use for each IBREAKA. */
19
static DEFINE_PER_CPU(struct perf_event *, bp_on_reg[XCHAL_NUM_IBREAK]);
20
21
/* Watchpoint currently in use for each DBREAKA. */
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
/* Type */
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
/* Len */
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
/* Address */
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
/* We don't have indexed wsr and creating instruction dynamically
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
/* Breakpoint */
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
/* Watchpoint */
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
/* Breakpoint */
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
/* Watchpoint */
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
}
310
311