Skip to content

Commit

Permalink
powerpc: Hardware breakpoints rewrite to handle non DABR breakpoint r…
Browse files Browse the repository at this point in the history
…egisters

This is a rewrite so that we don't assume we are using the DABR throughout the
code.  We now use the arch_hw_breakpoint to store the breakpoint in a generic
manner in the thread_struct, rather than storing the raw DABR value.

The ptrace GET/SET_DEBUGREG interface currently passes the raw DABR in from
userspace.  We keep this functionality, so that future changes (like the POWER8
DAWR), will still fake the DABR to userspace.

Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
  • Loading branch information
Michael Neuling authored and Benjamin Herrenschmidt committed Jan 10, 2013
1 parent a8190a5 commit 9422de3
Showing 14 changed files with 187 additions and 129 deletions.
15 changes: 9 additions & 6 deletions arch/powerpc/include/asm/debug.h
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@
#ifndef _ASM_POWERPC_DEBUG_H
#define _ASM_POWERPC_DEBUG_H

#include <asm/hw_breakpoint.h>

struct pt_regs;

extern struct dentry *powerpc_debugfs_root;
@@ -15,7 +17,7 @@ extern int (*__debugger_ipi)(struct pt_regs *regs);
extern int (*__debugger_bpt)(struct pt_regs *regs);
extern int (*__debugger_sstep)(struct pt_regs *regs);
extern int (*__debugger_iabr_match)(struct pt_regs *regs);
extern int (*__debugger_dabr_match)(struct pt_regs *regs);
extern int (*__debugger_break_match)(struct pt_regs *regs);
extern int (*__debugger_fault_handler)(struct pt_regs *regs);

#define DEBUGGER_BOILERPLATE(__NAME) \
@@ -31,7 +33,7 @@ DEBUGGER_BOILERPLATE(debugger_ipi)
DEBUGGER_BOILERPLATE(debugger_bpt)
DEBUGGER_BOILERPLATE(debugger_sstep)
DEBUGGER_BOILERPLATE(debugger_iabr_match)
DEBUGGER_BOILERPLATE(debugger_dabr_match)
DEBUGGER_BOILERPLATE(debugger_break_match)
DEBUGGER_BOILERPLATE(debugger_fault_handler)

#else
@@ -40,17 +42,18 @@ static inline int debugger_ipi(struct pt_regs *regs) { return 0; }
static inline int debugger_bpt(struct pt_regs *regs) { return 0; }
static inline int debugger_sstep(struct pt_regs *regs) { return 0; }
static inline int debugger_iabr_match(struct pt_regs *regs) { return 0; }
static inline int debugger_dabr_match(struct pt_regs *regs) { return 0; }
static inline int debugger_break_match(struct pt_regs *regs) { return 0; }
static inline int debugger_fault_handler(struct pt_regs *regs) { return 0; }
#endif

extern int set_dabr(unsigned long dabr, unsigned long dabrx);
int set_break(struct arch_hw_breakpoint *brk);
#ifdef CONFIG_PPC_ADV_DEBUG_REGS
extern void do_send_trap(struct pt_regs *regs, unsigned long address,
unsigned long error_code, int signal_code, int brkpt);
#else
extern void do_dabr(struct pt_regs *regs, unsigned long address,
unsigned long error_code);

extern void do_break(struct pt_regs *regs, unsigned long address,
unsigned long error_code);
#endif

#endif /* _ASM_POWERPC_DEBUG_H */
33 changes: 26 additions & 7 deletions arch/powerpc/include/asm/hw_breakpoint.h
Original file line number Diff line number Diff line change
@@ -24,16 +24,30 @@
#define _PPC_BOOK3S_64_HW_BREAKPOINT_H

#ifdef __KERNEL__
#ifdef CONFIG_HAVE_HW_BREAKPOINT

struct arch_hw_breakpoint {
unsigned long address;
unsigned long dabrx;
int type;
u8 len; /* length of the target data symbol */
bool extraneous_interrupt;
u16 type;
u16 len; /* length of the target data symbol */
};

/* Note: Don't change the the first 6 bits below as they are in the same order
* as the dabr and dabrx.
*/
#define HW_BRK_TYPE_READ 0x01
#define HW_BRK_TYPE_WRITE 0x02
#define HW_BRK_TYPE_TRANSLATE 0x04
#define HW_BRK_TYPE_USER 0x08
#define HW_BRK_TYPE_KERNEL 0x10
#define HW_BRK_TYPE_HYP 0x20
#define HW_BRK_TYPE_EXTRANEOUS_IRQ 0x80

/* bits that overlap with the bottom 3 bits of the dabr */
#define HW_BRK_TYPE_RDWR (HW_BRK_TYPE_READ | HW_BRK_TYPE_WRITE)
#define HW_BRK_TYPE_DABR (HW_BRK_TYPE_RDWR | HW_BRK_TYPE_TRANSLATE)
#define HW_BRK_TYPE_PRIV_ALL (HW_BRK_TYPE_USER | HW_BRK_TYPE_KERNEL | \
HW_BRK_TYPE_HYP)

