ASR_BASE
Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/arch/arm/mach-mmp/ramdump_util.c b/marvell/linux/arch/arm/mach-mmp/ramdump_util.c
new file mode 100644
index 0000000..deb6138
--- /dev/null
+++ b/marvell/linux/arch/arm/mach-mmp/ramdump_util.c
@@ -0,0 +1,571 @@
+/*
+ * 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);
+