mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-16 10:54:09 +08:00
09cffecaa7
Function show_regs() is usually called in interrupt handler or exception handler, it prints the registers specified by the parameter 'regs', then dump the stack traces. Although not explicitly documented, dump the stack traces based on'regs' seems to make the most sense. Although dump_stack() can finally dump the desired content, because 'regs' are saved by the entry of current interrupt or exception. In the following example we can see: 1) The backtrace of interrupt or exception handler is not expected, it causes confusion. 2) Something is printed repeatedly. The line with the kernel version "CPU: 0 PID: 70 Comm: test0 Not tainted 5.19.0+ #8", the registers saved in "Exception stack" which 'regs' actually point to. For example: rcu: INFO: rcu_sched self-detected stall on CPU rcu: 0-....: (499 ticks this GP) idle=379/1/0x40000002 softirq=91/91 fqs=249 (t=500 jiffies g=-911 q=13 ncpus=4) CPU: 0 PID: 70 Comm: test0 Not tainted 5.19.0+ #8 Hardware name: ARM-Versatile Express PC is at ktime_get+0x4c/0xe8 LR is at ktime_get+0x4c/0xe8 pc : 8019a474 lr : 8019a474 psr: 60000013 sp : cabd1f28 ip : 00000001 fp : 00000005 r10: 527bf1b8 r9 : 431bde82 r8 : d7b634db r7 : 0000156e r6 : 61f234f8 r5 : 00000001 r4 : 80ca86c0 r3 : ffffffff r2 : fe5bce0b r1 : 00000000 r0 : 01a431f4 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 6121406a DAC: 00000051 CPU: 0 PID: 70 Comm: test0 Not tainted 5.19.0+ #8 <-----------start---------- Hardware name: ARM-Versatile Express | unwind_backtrace from show_stack+0x10/0x14 | show_stack from dump_stack_lvl+0x40/0x4c | dump_stack_lvl from rcu_dump_cpu_stacks+0x10c/0x134 | rcu_dump_cpu_stacks from rcu_sched_clock_irq+0x780/0xaf4 | rcu_sched_clock_irq from update_process_times+0x54/0x74 | update_process_times from tick_periodic+0x3c/0xd4 | tick_periodic from tick_handle_periodic+0x20/0x80 worthless tick_handle_periodic from twd_handler+0x30/0x40 or twd_handler from handle_percpu_devid_irq+0x8c/0x1c8 duplicated handle_percpu_devid_irq from generic_handle_domain_irq+0x24/0x34 | generic_handle_domain_irq from gic_handle_irq+0x74/0x88 | gic_handle_irq from generic_handle_arch_irq+0x34/0x44 | generic_handle_arch_irq from call_with_stack+0x18/0x20 | call_with_stack from __irq_svc+0x98/0xb0 | Exception stack(0xcabd1ed8 to 0xcabd1f20) | 1ec0: 01a431f4 00000000 | 1ee0: fe5bce0b ffffffff 80ca86c0 00000001 61f234f8 0000156e d7b634db 431bde82 | 1f00: 527bf1b8 00000005 00000001 cabd1f28 8019a474 8019a474 60000013 ffffffff | __irq_svc from ktime_get+0x4c/0xe8 <---------end-------------- ktime_get from test_task+0x44/0x110 test_task from kthread+0xd8/0xf4 kthread from ret_from_fork+0x14/0x2c Exception stack(0xcabd1fb0 to 0xcabd1ff8) 1fa0: 00000000 00000000 00000000 00000000 1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 1fe0: 00000000 00000000 00000000 00000000 00000013 00000000 After replacing dump_stack() with dump_backtrace(): rcu: INFO: rcu_sched self-detected stall on CPU rcu: 0-....: (500 ticks this GP) idle=8f7/1/0x40000002 softirq=129/129 fqs=241 (t=500 jiffies g=-915 q=13 ncpus=4) CPU: 0 PID: 69 Comm: test0 Not tainted 5.19.0+ #9 Hardware name: ARM-Versatile Express PC is at ktime_get+0x4c/0xe8 LR is at ktime_get+0x4c/0xe8 pc : 8019a494 lr : 8019a494 psr: 60000013 sp : cabddf28 ip : 00000001 fp : 00000002 r10: 0779cb48 r9 : 431bde82 r8 : d7b634db r7 : 00000a66 r6 : e835ab70 r5 : 00000001 r4 : 80ca86c0 r3 : ffffffff r2 : ff337d39 r1 : 00000000 r0 : 00cc82c6 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 611d006a DAC: 00000051 ktime_get from test_task+0x44/0x110 test_task from kthread+0xd8/0xf4 kthread from ret_from_fork+0x14/0x2c Exception stack(0xcabddfb0 to 0xcabddff8) dfa0: 00000000 00000000 00000000 00000000 dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 dfe0: 00000000 00000000 00000000 00000000 00000013 00000000 Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com> Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
964 lines
24 KiB
C
964 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/arch/arm/kernel/traps.c
|
|
*
|
|
* Copyright (C) 1995-2009 Russell King
|
|
* Fragments that appear the same as linux/arch/i386/kernel/traps.c (C) Linus Torvalds
|
|
*
|
|
* 'traps.c' handles hardware exceptions after we have saved some state in
|
|
* 'linux/arch/arm/lib/traps.S'. Mostly a debugging aid, but will probably
|
|
* kill the offending process.
|
|
*/
|
|
#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/kprobes.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kexec.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/irq.h>
|
|
|
|
#include <linux/atomic.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/exception.h>
|
|
#include <asm/spectre.h>
|
|
#include <asm/unistd.h>
|
|
#include <asm/traps.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/unwind.h>
|
|
#include <asm/tls.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/system_misc.h>
|
|
#include <asm/opcodes.h>
|
|
|
|
|
|
static const char *handler[]= {
|
|
"prefetch abort",
|
|
"data abort",
|
|
"address exception",
|
|
"interrupt",
|
|
"undefined instruction",
|
|
};
|
|
|
|
void *vectors_page;
|
|
|
|
#ifdef CONFIG_DEBUG_USER
|
|
unsigned int user_debug;
|
|
|
|
static int __init user_debug_setup(char *str)
|
|
{
|
|
get_option(&str, &user_debug);
|
|
return 1;
|
|
}
|
|
__setup("user_debug=", user_debug_setup);
|
|
#endif
|
|
|
|
void dump_backtrace_entry(unsigned long where, unsigned long from,
|
|
unsigned long frame, const char *loglvl)
|
|
{
|
|
unsigned long end = frame + 4 + sizeof(struct pt_regs);
|
|
|
|
if (IS_ENABLED(CONFIG_UNWINDER_FRAME_POINTER) &&
|
|
IS_ENABLED(CONFIG_CC_IS_GCC) &&
|
|
end > ALIGN(frame, THREAD_SIZE)) {
|
|
/*
|
|
* If we are walking past the end of the stack, it may be due
|
|
* to the fact that we are on an IRQ or overflow stack. In this
|
|
* case, we can load the address of the other stack from the
|
|
* frame record.
|
|
*/
|
|
frame = ((unsigned long *)frame)[-2] - 4;
|
|
end = frame + 4 + sizeof(struct pt_regs);
|
|
}
|
|
|
|
#ifndef CONFIG_KALLSYMS
|
|
printk("%sFunction entered at [<%08lx>] from [<%08lx>]\n",
|
|
loglvl, where, from);
|
|
#elif defined CONFIG_BACKTRACE_VERBOSE
|
|
printk("%s[<%08lx>] (%ps) from [<%08lx>] (%pS)\n",
|
|
loglvl, where, (void *)where, from, (void *)from);
|
|
#else
|
|
printk("%s %ps from %pS\n", loglvl, (void *)where, (void *)from);
|
|
#endif
|
|
|
|
if (in_entry_text(from) && end <= ALIGN(frame, THREAD_SIZE))
|
|
dump_mem(loglvl, "Exception stack", frame + 4, end);
|
|
}
|
|
|
|
void dump_backtrace_stm(u32 *stack, u32 instruction, const char *loglvl)
|
|
{
|
|
char str[80], *p;
|
|
unsigned int x;
|
|
int reg;
|
|
|
|
for (reg = 10, x = 0, p = str; reg >= 0; reg--) {
|
|
if (instruction & BIT(reg)) {
|
|
p += sprintf(p, " r%d:%08x", reg, *stack--);
|
|
if (++x == 6) {
|
|
x = 0;
|
|
p = str;
|
|
printk("%s%s\n", loglvl, str);
|
|
}
|
|
}
|
|
}
|
|
if (p != str)
|
|
printk("%s%s\n", loglvl, str);
|
|
}
|
|
|
|
#ifndef CONFIG_ARM_UNWIND
|
|
/*
|
|
* Stack pointers should always be within the kernels view of
|
|
* physical memory. If it is not there, then we can't dump
|
|
* out any information relating to the stack.
|
|
*/
|
|
static int verify_stack(unsigned long sp)
|
|
{
|
|
if (sp < PAGE_OFFSET ||
|
|
(!IS_ENABLED(CONFIG_VMAP_STACK) &&
|
|
sp > (unsigned long)high_memory && high_memory != NULL))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Dump out the contents of some memory nicely...
|
|
*/
|
|
void dump_mem(const char *lvl, const char *str, unsigned long bottom,
|
|
unsigned long top)
|
|
{
|
|
unsigned long first;
|
|
int i;
|
|
|
|
printk("%s%s(0x%08lx to 0x%08lx)\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 long val;
|
|
if (!get_kernel_nofault(val, (unsigned long *)p))
|
|
sprintf(str + i * 9, " %08lx", val);
|
|
else
|
|
sprintf(str + i * 9, " ????????");
|
|
}
|
|
}
|
|
printk("%s%04lx:%s\n", lvl, first & 0xffff, str);
|
|
}
|
|
}
|
|
|
|
static void dump_instr(const char *lvl, struct pt_regs *regs)
|
|
{
|
|
unsigned long addr = instruction_pointer(regs);
|
|
const int thumb = thumb_mode(regs);
|
|
const int width = thumb ? 4 : 8;
|
|
char str[sizeof("00000000 ") * 5 + 2 + 1], *p = str;
|
|
int i;
|
|
|
|
/*
|
|
* Note that we now dump the code first, just in case the backtrace
|
|
* kills us.
|
|
*/
|
|
|
|
for (i = -4; i < 1 + !!thumb; i++) {
|
|
unsigned int val, bad;
|
|
|
|
if (!user_mode(regs)) {
|
|
if (thumb) {
|
|
u16 val16;
|
|
bad = get_kernel_nofault(val16, &((u16 *)addr)[i]);
|
|
val = val16;
|
|
} else {
|
|
bad = get_kernel_nofault(val, &((u32 *)addr)[i]);
|
|
}
|
|
} else {
|
|
if (thumb)
|
|
bad = get_user(val, &((u16 *)addr)[i]);
|
|
else
|
|
bad = get_user(val, &((u32 *)addr)[i]);
|
|
}
|
|
|
|
if (!bad)
|
|
p += sprintf(p, i == 0 ? "(%0*x) " : "%0*x ",
|
|
width, val);
|
|
else {
|
|
p += sprintf(p, "bad PC value");
|
|
break;
|
|
}
|
|
}
|
|
printk("%sCode: %s\n", lvl, str);
|
|
}
|
|
|
|
#ifdef CONFIG_ARM_UNWIND
|
|
void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk,
|
|
const char *loglvl)
|
|
{
|
|
unwind_backtrace(regs, tsk, loglvl);
|
|
}
|
|
#else
|
|
void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk,
|
|
const char *loglvl)
|
|
{
|
|
unsigned int fp, mode;
|
|
int ok = 1;
|
|
|
|
printk("%sBacktrace: ", loglvl);
|
|
|
|
if (!tsk)
|
|
tsk = current;
|
|
|
|
if (regs) {
|
|
fp = frame_pointer(regs);
|
|
mode = processor_mode(regs);
|
|
} else if (tsk != current) {
|
|
fp = thread_saved_fp(tsk);
|
|
mode = 0x10;
|
|
} else {
|
|
asm("mov %0, fp" : "=r" (fp) : : "cc");
|
|
mode = 0x10;
|
|
}
|
|
|
|
if (!fp) {
|
|
pr_cont("no frame pointer");
|
|
ok = 0;
|
|
} else if (verify_stack(fp)) {
|
|
pr_cont("invalid frame pointer 0x%08x", fp);
|
|
ok = 0;
|
|
} else if (fp < (unsigned long)end_of_stack(tsk))
|
|
pr_cont("frame pointer underflow");
|
|
pr_cont("\n");
|
|
|
|
if (ok)
|
|
c_backtrace(fp, mode, loglvl);
|
|
}
|
|
#endif
|
|
|
|
void show_stack(struct task_struct *tsk, unsigned long *sp, const char *loglvl)
|
|
{
|
|
dump_backtrace(NULL, tsk, loglvl);
|
|
barrier();
|
|
}
|
|
|
|
#ifdef CONFIG_PREEMPT
|
|
#define S_PREEMPT " PREEMPT"
|
|
#elif defined(CONFIG_PREEMPT_RT)
|
|
#define S_PREEMPT " PREEMPT_RT"
|
|
#else
|
|
#define S_PREEMPT ""
|
|
#endif
|
|
#ifdef CONFIG_SMP
|
|
#define S_SMP " SMP"
|
|
#else
|
|
#define S_SMP ""
|
|
#endif
|
|
#ifdef CONFIG_THUMB2_KERNEL
|
|
#define S_ISA " THUMB2"
|
|
#else
|
|
#define S_ISA " ARM"
|
|
#endif
|
|
|
|
static int __die(const char *str, int err, struct pt_regs *regs)
|
|
{
|
|
struct task_struct *tsk = current;
|
|
static int die_counter;
|
|
int ret;
|
|
|
|
pr_emerg("Internal error: %s: %x [#%d]" S_PREEMPT S_SMP S_ISA "\n",
|
|
str, err, ++die_counter);
|
|
|
|
/* trap and error numbers are mostly meaningless on ARM */
|
|
ret = notify_die(DIE_OOPS, str, regs, err, tsk->thread.trap_no, SIGSEGV);
|
|
if (ret == NOTIFY_STOP)
|
|
return 1;
|
|
|
|
print_modules();
|
|
__show_regs(regs);
|
|
__show_regs_alloc_free(regs);
|
|
pr_emerg("Process %.*s (pid: %d, stack limit = 0x%p)\n",
|
|
TASK_COMM_LEN, tsk->comm, task_pid_nr(tsk), end_of_stack(tsk));
|
|
|
|
if (!user_mode(regs) || in_interrupt()) {
|
|
dump_mem(KERN_EMERG, "Stack: ", regs->ARM_sp,
|
|
ALIGN(regs->ARM_sp - THREAD_SIZE, THREAD_ALIGN)
|
|
+ THREAD_SIZE);
|
|
dump_backtrace(regs, tsk, KERN_EMERG);
|
|
dump_instr(KERN_EMERG, regs);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static arch_spinlock_t die_lock = __ARCH_SPIN_LOCK_UNLOCKED;
|
|
static int die_owner = -1;
|
|
static unsigned int die_nest_count;
|
|
|
|
static unsigned long oops_begin(void)
|
|
{
|
|
int cpu;
|
|
unsigned long flags;
|
|
|
|
oops_enter();
|
|
|
|
/* racy, but better than risking deadlock. */
|
|
raw_local_irq_save(flags);
|
|
cpu = smp_processor_id();
|
|
if (!arch_spin_trylock(&die_lock)) {
|
|
if (cpu == die_owner)
|
|
/* nested oops. should stop eventually */;
|
|
else
|
|
arch_spin_lock(&die_lock);
|
|
}
|
|
die_nest_count++;
|
|
die_owner = cpu;
|
|
console_verbose();
|
|
bust_spinlocks(1);
|
|
return flags;
|
|
}
|
|
|
|
static void oops_end(unsigned long flags, struct pt_regs *regs, int signr)
|
|
{
|
|
if (regs && kexec_should_crash(current))
|
|
crash_kexec(regs);
|
|
|
|
bust_spinlocks(0);
|
|
die_owner = -1;
|
|
add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);
|
|
die_nest_count--;
|
|
if (!die_nest_count)
|
|
/* Nest count reaches zero, release the lock. */
|
|
arch_spin_unlock(&die_lock);
|
|
raw_local_irq_restore(flags);
|
|
oops_exit();
|
|
|
|
if (in_interrupt())
|
|
panic("Fatal exception in interrupt");
|
|
if (panic_on_oops)
|
|
panic("Fatal exception");
|
|
if (signr)
|
|
make_task_dead(signr);
|
|
}
|
|
|
|
/*
|
|
* This function is protected against re-entrancy.
|
|
*/
|
|
void die(const char *str, struct pt_regs *regs, int err)
|
|
{
|
|
enum bug_trap_type bug_type = BUG_TRAP_TYPE_NONE;
|
|
unsigned long flags = oops_begin();
|
|
int sig = SIGSEGV;
|
|
|
|
if (!user_mode(regs))
|
|
bug_type = report_bug(regs->ARM_pc, regs);
|
|
if (bug_type != BUG_TRAP_TYPE_NONE)
|
|
str = "Oops - BUG";
|
|
|
|
if (__die(str, err, regs))
|
|
sig = 0;
|
|
|
|
oops_end(flags, regs, sig);
|
|
}
|
|
|
|
void arm_notify_die(const char *str, struct pt_regs *regs,
|
|
int signo, int si_code, void __user *addr,
|
|
unsigned long err, unsigned long trap)
|
|
{
|
|
if (user_mode(regs)) {
|
|
current->thread.error_code = err;
|
|
current->thread.trap_no = trap;
|
|
|
|
force_sig_fault(signo, si_code, addr);
|
|
} else {
|
|
die(str, regs, err);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_GENERIC_BUG
|
|
|
|
int is_valid_bugaddr(unsigned long pc)
|
|
{
|
|
#ifdef CONFIG_THUMB2_KERNEL
|
|
u16 bkpt;
|
|
u16 insn = __opcode_to_mem_thumb16(BUG_INSTR_VALUE);
|
|
#else
|
|
u32 bkpt;
|
|
u32 insn = __opcode_to_mem_arm(BUG_INSTR_VALUE);
|
|
#endif
|
|
|
|
if (get_kernel_nofault(bkpt, (void *)pc))
|
|
return 0;
|
|
|
|
return bkpt == insn;
|
|
}
|
|
|
|
#endif
|
|
|
|
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 nokprobe_inline
|
|
int call_undef_hook(struct pt_regs *regs, unsigned int instr)
|
|
{
|
|
struct undef_hook *hook;
|
|
unsigned long flags;
|
|
int (*fn)(struct pt_regs *regs, unsigned int instr) = NULL;
|
|
|
|
raw_spin_lock_irqsave(&undef_lock, flags);
|
|
list_for_each_entry(hook, &undef_hook, node)
|
|
if ((instr & hook->instr_mask) == hook->instr_val &&
|
|
(regs->ARM_cpsr & hook->cpsr_mask) == hook->cpsr_val)
|
|
fn = hook->fn;
|
|
raw_spin_unlock_irqrestore(&undef_lock, flags);
|
|
|
|
return fn ? fn(regs, instr) : 1;
|
|
}
|
|
|
|
asmlinkage void do_undefinstr(struct pt_regs *regs)
|
|
{
|
|
unsigned int instr;
|
|
void __user *pc;
|
|
|
|
pc = (void __user *)instruction_pointer(regs);
|
|
|
|
if (processor_mode(regs) == SVC_MODE) {
|
|
#ifdef CONFIG_THUMB2_KERNEL
|
|
if (thumb_mode(regs)) {
|
|
instr = __mem_to_opcode_thumb16(((u16 *)pc)[0]);
|
|
if (is_wide_instruction(instr)) {
|
|
u16 inst2;
|
|
inst2 = __mem_to_opcode_thumb16(((u16 *)pc)[1]);
|
|
instr = __opcode_thumb32_compose(instr, inst2);
|
|
}
|
|
} else
|
|
#endif
|
|
instr = __mem_to_opcode_arm(*(u32 *) pc);
|
|
} else if (thumb_mode(regs)) {
|
|
if (get_user(instr, (u16 __user *)pc))
|
|
goto die_sig;
|
|
instr = __mem_to_opcode_thumb16(instr);
|
|
if (is_wide_instruction(instr)) {
|
|
unsigned int instr2;
|
|
if (get_user(instr2, (u16 __user *)pc+1))
|
|
goto die_sig;
|
|
instr2 = __mem_to_opcode_thumb16(instr2);
|
|
instr = __opcode_thumb32_compose(instr, instr2);
|
|
}
|
|
} else {
|
|
if (get_user(instr, (u32 __user *)pc))
|
|
goto die_sig;
|
|
instr = __mem_to_opcode_arm(instr);
|
|
}
|
|
|
|
if (call_undef_hook(regs, instr) == 0)
|
|
return;
|
|
|
|
die_sig:
|
|
#ifdef CONFIG_DEBUG_USER
|
|
if (user_debug & UDBG_UNDEFINED) {
|
|
pr_info("%s (%d): undefined instruction: pc=%px\n",
|
|
current->comm, task_pid_nr(current), pc);
|
|
__show_regs(regs);
|
|
dump_instr(KERN_INFO, regs);
|
|
}
|
|
#endif
|
|
arm_notify_die("Oops - undefined instruction", regs,
|
|
SIGILL, ILL_ILLOPC, pc, 0, 6);
|
|
}
|
|
NOKPROBE_SYMBOL(do_undefinstr)
|
|
|
|
/*
|
|
* Handle FIQ similarly to NMI on x86 systems.
|
|
*
|
|
* The runtime environment for NMIs is extremely restrictive
|
|
* (NMIs can pre-empt critical sections meaning almost all locking is
|
|
* forbidden) meaning this default FIQ handling must only be used in
|
|
* circumstances where non-maskability improves robustness, such as
|
|
* watchdog or debug logic.
|
|
*
|
|
* This handler is not appropriate for general purpose use in drivers
|
|
* platform code and can be overrideen using set_fiq_handler.
|
|
*/
|
|
asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *old_regs = set_irq_regs(regs);
|
|
|
|
nmi_enter();
|
|
|
|
/* nop. FIQ handlers for special arch/arm features can be added here. */
|
|
|
|
nmi_exit();
|
|
|
|
set_irq_regs(old_regs);
|
|
}
|
|
|
|
/*
|
|
* bad_mode handles the impossible case in the vectors. If you see one of
|
|
* these, then it's extremely serious, and could mean you have buggy hardware.
|
|
* It never returns, and never tries to sync. We hope that we can at least
|
|
* dump out some state information...
|
|
*/
|
|
asmlinkage void bad_mode(struct pt_regs *regs, int reason)
|
|
{
|
|
console_verbose();
|
|
|
|
pr_crit("Bad mode in %s handler detected\n", handler[reason]);
|
|
|
|
die("Oops - bad mode", regs, 0);
|
|
local_irq_disable();
|
|
panic("bad mode");
|
|
}
|
|
|
|
static int bad_syscall(int n, struct pt_regs *regs)
|
|
{
|
|
if ((current->personality & PER_MASK) != PER_LINUX) {
|
|
send_sig(SIGSEGV, current, 1);
|
|
return regs->ARM_r0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_USER
|
|
if (user_debug & UDBG_SYSCALL) {
|
|
pr_err("[%d] %s: obsolete system call %08x.\n",
|
|
task_pid_nr(current), current->comm, n);
|
|
dump_instr(KERN_ERR, regs);
|
|
}
|
|
#endif
|
|
|
|
arm_notify_die("Oops - bad syscall", regs, SIGILL, ILL_ILLTRP,
|
|
(void __user *)instruction_pointer(regs) -
|
|
(thumb_mode(regs) ? 2 : 4),
|
|
n, 0);
|
|
|
|
return regs->ARM_r0;
|
|
}
|
|
|
|
static inline int
|
|
__do_cache_op(unsigned long start, unsigned long end)
|
|
{
|
|
int ret;
|
|
|
|
do {
|
|
unsigned long chunk = min(PAGE_SIZE, end - start);
|
|
|
|
if (fatal_signal_pending(current))
|
|
return 0;
|
|
|
|
ret = flush_icache_user_range(start, start + chunk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
cond_resched();
|
|
start += chunk;
|
|
} while (start < end);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int
|
|
do_cache_op(unsigned long start, unsigned long end, int flags)
|
|
{
|
|
if (end < start || flags)
|
|
return -EINVAL;
|
|
|
|
if (!access_ok((void __user *)start, end - start))
|
|
return -EFAULT;
|
|
|
|
return __do_cache_op(start, end);
|
|
}
|
|
|
|
/*
|
|
* Handle all unrecognised system calls.
|
|
* 0x9f0000 - 0x9fffff are some more esoteric system calls
|
|
*/
|
|
#define NR(x) ((__ARM_NR_##x) - __ARM_NR_BASE)
|
|
asmlinkage int arm_syscall(int no, struct pt_regs *regs)
|
|
{
|
|
if ((no >> 16) != (__ARM_NR_BASE>> 16))
|
|
return bad_syscall(no, regs);
|
|
|
|
switch (no & 0xffff) {
|
|
case 0: /* branch through 0 */
|
|
arm_notify_die("branch through zero", regs,
|
|
SIGSEGV, SEGV_MAPERR, NULL, 0, 0);
|
|
return 0;
|
|
|
|
case NR(breakpoint): /* SWI BREAK_POINT */
|
|
regs->ARM_pc -= thumb_mode(regs) ? 2 : 4;
|
|
ptrace_break(regs);
|
|
return regs->ARM_r0;
|
|
|
|
/*
|
|
* Flush a region from virtual address 'r0' to virtual address 'r1'
|
|
* _exclusive_. There is no alignment requirement on either address;
|
|
* user space does not need to know the hardware cache layout.
|
|
*
|
|
* r2 contains flags. It should ALWAYS be passed as ZERO until it
|
|
* is defined to be something else. For now we ignore it, but may
|
|
* the fires of hell burn in your belly if you break this rule. ;)
|
|
*
|
|
* (at a later date, we may want to allow this call to not flush
|
|
* various aspects of the cache. Passing '0' will guarantee that
|
|
* everything necessary gets flushed to maintain consistency in
|
|
* the specified region).
|
|
*/
|
|
case NR(cacheflush):
|
|
return do_cache_op(regs->ARM_r0, regs->ARM_r1, regs->ARM_r2);
|
|
|
|
case NR(usr26):
|
|
if (!(elf_hwcap & HWCAP_26BIT))
|
|
break;
|
|
regs->ARM_cpsr &= ~MODE32_BIT;
|
|
return regs->ARM_r0;
|
|
|
|
case NR(usr32):
|
|
if (!(elf_hwcap & HWCAP_26BIT))
|
|
break;
|
|
regs->ARM_cpsr |= MODE32_BIT;
|
|
return regs->ARM_r0;
|
|
|
|
case NR(set_tls):
|
|
set_tls(regs->ARM_r0);
|
|
return 0;
|
|
|
|
case NR(get_tls):
|
|
return current_thread_info()->tp_value[0];
|
|
|
|
default:
|
|
/* Calls 9f00xx..9f07ff are defined to return -ENOSYS
|
|
if not implemented, rather than raising SIGILL. This
|
|
way the calling program can gracefully determine whether
|
|
a feature is supported. */
|
|
if ((no & 0xffff) <= 0x7ff)
|
|
return -ENOSYS;
|
|
break;
|
|
}
|
|
#ifdef CONFIG_DEBUG_USER
|
|
/*
|
|
* experience shows that these seem to indicate that
|
|
* something catastrophic has happened
|
|
*/
|
|
if (user_debug & UDBG_SYSCALL) {
|
|
pr_err("[%d] %s: arm syscall %d\n",
|
|
task_pid_nr(current), current->comm, no);
|
|
dump_instr(KERN_ERR, regs);
|
|
if (user_mode(regs)) {
|
|
__show_regs(regs);
|
|
c_backtrace(frame_pointer(regs), processor_mode(regs), KERN_ERR);
|
|
}
|
|
}
|
|
#endif
|
|
arm_notify_die("Oops - bad syscall(2)", regs, SIGILL, ILL_ILLTRP,
|
|
(void __user *)instruction_pointer(regs) -
|
|
(thumb_mode(regs) ? 2 : 4),
|
|
no, 0);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_TLS_REG_EMUL
|
|
|
|
/*
|
|
* We might be running on an ARMv6+ processor which should have the TLS
|
|
* register but for some reason we can't use it, or maybe an SMP system
|
|
* using a pre-ARMv6 processor (there are apparently a few prototypes like
|
|
* that in existence) and therefore access to that register must be
|
|
* emulated.
|
|
*/
|
|
|
|
static int get_tp_trap(struct pt_regs *regs, unsigned int instr)
|
|
{
|
|
int reg = (instr >> 12) & 15;
|
|
if (reg == 15)
|
|
return 1;
|
|
regs->uregs[reg] = current_thread_info()->tp_value[0];
|
|
regs->ARM_pc += 4;
|
|
return 0;
|
|
}
|
|
|
|
static struct undef_hook arm_mrc_hook = {
|
|
.instr_mask = 0x0fff0fff,
|
|
.instr_val = 0x0e1d0f70,
|
|
.cpsr_mask = PSR_T_BIT,
|
|
.cpsr_val = 0,
|
|
.fn = get_tp_trap,
|
|
};
|
|
|
|
static int __init arm_mrc_hook_init(void)
|
|
{
|
|
register_undef_hook(&arm_mrc_hook);
|
|
return 0;
|
|
}
|
|
|
|
late_initcall(arm_mrc_hook_init);
|
|
|
|
#endif
|
|
|
|
/*
|
|
* A data abort trap was taken, but we did not handle the instruction.
|
|
* Try to abort the user program, or panic if it was the kernel.
|
|
*/
|
|
asmlinkage void
|
|
baddataabort(int code, unsigned long instr, struct pt_regs *regs)
|
|
{
|
|
unsigned long addr = instruction_pointer(regs);
|
|
|
|
#ifdef CONFIG_DEBUG_USER
|
|
if (user_debug & UDBG_BADABORT) {
|
|
pr_err("8<--- cut here ---\n");
|
|
pr_err("[%d] %s: bad data abort: code %d instr 0x%08lx\n",
|
|
task_pid_nr(current), current->comm, code, instr);
|
|
dump_instr(KERN_ERR, regs);
|
|
show_pte(KERN_ERR, current->mm, addr);
|
|
}
|
|
#endif
|
|
|
|
arm_notify_die("unknown data abort code", regs,
|
|
SIGILL, ILL_ILLOPC, (void __user *)addr, instr, 0);
|
|
}
|
|
|
|
void __readwrite_bug(const char *fn)
|
|
{
|
|
pr_err("%s called, but not implemented\n", fn);
|
|
BUG();
|
|
}
|
|
EXPORT_SYMBOL(__readwrite_bug);
|
|
|
|
void __pte_error(const char *file, int line, pte_t pte)
|
|
{
|
|
pr_err("%s:%d: bad pte %08llx.\n", file, line, (long long)pte_val(pte));
|
|
}
|
|
|
|
void __pmd_error(const char *file, int line, pmd_t pmd)
|
|
{
|
|
pr_err("%s:%d: bad pmd %08llx.\n", file, line, (long long)pmd_val(pmd));
|
|
}
|
|
|
|
void __pgd_error(const char *file, int line, pgd_t pgd)
|
|
{
|
|
pr_err("%s:%d: bad pgd %08llx.\n", file, line, (long long)pgd_val(pgd));
|
|
}
|
|
|
|
asmlinkage void __div0(void)
|
|
{
|
|
pr_err("Division by zero in kernel.\n");
|
|
dump_stack();
|
|
}
|
|
EXPORT_SYMBOL(__div0);
|
|
|
|
void abort(void)
|
|
{
|
|
BUG();
|
|
|
|
/* if that doesn't kill us, halt */
|
|
panic("Oops failed to kill thread");
|
|
}
|
|
|
|
#ifdef CONFIG_KUSER_HELPERS
|
|
static void __init kuser_init(void *vectors)
|
|
{
|
|
extern char __kuser_helper_start[], __kuser_helper_end[];
|
|
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
|
|
|
|
memcpy(vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
|
|
|
|
/*
|
|
* vectors + 0xfe0 = __kuser_get_tls
|
|
* vectors + 0xfe8 = hardware TLS instruction at 0xffff0fe8
|
|
*/
|
|
if (tls_emu || has_tls_reg)
|
|
memcpy(vectors + 0xfe0, vectors + 0xfe8, 4);
|
|
}
|
|
#else
|
|
static inline void __init kuser_init(void *vectors)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#ifndef CONFIG_CPU_V7M
|
|
static void copy_from_lma(void *vma, void *lma_start, void *lma_end)
|
|
{
|
|
memcpy(vma, lma_start, lma_end - lma_start);
|
|
}
|
|
|
|
static void flush_vectors(void *vma, size_t offset, size_t size)
|
|
{
|
|
unsigned long start = (unsigned long)vma + offset;
|
|
unsigned long end = start + size;
|
|
|
|
flush_icache_range(start, end);
|
|
}
|
|
|
|
#ifdef CONFIG_HARDEN_BRANCH_HISTORY
|
|
int spectre_bhb_update_vectors(unsigned int method)
|
|
{
|
|
extern char __vectors_bhb_bpiall_start[], __vectors_bhb_bpiall_end[];
|
|
extern char __vectors_bhb_loop8_start[], __vectors_bhb_loop8_end[];
|
|
void *vec_start, *vec_end;
|
|
|
|
if (system_state >= SYSTEM_FREEING_INITMEM) {
|
|
pr_err("CPU%u: Spectre BHB workaround too late - system vulnerable\n",
|
|
smp_processor_id());
|
|
return SPECTRE_VULNERABLE;
|
|
}
|
|
|
|
switch (method) {
|
|
case SPECTRE_V2_METHOD_LOOP8:
|
|
vec_start = __vectors_bhb_loop8_start;
|
|
vec_end = __vectors_bhb_loop8_end;
|
|
break;
|
|
|
|
case SPECTRE_V2_METHOD_BPIALL:
|
|
vec_start = __vectors_bhb_bpiall_start;
|
|
vec_end = __vectors_bhb_bpiall_end;
|
|
break;
|
|
|
|
default:
|
|
pr_err("CPU%u: unknown Spectre BHB state %d\n",
|
|
smp_processor_id(), method);
|
|
return SPECTRE_VULNERABLE;
|
|
}
|
|
|
|
copy_from_lma(vectors_page, vec_start, vec_end);
|
|
flush_vectors(vectors_page, 0, vec_end - vec_start);
|
|
|
|
return SPECTRE_MITIGATED;
|
|
}
|
|
#endif
|
|
|
|
void __init early_trap_init(void *vectors_base)
|
|
{
|
|
extern char __stubs_start[], __stubs_end[];
|
|
extern char __vectors_start[], __vectors_end[];
|
|
unsigned i;
|
|
|
|
vectors_page = vectors_base;
|
|
|
|
/*
|
|
* Poison the vectors page with an undefined instruction. This
|
|
* instruction is chosen to be undefined for both ARM and Thumb
|
|
* ISAs. The Thumb version is an undefined instruction with a
|
|
* branch back to the undefined instruction.
|
|
*/
|
|
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
|
|
((u32 *)vectors_base)[i] = 0xe7fddef1;
|
|
|
|
/*
|
|
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
|
|
* into the vector page, mapped at 0xffff0000, and ensure these
|
|
* are visible to the instruction stream.
|
|
*/
|
|
copy_from_lma(vectors_base, __vectors_start, __vectors_end);
|
|
copy_from_lma(vectors_base + 0x1000, __stubs_start, __stubs_end);
|
|
|
|
kuser_init(vectors_base);
|
|
|
|
flush_vectors(vectors_base, 0, PAGE_SIZE * 2);
|
|
}
|
|
#else /* ifndef CONFIG_CPU_V7M */
|
|
void __init early_trap_init(void *vectors_base)
|
|
{
|
|
/*
|
|
* on V7-M there is no need to copy the vector table to a dedicated
|
|
* memory area. The address is configurable and so a table in the kernel
|
|
* image can be used.
|
|
*/
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_VMAP_STACK
|
|
|
|
DECLARE_PER_CPU(u8 *, irq_stack_ptr);
|
|
|
|
asmlinkage DEFINE_PER_CPU(u8 *, overflow_stack_ptr);
|
|
|
|
static int __init allocate_overflow_stacks(void)
|
|
{
|
|
u8 *stack;
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
stack = (u8 *)__get_free_page(GFP_KERNEL);
|
|
if (WARN_ON(!stack))
|
|
return -ENOMEM;
|
|
per_cpu(overflow_stack_ptr, cpu) = &stack[OVERFLOW_STACK_SIZE];
|
|
}
|
|
return 0;
|
|
}
|
|
early_initcall(allocate_overflow_stacks);
|
|
|
|
asmlinkage void handle_bad_stack(struct pt_regs *regs)
|
|
{
|
|
unsigned long tsk_stk = (unsigned long)current->stack;
|
|
#ifdef CONFIG_IRQSTACKS
|
|
unsigned long irq_stk = (unsigned long)raw_cpu_read(irq_stack_ptr);
|
|
#endif
|
|
unsigned long ovf_stk = (unsigned long)raw_cpu_read(overflow_stack_ptr);
|
|
|
|
console_verbose();
|
|
pr_emerg("Insufficient stack space to handle exception!");
|
|
|
|
pr_emerg("Task stack: [0x%08lx..0x%08lx]\n",
|
|
tsk_stk, tsk_stk + THREAD_SIZE);
|
|
#ifdef CONFIG_IRQSTACKS
|
|
pr_emerg("IRQ stack: [0x%08lx..0x%08lx]\n",
|
|
irq_stk - THREAD_SIZE, irq_stk);
|
|
#endif
|
|
pr_emerg("Overflow stack: [0x%08lx..0x%08lx]\n",
|
|
ovf_stk - OVERFLOW_STACK_SIZE, ovf_stk);
|
|
|
|
die("kernel stack overflow", regs, 0);
|
|
}
|
|
|
|
#ifndef CONFIG_ARM_LPAE
|
|
/*
|
|
* Normally, we rely on the logic in do_translation_fault() to update stale PMD
|
|
* entries covering the vmalloc space in a task's page tables when it first
|
|
* accesses the region in question. Unfortunately, this is not sufficient when
|
|
* the task stack resides in the vmalloc region, as do_translation_fault() is a
|
|
* C function that needs a stack to run.
|
|
*
|
|
* So we need to ensure that these PMD entries are up to date *before* the MM
|
|
* switch. As we already have some logic in the MM switch path that takes care
|
|
* of this, let's trigger it by bumping the counter every time the core vmalloc
|
|
* code modifies a PMD entry in the vmalloc region. Use release semantics on
|
|
* the store so that other CPUs observing the counter's new value are
|
|
* guaranteed to see the updated page table entries as well.
|
|
*/
|
|
void arch_sync_kernel_mappings(unsigned long start, unsigned long end)
|
|
{
|
|
if (start < VMALLOC_END && end > VMALLOC_START)
|
|
atomic_inc_return_release(&init_mm.context.vmalloc_seq);
|
|
}
|
|
#endif
|
|
#endif
|