Commit c91e02bd authored by Max Filippov's avatar Max Filippov Committed by Chris Zankel
Browse files

xtensa: support hardware breakpoints/watchpoints



Use perf framework to manage hardware instruction and data breakpoints.
Add two new ptrace calls: PTRACE_GETHBPREGS and PTRACE_SETHBPREGS to
query and set instruction and data breakpoints.
Address bit 0 choose instruction (0) or data (1) break register, bits
31..1 are the register number.
Both calls transfer two 32-bit words: address (0) and control (1).
Instruction breakpoint contorl word is 0 to clear breakpoint, 1 to set.
Data breakpoint control word bit 31 is 'trigger on store', bit 30 is
'trigger on load, bits 29..0 are length. Length 0 is used to clear a
breakpoint. To set a breakpoint length must be a power of 2 in the range
1..64 and the address must be length-aligned.

Introduce new thread_info flag: TIF_DB_DISABLED. Set it if debug
exception is raised by the kernel code accessing watched userspace
address and disable corresponding data breakpoint. On exit to userspace
check that flag and, if set, restore all data breakpoints.

Handle debug exceptions raised with PS.EXCM set. This may happen when
window overflow/underflow handler or fast exception handler hits data
breakpoint, in which case save and disable all data breakpoints,
single-step faulting instruction and restore data breakpoints.

