Skip to content

Commit

Permalink
powerpc: BookE hardware watchpoint support
Browse files Browse the repository at this point in the history
This patch implements support for HW based watchpoint via the
DBSR_DAC (Data Address Compare) facility of the BookE processors.

It does so by interfacing with the existing DABR breakpoint code
and adding the necessary bits and pieces for the new bits to
be properly set or cleared

Signed-off-by: Luis Machado <luisgpm@br.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
  • Loading branch information
Luis Machado authored and Benjamin Herrenschmidt committed Jul 25, 2008
1 parent 00bf6e9 commit d6a61bf
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 35 deletions.
6 changes: 3 additions & 3 deletions arch/powerpc/kernel/entry_32.S
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ transfer_to_handler:
/* Check to see if the dbcr0 register is set up to debug. Use the
internal debug mode bit to do this. */
lwz r12,THREAD_DBCR0(r12)
andis. r12,r12,DBCR0_IDM@h
andis. r12,r12,(DBCR0_IDM | DBSR_DAC1R | DBSR_DAC1W)@h
beq+ 3f
/* From user and task is ptraced - load up global dbcr0 */
li r12,-1 /* clear all pending debug events */
Expand Down Expand Up @@ -292,7 +292,7 @@ syscall_exit_cont:
/* If the process has its own DBCR0 value, load it up. The internal
debug mode bit tells us that dbcr0 should be loaded. */
lwz r0,THREAD+THREAD_DBCR0(r2)
andis. r10,r0,DBCR0_IDM@h
andis. r10,r0,(DBCR0_IDM | DBSR_DAC1R | DBSR_DAC1W)@h
bnel- load_dbcr0
#endif
#ifdef CONFIG_44x
Expand Down Expand Up @@ -720,7 +720,7 @@ restore_user:
/* Check whether this process has its own DBCR0 value. The internal
debug mode bit tells us that dbcr0 should be loaded. */
lwz r0,THREAD+THREAD_DBCR0(r2)
andis. r10,r0,DBCR0_IDM@h
andis. r10,r0,(DBCR0_IDM | DBSR_DAC1R | DBSR_DAC1W)@h
bnel- load_dbcr0
#endif

Expand Down
46 changes: 46 additions & 0 deletions arch/powerpc/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
#ifdef CONFIG_PPC64
#include <asm/firmware.h>
#endif
#include <linux/kprobes.h>
#include <linux/kdebug.h>

extern unsigned long _get_SP(void);

Expand Down Expand Up @@ -239,6 +241,35 @@ void discard_lazy_cpu_state(void)
}
#endif /* CONFIG_SMP */

void do_dabr(struct pt_regs *regs, unsigned long address,
unsigned long error_code)
{
siginfo_t info;

if (notify_die(DIE_DABR_MATCH, "dabr_match", regs, error_code,
11, SIGSEGV) == NOTIFY_STOP)
return;

if (debugger_dabr_match(regs))
return;

/* Clear the DAC and struct entries. One shot trigger */
#if (defined(CONFIG_44x) || defined(CONFIG_BOOKE))
mtspr(SPRN_DBCR0, mfspr(SPRN_DBCR0) & ~(DBSR_DAC1R | DBSR_DAC1W
| DBCR0_IDM));
#endif

/* Clear the DABR */
set_dabr(0);

/* Deliver the signal to userspace */
info.si_signo = SIGTRAP;
info.si_errno = 0;
info.si_code = TRAP_HWBKPT;
info.si_addr = (void __user *)address;
force_sig_info(SIGTRAP, &info, current);
}

static DEFINE_PER_CPU(unsigned long, current_dabr);

int set_dabr(unsigned long dabr)
Expand All @@ -254,6 +285,11 @@ int set_dabr(unsigned long dabr)
#if defined(CONFIG_PPC64) || defined(CONFIG_6xx)
mtspr(SPRN_DABR, dabr);
#endif

#if defined(CONFIG_44x) || defined(CONFIG_BOOKE)
mtspr(SPRN_DAC1, dabr);
#endif

