summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--arch/sh/include/asm/hw_breakpoint.h22
-rw-r--r--arch/sh/include/asm/processor_32.h4
-rw-r--r--arch/sh/kernel/cpu/sh4a/Makefile9
-rw-r--r--arch/sh/kernel/cpu/sh4a/ubc.c133
-rw-r--r--arch/sh/kernel/hw_breakpoint.c164
5 files changed, 254 insertions, 78 deletions
diff --git a/arch/sh/include/asm/hw_breakpoint.h b/arch/sh/include/asm/hw_breakpoint.h
index 0f4a00f6005..7295d629024 100644
--- a/arch/sh/include/asm/hw_breakpoint.h
+++ b/arch/sh/include/asm/hw_breakpoint.h
@@ -3,7 +3,6 @@
#include <linux/kdebug.h>
#include <linux/types.h>
-#include <asm/ubc.h>
#ifdef __KERNEL__
#define __ARCH_HW_BREAKPOINT_H
@@ -11,7 +10,6 @@
struct arch_hw_breakpoint {
char *name; /* Contains name of the symbol to set bkpt */
unsigned long address;
- unsigned long asid;
u16 len;
u16 type;
};
@@ -27,13 +25,28 @@ enum {
SH_BREAKPOINT_LEN_8 = (1 << 14),
};
-/* Total number of available UBC channels */
-#define HBP_NUM 1 /* XXX */
+struct sh_ubc {
+ const char *name;
+ unsigned int num_events;
+ unsigned int trap_nr;
+ void (*enable)(struct arch_hw_breakpoint *, int);
+ void (*disable)(struct arch_hw_breakpoint *, int);
+ void (*enable_all)(unsigned long);
+ void (*disable_all)(void);
+ unsigned long (*active_mask)(void);
+ unsigned long (*triggered_mask)(void);
+ void (*clear_triggered_mask)(unsigned long);
+ struct clk *clk; /* optional interface clock / MSTP bit */
+};
struct perf_event;
struct task_struct;
struct pmu;
+/* Maximum number of UBC channels */
+#define HBP_NUM 2
+
+/* arch/sh/kernel/hw_breakpoint.c */
extern int arch_check_va_in_userspace(unsigned long va, u16 hbp_len);
extern int arch_validate_hwbkpt_settings(struct perf_event *bp,
struct task_struct *tsk);
@@ -46,6 +59,7 @@ void hw_breakpoint_pmu_read(struct perf_event *bp);
void hw_breakpoint_pmu_unthrottle(struct perf_event *bp);
extern void arch_fill_perf_breakpoint(struct perf_event *bp);
+extern int register_sh_ubc(struct sh_ubc *);
extern struct pmu perf_ops_bp;
diff --git a/arch/sh/include/asm/processor_32.h b/arch/sh/include/asm/processor_32.h
index d60b28271a0..259112cecbd 100644
--- a/arch/sh/include/asm/processor_32.h
+++ b/arch/sh/include/asm/processor_32.h
@@ -14,7 +14,7 @@
#include <asm/page.h>
#include <asm/types.h>
#include <asm/ptrace.h>
-#include <asm/ubc.h>
+#include <asm/hw_breakpoint.h>
/*
* Default implementation of macro that returns current
@@ -102,7 +102,7 @@ struct thread_struct {
unsigned long pc;
/* Save middle states of ptrace breakpoints */
- struct perf_event *ptrace_bps[NR_UBC_CHANNELS];
+ struct perf_event *ptrace_bps[HBP_NUM];
/* floating point info */
union sh_fpu_union fpu;
diff --git a/arch/sh/kernel/cpu/sh4a/Makefile b/arch/sh/kernel/cpu/sh4a/Makefile
index 33bab477d2e..b144e8af89d 100644
--- a/arch/sh/kernel/cpu/sh4a/Makefile
+++ b/arch/sh/kernel/cpu/sh4a/Makefile
@@ -41,7 +41,8 @@ pinmux-$(CONFIG_CPU_SUBTYPE_SH7757) := pinmux-sh7757.o
pinmux-$(CONFIG_CPU_SUBTYPE_SH7785) := pinmux-sh7785.o
pinmux-$(CONFIG_CPU_SUBTYPE_SH7786) := pinmux-sh7786.o
-obj-y += $(clock-y)
-obj-$(CONFIG_SMP) += $(smp-y)
-obj-$(CONFIG_GENERIC_GPIO) += $(pinmux-y)
-obj-$(CONFIG_PERF_EVENTS) += perf_event.o
+obj-y += $(clock-y)
+obj-$(CONFIG_SMP) += $(smp-y)
+obj-$(CONFIG_GENERIC_GPIO) += $(pinmux-y)
+obj-$(CONFIG_PERF_EVENTS) += perf_event.o
+obj-$(CONFIG_HAVE_HW_BREAKPOINT) += ubc.o
diff --git a/arch/sh/kernel/cpu/sh4a/ubc.c b/arch/sh/kernel/cpu/sh4a/ubc.c
new file mode 100644
index 00000000000..efb2745bcb3
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh4a/ubc.c
@@ -0,0 +1,133 @@
+/*
+ * arch/sh/kernel/cpu/sh4a/ubc.c
+ *
+ * On-chip UBC support for SH-4A CPUs.
+ *
+ * Copyright (C) 2009 - 2010 Paul Mundt
+ *
+ * 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.
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <asm/hw_breakpoint.h>
+
+#define UBC_CBR(idx) (0xff200000 + (0x20 * idx))
+#define UBC_CRR(idx) (0xff200004 + (0x20 * idx))
+#define UBC_CAR(idx) (0xff200008 + (0x20 * idx))
+#define UBC_CAMR(idx) (0xff20000c + (0x20 * idx))
+
+#define UBC_CCMFR 0xff200600
+#define UBC_CBCR 0xff200620
+
+/* CRR */
+#define UBC_CRR_PCB (1 << 1)
+#define UBC_CRR_BIE (1 << 0)
+
+/* CBR */
+#define UBC_CBR_CE (1 << 0)
+
+static struct sh_ubc sh4a_ubc;
+
+static void sh4a_ubc_enable(struct arch_hw_breakpoint *info, int idx)
+{
+ __raw_writel(UBC_CBR_CE | info->len | info->type, UBC_CBR(idx));
+ __raw_writel(info->address, UBC_CAR(idx));
+}
+
+static void sh4a_ubc_disable(struct arch_hw_breakpoint *info, int idx)
+{
+ __raw_writel(0, UBC_CBR(idx));
+ __raw_writel(0, UBC_CAR(idx));
+}
+
+static void sh4a_ubc_enable_all(unsigned long mask)
+{
+ int i;
+
+ for (i = 0; i < sh4a_ubc.num_events; i++)
+ if (mask & (1 << i))
+ __raw_writel(__raw_readl(UBC_CBR(i)) | UBC_CBR_CE,
+ UBC_CBR(i));
+}
+
+static void sh4a_ubc_disable_all(void)
+{
+ int i;
+
+ for (i = 0; i < sh4a_ubc.num_events; i++)
+ __raw_writel(__raw_readl(UBC_CBR(i)) & ~UBC_CBR_CE,
+ UBC_CBR(i));
+}
+
+static unsigned long sh4a_ubc_active_mask(void)
+{
+ unsigned long active = 0;
+ int i;
+
+ for (i = 0; i < sh4a_ubc.num_events; i++)
+ if (__raw_readl(UBC_CBR(i)) & UBC_CBR_CE)
+ active |= (1 << i);
+
+ return active;
+}
+
+static unsigned long sh4a_ubc_triggered_mask(void)
+{
+ return __raw_readl(UBC_CCMFR);
+}
+
+static void sh4a_ubc_clear_triggered_mask(unsigned long mask)
+{
+ __raw_writel(__raw_readl(UBC_CCMFR) & ~mask, UBC_CCMFR);
+}
+
+static struct sh_ubc sh4a_ubc = {
+ .name = "SH-4A",
+ .num_events = 2,
+ .trap_nr = 0x1e0,
+ .enable = sh4a_ubc_enable,
+ .disable = sh4a_ubc_disable,
+ .enable_all = sh4a_ubc_enable_all,
+ .disable_all = sh4a_ubc_disable_all,
+ .active_mask = sh4a_ubc_active_mask,
+ .triggered_mask = sh4a_ubc_triggered_mask,
+ .clear_triggered_mask = sh4a_ubc_clear_triggered_mask,
+};
+
+static int __init sh4a_ubc_init(void)
+{
+ struct clk *ubc_iclk = clk_get(NULL, "ubc0");
+ int i;
+
+ /*
+ * The UBC MSTP bit is optional, as not all platforms will have
+ * it. Just ignore it if we can't find it.
+ */
+ if (IS_ERR(ubc_iclk))
+ ubc_iclk = NULL;
+
+ clk_enable(ubc_iclk);
+
+ __raw_writel(0, UBC_CBCR);
+
+ for (i = 0; i < sh4a_ubc.num_events; i++) {
+ __raw_writel(0, UBC_CAMR(i));
+ __raw_writel(0, UBC_CBR(i));
+
+ __raw_writel(UBC_CRR_BIE | UBC_CRR_PCB, UBC_CRR(i));
+
+ /* dummy read for write posting */
+ (void)__raw_readl(UBC_CRR(i));
+ }
+
+ clk_disable(ubc_iclk);
+
+ sh4a_ubc.clk = ubc_iclk;
+
+ return register_sh_ubc(&sh4a_ubc);
+}
+arch_initcall(sh4a_ubc_init);
diff --git a/arch/sh/kernel/hw_breakpoint.c b/arch/sh/kernel/hw_breakpoint.c
index c515a3ecf56..e2f1753d275 100644
--- a/arch/sh/kernel/hw_breakpoint.c
+++ b/arch/sh/kernel/hw_breakpoint.c
@@ -3,7 +3,7 @@
*
* Unified kernel/user-space hardware breakpoint facility for the on-chip UBC.
*
- * Copyright (C) 2009 Paul Mundt
+ * Copyright (C) 2009 - 2010 Paul Mundt
*
* 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
@@ -18,38 +18,24 @@
#include <linux/kprobes.h>
#include <linux/kdebug.h>
#include <linux/io.h>
+#include <linux/clk.h>
#include <asm/hw_breakpoint.h>
#include <asm/mmu_context.h>
#include <asm/ptrace.h>
-struct ubc_context {
- unsigned long pc;
- unsigned long state;
-};
-
-/* Per cpu ubc channel state */
-static DEFINE_PER_CPU(struct ubc_context, ubc_ctx[HBP_NUM]);
-
/*
* Stores the breakpoints currently in use on each breakpoint address
* register for each cpus
*/
static DEFINE_PER_CPU(struct perf_event *, bp_per_reg[HBP_NUM]);
-static int __init ubc_init(void)
-{
- __raw_writel(0, UBC_CAMR0);
- __raw_writel(0, UBC_CBR0);
- __raw_writel(0, UBC_CBCR);
-
- __raw_writel(UBC_CRR_BIE | UBC_CRR_PCB, UBC_CRR0);
-
- /* dummy read for write posting */
- (void)__raw_readl(UBC_CRR0);
+/*
+ * A dummy placeholder for early accesses until the CPUs get a chance to
+ * register their UBCs later in the boot process.
+ */
+static struct sh_ubc ubc_dummy = { .num_events = 0 };
- return 0;
-}
-arch_initcall(ubc_init);
+static struct sh_ubc *sh_ubc __read_mostly = &ubc_dummy;
/*
* Install a perf counter breakpoint.
@@ -62,10 +48,9 @@ arch_initcall(ubc_init);
int arch_install_hw_breakpoint(struct perf_event *bp)
{
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
- struct ubc_context *ubc_ctx;
int i;
- for (i = 0; i < HBP_NUM; i++) {
+ for (i = 0; i < sh_ubc->num_events; i++) {
struct perf_event **slot = &__get_cpu_var(bp_per_reg[i]);
if (!*slot) {
@@ -74,16 +59,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
}
}
- if (WARN_ONCE(i == HBP_NUM, "Can't find any breakpoint slot"))
+ if (WARN_ONCE(i == sh_ubc->num_events, "Can't find any breakpoint slot"))
return -EBUSY;
- ubc_ctx = &__get_cpu_var(ubc_ctx[i]);
-
- ubc_ctx->pc = info->address;
- ubc_ctx->state = info->len | info->type;
-
- __raw_writel(UBC_CBR_CE | ubc_ctx->state, UBC_CBR0);
- __raw_writel(ubc_ctx->pc, UBC_CAR0);
+ clk_enable(sh_ubc->clk);
+ sh_ubc->enable(info, i);
return 0;
}
@@ -100,10 +80,9 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
void arch_uninstall_hw_breakpoint(struct perf_event *bp)
{
struct arch_hw_breakpoint *info = counter_arch_bp(bp);
- struct ubc_context *ubc_ctx;
int i;
- for (i = 0; i < HBP_NUM; i++) {
+ for (i = 0; i < sh_ubc->num_events; i++) {
struct perf_event **slot = &__get_cpu_var(bp_per_reg[i]);
if (*slot == bp) {
@@ -112,15 +91,11 @@ void arch_uninstall_hw_breakpoint(struct perf_event *bp)
}
}
- if (WARN_ONCE(i == HBP_NUM, "Can't find any breakpoint slot"))
+ if (WARN_ONCE(i == sh_ubc->num_events, "Can't find any breakpoint slot"))
return;
- ubc_ctx = &__get_cpu_var(ubc_ctx[i]);
- ubc_ctx->pc = 0;
- ubc_ctx->state &= ~(info->len | info->type);
-
- __raw_writel(ubc_ctx->pc, UBC_CBR0);
- __raw_writel(ubc_ctx->state, UBC_CAR0);
+ sh_ubc->disable(info, i);
+ clk_disable(sh_ubc->clk);
}
static int get_hbp_len(u16 hbp_len)
@@ -182,10 +157,8 @@ static int arch_store_info(struct perf_event *bp)
*/
if (info->name)
info->address = (unsigned long)kallsyms_lookup_name(info->name);
- if (info->address) {
- info->asid = get_asid();
+ if (info->address)
return 0;
- }
return -EINVAL;
}
@@ -335,7 +308,7 @@ void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
int i;
struct thread_struct *t = &tsk->thread;
- for (i = 0; i < HBP_NUM; i++) {
+ for (i = 0; i < sh_ubc->num_events; i++) {
unregister_hw_breakpoint(t->ptrace_bps[i]);
t->ptrace_bps[i] = NULL;
}
@@ -345,13 +318,32 @@ static int __kprobes hw_breakpoint_handler(struct die_args *args)
{
int cpu, i, rc = NOTIFY_STOP;
struct perf_event *bp;
- unsigned long val;
+ unsigned int cmf, resume_mask;
+
+ /*
+ * Do an early return if none of the channels triggered.
+ */
+ cmf = sh_ubc->triggered_mask();
+ if (unlikely(!cmf))
+ return NOTIFY_DONE;
+
+ /*
+ * By default, resume all of the active channels.
+ */
+ resume_mask = sh_ubc->active_mask();
- val = __raw_readl(UBC_CBR0);
- __raw_writel(val & ~UBC_CBR_CE, UBC_CBR0);
+ /*
+ * Disable breakpoints during exception handling.
+ */
+ sh_ubc->disable_all();
cpu = get_cpu();
- for (i = 0; i < HBP_NUM; i++) {
+ for (i = 0; i < sh_ubc->num_events; i++) {
+ unsigned long event_mask = (1 << i);
+
+ if (likely(!(cmf & event_mask)))
+ continue;
+
/*
* The counter may be concurrently released but that can only
* occur from a call_rcu() path. We can then safely fetch
@@ -361,24 +353,52 @@ static int __kprobes hw_breakpoint_handler(struct die_args *args)
rcu_read_lock();
bp = per_cpu(bp_per_reg[i], cpu);
- if (bp) {
+ if (bp)
rc = NOTIFY_DONE;
- } else {
+
+ /*
+ * Reset the condition match flag to denote completion of
+ * exception handling.
+ */
+ sh_ubc->clear_triggered_mask(event_mask);
+
+ /*
+ * bp can be NULL due to concurrent perf counter
+ * removing.
+ */
+ if (!bp) {
rcu_read_unlock();
break;
}
+ /*
+ * Don't restore the channel if the breakpoint is from
+ * ptrace, as it always operates in one-shot mode.
+ */
+ if (bp->overflow_handler == ptrace_triggered)
+ resume_mask &= ~(1 << i);
+
perf_bp_event(bp, args->regs);
+ /* Deliver the signal to userspace */
+ if (arch_check_va_in_userspace(bp->attr.bp_addr,
+ bp->attr.bp_len)) {
+ siginfo_t info;
+
+ info.si_signo = args->signr;
+ info.si_errno = notifier_to_errno(rc);
+ info.si_code = TRAP_HWBKPT;
+
+ force_sig_info(args->signr, &info, current);
+ }
+
rcu_read_unlock();
}
- if (bp && bp->overflow_handler != ptrace_triggered) {
- struct arch_hw_breakpoint *info = counter_arch_bp(bp);
+ if (cmf == 0)
+ rc = NOTIFY_DONE;
- __raw_writel(UBC_CBR_CE | info->len | info->type, UBC_CBR0);
- __raw_writel(info->address, UBC_CAR0);
- }
+ sh_ubc->enable_all(resume_mask);
put_cpu();
@@ -388,19 +408,9 @@ static int __kprobes hw_breakpoint_handler(struct die_args *args)
BUILD_TRAP_HANDLER(breakpoint)
{
unsigned long ex = lookup_exception_vector();
- siginfo_t info;
- int err;
TRAP_HANDLER_DECL;
- err = notify_die(DIE_BREAKPOINT, "breakpoint", regs, 0, ex, SIGTRAP);
- if (err == NOTIFY_STOP)
- return;
-
- /* Deliver the signal to userspace */
- info.si_signo = SIGTRAP;
- info.si_errno = 0;
- info.si_code = TRAP_HWBKPT;
- force_sig_info(SIGTRAP, &info, current);
+ notify_die(DIE_BREAKPOINT, "breakpoint", regs, 0, ex, SIGTRAP);
}
/*
@@ -417,8 +427,12 @@ int __kprobes hw_breakpoint_exceptions_notify(struct notifier_block *unused,
/*
* If the breakpoint hasn't been triggered by the UBC, it's
* probably from a debugger, so don't do anything more here.
+ *
+ * This also permits the UBC interface clock to remain off for
+ * non-UBC breakpoints, as we don't need to check the triggered
+ * or active channel masks.
*/
- if (args->trapnr != 0x1e0)
+ if (args->trapnr != sh_ubc->trap_nr)
return NOTIFY_DONE;
return hw_breakpoint_handler(data);
@@ -433,3 +447,17 @@ void hw_breakpoint_pmu_unthrottle(struct perf_event *bp)
{
/* TODO */
}
+
+int register_sh_ubc(struct sh_ubc *ubc)
+{
+ /* Bail if it's already assigned */
+ if (sh_ubc != &ubc_dummy)
+ return -EBUSY;
+ sh_ubc = ubc;
+
+ pr_info("HW Breakpoints: %s UBC support registered\n", ubc->name);
+
+ WARN_ON(ubc->num_events > HBP_NUM);
+
+ return 0;
+}