| /* |
| * 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); |
| |