aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/kernel
diff options
context:
space:
mode:
authorWill Deacon <will.deacon@arm.com>2010-09-03 05:42:55 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2010-09-08 05:05:00 -0400
commit864232fa1a2f8dfe003438ef0851a56722740f3e (patch)
tree0f85f2b1b2f030e7c51f0c69b112dea746b3a861 /arch/arm/kernel
parentf81ef4a920c8e1af75adf9f15042c2daa49d3cb3 (diff)
ARM: 6357/1: hw-breakpoint: add new ptrace requests for hw-breakpoint interaction
For debuggers to take advantage of the hw-breakpoint framework in the kernel, it is necessary to expose the API calls via a ptrace interface. This patch exposes the hardware breakpoints framework as a collection of virtual registers, accesible using PTRACE_SETHBPREGS and PTRACE_GETHBPREGS requests. The breakpoints are stored in the debug_info struct of the running thread. Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: S. Karthikeyan <informkarthik@gmail.com> Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/kernel')
-rw-r--r--arch/arm/kernel/process.c5
-rw-r--r--arch/arm/kernel/ptrace.c239
2 files changed, 244 insertions, 0 deletions
diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c
index 401e38be1f78..974af1c3eb1d 100644
--- a/arch/arm/kernel/process.c
+++ b/arch/arm/kernel/process.c
@@ -29,6 +29,7 @@
29#include <linux/utsname.h> 29#include <linux/utsname.h>
30#include <linux/uaccess.h> 30#include <linux/uaccess.h>
31#include <linux/random.h> 31#include <linux/random.h>
32#include <linux/hw_breakpoint.h>
32 33
33#include <asm/cacheflush.h> 34#include <asm/cacheflush.h>
34#include <asm/leds.h> 35#include <asm/leds.h>
@@ -317,6 +318,8 @@ void flush_thread(void)
317 struct thread_info *thread = current_thread_info(); 318 struct thread_info *thread = current_thread_info();
318 struct task_struct *tsk = current; 319 struct task_struct *tsk = current;
319 320
321 flush_ptrace_hw_breakpoint(tsk);
322
320 memset(thread->used_cp, 0, sizeof(thread->used_cp)); 323 memset(thread->used_cp, 0, sizeof(thread->used_cp));
321 memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); 324 memset(&tsk->thread.debug, 0, sizeof(struct debug_info));
322 memset(&thread->fpstate, 0, sizeof(union fp_state)); 325 memset(&thread->fpstate, 0, sizeof(union fp_state));
@@ -345,6 +348,8 @@ copy_thread(unsigned long clone_flags, unsigned long stack_start,
345 thread->cpu_context.sp = (unsigned long)childregs; 348 thread->cpu_context.sp = (unsigned long)childregs;
346 thread->cpu_context.pc = (unsigned long)ret_from_fork; 349 thread->cpu_context.pc = (unsigned long)ret_from_fork;
347 350
351 clear_ptrace_hw_breakpoint(p);
352
348 if (clone_flags & CLONE_SETTLS) 353 if (clone_flags & CLONE_SETTLS)
349 thread->tp_value = regs->ARM_r3; 354 thread->tp_value = regs->ARM_r3;
350 355
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
index f99d489822d5..e0cb6370ed14 100644
--- a/arch/arm/kernel/ptrace.c
+++ b/arch/arm/kernel/ptrace.c
@@ -19,6 +19,8 @@
19#include <linux/init.h> 19#include <linux/init.h>
20#include <linux/signal.h> 20#include <linux/signal.h>
21#include <linux/uaccess.h> 21#include <linux/uaccess.h>
22#include <linux/perf_event.h>
23#include <linux/hw_breakpoint.h>
22 24
23#include <asm/pgtable.h> 25#include <asm/pgtable.h>
24#include <asm/system.h> 26#include <asm/system.h>
@@ -847,6 +849,232 @@ static int ptrace_setvfpregs(struct task_struct *tsk, void __user *data)
847} 849}
848#endif 850#endif
849 851
852#ifdef CONFIG_HAVE_HW_BREAKPOINT
853/*
854 * Convert a virtual register number into an index for a thread_info
855 * breakpoint array. Breakpoints are identified using positive numbers
856 * whilst watchpoints are negative. The registers are laid out as pairs
857 * of (address, control), each pair mapping to a unique hw_breakpoint struct.
858 * Register 0 is reserved for describing resource information.
859 */
860static int ptrace_hbp_num_to_idx(long num)
861{
862 if (num < 0)
863 num = (ARM_MAX_BRP << 1) - num;
864 return (num - 1) >> 1;
865}
866
867/*
868 * Returns the virtual register number for the address of the
869 * breakpoint at index idx.
870 */
871static long ptrace_hbp_idx_to_num(int idx)
872{
873 long mid = ARM_MAX_BRP << 1;
874 long num = (idx << 1) + 1;
875 return num > mid ? mid - num : num;
876}
877
878/*
879 * Handle hitting a HW-breakpoint.
880 */
881static void ptrace_hbptriggered(struct perf_event *bp, int unused,
882 struct perf_sample_data *data,
883 struct pt_regs *regs)
884{
885 struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
886 long num;
887 int i;
888 siginfo_t info;
889
890 for (i = 0; i < ARM_MAX_HBP_SLOTS; ++i)
891 if (current->thread.debug.hbp[i] == bp)
892 break;
893
894 num = (i == ARM_MAX_HBP_SLOTS) ? 0 : ptrace_hbp_idx_to_num(i);
895
896 info.si_signo = SIGTRAP;
897 info.si_errno = (int)num;
898 info.si_code = TRAP_HWBKPT;
899 info.si_addr = (void __user *)(bkpt->trigger);
900
901 force_sig_info(SIGTRAP, &info, current);
902}
903
904/*
905 * Set ptrace breakpoint pointers to zero for this task.
906 * This is required in order to prevent child processes from unregistering
907 * breakpoints held by their parent.
908 */
909void clear_ptrace_hw_breakpoint(struct task_struct *tsk)
910{
911 memset(tsk->thread.debug.hbp, 0, sizeof(tsk->thread.debug.hbp));
912}
913
914/*
915 * Unregister breakpoints from this task and reset the pointers in
916 * the thread_struct.
917 */
918void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
919{
920 int i;
921 struct thread_struct *t = &tsk->thread;
922
923 for (i = 0; i < ARM_MAX_HBP_SLOTS; i++) {
924 if (t->debug.hbp[i]) {
925 unregister_hw_breakpoint(t->debug.hbp[i]);
926 t->debug.hbp[i] = NULL;
927 }
928 }
929}
930
931static u32 ptrace_get_hbp_resource_info(void)
932{
933 u8 num_brps, num_wrps, debug_arch, wp_len;
934 u32 reg = 0;
935
936 num_brps = hw_breakpoint_slots(TYPE_INST);
937 num_wrps = hw_breakpoint_slots(TYPE_DATA);
938 debug_arch = arch_get_debug_arch();
939 wp_len = arch_get_max_wp_len();
940
941 reg |= debug_arch;
942 reg <<= 8;
943 reg |= wp_len;
944 reg <<= 8;
945 reg |= num_wrps;
946 reg <<= 8;
947 reg |= num_brps;
948
949 return reg;
950}
951
952static struct perf_event *ptrace_hbp_create(struct task_struct *tsk, int type)
953{
954 struct perf_event_attr attr;
955
956 ptrace_breakpoint_init(&attr);
957
958 /* Initialise fields to sane defaults. */
959 attr.bp_addr = 0;
960 attr.bp_len = HW_BREAKPOINT_LEN_4;
961 attr.bp_type = type;
962 attr.disabled = 1;
963
964 return register_user_hw_breakpoint(&attr, ptrace_hbptriggered, tsk);
965}
966
967static int ptrace_gethbpregs(struct task_struct *tsk, long num,
968 unsigned long __user *data)
969{
970 u32 reg;
971 int idx, ret = 0;
972 struct perf_event *bp;
973 struct arch_hw_breakpoint_ctrl arch_ctrl;
974
975 if (num == 0) {
976 reg = ptrace_get_hbp_resource_info();
977 } else {
978 idx = ptrace_hbp_num_to_idx(num);
979 if (idx < 0 || idx >= ARM_MAX_HBP_SLOTS) {
980 ret = -EINVAL;
981 goto out;
982 }
983
984 bp = tsk->thread.debug.hbp[idx];
985 if (!bp) {
986 reg = 0;
987 goto put;
988 }
989
990 arch_ctrl = counter_arch_bp(bp)->ctrl;
991
992 /*
993 * Fix up the len because we may have adjusted it
994 * to compensate for an unaligned address.
995 */
996 while (!(arch_ctrl.len & 0x1))
997 arch_ctrl.len >>= 1;
998
999 if (idx & 0x1)
1000 reg = encode_ctrl_reg(arch_ctrl);
1001 else
1002 reg = bp->attr.bp_addr;
1003 }
1004
1005put:
1006 if (put_user(reg, data))
1007 ret = -EFAULT;
1008
1009out:
1010 return ret;
1011}
1012
1013static int ptrace_sethbpregs(struct task_struct *tsk, long num,
1014 unsigned long __user *data)
1015{
1016 int idx, gen_len, gen_type, implied_type, ret = 0;
1017 u32 user_val;
1018 struct perf_event *bp;
1019 struct arch_hw_breakpoint_ctrl ctrl;
1020 struct perf_event_attr attr;
1021
1022 if (num == 0)
1023 goto out;
1024 else if (num < 0)
1025 implied_type = HW_BREAKPOINT_RW;
1026 else
1027 implied_type = HW_BREAKPOINT_X;
1028
1029 idx = ptrace_hbp_num_to_idx(num);
1030 if (idx < 0 || idx >= ARM_MAX_HBP_SLOTS) {
1031 ret = -EINVAL;
1032 goto out;
1033 }
1034
1035 if (get_user(user_val, data)) {
1036 ret = -EFAULT;
1037 goto out;
1038 }
1039
1040 bp = tsk->thread.debug.hbp[idx];
1041 if (!bp) {
1042 bp = ptrace_hbp_create(tsk, implied_type);
1043 if (IS_ERR(bp)) {
1044 ret = PTR_ERR(bp);
1045 goto out;
1046 }
1047 tsk->thread.debug.hbp[idx] = bp;
1048 }
1049
1050 attr = bp->attr;
1051
1052 if (num & 0x1) {
1053 /* Address */
1054 attr.bp_addr = user_val;
1055 } else {
1056 /* Control */
1057 decode_ctrl_reg(user_val, &ctrl);
1058 ret = arch_bp_generic_fields(ctrl, &gen_len, &gen_type);
1059 if (ret)
1060 goto out;
1061
1062 if ((gen_type & implied_type) != gen_type) {
1063 ret = -EINVAL;
1064 goto out;
1065 }
1066
1067 attr.bp_len = gen_len;
1068 attr.bp_type = gen_type;
1069 attr.disabled = !ctrl.enabled;
1070 }
1071
1072 ret = modify_user_hw_breakpoint(bp, &attr);
1073out:
1074 return ret;
1075}
1076#endif
1077
850long arch_ptrace(struct task_struct *child, long request, long addr, long data) 1078long arch_ptrace(struct task_struct *child, long request, long addr, long data)
851{ 1079{
852 int ret; 1080 int ret;
@@ -916,6 +1144,17 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
916 break; 1144 break;
917#endif 1145#endif
918 1146
1147#ifdef CONFIG_HAVE_HW_BREAKPOINT
1148 case PTRACE_GETHBPREGS:
1149 ret = ptrace_gethbpregs(child, addr,
1150 (unsigned long __user *)data);
1151 break;
1152 case PTRACE_SETHBPREGS:
1153 ret = ptrace_sethbpregs(child, addr,
1154 (unsigned long __user *)data);
1155 break;
1156#endif
1157
919 default: 1158 default:
920 ret = ptrace_request(child, request, addr, data); 1159 ret = ptrace_request(child, request, addr, data);
921 break; 1160 break;