
[ Upstream commit 130aee3fd9981297ff9354e5d5609cd59aafbbea ]
While working on something else, I noticed that the kernel would start
accepting interrupts again after crashing in an interrupt handler. Since
the kernel is already in inconsistent state, enabling interrupts is
dangerous and opens up risk of kernel state deteriorating further.
Interrupts do get enabled via what looks like an unintended side effect of
spin_unlock_irq, so switch to the more cautious
spin_lock_irqsave/spin_unlock_irqrestore instead.
Fixes: 76d2a0493a
("RISC-V: Init and Halt Code")
Signed-off-by: Mattias Nissler <mnissler@rivosinc.com>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
Link: https://lore.kernel.org/r/20230215144828.3370316-1-mnissler@rivosinc.com
Cc: stable@vger.kernel.org
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
189 lines
4.8 KiB
C
189 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2012 Regents of the University of California
|
|
*/
|
|
|
|
#include <linux/cpu.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/kexec.h>
|
|
|
|
#include <asm/processor.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/csr.h>
|
|
|
|
int show_unhandled_signals = 1;
|
|
|
|
extern asmlinkage void handle_exception(void);
|
|
|
|
static DEFINE_SPINLOCK(die_lock);
|
|
|
|
void die(struct pt_regs *regs, const char *str)
|
|
{
|
|
static int die_counter;
|
|
int ret;
|
|
long cause;
|
|
unsigned long flags;
|
|
|
|
oops_enter();
|
|
|
|
spin_lock_irqsave(&die_lock, flags);
|
|
console_verbose();
|
|
bust_spinlocks(1);
|
|
|
|
pr_emerg("%s [#%d]\n", str, ++die_counter);
|
|
print_modules();
|
|
if (regs)
|
|
show_regs(regs);
|
|
|
|
cause = regs ? regs->cause : -1;
|
|
ret = notify_die(DIE_OOPS, str, regs, 0, cause, SIGSEGV);
|
|
|
|
if (kexec_should_crash(current))
|
|
crash_kexec(regs);
|
|
|
|
bust_spinlocks(0);
|
|
add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);
|
|
spin_unlock_irqrestore(&die_lock, flags);
|
|
oops_exit();
|
|
|
|
if (in_interrupt())
|
|
panic("Fatal exception in interrupt");
|
|
if (panic_on_oops)
|
|
panic("Fatal exception");
|
|
if (ret != NOTIFY_STOP)
|
|
make_task_dead(SIGSEGV);
|
|
}
|
|
|
|
void do_trap(struct pt_regs *regs, int signo, int code, unsigned long addr)
|
|
{
|
|
struct task_struct *tsk = current;
|
|
|
|
if (show_unhandled_signals && unhandled_signal(tsk, signo)
|
|
&& printk_ratelimit()) {
|
|
pr_info("%s[%d]: unhandled signal %d code 0x%x at 0x" REG_FMT,
|
|
tsk->comm, task_pid_nr(tsk), signo, code, addr);
|
|
print_vma_addr(KERN_CONT " in ", instruction_pointer(regs));
|
|
pr_cont("\n");
|
|
show_regs(regs);
|
|
}
|
|
|
|
force_sig_fault(signo, code, (void __user *)addr);
|
|
}
|
|
|
|
static void do_trap_error(struct pt_regs *regs, int signo, int code,
|
|
unsigned long addr, const char *str)
|
|
{
|
|
if (user_mode(regs)) {
|
|
do_trap(regs, signo, code, addr);
|
|
} else {
|
|
if (!fixup_exception(regs))
|
|
die(regs, str);
|
|
}
|
|
}
|
|
|
|
#define DO_ERROR_INFO(name, signo, code, str) \
|
|
asmlinkage __visible void name(struct pt_regs *regs) \
|
|
{ \
|
|
do_trap_error(regs, signo, code, regs->epc, "Oops - " str); \
|
|
}
|
|
|
|
DO_ERROR_INFO(do_trap_unknown,
|
|
SIGILL, ILL_ILLTRP, "unknown exception");
|
|
DO_ERROR_INFO(do_trap_insn_misaligned,
|
|
SIGBUS, BUS_ADRALN, "instruction address misaligned");
|
|
DO_ERROR_INFO(do_trap_insn_fault,
|
|
SIGSEGV, SEGV_ACCERR, "instruction access fault");
|
|
DO_ERROR_INFO(do_trap_insn_illegal,
|
|
SIGILL, ILL_ILLOPC, "illegal instruction");
|
|
DO_ERROR_INFO(do_trap_load_fault,
|
|
SIGSEGV, SEGV_ACCERR, "load access fault");
|
|
#ifndef CONFIG_RISCV_M_MODE
|
|
DO_ERROR_INFO(do_trap_load_misaligned,
|
|
SIGBUS, BUS_ADRALN, "Oops - load address misaligned");
|
|
DO_ERROR_INFO(do_trap_store_misaligned,
|
|
SIGBUS, BUS_ADRALN, "Oops - store (or AMO) address misaligned");
|
|
#else
|
|
int handle_misaligned_load(struct pt_regs *regs);
|
|
int handle_misaligned_store(struct pt_regs *regs);
|
|
|
|
asmlinkage void do_trap_load_misaligned(struct pt_regs *regs)
|
|
{
|
|
if (!handle_misaligned_load(regs))
|
|
return;
|
|
do_trap_error(regs, SIGBUS, BUS_ADRALN, regs->epc,
|
|
"Oops - load address misaligned");
|
|
}
|
|
|
|
asmlinkage void do_trap_store_misaligned(struct pt_regs *regs)
|
|
{
|
|
if (!handle_misaligned_store(regs))
|
|
return;
|
|
do_trap_error(regs, SIGBUS, BUS_ADRALN, regs->epc,
|
|
"Oops - store (or AMO) address misaligned");
|
|
}
|
|
#endif
|
|
DO_ERROR_INFO(do_trap_store_fault,
|
|
SIGSEGV, SEGV_ACCERR, "store (or AMO) access fault");
|
|
DO_ERROR_INFO(do_trap_ecall_u,
|
|
SIGILL, ILL_ILLTRP, "environment call from U-mode");
|
|
DO_ERROR_INFO(do_trap_ecall_s,
|
|
SIGILL, ILL_ILLTRP, "environment call from S-mode");
|
|
DO_ERROR_INFO(do_trap_ecall_m,
|
|
SIGILL, ILL_ILLTRP, "environment call from M-mode");
|
|
|
|
static inline unsigned long get_break_insn_length(unsigned long pc)
|
|
{
|
|
bug_insn_t insn;
|
|
|
|
if (get_kernel_nofault(insn, (bug_insn_t *)pc))
|
|
return 0;
|
|
|
|
return GET_INSN_LENGTH(insn);
|
|
}
|
|
|
|
asmlinkage __visible void do_trap_break(struct pt_regs *regs)
|
|
{
|
|
if (user_mode(regs))
|
|
force_sig_fault(SIGTRAP, TRAP_BRKPT, (void __user *)regs->epc);
|
|
#ifdef CONFIG_KGDB
|
|
else if (notify_die(DIE_TRAP, "EBREAK", regs, 0, regs->cause, SIGTRAP)
|
|
== NOTIFY_STOP)
|
|
return;
|
|
#endif
|
|
else if (report_bug(regs->epc, regs) == BUG_TRAP_TYPE_WARN)
|
|
regs->epc += get_break_insn_length(regs->epc);
|
|
else
|
|
die(regs, "Kernel BUG");
|
|
}
|
|
|
|
#ifdef CONFIG_GENERIC_BUG
|
|
int is_valid_bugaddr(unsigned long pc)
|
|
{
|
|
bug_insn_t insn;
|
|
|
|
if (pc < VMALLOC_START)
|
|
return 0;
|
|
if (get_kernel_nofault(insn, (bug_insn_t *)pc))
|
|
return 0;
|
|
if ((insn & __INSN_LENGTH_MASK) == __INSN_LENGTH_32)
|
|
return (insn == __BUG_INSN_32);
|
|
else
|
|
return ((insn & __COMPRESSED_INSN_MASK) == __BUG_INSN_16);
|
|
}
|
|
#endif /* CONFIG_GENERIC_BUG */
|
|
|
|
/* stvec & scratch is already set from head.S */
|
|
void trap_init(void)
|
|
{
|
|
}
|