1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Author: Hanlu Li <lihanlu@loongson.cn> |
4 | * Huacai Chen <chenhuacai@loongson.cn> |
5 | * |
6 | * Copyright (C) 2020-2022 Loongson Technology Corporation Limited |
7 | * |
8 | * Derived from MIPS: |
9 | * Copyright (C) 1992 Ross Biro |
10 | * Copyright (C) Linus Torvalds |
11 | * Copyright (C) 1994, 95, 96, 97, 98, 2000 Ralf Baechle |
12 | * Copyright (C) 1996 David S. Miller |
13 | * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com |
14 | * Copyright (C) 1999 MIPS Technologies, Inc. |
15 | * Copyright (C) 2000 Ulf Carlsson |
16 | */ |
17 | #include <linux/kernel.h> |
18 | #include <linux/audit.h> |
19 | #include <linux/compiler.h> |
20 | #include <linux/context_tracking.h> |
21 | #include <linux/elf.h> |
22 | #include <linux/errno.h> |
23 | #include <linux/hw_breakpoint.h> |
24 | #include <linux/mm.h> |
25 | #include <linux/nospec.h> |
26 | #include <linux/ptrace.h> |
27 | #include <linux/regset.h> |
28 | #include <linux/sched.h> |
29 | #include <linux/sched/task_stack.h> |
30 | #include <linux/security.h> |
31 | #include <linux/smp.h> |
32 | #include <linux/stddef.h> |
33 | #include <linux/seccomp.h> |
34 | #include <linux/thread_info.h> |
35 | #include <linux/uaccess.h> |
36 | |
37 | #include <asm/byteorder.h> |
38 | #include <asm/cpu.h> |
39 | #include <asm/cpu-info.h> |
40 | #include <asm/fpu.h> |
41 | #include <asm/lbt.h> |
42 | #include <asm/loongarch.h> |
43 | #include <asm/page.h> |
44 | #include <asm/pgtable.h> |
45 | #include <asm/processor.h> |
46 | #include <asm/ptrace.h> |
47 | #include <asm/reg.h> |
48 | #include <asm/syscall.h> |
49 | |
50 | static void init_fp_ctx(struct task_struct *target) |
51 | { |
52 | /* The target already has context */ |
53 | if (tsk_used_math(target)) |
54 | return; |
55 | |
56 | /* Begin with data registers set to all 1s... */ |
57 | memset(&target->thread.fpu.fpr, ~0, sizeof(target->thread.fpu.fpr)); |
58 | set_stopped_child_used_math(target); |
59 | } |
60 | |
61 | /* |
62 | * Called by kernel/ptrace.c when detaching.. |
63 | * |
64 | * Make sure single step bits etc are not set. |
65 | */ |
66 | void ptrace_disable(struct task_struct *child) |
67 | { |
68 | /* Don't load the watchpoint registers for the ex-child. */ |
69 | clear_tsk_thread_flag(tsk: child, flag: TIF_LOAD_WATCH); |
70 | clear_tsk_thread_flag(tsk: child, TIF_SINGLESTEP); |
71 | } |
72 | |
73 | /* regset get/set implementations */ |
74 | |
75 | static int gpr_get(struct task_struct *target, |
76 | const struct user_regset *regset, |
77 | struct membuf to) |
78 | { |
79 | int r; |
80 | struct pt_regs *regs = task_pt_regs(target); |
81 | |
82 | r = membuf_write(s: &to, v: ®s->regs, size: sizeof(u64) * GPR_NUM); |
83 | r = membuf_write(s: &to, v: ®s->orig_a0, size: sizeof(u64)); |
84 | r = membuf_write(s: &to, v: ®s->csr_era, size: sizeof(u64)); |
85 | r = membuf_write(s: &to, v: ®s->csr_badvaddr, size: sizeof(u64)); |
86 | |
87 | return r; |
88 | } |
89 | |
90 | static int gpr_set(struct task_struct *target, |
91 | const struct user_regset *regset, |
92 | unsigned int pos, unsigned int count, |
93 | const void *kbuf, const void __user *ubuf) |
94 | { |
95 | int err; |
96 | int a0_start = sizeof(u64) * GPR_NUM; |
97 | int era_start = a0_start + sizeof(u64); |
98 | int badvaddr_start = era_start + sizeof(u64); |
99 | struct pt_regs *regs = task_pt_regs(target); |
100 | |
101 | err = user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
102 | data: ®s->regs, |
103 | start_pos: 0, end_pos: a0_start); |
104 | err |= user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
105 | data: ®s->orig_a0, |
106 | start_pos: a0_start, end_pos: a0_start + sizeof(u64)); |
107 | err |= user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
108 | data: ®s->csr_era, |
109 | start_pos: era_start, end_pos: era_start + sizeof(u64)); |
110 | err |= user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
111 | data: ®s->csr_badvaddr, |
112 | start_pos: badvaddr_start, end_pos: badvaddr_start + sizeof(u64)); |
113 | |
114 | return err; |
115 | } |
116 | |
117 | |
118 | /* |
119 | * Get the general floating-point registers. |
120 | */ |
121 | static int gfpr_get(struct task_struct *target, struct membuf *to) |
122 | { |
123 | return membuf_write(to, &target->thread.fpu.fpr, |
124 | sizeof(elf_fpreg_t) * NUM_FPU_REGS); |
125 | } |
126 | |
127 | static int gfpr_get_simd(struct task_struct *target, struct membuf *to) |
128 | { |
129 | int i, r; |
130 | u64 fpr_val; |
131 | |
132 | BUILD_BUG_ON(sizeof(fpr_val) != sizeof(elf_fpreg_t)); |
133 | for (i = 0; i < NUM_FPU_REGS; i++) { |
134 | fpr_val = get_fpr64(&target->thread.fpu.fpr[i], 0); |
135 | r = membuf_write(to, &fpr_val, sizeof(elf_fpreg_t)); |
136 | } |
137 | |
138 | return r; |
139 | } |
140 | |
141 | /* |
142 | * Choose the appropriate helper for general registers, and then copy |
143 | * the FCC and FCSR registers separately. |
144 | */ |
145 | static int fpr_get(struct task_struct *target, |
146 | const struct user_regset *regset, |
147 | struct membuf to) |
148 | { |
149 | int r; |
150 | |
151 | save_fpu_regs(target); |
152 | |
153 | if (sizeof(target->thread.fpu.fpr[0]) == sizeof(elf_fpreg_t)) |
154 | r = gfpr_get(target, to: &to); |
155 | else |
156 | r = gfpr_get_simd(target, to: &to); |
157 | |
158 | r = membuf_write(s: &to, v: &target->thread.fpu.fcc, size: sizeof(target->thread.fpu.fcc)); |
159 | r = membuf_write(s: &to, v: &target->thread.fpu.fcsr, size: sizeof(target->thread.fpu.fcsr)); |
160 | |
161 | return r; |
162 | } |
163 | |
164 | static int gfpr_set(struct task_struct *target, |
165 | unsigned int *pos, unsigned int *count, |
166 | const void **kbuf, const void __user **ubuf) |
167 | { |
168 | return user_regset_copyin(pos, count, kbuf, ubuf, |
169 | &target->thread.fpu.fpr, |
170 | 0, NUM_FPU_REGS * sizeof(elf_fpreg_t)); |
171 | } |
172 | |
173 | static int gfpr_set_simd(struct task_struct *target, |
174 | unsigned int *pos, unsigned int *count, |
175 | const void **kbuf, const void __user **ubuf) |
176 | { |
177 | int i, err; |
178 | u64 fpr_val; |
179 | |
180 | BUILD_BUG_ON(sizeof(fpr_val) != sizeof(elf_fpreg_t)); |
181 | for (i = 0; i < NUM_FPU_REGS && *count > 0; i++) { |
182 | err = user_regset_copyin(pos, count, kbuf, ubuf, |
183 | &fpr_val, i * sizeof(elf_fpreg_t), |
184 | (i + 1) * sizeof(elf_fpreg_t)); |
185 | if (err) |
186 | return err; |
187 | set_fpr64(&target->thread.fpu.fpr[i], 0, fpr_val); |
188 | } |
189 | |
190 | return 0; |
191 | } |
192 | |
193 | /* |
194 | * Choose the appropriate helper for general registers, and then copy |
195 | * the FCC register separately. |
196 | */ |
197 | static int fpr_set(struct task_struct *target, |
198 | const struct user_regset *regset, |
199 | unsigned int pos, unsigned int count, |
200 | const void *kbuf, const void __user *ubuf) |
201 | { |
202 | const int fcc_start = NUM_FPU_REGS * sizeof(elf_fpreg_t); |
203 | const int fcsr_start = fcc_start + sizeof(u64); |
204 | int err; |
205 | |
206 | BUG_ON(count % sizeof(elf_fpreg_t)); |
207 | if (pos + count > sizeof(elf_fpregset_t)) |
208 | return -EIO; |
209 | |
210 | init_fp_ctx(target); |
211 | |
212 | if (sizeof(target->thread.fpu.fpr[0]) == sizeof(elf_fpreg_t)) |
213 | err = gfpr_set(target, pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf); |
214 | else |
215 | err = gfpr_set_simd(target, pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf); |
216 | if (err) |
217 | return err; |
218 | |
219 | err |= user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
220 | data: &target->thread.fpu.fcc, start_pos: fcc_start, |
221 | end_pos: fcc_start + sizeof(u64)); |
222 | err |= user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
223 | data: &target->thread.fpu.fcsr, start_pos: fcsr_start, |
224 | end_pos: fcsr_start + sizeof(u32)); |
225 | |
226 | return err; |
227 | } |
228 | |
229 | static int cfg_get(struct task_struct *target, |
230 | const struct user_regset *regset, |
231 | struct membuf to) |
232 | { |
233 | int i, r; |
234 | u32 cfg_val; |
235 | |
236 | i = 0; |
237 | while (to.left > 0) { |
238 | cfg_val = read_cpucfg(i++); |
239 | r = membuf_write(s: &to, v: &cfg_val, size: sizeof(u32)); |
240 | } |
241 | |
242 | return r; |
243 | } |
244 | |
245 | /* |
246 | * CFG registers are read-only. |
247 | */ |
248 | static int cfg_set(struct task_struct *target, |
249 | const struct user_regset *regset, |
250 | unsigned int pos, unsigned int count, |
251 | const void *kbuf, const void __user *ubuf) |
252 | { |
253 | return 0; |
254 | } |
255 | |
256 | #ifdef CONFIG_CPU_HAS_LSX |
257 | |
258 | static void copy_pad_fprs(struct task_struct *target, |
259 | const struct user_regset *regset, |
260 | struct membuf *to, unsigned int live_sz) |
261 | { |
262 | int i, j; |
263 | unsigned long long fill = ~0ull; |
264 | unsigned int cp_sz, pad_sz; |
265 | |
266 | cp_sz = min(regset->size, live_sz); |
267 | pad_sz = regset->size - cp_sz; |
268 | WARN_ON(pad_sz % sizeof(fill)); |
269 | |
270 | for (i = 0; i < NUM_FPU_REGS; i++) { |
271 | membuf_write(to, &target->thread.fpu.fpr[i], cp_sz); |
272 | for (j = 0; j < (pad_sz / sizeof(fill)); j++) { |
273 | membuf_store(to, fill); |
274 | } |
275 | } |
276 | } |
277 | |
278 | static int simd_get(struct task_struct *target, |
279 | const struct user_regset *regset, |
280 | struct membuf to) |
281 | { |
282 | const unsigned int wr_size = NUM_FPU_REGS * regset->size; |
283 | |
284 | save_fpu_regs(target); |
285 | |
286 | if (!tsk_used_math(target)) { |
287 | /* The task hasn't used FP or LSX, fill with 0xff */ |
288 | copy_pad_fprs(target, regset, &to, 0); |
289 | } else if (!test_tsk_thread_flag(target, TIF_LSX_CTX_LIVE)) { |
290 | /* Copy scalar FP context, fill the rest with 0xff */ |
291 | copy_pad_fprs(target, regset, &to, 8); |
292 | #ifdef CONFIG_CPU_HAS_LASX |
293 | } else if (!test_tsk_thread_flag(target, TIF_LASX_CTX_LIVE)) { |
294 | /* Copy LSX 128 Bit context, fill the rest with 0xff */ |
295 | copy_pad_fprs(target, regset, &to, 16); |
296 | #endif |
297 | } else if (sizeof(target->thread.fpu.fpr[0]) == regset->size) { |
298 | /* Trivially copy the vector registers */ |
299 | membuf_write(&to, &target->thread.fpu.fpr, wr_size); |
300 | } else { |
301 | /* Copy as much context as possible, fill the rest with 0xff */ |
302 | copy_pad_fprs(target, regset, &to, sizeof(target->thread.fpu.fpr[0])); |
303 | } |
304 | |
305 | return 0; |
306 | } |
307 | |
308 | static int simd_set(struct task_struct *target, |
309 | const struct user_regset *regset, |
310 | unsigned int pos, unsigned int count, |
311 | const void *kbuf, const void __user *ubuf) |
312 | { |
313 | const unsigned int wr_size = NUM_FPU_REGS * regset->size; |
314 | unsigned int cp_sz; |
315 | int i, err, start; |
316 | |
317 | init_fp_ctx(target); |
318 | |
319 | if (sizeof(target->thread.fpu.fpr[0]) == regset->size) { |
320 | /* Trivially copy the vector registers */ |
321 | err = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
322 | &target->thread.fpu.fpr, |
323 | 0, wr_size); |
324 | } else { |
325 | /* Copy as much context as possible */ |
326 | cp_sz = min_t(unsigned int, regset->size, |
327 | sizeof(target->thread.fpu.fpr[0])); |
328 | |
329 | i = start = err = 0; |
330 | for (; i < NUM_FPU_REGS; i++, start += regset->size) { |
331 | err |= user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
332 | &target->thread.fpu.fpr[i], |
333 | start, start + cp_sz); |
334 | } |
335 | } |
336 | |
337 | return err; |
338 | } |
339 | |
340 | #endif /* CONFIG_CPU_HAS_LSX */ |
341 | |
342 | #ifdef CONFIG_CPU_HAS_LBT |
343 | static int lbt_get(struct task_struct *target, |
344 | const struct user_regset *regset, |
345 | struct membuf to) |
346 | { |
347 | int r; |
348 | |
349 | r = membuf_write(&to, &target->thread.lbt.scr0, sizeof(target->thread.lbt.scr0)); |
350 | r = membuf_write(&to, &target->thread.lbt.scr1, sizeof(target->thread.lbt.scr1)); |
351 | r = membuf_write(&to, &target->thread.lbt.scr2, sizeof(target->thread.lbt.scr2)); |
352 | r = membuf_write(&to, &target->thread.lbt.scr3, sizeof(target->thread.lbt.scr3)); |
353 | r = membuf_write(&to, &target->thread.lbt.eflags, sizeof(u32)); |
354 | r = membuf_write(&to, &target->thread.fpu.ftop, sizeof(u32)); |
355 | |
356 | return r; |
357 | } |
358 | |
359 | static int lbt_set(struct task_struct *target, |
360 | const struct user_regset *regset, |
361 | unsigned int pos, unsigned int count, |
362 | const void *kbuf, const void __user *ubuf) |
363 | { |
364 | int err = 0; |
365 | const int eflags_start = 4 * sizeof(target->thread.lbt.scr0); |
366 | const int ftop_start = eflags_start + sizeof(u32); |
367 | |
368 | err |= user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
369 | &target->thread.lbt.scr0, |
370 | 0, 4 * sizeof(target->thread.lbt.scr0)); |
371 | err |= user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
372 | &target->thread.lbt.eflags, |
373 | eflags_start, ftop_start); |
374 | err |= user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
375 | &target->thread.fpu.ftop, |
376 | ftop_start, ftop_start + sizeof(u32)); |
377 | |
378 | return err; |
379 | } |
380 | #endif /* CONFIG_CPU_HAS_LBT */ |
381 | |
382 | #ifdef CONFIG_HAVE_HW_BREAKPOINT |
383 | |
384 | /* |
385 | * Handle hitting a HW-breakpoint. |
386 | */ |
387 | static void ptrace_hbptriggered(struct perf_event *bp, |
388 | struct perf_sample_data *data, |
389 | struct pt_regs *regs) |
390 | { |
391 | int i; |
392 | struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp); |
393 | |
394 | for (i = 0; i < LOONGARCH_MAX_BRP; ++i) |
395 | if (current->thread.hbp_break[i] == bp) |
396 | break; |
397 | |
398 | for (i = 0; i < LOONGARCH_MAX_WRP; ++i) |
399 | if (current->thread.hbp_watch[i] == bp) |
400 | break; |
401 | |
402 | force_sig_ptrace_errno_trap(errno: i, addr: (void __user *)bkpt->address); |
403 | } |
404 | |
405 | static struct perf_event *ptrace_hbp_get_event(unsigned int note_type, |
406 | struct task_struct *tsk, |
407 | unsigned long idx) |
408 | { |
409 | struct perf_event *bp; |
410 | |
411 | switch (note_type) { |
412 | case NT_LOONGARCH_HW_BREAK: |
413 | if (idx >= LOONGARCH_MAX_BRP) |
414 | return ERR_PTR(error: -EINVAL); |
415 | idx = array_index_nospec(idx, LOONGARCH_MAX_BRP); |
416 | bp = tsk->thread.hbp_break[idx]; |
417 | break; |
418 | case NT_LOONGARCH_HW_WATCH: |
419 | if (idx >= LOONGARCH_MAX_WRP) |
420 | return ERR_PTR(error: -EINVAL); |
421 | idx = array_index_nospec(idx, LOONGARCH_MAX_WRP); |
422 | bp = tsk->thread.hbp_watch[idx]; |
423 | break; |
424 | } |
425 | |
426 | return bp; |
427 | } |
428 | |
429 | static int ptrace_hbp_set_event(unsigned int note_type, |
430 | struct task_struct *tsk, |
431 | unsigned long idx, |
432 | struct perf_event *bp) |
433 | { |
434 | switch (note_type) { |
435 | case NT_LOONGARCH_HW_BREAK: |
436 | if (idx >= LOONGARCH_MAX_BRP) |
437 | return -EINVAL; |
438 | idx = array_index_nospec(idx, LOONGARCH_MAX_BRP); |
439 | tsk->thread.hbp_break[idx] = bp; |
440 | break; |
441 | case NT_LOONGARCH_HW_WATCH: |
442 | if (idx >= LOONGARCH_MAX_WRP) |
443 | return -EINVAL; |
444 | idx = array_index_nospec(idx, LOONGARCH_MAX_WRP); |
445 | tsk->thread.hbp_watch[idx] = bp; |
446 | break; |
447 | } |
448 | |
449 | return 0; |
450 | } |
451 | |
452 | static struct perf_event *ptrace_hbp_create(unsigned int note_type, |
453 | struct task_struct *tsk, |
454 | unsigned long idx) |
455 | { |
456 | int err, type; |
457 | struct perf_event *bp; |
458 | struct perf_event_attr attr; |
459 | |
460 | switch (note_type) { |
461 | case NT_LOONGARCH_HW_BREAK: |
462 | type = HW_BREAKPOINT_X; |
463 | break; |
464 | case NT_LOONGARCH_HW_WATCH: |
465 | type = HW_BREAKPOINT_RW; |
466 | break; |
467 | default: |
468 | return ERR_PTR(error: -EINVAL); |
469 | } |
470 | |
471 | ptrace_breakpoint_init(attr: &attr); |
472 | |
473 | /* |
474 | * Initialise fields to sane defaults |
475 | * (i.e. values that will pass validation). |
476 | */ |
477 | attr.bp_addr = 0; |
478 | attr.bp_len = HW_BREAKPOINT_LEN_4; |
479 | attr.bp_type = type; |
480 | attr.disabled = 1; |
481 | |
482 | bp = register_user_hw_breakpoint(attr: &attr, triggered: ptrace_hbptriggered, NULL, tsk); |
483 | if (IS_ERR(ptr: bp)) |
484 | return bp; |
485 | |
486 | err = ptrace_hbp_set_event(note_type, tsk, idx, bp); |
487 | if (err) |
488 | return ERR_PTR(error: err); |
489 | |
490 | return bp; |
491 | } |
492 | |
493 | static int ptrace_hbp_fill_attr_ctrl(unsigned int note_type, |
494 | struct arch_hw_breakpoint_ctrl ctrl, |
495 | struct perf_event_attr *attr) |
496 | { |
497 | int err, len, type, offset; |
498 | |
499 | err = arch_bp_generic_fields(x86_len: ctrl, x86_type: &len, gen_len: &type, gen_type: &offset); |
500 | if (err) |
501 | return err; |
502 | |
503 | switch (note_type) { |
504 | case NT_LOONGARCH_HW_BREAK: |
505 | if ((type & HW_BREAKPOINT_X) != type) |
506 | return -EINVAL; |
507 | break; |
508 | case NT_LOONGARCH_HW_WATCH: |
509 | if ((type & HW_BREAKPOINT_RW) != type) |
510 | return -EINVAL; |
511 | break; |
512 | default: |
513 | return -EINVAL; |
514 | } |
515 | |
516 | attr->bp_len = len; |
517 | attr->bp_type = type; |
518 | attr->bp_addr += offset; |
519 | |
520 | return 0; |
521 | } |
522 | |
523 | static int ptrace_hbp_get_resource_info(unsigned int note_type, u64 *info) |
524 | { |
525 | u8 num; |
526 | u64 reg = 0; |
527 | |
528 | switch (note_type) { |
529 | case NT_LOONGARCH_HW_BREAK: |
530 | num = hw_breakpoint_slots(TYPE_INST); |
531 | break; |
532 | case NT_LOONGARCH_HW_WATCH: |
533 | num = hw_breakpoint_slots(TYPE_DATA); |
534 | break; |
535 | default: |
536 | return -EINVAL; |
537 | } |
538 | |
539 | *info = reg | num; |
540 | |
541 | return 0; |
542 | } |
543 | |
544 | static struct perf_event *ptrace_hbp_get_initialised_bp(unsigned int note_type, |
545 | struct task_struct *tsk, |
546 | unsigned long idx) |
547 | { |
548 | struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx); |
549 | |
550 | if (!bp) |
551 | bp = ptrace_hbp_create(note_type, tsk, idx); |
552 | |
553 | return bp; |
554 | } |
555 | |
556 | static int ptrace_hbp_get_ctrl(unsigned int note_type, |
557 | struct task_struct *tsk, |
558 | unsigned long idx, u32 *ctrl) |
559 | { |
560 | struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx); |
561 | |
562 | if (IS_ERR(ptr: bp)) |
563 | return PTR_ERR(ptr: bp); |
564 | |
565 | *ctrl = bp ? encode_ctrl_reg(counter_arch_bp(bp)->ctrl) : 0; |
566 | |
567 | return 0; |
568 | } |
569 | |
570 | static int ptrace_hbp_get_mask(unsigned int note_type, |
571 | struct task_struct *tsk, |
572 | unsigned long idx, u64 *mask) |
573 | { |
574 | struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx); |
575 | |
576 | if (IS_ERR(ptr: bp)) |
577 | return PTR_ERR(ptr: bp); |
578 | |
579 | *mask = bp ? counter_arch_bp(bp)->mask : 0; |
580 | |
581 | return 0; |
582 | } |
583 | |
584 | static int ptrace_hbp_get_addr(unsigned int note_type, |
585 | struct task_struct *tsk, |
586 | unsigned long idx, u64 *addr) |
587 | { |
588 | struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx); |
589 | |
590 | if (IS_ERR(ptr: bp)) |
591 | return PTR_ERR(ptr: bp); |
592 | |
593 | *addr = bp ? counter_arch_bp(bp)->address : 0; |
594 | |
595 | return 0; |
596 | } |
597 | |
598 | static int ptrace_hbp_set_ctrl(unsigned int note_type, |
599 | struct task_struct *tsk, |
600 | unsigned long idx, u32 uctrl) |
601 | { |
602 | int err; |
603 | struct perf_event *bp; |
604 | struct perf_event_attr attr; |
605 | struct arch_hw_breakpoint_ctrl ctrl; |
606 | |
607 | bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx); |
608 | if (IS_ERR(ptr: bp)) |
609 | return PTR_ERR(ptr: bp); |
610 | |
611 | attr = bp->attr; |
612 | decode_ctrl_reg(uctrl, &ctrl); |
613 | err = ptrace_hbp_fill_attr_ctrl(note_type, ctrl: ctrl, attr: &attr); |
614 | if (err) |
615 | return err; |
616 | |
617 | return modify_user_hw_breakpoint(bp, attr: &attr); |
618 | } |
619 | |
620 | static int ptrace_hbp_set_mask(unsigned int note_type, |
621 | struct task_struct *tsk, |
622 | unsigned long idx, u64 mask) |
623 | { |
624 | struct perf_event *bp; |
625 | struct perf_event_attr attr; |
626 | struct arch_hw_breakpoint *info; |
627 | |
628 | bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx); |
629 | if (IS_ERR(ptr: bp)) |
630 | return PTR_ERR(ptr: bp); |
631 | |
632 | attr = bp->attr; |
633 | info = counter_arch_bp(bp); |
634 | info->mask = mask; |
635 | |
636 | return modify_user_hw_breakpoint(bp, attr: &attr); |
637 | } |
638 | |
639 | static int ptrace_hbp_set_addr(unsigned int note_type, |
640 | struct task_struct *tsk, |
641 | unsigned long idx, u64 addr) |
642 | { |
643 | struct perf_event *bp; |
644 | struct perf_event_attr attr; |
645 | |
646 | bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx); |
647 | if (IS_ERR(ptr: bp)) |
648 | return PTR_ERR(ptr: bp); |
649 | |
650 | attr = bp->attr; |
651 | attr.bp_addr = addr; |
652 | |
653 | return modify_user_hw_breakpoint(bp, attr: &attr); |
654 | } |
655 | |
656 | #define PTRACE_HBP_ADDR_SZ sizeof(u64) |
657 | #define PTRACE_HBP_MASK_SZ sizeof(u64) |
658 | #define PTRACE_HBP_CTRL_SZ sizeof(u32) |
659 | #define PTRACE_HBP_PAD_SZ sizeof(u32) |
660 | |
661 | static int hw_break_get(struct task_struct *target, |
662 | const struct user_regset *regset, |
663 | struct membuf to) |
664 | { |
665 | u64 info; |
666 | u32 ctrl; |
667 | u64 addr, mask; |
668 | int ret, idx = 0; |
669 | unsigned int note_type = regset->core_note_type; |
670 | |
671 | /* Resource info */ |
672 | ret = ptrace_hbp_get_resource_info(note_type, info: &info); |
673 | if (ret) |
674 | return ret; |
675 | |
676 | membuf_write(s: &to, v: &info, size: sizeof(info)); |
677 | |
678 | /* (address, mask, ctrl) registers */ |
679 | while (to.left) { |
680 | ret = ptrace_hbp_get_addr(note_type, tsk: target, idx, addr: &addr); |
681 | if (ret) |
682 | return ret; |
683 | |
684 | ret = ptrace_hbp_get_mask(note_type, tsk: target, idx, mask: &mask); |
685 | if (ret) |
686 | return ret; |
687 | |
688 | ret = ptrace_hbp_get_ctrl(note_type, tsk: target, idx, ctrl: &ctrl); |
689 | if (ret) |
690 | return ret; |
691 | |
692 | membuf_store(&to, addr); |
693 | membuf_store(&to, mask); |
694 | membuf_store(&to, ctrl); |
695 | membuf_zero(s: &to, size: sizeof(u32)); |
696 | idx++; |
697 | } |
698 | |
699 | return 0; |
700 | } |
701 | |
702 | static int hw_break_set(struct task_struct *target, |
703 | const struct user_regset *regset, |
704 | unsigned int pos, unsigned int count, |
705 | const void *kbuf, const void __user *ubuf) |
706 | { |
707 | u32 ctrl; |
708 | u64 addr, mask; |
709 | int ret, idx = 0, offset, limit; |
710 | unsigned int note_type = regset->core_note_type; |
711 | |
712 | /* Resource info */ |
713 | offset = offsetof(struct user_watch_state, dbg_regs); |
714 | user_regset_copyin_ignore(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, start_pos: 0, end_pos: offset); |
715 | |
716 | /* (address, mask, ctrl) registers */ |
717 | limit = regset->n * regset->size; |
718 | while (count && offset < limit) { |
719 | if (count < PTRACE_HBP_ADDR_SZ) |
720 | return -EINVAL; |
721 | |
722 | ret = user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, data: &addr, |
723 | start_pos: offset, end_pos: offset + PTRACE_HBP_ADDR_SZ); |
724 | if (ret) |
725 | return ret; |
726 | |
727 | ret = ptrace_hbp_set_addr(note_type, tsk: target, idx, addr); |
728 | if (ret) |
729 | return ret; |
730 | offset += PTRACE_HBP_ADDR_SZ; |
731 | |
732 | if (!count) |
733 | break; |
734 | |
735 | ret = user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, data: &mask, |
736 | start_pos: offset, end_pos: offset + PTRACE_HBP_MASK_SZ); |
737 | if (ret) |
738 | return ret; |
739 | |
740 | ret = ptrace_hbp_set_mask(note_type, tsk: target, idx, mask); |
741 | if (ret) |
742 | return ret; |
743 | offset += PTRACE_HBP_MASK_SZ; |
744 | |
745 | ret = user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, data: &ctrl, |
746 | start_pos: offset, end_pos: offset + PTRACE_HBP_CTRL_SZ); |
747 | if (ret) |
748 | return ret; |
749 | |
750 | ret = ptrace_hbp_set_ctrl(note_type, tsk: target, idx, uctrl: ctrl); |
751 | if (ret) |
752 | return ret; |
753 | offset += PTRACE_HBP_CTRL_SZ; |
754 | |
755 | user_regset_copyin_ignore(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, |
756 | start_pos: offset, end_pos: offset + PTRACE_HBP_PAD_SZ); |
757 | offset += PTRACE_HBP_PAD_SZ; |
758 | |
759 | idx++; |
760 | } |
761 | |
762 | return 0; |
763 | } |
764 | |
765 | #endif |
766 | |
767 | struct pt_regs_offset { |
768 | const char *name; |
769 | int offset; |
770 | }; |
771 | |
772 | #define REG_OFFSET_NAME(n, r) {.name = #n, .offset = offsetof(struct pt_regs, r)} |
773 | #define REG_OFFSET_END {.name = NULL, .offset = 0} |
774 | |
775 | static const struct pt_regs_offset regoffset_table[] = { |
776 | REG_OFFSET_NAME(r0, regs[0]), |
777 | REG_OFFSET_NAME(r1, regs[1]), |
778 | REG_OFFSET_NAME(r2, regs[2]), |
779 | REG_OFFSET_NAME(r3, regs[3]), |
780 | REG_OFFSET_NAME(r4, regs[4]), |
781 | REG_OFFSET_NAME(r5, regs[5]), |
782 | REG_OFFSET_NAME(r6, regs[6]), |
783 | REG_OFFSET_NAME(r7, regs[7]), |
784 | REG_OFFSET_NAME(r8, regs[8]), |
785 | REG_OFFSET_NAME(r9, regs[9]), |
786 | REG_OFFSET_NAME(r10, regs[10]), |
787 | REG_OFFSET_NAME(r11, regs[11]), |
788 | REG_OFFSET_NAME(r12, regs[12]), |
789 | REG_OFFSET_NAME(r13, regs[13]), |
790 | REG_OFFSET_NAME(r14, regs[14]), |
791 | REG_OFFSET_NAME(r15, regs[15]), |
792 | REG_OFFSET_NAME(r16, regs[16]), |
793 | REG_OFFSET_NAME(r17, regs[17]), |
794 | REG_OFFSET_NAME(r18, regs[18]), |
795 | REG_OFFSET_NAME(r19, regs[19]), |
796 | REG_OFFSET_NAME(r20, regs[20]), |
797 | REG_OFFSET_NAME(r21, regs[21]), |
798 | REG_OFFSET_NAME(r22, regs[22]), |
799 | REG_OFFSET_NAME(r23, regs[23]), |
800 | REG_OFFSET_NAME(r24, regs[24]), |
801 | REG_OFFSET_NAME(r25, regs[25]), |
802 | REG_OFFSET_NAME(r26, regs[26]), |
803 | REG_OFFSET_NAME(r27, regs[27]), |
804 | REG_OFFSET_NAME(r28, regs[28]), |
805 | REG_OFFSET_NAME(r29, regs[29]), |
806 | REG_OFFSET_NAME(r30, regs[30]), |
807 | REG_OFFSET_NAME(r31, regs[31]), |
808 | REG_OFFSET_NAME(orig_a0, orig_a0), |
809 | REG_OFFSET_NAME(csr_era, csr_era), |
810 | REG_OFFSET_NAME(csr_badvaddr, csr_badvaddr), |
811 | REG_OFFSET_NAME(csr_crmd, csr_crmd), |
812 | REG_OFFSET_NAME(csr_prmd, csr_prmd), |
813 | REG_OFFSET_NAME(csr_euen, csr_euen), |
814 | REG_OFFSET_NAME(csr_ecfg, csr_ecfg), |
815 | REG_OFFSET_NAME(csr_estat, csr_estat), |
816 | REG_OFFSET_END, |
817 | }; |
818 | |
819 | /** |
820 | * regs_query_register_offset() - query register offset from its name |
821 | * @name: the name of a register |
822 | * |
823 | * regs_query_register_offset() returns the offset of a register in struct |
824 | * pt_regs from its name. If the name is invalid, this returns -EINVAL; |
825 | */ |
826 | int regs_query_register_offset(const char *name) |
827 | { |
828 | const struct pt_regs_offset *roff; |
829 | |
830 | for (roff = regoffset_table; roff->name != NULL; roff++) |
831 | if (!strcmp(roff->name, name)) |
832 | return roff->offset; |
833 | return -EINVAL; |
834 | } |
835 | |
836 | enum loongarch_regset { |
837 | REGSET_GPR, |
838 | REGSET_FPR, |
839 | REGSET_CPUCFG, |
840 | #ifdef CONFIG_CPU_HAS_LSX |
841 | REGSET_LSX, |
842 | #endif |
843 | #ifdef CONFIG_CPU_HAS_LASX |
844 | REGSET_LASX, |
845 | #endif |
846 | #ifdef CONFIG_CPU_HAS_LBT |
847 | REGSET_LBT, |
848 | #endif |
849 | #ifdef CONFIG_HAVE_HW_BREAKPOINT |
850 | REGSET_HW_BREAK, |
851 | REGSET_HW_WATCH, |
852 | #endif |
853 | }; |
854 | |
855 | static const struct user_regset loongarch64_regsets[] = { |
856 | [REGSET_GPR] = { |
857 | .core_note_type = NT_PRSTATUS, |
858 | .n = ELF_NGREG, |
859 | .size = sizeof(elf_greg_t), |
860 | .align = sizeof(elf_greg_t), |
861 | .regset_get = gpr_get, |
862 | .set = gpr_set, |
863 | }, |
864 | [REGSET_FPR] = { |
865 | .core_note_type = NT_PRFPREG, |
866 | .n = ELF_NFPREG, |
867 | .size = sizeof(elf_fpreg_t), |
868 | .align = sizeof(elf_fpreg_t), |
869 | .regset_get = fpr_get, |
870 | .set = fpr_set, |
871 | }, |
872 | [REGSET_CPUCFG] = { |
873 | .core_note_type = NT_LOONGARCH_CPUCFG, |
874 | .n = 64, |
875 | .size = sizeof(u32), |
876 | .align = sizeof(u32), |
877 | .regset_get = cfg_get, |
878 | .set = cfg_set, |
879 | }, |
880 | #ifdef CONFIG_CPU_HAS_LSX |
881 | [REGSET_LSX] = { |
882 | .core_note_type = NT_LOONGARCH_LSX, |
883 | .n = NUM_FPU_REGS, |
884 | .size = 16, |
885 | .align = 16, |
886 | .regset_get = simd_get, |
887 | .set = simd_set, |
888 | }, |
889 | #endif |
890 | #ifdef CONFIG_CPU_HAS_LASX |
891 | [REGSET_LASX] = { |
892 | .core_note_type = NT_LOONGARCH_LASX, |
893 | .n = NUM_FPU_REGS, |
894 | .size = 32, |
895 | .align = 32, |
896 | .regset_get = simd_get, |
897 | .set = simd_set, |
898 | }, |
899 | #endif |
900 | #ifdef CONFIG_CPU_HAS_LBT |
901 | [REGSET_LBT] = { |
902 | .core_note_type = NT_LOONGARCH_LBT, |
903 | .n = 5, |
904 | .size = sizeof(u64), |
905 | .align = sizeof(u64), |
906 | .regset_get = lbt_get, |
907 | .set = lbt_set, |
908 | }, |
909 | #endif |
910 | #ifdef CONFIG_HAVE_HW_BREAKPOINT |
911 | [REGSET_HW_BREAK] = { |
912 | .core_note_type = NT_LOONGARCH_HW_BREAK, |
913 | .n = sizeof(struct user_watch_state) / sizeof(u32), |
914 | .size = sizeof(u32), |
915 | .align = sizeof(u32), |
916 | .regset_get = hw_break_get, |
917 | .set = hw_break_set, |
918 | }, |
919 | [REGSET_HW_WATCH] = { |
920 | .core_note_type = NT_LOONGARCH_HW_WATCH, |
921 | .n = sizeof(struct user_watch_state) / sizeof(u32), |
922 | .size = sizeof(u32), |
923 | .align = sizeof(u32), |
924 | .regset_get = hw_break_get, |
925 | .set = hw_break_set, |
926 | }, |
927 | #endif |
928 | }; |
929 | |
930 | static const struct user_regset_view user_loongarch64_view = { |
931 | .name = "loongarch64" , |
932 | .e_machine = ELF_ARCH, |
933 | .regsets = loongarch64_regsets, |
934 | .n = ARRAY_SIZE(loongarch64_regsets), |
935 | }; |
936 | |
937 | |
938 | const struct user_regset_view *task_user_regset_view(struct task_struct *task) |
939 | { |
940 | return &user_loongarch64_view; |
941 | } |
942 | |
943 | static inline int read_user(struct task_struct *target, unsigned long addr, |
944 | unsigned long __user *data) |
945 | { |
946 | unsigned long tmp = 0; |
947 | |
948 | switch (addr) { |
949 | case 0 ... 31: |
950 | tmp = task_pt_regs(target)->regs[addr]; |
951 | break; |
952 | case ARG0: |
953 | tmp = task_pt_regs(target)->orig_a0; |
954 | break; |
955 | case PC: |
956 | tmp = task_pt_regs(target)->csr_era; |
957 | break; |
958 | case BADVADDR: |
959 | tmp = task_pt_regs(target)->csr_badvaddr; |
960 | break; |
961 | default: |
962 | return -EIO; |
963 | } |
964 | |
965 | return put_user(tmp, data); |
966 | } |
967 | |
968 | static inline int write_user(struct task_struct *target, unsigned long addr, |
969 | unsigned long data) |
970 | { |
971 | switch (addr) { |
972 | case 0 ... 31: |
973 | task_pt_regs(target)->regs[addr] = data; |
974 | break; |
975 | case ARG0: |
976 | task_pt_regs(target)->orig_a0 = data; |
977 | break; |
978 | case PC: |
979 | task_pt_regs(target)->csr_era = data; |
980 | break; |
981 | case BADVADDR: |
982 | task_pt_regs(target)->csr_badvaddr = data; |
983 | break; |
984 | default: |
985 | return -EIO; |
986 | } |
987 | |
988 | return 0; |
989 | } |
990 | |
991 | long arch_ptrace(struct task_struct *child, long request, |
992 | unsigned long addr, unsigned long data) |
993 | { |
994 | int ret; |
995 | unsigned long __user *datap = (void __user *) data; |
996 | |
997 | switch (request) { |
998 | case PTRACE_PEEKUSR: |
999 | ret = read_user(target: child, addr, data: datap); |
1000 | break; |
1001 | |
1002 | case PTRACE_POKEUSR: |
1003 | ret = write_user(target: child, addr, data); |
1004 | break; |
1005 | |
1006 | default: |
1007 | ret = ptrace_request(child, request, addr, data); |
1008 | break; |
1009 | } |
1010 | |
1011 | return ret; |
1012 | } |
1013 | |
1014 | #ifdef CONFIG_HAVE_HW_BREAKPOINT |
1015 | static void ptrace_triggered(struct perf_event *bp, |
1016 | struct perf_sample_data *data, struct pt_regs *regs) |
1017 | { |
1018 | struct perf_event_attr attr; |
1019 | |
1020 | attr = bp->attr; |
1021 | attr.disabled = true; |
1022 | modify_user_hw_breakpoint(bp, attr: &attr); |
1023 | } |
1024 | |
1025 | static int set_single_step(struct task_struct *tsk, unsigned long addr) |
1026 | { |
1027 | struct perf_event *bp; |
1028 | struct perf_event_attr attr; |
1029 | struct arch_hw_breakpoint *info; |
1030 | struct thread_struct *thread = &tsk->thread; |
1031 | |
1032 | bp = thread->hbp_break[0]; |
1033 | if (!bp) { |
1034 | ptrace_breakpoint_init(attr: &attr); |
1035 | |
1036 | attr.bp_addr = addr; |
1037 | attr.bp_len = HW_BREAKPOINT_LEN_8; |
1038 | attr.bp_type = HW_BREAKPOINT_X; |
1039 | |
1040 | bp = register_user_hw_breakpoint(attr: &attr, triggered: ptrace_triggered, |
1041 | NULL, tsk); |
1042 | if (IS_ERR(ptr: bp)) |
1043 | return PTR_ERR(ptr: bp); |
1044 | |
1045 | thread->hbp_break[0] = bp; |
1046 | } else { |
1047 | int err; |
1048 | |
1049 | attr = bp->attr; |
1050 | attr.bp_addr = addr; |
1051 | |
1052 | /* Reenable breakpoint */ |
1053 | attr.disabled = false; |
1054 | err = modify_user_hw_breakpoint(bp, attr: &attr); |
1055 | if (unlikely(err)) |
1056 | return err; |
1057 | |
1058 | csr_write64(attr.bp_addr, LOONGARCH_CSR_IB0ADDR); |
1059 | } |
1060 | info = counter_arch_bp(bp); |
1061 | info->mask = TASK_SIZE - 1; |
1062 | |
1063 | return 0; |
1064 | } |
1065 | |
1066 | /* ptrace API */ |
1067 | void user_enable_single_step(struct task_struct *task) |
1068 | { |
1069 | struct thread_info *ti = task_thread_info(task); |
1070 | |
1071 | set_single_step(tsk: task, task_pt_regs(task)->csr_era); |
1072 | task->thread.single_step = task_pt_regs(task)->csr_era; |
1073 | set_ti_thread_flag(ti, TIF_SINGLESTEP); |
1074 | } |
1075 | |
1076 | void user_disable_single_step(struct task_struct *task) |
1077 | { |
1078 | clear_tsk_thread_flag(tsk: task, TIF_SINGLESTEP); |
1079 | } |
1080 | #endif |
1081 | |