blob: deb613883e127a9b1daa083bd9c03f202dca4482 [file] [log] [blame]
/*
* Copyright: (C) Copyright 2015 Marvell International Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* publishhed by the Free Software Foundation.
*
* Author: Yan Markman (ymarkman@marvell.com)
*
* Utilities for ramdump.c debug capability extension:
* - Kernel-RO CRC16 check and reporting on panic
* - Control /dev/ramdump_ctl for ramdump enable/disable, panic, crc check...
*/
#define _RAMDUMP_UTIL_C
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/kthread.h>
#include <linux/crc16.h>
#include <asm/uaccess.h>
#include <asm-generic/sections.h>
#include <soc/asr/addr-map.h>
#include <linux/io.h>
#include <linux/sched/clock.h>
#include <soc/asr/ramdump.h>
#include <soc/asr/ramdump_util.h>
#include <soc/asr/ramdump_miscdevice.h>
#include <soc/asr/regs-timers.h>
#ifdef CONFIG_PXA_MIPSRAM
#include <linux/mipsram.h>
#endif
/* need to make sure all kernel modules are loaded before
* calculating the kernel CRC16 value, as kernel modules often
* invokes static_key APIs and dirties the kernel code area
*/
#define RDP_CRC16_CALC_SLP_SEC (64)
extern int k_signal_panic_guid_set(int set_uid, int *sig_guid_array, int size);
static int sysdbg_cdev_create_register(const char *dev_name,
const struct file_operations *fops);
#ifndef CONFIG_CRC16
int get_kernel_text_crc16_valid(void) { return 1; }
u16 get_kernel_text_crc16_on_panic(void) {}
int get_kernel_text_crc16_threaded_req(void) { return 0; }
#else
/* RAM consistency checker (also usable as Kernel-ID)
* applied over ReadOnly area - Kernel TEXT
* Called once on startup for "good reference" and once on panic
* "On panic" calculation has latency ~320mS
* "startup" called by late_initcall() and done over special "kcrc" kthread
* The CPU is 100% loaded, so delay calculation for 2sec and do it by
* chunks 32kB. Exit thread upon finish.
*/
static char *crc_txt = "KERNEL-TEXT-CRC:";
static u16 kernel_text_crc16_buf[2];
int get_kernel_text_crc16_valid(void)
{
if (!kernel_text_crc16_buf[1])
return -2; /* not accounted */
if (kernel_text_crc16_buf[0] &&
(kernel_text_crc16_buf[0] != kernel_text_crc16_buf[1]))
return -1;
return 0;
}
/* "one-shot" blocking with very long latency */
u16 get_kernel_text_crc16_on_panic(void)
{
u16 crc;
/* first parameter is - previous CRC value */
crc = crc16(0, (u8*)&_text, (size_t)&_etext - (size_t)&_text);
kernel_text_crc16_buf[1] = crc;
pr_err("%s orig/panic = 0x%x/0x%x\n", crc_txt,
kernel_text_crc16_buf[0], kernel_text_crc16_buf[1]);
return crc;
}
/* short latency pices with sleep - to be called by a thread only */
static int get_kernel_text_crc16_sleep(void *data)
{
const int chunk = 0x8000;
u16 crc;
int len, size;
u8 *p;
int initcall_run = ((int)data == 1);
if (initcall_run) {
/* Kernel-RO is modified by NET-pack
* Delay crc-account for 10sec
*/
ssleep(RDP_CRC16_CALC_SLP_SEC);
}
if (!initcall_run && !kernel_text_crc16_buf[0]) {
pr_debug("%s kcrc_init not finished\n", crc_txt);
return 0;
}
crc = 0;
p = (u8*)&_text;
size = (size_t)&_etext - (size_t)&_text;
do {
len = (size > chunk) ? chunk : size;
crc = crc16(crc, p, len);
p += len;
size -= len;
msleep(8);
} while (size);
if (initcall_run) {
/* Run on init */
kernel_text_crc16_buf[0] = crc;
pr_info("%s 0x%x\n", crc_txt, crc);
} else {
/* Run from proc command */
kernel_text_crc16_buf[1] = crc;
pr_err("%s orig/run = 0x%x/0x%x\n", crc_txt,
kernel_text_crc16_buf[0], kernel_text_crc16_buf[1]);
}
return 0;
}
int get_kernel_text_crc16_threaded_req(void)
{
pr_info("Kernel-RO CRC checking started. Takes about 4sec...\n");
kthread_run(get_kernel_text_crc16_sleep, NULL, "kcrc");
return 0;
}
static int kernel_text_crc16_init(void)
{
kthread_run(get_kernel_text_crc16_sleep, (void*)1, "kcrc_init");
return 0;
}
late_initcall(kernel_text_crc16_init);
#endif/*CONFIG_CRC16*/
/*#define KERNEL_BAD_CRC_DEBUG*/
#ifdef KERNEL_BAD_CRC_DEBUG
/* If Kernel CRC is wrong need to debug this,
* but KERNEL RO is not saved in RAMDUMP.
* Let's force-copy the Kernel into predefined address
* and panic at once
*/
static int get_kernel_text_copy2ddr(void)
{
unsigned long flags;
unsigned *src = (unsigned*)&_text;
unsigned *dst = (unsigned*)0xC2000000;
unsigned size = (unsigned)&_etext - (unsigned)&_text;
pr_err("Kernel size_0x%x copy from 0x%x to 0x%x\n",
size, (unsigned)src, (unsigned)dst);
size /= sizeof(int);
local_irq_save(flags);
while (size--)
*dst++ = *src++;
panic("Kernel-Copy");
return 0;
}
#endif
#if !defined(CONFIG_CPU_ASR18XX) && !defined(CONFIG_CPU_ASR1901)
void ramdump_clock_calibration(int full_calib) {}
#else
#define RD_LOOPS 100000
#define RD_LOOPS_CCNT (RD_LOOPS * 6 + 33)
#define RD_IRQ_CCNT 136 /* local_irq_save + restore */
void ramdump_clock_calibration(int full_calib)
{
unsigned long flags;
volatile unsigned cntr;
u64 ts_nsec0, ts_nsec1;
register unsigned int ccnt0, ccnt1;
if (full_calib)
pr_info("\n");
do {
/* Measure */
cntr = RD_LOOPS;
local_irq_save(flags);
ts_nsec0 = local_clock();
__asm__ __volatile__("mrc p15, 0, %0, c9, c13, 0" : "=r" (ccnt0));
if (full_calib) {
while(cntr--) /**/; /*no IRQs*/
} else {
local_irq_restore(flags);
while(cntr--) /**/;
local_irq_save(flags);
}
__asm__ __volatile__("mrc p15, 0, %0, c9, c13, 0" : "=r" (ccnt1));
ts_nsec1 = local_clock();
local_irq_restore(flags);
ts_nsec1 -= ts_nsec0;
ccnt1 -= ccnt0;
pr_info("Clocks %10u in %10u nsec = %dMHz\n",
ccnt1, (u32)ts_nsec1, (ccnt1 * 10)/((u32)ts_nsec1/100));
} while (full_calib--);
/* Known MIN job on 100.000 loops is 600.033 ccnt */
pr_info("CPI IDLE = %d%c\n",
((RD_LOOPS_CCNT * 1000) / (ccnt1 - RD_IRQ_CCNT)) / 10, '%');
}
#endif/*CONFIG_CPU_ASR18XX*/
/* We have 2 kinds of counters 32kHz used for timestamps:
* - In timer hw-module (one or more, free-running or interrupt)
* - Common 32kHz free running counter for AP/CP/MSA processors
* Kernel, MIPSRAMs, diag could use different.
* For offline analysis need to know delta.
* The clocks have different PLL-source, so delta may have drift
*/
struct rdp_ts_desc {
unsigned int cntr_tmr;
unsigned int cntr_cmn;
void __iomem *va_tmr;
void __iomem *va_cmn;
unsigned int pa_tmr;
unsigned int pa_cmn;
};
#define PMUTMR_CR(n) (0x28 + (n << 2))
static struct rdp_ts_desc ramdump_timestamps = {
.pa_tmr = APB_PHYS_BASE + 0x14000 + TMR_CR(1), /*TIMER_0_1 CR*/
#ifndef CONFIG_CPU_ASR18XX
.pa_cmn = APB_PHYS_BASE + 0x16000 + TMR_CR(0), /*TIMER_1_0 CR*/
#elif defined(CONFIG_CPU_ASR1903)
.pa_cmn = APB_PHYS_BASE + 0x16000 + PMUTMR_CR(1), /*PMUTIMER_1 CR*/
#else
.pa_cmn = APB_PHYS_BASE + 0x3A000 + TMR_CR(0), /*CP TIMER_1_0 CR*/
#endif
};
static void rdp_ts_init(void)
{
struct rdp_ts_desc *p = &ramdump_timestamps;
p->va_tmr = ioremap(p->pa_tmr, 0x4);
p->va_cmn = ioremap(p->pa_cmn, 0x4);
p->cntr_tmr = (p->va_tmr) ? __raw_readl(p->va_tmr) : 0;
p->cntr_cmn = (p->va_cmn) ? __raw_readl(p->va_cmn) : 0;
pr_info("TIMESTAMP 32kHz: timer-common = %u = 0x%08x - 0x%08x\n",
p->cntr_tmr - p->cntr_cmn, p->cntr_tmr, p->cntr_cmn);
}
static void rdp_ts_show(void)
{
struct rdp_ts_desc *p = &ramdump_timestamps;
unsigned int tmr, cmn;
tmr = (p->va_tmr) ? __raw_readl(p->va_tmr) : 0;
cmn = (p->va_cmn) ? __raw_readl(p->va_cmn) : 0;
pr_info("TIMESTAMP 32kHz: timer-common = %u = 0x%08x - 0x%08x\n",
tmr - cmn, tmr, cmn);
}
#ifdef CONFIG_INPUT_88PM80X_ONKEY
extern int (*pm80x_onkey_cb_ret0_to_continue)(unsigned int param);
/* Helpfull for immediate panic and PM wake event debug */
#ifdef CONFIG_PXA_MIPSRAM
#define MIPS_RAM_ONKEY_TRACE() MIPS_RAM_ADD_TRACE(0x10101010) /* IO IO */
#else
#define MIPS_RAM_ONKEY_TRACE
#endif
static int ramdump_onkey_empty_evnt(unsigned int val)
{
#ifdef CONFIG_PXA_MIPSRAM
MIPS_RAM_ONKEY_TRACE();
#endif
rdp_ts_show();
return -1; /* do nothing but empty event only */
}
static int ramdump_onkey_panic_cntr;
static int ramdump_onkey_panic(unsigned int val)
{
#ifdef CONFIG_PXA_MIPSRAM
MIPS_RAM_ONKEY_TRACE();
#endif
rdp_ts_show();
BUG_ON(!ramdump_onkey_panic_cntr--);
return -1;
}
#endif/*CONFIG_INPUT_88PM80X_ONKEY*/
void ramdump_ignore_fatal_signals(int on_shutdown)
{
/* This procedure is used on shutdown and is a "shortcut" alternative
* to the "conventional" /proc/sys/kernel/print-fatal-signals
* which is too long for shutdown
*/
extern int print_fatal_signals;
if (ramdump_level <= RAMDUMP_LEVEL_PANIC_ONLY) {
if (on_shutdown)
print_fatal_signals = 0x10;
else
print_fatal_signals = 2;
}
}
/*** "ramdump_ctl" - RAMDUMP Enable/Disable control device ********/
static int ramdump_ctl_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
int len;
char kbuf[24];
if (*f_pos)
return 0; /* second entry of same command */
/* Could be called as "cat /dev" or as read(1byte) from SW-code
* The output is different for these cases:
* - read(1byte) expects Row-Binary 1 byte
* (if ramdump_level<2 the User-Space-reader ignores error)
* - shell/system() read has count=4kB and obtains string
*/
if (count == 1) {
len = count;
kbuf[0] = ramdump_level & 0xff;
} else {
len = sprintf(kbuf, "\t ramdump_enable=%d\n", ramdump_level);
}
*f_pos = len;
if (copy_to_user(buf, kbuf, len))
return -EFAULT;
return len;
}
static const char *tstPanic = "Force-Emulate Kernel panic";
static int ramdump_ctl_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
char in;
char desc_buf[80];
int copy_done, copy_left, i;
if ((count <= 0) || *f_pos)
return 0;
if (copy_from_user(&in, buf, 1))
return -EFAULT;
if (in == 'd') {
ramdump_level = 0;
ramdump_ignore_fatal_signals(0);
} else if (in == 'e') {
ramdump_level = RAMDUMP_LEVEL_FULL;
} else if (in == 'a') {
ramdump_level = RAMDUMP_LEVEL_FULL_IN_ADVANCE;
ramdump_prepare_in_advance();
} else if (in == 'p') {
ramdump_level = 0xf;
desc_buf[0] = 0;
if (!copy_from_user(&in, &buf[1], 1)) {
if (in == '_') {
copy_left = copy_from_user(desc_buf, &buf[2], 79);
copy_done = 79 - copy_left;
desc_buf[copy_done] = 0;
/* strip LF */
i = 0;
while (i < copy_done) {
if (desc_buf[i] == '\n')
desc_buf[i] = 0;
if (!desc_buf[i++])
break;
}
}
}
if (desc_buf[0]) {
panic("%s \'%s\'\n", tstPanic, desc_buf);
} else {
panic("%s\n", tstPanic);
}
} else if (in == 'B') {
ramdump_level = 0xB;
pr_err("\n %s (BUG)\n", tstPanic);
BUG();
} else if (in == 'c') {
/* CRC blocking account with long latency! */
get_kernel_text_crc16_on_panic();
i = get_kernel_text_crc16_valid();
if (i == -1) {
pr_err("!Bad Kernel CRC!\n");
return -ERANGE;
} else if (i == -2) {
pr_err("CRC not accounted\n");
return -EAGAIN;
}
#ifdef KERNEL_BAD_CRC_DEBUG
} else if (in == 'C') {
get_kernel_text_copy2ddr();
#endif
/* Frequency/Clock/CPU-IDLE */
} else if (in == 'i') {
ramdump_clock_calibration(0);
} else if (in == 'f') {
ramdump_clock_calibration(1);
} else if (in == 'j') {
pr_info("jiffies = %lu\n", jiffies);
#ifdef CONFIG_INPUT_88PM80X_ONKEY
} else if (in == 'E') {
pr_info("OnKey DEBUG = EmptyEvent\n");
pm80x_onkey_cb_ret0_to_continue = ramdump_onkey_empty_evnt;
} else if (in == 'F') {
pr_info("OnKey DEBUG = Panic on First\n");
ramdump_onkey_panic_cntr = 0;
pm80x_onkey_cb_ret0_to_continue = ramdump_onkey_panic;
} else if (in == 'S') {
pr_info("OnKey DEBUG = Panic on Second\n");
ramdump_onkey_panic_cntr = 1;
pm80x_onkey_cb_ret0_to_continue = ramdump_onkey_panic;
#endif
} else if ((in == 'g') || (in == 'u')) {
#define K_SIG_GUIDS_MAX 7 /*in signal.c, including 0=root */
int num, guid[K_SIG_GUIDS_MAX+1];
copy_left = copy_from_user(desc_buf, &buf[1], 79);
copy_done = 79 - copy_left;
desc_buf[79] = 0;
/* Set all entries to "-1", plus 1 extra to check input NUM */
memset(guid, 0xFF, sizeof(guid));
num = sscanf(desc_buf, "id=%d,%d,%d,%d,%d,%d,%d,%d",
&guid[0], &guid[1], &guid[2], &guid[3],
&guid[4], &guid[5], &guid[6], &guid[7]);
if ((num <= 0) || (num > K_SIG_GUIDS_MAX))
return -EIO;
if (k_signal_panic_guid_set((in == 'u'), guid, num) != num)
return -EIO;
} else if ((in >= '0') && (in <= '9')) {
ramdump_level = (unsigned)(in - '0');
ramdump_ignore_fatal_signals(0);/*level-check is inside*/
} else {
pr_info("ramdump_ctl: options\n"
" 0/1=e/d, p[anic], B[UG], a[dvance]\n"
" gid=1,2,3,4 or uid=1,2,3,4 set guid for panic on fatal-signals\n"
" j[iffies], i[IDLE], f[frequency]\n"
" c[crc check]\n"
#ifdef CONFIG_INPUT_88PM80X_ONKEY
" OnKey: E[EmptyEvnt] F[FirstPanic] S[SecondPanic]\n"
#endif
"\n");
return -EINVAL;
}
*f_pos = count;
return count;
}
static const struct file_operations ramdump_ctl_fops = {
.owner = THIS_MODULE,
.read = ramdump_ctl_read,
.write = ramdump_ctl_write,
};
static int __init ramdump_ctl_init(void)
{
rdp_ts_init();
/* Create Register sysdbg-node (not a MISC) device */
return
sysdbg_cdev_create_register("ramdump_ctl", &ramdump_ctl_fops);
}
late_initcall(ramdump_ctl_init);
/****************************************************************************
* RAMDUMP Devices are for System Debug ErrorHandling and Health Management
* Working MISC-Devices may be stack whilst working but RAMDUMP-devices
* must be always ok to guaranty system recovery and logging.
*
* Do not use MISC-Device for RAMDUMPs but use own
* Major-Node-Number for them. The devices are still "char"
*/
static int sysdbg_major;
static int sysdbg_minor = -1;
static char *sysdbg_class_name = (char*)"sysdbg";
static struct class *sysdbg_class;
static int sysdbg_cdev_create_register(const char *dev_name,
const struct file_operations *fops)
{
struct cdev *cdev;
int err = 0;
dev_t dev = 0;
struct device *pdev;
if (!sysdbg_class)
sysdbg_class = class_create(THIS_MODULE, sysdbg_class_name);
sysdbg_minor++;
if (sysdbg_major) {
dev = MKDEV(sysdbg_major, sysdbg_minor);
err = register_chrdev_region(dev, 1, sysdbg_class_name);
} else {
err = alloc_chrdev_region(&dev, sysdbg_minor, 1, sysdbg_class_name);
sysdbg_major = MAJOR(dev);
}
if (err < 0) {
pr_err("%s:%s can't get major %d\n", sysdbg_class_name, dev_name,
sysdbg_major);
goto fail_1;
}
cdev = cdev_alloc();
if (!cdev) {
err = -ENOMEM;
goto fail_2;
}
dev = MKDEV(sysdbg_major, sysdbg_minor);
cdev->ops = fops;
cdev->owner = THIS_MODULE;
err = cdev_add(cdev, dev, 1);
if (!err) {
pdev = device_create(sysdbg_class, NULL, dev, NULL, dev_name);
if (pdev)
return 0;
}
/* ----- FAIL ------------------- */
/*device_destroy(sysdbg_class, devno) -- nothing to destroy*/
cdev_del(cdev);
fail_2:
unregister_chrdev_region(dev, 1);
pr_err("%s:%s cdev err %d\n", sysdbg_class_name, dev_name, err);
fail_1:
sysdbg_minor--;
/*class_destroy(sysdbg_class) - never destroy this class*/
return err;
}
/* GLOBAL alternator/wrappers */
int sysdbg_misc_register(struct miscdevice * misc)
{
return sysdbg_cdev_create_register(misc->name, misc->fops);
}
int sysdbg_misc_deregister(struct miscdevice * misc)
{
pr_err("Class %s device %s shuld never be deleted\n",
sysdbg_class_name, misc->name);
return 0;
}
static int (*ramdump_seh_callback)(char *buf, int len);
void ramdump_seh_callback_bind(void *send_msg_callback)
{
ramdump_seh_callback = send_msg_callback;
}
EXPORT_SYMBOL(ramdump_seh_callback_bind);
int ramdump_send_msg_to_seh (char *buf, int len)
{
if (!ramdump_seh_callback)
return -1;
return /* num-sent bytes > 0 are valid */
ramdump_seh_callback(buf, len);
}
EXPORT_SYMBOL(ramdump_send_msg_to_seh);