coresight: abort coresight tracing on kernel crash

Add trace events to control aborting CoreSight trace
dynamically based on module parameter.
Coresight driver will dump any trace present in the current sink
in case we hit a kernel panic, user fault or an undefined instruction.

Change-Id: I668c8cda285250a0b8dcccd2c1d2a8471977ce5c
Signed-off-by: Satyajit Desai <sadesai@codeaurora.org>
Signed-off-by: Rama Aparna Mallavarapu <aparnam@codeaurora.org>
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index e576c1d..5620500 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -43,6 +43,7 @@
 #include <asm/exception.h>
 #include <asm/system_misc.h>
 #include <asm/sysreg.h>
+#include <trace/events/exception.h>
 
 static const char *handler[]= {
 	"Synchronous Abort",
@@ -458,6 +459,8 @@
 
 asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
 {
+	void __user *pc = (void __user *)instruction_pointer(regs);
+
 	/* check for AArch32 breakpoint instructions */
 	if (!aarch32_break_handler(regs))
 		return;
@@ -465,6 +468,8 @@
 	if (call_undef_hook(regs) == 0)
 		return;
 
+	trace_undef_instr(regs, pc);
+
 	force_signal_inject(SIGILL, ILL_ILLOPC, regs, 0);
 }
 
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index d0ffade..b6b81f8 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -41,6 +41,7 @@
 #include <asm/pgtable.h>
 #include <asm/tlbflush.h>
 #include <soc/qcom/scm.h>
+#include <trace/events/exception.h>
 
 struct fault_info {
 	int	(*fn)(unsigned long addr, unsigned int esr,
@@ -212,6 +213,8 @@
 	struct siginfo si;
 	const struct fault_info *inf;
 
+	trace_user_fault(tsk, addr, esr);
+
 	if (unhandled_signal(tsk, sig) && show_unhandled_signals_ratelimited()) {
 		inf = esr_to_fault_info(esr);
 		pr_info("%s[%d]: unhandled %s (%d) at 0x%08lx, esr 0x%03x\n",
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig
index 285b80d..ae494d7 100644
--- a/drivers/hwtracing/coresight/Kconfig
+++ b/drivers/hwtracing/coresight/Kconfig
@@ -148,6 +148,14 @@
 	  hardware component to another. It can also be used to pass
 	  software generated events.
 
+config CORESIGHT_EVENT
+        tristate "CoreSight Event driver"
+        help
+          This driver provides support for registering with various events
+          and performing CoreSight actions like aborting trace on their
+          occurrence. These events can be controlled by using module
+          parameters.
+
 config CORESIGHT_CSR
 	bool "CoreSight Slave Register driver"
 	help
diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile
index af94ad7..0b5e434 100644
--- a/drivers/hwtracing/coresight/Makefile
+++ b/drivers/hwtracing/coresight/Makefile
@@ -19,6 +19,7 @@
 obj-$(CONFIG_CORESIGHT_OST) += coresight-ost.o
 obj-$(CONFIG_CORESIGHT_TPDA) += coresight-tpda.o
 obj-$(CONFIG_CORESIGHT_TPDM) += coresight-tpdm.o
+obj-$(CONFIG_CORESIGHT_EVENT) += coresight-event.o
 obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o
 obj-$(CONFIG_CORESIGHT_CSR) += coresight-csr.o
 obj-$(CONFIG_CORESIGHT_HWEVENT) += coresight-hwevent.o
diff --git a/drivers/hwtracing/coresight/coresight-event.c b/drivers/hwtracing/coresight/coresight-event.c
new file mode 100644
index 0000000..d5f304f
--- /dev/null
+++ b/drivers/hwtracing/coresight/coresight-event.c
@@ -0,0 +1,169 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/coresight.h>
+
+#include <trace/events/exception.h>
+
+static int event_abort_enable;
+static int event_abort_set(const char *val, struct kernel_param *kp);
+module_param_call(event_abort_enable, event_abort_set, param_get_int,
+		  &event_abort_enable, 0644);
+
+static int event_abort_early_panic = 1;
+static int event_abort_on_panic_set(const char *val, struct kernel_param *kp);
+module_param_call(event_abort_early_panic, event_abort_on_panic_set,
+		  param_get_int, &event_abort_early_panic, 0644);
+
+static void event_abort_user_fault(void *ignore,
+				   struct task_struct *task,
+				   unsigned long addr,
+				   unsigned int fsr)
+{
+	coresight_abort();
+	pr_debug("coresight_event: task_name: %s, addr: %lu, fsr:%u",
+		(char *)task->comm, addr, fsr);
+}
+
+static void event_abort_undef_instr(void *ignore,
+				    struct pt_regs *regs,
+				    void __user *pc)
+{
+	if (user_mode(regs)) {
+		coresight_abort();
+		pr_debug("coresight_event: pc: %pK", pc);
+	}
+}
+
+static void event_abort_unhandled_abort(void *ignore,
+					struct pt_regs *regs,
+					unsigned long addr,
+					unsigned int fsr)
+{
+	if (user_mode(regs)) {
+		coresight_abort();
+		pr_debug("coresight_event: addr: %lu, fsr:%u", addr, fsr);
+	}
+}
+
+static void event_abort_kernel_panic(void *ignore, long state)
+{
+	coresight_abort();
+}
+
+static int event_abort_register(void)
+{
+	int ret;
+
+	ret = register_trace_user_fault(event_abort_user_fault, NULL);
+	if (ret)
+		goto err_usr_fault;
+	ret = register_trace_undef_instr(event_abort_undef_instr, NULL);
+	if (ret)
+		goto err_undef_instr;
+	ret = register_trace_unhandled_abort(event_abort_unhandled_abort, NULL);
+	if (ret)
+		goto err_unhandled_abort;
+
+	return 0;
+
+err_unhandled_abort:
+	unregister_trace_undef_instr(event_abort_undef_instr, NULL);
+err_undef_instr:
+	unregister_trace_user_fault(event_abort_user_fault, NULL);
+err_usr_fault:
+	return ret;
+}
+
+static void event_abort_unregister(void)
+{
+	unregister_trace_user_fault(event_abort_user_fault, NULL);
+	unregister_trace_undef_instr(event_abort_undef_instr, NULL);
+	unregister_trace_unhandled_abort(event_abort_unhandled_abort, NULL);
+}
+
+static int event_abort_set(const char *val, struct kernel_param *kp)
+{
+	int ret;
+
+	ret = param_set_int(val, kp);
+	if (ret) {
+		pr_err("coresight_event: error setting value %d\n", ret);
+		return ret;
+	}
+
+	if (event_abort_enable)
+		ret = event_abort_register();
+	else
+		event_abort_unregister();
+
+	return ret;
+}
+
+static int event_abort_on_panic_set(const char *val, struct kernel_param *kp)
+{
+	int ret;
+
+	ret = param_set_int(val, kp);
+	if (ret) {
+		pr_err("coresight_event: error setting val on panic %d\n", ret);
+		return ret;
+	}
+
+	if (event_abort_early_panic) {
+		unregister_trace_kernel_panic_late(event_abort_kernel_panic,
+						   NULL);
+		 ret = register_trace_kernel_panic(event_abort_kernel_panic,
+						  NULL);
+		if (ret)
+			goto err;
+	} else {
+		unregister_trace_kernel_panic(event_abort_kernel_panic, NULL);
+		ret = register_trace_kernel_panic_late(event_abort_kernel_panic,
+							NULL);
+		if (ret)
+			goto err;
+	}
+	return 0;
+err:
+	pr_err("coresight_event: error registering panic event %d\n", ret);
+	return ret;
+}
+
+static int __init event_init(void)
+{
+	int ret;
+
+	ret = register_trace_kernel_panic(event_abort_kernel_panic, NULL);
+	if (ret) {
+		/* We do not want to fail module init. This module can still
+		 * be used to register other abort events.
+		 */
+		pr_err("coresight_event: error registering on panic %d\n", ret);
+	}
+	return 0;
+}
+module_init(event_init);
+
+static void __exit event_exit(void)
+{
+	unregister_trace_kernel_panic(event_abort_kernel_panic, NULL);
+}
+module_exit(event_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Coresight Event driver to abort tracing");
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c
index 85fe87f..4ade209 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etf.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c
@@ -484,6 +484,36 @@
 	CS_LOCK(drvdata->base);
 }
 
+static void tmc_abort_etf_sink(struct coresight_device *csdev)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+	unsigned long flags;
+	enum tmc_mode mode;
+
+	spin_lock_irqsave(&drvdata->spinlock, flags);
+	if (drvdata->reading)
+		goto out0;
+
+	if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) {
+		tmc_etb_disable_hw(drvdata);
+	} else {
+
+		mode = readl_relaxed(drvdata->base + TMC_MODE);
+		if (mode == TMC_MODE_CIRCULAR_BUFFER)
+			tmc_etb_disable_hw(drvdata);
+		else
+			goto out1;
+	}
+out0:
+	drvdata->enable = false;
+	spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	dev_info(drvdata->dev, "TMC aborted\n");
+	return;
+out1:
+	spin_unlock_irqrestore(&drvdata->spinlock, flags);
+}
+
 static const struct coresight_ops_sink tmc_etf_sink_ops = {
 	.enable		= tmc_enable_etf_sink,
 	.disable	= tmc_disable_etf_sink,
@@ -492,6 +522,7 @@
 	.set_buffer	= tmc_set_etf_buffer,
 	.reset_buffer	= tmc_reset_etf_buffer,
 	.update_buffer	= tmc_update_etf_buffer,
+	.abort		= tmc_abort_etf_sink,
 };
 
 static const struct coresight_ops_link tmc_etf_link_ops = {
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index eb355f4..2a5a1bd 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -925,9 +925,30 @@
 	dev_info(drvdata->dev, "TMC-ETR disabled\n");
 }
 
+static void tmc_abort_etr_sink(struct coresight_device *csdev)
+{
+	struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+	unsigned long flags;
+
+	spin_lock_irqsave(&drvdata->spinlock, flags);
+	if (drvdata->reading)
+		goto out0;
+
+	if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM)
+		tmc_etr_disable_hw(drvdata);
+	else if (drvdata->out_mode == TMC_ETR_OUT_MODE_USB)
+		__tmc_etr_disable_to_bam(drvdata);
+out0:
+	drvdata->enable = false;
+	spin_unlock_irqrestore(&drvdata->spinlock, flags);
+
+	dev_info(drvdata->dev, "TMC aborted\n");
+}
+
 static const struct coresight_ops_sink tmc_etr_sink_ops = {
 	.enable		= tmc_enable_etr_sink,
 	.disable	= tmc_disable_etr_sink,
+	.abort		= tmc_abort_etr_sink,
 };
 
 const struct coresight_ops tmc_etr_cs_ops = {
diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c
index e233e76..7598ab8 100644
--- a/drivers/hwtracing/coresight/coresight.c
+++ b/drivers/hwtracing/coresight/coresight.c
@@ -29,7 +29,7 @@
 #include "coresight-priv.h"
 
 static DEFINE_MUTEX(coresight_mutex);
-
+static struct coresight_device *curr_sink;
 /**
  * struct coresight_node - elements of a path, from source to sink
  * @csdev:	Address of an element.
@@ -577,7 +577,7 @@
 	coresight_release_path(csdev, path);
 	goto out;
 }
-EXPORT_SYMBOL_GPL(coresight_enable);
+EXPORT_SYMBOL(coresight_enable);
 
 static void __coresight_disable(struct coresight_device *csdev)
 {
@@ -604,7 +604,27 @@
 	__coresight_disable(csdev);
 	mutex_unlock(&coresight_mutex);
 }
-EXPORT_SYMBOL_GPL(coresight_disable);
+EXPORT_SYMBOL(coresight_disable);
+
+void coresight_abort(void)
+{
+	if (!mutex_trylock(&coresight_mutex)) {
+		pr_err("coresight: abort could not be processed\n");
+		return;
+	}
+	if (!curr_sink)
+		goto out;
+
+	if (curr_sink->enable && sink_ops(curr_sink)->abort) {
+		sink_ops(curr_sink)->abort(curr_sink);
+		curr_sink->enable = false;
+	}
+
+out:
+	mutex_unlock(&coresight_mutex);
+}
+EXPORT_SYMBOL(coresight_abort);
+
 
 static ssize_t enable_sink_show(struct device *dev,
 				struct device_attribute *attr, char *buf)
diff --git a/include/linux/coresight.h b/include/linux/coresight.h
index 10842bb..a8003cc7 100644
--- a/include/linux/coresight.h
+++ b/include/linux/coresight.h
@@ -158,6 +158,7 @@
  * @activated:	'true' only if a _sink_ has been activated.  A sink can be
 		activated but not yet enabled.  Enabling for a _sink_
 		happens when a source has been selected for that it.
+ * @abort:     captures sink trace on abort.
  */
 struct coresight_device {
 	struct coresight_connection *conns;
@@ -206,6 +207,7 @@
 	void (*update_buffer)(struct coresight_device *csdev,
 			      struct perf_output_handle *handle,
 			      void *sink_config);
+	void (*abort)(struct coresight_device *csdev);
 };
 
 /**
@@ -252,6 +254,7 @@
 extern void coresight_disable(struct coresight_device *csdev);
 extern int coresight_timeout(void __iomem *addr, u32 offset,
 			     int position, int value);
+extern void coresight_abort(void);
 #else
 static inline struct coresight_device *
 coresight_register(struct coresight_desc *desc) { return NULL; }
@@ -261,6 +264,7 @@
 static inline void coresight_disable(struct coresight_device *csdev) {}
 static inline int coresight_timeout(void __iomem *addr, u32 offset,
 				     int position, int value) { return 1; }
+static inline void coresight_abort(void) {}
 #endif
 
 #ifdef CONFIG_OF
diff --git a/include/trace/events/exception.h b/include/trace/events/exception.h
new file mode 100644
index 0000000..2e8ddc1
--- /dev/null
+++ b/include/trace/events/exception.h
@@ -0,0 +1,124 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM exception
+
+#if !defined(_TRACE_EXCEPTION_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_EXCEPTION_H
+
+#include <linux/tracepoint.h>
+
+struct task_struct;
+
+TRACE_EVENT(user_fault,
+
+	TP_PROTO(struct task_struct *tsk, unsigned long addr, unsigned int fsr),
+
+	TP_ARGS(tsk, addr, fsr),
+
+	TP_STRUCT__entry(
+		__string(task_name, tsk->comm)
+		__field(unsigned long, addr)
+		__field(unsigned int, fsr)
+	),
+
+	TP_fast_assign(
+	__assign_str(task_name, tsk->comm)
+		__entry->addr	= addr;
+		__entry->fsr	= fsr;
+	),
+
+	TP_printk("task_name:%s addr:%lu, fsr:%u", __get_str(task_name),
+		__entry->addr, __entry->fsr)
+);
+
+
+struct pt_regs;
+
+TRACE_EVENT(undef_instr,
+
+	TP_PROTO(struct pt_regs *regs, void __user *prog_cnt),
+
+	TP_ARGS(regs, prog_cnt),
+
+	TP_STRUCT__entry(
+		__field(void __user *, prog_cnt)
+		__field(struct pt_regs *, regs)
+	),
+
+	TP_fast_assign(
+		__entry->regs		= regs;
+		__entry->prog_cnt	= prog_cnt;
+	),
+
+	TP_printk("pc:%p", __entry->prog_cnt)
+);
+
+TRACE_EVENT(unhandled_abort,
+
+	TP_PROTO(struct pt_regs *regs, unsigned long addr, unsigned int fsr),
+
+	TP_ARGS(regs, addr, fsr),
+
+	TP_STRUCT__entry(
+		__field(struct pt_regs *, regs)
+		__field(unsigned long, addr)
+		__field(unsigned int, fsr)
+	),
+
+	TP_fast_assign(
+		__entry->regs	= regs;
+		__entry->addr	= addr;
+		__entry->fsr	= fsr;
+	),
+
+	TP_printk("addr:%lu, fsr:%u", __entry->addr, __entry->fsr)
+);
+
+TRACE_EVENT(kernel_panic,
+
+	TP_PROTO(long dummy),
+
+	TP_ARGS(dummy),
+
+	TP_STRUCT__entry(
+		__field(long, dummy)
+	),
+
+	TP_fast_assign(
+		__entry->dummy	= dummy;
+	),
+
+	TP_printk("dummy:%ld", __entry->dummy)
+);
+
+TRACE_EVENT(kernel_panic_late,
+
+	TP_PROTO(long dummy),
+
+	TP_ARGS(dummy),
+
+	TP_STRUCT__entry(
+		__field(long, dummy)
+	),
+
+	TP_fast_assign(
+		__entry->dummy	= dummy;
+	),
+
+	TP_printk("dummy:%ld", __entry->dummy)
+);
+
+#endif
+
+#include <trace/define_trace.h>
diff --git a/kernel/panic.c b/kernel/panic.c
index dbec387..fcc8786 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -25,6 +25,8 @@
 #include <linux/nmi.h>
 #include <linux/console.h>
 #include <linux/bug.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/exception.h>
 
 #define PANIC_TIMER_STEP 100
 #define PANIC_BLINK_SPD 18
@@ -136,6 +138,8 @@
 	int old_cpu, this_cpu;
 	bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;
 
+	trace_kernel_panic(0);
+
 	/*
 	 * Disable local interrupts. This will prevent panic_smp_self_stop
 	 * from deadlocking the first cpu that invokes the panic, since
@@ -260,6 +264,9 @@
 			mdelay(PANIC_TIMER_STEP);
 		}
 	}
+
+	trace_kernel_panic_late(0);
+
 	if (panic_timeout != 0) {
 		/*
 		 * This will not be a clean reboot, with everything