diff options
author | Anton Vorontsov <anton.vorontsov@linaro.org> | 2012-07-09 20:10:41 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-07-17 13:05:52 -0400 |
commit | 060287b8c467bf49a594d8d669e1986c6d8d76b0 (patch) | |
tree | f869d1cc6d09875a0dc4f42b230f144617fc3bfc | |
parent | 897dba027445be93f40e5caf550556ca38c48c51 (diff) |
pstore: Add persistent function tracing
With this support kernel can save function call chain log into a
persistent ram buffer that can be decoded and dumped after reboot
through pstore filesystem. It can be used to determine what function
was last called before a reset or panic.
We store the log in a binary format and then decode it at read time.
p.s.
Mostly the code comes from trace_persistent.c driver found in the
Android git tree, written by Colin Cross <ccross@android.com>
(according to sign-off history). I reworked the driver a little bit,
and ported it to pstore.
Signed-off-by: Anton Vorontsov <anton.vorontsov@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | fs/pstore/Kconfig | 12 | ||||
-rw-r--r-- | fs/pstore/Makefile | 1 | ||||
-rw-r--r-- | fs/pstore/ftrace.c | 35 | ||||
-rw-r--r-- | fs/pstore/inode.c | 111 | ||||
-rw-r--r-- | fs/pstore/internal.h | 43 | ||||
-rw-r--r-- | fs/pstore/platform.c | 2 | ||||
-rw-r--r-- | include/linux/pstore.h | 9 |
7 files changed, 208 insertions, 5 deletions
diff --git a/fs/pstore/Kconfig b/fs/pstore/Kconfig index d044de6ee308..d39bb5cce883 100644 --- a/fs/pstore/Kconfig +++ b/fs/pstore/Kconfig | |||
@@ -19,6 +19,18 @@ config PSTORE_CONSOLE | |||
19 | When the option is enabled, pstore will log all kernel | 19 | When the option is enabled, pstore will log all kernel |
20 | messages, even if no oops or panic happened. | 20 | messages, even if no oops or panic happened. |
21 | 21 | ||
22 | config PSTORE_FTRACE | ||
23 | bool "Persistent function tracer" | ||
24 | depends on PSTORE | ||
25 | depends on FUNCTION_TRACER | ||
26 | help | ||
27 | With this option kernel traces function calls into a persistent | ||
28 | ram buffer that can be decoded and dumped after reboot through | ||
29 | pstore filesystem. It can be used to determine what function | ||
30 | was last called before a reset or panic. | ||
31 | |||
32 | If unsure, say N. | ||
33 | |||
22 | config PSTORE_RAM | 34 | config PSTORE_RAM |
23 | tristate "Log panic/oops to a RAM buffer" | 35 | tristate "Log panic/oops to a RAM buffer" |
24 | depends on PSTORE | 36 | depends on PSTORE |
diff --git a/fs/pstore/Makefile b/fs/pstore/Makefile index 278a44e0d4e1..4c9095c2781e 100644 --- a/fs/pstore/Makefile +++ b/fs/pstore/Makefile | |||
@@ -5,6 +5,7 @@ | |||
5 | obj-y += pstore.o | 5 | obj-y += pstore.o |
6 | 6 | ||
7 | pstore-objs += inode.o platform.o | 7 | pstore-objs += inode.o platform.o |
8 | obj-$(CONFIG_PSTORE_FTRACE) += ftrace.o | ||
8 | 9 | ||
9 | ramoops-objs += ram.o ram_core.o | 10 | ramoops-objs += ram.o ram_core.o |
10 | obj-$(CONFIG_PSTORE_RAM) += ramoops.o | 11 | obj-$(CONFIG_PSTORE_RAM) += ramoops.o |
diff --git a/fs/pstore/ftrace.c b/fs/pstore/ftrace.c new file mode 100644 index 000000000000..a130d484b7d3 --- /dev/null +++ b/fs/pstore/ftrace.c | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * Copyright 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 | #include <linux/kernel.h> | ||
15 | #include <linux/compiler.h> | ||
16 | #include <linux/irqflags.h> | ||
17 | #include <linux/percpu.h> | ||
18 | #include <linux/smp.h> | ||
19 | #include <linux/atomic.h> | ||
20 | #include <asm/barrier.h> | ||
21 | #include "internal.h" | ||
22 | |||
23 | void notrace pstore_ftrace_call(unsigned long ip, unsigned long parent_ip) | ||
24 | { | ||
25 | struct pstore_ftrace_record rec = {}; | ||
26 | |||
27 | if (unlikely(oops_in_progress)) | ||
28 | return; | ||
29 | |||
30 | rec.ip = ip; | ||
31 | rec.parent_ip = parent_ip; | ||
32 | pstore_ftrace_encode_cpu(&rec, raw_smp_processor_id()); | ||
33 | psinfo->write_buf(PSTORE_TYPE_FTRACE, 0, NULL, 0, (void *)&rec, | ||
34 | sizeof(rec), psinfo); | ||
35 | } | ||
diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c index 45bff5441b04..4ab572e6d277 100644 --- a/fs/pstore/inode.c +++ b/fs/pstore/inode.c | |||
@@ -27,6 +27,7 @@ | |||
27 | #include <linux/list.h> | 27 | #include <linux/list.h> |
28 | #include <linux/string.h> | 28 | #include <linux/string.h> |
29 | #include <linux/mount.h> | 29 | #include <linux/mount.h> |
30 | #include <linux/seq_file.h> | ||
30 | #include <linux/ramfs.h> | 31 | #include <linux/ramfs.h> |
31 | #include <linux/parser.h> | 32 | #include <linux/parser.h> |
32 | #include <linux/sched.h> | 33 | #include <linux/sched.h> |
@@ -52,18 +53,117 @@ struct pstore_private { | |||
52 | char data[]; | 53 | char data[]; |
53 | }; | 54 | }; |
54 | 55 | ||
56 | struct pstore_ftrace_seq_data { | ||
57 | const void *ptr; | ||
58 | size_t off; | ||
59 | size_t size; | ||
60 | }; | ||
61 | |||
62 | #define REC_SIZE sizeof(struct pstore_ftrace_record) | ||
63 | |||
64 | static void *pstore_ftrace_seq_start(struct seq_file *s, loff_t *pos) | ||
65 | { | ||
66 | struct pstore_private *ps = s->private; | ||
67 | struct pstore_ftrace_seq_data *data; | ||
68 | |||
69 | data = kzalloc(sizeof(*data), GFP_KERNEL); | ||
70 | if (!data) | ||
71 | return NULL; | ||
72 | |||
73 | data->off = ps->size % REC_SIZE; | ||
74 | data->off += *pos * REC_SIZE; | ||
75 | if (data->off + REC_SIZE > ps->size) { | ||
76 | kfree(data); | ||
77 | return NULL; | ||
78 | } | ||
79 | |||
80 | return data; | ||
81 | |||
82 | } | ||
83 | |||
84 | static void pstore_ftrace_seq_stop(struct seq_file *s, void *v) | ||
85 | { | ||
86 | kfree(v); | ||
87 | } | ||
88 | |||
89 | static void *pstore_ftrace_seq_next(struct seq_file *s, void *v, loff_t *pos) | ||
90 | { | ||
91 | struct pstore_private *ps = s->private; | ||
92 | struct pstore_ftrace_seq_data *data = v; | ||
93 | |||
94 | data->off += REC_SIZE; | ||
95 | if (data->off + REC_SIZE > ps->size) | ||
96 | return NULL; | ||
97 | |||
98 | (*pos)++; | ||
99 | return data; | ||
100 | } | ||
101 | |||
102 | static int pstore_ftrace_seq_show(struct seq_file *s, void *v) | ||
103 | { | ||
104 | struct pstore_private *ps = s->private; | ||
105 | struct pstore_ftrace_seq_data *data = v; | ||
106 | struct pstore_ftrace_record *rec = (void *)(ps->data + data->off); | ||
107 | |||
108 | seq_printf(s, "%d %08lx %08lx %pf <- %pF\n", | ||
109 | pstore_ftrace_decode_cpu(rec), rec->ip, rec->parent_ip, | ||
110 | (void *)rec->ip, (void *)rec->parent_ip); | ||
111 | |||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static const struct seq_operations pstore_ftrace_seq_ops = { | ||
116 | .start = pstore_ftrace_seq_start, | ||
117 | .next = pstore_ftrace_seq_next, | ||
118 | .stop = pstore_ftrace_seq_stop, | ||
119 | .show = pstore_ftrace_seq_show, | ||
120 | }; | ||
121 | |||
55 | static ssize_t pstore_file_read(struct file *file, char __user *userbuf, | 122 | static ssize_t pstore_file_read(struct file *file, char __user *userbuf, |
56 | size_t count, loff_t *ppos) | 123 | size_t count, loff_t *ppos) |
57 | { | 124 | { |
58 | struct pstore_private *ps = file->private_data; | 125 | struct seq_file *sf = file->private_data; |
126 | struct pstore_private *ps = sf->private; | ||
59 | 127 | ||
128 | if (ps->type == PSTORE_TYPE_FTRACE) | ||
129 | return seq_read(file, userbuf, count, ppos); | ||
60 | return simple_read_from_buffer(userbuf, count, ppos, ps->data, ps->size); | 130 | return simple_read_from_buffer(userbuf, count, ppos, ps->data, ps->size); |
61 | } | 131 | } |
62 | 132 | ||
133 | static int pstore_file_open(struct inode *inode, struct file *file) | ||
134 | { | ||
135 | struct pstore_private *ps = inode->i_private; | ||
136 | struct seq_file *sf; | ||
137 | int err; | ||
138 | const struct seq_operations *sops = NULL; | ||
139 | |||
140 | if (ps->type == PSTORE_TYPE_FTRACE) | ||
141 | sops = &pstore_ftrace_seq_ops; | ||
142 | |||
143 | err = seq_open(file, sops); | ||
144 | if (err < 0) | ||
145 | return err; | ||
146 | |||
147 | sf = file->private_data; | ||
148 | sf->private = ps; | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static loff_t pstore_file_llseek(struct file *file, loff_t off, int origin) | ||
154 | { | ||
155 | struct seq_file *sf = file->private_data; | ||
156 | |||
157 | if (sf->op) | ||
158 | return seq_lseek(file, off, origin); | ||
159 | return default_llseek(file, off, origin); | ||
160 | } | ||
161 | |||
63 | static const struct file_operations pstore_file_operations = { | 162 | static const struct file_operations pstore_file_operations = { |
64 | .open = simple_open, | 163 | .open = pstore_file_open, |
65 | .read = pstore_file_read, | 164 | .read = pstore_file_read, |
66 | .llseek = default_llseek, | 165 | .llseek = pstore_file_llseek, |
166 | .release = seq_release, | ||
67 | }; | 167 | }; |
68 | 168 | ||
69 | /* | 169 | /* |
@@ -215,6 +315,9 @@ int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id, | |||
215 | case PSTORE_TYPE_CONSOLE: | 315 | case PSTORE_TYPE_CONSOLE: |
216 | sprintf(name, "console-%s", psname); | 316 | sprintf(name, "console-%s", psname); |
217 | break; | 317 | break; |
318 | case PSTORE_TYPE_FTRACE: | ||
319 | sprintf(name, "ftrace-%s", psname); | ||
320 | break; | ||
218 | case PSTORE_TYPE_MCE: | 321 | case PSTORE_TYPE_MCE: |
219 | sprintf(name, "mce-%s-%lld", psname, id); | 322 | sprintf(name, "mce-%s-%lld", psname, id); |
220 | break; | 323 | break; |
diff --git a/fs/pstore/internal.h b/fs/pstore/internal.h index 3bde461c3f34..958c48d8905c 100644 --- a/fs/pstore/internal.h +++ b/fs/pstore/internal.h | |||
@@ -1,6 +1,49 @@ | |||
1 | #ifndef __PSTORE_INTERNAL_H__ | ||
2 | #define __PSTORE_INTERNAL_H__ | ||
3 | |||
4 | #include <linux/pstore.h> | ||
5 | |||
6 | #if NR_CPUS <= 2 && defined(CONFIG_ARM_THUMB) | ||
7 | #define PSTORE_CPU_IN_IP 0x1 | ||
8 | #elif NR_CPUS <= 4 && defined(CONFIG_ARM) | ||
9 | #define PSTORE_CPU_IN_IP 0x3 | ||
10 | #endif | ||
11 | |||
12 | struct pstore_ftrace_record { | ||
13 | unsigned long ip; | ||
14 | unsigned long parent_ip; | ||
15 | #ifndef PSTORE_CPU_IN_IP | ||
16 | unsigned int cpu; | ||
17 | #endif | ||
18 | }; | ||
19 | |||
20 | static inline void | ||
21 | pstore_ftrace_encode_cpu(struct pstore_ftrace_record *rec, unsigned int cpu) | ||
22 | { | ||
23 | #ifndef PSTORE_CPU_IN_IP | ||
24 | rec->cpu = cpu; | ||
25 | #else | ||
26 | rec->ip |= cpu; | ||
27 | #endif | ||
28 | } | ||
29 | |||
30 | static inline unsigned int | ||
31 | pstore_ftrace_decode_cpu(struct pstore_ftrace_record *rec) | ||
32 | { | ||
33 | #ifndef PSTORE_CPU_IN_IP | ||
34 | return rec->cpu; | ||
35 | #else | ||
36 | return rec->ip & PSTORE_CPU_IN_IP; | ||
37 | #endif | ||
38 | } | ||
39 | |||
40 | extern struct pstore_info *psinfo; | ||
41 | |||
1 | extern void pstore_set_kmsg_bytes(int); | 42 | extern void pstore_set_kmsg_bytes(int); |
2 | extern void pstore_get_records(int); | 43 | extern void pstore_get_records(int); |
3 | extern int pstore_mkfile(enum pstore_type_id, char *psname, u64 id, | 44 | extern int pstore_mkfile(enum pstore_type_id, char *psname, u64 id, |
4 | char *data, size_t size, | 45 | char *data, size_t size, |
5 | struct timespec time, struct pstore_info *psi); | 46 | struct timespec time, struct pstore_info *psi); |
6 | extern int pstore_is_mounted(void); | 47 | extern int pstore_is_mounted(void); |
48 | |||
49 | #endif | ||
diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index ef5ca8a0255c..29996e8793a7 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c | |||
@@ -61,7 +61,7 @@ static DECLARE_WORK(pstore_work, pstore_dowork); | |||
61 | * calls to pstore_register() | 61 | * calls to pstore_register() |
62 | */ | 62 | */ |
63 | static DEFINE_SPINLOCK(pstore_lock); | 63 | static DEFINE_SPINLOCK(pstore_lock); |
64 | static struct pstore_info *psinfo; | 64 | struct pstore_info *psinfo; |
65 | 65 | ||
66 | static char *backend; | 66 | static char *backend; |
67 | 67 | ||
diff --git a/include/linux/pstore.h b/include/linux/pstore.h index b107484192fc..120443b0fda5 100644 --- a/include/linux/pstore.h +++ b/include/linux/pstore.h | |||
@@ -30,6 +30,7 @@ enum pstore_type_id { | |||
30 | PSTORE_TYPE_DMESG = 0, | 30 | PSTORE_TYPE_DMESG = 0, |
31 | PSTORE_TYPE_MCE = 1, | 31 | PSTORE_TYPE_MCE = 1, |
32 | PSTORE_TYPE_CONSOLE = 2, | 32 | PSTORE_TYPE_CONSOLE = 2, |
33 | PSTORE_TYPE_FTRACE = 3, | ||
33 | PSTORE_TYPE_UNKNOWN = 255 | 34 | PSTORE_TYPE_UNKNOWN = 255 |
34 | }; | 35 | }; |
35 | 36 | ||
@@ -57,6 +58,14 @@ struct pstore_info { | |||
57 | void *data; | 58 | void *data; |
58 | }; | 59 | }; |
59 | 60 | ||
61 | |||
62 | #ifdef CONFIG_PSTORE_FTRACE | ||
63 | extern void pstore_ftrace_call(unsigned long ip, unsigned long parent_ip); | ||
64 | #else | ||
65 | static inline void pstore_ftrace_call(unsigned long ip, unsigned long parent_ip) | ||
66 | { } | ||
67 | #endif | ||
68 | |||
60 | #ifdef CONFIG_PSTORE | 69 | #ifdef CONFIG_PSTORE |
61 | extern int pstore_register(struct pstore_info *); | 70 | extern int pstore_register(struct pstore_info *); |
62 | #else | 71 | #else |