mirror of
https://github.com/torvalds/linux
synced 2024-11-05 18:23:50 +00:00
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>
This commit is contained in:
parent
897dba0274
commit
060287b8c4
7 changed files with 208 additions and 5 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
35
fs/pstore/ftrace.c
Normal file
35
fs/pstore/ftrace.c
Normal file
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue