| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * Copyright 2010 Tilera Corporation. All Rights Reserved. |
| 4 | * Copyright 2015 Regents of the University of California |
| 5 | * Copyright 2017 SiFive |
| 6 | * |
| 7 | * Copied from arch/tile/kernel/ptrace.c |
| 8 | */ |
| 9 | |
| 10 | #include <asm/vector.h> |
| 11 | #include <asm/ptrace.h> |
| 12 | #include <asm/syscall.h> |
| 13 | #include <asm/thread_info.h> |
| 14 | #include <asm/switch_to.h> |
| 15 | #include <linux/audit.h> |
| 16 | #include <linux/compat.h> |
| 17 | #include <linux/ptrace.h> |
| 18 | #include <linux/elf.h> |
| 19 | #include <linux/regset.h> |
| 20 | #include <linux/sched.h> |
| 21 | #include <linux/sched/task_stack.h> |
| 22 | |
| 23 | enum riscv_regset { |
| 24 | REGSET_X, |
| 25 | #ifdef CONFIG_FPU |
| 26 | REGSET_F, |
| 27 | #endif |
| 28 | #ifdef CONFIG_RISCV_ISA_V |
| 29 | REGSET_V, |
| 30 | #endif |
| 31 | #ifdef CONFIG_RISCV_ISA_SUPM |
| 32 | REGSET_TAGGED_ADDR_CTRL, |
| 33 | #endif |
| 34 | }; |
| 35 | |
| 36 | static int riscv_gpr_get(struct task_struct *target, |
| 37 | const struct user_regset *regset, |
| 38 | struct membuf to) |
| 39 | { |
| 40 | return membuf_write(s: &to, task_pt_regs(target), |
| 41 | size: sizeof(struct user_regs_struct)); |
| 42 | } |
| 43 | |
| 44 | static int riscv_gpr_set(struct task_struct *target, |
| 45 | const struct user_regset *regset, |
| 46 | unsigned int pos, unsigned int count, |
| 47 | const void *kbuf, const void __user *ubuf) |
| 48 | { |
| 49 | struct pt_regs *regs; |
| 50 | |
| 51 | regs = task_pt_regs(target); |
| 52 | return user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, data: regs, start_pos: 0, end_pos: -1); |
| 53 | } |
| 54 | |
| 55 | #ifdef CONFIG_FPU |
| 56 | static int riscv_fpr_get(struct task_struct *target, |
| 57 | const struct user_regset *regset, |
| 58 | struct membuf to) |
| 59 | { |
| 60 | struct __riscv_d_ext_state *fstate = &target->thread.fstate; |
| 61 | |
| 62 | if (target == current) |
| 63 | fstate_save(current, task_pt_regs(current)); |
| 64 | |
| 65 | membuf_write(&to, fstate, offsetof(struct __riscv_d_ext_state, fcsr)); |
| 66 | membuf_store(&to, fstate->fcsr); |
| 67 | return membuf_zero(&to, 4); // explicitly pad |
| 68 | } |
| 69 | |
| 70 | static int riscv_fpr_set(struct task_struct *target, |
| 71 | const struct user_regset *regset, |
| 72 | unsigned int pos, unsigned int count, |
| 73 | const void *kbuf, const void __user *ubuf) |
| 74 | { |
| 75 | int ret; |
| 76 | struct __riscv_d_ext_state *fstate = &target->thread.fstate; |
| 77 | |
| 78 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, fstate, 0, |
| 79 | offsetof(struct __riscv_d_ext_state, fcsr)); |
| 80 | if (!ret) { |
| 81 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, fstate, 0, |
| 82 | offsetof(struct __riscv_d_ext_state, fcsr) + |
| 83 | sizeof(fstate->fcsr)); |
| 84 | } |
| 85 | |
| 86 | return ret; |
| 87 | } |
| 88 | #endif |
| 89 | |
| 90 | #ifdef CONFIG_RISCV_ISA_V |
| 91 | static int riscv_vr_get(struct task_struct *target, |
| 92 | const struct user_regset *regset, |
| 93 | struct membuf to) |
| 94 | { |
| 95 | struct __riscv_v_ext_state *vstate = &target->thread.vstate; |
| 96 | struct __riscv_v_regset_state ptrace_vstate; |
| 97 | |
| 98 | if (!riscv_v_vstate_query(task_pt_regs(target))) |
| 99 | return -EINVAL; |
| 100 | |
| 101 | /* |
| 102 | * Ensure the vector registers have been saved to the memory before |
| 103 | * copying them to membuf. |
| 104 | */ |
| 105 | if (target == current) { |
| 106 | get_cpu_vector_context(); |
| 107 | riscv_v_vstate_save(¤t->thread.vstate, task_pt_regs(current)); |
| 108 | put_cpu_vector_context(); |
| 109 | } |
| 110 | |
| 111 | ptrace_vstate.vstart = vstate->vstart; |
| 112 | ptrace_vstate.vl = vstate->vl; |
| 113 | ptrace_vstate.vtype = vstate->vtype; |
| 114 | ptrace_vstate.vcsr = vstate->vcsr; |
| 115 | ptrace_vstate.vlenb = vstate->vlenb; |
| 116 | |
| 117 | /* Copy vector header from vstate. */ |
| 118 | membuf_write(&to, &ptrace_vstate, sizeof(struct __riscv_v_regset_state)); |
| 119 | |
| 120 | /* Copy all the vector registers from vstate. */ |
| 121 | return membuf_write(&to, vstate->datap, riscv_v_vsize); |
| 122 | } |
| 123 | |
| 124 | static int riscv_vr_set(struct task_struct *target, |
| 125 | const struct user_regset *regset, |
| 126 | unsigned int pos, unsigned int count, |
| 127 | const void *kbuf, const void __user *ubuf) |
| 128 | { |
| 129 | int ret; |
| 130 | struct __riscv_v_ext_state *vstate = &target->thread.vstate; |
| 131 | struct __riscv_v_regset_state ptrace_vstate; |
| 132 | |
| 133 | if (!riscv_v_vstate_query(task_pt_regs(target))) |
| 134 | return -EINVAL; |
| 135 | |
| 136 | /* Copy rest of the vstate except datap */ |
| 137 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ptrace_vstate, 0, |
| 138 | sizeof(struct __riscv_v_regset_state)); |
| 139 | if (unlikely(ret)) |
| 140 | return ret; |
| 141 | |
| 142 | if (vstate->vlenb != ptrace_vstate.vlenb) |
| 143 | return -EINVAL; |
| 144 | |
| 145 | vstate->vstart = ptrace_vstate.vstart; |
| 146 | vstate->vl = ptrace_vstate.vl; |
| 147 | vstate->vtype = ptrace_vstate.vtype; |
| 148 | vstate->vcsr = ptrace_vstate.vcsr; |
| 149 | |
| 150 | /* Copy all the vector registers. */ |
| 151 | pos = 0; |
| 152 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, vstate->datap, |
| 153 | 0, riscv_v_vsize); |
| 154 | return ret; |
| 155 | } |
| 156 | |
| 157 | static int riscv_vr_active(struct task_struct *target, const struct user_regset *regset) |
| 158 | { |
| 159 | if (!(has_vector() || has_xtheadvector())) |
| 160 | return -ENODEV; |
| 161 | |
| 162 | if (!riscv_v_vstate_query(task_pt_regs(target))) |
| 163 | return 0; |
| 164 | |
| 165 | return regset->n; |
| 166 | } |
| 167 | #endif |
| 168 | |
| 169 | #ifdef CONFIG_RISCV_ISA_SUPM |
| 170 | static int tagged_addr_ctrl_get(struct task_struct *target, |
| 171 | const struct user_regset *regset, |
| 172 | struct membuf to) |
| 173 | { |
| 174 | long ctrl = get_tagged_addr_ctrl(target); |
| 175 | |
| 176 | if (IS_ERR_VALUE(ctrl)) |
| 177 | return ctrl; |
| 178 | |
| 179 | return membuf_write(&to, &ctrl, sizeof(ctrl)); |
| 180 | } |
| 181 | |
| 182 | static int tagged_addr_ctrl_set(struct task_struct *target, |
| 183 | const struct user_regset *regset, |
| 184 | unsigned int pos, unsigned int count, |
| 185 | const void *kbuf, const void __user *ubuf) |
| 186 | { |
| 187 | int ret; |
| 188 | long ctrl; |
| 189 | |
| 190 | ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ctrl, 0, -1); |
| 191 | if (ret) |
| 192 | return ret; |
| 193 | |
| 194 | return set_tagged_addr_ctrl(target, ctrl); |
| 195 | } |
| 196 | #endif |
| 197 | |
| 198 | static struct user_regset riscv_user_regset[] __ro_after_init = { |
| 199 | [REGSET_X] = { |
| 200 | USER_REGSET_NOTE_TYPE(PRSTATUS), |
| 201 | .n = ELF_NGREG, |
| 202 | .size = sizeof(elf_greg_t), |
| 203 | .align = sizeof(elf_greg_t), |
| 204 | .regset_get = riscv_gpr_get, |
| 205 | .set = riscv_gpr_set, |
| 206 | }, |
| 207 | #ifdef CONFIG_FPU |
| 208 | [REGSET_F] = { |
| 209 | USER_REGSET_NOTE_TYPE(PRFPREG), |
| 210 | .n = ELF_NFPREG, |
| 211 | .size = sizeof(elf_fpreg_t), |
| 212 | .align = sizeof(elf_fpreg_t), |
| 213 | .regset_get = riscv_fpr_get, |
| 214 | .set = riscv_fpr_set, |
| 215 | }, |
| 216 | #endif |
| 217 | #ifdef CONFIG_RISCV_ISA_V |
| 218 | [REGSET_V] = { |
| 219 | USER_REGSET_NOTE_TYPE(RISCV_VECTOR), |
| 220 | .align = 16, |
| 221 | .size = sizeof(__u32), |
| 222 | .regset_get = riscv_vr_get, |
| 223 | .set = riscv_vr_set, |
| 224 | .active = riscv_vr_active, |
| 225 | }, |
| 226 | #endif |
| 227 | #ifdef CONFIG_RISCV_ISA_SUPM |
| 228 | [REGSET_TAGGED_ADDR_CTRL] = { |
| 229 | USER_REGSET_NOTE_TYPE(RISCV_TAGGED_ADDR_CTRL), |
| 230 | .n = 1, |
| 231 | .size = sizeof(long), |
| 232 | .align = sizeof(long), |
| 233 | .regset_get = tagged_addr_ctrl_get, |
| 234 | .set = tagged_addr_ctrl_set, |
| 235 | }, |
| 236 | #endif |
| 237 | }; |
| 238 | |
| 239 | static const struct user_regset_view riscv_user_native_view = { |
| 240 | .name = "riscv" , |
| 241 | .e_machine = EM_RISCV, |
| 242 | .regsets = riscv_user_regset, |
| 243 | .n = ARRAY_SIZE(riscv_user_regset), |
| 244 | }; |
| 245 | |
| 246 | #ifdef CONFIG_RISCV_ISA_V |
| 247 | void __init update_regset_vector_info(unsigned long size) |
| 248 | { |
| 249 | riscv_user_regset[REGSET_V].n = (size + sizeof(struct __riscv_v_regset_state)) / |
| 250 | sizeof(__u32); |
| 251 | } |
| 252 | #endif |
| 253 | |
| 254 | struct pt_regs_offset { |
| 255 | const char *name; |
| 256 | int offset; |
| 257 | }; |
| 258 | |
| 259 | #define REG_OFFSET_NAME(r) {.name = #r, .offset = offsetof(struct pt_regs, r)} |
| 260 | #define REG_OFFSET_END {.name = NULL, .offset = 0} |
| 261 | |
| 262 | static const struct pt_regs_offset regoffset_table[] = { |
| 263 | REG_OFFSET_NAME(epc), |
| 264 | REG_OFFSET_NAME(ra), |
| 265 | REG_OFFSET_NAME(sp), |
| 266 | REG_OFFSET_NAME(gp), |
| 267 | REG_OFFSET_NAME(tp), |
| 268 | REG_OFFSET_NAME(t0), |
| 269 | REG_OFFSET_NAME(t1), |
| 270 | REG_OFFSET_NAME(t2), |
| 271 | REG_OFFSET_NAME(s0), |
| 272 | REG_OFFSET_NAME(s1), |
| 273 | REG_OFFSET_NAME(a0), |
| 274 | REG_OFFSET_NAME(a1), |
| 275 | REG_OFFSET_NAME(a2), |
| 276 | REG_OFFSET_NAME(a3), |
| 277 | REG_OFFSET_NAME(a4), |
| 278 | REG_OFFSET_NAME(a5), |
| 279 | REG_OFFSET_NAME(a6), |
| 280 | REG_OFFSET_NAME(a7), |
| 281 | REG_OFFSET_NAME(s2), |
| 282 | REG_OFFSET_NAME(s3), |
| 283 | REG_OFFSET_NAME(s4), |
| 284 | REG_OFFSET_NAME(s5), |
| 285 | REG_OFFSET_NAME(s6), |
| 286 | REG_OFFSET_NAME(s7), |
| 287 | REG_OFFSET_NAME(s8), |
| 288 | REG_OFFSET_NAME(s9), |
| 289 | REG_OFFSET_NAME(s10), |
| 290 | REG_OFFSET_NAME(s11), |
| 291 | REG_OFFSET_NAME(t3), |
| 292 | REG_OFFSET_NAME(t4), |
| 293 | REG_OFFSET_NAME(t5), |
| 294 | REG_OFFSET_NAME(t6), |
| 295 | REG_OFFSET_NAME(status), |
| 296 | REG_OFFSET_NAME(badaddr), |
| 297 | REG_OFFSET_NAME(cause), |
| 298 | REG_OFFSET_NAME(orig_a0), |
| 299 | REG_OFFSET_END, |
| 300 | }; |
| 301 | |
| 302 | /** |
| 303 | * regs_query_register_offset() - query register offset from its name |
| 304 | * @name: the name of a register |
| 305 | * |
| 306 | * regs_query_register_offset() returns the offset of a register in struct |
| 307 | * pt_regs from its name. If the name is invalid, this returns -EINVAL; |
| 308 | */ |
| 309 | int regs_query_register_offset(const char *name) |
| 310 | { |
| 311 | const struct pt_regs_offset *roff; |
| 312 | |
| 313 | for (roff = regoffset_table; roff->name != NULL; roff++) |
| 314 | if (!strcmp(roff->name, name)) |
| 315 | return roff->offset; |
| 316 | return -EINVAL; |
| 317 | } |
| 318 | |
| 319 | /** |
| 320 | * regs_within_kernel_stack() - check the address in the stack |
| 321 | * @regs: pt_regs which contains kernel stack pointer. |
| 322 | * @addr: address which is checked. |
| 323 | * |
| 324 | * regs_within_kernel_stack() checks @addr is within the kernel stack page(s). |
| 325 | * If @addr is within the kernel stack, it returns true. If not, returns false. |
| 326 | */ |
| 327 | static bool regs_within_kernel_stack(struct pt_regs *regs, unsigned long addr) |
| 328 | { |
| 329 | return (addr & ~(THREAD_SIZE - 1)) == |
| 330 | (kernel_stack_pointer(regs) & ~(THREAD_SIZE - 1)); |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * regs_get_kernel_stack_nth() - get Nth entry of the stack |
| 335 | * @regs: pt_regs which contains kernel stack pointer. |
| 336 | * @n: stack entry number. |
| 337 | * |
| 338 | * regs_get_kernel_stack_nth() returns @n th entry of the kernel stack which |
| 339 | * is specified by @regs. If the @n th entry is NOT in the kernel stack, |
| 340 | * this returns 0. |
| 341 | */ |
| 342 | unsigned long regs_get_kernel_stack_nth(struct pt_regs *regs, unsigned int n) |
| 343 | { |
| 344 | unsigned long *addr = (unsigned long *)kernel_stack_pointer(regs); |
| 345 | |
| 346 | addr += n; |
| 347 | if (regs_within_kernel_stack(regs, addr: (unsigned long)addr)) |
| 348 | return *addr; |
| 349 | else |
| 350 | return 0; |
| 351 | } |
| 352 | |
| 353 | void ptrace_disable(struct task_struct *child) |
| 354 | { |
| 355 | } |
| 356 | |
| 357 | long arch_ptrace(struct task_struct *child, long request, |
| 358 | unsigned long addr, unsigned long data) |
| 359 | { |
| 360 | long ret = -EIO; |
| 361 | |
| 362 | switch (request) { |
| 363 | default: |
| 364 | ret = ptrace_request(child, request, addr, data); |
| 365 | break; |
| 366 | } |
| 367 | |
| 368 | return ret; |
| 369 | } |
| 370 | |
| 371 | #ifdef CONFIG_COMPAT |
| 372 | static int compat_riscv_gpr_get(struct task_struct *target, |
| 373 | const struct user_regset *regset, |
| 374 | struct membuf to) |
| 375 | { |
| 376 | struct compat_user_regs_struct cregs; |
| 377 | |
| 378 | regs_to_cregs(&cregs, task_pt_regs(target)); |
| 379 | |
| 380 | return membuf_write(&to, &cregs, |
| 381 | sizeof(struct compat_user_regs_struct)); |
| 382 | } |
| 383 | |
| 384 | static int compat_riscv_gpr_set(struct task_struct *target, |
| 385 | const struct user_regset *regset, |
| 386 | unsigned int pos, unsigned int count, |
| 387 | const void *kbuf, const void __user *ubuf) |
| 388 | { |
| 389 | int ret; |
| 390 | struct compat_user_regs_struct cregs; |
| 391 | |
| 392 | ret = user_regset_copyin(pos: &pos, count: &count, kbuf: &kbuf, ubuf: &ubuf, data: &cregs, start_pos: 0, end_pos: -1); |
| 393 | |
| 394 | cregs_to_regs(&cregs, task_pt_regs(target)); |
| 395 | |
| 396 | return ret; |
| 397 | } |
| 398 | |
| 399 | static const struct user_regset compat_riscv_user_regset[] = { |
| 400 | [REGSET_X] = { |
| 401 | USER_REGSET_NOTE_TYPE(PRSTATUS), |
| 402 | .n = ELF_NGREG, |
| 403 | .size = sizeof(compat_elf_greg_t), |
| 404 | .align = sizeof(compat_elf_greg_t), |
| 405 | .regset_get = compat_riscv_gpr_get, |
| 406 | .set = compat_riscv_gpr_set, |
| 407 | }, |
| 408 | #ifdef CONFIG_FPU |
| 409 | [REGSET_F] = { |
| 410 | USER_REGSET_NOTE_TYPE(PRFPREG), |
| 411 | .n = ELF_NFPREG, |
| 412 | .size = sizeof(elf_fpreg_t), |
| 413 | .align = sizeof(elf_fpreg_t), |
| 414 | .regset_get = riscv_fpr_get, |
| 415 | .set = riscv_fpr_set, |
| 416 | }, |
| 417 | #endif |
| 418 | }; |
| 419 | |
| 420 | static const struct user_regset_view compat_riscv_user_native_view = { |
| 421 | .name = "riscv" , |
| 422 | .e_machine = EM_RISCV, |
| 423 | .regsets = compat_riscv_user_regset, |
| 424 | .n = ARRAY_SIZE(compat_riscv_user_regset), |
| 425 | }; |
| 426 | |
| 427 | long compat_arch_ptrace(struct task_struct *child, compat_long_t request, |
| 428 | compat_ulong_t caddr, compat_ulong_t cdata) |
| 429 | { |
| 430 | long ret = -EIO; |
| 431 | |
| 432 | switch (request) { |
| 433 | default: |
| 434 | ret = compat_ptrace_request(child, request, addr: caddr, data: cdata); |
| 435 | break; |
| 436 | } |
| 437 | |
| 438 | return ret; |
| 439 | } |
| 440 | #else |
| 441 | static const struct user_regset_view compat_riscv_user_native_view = {}; |
| 442 | #endif /* CONFIG_COMPAT */ |
| 443 | |
| 444 | const struct user_regset_view *task_user_regset_view(struct task_struct *task) |
| 445 | { |
| 446 | if (is_compat_thread(&task->thread_info)) |
| 447 | return &compat_riscv_user_native_view; |
| 448 | else |
| 449 | return &riscv_user_native_view; |
| 450 | } |
| 451 | |