return 0;
}

Expand Down Expand Up @@ -337,6 +373,12 @@ struct task_struct *__switch_to(struct task_struct *prev,
if (unlikely(__get_cpu_var(current_dabr) != new->thread.dabr))
set_dabr(new->thread.dabr);

#if defined(CONFIG_44x) || defined(CONFIG_BOOKE)
/* If new thread DAC (HW breakpoint) is the same then leave it */
if (new->thread.dabr)
set_dabr(new->thread.dabr);
#endif

new_thread = &new->thread;
old_thread = &current->thread;

Expand Down Expand Up @@ -525,6 +567,10 @@ void flush_thread(void)
if (current->thread.dabr) {
current->thread.dabr = 0;
set_dabr(0);

#if defined(CONFIG_44x) || defined(CONFIG_BOOKE)
current->thread.dbcr0 &= ~(DBSR_DAC1R | DBSR_DAC1W);
#endif
}
}

Expand Down
72 changes: 66 additions & 6 deletions arch/powerpc/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ void user_enable_single_step(struct task_struct *task)

if (regs != NULL) {
#if defined(CONFIG_40x) || defined(CONFIG_BOOKE)
task->thread.dbcr0 = DBCR0_IDM | DBCR0_IC;
task->thread.dbcr0 |= DBCR0_IDM | DBCR0_IC;
regs->msr |= MSR_DE;
#else
regs->msr |= MSR_SE;
Expand All @@ -716,9 +716,16 @@ void user_disable_single_step(struct task_struct *task)
{
struct pt_regs *regs = task->thread.regs;


#if defined(CONFIG_44x) || defined(CONFIG_BOOKE)
/* If DAC then do not single step, skip */
if (task->thread.dabr)
return;
#endif

if (regs != NULL) {
#if defined(CONFIG_40x) || defined(CONFIG_BOOKE)
task->thread.dbcr0 = 0;
task->thread.dbcr0 &= ~(DBCR0_IC | DBCR0_IDM);
regs->msr &= ~MSR_DE;
#else
regs->msr &= ~MSR_SE;
Expand All @@ -727,22 +734,75 @@ void user_disable_single_step(struct task_struct *task)
clear_tsk_thread_flag(task, TIF_SINGLESTEP);
}

static int ptrace_set_debugreg(struct task_struct *task, unsigned long addr,
int ptrace_set_debugreg(struct task_struct *task, unsigned long addr,
unsigned long data)
{
/* We only support one DABR and no IABRS at the moment */
/* For ppc64 we support one DABR and no IABR's at the moment (ppc64).
* For embedded processors we support one DAC and no IAC's at the
* moment.
*/
if (addr > 0)
return -EINVAL;

/* The bottom 3 bits are flags */
if ((data & ~0x7UL) >= TASK_SIZE)
return -EIO;

/* Ensure translation is on */
#ifdef CONFIG_PPC64

/* For processors using DABR (i.e. 970), the bottom 3 bits are flags.
* It was assumed, on previous implementations, that 3 bits were
* passed together with the data address, fitting the design of the
* DABR register, as follows:
*
* bit 0: Read flag
* bit 1: Write flag
* bit 2: Breakpoint translation
*
* Thus, we use them here as so.
*/

/* Ensure breakpoint translation bit is set */
if (data && !(data & DABR_TRANSLATION))
return -EIO;

/* Move contents to the DABR register */
task->thread.dabr = data;

#endif
#if defined(CONFIG_44x) || defined(CONFIG_BOOKE)

/* As described above, it was assumed 3 bits were passed with the data
* address, but we will assume only the mode bits will be passed
* as to not cause alignment restrictions for DAC-based processors.
*/

/* DAC's hold the whole address without any mode flags */
task->thread.dabr = data & ~0x3UL;

if (task->thread.dabr == 0) {
task->thread.dbcr0 &= ~(DBSR_DAC1R | DBSR_DAC1W | DBCR0_IDM);
task->thread.regs->msr &= ~MSR_DE;
return 0;
}

/* Read or Write bits must be set */

if (!(data & 0x3UL))
return -EINVAL;

/* Set the Internal Debugging flag (IDM bit 1) for the DBCR0
register */
task->thread.dbcr0 = DBCR0_IDM;

/* Check for write and read flags and set DBCR0
accordingly */
if (data & 0x1UL)
task->thread.dbcr0 |= DBSR_DAC1R;
if (data & 0x2UL)
task->thread.dbcr0 |= DBSR_DAC1W;

task->thread.regs->msr |= MSR_DE;
#endif
return 0;
}

Expand Down
6 changes: 5 additions & 1 deletion arch/powerpc/kernel/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,12 @@ int do_signal(sigset_t *oldset, struct pt_regs *regs)
* user space. The DABR will have been cleared if it
* triggered inside the kernel.
*/
if (current->thread.dabr)
if (current->thread.dabr) {
set_dabr(current->thread.dabr);
#if defined(CONFIG_44x) || defined(CONFIG_BOOKE)
mtspr(SPRN_DBCR0, current->thread.dbcr0);
#endif
}

if (is32) {
if (ka.sa.sa_flags & SA_SIGINFO)
Expand Down
16 changes: 16 additions & 0 deletions arch/powerpc/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,22 @@ void __kprobes DebugException(struct pt_regs *regs, unsigned long debug_status)
}

_exception(SIGTRAP, regs, TRAP_TRACE, regs->nip);
} else if (debug_status & (DBSR_DAC1R | DBSR_DAC1W)) {
regs->msr &= ~MSR_DE;

if (user_mode(regs)) {
current->thread.dbcr0 &= ~(DBSR_DAC1R | DBSR_DAC1W |
DBCR0_IDM);
} else {
/* Disable DAC interupts */
mtspr(SPRN_DBCR0, mfspr(SPRN_DBCR0) & ~(DBSR_DAC1R |
DBSR_DAC1W | DBCR0_IDM));

/* Clear the DAC event */
mtspr(SPRN_DBSR, (DBSR_DAC1R | DBSR_DAC1W));
}
/* Setup and send the trap to the handler */
do_dabr(regs, mfspr(SPRN_DAC1), debug_status);
}
}
#endif /* CONFIG_4xx || CONFIG_BOOKE */
Expand Down
25 changes: 0 additions & 25 deletions arch/powerpc/mm/fault.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,31 +100,6 @@ static int store_updates_sp(struct pt_regs *regs)
return 0;
}