Signed-off-by: default avatarMax Filippov <jcmvbkbc@gmail.com>
parent 6ec7026a
......@@ -18,6 +18,7 @@ config XTENSA
select HAVE_DMA_ATTRS
select HAVE_FUNCTION_TRACER
select HAVE_FUTEX_CMPXCHG if !MMU
select HAVE_HW_BREAKPOINT if PERF_EVENTS
select HAVE_IRQ_TIME_ACCOUNTING
select HAVE_OPROFILE
select HAVE_PERF_EVENTS
......
/*
* Xtensa hardware breakpoints/watchpoints handling functions
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2016 Cadence Design Systems Inc.
*/
#ifndef __ASM_XTENSA_HW_BREAKPOINT_H
#define __ASM_XTENSA_HW_BREAKPOINT_H
#ifdef CONFIG_HAVE_HW_BREAKPOINT
#include <linux/kdebug.h>
#include <linux/types.h>
#include <uapi/linux/hw_breakpoint.h>
/* Breakpoint */
#define XTENSA_BREAKPOINT_EXECUTE 0
/* Watchpoints */
#define XTENSA_BREAKPOINT_LOAD 1
#define XTENSA_BREAKPOINT_STORE 2
struct arch_hw_breakpoint {
unsigned long address;
u16 len;
u16 type;
};
struct perf_event;
struct pt_regs;
struct task_struct;
int hw_breakpoint_slots(int type);
int arch_check_bp_in_kernelspace(struct perf_event *bp);
int arch_validate_hwbkpt_settings(struct perf_event *bp);
int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
unsigned long val, void *data);
int arch_install_hw_breakpoint(struct perf_event *bp);
void arch_uninstall_hw_breakpoint(struct perf_event *bp);
void hw_breakpoint_pmu_read(struct perf_event *bp);
int check_hw_breakpoint(struct pt_regs *regs);
void clear_ptrace_hw_breakpoint(struct task_struct *tsk);
#else
struct task_struct;
static inline void clear_ptrace_hw_breakpoint(struct task_struct *tsk)
{
}
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
#endif /* __ASM_XTENSA_HW_BREAKPOINT_H */
......@@ -13,6 +13,7 @@
#define _XTENSA_IRQFLAGS_H
#include <linux/types.h>
#include <asm/processor.h>
static inline unsigned long arch_local_save_flags(void)
{
......
......@@ -130,11 +130,10 @@ struct thread_struct {
unsigned long bad_vaddr; /* last user fault */
unsigned long bad_uaddr; /* last kernel fault accessing user space */
unsigned long error_code;
unsigned long ibreak[XCHAL_NUM_IBREAK];
unsigned long dbreaka[XCHAL_NUM_DBREAK];
unsigned long dbreakc[XCHAL_NUM_DBREAK];
#ifdef CONFIG_HAVE_HW_BREAKPOINT
struct perf_event *ptrace_bp[XCHAL_NUM_IBREAK];
struct perf_event *ptrace_wp[XCHAL_NUM_DBREAK];
#endif
/* Make structure 16 bytes aligned. */
int align[0] __attribute__ ((aligned(16)));
};
......
......@@ -28,6 +28,7 @@
/* Special registers. */
#define SREG_MR 32
#define SREG_IBREAKENABLE 96
#define SREG_IBREAKA 128
#define SREG_DBREAKA 144
#define SREG_DBREAKC 160
......@@ -103,6 +104,8 @@
/* DEBUGCAUSE register fields. */
#define DEBUGCAUSE_DBNUM_MASK 0xf00
#define DEBUGCAUSE_DBNUM_SHIFT 8 /* First bit of DBNUM field */
#define DEBUGCAUSE_DEBUGINT_BIT 5 /* External debug interrupt */
#define DEBUGCAUSE_BREAKN_BIT 4 /* BREAK.N instruction */
#define DEBUGCAUSE_BREAK_BIT 3 /* BREAK instruction */
......
......@@ -111,6 +111,7 @@ static inline struct thread_info *current_thread_info(void)
#define TIF_MEMDIE 5 /* is terminating due to OOM killer */
#define TIF_RESTORE_SIGMASK 6 /* restore signal mask in do_signal() */
#define TIF_NOTIFY_RESUME 7 /* callback before returning to user */
#define TIF_DB_DISABLED 8 /* debug trap disabled for syscall */
#define _TIF_SYSCALL_TRACE (1<<TIF_SYSCALL_TRACE)
#define _TIF_SIGPENDING (1<<TIF_SIGPENDING)
......
......@@ -70,6 +70,14 @@ struct debug_table {
void (*debug_exception)(void);
/* Temporary register save area */
unsigned long debug_save[1];
#ifdef CONFIG_HAVE_HW_BREAKPOINT
/* Save area for DBREAKC registers */
unsigned long dbreakc_save[XCHAL_NUM_DBREAK];
/* Saved ICOUNT register */
unsigned long icount_save;
/* Saved ICOUNTLEVEL register */
unsigned long icount_level_save;
#endif
};
void debug_exception(void);
......
......@@ -72,6 +72,8 @@
#define PTRACE_SETREGS 13
#define PTRACE_GETXTREGS 18
#define PTRACE_SETXTREGS 19
#define PTRACE_GETHBPREGS 20
#define PTRACE_SETHBPREGS 21
#endif /* _UAPI_XTENSA_PTRACE_H */
......@@ -13,6 +13,7 @@ obj-$(CONFIG_MODULES) += xtensa_ksyms.o module.o
obj-$(CONFIG_FUNCTION_TRACER) += mcount.o
obj-$(CONFIG_SMP) += smp.o mxhead.o
obj-$(CONFIG_XTENSA_VARIANT_HAVE_PERF_EVENTS) += perf_event.o
obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
AFLAGS_head.o += -mtext-section-literals
AFLAGS_mxhead.o += -mtext-section-literals
......
......@@ -122,6 +122,12 @@ int main(void)
DEFINE(DT_DEBUG_EXCEPTION,
offsetof(struct debug_table, debug_exception));
DEFINE(DT_DEBUG_SAVE, offsetof(struct debug_table, debug_save));
#ifdef CONFIG_HAVE_HW_BREAKPOINT
DEFINE(DT_DBREAKC_SAVE, offsetof(struct debug_table, dbreakc_save));
DEFINE(DT_ICOUNT_SAVE, offsetof(struct debug_table, icount_save));
DEFINE(DT_ICOUNT_LEVEL_SAVE,
offsetof(struct debug_table, icount_level_save));
#endif
return 0;
}
......@@ -543,6 +543,12 @@ common_exception_return:
#endif
5:
#ifdef CONFIG_HAVE_HW_BREAKPOINT
_bbci.l a4, TIF_DB_DISABLED, 7f
movi a4, restore_dbreak
callx4 a4
7:
#endif
#ifdef CONFIG_DEBUG_TLB_SANITY
l32i a4, a1, PT_DEPC
bgeui a4, VALID_DOUBLE_EXCEPTION_ADDRESS, 4f
......@@ -808,6 +814,18 @@ ENTRY(debug_exception)
s32i a0, a2, PT_AREG2
mov a1, a2
/* Debug exception is handled as an exception, so interrupts will
* likely be enabled in the common exception handler. Disable
* preemption if we have HW breakpoints to preserve DEBUGCAUSE.DBNUM
* meaning.
*/
#if defined(CONFIG_PREEMPT_COUNT) && defined(CONFIG_HAVE_HW_BREAKPOINT)
GET_THREAD_INFO(a2, a1)
l32i a3, a2, TI_PRE_COUNT
addi a3, a3, 1
s32i a3, a2, TI_PRE_COUNT
#endif
rsr a2, ps
bbsi.l a2, PS_UM_BIT, _user_exception
j _kernel_exception
......@@ -816,8 +834,60 @@ ENTRY(debug_exception)
l32i a2, a2, EXC_TABLE_KSTK # load kernel stack pointer
j 3b
/* Debug exception while in exception mode. */
#ifdef CONFIG_HAVE_HW_BREAKPOINT
/* Debug exception while in exception mode. This may happen when
* window overflow/underflow handler or fast exception handler hits
* data breakpoint, in which case save and disable all data
* breakpoints, single-step faulting instruction and restore data
* breakpoints.
*/
1:
bbci.l a0, PS_UM_BIT, 1b # jump if kernel mode
rsr a0, debugcause
bbsi.l a0, DEBUGCAUSE_DBREAK_BIT, .Ldebug_save_dbreak
.set _index, 0
.rept XCHAL_NUM_DBREAK
l32i a0, a3, DT_DBREAKC_SAVE + _index * 4
wsr a0, SREG_DBREAKC + _index
.set _index, _index + 1
.endr
l32i a0, a3, DT_ICOUNT_LEVEL_SAVE
wsr a0, icountlevel
l32i a0, a3, DT_ICOUNT_SAVE
xsr a0, icount
l32i a0, a3, DT_DEBUG_SAVE
xsr a3, SREG_EXCSAVE + XCHAL_DEBUGLEVEL
rfi XCHAL_DEBUGLEVEL
.Ldebug_save_dbreak:
.set _index, 0
.rept XCHAL_NUM_DBREAK
movi a0, 0
xsr a0, SREG_DBREAKC + _index
s32i a0, a3, DT_DBREAKC_SAVE + _index * 4
.set _index, _index + 1
.endr
movi a0, XCHAL_EXCM_LEVEL + 1
xsr a0, icountlevel
s32i a0, a3, DT_ICOUNT_LEVEL_SAVE
movi a0, 0xfffffffe
xsr a0, icount
s32i a0, a3, DT_ICOUNT_SAVE
l32i a0, a3, DT_DEBUG_SAVE
xsr a3, SREG_EXCSAVE + XCHAL_DEBUGLEVEL
rfi XCHAL_DEBUGLEVEL
#else
/* Debug exception while in exception mode. Should not happen. */
1: j 1b // FIXME!!
#endif
ENDPROC(debug_exception)
......
/*
* Xtensa hardware breakpoints/watchpoints handling functions
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2016 Cadence Design Systems Inc.
*/
#include <linux/hw_breakpoint.h>
#include <linux/log2.h>
#include <linux/percpu.h>
#include <linux/perf_event.h>
#include <variant/core.h>
/* Breakpoint currently in use for each IBREAKA. */
static DEFINE_PER_CPU(struct perf_event *, bp_on_reg[XCHAL_NUM_IBREAK]);
/* Watchpoint currently in use for each DBREAKA. */
static DEFINE_PER_CPU(struct perf_event *, wp_on_reg[XCHAL_NUM_DBREAK]);
int hw_breakpoint_slots(int type)
{
switch (type) {
case TYPE_INST:
return XCHAL_NUM_IBREAK;
case TYPE_DATA:
return XCHAL_NUM_DBREAK;
default:
pr_warn("unknown slot type: %d\n", type);
return 0;
}
}
int arch_check_bp_in_kernelspace(struct perf_event *bp)
{
unsigned int len;
unsigned long va;
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
va = info->address;
len = bp->attr.bp_len;
return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);
}
/*
* Construct an arch_hw_breakpoint from a perf_event.
*/
static int arch_build_bp_info(struct perf_event *bp)
{
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
/* Type */
switch (bp->attr.bp_type) {
case HW_BREAKPOINT_X:
info->type = XTENSA_BREAKPOINT_EXECUTE;
break;
case HW_BREAKPOINT_R:
info->type = XTENSA_BREAKPOINT_LOAD;
break;
case HW_BREAKPOINT_W:
info->type = XTENSA_BREAKPOINT_STORE;
break;
case HW_BREAKPOINT_RW:
info->type = XTENSA_BREAKPOINT_LOAD | XTENSA_BREAKPOINT_STORE;
break;
default:
return -EINVAL;
}
/* Len */
info->len = bp->attr.bp_len;
if (info->len < 1 || info->len > 64 || !is_power_of_2(info->len))
return -EINVAL;
/* Address */
info->address = bp->attr.bp_addr;
if (info->address & (info->len - 1))
return -EINVAL;
return 0;
}
int arch_validate_hwbkpt_settings(struct perf_event *bp)
{
int ret;
/* Build the arch_hw_breakpoint. */
ret = arch_build_bp_info(bp);
return ret;
}
int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
unsigned long val, void *data)
{
return NOTIFY_DONE;
}
static void xtensa_wsr(unsigned long v, u8 sr)
{
/* We don't have indexed wsr and creating instruction dynamically
* doesn't seem worth it given how small XCHAL_NUM_IBREAK and
* XCHAL_NUM_DBREAK are. Thus the switch. In case build breaks here
* the switch below needs to be extended.
*/
BUILD_BUG_ON(XCHAL_NUM_IBREAK > 2);
BUILD_BUG_ON(XCHAL_NUM_DBREAK > 2);
switch (sr) {
#if XCHAL_NUM_IBREAK > 0
case SREG_IBREAKA + 0:
WSR(v, SREG_IBREAKA + 0);
break;
#endif
#if XCHAL_NUM_IBREAK > 1
case SREG_IBREAKA + 1:
WSR(v, SREG_IBREAKA + 1);
break;
#endif
#if XCHAL_NUM_DBREAK > 0
case SREG_DBREAKA + 0:
WSR(v, SREG_DBREAKA + 0);
break;
case SREG_DBREAKC + 0:
WSR(v, SREG_DBREAKC + 0);
break;
#endif
#if XCHAL_NUM_DBREAK > 1
case SREG_DBREAKA + 1:
WSR(v, SREG_DBREAKA + 1);
break;
case SREG_DBREAKC + 1:
WSR(v, SREG_DBREAKC + 1);
break;
#endif
}
}
static int alloc_slot(struct perf_event **slot, size_t n,
struct perf_event *bp)
{
size_t i;
for (i = 0; i < n; ++i) {
if (!slot[i]) {
slot[i] = bp;
return i;
}
}
return -EBUSY;
}
static void set_ibreak_regs(int reg, struct perf_event *bp)
{
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
unsigned long ibreakenable;
xtensa_wsr(info->address, SREG_IBREAKA + reg);
RSR(ibreakenable, SREG_IBREAKENABLE);
WSR(ibreakenable | (1 << reg), SREG_IBREAKENABLE);
}
static void set_dbreak_regs(int reg, struct perf_event *bp)
{
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
unsigned long dbreakc = DBREAKC_MASK_MASK & -info->len;
if (info->type & XTENSA_BREAKPOINT_LOAD)
dbreakc |= DBREAKC_LOAD_MASK;
if (info->type & XTENSA_BREAKPOINT_STORE)
dbreakc |= DBREAKC_STOR_MASK;
xtensa_wsr(info->address, SREG_DBREAKA + reg);
xtensa_wsr(dbreakc, SREG_DBREAKC + reg);
}
int arch_install_hw_breakpoint(struct perf_event *bp)
{
int i;
if (counter_arch_bp(bp)->type == XTENSA_BREAKPOINT_EXECUTE) {
/* Breakpoint */
i = alloc_slot(this_cpu_ptr(bp_on_reg), XCHAL_NUM_IBREAK, bp);
if (i < 0)
return i;
set_ibreak_regs(i, bp);
} else {
/* Watchpoint */
i = alloc_slot(this_cpu_ptr(wp_on_reg), XCHAL_NUM_DBREAK, bp);
if (i < 0)
return i;
set_dbreak_regs(i, bp);
}
return 0;
}
static int free_slot(struct perf_event **slot, size_t n,
struct perf_event *bp)
{
size_t i;
for (i = 0; i < n; ++i) {
if (slot[i] == bp) {
slot[i] = NULL;
return i;
}
}
return -EBUSY;
}
void arch_uninstall_hw_breakpoint(struct perf_event *bp)
{
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
int i;
if (info->type == XTENSA_BREAKPOINT_EXECUTE) {
unsigned long ibreakenable;
/* Breakpoint */
i = free_slot(this_cpu_ptr(bp_on_reg), XCHAL_NUM_IBREAK, bp);
if (i >= 0) {
RSR(ibreakenable, SREG_IBREAKENABLE);
WSR(ibreakenable & ~(1 << i), SREG_IBREAKENABLE);
}
} else {
/* Watchpoint */
i = free_slot(this_cpu_ptr(wp_on_reg), XCHAL_NUM_DBREAK, bp);
if (i >= 0)
xtensa_wsr(0, SREG_DBREAKC + i);
}
}
void hw_breakpoint_pmu_read(struct perf_event *bp)
{
}
void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
{
int i;
struct thread_struct *t = &tsk->thread;
for (i = 0; i < XCHAL_NUM_IBREAK; ++i) {
if (t->ptrace_bp[i]) {
unregister_hw_breakpoint(t->ptrace_bp[i]);
t->ptrace_bp[i] = NULL;
}
}
for (i = 0; i < XCHAL_NUM_DBREAK; ++i) {
if (t->ptrace_wp[i]) {
unregister_hw_breakpoint(t->ptrace_wp[i]);
t->ptrace_wp[i] = NULL;
}
}
}
/*
* Set ptrace breakpoint pointers to zero for this task.
* This is required in order to prevent child processes from unregistering
* breakpoints held by their parent.
*/
void clear_ptrace_hw_breakpoint(struct task_struct *tsk)
{
memset(tsk->thread.ptrace_bp, 0, sizeof(tsk->thread.ptrace_bp));
memset(tsk->thread.ptrace_wp, 0, sizeof(tsk->thread.ptrace_wp));
}
void restore_dbreak(void)
{
int i;
for (i = 0; i < XCHAL_NUM_DBREAK; ++i) {
struct perf_event *bp = this_cpu_ptr(wp_on_reg)[i];
if (bp)
set_dbreak_regs(i, bp);
}
clear_thread_flag(TIF_DB_DISABLED);
}
int check_hw_breakpoint(struct pt_regs *regs)
{
if (regs->debugcause & BIT(DEBUGCAUSE_IBREAK_BIT)) {
int i;
struct perf_event **bp = this_cpu_ptr(bp_on_reg);
for (i = 0; i < XCHAL_NUM_IBREAK; ++i) {
if (bp[i] && !bp[i]->attr.disabled &&
regs->pc == bp[i]->attr.bp_addr)
perf_bp_event(bp[i], regs);
}
return 0;
} else if (regs->debugcause & BIT(DEBUGCAUSE_DBREAK_BIT)) {
struct perf_event **bp = this_cpu_ptr(wp_on_reg);
int dbnum = (regs->debugcause & DEBUGCAUSE_DBNUM_MASK) >>
DEBUGCAUSE_DBNUM_SHIFT;
if (dbnum < XCHAL_NUM_DBREAK && bp[dbnum]) {
if (user_mode(regs)) {
perf_bp_event(bp[dbnum], regs);
} else {
set_thread_flag(TIF_DB_DISABLED);
xtensa_wsr(0, SREG_DBREAKC + dbnum);
}
} else {
WARN_ONCE(1,
"Wrong/unconfigured DBNUM reported in DEBUGCAUSE: %d\n",
dbnum);
}
return 0;
}
return -ENOENT;
}
......@@ -24,6 +24,7 @@
#include <linux/unistd.h>
#include <linux/ptrace.h>
#include <linux/elf.h>
#include <linux/hw_breakpoint.h>
#include <linux/init.h>
#include <linux/prctl.h>
#include <linux/init_task.h>
......@@ -43,6 +44,7 @@
#include <linux/atomic.h>
#include <asm/asm-offsets.h>
#include <asm/regs.h>
#include <asm/hw_breakpoint.h>
extern void ret_from_fork(void);
extern void ret_from_kernel_thread(void);
......@@ -131,6 +133,7 @@ void flush_thread(void)
coprocessor_flush_all(ti);
coprocessor_release_all(ti);
#endif
flush_ptrace_hw_breakpoint(current);
}
/*
......@@ -273,6 +276,8 @@ int copy_thread(unsigned long clone_flags, unsigned long usp_thread_fn,
ti->cpenable = 0;
#endif
clear_ptrace_hw_breakpoint(p);
return 0;
}
......