[patch 2/3] S390-HWBKPT: :S390 architecture specific Hardware breakpoint interfaces.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Introduce s390 specific implementation for the perf-events based hardware
breakpoint interfaces defined in kernel/hw_breakpoint.c. Enable the
HAVE_HW_BREAKPOINT flag and the Makefile.

Signed-off-by: Mahesh Salgaonkar <mahesh@xxxxxxxxxxxxxxxxxx>
---
 arch/s390/Kconfig                       |    1 
 arch/s390/include/asm/Kbuild            |    1 
 arch/s390/kernel/Makefile               |    2 
 arch/s390/kernel/hw_breakpoint.c        |  340 ++++++++++++++++++++++++++++++++
 kernel/hw_breakpoint.c                  |   13 +
 samples/hw_breakpoint/data_breakpoint.c |    4 
 6 files changed, 360 insertions(+), 1 deletion(-)

Index: linux-2.6-tip/arch/s390/kernel/hw_breakpoint.c
===================================================================
--- /dev/null
+++ linux-2.6-tip/arch/s390/kernel/hw_breakpoint.c
@@ -0,0 +1,340 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2009 IBM Corporation
+ * Author: Mahesh Salgaonkar <mahesh@xxxxxxxxxxxxxxxxxx>
+ */
+
+/*
+ * HW_breakpoint: a unified kernel/user-space hardware breakpoint facility,
+ * using the CPU's PER control registers.
+ */
+
+#include <asm/bitsperlong.h>
+#include <linux/hw_breakpoint.h>
+#include <linux/irqflags.h>
+#include <linux/notifier.h>
+#include <linux/kallsyms.h>
+#include <linux/kprobes.h>
+#include <linux/percpu.h>
+#include <linux/kdebug.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/smp.h>
+
+#include <asm/hw_breakpoint.h>
+#include <asm/processor.h>
+#include <asm/sections.h>
+#include <asm/uaccess.h>
+
+/* Per cpu PER control register values */
+DEFINE_PER_CPU(per_cr_bits, cpu_per_regs[1]);
+
+/*
+ * 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]);
+
+/*
+ * Install a perf counter breakpoint.
+ *
+ * S390 provides only one hardware breakpoint register. Use it for this
+ * breakpoint if free.
+ *
+ * Atomic: we hold the counter->ctx->lock and we only handle variables,
+ * lowcore area and control registers local to this cpu.
+ */
+int arch_install_hw_breakpoint(struct perf_event *bp)
+{
+	struct arch_hw_breakpoint *info = counter_arch_bp(bp);
+	struct perf_event **slot;
+	struct task_struct *tsk = bp->ctx->task;
+	per_cr_bits per_regs[1];
+
+	memset(per_regs, 0, sizeof(per_cr_bits));
+
+	slot = &__get_cpu_var(bp_per_reg[0]);
+	if (!*slot) {
+		*slot = bp;
+	} else {
+		WARN_ONCE(1 , "Can't find any breakpoint slot");
+		return -EBUSY;
+	}
+
+	per_regs[0].em_storage_alteration =
+		(info->type == S390_BREAKPOINT_WRITE) ? 1 : 0;
+	per_regs[0].em_instruction_fetch =
+		(info->type == S390_BREAKPOINT_EXECUTE) ? 1 : 0;
+	per_regs[0].starting_addr = (unsigned long) info->address;
+	per_regs[0].ending_addr =
+		(unsigned long) info->address + info->len - 1;
+
+	/* Load the control register 9, 10 and 11 with per info */
+	__ctl_load(per_regs, 9, 11);
+	__get_cpu_var(cpu_per_regs[0]) = per_regs[0];
+
+	if (((per_cr_words *)per_regs)->cr[0] & PER_EM_MASK) {
+		if (!tsk) {
+			/* wide breakpoint in the kernel */
+			/* Disable lowcore protection */
+			__ctl_clear_bit(0, 28);
+
+			S390_lowcore.svc_new_psw.mask |= PSW_MASK_PER;
+
+			/* Enable lowcore protection */
+			__ctl_set_bit(0, 28);
+		} else {
+			/* user-space hardware breakpoint */
+			struct pt_regs *regs = task_pt_regs(tsk);
+
+			regs->psw.mask |= PSW_MASK_PER;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Uninstall the breakpoint contained in the given counter.
+ *
+ * Atomic: we hold the counter->ctx->lock and we only handle variables,
+ * lowcore area and control registers local to this cpu.
+ */
+void arch_uninstall_hw_breakpoint(struct perf_event *bp)
+{
+	struct perf_event **slot;
+	per_cr_bits per_regs[1];
+	struct task_struct *tsk = bp->ctx->task;
+
+	slot = &__get_cpu_var(bp_per_reg[0]);
+	if (*slot == bp) {
+		*slot = NULL;
+	} else {
+		WARN_ONCE(1, "Can't find any breakpoint slot");
+		return;
+	}
+	memset(per_regs, 0, sizeof(per_cr_bits));
+	__ctl_load(per_regs, 9, 11);
+
+	if (!tsk) {
+		/* wide breakpoint in the kernel */
+		/* Disable lowcore protection */
+		__ctl_clear_bit(0, 28);
+		S390_lowcore.svc_new_psw.mask &= ~PSW_MASK_PER;
+		/* Enable lowcore protection */
+		__ctl_set_bit(0, 28);
+	} else {
+		/* user-space hardware breakpoint */
+		struct pt_regs *regs = task_pt_regs(tsk);
+
+		regs->psw.mask &= ~PSW_MASK_PER;
+	}
+	return;
+}
+
+/*
+ * Check for virtual address in user space.
+ */
+int arch_check_va_in_userspace(unsigned long va, unsigned long hbp_len)
+{
+	return access_ok(VERIFY_WRITE, (void __user *)va, hbp_len);
+}
+
+static inline int is_kernel(unsigned long addr)
+{
+	if (addr >= (unsigned long)_stext && addr <= (unsigned long)_end)
+		return 1;
+	return in_gate_area_no_task(addr);
+}
+
+/*
+ * Check for virtual address in kernel space.
+ */
+static int arch_check_va_in_kernelspace(unsigned long va, unsigned long hbp_len)
+{
+	if (is_kernel(va))
+		return is_kernel(va + hbp_len);
+
+	if (is_module_address(va))
+		return is_module_address(va + hbp_len);
+
+	return 0;
+}
+
+/*
+ * Store a breakpoint's encoded address, length, and type.
+ */
+static int arch_store_info(struct perf_event *bp)
+{
+	struct arch_hw_breakpoint *info = counter_arch_bp(bp);
+	/*
+	 * For kernel-addresses, either the address or symbol name can be
+	 * specified.
+	 */
+	if (info->name)
+		info->address = (unsigned long)
+				kallsyms_lookup_name(info->name);
+
+	if (info->len == 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int arch_build_bp_info(struct perf_event *bp)
+{
+	struct arch_hw_breakpoint *info = counter_arch_bp(bp);
+
+	info->address = bp->attr.bp_addr;
+	info->len = bp->attr.bp_len;
+
+	/*
+	 * For now, we'll allow only data modification breakpoint.
+	 */
+	switch (bp->attr.bp_type) {
+	case HW_BREAKPOINT_W:
+		info->type = S390_BREAKPOINT_WRITE;
+		break;
+	case HW_BREAKPOINT_X:
+		info->type = S390_BREAKPOINT_EXECUTE;
+		if (info->len == 0)
+			info->len = 1;
+		break;
+	case HW_BREAKPOINT_W | HW_BREAKPOINT_R:
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+/*
+ * Validate the arch-specific HW Breakpoint register settings
+ */
+int arch_validate_hwbkpt_settings(struct perf_event *bp,
+					struct task_struct *tsk)
+{
+	struct arch_hw_breakpoint *info = counter_arch_bp(bp);
+	int ret;
+
+	ret = arch_build_bp_info(bp);
+	if (ret)
+		return ret;
+
+	ret = arch_store_info(bp);
+
+	if (ret < 0)
+		return ret;
+
+	/* Check that the virtual address is in the proper range */
+	if (tsk) {
+		if (!arch_check_va_in_userspace(info->address, info->len))
+			return -EFAULT;
+	} else {
+		if (!arch_check_va_in_kernelspace(info->address, info->len))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+/*
+ * Release the user breakpoints used by ptrace
+ */
+void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
+{
+	struct thread_struct *t = &tsk->thread;
+
+	unregister_hw_breakpoint(t->ptrace_bps[0]);
+	t->ptrace_bps[0] = NULL;
+}
+
+/*
+ * Handle debug exception notifications.
+ */
+static int __kprobes hw_breakpoint_handler(struct die_args *args)
+{
+	struct perf_event *bp;
+	per_cr_bits saved_cregs[1];
+	per_cr_bits per_regs[1];
+	per_lowcore_bits *per_code;
+	int cpu, rc = NOTIFY_STOP;
+
+	/* Do an early return if this is not a storage-alteration/instruction
+	 * fetch event */
+	per_code = (per_lowcore_bits *)&S390_lowcore.per_perc_atmid;
+	if ((!per_code->perc_storage_alteration &&
+			!per_code->perc_instruction_fetch) ||
+		(per_code->perc_storage_alteration &&
+			per_code->perc_store_real_address))
+		return NOTIFY_DONE;
+
+	/* Disable breakpoints during exception handling */
+	__ctl_store(saved_cregs, 9, 11);
+	memset(per_regs, 0, sizeof(per_cr_bits));
+	__ctl_load(per_regs, 9, 11);
+
+	cpu = get_cpu();
+
+	/*
+	 * The counter may be concurrently released but that can only
+	 * occur from a call_rcu() path. We can then safely fetch
+	 * the breakpoint, use its callback, touch its counter
+	 * while we are in an rcu_read_lock() path.
+	 */
+
+	rcu_read_lock();
+	bp = per_cpu(bp_per_reg[0], cpu);
+	/*
+	 * bp can be NULL due to lazy debug register switching
+	 * or due to concurrent perf counter removing.
+	 */
+	if (bp)
+		rc = NOTIFY_DONE;
+
+	perf_bp_event(bp, args->regs);
+	rcu_read_unlock();
+
+	/* Enable breakpoints */
+	__ctl_load(saved_cregs, 9, 11);
+	put_cpu();
+
+	return rc;
+}
+
+/*
+ * Handle debug exception notifications.
+ */
+int __kprobes hw_breakpoint_exceptions_notify(
+		struct notifier_block *unused, unsigned long val, void *data)
+{
+	if (val != DIE_SSTEP)
+		return NOTIFY_DONE;
+
+	return hw_breakpoint_handler(data);
+}
+
+void hw_breakpoint_pmu_read(struct perf_event *bp)
+{
+	/* TODO */
+}
+
+void hw_breakpoint_pmu_unthrottle(struct perf_event *bp)
+{
+	/* TODO */
+}
Index: linux-2.6-tip/arch/s390/kernel/Makefile
===================================================================
--- linux-2.6-tip.orig/arch/s390/kernel/Makefile
+++ linux-2.6-tip/arch/s390/kernel/Makefile
@@ -23,7 +23,7 @@ CFLAGS_sysinfo.o += -Iinclude/math-emu -
 obj-y	:=  bitmap.o traps.o time.o process.o base.o early.o setup.o \
 	    processor.o sys_s390.o ptrace.o signal.o cpcmd.o ebcdic.o \
 	    s390_ext.o debug.o irq.o ipl.o dis.o diag.o mem_detect.o \
-	    vdso.o vtime.o sysinfo.o nmi.o sclp.o
+	    vdso.o vtime.o sysinfo.o nmi.o sclp.o hw_breakpoint.o
 
 obj-y	+= $(if $(CONFIG_64BIT),entry64.o,entry.o)
 obj-y	+= $(if $(CONFIG_64BIT),reipl64.o,reipl.o)
Index: linux-2.6-tip/arch/s390/Kconfig
===================================================================
--- linux-2.6-tip.orig/arch/s390/Kconfig
+++ linux-2.6-tip/arch/s390/Kconfig
@@ -123,6 +123,7 @@ config S390
 	select ARCH_INLINE_WRITE_UNLOCK_BH
 	select ARCH_INLINE_WRITE_UNLOCK_IRQ
 	select ARCH_INLINE_WRITE_UNLOCK_IRQRESTORE
+	select HAVE_HW_BREAKPOINT
 
 config SCHED_OMIT_FRAME_POINTER
 	bool
Index: linux-2.6-tip/arch/s390/include/asm/Kbuild
===================================================================
--- linux-2.6-tip.orig/arch/s390/include/asm/Kbuild
+++ linux-2.6-tip/arch/s390/include/asm/Kbuild
@@ -8,6 +8,7 @@ header-y += ucontext.h
 header-y += vtoc.h
 header-y += zcrypt.h
 header-y += chsc.h
+header-y += hw_breakpoint.h
 
 unifdef-y += cmb.h
 unifdef-y += debug.h
Index: linux-2.6-tip/samples/hw_breakpoint/data_breakpoint.c
===================================================================
--- linux-2.6-tip.orig/samples/hw_breakpoint/data_breakpoint.c
+++ linux-2.6-tip/samples/hw_breakpoint/data_breakpoint.c
@@ -58,7 +58,11 @@ static int __init hw_break_module_init(v
 	hw_breakpoint_init(&attr);
 	attr.bp_addr = kallsyms_lookup_name(ksym_name);
 	attr.bp_len = HW_BREAKPOINT_LEN_4;
+#ifdef CONFIG_S390
+	attr.bp_type = HW_BREAKPOINT_W;
+#else
 	attr.bp_type = HW_BREAKPOINT_W | HW_BREAKPOINT_R;
+#endif
 
 	sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler);
 	if (IS_ERR(sample_hbp)) {
Index: linux-2.6-tip/kernel/hw_breakpoint.c
===================================================================
--- linux-2.6-tip.orig/kernel/hw_breakpoint.c
+++ linux-2.6-tip/kernel/hw_breakpoint.c
@@ -298,6 +298,10 @@ int register_perf_hw_breakpoint(struct p
 	if (!bp->attr.disabled || !bp->overflow_handler)
 		ret = arch_validate_hwbkpt_settings(bp, bp->ctx->task);
 
+	/* if arch_validate_hwbkpt_settings() fails then release bp slot */
+	if (ret)
+		release_bp_slot(bp);
+
 	return ret;
 }
 
@@ -391,7 +395,16 @@ register_wide_hw_breakpoint(struct perf_
 	if (!cpu_events)
 		return ERR_PTR(-ENOMEM);
 
+#ifdef CONFIG_S390
+	/* In s390, possible_cpu > online_cpu and the perf event framework does
+	 * not yet handle this situation and returns error -ENODEV.
+	 * We will use for_each_possible_cpu() once the perf event framework
+	 * handles this situation.
+	 */
+	for_each_online_cpu(cpu) {
+#else
 	for_each_possible_cpu(cpu) {
+#endif
 		pevent = per_cpu_ptr(cpu_events, cpu);
 		bp = perf_event_create_kernel_counter(attr, cpu, -1, triggered);
 

--
To unsubscribe from this list: send the line "unsubscribe linux-s390" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Kernel Development]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Info]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Linux Media]     [Device Mapper]

  Powered by Linux