Kernel  |  3.4

下载     查看原文件
C++程序  |  243行  |  5.44 KB
/*
 * 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);