pstore: Add persistent function tracing
authorAnton Vorontsov <anton.vorontsov@linaro.org>
Tue, 10 Jul 2012 00:10:41 +0000 (17:10 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 17 Jul 2012 17:05:52 +0000 (10:05 -0700)
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>
fs/pstore/Kconfig
fs/pstore/Makefile
fs/pstore/ftrace.c [new file with mode: 0644]
fs/pstore/inode.c
fs/pstore/internal.h
fs/pstore/platform.c
include/linux/pstore.h

index d044de6ee3080aa14268d8610effbd2338d9a764..d39bb5cce88389809cf00a9010bb00bacb255e58 100644 (file)
@@ -19,6 +19,18 @@ config PSTORE_CONSOLE
          When the option is enabled, pstore will log all kernel
          messages, even if no oops or panic happened.
 
+config PSTORE_FTRACE
+       bool "Persistent function tracer"
+       depends on PSTORE
+       depends on FUNCTION_TRACER
+       help
+         With this option kernel traces function calls 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.
+
+         If unsure, say N.
+
 config PSTORE_RAM
        tristate "Log panic/oops to a RAM buffer"
        depends on PSTORE
index 278a44e0d4e176716ebee280c0fc19dfb5042823..4c9095c2781e17c2d944081f20f9e2c639a9a6b8 100644 (file)
@@ -5,6 +5,7 @@
 obj-y += pstore.o
 
 pstore-objs += inode.o platform.o
+obj-$(CONFIG_PSTORE_FTRACE)    += ftrace.o
 
 ramoops-objs += ram.o ram_core.o
 obj-$(CONFIG_PSTORE_RAM)       += ramoops.o
diff --git a/fs/pstore/ftrace.c b/fs/pstore/ftrace.c
new file mode 100644 (file)
index 0000000..a130d48
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright 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/kernel.h>
+#include <linux/compiler.h>
+#include <linux/irqflags.h>
+#include <linux/percpu.h>
+#include <linux/smp.h>
+#include <linux/atomic.h>
+#include <asm/barrier.h>
+#include "internal.h"
+
+void notrace pstore_ftrace_call(unsigned long ip, unsigned long parent_ip)
+{
+       struct pstore_ftrace_record rec = {};
+
+       if (unlikely(oops_in_progress))
+               return;
+
+       rec.ip = ip;
+       rec.parent_ip = parent_ip;
+       pstore_ftrace_encode_cpu(&rec, raw_smp_processor_id());
+       psinfo->write_buf(PSTORE_TYPE_FTRACE, 0, NULL, 0, (void *)&rec,
+                         sizeof(rec), psinfo);
+}
index 45bff5441b042a646bb22ef03500321976f6f7b1..4ab572e6d27782ad40efcdaff5e279b5e4c15daa 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/list.h>
 #include <linux/string.h>
 #include <linux/mount.h>
+#include <linux/seq_file.h>
 #include <linux/ramfs.h>
 #include <linux/parser.h>
 #include <linux/sched.h>
@@ -52,18 +53,117 @@ struct pstore_private {
        char    data[];
 };
 
+struct pstore_ftrace_seq_data {
+       const void *ptr;
+       size_t off;
+       size_t size;
+};
+
+#define REC_SIZE sizeof(struct pstore_ftrace_record)
+
+static void *pstore_ftrace_seq_start(struct seq_file *s, loff_t *pos)
+{
+       struct pstore_private *ps = s->private;
+       struct pstore_ftrace_seq_data *data;
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return NULL;
+
+       data->off = ps->size % REC_SIZE;
+       data->off += *pos * REC_SIZE;
+       if (data->off + REC_SIZE > ps->size) {
+               kfree(data);
+               return NULL;
+       }
+
+       return data;
+
+}
+
+static void pstore_ftrace_seq_stop(struct seq_file *s, void *v)
+{
+       kfree(v);
+}
+
+static void *pstore_ftrace_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+       struct pstore_private *ps = s->private;
+       struct pstore_ftrace_seq_data *data = v;
+
+       data->off += REC_SIZE;
+       if (data->off + REC_SIZE > ps->size)
+               return NULL;
+
+       (*pos)++;
+       return data;
+}
+
+static int pstore_ftrace_seq_show(struct seq_file *s, void *v)
+{
+       struct pstore_private *ps = s->private;
+       struct pstore_ftrace_seq_data *data = v;
+       struct pstore_ftrace_record *rec = (void *)(ps->data + data->off);
+
+       seq_printf(s, "%d %08lx  %08lx  %pf <- %pF\n",
+               pstore_ftrace_decode_cpu(rec), rec->ip, rec->parent_ip,
+               (void *)rec->ip, (void *)rec->parent_ip);
+
+       return 0;
+}
+
+static const struct seq_operations pstore_ftrace_seq_ops = {
+       .start  = pstore_ftrace_seq_start,
+       .next   = pstore_ftrace_seq_next,
+       .stop   = pstore_ftrace_seq_stop,
+       .show   = pstore_ftrace_seq_show,
+};
+
 static ssize_t pstore_file_read(struct file *file, char __user *userbuf,
                                                size_t count, loff_t *ppos)
 {
-       struct pstore_private *ps = file->private_data;
+       struct seq_file *sf = file->private_data;
+       struct pstore_private *ps = sf->private;
 
+       if (ps->type == PSTORE_TYPE_FTRACE)
+               return seq_read(file, userbuf, count, ppos);
        return simple_read_from_buffer(userbuf, count, ppos, ps->data, ps->size);
 }
 
