2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* S390 version
|
2012-07-20 17:15:04 +08:00
|
|
|
* Copyright IBM Corp. 1999, 2000
|
2005-04-17 06:20:36 +08:00
|
|
|
* 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'.
|
|
|
|
*/
|
2013-03-14 20:44:25 +08:00
|
|
|
#include <linux/kprobes.h>
|
|
|
|
#include <linux/kdebug.h>
|
|
|
|
#include <linux/module.h>
|
2011-07-24 16:48:33 +08:00
|
|
|
#include <linux/ptrace.h>
|
2013-03-14 20:44:25 +08:00
|
|
|
#include <linux/sched.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/mm.h>
|
2014-10-06 23:53:53 +08:00
|
|
|
#include <linux/slab.h>
|
2015-06-11 21:33:54 +08:00
|
|
|
#include <asm/fpu-internal.h>
|
2008-04-17 13:46:26 +08:00
|
|
|
#include "entry.h"
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2011-12-27 18:27:31 +08:00
|
|
|
int show_unhandled_signals = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-07-31 17:03:04 +08:00
|
|
|
static inline void __user *get_trap_ip(struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
unsigned long address;
|
|
|
|
|
|
|
|
if (regs->int_code & 0x200)
|
|
|
|
address = *(unsigned long *)(current->thread.trap_tdb + 24);
|
|
|
|
else
|
|
|
|
address = regs->psw.addr;
|
|
|
|
return (void __user *)
|
|
|
|
((address - (regs->int_code >> 16)) & PSW_ADDR_INSN);
|
|
|
|
}
|
|
|
|
|
2011-12-27 18:27:18 +08:00
|
|
|
static inline void report_user_fault(struct pt_regs *regs, int signr)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2010-05-17 16:00:21 +08:00
|
|
|
if ((task_pid_nr(current) > 1) && !show_unhandled_signals)
|
2005-04-17 06:20:36 +08:00
|
|
|
return;
|
2010-05-17 16:00:21 +08:00
|
|
|
if (!unhandled_signal(current, signr))
|
|
|
|
return;
|
|
|
|
if (!printk_ratelimit())
|
|
|
|
return;
|
2014-11-19 20:31:08 +08:00
|
|
|
printk("User process fault: interruption code %04x ilc:%d ",
|
|
|
|
regs->int_code & 0xffff, regs->int_code >> 17);
|
2010-05-17 16:00:21 +08:00
|
|
|
print_vma_addr("in ", regs->psw.addr & PSW_ADDR_INSN);
|
|
|
|
printk("\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
show_regs(regs);
|
|
|
|
}
|
|
|
|
|
2007-04-27 22:01:42 +08:00
|
|
|
int is_valid_bugaddr(unsigned long addr)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-09-22 22:39:06 +08:00
|
|
|
void do_report_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
2011-12-27 18:27:18 +08:00
|
|
|
{
|
|
|
|
siginfo_t info;
|
|
|
|
|
2012-07-27 16:31:12 +08:00
|
|
|
if (user_mode(regs)) {
|
2011-12-27 18:27:18 +08:00
|
|
|
info.si_signo = si_signo;
|
|
|
|
info.si_errno = 0;
|
|
|
|
info.si_code = si_code;
|
2012-07-31 17:03:04 +08:00
|
|
|
info.si_addr = get_trap_ip(regs);
|
2011-12-27 18:27:18 +08:00
|
|
|
force_sig_info(si_signo, &info, current);
|
|
|
|
report_user_fault(regs, si_signo);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
const struct exception_table_entry *fixup;
|
|
|
|
fixup = search_exception_tables(regs->psw.addr & PSW_ADDR_INSN);
|
|
|
|
if (fixup)
|
2012-09-05 19:26:11 +08:00
|
|
|
regs->psw.addr = extable_fixup(fixup) | PSW_ADDR_AMODE;
|
2007-04-27 22:01:42 +08:00
|
|
|
else {
|
|
|
|
enum bug_trap_type btt;
|
|
|
|
|
2007-07-16 14:41:39 +08:00
|
|
|
btt = report_bug(regs->psw.addr & PSW_ADDR_INSN, regs);
|
2007-04-27 22:01:42 +08:00
|
|
|
if (btt == BUG_TRAP_TYPE_WARN)
|
|
|
|
return;
|
2011-12-27 18:27:18 +08:00
|
|
|
die(regs, str);
|
2007-04-27 22:01:42 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-22 18:42:38 +08:00
|
|
|
static void do_trap(struct pt_regs *regs, int si_signo, int si_code, char *str)
|
2014-09-22 22:39:06 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
2014-10-22 18:42:38 +08:00
|
|
|
NOKPROBE_SYMBOL(do_trap);
|
2014-09-22 22:39:06 +08:00
|
|
|
|
2014-10-22 18:42:38 +08:00
|
|
|
void do_per_trap(struct pt_regs *regs)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2011-07-24 16:48:33 +08:00
|
|
|
siginfo_t info;
|
|
|
|
|
2011-01-05 19:48:10 +08:00
|
|
|
if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, SIGTRAP) == NOTIFY_STOP)
|
2006-09-20 21:58:39 +08:00
|
|
|
return;
|
2011-07-24 16:48:33 +08:00
|
|
|
if (!current->ptrace)
|
|
|
|
return;
|
|
|
|
info.si_signo = SIGTRAP;
|
|
|
|
info.si_errno = 0;
|
|
|
|
info.si_code = TRAP_HWBKPT;
|
2011-10-30 22:17:15 +08:00
|
|
|
info.si_addr =
|
|
|
|
(void __force __user *) current->thread.per_event.address;
|
2011-07-24 16:48:33 +08:00
|
|
|
force_sig_info(SIGTRAP, &info, current);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2014-10-22 18:42:38 +08:00
|
|
|
NOKPROBE_SYMBOL(do_per_trap);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-10-19 00:10:06 +08:00
|
|
|
void default_trap_handler(struct pt_regs *regs)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2012-07-27 16:31:12 +08:00
|
|
|
if (user_mode(regs)) {
|
2011-12-27 18:27:18 +08:00
|
|
|
report_user_fault(regs, SIGSEGV);
|
2010-05-17 16:00:13 +08:00
|
|
|
do_exit(SIGSEGV);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
2011-12-27 18:27:18 +08:00
|
|
|
die(regs, "Unknown program exception");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2010-10-25 22:10:37 +08:00
|
|
|
#define DO_ERROR_INFO(name, signr, sicode, str) \
|
2012-10-19 00:10:06 +08:00
|
|
|
void name(struct pt_regs *regs) \
|
|
|
|
{ \
|
|
|
|
do_trap(regs, signr, sicode, str); \
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2010-10-25 22:10:37 +08:00
|
|
|
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")
|
2012-07-31 17:03:04 +08:00
|
|
|
DO_ERROR_INFO(transaction_exception, SIGILL, ILL_ILLOPN,
|
|
|
|
"transaction constraint exception")
|
|
|
|
|
2011-12-27 18:27:18 +08:00
|
|
|
static inline void do_fp_trap(struct pt_regs *regs, int fpc)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2011-12-27 18:27:18 +08:00
|
|
|
int si_code = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
/* 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 */
|
2011-12-27 18:27:18 +08:00
|
|
|
si_code = FPE_FLTINV;
|
2005-04-17 06:20:36 +08:00
|
|
|
else if (fpc & 0x4000) /* div by 0 */
|
2011-12-27 18:27:18 +08:00
|
|
|
si_code = FPE_FLTDIV;
|
2005-04-17 06:20:36 +08:00
|
|
|
else if (fpc & 0x2000) /* overflow */
|
2011-12-27 18:27:18 +08:00
|
|
|
si_code = FPE_FLTOVF;
|
2005-04-17 06:20:36 +08:00
|
|
|
else if (fpc & 0x1000) /* underflow */
|
2011-12-27 18:27:18 +08:00
|
|
|
si_code = FPE_FLTUND;
|
2005-04-17 06:20:36 +08:00
|
|
|
else if (fpc & 0x0800) /* inexact */
|
2011-12-27 18:27:18 +08:00
|
|
|
si_code = FPE_FLTRES;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2011-12-27 18:27:18 +08:00
|
|
|
do_trap(regs, SIGFPE, si_code, "floating point exception");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2014-11-19 21:05:52 +08:00
|
|
|
void translation_exception(struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
/* May never happen. */
|
2015-02-18 21:17:14 +08:00
|
|
|
panic("Translation exception");
|
2014-11-19 21:05:52 +08:00
|
|
|
}
|
|
|
|
|
2014-10-22 18:42:38 +08:00
|
|
|
void illegal_op(struct pt_regs *regs)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
siginfo_t info;
|
|
|
|
__u8 opcode[6];
|
2006-07-12 22:41:55 +08:00
|
|
|
__u16 __user *location;
|
2014-09-22 22:39:06 +08:00
|
|
|
int is_uprobe_insn = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
int signal = 0;
|
|
|
|
|
2012-07-31 17:03:04 +08:00
|
|
|
location = get_trap_ip(regs);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-07-27 16:31:12 +08:00
|
|
|
if (user_mode(regs)) {
|
2006-10-27 18:39:22 +08:00
|
|
|
if (get_user(*((__u16 *) opcode), (__u16 __user *) location))
|
|
|
|
return;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (*((__u16 *) opcode) == S390_BREAKPOINT_U16) {
|
2011-07-24 16:48:33 +08:00
|
|
|
if (current->ptrace) {
|
|
|
|
info.si_signo = SIGTRAP;
|
|
|
|
info.si_errno = 0;
|
|
|
|
info.si_code = TRAP_BRKPT;
|
|
|
|
info.si_addr = location;
|
|
|
|
force_sig_info(SIGTRAP, &info, current);
|
|
|
|
} else
|
2005-04-17 06:20:36 +08:00
|
|
|
signal = SIGILL;
|
2014-09-22 22:39:06 +08:00
|
|
|
#ifdef CONFIG_UPROBES
|
|
|
|
} else if (*((__u16 *) opcode) == UPROBE_SWBP_INSN) {
|
|
|
|
is_uprobe_insn = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
#endif
|
|
|
|
} else
|
|
|
|
signal = SIGILL;
|
2014-09-22 22:39:06 +08:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* 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)) {
|
2011-12-27 18:27:18 +08:00
|
|
|
if (notify_die(DIE_BPT, "bpt", regs, 0,
|
2007-02-06 04:17:29 +08:00
|
|
|
3, SIGTRAP) != NOTIFY_STOP)
|
|
|
|
signal = SIGILL;
|
|
|
|
}
|
2011-12-27 18:27:18 +08:00
|
|
|
if (signal)
|
|
|
|
do_trap(regs, signal, ILL_ILLOPC, "illegal operation");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2014-10-22 18:42:38 +08:00
|
|
|
NOKPROBE_SYMBOL(illegal_op);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-10-25 22:10:37 +08:00
|
|
|
DO_ERROR_INFO(specification_exception, SIGILL, ILL_ILLOPN,
|
|
|
|
"specification exception");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2014-10-06 23:53:53 +08:00
|
|
|
int alloc_vector_registers(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
__vector128 *vxrs;
|
2015-06-11 22:57:20 +08:00
|
|
|
freg_t *fprs;
|
2014-10-06 23:53:53 +08:00
|
|
|
|
|
|
|
/* Allocate vector register save area. */
|
|
|
|
vxrs = kzalloc(sizeof(__vector128) * __NUM_VXRS,
|
|
|
|
GFP_KERNEL|__GFP_REPEAT);
|
|
|
|
if (!vxrs)
|
|
|
|
return -ENOMEM;
|
|
|
|
preempt_disable();
|
|
|
|
if (tsk == current)
|
2015-06-11 21:33:54 +08:00
|
|
|
save_fp_regs(tsk->thread.fpu.fprs);
|
2014-10-06 23:53:53 +08:00
|
|
|
/* Copy the 16 floating point registers */
|
2015-06-11 21:33:54 +08:00
|
|
|
convert_fp_to_vx(vxrs, tsk->thread.fpu.fprs);
|
2015-06-11 22:57:20 +08:00
|
|
|
fprs = tsk->thread.fpu.fprs;
|
2015-06-11 21:33:54 +08:00
|
|
|
tsk->thread.fpu.vxrs = vxrs;
|
2015-06-11 22:57:20 +08:00
|
|
|
tsk->thread.fpu.flags |= FPU_USE_VX;
|
|
|
|
kfree(fprs);
|
2014-10-06 23:53:53 +08:00
|
|
|
if (tsk == current) {
|
|
|
|
__ctl_set_bit(0, 17);
|
|
|
|
restore_vx_regs(vxrs);
|
|
|
|
}
|
|
|
|
preempt_enable();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 */
|
2015-06-11 21:33:54 +08:00
|
|
|
asm volatile("stfpc %0" : "=Q" (current->thread.fpu.fpc));
|
|
|
|
vic = (current->thread.fpu.fpc & 0xf00) >> 8;
|
2014-10-06 23:53:53 +08:00
|
|
|
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 int __init disable_vector_extension(char *str)
|
|
|
|
{
|
|
|
|
S390_lowcore.machine_flags &= ~MACHINE_FLAG_VX;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
__setup("novx", disable_vector_extension);
|
|
|
|
|
2012-10-19 00:10:06 +08:00
|
|
|
void data_exception(struct pt_regs *regs)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-07-12 22:41:55 +08:00
|
|
|
__u16 __user *location;
|
2005-04-17 06:20:36 +08:00
|
|
|
int signal = 0;
|
|
|
|
|
2012-07-31 17:03:04 +08:00
|
|
|
location = get_trap_ip(regs);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2015-06-11 21:33:54 +08:00
|
|
|
asm volatile("stfpc %0" : "=Q" (current->thread.fpu.fpc));
|
2014-10-06 23:53:53 +08:00
|
|
|
/* Check for vector register enablement */
|
2015-06-11 21:33:54 +08:00
|
|
|
if (MACHINE_HAS_VX && !is_vx_task(current) &&
|
|
|
|
(current->thread.fpu.fpc & FPC_DXC_MASK) == 0xfe00) {
|
2014-10-06 23:53:53 +08:00
|
|
|
alloc_vector_registers(current);
|
|
|
|
/* Vector data exception is suppressing, rewind psw. */
|
|
|
|
regs->psw.addr = __rewind_psw(regs->psw, regs->int_code >> 16);
|
|
|
|
clear_pt_regs_flag(regs, PIF_PER_TRAP);
|
|
|
|
return;
|
|
|
|
}
|
2015-06-11 21:33:54 +08:00
|
|
|
if (current->thread.fpu.fpc & FPC_DXC_MASK)
|
2005-04-17 06:20:36 +08:00
|
|
|
signal = SIGFPE;
|
|
|
|
else
|
|
|
|
signal = SIGILL;
|
2015-02-12 20:08:27 +08:00
|
|
|
if (signal == SIGFPE)
|
2015-06-11 21:33:54 +08:00
|
|
|
do_fp_trap(regs, current->thread.fpu.fpc);
|
2011-12-27 18:27:18 +08:00
|
|
|
else if (signal)
|
|
|
|
do_trap(regs, signal, ILL_ILLOPN, "data exception");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2012-10-19 00:10:06 +08:00
|
|
|
void space_switch_exception(struct pt_regs *regs)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
/* Set user psw back to home space mode. */
|
2012-07-27 16:31:12 +08:00
|
|
|
if (user_mode(regs))
|
2005-04-17 06:20:36 +08:00
|
|
|
regs->psw.mask |= PSW_ASC_HOME;
|
|
|
|
/* Send SIGILL. */
|
2011-12-27 18:27:18 +08:00
|
|
|
do_trap(regs, SIGILL, ILL_PRVOPC, "space switch event");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2014-10-22 18:42:38 +08:00
|
|
|
void kernel_stack_overflow(struct pt_regs *regs)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-06-22 08:16:28 +08:00
|
|
|
bust_spinlocks(1);
|
|
|
|
printk("Kernel stack overflow.\n");
|
|
|
|
show_regs(regs);
|
|
|
|
bust_spinlocks(0);
|
2005-04-17 06:20:36 +08:00
|
|
|
panic("Corrupt kernel stack, can't continue.");
|
|
|
|
}
|
2014-10-22 18:42:38 +08:00
|
|
|
NOKPROBE_SYMBOL(kernel_stack_overflow);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
void __init trap_init(void)
|
|
|
|
{
|
2011-01-05 19:48:00 +08:00
|
|
|
local_mcck_enable();
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|