From c34bf4be22d64499449d1cfe550b05365d048403 Mon Sep 17 00:00:00 2001 From: Satyajit Desai Date: Mon, 27 Jun 2016 12:39:34 -0700 Subject: [PATCH] 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: Iee1ccf5cbd7b767753a3115c0570e63fbe2aa8f3 Signed-off-by: Satyajit Desai --- arch/arm64/kernel/traps.c | 4 + arch/arm64/mm/fault.c | 4 + drivers/hwtracing/coresight/Kconfig | 8 + drivers/hwtracing/coresight/Makefile | 1 + drivers/hwtracing/coresight/coresight-event.c | 169 ++++++++++++++++++ drivers/hwtracing/coresight/coresight-tmc.c | 35 ++++ drivers/hwtracing/coresight/coresight-tpiu.c | 12 +- drivers/hwtracing/coresight/coresight.c | 19 ++ include/linux/coresight.h | 4 + include/trace/events/exception.h | 124 +++++++++++++ kernel/panic.c | 8 + 11 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 drivers/hwtracing/coresight/coresight-event.c create mode 100644 include/trace/events/exception.h diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c index e8b1f7910490..48b75ece4c17 100644 --- a/arch/arm64/kernel/traps.c +++ b/arch/arm64/kernel/traps.c @@ -44,6 +44,8 @@ #include #include +#include + static const char *handler[]= { "Synchronous Abort", "IRQ", @@ -421,6 +423,8 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs) if (call_undef_hook(regs) == 0) return; + trace_undef_instr(regs, (void *)pc); + if (unhandled_signal(current, SIGILL) && show_unhandled_signals_ratelimited()) { pr_info("%s[%d]: undefined instruction: pc=%p\n", current->comm, task_pid_nr(current), pc); diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 7bb08670fc10..69079e5bfc84 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -40,6 +40,8 @@ #include #include +#include + static const char *fault_name(unsigned int esr); /* @@ -118,6 +120,8 @@ static void __do_user_fault(struct task_struct *tsk, unsigned long addr, { struct siginfo si; + trace_user_fault(tsk, addr, esr); + if (unhandled_signal(tsk, sig) && show_unhandled_signals_ratelimited()) { pr_info("%s[%d]: unhandled %s (%d) at 0x%08lx, esr 0x%03x\n", tsk->comm, task_pid_nr(tsk), fault_name(esr), sig, diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 3228282dc49c..8c92a564299d 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -13,6 +13,14 @@ menuconfig CORESIGHT if CORESIGHT +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 ee3a77fede53..09433897b6a2 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_CORESIGHT) += coresight.o obj-$(CONFIG_OF) += of_coresight.o obj-$(CONFIG_CORESIGHT_CSR) += coresight-csr.o +obj-$(CONFIG_CORESIGHT_EVENT) += coresight-event.o obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o diff --git a/drivers/hwtracing/coresight/coresight-event.c b/drivers/hwtracing/coresight/coresight-event.c new file mode 100644 index 000000000000..0bced010d4c5 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-event.c @@ -0,0 +1,169 @@ +/* Copyright (c) 2012-2016, 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 +#include +#include +#include +#include +#include + +#include + +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 *pc) +{ + if (user_mode(regs)) { + coresight_abort(); + pr_debug("coresight_event: pc: %p", 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.c b/drivers/hwtracing/coresight/coresight-tmc.c index 10e50df1e6d5..d48d8485f979 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -1064,9 +1064,44 @@ static void tmc_disable_link(struct coresight_device *csdev, int inport, tmc_disable(drvdata, TMC_MODE_HARDWARE_FIFO); } +static void tmc_abort(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 if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + 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); + } 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_sink_ops = { .enable = tmc_enable_sink, .disable = tmc_disable_sink, + .abort = tmc_abort, }; static const struct coresight_ops_link tmc_link_ops = { diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c index 7214efd10db5..7baa1e750a23 100644 --- a/drivers/hwtracing/coresight/coresight-tpiu.c +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2012, 2016 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 @@ -103,9 +103,19 @@ static void tpiu_disable(struct coresight_device *csdev) dev_info(drvdata->dev, "TPIU disabled\n"); } +static void tpiu_abort(struct coresight_device *csdev) +{ + struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + tpiu_disable_hw(drvdata); + + dev_info(drvdata->dev, "TPIU aborted\n"); +} + static const struct coresight_ops_sink tpiu_sink_ops = { .enable = tpiu_enable, .disable = tpiu_disable, + .abort = tpiu_abort, }; static const struct coresight_ops tpiu_cs_ops = { diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index a4d2ac601556..c34599c0594d 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -383,6 +383,25 @@ out: } EXPORT_SYMBOL_GPL(coresight_disable); +void coresight_abort(void) +{ + if (!mutex_trylock(&coresight_mutex)) { + pr_err_ratelimited("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_GPL(coresight_abort); + static int coresight_disable_all_source(struct device *dev, void *data) { struct coresight_device *csdev; diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 903a8e852f5d..66bf56640fe1 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -194,10 +194,12 @@ struct coresight_device { * Operations available for sinks * @enable: enables the sink. * @disable: disables the sink. + * @abort: captures sink trace on abort */ struct coresight_ops_sink { int (*enable)(struct coresight_device *csdev); void (*disable)(struct coresight_device *csdev); + void (*abort)(struct coresight_device *csdev); }; /** @@ -239,6 +241,7 @@ extern int coresight_enable(struct coresight_device *csdev); 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; } @@ -248,6 +251,7 @@ coresight_enable(struct coresight_device *csdev) { return -ENOSYS; } 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 #if defined(CONFIG_OF) && defined(CONFIG_CORESIGHT) diff --git a/include/trace/events/exception.h b/include/trace/events/exception.h new file mode 100644 index 000000000000..6b525da1432e --- /dev/null +++ b/include/trace/events/exception.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2012-2016, 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 + +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 *prog_cnt), + + TP_ARGS(regs, prog_cnt), + + TP_STRUCT__entry( + __field(void *, 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 diff --git a/kernel/panic.c b/kernel/panic.c index 223564d3e1f8..b4a0edc489c5 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -25,6 +25,9 @@ #include #include +#define CREATE_TRACE_POINTS +#include + #define PANIC_TIMER_STEP 100 #define PANIC_BLINK_SPD 18 @@ -80,6 +83,8 @@ void panic(const char *fmt, ...) long i, i_next = 0; int state = 0; + trace_kernel_panic(0); + /* * Disable local interrupts. This will prevent panic_smp_self_stop * from deadlocking the first cpu that invokes the panic, since @@ -181,6 +186,9 @@ void panic(const char *fmt, ...) mdelay(PANIC_TIMER_STEP); } } + + trace_kernel_panic_late(0); + if (panic_timeout != 0) { /* * This will not be a clean reboot, with everything