+static int pstore_file_open(struct inode *inode, struct file *file)
+{
+       struct pstore_private *ps = inode->i_private;
+       struct seq_file *sf;
+       int err;
+       const struct seq_operations *sops = NULL;
+
+       if (ps->type == PSTORE_TYPE_FTRACE)
+               sops = &pstore_ftrace_seq_ops;
+
+       err = seq_open(file, sops);
+       if (err < 0)
+               return err;
+
+       sf = file->private_data;
+       sf->private = ps;
+
+       return 0;
+}
+
+static loff_t pstore_file_llseek(struct file *file, loff_t off, int origin)
+{
+       struct seq_file *sf = file->private_data;
+
+       if (sf->op)
+               return seq_lseek(file, off, origin);
+       return default_llseek(file, off, origin);
+}
+
 static const struct file_operations pstore_file_operations = {
-       .open   = simple_open,
-       .read   = pstore_file_read,
-       .llseek = default_llseek,
+       .open           = pstore_file_open,
+       .read           = pstore_file_read,
+       .llseek         = pstore_file_llseek,
+       .release        = seq_release,
 };
 
 /*
@@ -215,6 +315,9 @@ int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id,
        case PSTORE_TYPE_CONSOLE:
                sprintf(name, "console-%s", psname);
                break;
+       case PSTORE_TYPE_FTRACE:
+               sprintf(name, "ftrace-%s", psname);
+               break;
        case PSTORE_TYPE_MCE:
                sprintf(name, "mce-%s-%lld", psname, id);
                break;
index 3bde461c3f349cfe1932d94a5f3b424e9184bebc..958c48d8905c4f4c6ce7cc7c45545b53eff13e2e 100644 (file)
@@ -1,6 +1,49 @@
+#ifndef __PSTORE_INTERNAL_H__
+#define __PSTORE_INTERNAL_H__
+
+#include <linux/pstore.h>
+
+#if NR_CPUS <= 2 && defined(CONFIG_ARM_THUMB)
+#define PSTORE_CPU_IN_IP 0x1
+#elif NR_CPUS <= 4 && defined(CONFIG_ARM)
+#define PSTORE_CPU_IN_IP 0x3
+#endif
+
+struct pstore_ftrace_record {
+       unsigned long ip;
+       unsigned long parent_ip;
+#ifndef PSTORE_CPU_IN_IP
+       unsigned int cpu;
+#endif
+};
+
+static inline void
+pstore_ftrace_encode_cpu(struct pstore_ftrace_record *rec, unsigned int cpu)
+{
+#ifndef PSTORE_CPU_IN_IP
+       rec->cpu = cpu;
+#else
+       rec->ip |= cpu;
+#endif
+}
+
+static inline unsigned int
+pstore_ftrace_decode_cpu(struct pstore_ftrace_record *rec)
+{
+#ifndef PSTORE_CPU_IN_IP
+       return rec->cpu;
+#else
+       return rec->ip & PSTORE_CPU_IN_IP;
+#endif
+}
+
+extern struct pstore_info *psinfo;
+
 extern void    pstore_set_kmsg_bytes(int);
 extern void    pstore_get_records(int);
 extern int     pstore_mkfile(enum pstore_type_id, char *psname, u64 id,
                              char *data, size_t size,
                              struct timespec time, struct pstore_info *psi);
 extern int     pstore_is_mounted(void);
+
+#endif
index ef5ca8a0255c8926d8f65fed389a73fae2862327..29996e8793a7753139bdb7105bc00994bb411ba0 100644 (file)
@@ -61,7 +61,7 @@ static DECLARE_WORK(pstore_work, pstore_dowork);
  * calls to pstore_register()
  */
 static DEFINE_SPINLOCK(pstore_lock);
-static struct pstore_info *psinfo;
+struct pstore_info *psinfo;
 
 static char *backend;
 
index b107484192fc1263348375ca21898f92cd6778a2..120443b0fda51dc10444fe90ef59457952d2b403 100644 (file)
@@ -30,6 +30,7 @@ enum pstore_type_id {
        PSTORE_TYPE_DMESG       = 0,
        PSTORE_TYPE_MCE         = 1,
        PSTORE_TYPE_CONSOLE     = 2,
+       PSTORE_TYPE_FTRACE      = 3,
        PSTORE_TYPE_UNKNOWN     = 255
 };
 
@@ -57,6 +58,14 @@ struct pstore_info {
        void            *data;
 };
 
+
+#ifdef CONFIG_PSTORE_FTRACE
+extern void pstore_ftrace_call(unsigned long ip, unsigned long parent_ip);
+#else
+static inline void pstore_ftrace_call(unsigned long ip, unsigned long parent_ip)
+{ }
+#endif
+
 #ifdef CONFIG_PSTORE
 extern int pstore_register(struct pstore_info *);
 #else