arm64: dts: rk3399-box: vdd_gpu force PWM mode via regulator mode
[firefly-linux-kernel-4.4.55.git] / arch / arm64 / kernel / traps.c
index f30852d28590358c6780a22c14049f92a124bdb8..c5392081b49ba4ac4f48d9a0782442ac888ed222 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/bug.h>
 #include <linux/signal.h>
 #include <linux/personality.h>
 #include <linux/kallsyms.h>
 #include <linux/syscalls.h>
 
 #include <asm/atomic.h>
+#include <asm/bug.h>
+#include <asm/debug-monitors.h>
+#include <asm/esr.h>
+#include <asm/insn.h>
 #include <asm/traps.h>
 #include <asm/stacktrace.h>
 #include <asm/exception.h>
@@ -50,11 +55,12 @@ 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 top, bool compat)
 {
        unsigned long first;
        mm_segment_t fs;
        int i;
+       unsigned int width = compat ? 4 : 8;
 
        /*
         * We need to switch to kernel mode so that we can use __get_user
@@ -73,13 +79,22 @@ static void dump_mem(const char *lvl, const char *str, unsigned long bottom,
                memset(str, ' ', sizeof(str));
                str[sizeof(str) - 1] = '\0';
 
-               for (p = first, i = 0; i < 8 && p < top; i++, p += 4) {
+               for (p = first, i = 0; i < (32 / width)
+                                       && p < top; i++, p += width) {
                        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, " ????????");
+                               unsigned long val;
+
+                               if (width == 8) {
+                                       if (__get_user(val, (unsigned long *)p) == 0)
+                                               sprintf(str + i * 17, " %016lx", val);
+                                       else
+                                               sprintf(str + i * 17, " ????????????????");
+                               } else {
+                                       if (__get_user(val, (unsigned int *)p) == 0)
+                                               sprintf(str + i * 9, " %08lx", val);
+                                       else
+                                               sprintf(str + i * 9, " ????????");
+                               }
                        }
                }
                printk("%s%04lx:%s\n", lvl, first & 0xffff, str);
@@ -88,12 +103,12 @@ static void dump_mem(const char *lvl, const char *str, unsigned long bottom,
        set_fs(fs);
 }
 
-static void dump_backtrace_entry(unsigned long where, unsigned long stack)
+static void dump_backtrace_entry(unsigned long where)
 {
+       /*
+        * Note that 'where' can have a physical address, but it's not handled.
+        */
        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)
@@ -131,20 +146,26 @@ static void dump_instr(const char *lvl, struct pt_regs *regs)
 static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 {
        struct stackframe frame;
-       const register unsigned long current_sp asm ("sp");
+       unsigned long irq_stack_ptr;
+       int skip;
+
+       /*
+        * Switching between stacks is valid when tracing current and in
+        * non-preemptible context.
+        */
+       if (tsk == current && !preemptible())
+               irq_stack_ptr = IRQ_STACK_PTR(smp_processor_id());
+       else
+               irq_stack_ptr = 0;
 
        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) {
+       if (tsk == current) {
                frame.fp = (unsigned long)__builtin_frame_address(0);
-               frame.sp = current_sp;
+               frame.sp = current_stack_pointer;
                frame.pc = (unsigned long)dump_backtrace;
        } else {
                /*
@@ -154,16 +175,49 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
                frame.sp = thread_saved_sp(tsk);
                frame.pc = thread_saved_pc(tsk);
        }
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+       frame.graph = tsk->curr_ret_stack;
+#endif
 
+       skip = !!regs;
        printk("Call trace:\n");
        while (1) {
                unsigned long where = frame.pc;
+               unsigned long stack;
                int ret;
 
-               ret = unwind_frame(&frame);
+               /* skip until specified stack frame */
+               if (!skip) {
+                       dump_backtrace_entry(where);
+               } else if (frame.fp == regs->regs[29]) {
+                       skip = 0;
+                       /*
+                        * Mostly, this is the case where this function is
+                        * called in panic/abort. As exception handler's
+                        * stack frame does not contain the corresponding pc
+                        * at which an exception has taken place, use regs->pc
+                        * instead.
+                        */
+                       dump_backtrace_entry(regs->pc);
+               }
+               ret = unwind_frame(tsk, &frame);
                if (ret < 0)
                        break;
-               dump_backtrace_entry(where, frame.sp);
+               stack = frame.sp;
+               if (in_exception_text(where)) {
+                       /*
+                        * If we switched to the irq_stack before calling this
+                        * exception handler, then the pt_regs will be on the
+                        * task stack. The easiest way to tell is if the large
+                        * pt_regs would overlap with the end of the irq_stack.
+                        */
+                       if (stack < irq_stack_ptr &&
+                           (stack + sizeof(struct pt_regs)) > irq_stack_ptr)
+                               stack = IRQ_STACK_TO_TASK_STACK(irq_stack_ptr);
+
+                       dump_mem("", "Exception stack", stack,
+                                stack + sizeof(struct pt_regs), false);
+               }
        }
 }
 
