blob: 7c3e9499fba10ab0c85237a11e0365b217ef2526 [file] [log] [blame]
/*
* 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 __devinit 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);