mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-27 14:14:24 +08:00
84e599e3ad
The kernel starts with machine checks disabled (machine check mask bit in the PSW is zero), and machine checks are enabled when trap_init() is called. The rationale is that this allows to assume that the system is initialized up to a certain point before the machine check handler may be invoked. However the implementation is incomplete: all new PSW masks in lowcore have the machine check mask bit. This means that e.g. for any early program check machine checks are enabled within the program check handler. This contradicts the whole point of enabling machine checks at a single place. Change this and initialize all new PSWs in lowcore so they have the machine check mask bit not set. Set the bit in all masks in trap_init(). This way machine check enabling is consistent. Reviewed-by: Alexander Gordeev <agordeev@linux.ibm.com> Signed-off-by: Heiko Carstens <hca@linux.ibm.com> Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
415 lines
11 KiB
C
415 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* S390 version
|
|
* Copyright IBM Corp. 1999, 2000
|
|
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
|
|
* Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com),
|
|
*
|
|
* Derived from "arch/i386/kernel/traps.c"
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*/
|
|
|
|
/*
|
|
* 'Traps.c' handles hardware traps and faults after we have saved some
|
|
* state in 'asm.s'.
|
|
*/
|
|
#include "asm/irqflags.h"
|
|
#include "asm/ptrace.h"
|
|
#include <linux/kprobes.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/randomize_kstack.h>
|
|
#include <linux/extable.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/entry-common.h>
|
|
#include <asm/asm-extable.h>
|
|
#include <asm/fpu/api.h>
|
|
#include <asm/vtime.h>
|
|
#include "entry.h"
|
|
|
|
static inline void __user *get_trap_ip(struct pt_regs *regs)
|
|
{
|
|
unsigned long address;
|
|
|
|
if (regs->int_code & 0x200)
|
|
address = current->thread.trap_tdb.data[3];
|
|
else
|
|
address = regs->psw.addr;
|
|
return (void __user *) (address - (regs->int_code >> 16));
|
|
}
|
|
|
|
int is_valid_bugaddr(unsigned long addr)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void do_report_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
|
{
|
|
if (user_mode(regs)) {
|
|
force_sig_fault(si_signo, si_code, get_trap_ip(regs));
|
|
report_user_fault(regs, si_signo, 0);
|
|
} else {
|
|
if (!fixup_exception(regs))
|
|
die(regs, str);
|
|
}
|
|
}
|
|
|
|
static void do_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
|
{
|
|
if (notify_die(DIE_TRAP, str, regs, 0,
|
|
regs->int_code, si_signo) == NOTIFY_STOP)
|
|
return;
|
|
do_report_trap(regs, si_signo, si_code, str);
|
|
}
|
|
NOKPROBE_SYMBOL(do_trap);
|
|
|
|
void do_per_trap(struct pt_regs *regs)
|
|
{
|
|
if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, SIGTRAP) == NOTIFY_STOP)
|
|
return;
|
|
if (!current->ptrace)
|
|
return;
|
|
force_sig_fault(SIGTRAP, TRAP_HWBKPT,
|
|
(void __force __user *) current->thread.per_event.address);
|
|
}
|
|
NOKPROBE_SYMBOL(do_per_trap);
|
|
|
|
static void default_trap_handler(struct pt_regs *regs)
|
|
{
|
|
if (user_mode(regs)) {
|
|
report_user_fault(regs, SIGSEGV, 0);
|
|
force_exit_sig(SIGSEGV);
|
|
} else
|
|
die(regs, "Unknown program exception");
|
|
}
|
|
|
|
#define DO_ERROR_INFO(name, signr, sicode, str) \
|
|
static void name(struct pt_regs *regs) \
|
|
{ \
|
|
do_trap(regs, signr, sicode, str); \
|
|
}
|
|
|
|
DO_ERROR_INFO(addressing_exception, SIGILL, ILL_ILLADR,
|
|
"addressing exception")
|
|
DO_ERROR_INFO(execute_exception, SIGILL, ILL_ILLOPN,
|
|
"execute exception")
|
|
DO_ERROR_INFO(divide_exception, SIGFPE, FPE_INTDIV,
|
|
"fixpoint divide exception")
|
|
DO_ERROR_INFO(overflow_exception, SIGFPE, FPE_INTOVF,
|
|
"fixpoint overflow exception")
|
|
DO_ERROR_INFO(hfp_overflow_exception, SIGFPE, FPE_FLTOVF,
|
|
"HFP overflow exception")
|
|
DO_ERROR_INFO(hfp_underflow_exception, SIGFPE, FPE_FLTUND,
|
|
"HFP underflow exception")
|
|
DO_ERROR_INFO(hfp_significance_exception, SIGFPE, FPE_FLTRES,
|
|
"HFP significance exception")
|
|
DO_ERROR_INFO(hfp_divide_exception, SIGFPE, FPE_FLTDIV,
|
|
"HFP divide exception")
|
|
DO_ERROR_INFO(hfp_sqrt_exception, SIGFPE, FPE_FLTINV,
|
|
"HFP square root exception")
|
|
DO_ERROR_INFO(operand_exception, SIGILL, ILL_ILLOPN,
|
|
"operand exception")
|
|
DO_ERROR_INFO(privileged_op, SIGILL, ILL_PRVOPC,
|
|
"privileged operation")
|
|
DO_ERROR_INFO(special_op_exception, SIGILL, ILL_ILLOPN,
|
|
"special operation exception")
|
|
DO_ERROR_INFO(transaction_exception, SIGILL, ILL_ILLOPN,
|
|
"transaction constraint exception")
|
|
|
|
static inline void do_fp_trap(struct pt_regs *regs, __u32 fpc)
|
|
{
|
|
int si_code = 0;
|
|
/* FPC[2] is Data Exception Code */
|
|
if ((fpc & 0x00000300) == 0) {
|
|
/* bits 6 and 7 of DXC are 0 iff IEEE exception */
|
|
if (fpc & 0x8000) /* invalid fp operation */
|
|
si_code = FPE_FLTINV;
|
|
else if (fpc & 0x4000) /* div by 0 */
|
|
si_code = FPE_FLTDIV;
|
|
else if (fpc & 0x2000) /* overflow */
|
|
si_code = FPE_FLTOVF;
|
|
else if (fpc & 0x1000) /* underflow */
|
|
si_code = FPE_FLTUND;
|
|
else if (fpc & 0x0800) /* inexact */
|
|
si_code = FPE_FLTRES;
|
|
}
|
|
do_trap(regs, SIGFPE, si_code, "floating point exception");
|
|
}
|
|
|
|
static void translation_specification_exception(struct pt_regs *regs)
|
|
{
|
|
/* May never happen. */
|
|
panic("Translation-Specification Exception");
|
|
}
|
|
|
|
static void illegal_op(struct pt_regs *regs)
|
|
{
|
|
__u8 opcode[6];
|
|
__u16 __user *location;
|
|
int is_uprobe_insn = 0;
|
|
int signal = 0;
|
|
|
|
location = get_trap_ip(regs);
|
|
|
|
if (user_mode(regs)) {
|
|
if (get_user(*((__u16 *) opcode), (__u16 __user *) location))
|
|
return;
|
|
if (*((__u16 *) opcode) == S390_BREAKPOINT_U16) {
|
|
if (current->ptrace)
|
|
force_sig_fault(SIGTRAP, TRAP_BRKPT, location);
|
|
else
|
|
signal = SIGILL;
|
|
#ifdef CONFIG_UPROBES
|
|
} else if (*((__u16 *) opcode) == UPROBE_SWBP_INSN) {
|
|
is_uprobe_insn = 1;
|
|
#endif
|
|
} else
|
|
signal = SIGILL;
|
|
}
|
|
/*
|
|
* We got either an illegal op in kernel mode, or user space trapped
|
|
* on a uprobes illegal instruction. See if kprobes or uprobes picks
|
|
* it up. If not, SIGILL.
|
|
*/
|
|
if (is_uprobe_insn || !user_mode(regs)) {
|
|
if (notify_die(DIE_BPT, "bpt", regs, 0,
|
|
3, SIGTRAP) != NOTIFY_STOP)
|
|
signal = SIGILL;
|
|
}
|
|
if (signal)
|
|
do_trap(regs, signal, ILL_ILLOPC, "illegal operation");
|
|
}
|
|
NOKPROBE_SYMBOL(illegal_op);
|
|
|
|
DO_ERROR_INFO(specification_exception, SIGILL, ILL_ILLOPN,
|
|
"specification exception");
|
|
|
|
static void vector_exception(struct pt_regs *regs)
|
|
{
|
|
int si_code, vic;
|
|
|
|
if (!MACHINE_HAS_VX) {
|
|
do_trap(regs, SIGILL, ILL_ILLOPN, "illegal operation");
|
|
return;
|
|
}
|
|
|
|
/* get vector interrupt code from fpc */
|
|
save_fpu_regs();
|
|
vic = (current->thread.fpu.fpc & 0xf00) >> 8;
|
|
switch (vic) {
|
|
case 1: /* invalid vector operation */
|
|
si_code = FPE_FLTINV;
|
|
break;
|
|
case 2: /* division by zero */
|
|
si_code = FPE_FLTDIV;
|
|
break;
|
|
case 3: /* overflow */
|
|
si_code = FPE_FLTOVF;
|
|
break;
|
|
case 4: /* underflow */
|
|
si_code = FPE_FLTUND;
|
|
break;
|
|
case 5: /* inexact */
|
|
si_code = FPE_FLTRES;
|
|
break;
|
|
default: /* unknown cause */
|
|
si_code = 0;
|
|
}
|
|
do_trap(regs, SIGFPE, si_code, "vector exception");
|
|
}
|
|
|
|
static void data_exception(struct pt_regs *regs)
|
|
{
|
|
save_fpu_regs();
|
|
if (current->thread.fpu.fpc & FPC_DXC_MASK)
|
|
do_fp_trap(regs, current->thread.fpu.fpc);
|
|
else
|
|
do_trap(regs, SIGILL, ILL_ILLOPN, "data exception");
|
|
}
|
|
|
|
static void space_switch_exception(struct pt_regs *regs)
|
|
{
|
|
/* Set user psw back to home space mode. */
|
|
if (user_mode(regs))
|
|
regs->psw.mask |= PSW_ASC_HOME;
|
|
/* Send SIGILL. */
|
|
do_trap(regs, SIGILL, ILL_PRVOPC, "space switch event");
|
|
}
|
|
|
|
static void monitor_event_exception(struct pt_regs *regs)
|
|
{
|
|
if (user_mode(regs))
|
|
return;
|
|
|
|
switch (report_bug(regs->psw.addr - (regs->int_code >> 16), regs)) {
|
|
case BUG_TRAP_TYPE_NONE:
|
|
fixup_exception(regs);
|
|
break;
|
|
case BUG_TRAP_TYPE_WARN:
|
|
break;
|
|
case BUG_TRAP_TYPE_BUG:
|
|
die(regs, "monitor event");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void kernel_stack_overflow(struct pt_regs *regs)
|
|
{
|
|
bust_spinlocks(1);
|
|
printk("Kernel stack overflow.\n");
|
|
show_regs(regs);
|
|
bust_spinlocks(0);
|
|
panic("Corrupt kernel stack, can't continue.");
|
|
}
|
|
NOKPROBE_SYMBOL(kernel_stack_overflow);
|
|
|
|
static void __init test_monitor_call(void)
|
|
{
|
|
int val = 1;
|
|
|
|
if (!IS_ENABLED(CONFIG_BUG))
|
|
return;
|
|
asm volatile(
|
|
" mc 0,0\n"
|
|
"0: xgr %0,%0\n"
|
|
"1:\n"
|
|
EX_TABLE(0b,1b)
|
|
: "+d" (val));
|
|
if (!val)
|
|
panic("Monitor call doesn't work!\n");
|
|
}
|
|
|
|
void __init trap_init(void)
|
|
{
|
|
unsigned long flags;
|
|
struct ctlreg cr0;
|
|
|
|
local_irq_save(flags);
|
|
cr0 = local_ctl_clear_bit(0, CR0_LOW_ADDRESS_PROTECTION_BIT);
|
|
psw_bits(S390_lowcore.external_new_psw).mcheck = 1;
|
|
psw_bits(S390_lowcore.program_new_psw).mcheck = 1;
|
|
psw_bits(S390_lowcore.svc_new_psw).mcheck = 1;
|
|
psw_bits(S390_lowcore.io_new_psw).mcheck = 1;
|
|
local_ctl_load(0, &cr0);
|
|
local_irq_restore(flags);
|
|
local_mcck_enable();
|
|
test_monitor_call();
|
|
}
|
|
|
|
static void (*pgm_check_table[128])(struct pt_regs *regs);
|
|
|
|
void noinstr __do_pgm_check(struct pt_regs *regs)
|
|
{
|
|
unsigned int trapnr;
|
|
irqentry_state_t state;
|
|
|
|
regs->int_code = S390_lowcore.pgm_int_code;
|
|
regs->int_parm_long = S390_lowcore.trans_exc_code;
|
|
|
|
state = irqentry_enter(regs);
|
|
|
|
if (user_mode(regs)) {
|
|
update_timer_sys();
|
|
if (!static_branch_likely(&cpu_has_bear)) {
|
|
if (regs->last_break < 4096)
|
|
regs->last_break = 1;
|
|
}
|
|
current->thread.last_break = regs->last_break;
|
|
}
|
|
|
|
if (S390_lowcore.pgm_code & 0x0200) {
|
|
/* transaction abort */
|
|
current->thread.trap_tdb = S390_lowcore.pgm_tdb;
|
|
}
|
|
|
|
if (S390_lowcore.pgm_code & PGM_INT_CODE_PER) {
|
|
if (user_mode(regs)) {
|
|
struct per_event *ev = ¤t->thread.per_event;
|
|
|
|
set_thread_flag(TIF_PER_TRAP);
|
|
ev->address = S390_lowcore.per_address;
|
|
ev->cause = S390_lowcore.per_code_combined;
|
|
ev->paid = S390_lowcore.per_access_id;
|
|
} else {
|
|
/* PER event in kernel is kprobes */
|
|
__arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER);
|
|
do_per_trap(regs);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!irqs_disabled_flags(regs->psw.mask))
|
|
trace_hardirqs_on();
|
|
__arch_local_irq_ssm(regs->psw.mask & ~PSW_MASK_PER);
|
|
|
|
trapnr = regs->int_code & PGM_INT_CODE_MASK;
|
|
if (trapnr)
|
|
pgm_check_table[trapnr](regs);
|
|
out:
|
|
local_irq_disable();
|
|
irqentry_exit(regs, state);
|
|
}
|
|
|
|
/*
|
|
* The program check table contains exactly 128 (0x00-0x7f) entries. Each
|
|
* line defines the function to be called corresponding to the program check
|
|
* interruption code.
|
|
*/
|
|
static void (*pgm_check_table[128])(struct pt_regs *regs) = {
|
|
[0x00] = default_trap_handler,
|
|
[0x01] = illegal_op,
|
|
[0x02] = privileged_op,
|
|
[0x03] = execute_exception,
|
|
[0x04] = do_protection_exception,
|
|
[0x05] = addressing_exception,
|
|
[0x06] = specification_exception,
|
|
[0x07] = data_exception,
|
|
[0x08] = overflow_exception,
|
|
[0x09] = divide_exception,
|
|
[0x0a] = overflow_exception,
|
|
[0x0b] = divide_exception,
|
|
[0x0c] = hfp_overflow_exception,
|
|
[0x0d] = hfp_underflow_exception,
|
|
[0x0e] = hfp_significance_exception,
|
|
[0x0f] = hfp_divide_exception,
|
|
[0x10] = do_dat_exception,
|
|
[0x11] = do_dat_exception,
|
|
[0x12] = translation_specification_exception,
|
|
[0x13] = special_op_exception,
|
|
[0x14] = default_trap_handler,
|
|
[0x15] = operand_exception,
|
|
[0x16] = default_trap_handler,
|
|
[0x17] = default_trap_handler,
|
|
[0x18] = transaction_exception,
|
|
[0x19] = default_trap_handler,
|
|
[0x1a] = default_trap_handler,
|
|
[0x1b] = vector_exception,
|
|
[0x1c] = space_switch_exception,
|
|
[0x1d] = hfp_sqrt_exception,
|
|
[0x1e ... 0x37] = default_trap_handler,
|
|
[0x38] = do_dat_exception,
|
|
[0x39] = do_dat_exception,
|
|
[0x3a] = do_dat_exception,
|
|
[0x3b] = do_dat_exception,
|
|
[0x3c] = default_trap_handler,
|
|
[0x3d] = do_secure_storage_access,
|
|
[0x3e] = do_non_secure_storage_access,
|
|
[0x3f] = do_secure_storage_violation,
|
|
[0x40] = monitor_event_exception,
|
|
[0x41 ... 0x7f] = default_trap_handler,
|
|
};
|
|
|
|
#define COND_TRAP(x) asm( \
|
|
".weak " __stringify(x) "\n\t" \
|
|
".set " __stringify(x) "," \
|
|
__stringify(default_trap_handler))
|
|
|
|
COND_TRAP(do_secure_storage_access);
|
|
COND_TRAP(do_non_secure_storage_access);
|
|
COND_TRAP(do_secure_storage_violation);
|