diff options
author | Konrad Rzeszutek Wilk <konrad.wilk@oracle.com> | 2016-03-29 15:33:47 +0200 |
---|---|---|
committer | Konrad Rzeszutek Wilk <konrad.wilk@oracle.com> | 2016-03-29 15:33:47 +0200 |
commit | 8041dcc881c928134c546ae85e6e59e65804357c (patch) | |
tree | be5d1c21af8cf38ac32ed8708396881aabd44d4e /arch/xtensa/kernel/hw_breakpoint.c | |
parent | xen/apic: Provide Xen-specific version of cpu_present_to_apicid APIC op (diff) | |
parent | Linux 4.6-rc1 (diff) | |
download | linux-8041dcc881c928134c546ae85e6e59e65804357c.tar.xz linux-8041dcc881c928134c546ae85e6e59e65804357c.zip |
Merge tag 'v4.6-rc1' into for-linus-4.6
Linux 4.6-rc1
* tag 'v4.6-rc1': (12823 commits)
Linux 4.6-rc1
f2fs/crypto: fix xts_tweak initialization
NTB: Remove _addr functions from ntb_hw_amd
orangefs: fix orangefs_superblock locking
orangefs: fix do_readv_writev() handling of error halfway through
orangefs: have ->kill_sb() evict the VFS side of things first
orangefs: sanitize ->llseek()
orangefs-bufmap.h: trim unused junk
orangefs: saner calling conventions for getting a slot
orangefs_copy_{to,from}_bufmap(): don't pass bufmap pointer
orangefs: get rid of readdir_handle_s
thp: fix typo in khugepaged_scan_pmd()
MAINTAINERS: fill entries for KASAN
mm/filemap: generic_file_read_iter(): check for zero reads unconditionally
kasan: test fix: warn if the UAF could not be detected in kmalloc_uaf2
mm, kasan: stackdepot implementation. Enable stackdepot for SLAB
arch, ftrace: for KASAN put hard/soft IRQ entries into separate sections
mm, kasan: add GFP flags to KASAN API
mm, kasan: SLAB support
kasan: modify kmalloc_large_oob_right(), add kmalloc_pagealloc_oob_right()
...
Diffstat (limited to 'arch/xtensa/kernel/hw_breakpoint.c')
-rw-r--r-- | arch/xtensa/kernel/hw_breakpoint.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/arch/xtensa/kernel/hw_breakpoint.c b/arch/xtensa/kernel/hw_breakpoint.c new file mode 100644 index 000000000000..b35656ab7dbd --- /dev/null +++ b/arch/xtensa/kernel/hw_breakpoint.c @@ -0,0 +1,317 @@ +/* + * 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; +} |