| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Based on arch/arm/kernel/traps.c | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 1995-2009 Russell King | 
					
						
							|  |  |  |  * Copyright (C) 2012 ARM 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 | 
					
						
							|  |  |  |  * published by the Free Software Foundation. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/signal.h>
 | 
					
						
							|  |  |  | #include <linux/personality.h>
 | 
					
						
							|  |  |  | #include <linux/kallsyms.h>
 | 
					
						
							|  |  |  | #include <linux/spinlock.h>
 | 
					
						
							|  |  |  | #include <linux/uaccess.h>
 | 
					
						
							|  |  |  | #include <linux/hardirq.h>
 | 
					
						
							|  |  |  | #include <linux/kdebug.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/kexec.h>
 | 
					
						
							|  |  |  | #include <linux/delay.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/syscalls.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <asm/atomic.h>
 | 
					
						
							| 
									
										
										
										
											2013-03-16 08:48:13 +00:00
										 |  |  | #include <asm/debug-monitors.h>
 | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | #include <asm/traps.h>
 | 
					
						
							|  |  |  | #include <asm/stacktrace.h>
 | 
					
						
							|  |  |  | #include <asm/exception.h>
 | 
					
						
							|  |  |  | #include <asm/system_misc.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const char *handler[]= { | 
					
						
							|  |  |  | 	"Synchronous Abort", | 
					
						
							|  |  |  | 	"IRQ", | 
					
						
							|  |  |  | 	"FIQ", | 
					
						
							|  |  |  | 	"Error" | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int show_unhandled_signals = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Dump out the contents of some memory nicely... | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void dump_mem(const char *lvl, const char *str, unsigned long bottom, | 
					
						
							|  |  |  | 		     unsigned long top) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long first; | 
					
						
							|  |  |  | 	mm_segment_t fs; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * We need to switch to kernel mode so that we can use __get_user | 
					
						
							|  |  |  | 	 * to safely read from kernel space.  Note that we now dump the | 
					
						
							|  |  |  | 	 * code first, just in case the backtrace kills us. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	fs = get_fs(); | 
					
						
							|  |  |  | 	set_fs(KERNEL_DS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	printk("%s%s(0x%016lx to 0x%016lx)\n", lvl, str, bottom, top); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (first = bottom & ~31; first < top; first += 32) { | 
					
						
							|  |  |  | 		unsigned long p; | 
					
						
							|  |  |  | 		char str[sizeof(" 12345678") * 8 + 1]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memset(str, ' ', sizeof(str)); | 
					
						
							|  |  |  | 		str[sizeof(str) - 1] = '\0'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (p = first, i = 0; i < 8 && p < top; i++, p += 4) { | 
					
						
							|  |  |  | 			if (p >= bottom && p < top) { | 
					
						
							|  |  |  | 				unsigned int val; | 
					
						
							|  |  |  | 				if (__get_user(val, (unsigned int *)p) == 0) | 
					
						
							|  |  |  | 					sprintf(str + i * 9, " %08x", val); | 
					
						
							|  |  |  | 				else | 
					
						
							|  |  |  | 					sprintf(str + i * 9, " ????????"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		printk("%s%04lx:%s\n", lvl, first & 0xffff, str); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_fs(fs); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void dump_backtrace_entry(unsigned long where, unsigned long stack) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	print_ip_sym(where); | 
					
						
							|  |  |  | 	if (in_exception_text(where)) | 
					
						
							|  |  |  | 		dump_mem("", "Exception stack", stack, | 
					
						
							|  |  |  | 			 stack + sizeof(struct pt_regs)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void dump_instr(const char *lvl, struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long addr = instruction_pointer(regs); | 
					
						
							|  |  |  | 	mm_segment_t fs; | 
					
						
							|  |  |  | 	char str[sizeof("00000000 ") * 5 + 2 + 1], *p = str; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * We need to switch to kernel mode so that we can use __get_user | 
					
						
							|  |  |  | 	 * to safely read from kernel space.  Note that we now dump the | 
					
						
							|  |  |  | 	 * code first, just in case the backtrace kills us. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	fs = get_fs(); | 
					
						
							|  |  |  | 	set_fs(KERNEL_DS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = -4; i < 1; i++) { | 
					
						
							|  |  |  | 		unsigned int val, bad; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		bad = __get_user(val, &((u32 *)addr)[i]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!bad) | 
					
						
							|  |  |  | 			p += sprintf(p, i == 0 ? "(%08x) " : "%08x ", val); | 
					
						
							|  |  |  | 		else { | 
					
						
							|  |  |  | 			p += sprintf(p, "bad PC value"); | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	printk("%sCode: %s\n", lvl, str); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_fs(fs); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct stackframe frame; | 
					
						
							|  |  |  | 	const register unsigned long current_sp asm ("sp"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!tsk) | 
					
						
							|  |  |  | 		tsk = current; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (regs) { | 
					
						
							|  |  |  | 		frame.fp = regs->regs[29]; | 
					
						
							|  |  |  | 		frame.sp = regs->sp; | 
					
						
							|  |  |  | 		frame.pc = regs->pc; | 
					
						
							|  |  |  | 	} else if (tsk == current) { | 
					
						
							|  |  |  | 		frame.fp = (unsigned long)__builtin_frame_address(0); | 
					
						
							|  |  |  | 		frame.sp = current_sp; | 
					
						
							|  |  |  | 		frame.pc = (unsigned long)dump_backtrace; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * task blocked in __switch_to | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		frame.fp = thread_saved_fp(tsk); | 
					
						
							|  |  |  | 		frame.sp = thread_saved_sp(tsk); | 
					
						
							|  |  |  | 		frame.pc = thread_saved_pc(tsk); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	printk("Call trace:\n"); | 
					
						
							|  |  |  | 	while (1) { | 
					
						
							|  |  |  | 		unsigned long where = frame.pc; | 
					
						
							|  |  |  | 		int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = unwind_frame(&frame); | 
					
						
							|  |  |  | 		if (ret < 0) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		dump_backtrace_entry(where, frame.sp); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void show_stack(struct task_struct *tsk, unsigned long *sp) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	dump_backtrace(NULL, tsk); | 
					
						
							|  |  |  | 	barrier(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_PREEMPT
 | 
					
						
							|  |  |  | #define S_PREEMPT " PREEMPT"
 | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | #define S_PREEMPT ""
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | #ifdef CONFIG_SMP
 | 
					
						
							|  |  |  | #define S_SMP " SMP"
 | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | #define S_SMP ""
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __die(const char *str, int err, struct thread_info *thread, | 
					
						
							|  |  |  | 		 struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct task_struct *tsk = thread->task; | 
					
						
							|  |  |  | 	static int die_counter; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_emerg("Internal error: %s: %x [#%d]" S_PREEMPT S_SMP "\n", | 
					
						
							|  |  |  | 		 str, err, ++die_counter); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* trap and error numbers are mostly meaningless on ARM */ | 
					
						
							|  |  |  | 	ret = notify_die(DIE_OOPS, str, regs, err, 0, SIGSEGV); | 
					
						
							|  |  |  | 	if (ret == NOTIFY_STOP) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	print_modules(); | 
					
						
							|  |  |  | 	__show_regs(regs); | 
					
						
							|  |  |  | 	pr_emerg("Process %.*s (pid: %d, stack limit = 0x%p)\n", | 
					
						
							|  |  |  | 		 TASK_COMM_LEN, tsk->comm, task_pid_nr(tsk), thread + 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!user_mode(regs) || in_interrupt()) { | 
					
						
							|  |  |  | 		dump_mem(KERN_EMERG, "Stack: ", regs->sp, | 
					
						
							|  |  |  | 			 THREAD_SIZE + (unsigned long)task_stack_page(tsk)); | 
					
						
							|  |  |  | 		dump_backtrace(regs, tsk); | 
					
						
							|  |  |  | 		dump_instr(KERN_EMERG, regs); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static DEFINE_RAW_SPINLOCK(die_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * This function is protected against re-entrancy. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void die(const char *str, struct pt_regs *regs, int err) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct thread_info *thread = current_thread_info(); | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	oops_enter(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	raw_spin_lock_irq(&die_lock); | 
					
						
							|  |  |  | 	console_verbose(); | 
					
						
							|  |  |  | 	bust_spinlocks(1); | 
					
						
							|  |  |  | 	ret = __die(str, err, thread, regs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (regs && kexec_should_crash(thread->task)) | 
					
						
							|  |  |  | 		crash_kexec(regs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bust_spinlocks(0); | 
					
						
							| 
									
										
										
										
											2013-01-21 17:17:39 +10:30
										 |  |  | 	add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE); | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 	raw_spin_unlock_irq(&die_lock); | 
					
						
							|  |  |  | 	oops_exit(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (in_interrupt()) | 
					
						
							|  |  |  | 		panic("Fatal exception in interrupt"); | 
					
						
							|  |  |  | 	if (panic_on_oops) | 
					
						
							|  |  |  | 		panic("Fatal exception"); | 
					
						
							|  |  |  | 	if (ret != NOTIFY_STOP) | 
					
						
							|  |  |  | 		do_exit(SIGSEGV); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void arm64_notify_die(const char *str, struct pt_regs *regs, | 
					
						
							|  |  |  | 		      struct siginfo *info, int err) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-04-06 23:04:12 +01:00
										 |  |  | 	if (user_mode(regs)) { | 
					
						
							|  |  |  | 		current->thread.fault_address = 0; | 
					
						
							|  |  |  | 		current->thread.fault_code = err; | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 		force_sig_info(info->si_signo, info, current); | 
					
						
							| 
									
										
										
										
											2014-04-06 23:04:12 +01:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 		die(str, regs, err); | 
					
						
							| 
									
										
										
										
											2014-04-06 23:04:12 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | asmlinkage void __exception do_undefinstr(struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	siginfo_t info; | 
					
						
							|  |  |  | 	void __user *pc = (void __user *)instruction_pointer(regs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* check for AArch32 breakpoint instructions */ | 
					
						
							| 
									
										
										
										
											2013-03-16 08:48:13 +00:00
										 |  |  | 	if (!aarch32_break_handler(regs)) | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-05-21 12:16:56 +01:00
										 |  |  | 	if (show_unhandled_signals && unhandled_signal(current, SIGILL) && | 
					
						
							|  |  |  | 	    printk_ratelimit()) { | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 		pr_info("%s[%d]: undefined instruction: pc=%p\n", | 
					
						
							|  |  |  | 			current->comm, task_pid_nr(current), pc); | 
					
						
							|  |  |  | 		dump_instr(KERN_INFO, regs); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	info.si_signo = SIGILL; | 
					
						
							|  |  |  | 	info.si_errno = 0; | 
					
						
							|  |  |  | 	info.si_code  = ILL_ILLOPC; | 
					
						
							|  |  |  | 	info.si_addr  = pc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	arm64_notify_die("Oops - undefined instruction", regs, &info, 0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | long compat_arm_syscall(struct pt_regs *regs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | asmlinkage long do_ni_syscall(struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | #ifdef CONFIG_COMPAT
 | 
					
						
							|  |  |  | 	long ret; | 
					
						
							|  |  |  | 	if (is_compat_task()) { | 
					
						
							|  |  |  | 		ret = compat_arm_syscall(regs); | 
					
						
							|  |  |  | 		if (ret != -ENOSYS) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-05-21 12:16:56 +01:00
										 |  |  | 	if (show_unhandled_signals && printk_ratelimit()) { | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 		pr_info("%s[%d]: syscall %d\n", current->comm, | 
					
						
							|  |  |  | 			task_pid_nr(current), (int)regs->syscallno); | 
					
						
							|  |  |  | 		dump_instr("", regs); | 
					
						
							|  |  |  | 		if (user_mode(regs)) | 
					
						
							|  |  |  | 			__show_regs(regs); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sys_ni_syscall(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * bad_mode handles the impossible case in the exception vector. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-05-28 15:54:15 +01:00
										 |  |  | 	siginfo_t info; | 
					
						
							|  |  |  | 	void __user *pc = (void __user *)instruction_pointer(regs); | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 	console_verbose(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_crit("Bad mode in %s handler detected, code 0x%08x\n", | 
					
						
							|  |  |  | 		handler[reason], esr); | 
					
						
							| 
									
										
										
										
											2013-05-28 15:54:15 +01:00
										 |  |  | 	__show_regs(regs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	info.si_signo = SIGILL; | 
					
						
							|  |  |  | 	info.si_errno = 0; | 
					
						
							|  |  |  | 	info.si_code  = ILL_ILLOPC; | 
					
						
							|  |  |  | 	info.si_addr  = pc; | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-05-28 15:54:15 +01:00
										 |  |  | 	arm64_notify_die("Oops - bad mode", regs, &info, 0); | 
					
						
							| 
									
										
										
										
											2012-03-05 11:49:27 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void __pte_error(const char *file, int line, unsigned long val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	printk("%s:%d: bad pte %016lx.\n", file, line, val); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void __pmd_error(const char *file, int line, unsigned long val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	printk("%s:%d: bad pmd %016lx.\n", file, line, val); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void __pgd_error(const char *file, int line, unsigned long val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	printk("%s:%d: bad pgd %016lx.\n", file, line, val); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void __init trap_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return; | 
					
						
							|  |  |  | } |