blob: 2f8806072f2998068893b69acf03b77aec432d7f [file] [log] [blame]
/*
* (C) Copyright 2011 Quantenna Communications Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/jiffies.h>
#include <asm/board/kdump.h>
#include <asm/cache.h>
#include <asm/cacheflush.h>
#include <linux/crc32.h>
#include <common/queue.h>
#define PROC_NAME "kdump"
extern unsigned long _text, _etext, __sram_text_start, __sram_text_end;
#if CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT > 0
#define KERNEL_TEXT_SNAPSHOT_DATA_BYTES (6 * 1024 * 1024)
/*
* kernel dram, kernel sram,
* one for each of dram and sram for each module
*/
#define KDUMP_MODULE_SNAPSHOT_SECTIONS 16
#define KDUMP_BASE_SNAPSHOT_SECTIONS 2 /* dram and sram */
#define KDUMP_MAX_SNAPSHOT_SECTIONS (KDUMP_MODULE_SNAPSHOT_SECTIONS + KDUMP_BASE_SNAPSHOT_SECTIONS)
static struct module *modules[KDUMP_MODULE_SNAPSHOT_SECTIONS];
struct kdump_snapshot_section {
const char *name;
unsigned long crc32;
unsigned long *text_store;
const unsigned long *start;
const unsigned long *end;
unsigned long bytes;
};
struct kdump_snapshot {
unsigned long jiff;
const char *description;
unsigned long _text[KERNEL_TEXT_SNAPSHOT_DATA_BYTES / sizeof(unsigned long)];
struct kdump_snapshot_section sections[KDUMP_MAX_SNAPSHOT_SECTIONS];
unsigned int section_index;
};
static struct kdump_snapshot snapshots[CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT];
static int snapshot_index = 0;
static unsigned long kdump_snapshot_section_crc(const struct kdump_snapshot_section *kss)
{
return crc32_le(0, (const uint8_t*)kss->text_store, kss->bytes);
}
static int kdump_verify_snapshot_section_crc(const struct kdump_snapshot_section *kss)
{
return kdump_snapshot_section_crc(kss) == kss->crc32;
}
static void kdump_take_snapshot_section(struct kdump_snapshot_section *kss)
{
const unsigned long *pkern = kss->start;
unsigned long *pdump = kss->text_store;
kss->bytes = 0;
while (pkern < kss->end) {
unsigned long d = arc_read_uncached_32(pkern);
*pdump = d;
kss->bytes += sizeof(d);
pkern++;
pdump++;
}
kss->crc32 = kdump_snapshot_section_crc(kss);
printk(KERN_WARNING "%s %s stored from 0x%p to 0x%p\n\t%lu bytes at 0x%p, crc 0x%lx\n",
__FUNCTION__, kss->name,
kss->start, kss->end,
kss->bytes, kss->text_store, kss->crc32);
}
static void kdump_take_base_snapshots(struct kdump_snapshot *snap)
{
const unsigned long * const dram_start = &_text;
const unsigned long * const dram_end = &_etext;
const unsigned long dram_size = (unsigned long)dram_end - (unsigned long)dram_start;
const unsigned long * const sram_start = &__sram_text_start;
const unsigned long * const sram_end = &__sram_text_end;
const unsigned long sram_size = (unsigned long)sram_end - (unsigned long)sram_start;
snap->sections[0].name = "dram";
snap->sections[0].text_store = &snap->_text[0];
snap->sections[0].start = dram_start;
snap->sections[0].end = dram_end;
kdump_take_snapshot_section(&snap->sections[0]);
snap->sections[1].name = "sram";
snap->sections[1].text_store = &snap->_text[dram_size / sizeof(snap->_text[0])];
snap->sections[1].start = sram_start;
snap->sections[1].end = sram_end;
kdump_take_snapshot_section(&snap->sections[1]);
snap->section_index = 2;
}
static void kdump_take_module_snapshot(struct kdump_snapshot *snap, struct module *mod)
{
int i;
int section_index;
int found = 0;
struct kdump_snapshot_section *sec;
struct kdump_snapshot_section *prevsec;
for (i = 0; found == 0 && i < KDUMP_MODULE_SNAPSHOT_SECTIONS; i++) {
if (modules[i] == mod) {
section_index = i;
found = 1;
}
}
for (i = 0; found == 0 && i < KDUMP_MODULE_SNAPSHOT_SECTIONS; i++) {
if (modules[i] == NULL) {
modules[i] = mod;
section_index = i;
found = 1;
}
}
if (!found) {
printk(KERN_ERR "%s: no module snapshot space left\n", __FUNCTION__);
return;
}
sec = &snap->sections[snap->section_index];
prevsec = &snap->sections[snap->section_index - 1];
sec->name = mod->name;
sec->text_store = ((uint8_t*)prevsec->text_store) + prevsec->bytes;
sec->start = mod->module_core;
sec->end = (unsigned long*)(((uint8_t*)sec->start) + mod->core_text_size);
snap->section_index++;
kdump_take_snapshot_section(sec);
if (mod->module_sram) {
sec = &snap->sections[snap->section_index];
prevsec = &snap->sections[snap->section_index - 1];
sec->name = mod->name;
sec->text_store = ((uint8_t*)prevsec->text_store) + prevsec->bytes;
sec->start = mod->module_sram;
sec->end = (unsigned long*)(((uint8_t*)sec->start) + mod->sram_text_size);
snap->section_index++;
kdump_take_snapshot_section(sec);
}
}
void kdump_do_take_snapshot(const char* description, int index)
{
struct kdump_snapshot *snap;
int i;
printk(KERN_WARNING "%s: taking snapshot '%s' index %d\n",
__FUNCTION__, description, index);
snap = &snapshots[index];
memset(snap, 0, sizeof(snap));
snap->jiff = jiffies;
snap->description = description;
kdump_take_base_snapshots(snap);
for (i = 0; i < KDUMP_MODULE_SNAPSHOT_SECTIONS; i++) {
if (modules[i]) {
kdump_take_module_snapshot(snap, modules[i]);
}
}
}
void kdump_add_module(struct module *mod)
{
// add to current snapshot
int current_snap_index = (snapshot_index + CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT - 1) % CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT;
flush_cache_all();
kdump_take_module_snapshot(&snapshots[current_snap_index], mod);
}
void kdump_remove_module(struct module *mod)
{
int i;
printk(KERN_ERR "%s not fully implemented, kernel text snapshots will break.\n", __FUNCTION__);
for (i = 0; i < KDUMP_MODULE_SNAPSHOT_SECTIONS; i++) {
if (modules[i] == mod) {
modules[i] = NULL;
}
}
}
int kdump_take_snapshot(const char *description)
{
int index = snapshot_index;
kdump_do_take_snapshot(description, snapshot_index);
snapshot_index = (snapshot_index + 1) % CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT;
return index;
}
EXPORT_SYMBOL(kdump_take_snapshot);
static void kdump_compare_snapshot_section(const char *context,
const struct kdump_snapshot_section *kss0,
const struct kdump_snapshot_section *kss1)
{
const unsigned long *d0 = kss0->text_store;
const unsigned long *d1 = kss1->text_store;
const unsigned long start = (unsigned long)kss0->start;
int i;
if (kss0->bytes != kss1->bytes) {
printk(KERN_WARNING "%s '%s': snapshot lengths differ\n",
__FUNCTION__, context);
} else if (!kdump_verify_snapshot_section_crc(kss0)) {
printk(KERN_WARNING "%s '%s': snapshot 0 crc is bad\n",
__FUNCTION__, context);
} else if (!kdump_verify_snapshot_section_crc(kss1)) {
printk(KERN_WARNING "%s '%s': snapshot 1 crc is bad\n",
__FUNCTION__, context);
} else if (kss0->crc32 == kss1->crc32) {
printk(KERN_WARNING "%s '%s': checksums match\n",
__FUNCTION__, context);
} else {
printk(KERN_WARNING "%s '%s': checksums differ...\n",
__FUNCTION__, context);
for (i = 0; i < kss0->bytes; i += sizeof(*d0)) {
if (*d0 != *d1) {
const unsigned long addr = start + i;
printk(KERN_WARNING "\tcorruption at 0x%lx: 0x%lx vs 0x%lx\n",
addr, *d0, *d1);
}
d0++;
d1++;
}
}
}
void kdump_compare_snapshots(int index0, int index1)
{
int i;
const struct kdump_snapshot *s0 = &snapshots[index0];
const struct kdump_snapshot *s1 = &snapshots[index1];
printk(KERN_WARNING "%s: comparing kernel text section snapshots:\n"
"\t'%s' vs '%s'...\n",
__FUNCTION__, s0->description, s1->description);
for (i = 0; i < KDUMP_MAX_SNAPSHOT_SECTIONS; i++) {
const struct kdump_snapshot_section *ss0 = &s0->sections[i];
const struct kdump_snapshot_section *ss1 = &s1->sections[i];
if (ss0->text_store && ss1->text_store && ss0->start == ss1->start) {
kdump_compare_snapshot_section(ss0->name, ss0, ss1);
}
}
}
void kdump_compare_all_snapshots(void)
{
int i;
int j;
for (i = 0; i < CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT - 1; i++) {
for (j = i + 1; j < CONFIG_KERNEL_TEXT_SNAPSHOT_COUNT; j++) {
kdump_compare_snapshots(i, j);
}
}
}
EXPORT_SYMBOL(kdump_compare_all_snapshots);
#endif
unsigned long kdump_calc_ktext_checksum(void)
{
const unsigned long * const start = &_text;
const unsigned long * const end = &_etext;
const unsigned long *p;
unsigned long hash = 0;
for (p = start; p < end; p++) {
hash = hash * 17 + *p;
}
return hash;
}
void kdump_print_ktext_checksum(void)
{
const unsigned long * const start = &_text;
const unsigned long * const end = &_etext;
printk(KERN_WARNING "Kernel text section: %p to %p checksum %lu\n",
start, end, kdump_calc_ktext_checksum());
}
EXPORT_SYMBOL(kdump_print_ktext_checksum);
#define KDUMP_WORDS_ROUNDUP_S 2
#define KDUMP_WORDS_ROUNDUP_M ((1 << KDUMP_WORDS_ROUNDUP_S) - 1)
static __inline__ __pure int kdump_dram_words(void)
{
const unsigned long dram_start = (unsigned long)&_text;
const unsigned long dram_end = (unsigned long)&_etext;
return (dram_end - dram_start) / sizeof(unsigned long);
}
static __inline__ __pure int kdump_sram_words(void)
{
const unsigned long sram_start = (unsigned long)&__sram_text_start;
const unsigned long sram_end = (unsigned long)&__sram_text_end;
return (sram_end - sram_start) / sizeof(unsigned long);
}
static __inline__ __pure int kdump_done(loff_t pos)
{
int words = kdump_dram_words() + kdump_sram_words();
return pos >= words;
}
static __inline__ __pure int kdump_section_offset(loff_t pos)
{
int dram_words = kdump_dram_words();
if (pos >= dram_words) {
return pos - dram_words;
}
return pos;
}
static __inline__ __pure int kdump_section_lastword(loff_t pos)
{
return pos + 1 == kdump_dram_words() || kdump_done(pos + 1);
}
static unsigned long* __pure kdump_word_from_index(loff_t pos)
{
unsigned long *p;
if (pos < kdump_dram_words()) {
p = &_text;
return &p[pos];
} else {
p = &__sram_text_start;
return &p[pos - kdump_dram_words()];
}
}
static void* kdump_seq_start(struct seq_file *sfile, loff_t *pos)
{
loff_t *ppos = kmalloc(sizeof(*ppos), GFP_KERNEL);
if (ppos == NULL || kdump_done(*pos + 1)) {
return NULL;
}
*ppos = *pos;
return ppos;
}
static void* kdump_seq_next(struct seq_file *sfile, void *v, loff_t *pos)
{
loff_t *ppos = v;
if (ppos == NULL || kdump_done(*pos + 1)) {
return NULL;
}
*pos = *pos + 1;
*ppos = *pos;
return ppos;
}
static void kdump_seq_stop(struct seq_file *sfile, void *v)
{
loff_t *ppos = v;
if (ppos) {
kfree(ppos);
}
}
static int kdump_seq_show(struct seq_file *sfile, void *v)
{
static const int values_per_line = 4;
loff_t *ppos = v;
int section_idx = kdump_section_offset(*ppos);
unsigned long *word = kdump_word_from_index(*ppos);
int print_addr = section_idx % values_per_line == 0;
int print_newline = (section_idx + 1) % values_per_line == 0 ||
kdump_section_lastword(*ppos);
if (print_addr) {
seq_printf(sfile, "%p:", word);
}
seq_printf(sfile, " %lx", *word);
if (print_newline) {
seq_printf(sfile, "\n");
}
return 0;
}
static struct seq_operations kdump_seq_ops = {
.start = kdump_seq_start,
.next = kdump_seq_next,
.stop = kdump_seq_stop,
.show = kdump_seq_show
};
static int kdump_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &kdump_seq_ops);
}
static struct file_operations kdump_proc_ops = {
.owner = THIS_MODULE,
.open = kdump_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static int __init kdump_init(void)
{
printk(KERN_INFO "loading %s ...\n", PROC_NAME);
kdump_take_snapshot("kdump module load");
struct proc_dir_entry *kdump_entry = create_proc_entry(PROC_NAME, 0, NULL);
if (!kdump_entry) {
return -ENOENT;
}
kdump_entry->proc_fops = &kdump_proc_ops;
return 0;
}
static void __exit kdump_exit(void)
{
printk(KERN_INFO "unloading %s ...\n", PROC_NAME);
remove_proc_entry(PROC_NAME, NULL);
}
module_init(kdump_init);
module_exit(kdump_exit);
MODULE_LICENSE("GPL");