diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/staging/android/Kconfig | 14 | ||||
-rw-r--r-- | drivers/staging/android/Makefile | 3 | ||||
-rw-r--r-- | drivers/staging/android/trace_persistent.c | 242 |
3 files changed, 259 insertions, 0 deletions
diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig index df29cdf69b4..829b990ddd0 100644 --- a/drivers/staging/android/Kconfig +++ b/drivers/staging/android/Kconfig @@ -36,6 +36,20 @@ config ANDROID_RAM_CONSOLE select ANDROID_PERSISTENT_RAM default n +config PERSISTENT_TRACER + bool "Persistent function tracer" + depends on HAVE_FUNCTION_TRACER + select FUNCTION_TRACER + select ANDROID_PERSISTENT_RAM + help + persistent_trace traces function calls into a persistent ram + buffer that can be decoded and dumped after reboot through + /sys/kernel/debug/persistent_trace. It can be used to + determine what function was last called before a reset or + panic. + + If unsure, say N. + config ANDROID_TIMED_OUTPUT bool "Timed output class driver" default y diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile index 765ec986cf4..8618ebf4e97 100644 --- a/drivers/staging/android/Makefile +++ b/drivers/staging/android/Makefile @@ -9,3 +9,6 @@ obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o obj-$(CONFIG_ANDROID_PMEM) += pmem.o obj-$(CONFIG_ANDROID_SWITCH) += switch/ obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o +obj-$(CONFIG_PERSISTENT_TRACER) += trace_persistent.o + +CFLAGS_REMOVE_trace_persistent.o = -pg diff --git a/drivers/staging/android/trace_persistent.c b/drivers/staging/android/trace_persistent.c new file mode 100644 index 00000000000..377d4d98995 --- /dev/null +++ b/drivers/staging/android/trace_persistent.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/debugfs.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/persistent_ram.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> + +#include "../../../kernel/trace/trace.h" + +struct persistent_trace_record { + unsigned long ip; + unsigned long parent_ip; +}; + +#define REC_SIZE sizeof(struct persistent_trace_record) + +static struct persistent_ram_zone *persistent_trace; + +static int persistent_trace_enabled; + +static struct trace_array *persistent_trace_array; + +static struct ftrace_ops trace_ops; + +static int persistent_tracer_init(struct trace_array *tr) +{ + persistent_trace_array = tr; + tr->cpu = get_cpu(); + put_cpu(); + + tracing_start_cmdline_record(); + + persistent_trace_enabled = 0; + smp_wmb(); + + register_ftrace_function(&trace_ops); + + smp_wmb(); + persistent_trace_enabled = 1; + + return 0; +} + +static void persistent_trace_reset(struct trace_array *tr) +{ + persistent_trace_enabled = 0; + smp_wmb(); + + unregister_ftrace_function(&trace_ops); + + tracing_stop_cmdline_record(); +} + +static void persistent_trace_start(struct trace_array *tr) +{ + tracing_reset_online_cpus(tr); +} + +static void persistent_trace_call(unsigned long ip, unsigned long parent_ip) +{ + struct trace_array *tr = persistent_trace_array; + struct trace_array_cpu *data; + long disabled; + struct persistent_trace_record rec; + unsigned long flags; + int cpu; + + smp_rmb(); + if (unlikely(!persistent_trace_enabled)) + return; + + if (unlikely(oops_in_progress)) + return; + + /* + * Need to use raw, since this must be called before the + * recursive protection is performed. + */ + local_irq_save(flags); + cpu = raw_smp_processor_id(); + data = tr->data[cpu]; + disabled = atomic_inc_return(&data->disabled); + + if (likely(disabled == 1)) { + rec.ip = ip; + rec.parent_ip = parent_ip; + rec.ip |= cpu; + persistent_ram_write(persistent_trace, &rec, sizeof(rec)); + } + + atomic_dec(&data->disabled); + local_irq_restore(flags); +} + +static struct ftrace_ops trace_ops __read_mostly = { + .func = persistent_trace_call, + .flags = FTRACE_OPS_FL_GLOBAL, +}; + +static struct tracer persistent_tracer __read_mostly = { + .name = "persistent", + .init = persistent_tracer_init, + .reset = persistent_trace_reset, + .start = persistent_trace_start, + .wait_pipe = poll_wait_pipe, +}; + +struct persistent_trace_seq_data { + const void *ptr; + size_t off; + size_t size; +}; + +void *persistent_trace_seq_start(struct seq_file *s, loff_t *pos) +{ + struct persistent_trace_seq_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->ptr = persistent_ram_old(persistent_trace); + data->size = persistent_ram_old_size(persistent_trace); + data->off = data->size % REC_SIZE; + + data->off += *pos * REC_SIZE; + + if (data->off + REC_SIZE > data->size) { + kfree(data); + return NULL; + } + + return data; + +} +void persistent_trace_seq_stop(struct seq_file *s, void *v) +{ + kfree(v); +} + +void *persistent_trace_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct persistent_trace_seq_data *data = v; + + data->off += REC_SIZE; + + if (data->off + REC_SIZE > data->size) + return NULL; + + (*pos)++; + + return data; +} + +int persistent_trace_seq_show(struct seq_file *s, void *v) +{ + struct persistent_trace_seq_data *data = v; + struct persistent_trace_record *rec; + + rec = (struct persistent_trace_record *)(data->ptr + data->off); + + seq_printf(s, "%ld %08lx %08lx %pf <- %pF\n", + rec->ip & 3, rec->ip, rec->parent_ip, + (void *)rec->ip, (void *)rec->parent_ip); + + return 0; +} + +static const struct seq_operations persistent_trace_seq_ops = { + .start = persistent_trace_seq_start, + .next = persistent_trace_seq_next, + .stop = persistent_trace_seq_stop, + .show = persistent_trace_seq_show, +}; + +static int persistent_trace_old_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &persistent_trace_seq_ops); +} + +static const struct file_operations persistent_trace_old_fops = { + .open = persistent_trace_old_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int persistent_trace_probe(struct platform_device *pdev) +{ + struct dentry *d; + int ret; + + persistent_trace = persistent_ram_init_ringbuffer(&pdev->dev, false); + if (IS_ERR(persistent_trace)) { + pr_err("persistent_trace: failed to init ringbuffer: %ld\n", + PTR_ERR(persistent_trace)); + return PTR_ERR(persistent_trace); + } + + ret = register_tracer(&persistent_tracer); + if (ret) + pr_err("persistent_trace: failed to register tracer"); + + if (persistent_ram_old_size(persistent_trace) > 0) { + d = debugfs_create_file("persistent_trace", S_IRUGO, NULL, + NULL, &persistent_trace_old_fops); + if (IS_ERR_OR_NULL(d)) + pr_err("persistent_trace: failed to create old file\n"); + } + + return 0; +} + +static struct platform_driver persistent_trace_driver = { + .probe = persistent_trace_probe, + .driver = { + .name = "persistent_trace", + }, +}; + +static int __init persistent_trace_init(void) +{ + return platform_driver_register(&persistent_trace_driver); +} +core_initcall(persistent_trace_init); |