Commit 060287b8c467bf49a594d8d669e1986c6d8d76b0
Committed by
Greg Kroah-Hartman
1 parent
897dba0274
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
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>
Showing 7 changed files with 208 additions and 5 deletions Side-by-side Diff
fs/pstore/Kconfig
... | ... | @@ -19,6 +19,18 @@ |
19 | 19 | When the option is enabled, pstore will log all kernel |
20 | 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 | 34 | config PSTORE_RAM |
23 | 35 | tristate "Log panic/oops to a RAM buffer" |
24 | 36 | depends on PSTORE |
fs/pstore/Makefile
fs/pstore/ftrace.c
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 | +} |
fs/pstore/inode.c
... | ... | @@ -27,6 +27,7 @@ |
27 | 27 | #include <linux/list.h> |
28 | 28 | #include <linux/string.h> |
29 | 29 | #include <linux/mount.h> |
30 | +#include <linux/seq_file.h> | |
30 | 31 | #include <linux/ramfs.h> |
31 | 32 | #include <linux/parser.h> |
32 | 33 | #include <linux/sched.h> |
33 | 34 | |
34 | 35 | |
35 | 36 | |
36 | 37 | |
... | ... | @@ -52,18 +53,117 @@ |
52 | 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 | 122 | static ssize_t pstore_file_read(struct file *file, char __user *userbuf, |
56 | 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 | 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 | 162 | static const struct file_operations pstore_file_operations = { |
64 | - .open = simple_open, | |
65 | - .read = pstore_file_read, | |
66 | - .llseek = default_llseek, | |
163 | + .open = pstore_file_open, | |
164 | + .read = pstore_file_read, | |
165 | + .llseek = pstore_file_llseek, | |
166 | + .release = seq_release, | |
67 | 167 | }; |
68 | 168 | |
69 | 169 | /* |
... | ... | @@ -214,6 +314,9 @@ |
214 | 314 | break; |
215 | 315 | case PSTORE_TYPE_CONSOLE: |
216 | 316 | sprintf(name, "console-%s", psname); |
317 | + break; | |
318 | + case PSTORE_TYPE_FTRACE: | |
319 | + sprintf(name, "ftrace-%s", psname); | |
217 | 320 | break; |
218 | 321 | case PSTORE_TYPE_MCE: |
219 | 322 | sprintf(name, "mce-%s-%lld", psname, id); |
fs/pstore/internal.h
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 | 42 | extern void pstore_set_kmsg_bytes(int); |
2 | 43 | extern void pstore_get_records(int); |
3 | 44 | extern int pstore_mkfile(enum pstore_type_id, char *psname, u64 id, |
4 | 45 | char *data, size_t size, |
5 | 46 | struct timespec time, struct pstore_info *psi); |
6 | 47 | extern int pstore_is_mounted(void); |
48 | + | |
49 | +#endif |
fs/pstore/platform.c
include/linux/pstore.h
... | ... | @@ -30,6 +30,7 @@ |
30 | 30 | PSTORE_TYPE_DMESG = 0, |
31 | 31 | PSTORE_TYPE_MCE = 1, |
32 | 32 | PSTORE_TYPE_CONSOLE = 2, |
33 | + PSTORE_TYPE_FTRACE = 3, | |
33 | 34 | PSTORE_TYPE_UNKNOWN = 255 |
34 | 35 | }; |
35 | 36 | |
... | ... | @@ -56,6 +57,14 @@ |
56 | 57 | struct pstore_info *psi); |
57 | 58 | void *data; |
58 | 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 | |
59 | 68 | |
60 | 69 | #ifdef CONFIG_PSTORE |
61 | 70 | extern int pstore_register(struct pstore_info *); |