| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com) |
| */ |
| |
| #include <linux/ptrace.h> |
| #include <linux/module.h> |
| #include <linux/mm.h> |
| #include <linux/fs.h> |
| #include <linux/kdev_t.h> |
| #include <linux/proc_fs.h> |
| #include <linux/file.h> |
| #include <linux/sched/mm.h> |
| #include <linux/sched/debug.h> |
| |
| #include <asm/arcregs.h> |
| #include <asm/irqflags.h> |
| |
| #define ARC_PATH_MAX 256 |
| |
| static noinline void print_regs_scratch(struct pt_regs *regs) |
| { |
| pr_cont("BTA: 0x%08lx\n SP: 0x%08lx FP: 0x%08lx BLK: %pS\n", |
| regs->bta, regs->sp, regs->fp, (void *)regs->blink); |
| pr_cont("LPS: 0x%08lx\tLPE: 0x%08lx\tLPC: 0x%08lx\n", |
| regs->lp_start, regs->lp_end, regs->lp_count); |
| |
| pr_info("r00: 0x%08lx\tr01: 0x%08lx\tr02: 0x%08lx\n" \ |
| "r03: 0x%08lx\tr04: 0x%08lx\tr05: 0x%08lx\n" \ |
| "r06: 0x%08lx\tr07: 0x%08lx\tr08: 0x%08lx\n" \ |
| "r09: 0x%08lx\tr10: 0x%08lx\tr11: 0x%08lx\n" \ |
| "r12: 0x%08lx\t", |
| regs->r0, regs->r1, regs->r2, |
| regs->r3, regs->r4, regs->r5, |
| regs->r6, regs->r7, regs->r8, |
| regs->r9, regs->r10, regs->r11, |
| regs->r12); |
| } |
| |
| static void print_regs_callee(struct callee_regs *regs) |
| { |
| pr_cont("r13: 0x%08lx\tr14: 0x%08lx\n" \ |
| "r15: 0x%08lx\tr16: 0x%08lx\tr17: 0x%08lx\n" \ |
| "r18: 0x%08lx\tr19: 0x%08lx\tr20: 0x%08lx\n" \ |
| "r21: 0x%08lx\tr22: 0x%08lx\tr23: 0x%08lx\n" \ |
| "r24: 0x%08lx\tr25: 0x%08lx\n", |
| regs->r13, regs->r14, |
| regs->r15, regs->r16, regs->r17, |
| regs->r18, regs->r19, regs->r20, |
| regs->r21, regs->r22, regs->r23, |
| regs->r24, regs->r25); |
| } |
| |
| static void print_task_path_n_nm(struct task_struct *tsk) |
| { |
| char *path_nm = NULL; |
| struct mm_struct *mm; |
| struct file *exe_file; |
| char buf[ARC_PATH_MAX]; |
| |
| 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, ARC_PATH_MAX-1); |
| fput(exe_file); |
| } |
| |
| done: |
| pr_info("Path: %s\n", !IS_ERR(path_nm) ? path_nm : "?"); |
| } |
| |
| static void show_faulting_vma(unsigned long address) |
| { |
| struct vm_area_struct *vma; |
| struct mm_struct *active_mm = current->active_mm; |
| |
| /* can't use print_vma_addr() yet as it doesn't check for |
| * non-inclusive vma |
| */ |
| mmap_read_lock(active_mm); |
| vma = vma_lookup(active_mm, address); |
| |
| /* Lookup the vma at the address and report if the container VMA is not |
| * found |
| */ |
| if (vma) { |
| char buf[ARC_PATH_MAX]; |
| char *nm = "anon"; |
| |
| if (vma->vm_file) { |
| /* XXX: can we use %pD below and get rid of buf? */ |
| nm = d_path(file_user_path(vma->vm_file), buf, |
| ARC_PATH_MAX-1); |
| if (IS_ERR(nm)) |
| nm = "?"; |
| } |
| pr_info(" @off 0x%lx in [%s] 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"); |
| |
| mmap_read_unlock(active_mm); |
| } |
| |
| static void show_ecr_verbose(struct pt_regs *regs) |
| { |
| unsigned int vec, cause_code; |
| unsigned long address; |
| |
| /* 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 @ %pS\n", |
| (cause_code == 0x01) ? "Read" : |
| ((cause_code == 0x02) ? "Write" : "EX"), |
| address, (void *)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("Machine Check (%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 && |
| IS_ENABLED(CONFIG_ISA_ARCOMPACT)) |
| 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"); |
| } else if (vec == ECR_V_MISALIGN) { |
| pr_cont("Misaligned r/w from 0x%08lx\n", address); |
| #endif |
| } else if (vec == ECR_V_TRAP) { |
| if (regs->ecr.param == 5) |
| pr_cont("gcc generated __builtin_trap\n"); |
| } else { |
| pr_cont("Check Programmer's Manual\n"); |
| } |
| } |
| |
| /************************************************************************ |
| * API called by rest of kernel |
| ***********************************************************************/ |
| |
| void show_regs(struct pt_regs *regs) |
| { |
| struct task_struct *tsk = current; |
| struct callee_regs *cregs = (struct callee_regs *)tsk->thread.callee_reg; |
| |
| /* |
| * generic code calls us with preemption disabled, but some calls |
| * here could sleep, so re-enable to avoid lockdep splat |
| */ |
| preempt_enable(); |
| |
| print_task_path_n_nm(tsk); |
| show_regs_print_info(KERN_INFO); |
| |
| show_ecr_verbose(regs); |
| |
| if (user_mode(regs)) |
| show_faulting_vma(regs->ret); /* faulting code, not data */ |
| |
| pr_info("ECR: 0x%08lx EFA: 0x%08lx ERET: 0x%08lx\n", |
| regs->ecr.full, current->thread.fault_address, regs->ret); |
| |
| 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]", |
| (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] ", |
| STS_BIT(regs, IE), |
| (regs->status32 & STATUS_U_MASK) ? "U " : "K ", |
| STS_BIT(regs, DE), STS_BIT(regs, AE)); |
| #endif |
| |
| print_regs_scratch(regs); |
| if (cregs) |
| print_regs_callee(cregs); |
| |
| preempt_disable(); |
| } |
| |
| void show_kernel_fault_diag(const char *str, struct pt_regs *regs, |
| unsigned long address) |
| { |
| current->thread.fault_address = address; |
| |
| /* Show fault description */ |
| pr_info("\n%s\n", str); |
| |
| /* 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, KERN_DEFAULT); |
| } |