#if !(defined(CONFIG_4xx) || defined(CONFIG_BOOKE))
static void do_dabr(struct pt_regs *regs, unsigned long address,
unsigned long error_code)
{
siginfo_t info;

if (notify_die(DIE_DABR_MATCH, "dabr_match", regs, error_code,
11, SIGSEGV) == NOTIFY_STOP)
return;

if (debugger_dabr_match(regs))
return;

/* Clear the DABR */
set_dabr(0);

/* Deliver the signal to userspace */
info.si_signo = SIGTRAP;
info.si_errno = 0;
info.si_code = TRAP_HWBKPT;
info.si_addr = (void __user *)address;
force_sig_info(SIGTRAP, &info, current);
}
#endif /* !(CONFIG_4xx || CONFIG_BOOKE)*/

/*
* For 600- and 800-family processors, the error_code parameter is DSISR
* for a data fault, SRR1 for an instruction fault. For 400-family processors
Expand Down
2 changes: 2 additions & 0 deletions include/asm-powerpc/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ static inline int debugger_fault_handler(struct pt_regs *regs) { return 0; }
#endif

extern int set_dabr(unsigned long dabr);
extern void do_dabr(struct pt_regs *regs, unsigned long address,
unsigned long error_code);
extern void print_backtrace(unsigned long *);
extern void show_regs(struct pt_regs * regs);
extern void flush_instruction_cache(void);
Expand Down

0 comments on commit d6a61bf

Please sign in to comment.