#ifdef CONFIG_HAVE_HW_BREAKPOINT
#include <linux/kdebug.h>
#include <asm/reg.h>
#include <asm/debug.h>
@@ -62,7 +76,12 @@ extern void ptrace_triggered(struct perf_event *bp,
struct perf_sample_data *data, struct pt_regs *regs);
static inline void hw_breakpoint_disable(void)
{
set_dabr(0, 0);
struct arch_hw_breakpoint brk;

brk.address = 0;
brk.type = 0;
brk.len = 0;
set_break(&brk);
}
extern void thread_change_pc(struct task_struct *tsk, struct pt_regs *regs);

4 changes: 2 additions & 2 deletions arch/powerpc/include/asm/processor.h
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
#include <linux/cache.h>
#include <asm/ptrace.h>
#include <asm/types.h>
#include <asm/hw_breakpoint.h>

/* We do _not_ want to define new machine types at all, those must die
* in favor of using the device-tree
@@ -225,8 +226,7 @@ struct thread_struct {
struct perf_event *last_hit_ubp;
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
#endif
unsigned long dabr; /* Data address breakpoint register */
unsigned long dabrx; /* ... extension */
struct arch_hw_breakpoint hw_brk; /* info on the hardware breakpoint */
unsigned long trap_nr; /* last trap # on this thread */
#ifdef CONFIG_ALTIVEC
/* Complete AltiVec register set */
3 changes: 0 additions & 3 deletions arch/powerpc/include/asm/reg.h
Original file line number Diff line number Diff line change
@@ -206,9 +206,6 @@
#define DAWRX_KERNEL (1UL << 1)
#define DAWRX_HYP (1UL << 2)
#define SPRN_DABR 0x3F5 /* Data Address Breakpoint Register */
#define DABR_TRANSLATION (1UL << 2)
#define DABR_DATA_WRITE (1UL << 1)
#define DABR_DATA_READ (1UL << 0)
#define SPRN_DABR2 0x13D /* e300 */
#define SPRN_DABRX 0x3F7 /* Data Address Breakpoint Register Extension */
#define DABRX_USER (1UL << 0)
2 changes: 1 addition & 1 deletion arch/powerpc/kernel/exceptions-64s.S
Original file line number Diff line number Diff line change
@@ -1251,7 +1251,7 @@ handle_dabr_fault:
ld r4,_DAR(r1)
ld r5,_DSISR(r1)
addi r3,r1,STACK_FRAME_OVERHEAD
bl .do_dabr
bl .do_break
12: b .ret_from_except_lite


72 changes: 30 additions & 42 deletions arch/powerpc/kernel/hw_breakpoint.c
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
* If so, DABR will be populated in single_step_dabr_instruction().
*/
if (current->thread.last_hit_ubp != bp)
set_dabr(info->address | info->type | DABR_TRANSLATION, info->dabrx);
set_break(info);

return 0;
}
@@ -97,7 +97,7 @@ void arch_uninstall_hw_breakpoint(struct perf_event *bp)
}

*slot = NULL;
set_dabr(0, 0);
hw_breakpoint_disable();
}

/*
@@ -127,19 +127,13 @@ int arch_check_bp_in_kernelspace(struct perf_event *bp)

int arch_bp_generic_fields(int type, int *gen_bp_type)
{
switch (type) {
case DABR_DATA_READ:
*gen_bp_type = HW_BREAKPOINT_R;
break;
case DABR_DATA_WRITE:
*gen_bp_type = HW_BREAKPOINT_W;
break;
case (DABR_DATA_WRITE | DABR_DATA_READ):
*gen_bp_type = (HW_BREAKPOINT_W | HW_BREAKPOINT_R);
break;
default:
*gen_bp_type = 0;
if (type & HW_BRK_TYPE_READ)
*gen_bp_type |= HW_BREAKPOINT_R;
if (type & HW_BRK_TYPE_WRITE)
*gen_bp_type |= HW_BREAKPOINT_W;
if (*gen_bp_type == 0)
return -EINVAL;
}
return 0;
}

@@ -154,29 +148,22 @@ int arch_validate_hwbkpt_settings(struct perf_event *bp)
if (!bp)
return ret;

switch (bp->attr.bp_type) {
case HW_BREAKPOINT_R:
info->type = DABR_DATA_READ;
break;
case HW_BREAKPOINT_W:
info->type = DABR_DATA_WRITE;
break;
case HW_BREAKPOINT_R | HW_BREAKPOINT_W:
info->type = (DABR_DATA_READ | DABR_DATA_WRITE);
break;
default:
info->type = HW_BRK_TYPE_TRANSLATE;
if (bp->attr.bp_type & HW_BREAKPOINT_R)
info->type |= HW_BRK_TYPE_READ;
if (bp->attr.bp_type & HW_BREAKPOINT_W)
info->type |= HW_BRK_TYPE_WRITE;
if (info->type == HW_BRK_TYPE_TRANSLATE)
/* must set alteast read or write */
return ret;
}

