1/*
2 * Copyright (C) 2012 Google, Inc.
3 *
4 * This software is licensed under the terms of the GNU General Public
5 * License version 2, as published by the Free Software Foundation, and
6 * may be copied, distributed, and modified under those terms.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 */
14
15#include <linux/debugfs.h>
16#include <linux/errno.h>
17#include <linux/kernel.h>
18#include <linux/init.h>
19#include <linux/io.h>
20#include <linux/persistent_ram.h>
21#include <linux/platform_device.h>
22#include <linux/seq_file.h>
23#include <linux/slab.h>
24
25#include "../../../kernel/trace/trace.h"
26
27struct persistent_trace_record {
28	unsigned long ip;
29	unsigned long parent_ip;
30};
31
32#define REC_SIZE sizeof(struct persistent_trace_record)
33
34static struct persistent_ram_zone *persistent_trace;
35
36static int persistent_trace_enabled;
37
38static struct trace_array *persistent_trace_array;
39
40static struct ftrace_ops trace_ops;
41
42static int persistent_tracer_init(struct trace_array *tr)
43{
44	persistent_trace_array = tr;
45	tr->cpu = get_cpu();
46	put_cpu();
47
48	tracing_start_cmdline_record();
49
50	persistent_trace_enabled = 0;
51	smp_wmb();
52
53	register_ftrace_function(&trace_ops);
54
55	smp_wmb();
56	persistent_trace_enabled = 1;
57
58	return 0;
59}
60
61static void persistent_trace_reset(struct trace_array *tr)
62{
63	persistent_trace_enabled = 0;
64	smp_wmb();
65
66	unregister_ftrace_function(&trace_ops);
67
68	tracing_stop_cmdline_record();
69}
70
71static void persistent_trace_start(struct trace_array *tr)
72{
73	tracing_reset_online_cpus(tr);
74}
75
76static void persistent_trace_call(unsigned long ip, unsigned long parent_ip)
77{
78	struct trace_array *tr = persistent_trace_array;
79	struct trace_array_cpu *data;
80	long disabled;
81	struct persistent_trace_record rec;
82	unsigned long flags;
83	int cpu;
84
85	smp_rmb();
86	if (unlikely(!persistent_trace_enabled))
87		return;
88
89	if (unlikely(oops_in_progress))
90		return;
91
92	/*
93	 * Need to use raw, since this must be called before the
94	 * recursive protection is performed.
95	 */
96	local_irq_save(flags);
97	cpu = raw_smp_processor_id();
98	data = tr->data[cpu];
99	disabled = atomic_inc_return(&data->disabled);
100
101	if (likely(disabled == 1)) {
102		rec.ip = ip;
103		rec.parent_ip = parent_ip;
104		rec.ip |= cpu;
105		persistent_ram_write(persistent_trace, &rec, sizeof(rec));
106	}
107
108	atomic_dec(&data->disabled);
109	local_irq_restore(flags);
110}
111
112static struct ftrace_ops trace_ops __read_mostly = {
113	.func = persistent_trace_call,
114	.flags = FTRACE_OPS_FL_GLOBAL,
115};
116
117static struct tracer persistent_tracer __read_mostly = {
118	.name		= "persistent",
119	.init		= persistent_tracer_init,
120	.reset		= persistent_trace_reset,
121	.start		= persistent_trace_start,
122	.wait_pipe	= poll_wait_pipe,
123};
124
125struct persistent_trace_seq_data {
126	const void *ptr;
127	size_t off;
128	size_t size;
129};
130
131void *persistent_trace_seq_start(struct seq_file *s, loff_t *pos)
132{
133	struct persistent_trace_seq_data *data;
134
135	data = kzalloc(sizeof(*data), GFP_KERNEL);
136	if (!data)
137		return NULL;
138
139	data->ptr = persistent_ram_old(persistent_trace);
140	data->size = persistent_ram_old_size(persistent_trace);
141	data->off = data->size % REC_SIZE;
142
143	data->off += *pos * REC_SIZE;
144
145	if (data->off + REC_SIZE > data->size) {
146		kfree(data);
147		return NULL;
148	}
149
150	return data;
151
152}
153void persistent_trace_seq_stop(struct seq_file *s, void *v)
154{
155	kfree(v);
156}
157
158void *persistent_trace_seq_next(struct seq_file *s, void *v, loff_t *pos)
159{
160	struct persistent_trace_seq_data *data = v;
161
162	data->off += REC_SIZE;
163
164	if (data->off + REC_SIZE > data->size)
165		return NULL;
166
167	(*pos)++;
168
169	return data;
170}
171
172int persistent_trace_seq_show(struct seq_file *s, void *v)
173{
174	struct persistent_trace_seq_data *data = v;
175	struct persistent_trace_record *rec;
176
177	rec = (struct persistent_trace_record *)(data->ptr + data->off);
178
179	seq_printf(s, "%ld %08lx  %08lx  %pf <- %pF\n",
180		rec->ip & 3, rec->ip, rec->parent_ip,
181		(void *)rec->ip, (void *)rec->parent_ip);
182
183	return 0;
184}
185
186static const struct seq_operations persistent_trace_seq_ops = {
187	.start = persistent_trace_seq_start,
188	.next = persistent_trace_seq_next,
189	.stop = persistent_trace_seq_stop,
190	.show = persistent_trace_seq_show,
191};
192
193static int persistent_trace_old_open(struct inode *inode, struct file *file)
194{
195	return seq_open(file, &persistent_trace_seq_ops);
196}
197
198static const struct file_operations persistent_trace_old_fops = {
199	.open		= persistent_trace_old_open,
200	.read		= seq_read,
201	.llseek		= seq_lseek,
202	.release	= seq_release,
203};
204
205static int __devinit persistent_trace_probe(struct platform_device *pdev)
206{
207	struct dentry *d;
208	int ret;
209
210	persistent_trace = persistent_ram_init_ringbuffer(&pdev->dev, false);
211	if (IS_ERR(persistent_trace)) {
212		pr_err("persistent_trace: failed to init ringbuffer: %ld\n",
213				PTR_ERR(persistent_trace));
214		return PTR_ERR(persistent_trace);
215	}
216
217	ret = register_tracer(&persistent_tracer);
218	if (ret)
219		pr_err("persistent_trace: failed to register tracer");
220
221	if (persistent_ram_old_size(persistent_trace) > 0) {
222		d = debugfs_create_file("persistent_trace", S_IRUGO, NULL,
223			NULL, &persistent_trace_old_fops);
224		if (IS_ERR_OR_NULL(d))
225			pr_err("persistent_trace: failed to create old file\n");
226	}
227
228	return 0;
229}
230
231static struct platform_driver persistent_trace_driver  = {
232	.probe = persistent_trace_probe,
233	.driver		= {
234		.name	= "persistent_trace",
235	},
236};
237
238static int __init persistent_trace_init(void)
239{
240	return platform_driver_register(&persistent_trace_driver);
241}
242core_initcall(persistent_trace_init);
243