@@ -178,11 +232,7 @@ void show_stack(struct task_struct *tsk, unsigned long *sp)
 #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)
@@ -206,7 +256,8 @@ static int __die(const char *str, int err, struct thread_info *thread,
 
        if (!user_mode(regs) || in_interrupt()) {
                dump_mem(KERN_EMERG, "Stack: ", regs->sp,
-                        THREAD_SIZE + (unsigned long)task_stack_page(tsk));
+                        THREAD_SIZE + (unsigned long)task_stack_page(tsk),
+                        compat_user_mode(regs));
                dump_backtrace(regs, tsk);
                dump_instr(KERN_EMERG, regs);
        }
@@ -250,10 +301,76 @@ void die(const char *str, struct pt_regs *regs, int err)
 void arm64_notify_die(const char *str, struct pt_regs *regs,
                      struct siginfo *info, int err)
 {
-       if (user_mode(regs))
+       if (user_mode(regs)) {
+               current->thread.fault_address = 0;
+               current->thread.fault_code = err;
                force_sig_info(info->si_signo, info, current);
-       else
+       } else {
                die(str, regs, err);
+       }
+}
+
+static LIST_HEAD(undef_hook);
+static DEFINE_RAW_SPINLOCK(undef_lock);
+
+void register_undef_hook(struct undef_hook *hook)
+{
+       unsigned long flags;
+
+       raw_spin_lock_irqsave(&undef_lock, flags);
+       list_add(&hook->node, &undef_hook);
+       raw_spin_unlock_irqrestore(&undef_lock, flags);
+}
+
+void unregister_undef_hook(struct undef_hook *hook)
+{
+       unsigned long flags;
+
+       raw_spin_lock_irqsave(&undef_lock, flags);
+       list_del(&hook->node);
+       raw_spin_unlock_irqrestore(&undef_lock, flags);
+}
+
+static int call_undef_hook(struct pt_regs *regs)
+{
+       struct undef_hook *hook;
+       unsigned long flags;
+       u32 instr;
+       int (*fn)(struct pt_regs *regs, u32 instr) = NULL;
+       void __user *pc = (void __user *)instruction_pointer(regs);
+
+       if (!user_mode(regs))
+               return 1;
+
+       if (compat_thumb_mode(regs)) {
+               /* 16-bit Thumb instruction */
+               if (get_user(instr, (u16 __user *)pc))
+                       goto exit;
+               instr = le16_to_cpu(instr);
+               if (aarch32_insn_is_wide(instr)) {
+                       u32 instr2;
+
+                       if (get_user(instr2, (u16 __user *)(pc + 2)))
+                               goto exit;
+                       instr2 = le16_to_cpu(instr2);
+                       instr = (instr << 16) | instr2;
+               }
+       } else {
+               /* 32-bit ARM instruction */
+               if (get_user(instr, (u32 __user *)pc))
+                       goto exit;
+               instr = le32_to_cpu(instr);
+       }
+
+       raw_spin_lock_irqsave(&undef_lock, flags);
+       list_for_each_entry(hook, &undef_hook, node)
+               if ((instr & hook->instr_mask) == hook->instr_val &&
+                       (regs->pstate & hook->pstate_mask) == hook->pstate_val)
+                       fn = hook->fn;
+
+       raw_spin_unlock_irqrestore(&undef_lock, flags);
+exit:
+       return fn ? fn(regs, instr) : 1;
 }
 
 asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