if (!(bp->attr.exclude_user))
info->type |= HW_BRK_TYPE_USER;
if (!(bp->attr.exclude_kernel))
info->type |= HW_BRK_TYPE_KERNEL;
if (!(bp->attr.exclude_hv))
info->type |= HW_BRK_TYPE_HYP;
info->address = bp->attr.bp_addr;
info->len = bp->attr.bp_len;
info->dabrx = DABRX_ALL;
if (bp->attr.exclude_user)
info->dabrx &= ~DABRX_USER;
if (bp->attr.exclude_kernel)
info->dabrx &= ~DABRX_KERNEL;
if (bp->attr.exclude_hv)
info->dabrx &= ~DABRX_HYP;

/*
* Since breakpoint length can be a maximum of HW_BREAKPOINT_LEN(8)
@@ -204,7 +191,7 @@ void thread_change_pc(struct task_struct *tsk, struct pt_regs *regs)

info = counter_arch_bp(tsk->thread.last_hit_ubp);
regs->msr &= ~MSR_SE;
set_dabr(info->address | info->type | DABR_TRANSLATION, info->dabrx);
set_break(info);
tsk->thread.last_hit_ubp = NULL;
}

@@ -222,7 +209,7 @@ int __kprobes hw_breakpoint_handler(struct die_args *args)
unsigned long dar = regs->dar;

/* Disable breakpoints during exception handling */
set_dabr(0, 0);
hw_breakpoint_disable();

/*
* The counter may be concurrently released but that can only
@@ -255,8 +242,9 @@ int __kprobes hw_breakpoint_handler(struct die_args *args)
* we still need to single-step the instruction, but we don't
* generate an event.
*/
info->extraneous_interrupt = !((bp->attr.bp_addr <= dar) &&
(dar - bp->attr.bp_addr < bp->attr.bp_len));
if (!((bp->attr.bp_addr <= dar) &&
(dar - bp->attr.bp_addr < bp->attr.bp_len)))
info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;

/* Do not emulate user-space instructions, instead single-step them */
if (user_mode(regs)) {
@@ -285,10 +273,10 @@ int __kprobes hw_breakpoint_handler(struct die_args *args)
* As a policy, the callback is invoked in a 'trigger-after-execute'
* fashion
*/
if (!info->extraneous_interrupt)
if (!(info->type & HW_BRK_TYPE_EXTRANEOUS_IRQ))
perf_bp_event(bp, regs);

set_dabr(info->address | info->type | DABR_TRANSLATION, info->dabrx);
set_break(info);
out:
rcu_read_unlock();
return rc;
@@ -317,10 +305,10 @@ int __kprobes single_step_dabr_instruction(struct die_args *args)
* We shall invoke the user-defined callback function in the single
* stepping handler to confirm to 'trigger-after-execute' semantics
*/
if (!info->extraneous_interrupt)
if (!(info->type & HW_BRK_TYPE_EXTRANEOUS_IRQ))
perf_bp_event(bp, regs);

set_dabr(info->address | info->type | DABR_TRANSLATION, info->dabrx);
set_break(info);
current->thread.last_hit_ubp = NULL;

/*
10 changes: 5 additions & 5 deletions arch/powerpc/kernel/kgdb.c
Original file line number Diff line number Diff line change
@@ -198,7 +198,7 @@ static int kgdb_iabr_match(struct pt_regs *regs)
return 1;
}

static int kgdb_dabr_match(struct pt_regs *regs)
static int kgdb_break_match(struct pt_regs *regs)
{
if (user_mode(regs))
return 0;
@@ -458,7 +458,7 @@ static void *old__debugger;
static void *old__debugger_bpt;
static void *old__debugger_sstep;
static void *old__debugger_iabr_match;
static void *old__debugger_dabr_match;
static void *old__debugger_break_match;
static void *old__debugger_fault_handler;

int kgdb_arch_init(void)
@@ -468,15 +468,15 @@ int kgdb_arch_init(void)
old__debugger_bpt = __debugger_bpt;
old__debugger_sstep = __debugger_sstep;
old__debugger_iabr_match = __debugger_iabr_match;
old__debugger_dabr_match = __debugger_dabr_match;
old__debugger_break_match = __debugger_break_match;
old__debugger_fault_handler = __debugger_fault_handler;

__debugger_ipi = kgdb_call_nmi_hook;
__debugger = kgdb_debugger;
__debugger_bpt = kgdb_handle_breakpoint;
__debugger_sstep = kgdb_singlestep;
__debugger_iabr_match = kgdb_iabr_match;
__debugger_dabr_match = kgdb_dabr_match;
__debugger_break_match = kgdb_break_match;
__debugger_fault_handler = kgdb_not_implemented;

return 0;
@@ -489,6 +489,6 @@ void kgdb_arch_exit(void)
__debugger_bpt = old__debugger_bpt;
__debugger_sstep = old__debugger_sstep;
__debugger_iabr_match = old__debugger_iabr_match;
__debugger_dabr_match = old__debugger_dabr_match;
__debugger_breakx_match = old__debugger_break_match;
__debugger_fault_handler = old__debugger_fault_handler;
}
Loading

0 comments on commit 9422de3

Please sign in to comment.