diff options
Diffstat (limited to 'arch/xtensa/kernel/ptrace.c')
| -rw-r--r-- | arch/xtensa/kernel/ptrace.c | 164 |
1 files changed, 157 insertions, 7 deletions
diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c index 4d54b481123b..a651f3a628ee 100644 --- a/arch/xtensa/kernel/ptrace.c +++ b/arch/xtensa/kernel/ptrace.c | |||
| @@ -13,21 +13,23 @@ | |||
| 13 | * Marc Gauthier<marc@tensilica.com> <marc@alumni.uwaterloo.ca> | 13 | * Marc Gauthier<marc@tensilica.com> <marc@alumni.uwaterloo.ca> |
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | #include <linux/errno.h> | ||
| 17 | #include <linux/hw_breakpoint.h> | ||
| 16 | #include <linux/kernel.h> | 18 | #include <linux/kernel.h> |
| 17 | #include <linux/sched.h> | ||
| 18 | #include <linux/mm.h> | 19 | #include <linux/mm.h> |
| 19 | #include <linux/errno.h> | 20 | #include <linux/perf_event.h> |
| 20 | #include <linux/ptrace.h> | 21 | #include <linux/ptrace.h> |
| 21 | #include <linux/smp.h> | 22 | #include <linux/sched.h> |
| 22 | #include <linux/security.h> | 23 | #include <linux/security.h> |
| 23 | #include <linux/signal.h> | 24 | #include <linux/signal.h> |
| 25 | #include <linux/smp.h> | ||
| 24 | 26 | ||
| 25 | #include <asm/pgtable.h> | 27 | #include <asm/coprocessor.h> |
| 28 | #include <asm/elf.h> | ||
| 26 | #include <asm/page.h> | 29 | #include <asm/page.h> |
| 27 | #include <asm/uaccess.h> | 30 | #include <asm/pgtable.h> |
| 28 | #include <asm/ptrace.h> | 31 | #include <asm/ptrace.h> |
| 29 | #include <asm/elf.h> | 32 | #include <asm/uaccess.h> |
| 30 | #include <asm/coprocessor.h> | ||
| 31 | 33 | ||
| 32 | 34 | ||
| 33 | void user_enable_single_step(struct task_struct *child) | 35 | void user_enable_single_step(struct task_struct *child) |
| @@ -267,6 +269,146 @@ int ptrace_pokeusr(struct task_struct *child, long regno, long val) | |||
| 267 | return 0; | 269 | return 0; |
| 268 | } | 270 | } |
| 269 | 271 | ||
| 272 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | ||
| 273 | static void ptrace_hbptriggered(struct perf_event *bp, | ||
| 274 | struct perf_sample_data *data, | ||
| 275 | struct pt_regs *regs) | ||
| 276 | { | ||
| 277 | int i; | ||
| 278 | siginfo_t info; | ||
| 279 | struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp); | ||
| 280 | |||
| 281 | if (bp->attr.bp_type & HW_BREAKPOINT_X) { | ||
| 282 | for (i = 0; i < XCHAL_NUM_IBREAK; ++i) | ||
| 283 | if (current->thread.ptrace_bp[i] == bp) | ||
| 284 | break; | ||
| 285 | i <<= 1; | ||
| 286 | } else { | ||
| 287 | for (i = 0; i < XCHAL_NUM_DBREAK; ++i) | ||
| 288 | if (current->thread.ptrace_wp[i] == bp) | ||
| 289 | break; | ||
| 290 | i = (i << 1) | 1; | ||
| 291 | } | ||
| 292 | |||
| 293 | info.si_signo = SIGTRAP; | ||
| 294 | info.si_errno = i; | ||
| 295 | info.si_code = TRAP_HWBKPT; | ||
| 296 | info.si_addr = (void __user *)bkpt->address; | ||
| 297 | |||
| 298 | force_sig_info(SIGTRAP, &info, current); | ||
| 299 | } | ||
| 300 | |||
| 301 | static struct perf_event *ptrace_hbp_create(struct task_struct *tsk, int type) | ||
| 302 | { | ||
| 303 | struct perf_event_attr attr; | ||
| 304 | |||
| 305 | ptrace_breakpoint_init(&attr); | ||
| 306 | |||
| 307 | /* Initialise fields to sane defaults. */ | ||
| 308 | attr.bp_addr = 0; | ||
| 309 | attr.bp_len = 1; | ||
| 310 | attr.bp_type = type; | ||
| 311 | attr.disabled = 1; | ||
| 312 | |||
| 313 | return register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL, | ||
| 314 | tsk); | ||
| 315 | } | ||
| 316 | |||
| 317 | /* | ||
| 318 | * Address bit 0 choose instruction (0) or data (1) break register, bits | ||
| 319 | * 31..1 are the register number. | ||
| 320 | * Both PTRACE_GETHBPREGS and PTRACE_SETHBPREGS transfer two 32-bit words: | ||
| 321 | * address (0) and control (1). | ||
| 322 | * Instruction breakpoint contorl word is 0 to clear breakpoint, 1 to set. | ||
| 323 | * Data breakpoint control word bit 31 is 'trigger on store', bit 30 is | ||
| 324 | * 'trigger on load, bits 29..0 are length. Length 0 is used to clear a | ||
| 325 | * breakpoint. To set a breakpoint length must be a power of 2 in the range | ||
| 326 | * 1..64 and the address must be length-aligned. | ||
| 327 | */ | ||
| 328 | |||
| 329 | static long ptrace_gethbpregs(struct task_struct *child, long addr, | ||
| 330 | long __user *datap) | ||
| 331 | { | ||
| 332 | struct perf_event *bp; | ||
| 333 | u32 user_data[2] = {0}; | ||
| 334 | bool dbreak = addr & 1; | ||
| 335 | unsigned idx = addr >> 1; | ||
| 336 | |||
| 337 | if ((!dbreak && idx >= XCHAL_NUM_IBREAK) || | ||
| 338 | (dbreak && idx >= XCHAL_NUM_DBREAK)) | ||
| 339 | return -EINVAL; | ||
| 340 | |||
| 341 | if (dbreak) | ||
| 342 | bp = child->thread.ptrace_wp[idx]; | ||
| 343 | else | ||
| 344 | bp = child->thread.ptrace_bp[idx]; | ||
| 345 | |||
| 346 | if (bp) { | ||
| 347 | user_data[0] = bp->attr.bp_addr; | ||
| 348 | user_data[1] = bp->attr.disabled ? 0 : bp->attr.bp_len; | ||
| 349 | if (dbreak) { | ||
| 350 | if (bp->attr.bp_type & HW_BREAKPOINT_R) | ||
| 351 | user_data[1] |= DBREAKC_LOAD_MASK; | ||
| 352 | if (bp->attr.bp_type & HW_BREAKPOINT_W) | ||
| 353 | user_data[1] |= DBREAKC_STOR_MASK; | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | if (copy_to_user(datap, user_data, sizeof(user_data))) | ||
| 358 | return -EFAULT; | ||
| 359 | |||
| 360 | return 0; | ||
| 361 | } | ||
| 362 | |||
| 363 | static long ptrace_sethbpregs(struct task_struct *child, long addr, | ||
| 364 | long __user *datap) | ||
| 365 | { | ||
| 366 | struct perf_event *bp; | ||
| 367 | struct perf_event_attr attr; | ||
| 368 | u32 user_data[2]; | ||
| 369 | bool dbreak = addr & 1; | ||
| 370 | unsigned idx = addr >> 1; | ||
| 371 | int bp_type = 0; | ||
| 372 | |||
| 373 | if ((!dbreak && idx >= XCHAL_NUM_IBREAK) || | ||
| 374 | (dbreak && idx >= XCHAL_NUM_DBREAK)) | ||
| 375 | return -EINVAL; | ||
| 376 | |||
| 377 | if (copy_from_user(user_data, datap, sizeof(user_data))) | ||
| 378 | return -EFAULT; | ||
| 379 | |||
| 380 | if (dbreak) { | ||
| 381 | bp = child->thread.ptrace_wp[idx]; | ||
| 382 | if (user_data[1] & DBREAKC_LOAD_MASK) | ||
| 383 | bp_type |= HW_BREAKPOINT_R; | ||
| 384 | if (user_data[1] & DBREAKC_STOR_MASK) | ||
| 385 | bp_type |= HW_BREAKPOINT_W; | ||
| 386 | } else { | ||
| 387 | bp = child->thread.ptrace_bp[idx]; | ||
| 388 | bp_type = HW_BREAKPOINT_X; | ||
| 389 | } | ||
| 390 | |||
| 391 | if (!bp) { | ||
| 392 | bp = ptrace_hbp_create(child, | ||
| 393 | bp_type ? bp_type : HW_BREAKPOINT_RW); | ||
| 394 | if (IS_ERR(bp)) | ||
| 395 | return PTR_ERR(bp); | ||
| 396 | if (dbreak) | ||
| 397 | child->thread.ptrace_wp[idx] = bp; | ||
| 398 | else | ||
| 399 | child->thread.ptrace_bp[idx] = bp; | ||
| 400 | } | ||
| 401 | |||
| 402 | attr = bp->attr; | ||
| 403 | attr.bp_addr = user_data[0]; | ||
| 404 | attr.bp_len = user_data[1] & ~(DBREAKC_LOAD_MASK | DBREAKC_STOR_MASK); | ||
| 405 | attr.bp_type = bp_type; | ||
| 406 | attr.disabled = !attr.bp_len; | ||
| 407 | |||
| 408 | return modify_user_hw_breakpoint(bp, &attr); | ||
| 409 | } | ||
| 410 | #endif | ||
| 411 | |||
| 270 | long arch_ptrace(struct task_struct *child, long request, | 412 | long arch_ptrace(struct task_struct *child, long request, |
| 271 | unsigned long addr, unsigned long data) | 413 | unsigned long addr, unsigned long data) |
| 272 | { | 414 | { |
| @@ -307,7 +449,15 @@ long arch_ptrace(struct task_struct *child, long request, | |||
| 307 | case PTRACE_SETXTREGS: | 449 | case PTRACE_SETXTREGS: |
| 308 | ret = ptrace_setxregs(child, datap); | 450 | ret = ptrace_setxregs(child, datap); |
| 309 | break; | 451 | break; |
| 452 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | ||
| 453 | case PTRACE_GETHBPREGS: | ||
| 454 | ret = ptrace_gethbpregs(child, addr, datap); | ||
| 455 | break; | ||
| 310 | 456 | ||
| 457 | case PTRACE_SETHBPREGS: | ||
| 458 | ret = ptrace_sethbpregs(child, addr, datap); | ||
| 459 | break; | ||
| 460 | #endif | ||
| 311 | default: | 461 | default: |
| 312 | ret = ptrace_request(child, request, addr, data); | 462 | ret = ptrace_request(child, request, addr, data); |
| 313 | break; | 463 | break; |