@@ -261,14 +378,14 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
        siginfo_t info;
        void __user *pc = (void __user *)instruction_pointer(regs);
 
-#ifdef CONFIG_COMPAT
        /* check for AArch32 breakpoint instructions */
-       if (compat_user_mode(regs) && aarch32_break_trap(regs) == 0)
+       if (!aarch32_break_handler(regs))
                return;
-#endif
 
-       if (show_unhandled_signals && unhandled_signal(current, SIGILL) &&
-           printk_ratelimit()) {
+       if (call_undef_hook(regs) == 0)
+               return;
+
+       if (unhandled_signal(current, SIGILL) && show_unhandled_signals_ratelimited()) {
                pr_info("%s[%d]: undefined instruction: pc=%p\n",
                        current->comm, task_pid_nr(current), pc);
                dump_instr(KERN_INFO, regs);
@@ -295,7 +412,7 @@ asmlinkage long do_ni_syscall(struct pt_regs *regs)
        }
 #endif
 
-       if (show_unhandled_signals && printk_ratelimit()) {
+       if (show_unhandled_signals_ratelimited()) {
                pr_info("%s[%d]: syscall %d\n", current->comm,
                        task_pid_nr(current), (int)regs->syscallno);
                dump_instr("", regs);
@@ -306,6 +423,51 @@ asmlinkage long do_ni_syscall(struct pt_regs *regs)
        return sys_ni_syscall();
 }
 
+static const char *esr_class_str[] = {
+       [0 ... ESR_ELx_EC_MAX]          = "UNRECOGNIZED EC",
+       [ESR_ELx_EC_UNKNOWN]            = "Unknown/Uncategorized",
+       [ESR_ELx_EC_WFx]                = "WFI/WFE",
+       [ESR_ELx_EC_CP15_32]            = "CP15 MCR/MRC",
+       [ESR_ELx_EC_CP15_64]            = "CP15 MCRR/MRRC",
+       [ESR_ELx_EC_CP14_MR]            = "CP14 MCR/MRC",
+       [ESR_ELx_EC_CP14_LS]            = "CP14 LDC/STC",
+       [ESR_ELx_EC_FP_ASIMD]           = "ASIMD",
+       [ESR_ELx_EC_CP10_ID]            = "CP10 MRC/VMRS",
+       [ESR_ELx_EC_CP14_64]            = "CP14 MCRR/MRRC",
+       [ESR_ELx_EC_ILL]                = "PSTATE.IL",
+       [ESR_ELx_EC_SVC32]              = "SVC (AArch32)",
+       [ESR_ELx_EC_HVC32]              = "HVC (AArch32)",
+       [ESR_ELx_EC_SMC32]              = "SMC (AArch32)",
+       [ESR_ELx_EC_SVC64]              = "SVC (AArch64)",
+       [ESR_ELx_EC_HVC64]              = "HVC (AArch64)",
+       [ESR_ELx_EC_SMC64]              = "SMC (AArch64)",
+       [ESR_ELx_EC_SYS64]              = "MSR/MRS (AArch64)",
+       [ESR_ELx_EC_IMP_DEF]            = "EL3 IMP DEF",
+       [ESR_ELx_EC_IABT_LOW]           = "IABT (lower EL)",
+       [ESR_ELx_EC_IABT_CUR]           = "IABT (current EL)",
+       [ESR_ELx_EC_PC_ALIGN]           = "PC Alignment",
+       [ESR_ELx_EC_DABT_LOW]           = "DABT (lower EL)",
+       [ESR_ELx_EC_DABT_CUR]           = "DABT (current EL)",
+       [ESR_ELx_EC_SP_ALIGN]           = "SP Alignment",
+       [ESR_ELx_EC_FP_EXC32]           = "FP (AArch32)",
+       [ESR_ELx_EC_FP_EXC64]           = "FP (AArch64)",
+       [ESR_ELx_EC_SERROR]             = "SError",
+       [ESR_ELx_EC_BREAKPT_LOW]        = "Breakpoint (lower EL)",
+       [ESR_ELx_EC_BREAKPT_CUR]        = "Breakpoint (current EL)",
+       [ESR_ELx_EC_SOFTSTP_LOW]        = "Software Step (lower EL)",
+       [ESR_ELx_EC_SOFTSTP_CUR]        = "Software Step (current EL)",
+       [ESR_ELx_EC_WATCHPT_LOW]        = "Watchpoint (lower EL)",
+       [ESR_ELx_EC_WATCHPT_CUR]        = "Watchpoint (current EL)",
+       [ESR_ELx_EC_BKPT32]             = "BKPT (AArch32)",
+       [ESR_ELx_EC_VECTOR32]           = "Vector catch (AArch32)",
+       [ESR_ELx_EC_BRK64]              = "BRK (AArch64)",
+};
+
+const char *esr_get_class_string(u32 esr)
+{
+       return esr_class_str[esr >> ESR_ELx_EC_SHIFT];
+}
+
 /*
  * bad_mode handles the impossible case in the exception vector.
  */
@@ -315,8 +477,8 @@ asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
        void __user *pc = (void __user *)instruction_pointer(regs);
        console_verbose();
 
-       pr_crit("Bad mode in %s handler detected, code 0x%08x\n",
-               handler[reason], esr);
+       pr_crit("Bad mode in %s handler detected, code 0x%08x -- %s\n",
+               handler[reason], esr, esr_get_class_string(esr));
        __show_regs(regs);
 
        info.si_signo = SIGILL;
@@ -329,20 +491,81 @@ asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
 
 void __pte_error(const char *file, int line, unsigned long val)
 {
-       printk("%s:%d: bad pte %016lx.\n", file, line, val);
+       pr_err("%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);
+       pr_err("%s:%d: bad pmd %016lx.\n", file, line, val);
+}
+
+void __pud_error(const char *file, int line, unsigned long val)
+{
+       pr_err("%s:%d: bad pud %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);
+       pr_err("%s:%d: bad pgd %016lx.\n", file, line, val);
+}
+
+/* GENERIC_BUG traps */
+
+int is_valid_bugaddr(unsigned long addr)
+{
+       /*
+        * bug_handler() only called for BRK #BUG_BRK_IMM.
+        * So the answer is trivial -- any spurious instances with no
+        * bug table entry will be rejected by report_bug() and passed
+        * back to the debug-monitors code and handled as a fatal
+        * unexpected debug exception.
+        */
+       return 1;
+}
+
+static int bug_handler(struct pt_regs *regs, unsigned int esr)
+{
+       if (user_mode(regs))
+               return DBG_HOOK_ERROR;
+
+       switch (report_bug(regs->pc, regs)) {
+       case BUG_TRAP_TYPE_BUG:
+               die("Oops - BUG", regs, 0);
+               break;
+
+       case BUG_TRAP_TYPE_WARN:
+               /* Ideally, report_bug() should backtrace for us... but no. */
+               dump_backtrace(regs, NULL);
+               break;
+
+       default:
+               /* unknown/unrecognised bug trap type */
+               return DBG_HOOK_ERROR;
+       }
+
+       /* If thread survives, skip over the BUG instruction and continue: */
+       regs->pc += AARCH64_INSN_SIZE;  /* skip BRK and resume */
+       return DBG_HOOK_HANDLED;
+}
+
+static struct break_hook bug_break_hook = {
+       .esr_val = 0xf2000000 | BUG_BRK_IMM,
+       .esr_mask = 0xffffffff,
+       .fn = bug_handler,
+};
+
+/*
+ * Initial handler for AArch64 BRK exceptions
+ * This handler only used until debug_traps_init().
+ */
+int __init early_brk64(unsigned long addr, unsigned int esr,
+               struct pt_regs *regs)
+{
+       return bug_handler(regs, esr) != DBG_HOOK_HANDLED;
 }
 
+/* This registration must happen early, before debug_traps_init(). */
 void __init trap_init(void)
 {
-       return;
+       register_break_hook(&bug_break_hook);
 }