| /* |
| * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com) |
| * |
| * 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 |
| */ |
| |
| #include <linux/ptrace.h> |
| #include <linux/module.h> |
| #include <linux/mm.h> |
| #include <linux/fs.h> |
| #include <linux/kdev_t.h> |
| #include <linux/fs_struct.h> |
| #include <linux/proc_fs.h> |
| #include <linux/file.h> |
| #include <asm/arcregs.h> |
| #include <asm/irqflags.h> |
| #include <asm/board/troubleshoot.h> |
| #include <asm/cacheflush.h> |
| #include <linux/kmsg_dump.h> |
| #include <linux/vmalloc.h> |
| #include <linux/zlib.h> |
| #include <linux/slab.h> |
| |
| /* 3.8 times seems safe for LHost, MuC and/or AuC crash - found by trial and error */ |
| #define CORE_DUMP_COMPRESS_RATIO (380) |
| |
| static void kmsg_dumper_core_dump(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason); |
| |
| static unsigned long sram_safe_start; |
| static unsigned long sram_safe_size; |
| |
| static arc_troubleshoot_start_hook_cbk troubleshoot_start; |
| static void *troubleshoot_ctx; |
| |
| static struct kmsg_dumper dumper_core_dump = {.dump = kmsg_dumper_core_dump, .max_reason = KMSG_DUMP_PANIC}; |
| |
| /* |
| * Common routine to print scratch regs (r0-r12) or callee regs (r13-r25) |
| * -Prints 3 regs per line and a CR. |
| * -To continue, callee regs right after scratch, special handling of CR |
| */ |
| static noinline void print_reg_file(long *reg_rev, int start_num) |
| { |
| unsigned int i; |
| char buf[512]; |
| int n = 0, len = sizeof(buf); |
| |
| for (i = start_num; i < start_num + 13; i++) { |
| n += scnprintf(buf + n, len - n, "r%02u: 0x%08lx\t", |
| i, (unsigned long)*reg_rev); |
| |
| if (((i + 1) % 3) == 0) |
| n += scnprintf(buf + n, len - n, "\n"); |
| |
| /* because pt_regs has regs reversed: r12..r0, r25..r13 */ |
| if (is_isa_arcv2() && start_num == 0) |
| reg_rev++; |
| else |
| reg_rev--; |
| } |
| |
| if (start_num != 0) |
| n += scnprintf(buf + n, len - n, "\n\n"); |
| |
| /* To continue printing callee regs on same line as scratch regs */ |
| if (start_num == 0) |
| pr_info("%s", buf); |
| else |
| pr_cont("%s\n", buf); |
| } |
| |
| static void show_callee_regs(struct callee_regs *cregs) |
| { |
| print_reg_file(&(cregs->r13), 13); |
| } |
| |
| static void print_task_path_n_nm(struct task_struct *tsk, char *buf) |
| { |
| char *path_nm = NULL; |
| struct mm_struct *mm; |
| struct file *exe_file; |
| |
| mm = get_task_mm(tsk); |
| if (!mm) |
| goto done; |
| |
| exe_file = get_mm_exe_file(mm); |
| mmput(mm); |
| |
| if (exe_file) { |
| path_nm = file_path(exe_file, buf, 255); |
| fput(exe_file); |
| } |
| |
| done: |
| pr_info("Path: %s\n", !IS_ERR(path_nm) ? path_nm : "?"); |
| } |
| |
| static void show_faulting_vma(unsigned long address, char *buf) |
| { |
| struct vm_area_struct *vma; |
| struct inode *inode; |
| unsigned long ino = 0; |
| dev_t dev = 0; |
| char *nm = buf; |
| struct mm_struct *active_mm = current->active_mm; |
| |
| /* can't use print_vma_addr() yet as it doesn't check for |
| * non-inclusive vma |
| */ |
| down_read(&active_mm->mmap_sem); |
| vma = find_vma(active_mm, address); |
| |
| /* check against the find_vma( ) behaviour which returns the next VMA |
| * if the container VMA is not found |
| */ |
| if (vma && (vma->vm_start <= address)) { |
| struct file *file = vma->vm_file; |
| if (file) { |
| nm = file_path(file, buf, PAGE_SIZE - 1); |
| inode = file_inode(vma->vm_file); |
| dev = inode->i_sb->s_dev; |
| ino = inode->i_ino; |
| } |
| pr_info(" @off 0x%lx in [%s]\n" |
| " VMA: 0x%08lx to 0x%08lx\n", |
| vma->vm_start < TASK_UNMAPPED_BASE ? |
| address : address - vma->vm_start, |
| nm, vma->vm_start, vma->vm_end); |
| } else |
| pr_info(" @No matching VMA found\n"); |
| |
| up_read(&active_mm->mmap_sem); |
| } |
| |
| static void show_ecr_verbose(struct pt_regs *regs) |
| { |
| unsigned int vec, cause_code; |
| unsigned long address; |
| |
| pr_info("\n[ECR ]: 0x%08lx => ", regs->event); |
| |
| /* For Data fault, this is data address not instruction addr */ |
| address = current->thread.fault_address; |
| |
| vec = regs->ecr_vec; |
| cause_code = regs->ecr_cause; |
| |
| /* For DTLB Miss or ProtV, display the memory involved too */ |
| if (vec == ECR_V_DTLB_MISS) { |
| pr_cont("Invalid %s @ 0x%08lx by insn @ 0x%08lx\n", |
| (cause_code == 0x01) ? "Read" : |
| ((cause_code == 0x02) ? "Write" : "EX"), |
| address, regs->ret); |
| } else if (vec == ECR_V_ITLB_MISS) { |
| pr_cont("Insn could not be fetched\n"); |
| } else if (vec == ECR_V_MACH_CHK) { |
| pr_cont("%s\n", (cause_code == 0x0) ? |
| "Double Fault" : "Other Fatal Err"); |
| |
| } else if (vec == ECR_V_PROTV) { |
| if (cause_code == ECR_C_PROTV_INST_FETCH) |
| pr_cont("Execute from Non-exec Page\n"); |
| else if (cause_code == ECR_C_PROTV_MISALIG_DATA) |
| pr_cont("Misaligned r/w from 0x%08lx\n", address); |
| else |
| pr_cont("%s access not allowed on page\n", |
| (cause_code == 0x01) ? "Read" : |
| ((cause_code == 0x02) ? "Write" : "EX")); |
| } else if (vec == ECR_V_INSN_ERR) { |
| pr_cont("Illegal Insn\n"); |
| #ifdef CONFIG_ISA_ARCV2 |
| } else if (vec == ECR_V_MEM_ERR) { |
| if (cause_code == 0x00) |
| pr_cont("Bus Error from Insn Mem\n"); |
| else if (cause_code == 0x10) |
| pr_cont("Bus Error from Data Mem\n"); |
| else |
| pr_cont("Bus Error, check PRM\n"); |
| #endif |
| } else { |
| pr_cont("Check Programmer's Manual\n"); |
| } |
| } |
| |
| static int32_t compress_chunk(z_stream *stream, const bool is_last_stream) |
| { |
| int32_t zlib_retval; |
| |
| if (!stream) { |
| printk(KERN_ERR "%s: stream is NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (is_last_stream) { |
| zlib_retval = zlib_deflate(stream, Z_FINISH); |
| if (zlib_retval != Z_STREAM_END) { |
| if (zlib_retval != Z_OK) { |
| printk(KERN_ERR "%s: only chunk; zlib_deflate returned %d\n", __func__, |
| zlib_retval); |
| return -1; |
| } |
| |
| printk(KERN_WARNING "%s: zlib_deflate (only chunk): some data would be missing\n", |
| __func__); |
| } |
| } else { |
| zlib_retval = zlib_deflate(stream, Z_SYNC_FLUSH); |
| if (zlib_retval != Z_OK) { |
| printk(KERN_ERR "%s: first chunk, zlib_deflate returned %d\n", __func__, |
| zlib_retval); |
| return -1; |
| } |
| |
| /* |
| * If stream.avail_in is not 0, some logs could be missing; we should recheck len_max_input in |
| * arc_save_to_sram_safe_area() |
| */ |
| if (stream->avail_in != 0) |
| printk(KERN_WARNING "%s: stream->avail_in (%ld) is not 0\n", __func__, stream->avail_in); |
| } |
| |
| return 0; |
| } |
| |
| static int32_t compress_circular_buffer(const char *buf_chunk_one, uint32_t len_chunk_one, |
| const char *buf_chunk_two, uint32_t len_chunk_two, |
| char *buf_out, uint32_t len_buf_out, |
| uint32_t *len_compress_data) |
| { |
| int32_t retval = -1; |
| int32_t zlib_retval; |
| int32_t workspace_size; |
| z_stream stream; |
| |
| if (!len_compress_data) { |
| printk(KERN_ERR "%s: len_compress_data is NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| *len_compress_data = 0; |
| |
| workspace_size = zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL); |
| printk(KERN_ERR "%s: workspace_size = %d\n", __func__, workspace_size); |
| |
| stream.workspace = kmalloc(workspace_size, GFP_ATOMIC); |
| if (!stream.workspace) { |
| printk(KERN_ERR "%s: Failed to allocate %d bytes for deflate workspace\n", __func__, |
| workspace_size); |
| return -ENOMEM; |
| } |
| |
| /* Use zlib format because gzip is not available */ |
| zlib_retval = zlib_deflateInit(&stream, Z_DEFAULT_COMPRESSION); |
| if (zlib_retval != Z_OK) { |
| printk(KERN_ERR "%s: zlib_deflateInit failed, zlib_retval = %d\n", __func__, zlib_retval); |
| kfree(stream.workspace); |
| return retval; |
| } |
| |
| stream.next_out = (Byte *) buf_out; |
| stream.avail_out = len_buf_out; |
| stream.total_out = 0; |
| |
| if (len_chunk_one) { |
| /* Compress the first chunk */ |
| stream.next_in = (Byte *) buf_chunk_one; |
| stream.avail_in = len_chunk_one; |
| stream.total_in = 0; |
| |
| if (compress_chunk(&stream, len_chunk_two ? false : true)) |
| goto deflate_end; |
| } |
| |
| if (len_chunk_two) { |
| /* Compress the second chunk */ |
| stream.next_in = (Byte *) buf_chunk_two; |
| stream.avail_in = len_chunk_two; |
| stream.total_in = 0; |
| |
| if (compress_chunk(&stream, true)) |
| goto deflate_end; |
| } |
| |
| *len_compress_data = stream.total_out; |
| printk(KERN_ERR "%s: compressed data length = %u\n", __func__, *len_compress_data); |
| |
| retval = 0; |
| |
| deflate_end: |
| zlib_retval = zlib_deflateEnd(&stream); |
| if (zlib_retval != Z_OK) { |
| printk(KERN_WARNING "%s: zlib_deflateEnd failed, zlib_retval = %d\n", __func__, zlib_retval); |
| } |
| |
| kfree(stream.workspace); |
| |
| return retval; |
| } |
| |
| /* Save the printk circular buffer to the SRAM safe area */ |
| void arc_save_to_sram_safe_area(const char *buf_chunk_one, uint32_t len_chunk_one, |
| const char *buf_chunk_two, uint32_t len_chunk_two, |
| uint32_t len_max_input) |
| { |
| uint32_t extra_margin; |
| uint32_t len_compress_data; |
| |
| /* Ensure that there is something in the printk circular buffer */ |
| if (!(len_chunk_one + len_chunk_two)) { |
| printk(KERN_ERR "%s: len_chunk_one + len_chunk_two is 0\n", __func__); |
| return; |
| } |
| |
| if (len_max_input < len_chunk_one + len_chunk_two) { |
| if (len_max_input <= len_chunk_two) { |
| buf_chunk_one = NULL; |
| len_chunk_one = 0; |
| |
| buf_chunk_two += (len_chunk_two - len_max_input); |
| len_chunk_two = len_max_input; |
| } else { |
| len_max_input -= len_chunk_two; |
| |
| buf_chunk_one += (len_chunk_one - len_max_input); |
| len_chunk_one = len_max_input; |
| } |
| } |
| |
| /* Refer to include/linux/zlib.h */ |
| extra_margin = (len_chunk_one + len_chunk_two) - (((len_chunk_one + len_chunk_two) * 1000) / 1001) |
| + 12; |
| |
| if (extra_margin < len_chunk_one) { |
| buf_chunk_one += extra_margin; |
| len_chunk_one -= extra_margin; |
| } else { |
| extra_margin -= len_chunk_one; |
| |
| buf_chunk_one = NULL; |
| len_chunk_one = 0; |
| |
| buf_chunk_two += extra_margin; |
| len_chunk_two -= extra_margin; |
| } |
| |
| /* |
| * Compress the printk circular buffer and store it in SRAM safe aread (so as to retrieve it on the |
| * next reboot) |
| */ |
| compress_circular_buffer(buf_chunk_one, len_chunk_one, buf_chunk_two, len_chunk_two, |
| (char *) (sram_safe_start + 4), sram_safe_size - 4, |
| &len_compress_data); |
| |
| *((uint16_t *) sram_safe_start) = HEADER_CORE_DUMP; |
| *((uint16_t *) (sram_safe_start + 2)) = (uint16_t) len_compress_data; |
| |
| flush_cache_all(); |
| } |
| |
| static void kmsg_dumper_core_dump(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason) |
| { |
| char *buf; |
| uint32_t buf_in_len; |
| uint32_t buf_out_len = 0; |
| |
| if (reason < KMSG_DUMP_PANIC) |
| return; |
| |
| /* |
| * Format of buffer: |
| * 2 bytes: Header (HEADER_CORE_DUMP) |
| * 2 bytes: Length of the compressed logs (n) |
| * n bytes: Compressed logs |
| */ |
| |
| /* Expecting sram_safe_start to be 2 byte aligned */ |
| if ((sram_safe_start & 0x1) != 0) { |
| printk(KERN_ERR "%s: sram_safe_start (%lu) is not 2 bytes aligned\n", __func__, |
| sram_safe_start); |
| return; |
| } |
| |
| /* Needed to inform the owner that its memory is about to be used */ |
| if (!troubleshoot_start) { |
| printk(KERN_ERR "%s: troubleshoot_start not set\n", __func__); |
| return; |
| } |
| |
| /* |
| * Find the max input length that could be fed into the compression algo [remove the header (2b) |
| * and length (2b)] |
| */ |
| buf_in_len = (CORE_DUMP_COMPRESS_RATIO * (sram_safe_size - 4)) / 100; |
| |
| buf = kmalloc(buf_in_len, GFP_ATOMIC); |
| if (!buf) { |
| printk(KERN_ERR "%s: Failed to allocate %u bytes for core dump buffer\n", __func__, |
| buf_in_len); |
| return; |
| } |
| |
| if (false == kmsg_dump_get_buffer(dumper, false, buf, buf_in_len, &buf_out_len)) { |
| printk(KERN_ERR "%s: kmsg_dump_get_buffer failed\n", __func__); |
| kfree(buf); |
| return; |
| } |
| |
| (*troubleshoot_start)(troubleshoot_ctx); |
| |
| arc_save_to_sram_safe_area(buf, buf_out_len, NULL, 0, buf_out_len); |
| |
| kfree(buf); |
| } |
| |
| /************************************************************************ |
| * API called by rest of kernel |
| ***********************************************************************/ |
| |
| void show_regs(struct pt_regs *regs) |
| { |
| struct task_struct *tsk = current; |
| struct callee_regs *cregs; |
| char *buf; |
| |
| buf = (char *)__get_free_page(GFP_TEMPORARY); |
| if (!buf) |
| return; |
| |
| print_task_path_n_nm(tsk, buf); |
| show_regs_print_info(KERN_INFO); |
| |
| show_ecr_verbose(regs); |
| |
| pr_info("[EFA ]: 0x%08lx\n[BLINK ]: %pS\n[ERET ]: %pS\n", |
| current->thread.fault_address, |
| (void *)regs->blink, (void *)regs->ret); |
| |
| if (user_mode(regs)) |
| show_faulting_vma(regs->ret, buf); /* faulting code, not data */ |
| |
| pr_info("[STAT32]: 0x%08lx", regs->status32); |
| |
| #define STS_BIT(r, bit) r->status32 & STATUS_##bit##_MASK ? #bit" " : "" |
| |
| #ifdef CONFIG_ISA_ARCOMPACT |
| pr_cont(" : %2s%2s%2s%2s%2s%2s%2s\n", |
| (regs->status32 & STATUS_U_MASK) ? "U " : "K ", |
| STS_BIT(regs, DE), STS_BIT(regs, AE), |
| STS_BIT(regs, A2), STS_BIT(regs, A1), |
| STS_BIT(regs, E2), STS_BIT(regs, E1)); |
| #else |
| pr_cont(" : %2s%2s%2s%2s\n", |
| STS_BIT(regs, IE), |
| (regs->status32 & STATUS_U_MASK) ? "U " : "K ", |
| STS_BIT(regs, DE), STS_BIT(regs, AE)); |
| #endif |
| pr_info("BTA: 0x%08lx\t SP: 0x%08lx\t FP: 0x%08lx\n", |
| regs->bta, regs->sp, regs->fp); |
| pr_info("LPS: 0x%08lx\tLPE: 0x%08lx\tLPC: 0x%08lx\n", |
| regs->lp_start, regs->lp_end, regs->lp_count); |
| |
| /* print regs->r0 thru regs->r12 |
| * Sequential printing was generating horrible code |
| */ |
| print_reg_file(&(regs->r0), 0); |
| |
| /* If Callee regs were saved, display them too */ |
| cregs = (struct callee_regs *)current->thread.callee_reg; |
| if (cregs) |
| show_callee_regs(cregs); |
| |
| free_page((unsigned long)buf); |
| } |
| |
| void show_kernel_fault_diag(const char *str, struct pt_regs *regs, |
| unsigned long address) |
| { |
| current->thread.fault_address = address; |
| |
| /* Caller and Callee regs */ |
| show_regs(regs); |
| |
| /* Show stack trace if this Fatality happened in kernel mode */ |
| if (!user_mode(regs)) |
| show_stacktrace(current, regs); |
| |
| kmsg_dump(KMSG_DUMP_PANIC); |
| } |
| |
| void arc_set_sram_safe_area(unsigned long sram_start, unsigned long sram_end) |
| { |
| int retval; |
| |
| sram_safe_start = sram_start; |
| sram_safe_size = sram_end - sram_safe_start; |
| |
| printk("%s: sram_safe_start=%p, size=%#x\n", sram_safe_start, sram_safe_size); |
| /* Register the kmsg dumper - will be called if LHost, MuC and/or AuC crashes */ |
| retval = kmsg_dump_register(&dumper_core_dump); |
| if (retval) |
| printk(KERN_ERR "%s: Could not register dumper_core_dump\n", __func__); |
| } |
| EXPORT_SYMBOL(arc_set_sram_safe_area); |
| |
| void arc_set_troubleshoot_start_hook(arc_troubleshoot_start_hook_cbk start, void *ctx) |
| { |
| troubleshoot_start = start; |
| troubleshoot_ctx = ctx; |
| } |
| EXPORT_SYMBOL(arc_set_troubleshoot_start_hook); |
| |
| #ifdef CONFIG_DEBUG_FS |
| |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/mount.h> |
| #include <linux/pagemap.h> |
| #include <linux/init.h> |
| #include <linux/namei.h> |
| #include <linux/debugfs.h> |
| |
| static struct dentry *test_dentry; |
| static struct dentry *test_dir; |
| static struct dentry *test_u32_dentry; |
| |
| static u32 clr_on_read = 1; |
| |
| #ifdef CONFIG_ARC_DBG_TLB_MISS_COUNT |
| u32 numitlb, numdtlb, num_pte_not_present; |
| |
| static int fill_display_data(char *kbuf) |
| { |
| size_t num = 0; |
| num += sprintf(kbuf + num, "I-TLB Miss %x\n", numitlb); |
| num += sprintf(kbuf + num, "D-TLB Miss %x\n", numdtlb); |
| num += sprintf(kbuf + num, "PTE not present %x\n", num_pte_not_present); |
| |
| if (clr_on_read) |
| numitlb = numdtlb = num_pte_not_present = 0; |
| |
| return num; |
| } |
| |
| static int tlb_stats_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = (void *)__get_free_page(GFP_KERNEL); |
| return 0; |
| } |
| |
| /* called on user read(): display the counters */ |
| static ssize_t tlb_stats_output(struct file *file, /* file descriptor */ |
| char __user *user_buf, /* user buffer */ |
| size_t len, /* length of buffer */ |
| loff_t *offset) /* offset in the file */ |
| { |
| size_t num; |
| char *kbuf = (char *)file->private_data; |
| |
| /* All of the data can he shoved in one iteration */ |
| if (*offset != 0) |
| return 0; |
| |
| num = fill_display_data(kbuf); |
| |
| /* simple_read_from_buffer() is helper for copy to user space |
| It copies up to @2 (num) bytes from kernel buffer @4 (kbuf) at offset |
| @3 (offset) into the user space address starting at @1 (user_buf). |
| @5 (len) is max size of user buffer |
| */ |
| return simple_read_from_buffer(user_buf, num, offset, kbuf, len); |
| } |
| |
| /* called on user write : clears the counters */ |
| static ssize_t tlb_stats_clear(struct file *file, const char __user *user_buf, |
| size_t length, loff_t *offset) |
| { |
| numitlb = numdtlb = num_pte_not_present = 0; |
| return length; |
| } |
| |
| static int tlb_stats_close(struct inode *inode, struct file *file) |
| { |
| free_page((unsigned long)(file->private_data)); |
| return 0; |
| } |
| |
| static const struct file_operations tlb_stats_file_ops = { |
| .read = tlb_stats_output, |
| .write = tlb_stats_clear, |
| .open = tlb_stats_open, |
| .release = tlb_stats_close |
| }; |
| #endif |
| |
| static int __init arc_debugfs_init(void) |
| { |
| test_dir = debugfs_create_dir("arc", NULL); |
| |
| #ifdef CONFIG_ARC_DBG_TLB_MISS_COUNT |
| test_dentry = debugfs_create_file("tlb_stats", 0444, test_dir, NULL, |
| &tlb_stats_file_ops); |
| #endif |
| |
| test_u32_dentry = |
| debugfs_create_u32("clr_on_read", 0444, test_dir, &clr_on_read); |
| |
| return 0; |
| } |
| |
| module_init(arc_debugfs_init); |
| |
| static void __exit arc_debugfs_exit(void) |
| { |
| debugfs_remove(test_u32_dentry); |
| debugfs_remove(test_dentry); |
| debugfs_remove(test_dir); |
| } |
| module_exit(arc_debugfs_exit); |
| |
| #endif |