diff options
Diffstat (limited to 'arch/arm/kernel/hw_breakpoint.c')
| -rw-r--r-- | arch/arm/kernel/hw_breakpoint.c | 134 |
1 files changed, 77 insertions, 57 deletions
diff --git a/arch/arm/kernel/hw_breakpoint.c b/arch/arm/kernel/hw_breakpoint.c index b16c4568cb01..81b63e94dfe0 100644 --- a/arch/arm/kernel/hw_breakpoint.c +++ b/arch/arm/kernel/hw_breakpoint.c | |||
| @@ -316,23 +316,6 @@ u8 arch_get_max_wp_len(void) | |||
| 316 | } | 316 | } |
| 317 | 317 | ||
| 318 | /* | 318 | /* |
| 319 | * Handler for reactivating a suspended watchpoint when the single | ||
| 320 | * step `mismatch' breakpoint is triggered. | ||
| 321 | */ | ||
| 322 | static void wp_single_step_handler(struct perf_event *bp, int unused, | ||
| 323 | struct perf_sample_data *data, | ||
| 324 | struct pt_regs *regs) | ||
| 325 | { | ||
| 326 | perf_event_enable(counter_arch_bp(bp)->suspended_wp); | ||
| 327 | unregister_hw_breakpoint(bp); | ||
| 328 | } | ||
| 329 | |||
| 330 | static int bp_is_single_step(struct perf_event *bp) | ||
| 331 | { | ||
| 332 | return bp->overflow_handler == wp_single_step_handler; | ||
| 333 | } | ||
| 334 | |||
| 335 | /* | ||
| 336 | * Install a perf counter breakpoint. | 319 | * Install a perf counter breakpoint. |
| 337 | */ | 320 | */ |
| 338 | int arch_install_hw_breakpoint(struct perf_event *bp) | 321 | int arch_install_hw_breakpoint(struct perf_event *bp) |
| @@ -340,29 +323,35 @@ int arch_install_hw_breakpoint(struct perf_event *bp) | |||
| 340 | struct arch_hw_breakpoint *info = counter_arch_bp(bp); | 323 | struct arch_hw_breakpoint *info = counter_arch_bp(bp); |
| 341 | struct perf_event **slot, **slots; | 324 | struct perf_event **slot, **slots; |
| 342 | int i, max_slots, ctrl_base, val_base, ret = 0; | 325 | int i, max_slots, ctrl_base, val_base, ret = 0; |
| 326 | u32 addr, ctrl; | ||
| 343 | 327 | ||
| 344 | /* Ensure that we are in monitor mode and halting mode is disabled. */ | 328 | /* Ensure that we are in monitor mode and halting mode is disabled. */ |
| 345 | ret = enable_monitor_mode(); | 329 | ret = enable_monitor_mode(); |
| 346 | if (ret) | 330 | if (ret) |
| 347 | goto out; | 331 | goto out; |
| 348 | 332 | ||
| 333 | addr = info->address; | ||
| 334 | ctrl = encode_ctrl_reg(info->ctrl) | 0x1; | ||
| 335 | |||
| 349 | if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) { | 336 | if (info->ctrl.type == ARM_BREAKPOINT_EXECUTE) { |
| 350 | /* Breakpoint */ | 337 | /* Breakpoint */ |
| 351 | ctrl_base = ARM_BASE_BCR; | 338 | ctrl_base = ARM_BASE_BCR; |
| 352 | val_base = ARM_BASE_BVR; | 339 | val_base = ARM_BASE_BVR; |
| 353 | slots = __get_cpu_var(bp_on_reg); | 340 | slots = __get_cpu_var(bp_on_reg); |
| 354 | max_slots = core_num_brps; | 341 | max_slots = core_num_brps; |
| 355 | |||
| 356 | if (bp_is_single_step(bp)) { | ||
| 357 | info->ctrl.mismatch = 1; | ||
| 358 | i = max_slots; | ||
| 359 | slots[i] = bp; | ||
| 360 | goto setup; | ||
| 361 | } | ||
| 362 | } else { | 342 | } else { |
| 363 | /* Watchpoint */ | 343 | /* Watchpoint */ |
| 364 | ctrl_base = ARM_BASE_WCR; | 344 | if (info->step_ctrl.enabled) { |
| 365 | val_base = ARM_BASE_WVR; | 345 | /* Install into the reserved breakpoint region. */ |
| 346 | ctrl_base = ARM_BASE_BCR + core_num_brps; | ||
| 347 | val_base = ARM_BASE_BVR + core_num_brps; | ||
| 348 | /* Override the watchpoint data with the step data. */ | ||
| 349 | addr = info->trigger & ~0x3; | ||
| 350 | ctrl = encode_ctrl_reg(info->step_ctrl); | ||
| 351 | } else { | ||
| 352 | ctrl_base = ARM_BASE_WCR; | ||
| 353 | val_base = ARM_BASE_WVR; | ||
| 354 | } | ||
| 366 | slots = __get_cpu_var(wp_on_reg); | 355 | slots = __get_cpu_var(wp_on_reg); |
| 367 | max_slots = core_num_wrps; | 356 | max_slots = core_num_wrps; |
| 368 | } | 357 | } |
| @@ -381,12 +370,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp) | |||
| 381 | goto out; | 370 | goto out; |
| 382 | } | 371 | } |
| 383 | 372 | ||
| 384 | setup: | ||
| 385 | /* Setup the address register. */ | 373 | /* Setup the address register. */ |
| 386 | write_wb_reg(val_base + i, info->address); | 374 | write_wb_reg(val_base + i, addr); |
| 387 | 375 | ||
| 388 | /* Setup the control register. */ | 376 | /* Setup the control register. */ |
| 389 | write_wb_reg(ctrl_base + i, encode_ctrl_reg(info->ctrl) | 0x1); | 377 | write_wb_reg(ctrl_base + i, ctrl); |
| 390 | 378 | ||
| 391 | out: | 379 | out: |
| 392 | return ret; | 380 | return ret; |
| @@ -403,15 +391,12 @@ void arch_uninstall_hw_breakpoint(struct perf_event *bp) | |||
| 403 | base = ARM_BASE_BCR; | 391 | base = ARM_BASE_BCR; |
| 404 | slots = __get_cpu_var(bp_on_reg); | 392 | slots = __get_cpu_var(bp_on_reg); |
| 405 | max_slots = core_num_brps; | 393 | max_slots = core_num_brps; |
| 406 | |||
| 407 | if (bp_is_single_step(bp)) { | ||
| 408 | i = max_slots; | ||
| 409 | slots[i] = NULL; | ||
| 410 | goto reset; | ||
| 411 | } | ||
| 412 | } else { | 394 | } else { |
| 413 | /* Watchpoint */ | 395 | /* Watchpoint */ |
| 414 | base = ARM_BASE_WCR; | 396 | if (info->step_ctrl.enabled) |
| 397 | base = ARM_BASE_BCR + core_num_brps; | ||
| 398 | else | ||
| 399 | base = ARM_BASE_WCR; | ||
| 415 | slots = __get_cpu_var(wp_on_reg); | 400 | slots = __get_cpu_var(wp_on_reg); |
| 416 | max_slots = core_num_wrps; | 401 | max_slots = core_num_wrps; |
| 417 | } | 402 | } |
| @@ -429,7 +414,6 @@ void arch_uninstall_hw_breakpoint(struct perf_event *bp) | |||
| 429 | if (WARN_ONCE(i == max_slots, "Can't find any breakpoint slot")) | 414 | if (WARN_ONCE(i == max_slots, "Can't find any breakpoint slot")) |
| 430 | return; | 415 | return; |
| 431 | 416 | ||
| 432 | reset: | ||
| 433 | /* Reset the control register. */ | 417 | /* Reset the control register. */ |
| 434 | write_wb_reg(base + i, 0); | 418 | write_wb_reg(base + i, 0); |
| 435 | } | 419 | } |
| @@ -579,7 +563,7 @@ static int arch_build_bp_info(struct perf_event *bp) | |||
| 579 | 563 | ||
| 580 | /* Privilege */ | 564 | /* Privilege */ |
| 581 | info->ctrl.privilege = ARM_BREAKPOINT_USER; | 565 | info->ctrl.privilege = ARM_BREAKPOINT_USER; |
| 582 | if (arch_check_bp_in_kernelspace(bp) && !bp_is_single_step(bp)) | 566 | if (arch_check_bp_in_kernelspace(bp)) |
| 583 | info->ctrl.privilege |= ARM_BREAKPOINT_PRIV; | 567 | info->ctrl.privilege |= ARM_BREAKPOINT_PRIV; |
| 584 | 568 | ||
| 585 | /* Enabled? */ | 569 | /* Enabled? */ |
| @@ -664,22 +648,18 @@ static void update_mismatch_flag(int idx, int flag) | |||
| 664 | static void watchpoint_handler(unsigned long unknown, struct pt_regs *regs) | 648 | static void watchpoint_handler(unsigned long unknown, struct pt_regs *regs) |
| 665 | { | 649 | { |
| 666 | int i; | 650 | int i; |
| 667 | struct perf_event *bp, **slots = __get_cpu_var(wp_on_reg); | 651 | struct perf_event *wp, **slots = __get_cpu_var(wp_on_reg); |
| 668 | struct arch_hw_breakpoint *info; | 652 | struct arch_hw_breakpoint *info; |
| 669 | struct perf_event_attr attr; | ||
| 670 | 653 | ||
| 671 | /* Without a disassembler, we can only handle 1 watchpoint. */ | 654 | /* Without a disassembler, we can only handle 1 watchpoint. */ |
| 672 | BUG_ON(core_num_wrps > 1); | 655 | BUG_ON(core_num_wrps > 1); |
| 673 | 656 | ||
| 674 | hw_breakpoint_init(&attr); | ||
| 675 | attr.bp_addr = regs->ARM_pc & ~0x3; | ||
| 676 | attr.bp_len = HW_BREAKPOINT_LEN_4; | ||
| 677 | attr.bp_type = HW_BREAKPOINT_X; | ||
| 678 | |||
| 679 | for (i = 0; i < core_num_wrps; ++i) { | 657 | for (i = 0; i < core_num_wrps; ++i) { |
| 680 | rcu_read_lock(); | 658 | rcu_read_lock(); |
| 681 | 659 | ||
| 682 | if (slots[i] == NULL) { | 660 | wp = slots[i]; |
| 661 | |||
| 662 | if (wp == NULL) { | ||
| 683 | rcu_read_unlock(); | 663 | rcu_read_unlock(); |
| 684 | continue; | 664 | continue; |
| 685 | } | 665 | } |
| @@ -689,24 +669,60 @@ static void watchpoint_handler(unsigned long unknown, struct pt_regs *regs) | |||
| 689 | * single watchpoint, we can set the trigger to the lowest | 669 | * single watchpoint, we can set the trigger to the lowest |
| 690 | * possible faulting address. | 670 | * possible faulting address. |
| 691 | */ | 671 | */ |
| 692 | info = counter_arch_bp(slots[i]); | 672 | info = counter_arch_bp(wp); |
| 693 | info->trigger = slots[i]->attr.bp_addr; | 673 | info->trigger = wp->attr.bp_addr; |
| 694 | pr_debug("watchpoint fired: address = 0x%x\n", info->trigger); | 674 | pr_debug("watchpoint fired: address = 0x%x\n", info->trigger); |
| 695 | perf_bp_event(slots[i], regs); | 675 | perf_bp_event(wp, regs); |
| 696 | 676 | ||
| 697 | /* | 677 | /* |
| 698 | * If no overflow handler is present, insert a temporary | 678 | * If no overflow handler is present, insert a temporary |
| 699 | * mismatch breakpoint so we can single-step over the | 679 | * mismatch breakpoint so we can single-step over the |
| 700 | * watchpoint trigger. | 680 | * watchpoint trigger. |
| 701 | */ | 681 | */ |
| 702 | if (!slots[i]->overflow_handler) { | 682 | if (!wp->overflow_handler) { |
| 703 | bp = register_user_hw_breakpoint(&attr, | 683 | arch_uninstall_hw_breakpoint(wp); |
| 704 | wp_single_step_handler, | 684 | info->step_ctrl.mismatch = 1; |
| 705 | current); | 685 | info->step_ctrl.len = ARM_BREAKPOINT_LEN_4; |
| 706 | counter_arch_bp(bp)->suspended_wp = slots[i]; | 686 | info->step_ctrl.type = ARM_BREAKPOINT_EXECUTE; |
| 707 | perf_event_disable(slots[i]); | 687 | info->step_ctrl.privilege = info->ctrl.privilege; |
| 688 | info->step_ctrl.enabled = 1; | ||
| 689 | info->trigger = regs->ARM_pc; | ||
| 690 | arch_install_hw_breakpoint(wp); | ||
| 691 | } | ||
| 692 | |||
| 693 | rcu_read_unlock(); | ||
| 694 | } | ||
| 695 | } | ||
| 696 | |||
| 697 | static void watchpoint_single_step_handler(unsigned long pc) | ||
| 698 | { | ||
| 699 | int i; | ||
| 700 | struct perf_event *wp, **slots = __get_cpu_var(wp_on_reg); | ||
| 701 | struct arch_hw_breakpoint *info; | ||
| 702 | |||
| 703 | for (i = 0; i < core_num_reserved_brps; ++i) { | ||
| 704 | rcu_read_lock(); | ||
| 705 | |||
| 706 | wp = slots[i]; | ||
| 707 | |||
| 708 | if (wp == NULL) | ||
| 709 | goto unlock; | ||
| 710 | |||
| 711 | info = counter_arch_bp(wp); | ||
| 712 | if (!info->step_ctrl.enabled) | ||
| 713 | goto unlock; | ||
| 714 | |||
| 715 | /* | ||
| 716 | * Restore the original watchpoint if we've completed the | ||
| 717 | * single-step. | ||
| 718 | */ | ||
| 719 | if (info->trigger != pc) { | ||
| 720 | arch_uninstall_hw_breakpoint(wp); | ||
| 721 | info->step_ctrl.enabled = 0; | ||
| 722 | arch_install_hw_breakpoint(wp); | ||
| 708 | } | 723 | } |
| 709 | 724 | ||
| 725 | unlock: | ||
| 710 | rcu_read_unlock(); | 726 | rcu_read_unlock(); |
| 711 | } | 727 | } |
| 712 | } | 728 | } |
| @@ -723,7 +739,8 @@ static void breakpoint_handler(unsigned long unknown, struct pt_regs *regs) | |||
| 723 | /* The exception entry code places the amended lr in the PC. */ | 739 | /* The exception entry code places the amended lr in the PC. */ |
| 724 | addr = regs->ARM_pc; | 740 | addr = regs->ARM_pc; |
| 725 | 741 | ||
| 726 | for (i = 0; i < core_num_brps + core_num_reserved_brps; ++i) { | 742 | /* Check the currently installed breakpoints first. */ |
| 743 | for (i = 0; i < core_num_brps; ++i) { | ||
| 727 | rcu_read_lock(); | 744 | rcu_read_lock(); |
| 728 | 745 | ||
| 729 | bp = slots[i]; | 746 | bp = slots[i]; |
| @@ -750,7 +767,7 @@ static void breakpoint_handler(unsigned long unknown, struct pt_regs *regs) | |||
| 750 | } | 767 | } |
| 751 | 768 | ||
| 752 | unlock: | 769 | unlock: |
| 753 | if ((mismatch && !info->ctrl.mismatch) || bp_is_single_step(bp)) { | 770 | if (mismatch && !info->ctrl.mismatch) { |
| 754 | pr_debug("breakpoint fired: address = 0x%x\n", addr); | 771 | pr_debug("breakpoint fired: address = 0x%x\n", addr); |
| 755 | perf_bp_event(bp, regs); | 772 | perf_bp_event(bp, regs); |
| 756 | } | 773 | } |
| @@ -758,6 +775,9 @@ unlock: | |||
| 758 | update_mismatch_flag(i, mismatch); | 775 | update_mismatch_flag(i, mismatch); |
| 759 | rcu_read_unlock(); | 776 | rcu_read_unlock(); |
| 760 | } | 777 | } |
| 778 | |||
| 779 | /* Handle any pending watchpoint single-step breakpoints. */ | ||
| 780 | watchpoint_single_step_handler(addr); | ||
| 761 | } | 781 | } |
| 762 | 782 | ||
| 763 | /* | 783 | /* |
