]>
Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* |
1da177e4 | 2 | * S390 version |
a53c8fab | 3 | * Copyright IBM Corp. 1999, 2000 |
1da177e4 LT |
4 | * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com), |
5 | * Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com), | |
6 | * | |
7 | * Derived from "arch/i386/kernel/traps.c" | |
8 | * Copyright (C) 1991, 1992 Linus Torvalds | |
9 | */ | |
10 | ||
11 | /* | |
12 | * 'Traps.c' handles hardware traps and faults after we have saved some | |
13 | * state in 'asm.s'. | |
14 | */ | |
1bca09f7 HC |
15 | #include <linux/kprobes.h> |
16 | #include <linux/kdebug.h> | |
17 | #include <linux/module.h> | |
73b7d40f | 18 | #include <linux/ptrace.h> |
1bca09f7 | 19 | #include <linux/sched.h> |
1da177e4 | 20 | #include <linux/mm.h> |
80703617 MS |
21 | #include <linux/slab.h> |
22 | #include <asm/switch_to.h> | |
a806170e | 23 | #include "entry.h" |
1da177e4 | 24 | |
02834eec | 25 | int show_unhandled_signals = 1; |
1da177e4 | 26 | |
d35339a4 MS |
27 | static inline void __user *get_trap_ip(struct pt_regs *regs) |
28 | { | |
d35339a4 MS |
29 | unsigned long address; |
30 | ||
31 | if (regs->int_code & 0x200) | |
32 | address = *(unsigned long *)(current->thread.trap_tdb + 24); | |
33 | else | |
34 | address = regs->psw.addr; | |
35 | return (void __user *) | |
36 | ((address - (regs->int_code >> 16)) & PSW_ADDR_INSN); | |
d35339a4 MS |
37 | } |
38 | ||
aa33c8cb | 39 | static inline void report_user_fault(struct pt_regs *regs, int signr) |
1da177e4 | 40 | { |
ab3c68ee | 41 | if ((task_pid_nr(current) > 1) && !show_unhandled_signals) |
1da177e4 | 42 | return; |
ab3c68ee HC |
43 | if (!unhandled_signal(current, signr)) |
44 | return; | |
45 | if (!printk_ratelimit()) | |
46 | return; | |
413d4047 HC |
47 | printk("User process fault: interruption code %04x ilc:%d ", |
48 | regs->int_code & 0xffff, regs->int_code >> 17); | |
ab3c68ee HC |
49 | print_vma_addr("in ", regs->psw.addr & PSW_ADDR_INSN); |
50 | printk("\n"); | |
1da177e4 | 51 | show_regs(regs); |
1da177e4 LT |
52 | } |
53 | ||
c0007f1a HC |
54 | int is_valid_bugaddr(unsigned long addr) |
55 | { | |
56 | return 1; | |
57 | } | |
58 | ||
2a0a5b22 | 59 | void do_report_trap(struct pt_regs *regs, int si_signo, int si_code, char *str) |
aa33c8cb MS |
60 | { |
61 | siginfo_t info; | |
62 | ||
7d256175 | 63 | if (user_mode(regs)) { |
aa33c8cb MS |
64 | info.si_signo = si_signo; |
65 | info.si_errno = 0; | |
66 | info.si_code = si_code; | |
d35339a4 | 67 | info.si_addr = get_trap_ip(regs); |
aa33c8cb MS |
68 | force_sig_info(si_signo, &info, current); |
69 | report_user_fault(regs, si_signo); | |
1da177e4 LT |
70 | } else { |
71 | const struct exception_table_entry *fixup; | |
72 | fixup = search_exception_tables(regs->psw.addr & PSW_ADDR_INSN); | |
73 | if (fixup) | |
eb608fb3 | 74 | regs->psw.addr = extable_fixup(fixup) | PSW_ADDR_AMODE; |
c0007f1a HC |
75 | else { |
76 | enum bug_trap_type btt; | |
77 | ||
608e2619 | 78 | btt = report_bug(regs->psw.addr & PSW_ADDR_INSN, regs); |
c0007f1a HC |
79 | if (btt == BUG_TRAP_TYPE_WARN) |
80 | return; | |
aa33c8cb | 81 | die(regs, str); |
c0007f1a | 82 | } |
1da177e4 LT |
83 | } |
84 | } | |
85 | ||
7a5388de | 86 | static void do_trap(struct pt_regs *regs, int si_signo, int si_code, char *str) |
2a0a5b22 JW |
87 | { |
88 | if (notify_die(DIE_TRAP, str, regs, 0, | |
89 | regs->int_code, si_signo) == NOTIFY_STOP) | |
90 | return; | |
91 | do_report_trap(regs, si_signo, si_code, str); | |
92 | } | |
7a5388de | 93 | NOKPROBE_SYMBOL(do_trap); |
2a0a5b22 | 94 | |
7a5388de | 95 | void do_per_trap(struct pt_regs *regs) |
1da177e4 | 96 | { |
73b7d40f MS |
97 | siginfo_t info; |
98 | ||
5e9a2692 | 99 | if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, SIGTRAP) == NOTIFY_STOP) |
4ba069b8 | 100 | return; |
73b7d40f MS |
101 | if (!current->ptrace) |
102 | return; | |
103 | info.si_signo = SIGTRAP; | |
104 | info.si_errno = 0; | |
105 | info.si_code = TRAP_HWBKPT; | |
3c52e49d MS |
106 | info.si_addr = |
107 | (void __force __user *) current->thread.per_event.address; | |
73b7d40f | 108 | force_sig_info(SIGTRAP, &info, current); |
1da177e4 | 109 | } |
7a5388de | 110 | NOKPROBE_SYMBOL(do_per_trap); |
1da177e4 | 111 | |
b01a37a7 | 112 | void default_trap_handler(struct pt_regs *regs) |
1da177e4 | 113 | { |
7d256175 | 114 | if (user_mode(regs)) { |
aa33c8cb | 115 | report_user_fault(regs, SIGSEGV); |
6ea50968 | 116 | do_exit(SIGSEGV); |
1da177e4 | 117 | } else |
aa33c8cb | 118 | die(regs, "Unknown program exception"); |
1da177e4 LT |
119 | } |
120 | ||
1e54622e | 121 | #define DO_ERROR_INFO(name, signr, sicode, str) \ |
b01a37a7 HC |
122 | void name(struct pt_regs *regs) \ |
123 | { \ | |
124 | do_trap(regs, signr, sicode, str); \ | |
1da177e4 LT |
125 | } |
126 | ||
1e54622e MS |
127 | DO_ERROR_INFO(addressing_exception, SIGILL, ILL_ILLADR, |
128 | "addressing exception") | |
129 | DO_ERROR_INFO(execute_exception, SIGILL, ILL_ILLOPN, | |
130 | "execute exception") | |
131 | DO_ERROR_INFO(divide_exception, SIGFPE, FPE_INTDIV, | |
132 | "fixpoint divide exception") | |
133 | DO_ERROR_INFO(overflow_exception, SIGFPE, FPE_INTOVF, | |
134 | "fixpoint overflow exception") | |
135 | DO_ERROR_INFO(hfp_overflow_exception, SIGFPE, FPE_FLTOVF, | |
136 | "HFP overflow exception") | |
137 | DO_ERROR_INFO(hfp_underflow_exception, SIGFPE, FPE_FLTUND, | |
138 | "HFP underflow exception") | |
139 | DO_ERROR_INFO(hfp_significance_exception, SIGFPE, FPE_FLTRES, | |
140 | "HFP significance exception") | |
141 | DO_ERROR_INFO(hfp_divide_exception, SIGFPE, FPE_FLTDIV, | |
142 | "HFP divide exception") | |
143 | DO_ERROR_INFO(hfp_sqrt_exception, SIGFPE, FPE_FLTINV, | |
144 | "HFP square root exception") | |
145 | DO_ERROR_INFO(operand_exception, SIGILL, ILL_ILLOPN, | |
146 | "operand exception") | |
147 | DO_ERROR_INFO(privileged_op, SIGILL, ILL_PRVOPC, | |
148 | "privileged operation") | |
149 | DO_ERROR_INFO(special_op_exception, SIGILL, ILL_ILLOPN, | |
150 | "special operation exception") | |
d35339a4 MS |
151 | DO_ERROR_INFO(transaction_exception, SIGILL, ILL_ILLOPN, |
152 | "transaction constraint exception") | |
d35339a4 | 153 | |
aa33c8cb | 154 | static inline void do_fp_trap(struct pt_regs *regs, int fpc) |
1da177e4 | 155 | { |
aa33c8cb | 156 | int si_code = 0; |
1da177e4 LT |
157 | /* FPC[2] is Data Exception Code */ |
158 | if ((fpc & 0x00000300) == 0) { | |
159 | /* bits 6 and 7 of DXC are 0 iff IEEE exception */ | |
160 | if (fpc & 0x8000) /* invalid fp operation */ | |
aa33c8cb | 161 | si_code = FPE_FLTINV; |
1da177e4 | 162 | else if (fpc & 0x4000) /* div by 0 */ |
aa33c8cb | 163 | si_code = FPE_FLTDIV; |
1da177e4 | 164 | else if (fpc & 0x2000) /* overflow */ |
aa33c8cb | 165 | si_code = FPE_FLTOVF; |
1da177e4 | 166 | else if (fpc & 0x1000) /* underflow */ |
aa33c8cb | 167 | si_code = FPE_FLTUND; |
1da177e4 | 168 | else if (fpc & 0x0800) /* inexact */ |
aa33c8cb | 169 | si_code = FPE_FLTRES; |
1da177e4 | 170 | } |
aa33c8cb | 171 | do_trap(regs, SIGFPE, si_code, "floating point exception"); |
1da177e4 LT |
172 | } |
173 | ||
e56da345 HC |
174 | void translation_exception(struct pt_regs *regs) |
175 | { | |
176 | /* May never happen. */ | |
e1d12d70 | 177 | panic("Translation exception"); |
e56da345 HC |
178 | } |
179 | ||
7a5388de | 180 | void illegal_op(struct pt_regs *regs) |
1da177e4 LT |
181 | { |
182 | siginfo_t info; | |
183 | __u8 opcode[6]; | |
d2c993d8 | 184 | __u16 __user *location; |
2a0a5b22 | 185 | int is_uprobe_insn = 0; |
1da177e4 LT |
186 | int signal = 0; |
187 | ||
d35339a4 | 188 | location = get_trap_ip(regs); |
1da177e4 | 189 | |
7d256175 | 190 | if (user_mode(regs)) { |
12bae235 HC |
191 | if (get_user(*((__u16 *) opcode), (__u16 __user *) location)) |
192 | return; | |
1da177e4 | 193 | if (*((__u16 *) opcode) == S390_BREAKPOINT_U16) { |
73b7d40f MS |
194 | if (current->ptrace) { |
195 | info.si_signo = SIGTRAP; | |
196 | info.si_errno = 0; | |
197 | info.si_code = TRAP_BRKPT; | |
198 | info.si_addr = location; | |
199 | force_sig_info(SIGTRAP, &info, current); | |
200 | } else | |
1da177e4 | 201 | signal = SIGILL; |
2a0a5b22 JW |
202 | #ifdef CONFIG_UPROBES |
203 | } else if (*((__u16 *) opcode) == UPROBE_SWBP_INSN) { | |
204 | is_uprobe_insn = 1; | |
1da177e4 LT |
205 | #endif |
206 | } else | |
207 | signal = SIGILL; | |
2a0a5b22 JW |
208 | } |
209 | /* | |
210 | * We got either an illegal op in kernel mode, or user space trapped | |
211 | * on a uprobes illegal instruction. See if kprobes or uprobes picks | |
212 | * it up. If not, SIGILL. | |
213 | */ | |
214 | if (is_uprobe_insn || !user_mode(regs)) { | |
aa33c8cb | 215 | if (notify_die(DIE_BPT, "bpt", regs, 0, |
35df8d53 HC |
216 | 3, SIGTRAP) != NOTIFY_STOP) |
217 | signal = SIGILL; | |
218 | } | |
aa33c8cb MS |
219 | if (signal) |
220 | do_trap(regs, signal, ILL_ILLOPC, "illegal operation"); | |
1da177e4 | 221 | } |
7a5388de | 222 | NOKPROBE_SYMBOL(illegal_op); |
1da177e4 | 223 | |
1e54622e MS |
224 | DO_ERROR_INFO(specification_exception, SIGILL, ILL_ILLOPN, |
225 | "specification exception"); | |
1da177e4 | 226 | |
80703617 MS |
227 | int alloc_vector_registers(struct task_struct *tsk) |
228 | { | |
229 | __vector128 *vxrs; | |
230 | int i; | |
231 | ||
232 | /* Allocate vector register save area. */ | |
233 | vxrs = kzalloc(sizeof(__vector128) * __NUM_VXRS, | |
234 | GFP_KERNEL|__GFP_REPEAT); | |
235 | if (!vxrs) | |
236 | return -ENOMEM; | |
237 | preempt_disable(); | |
238 | if (tsk == current) | |
239 | save_fp_regs(tsk->thread.fp_regs.fprs); | |
240 | /* Copy the 16 floating point registers */ | |
241 | for (i = 0; i < 16; i++) | |
242 | *(freg_t *) &vxrs[i] = tsk->thread.fp_regs.fprs[i]; | |
243 | tsk->thread.vxrs = vxrs; | |
244 | if (tsk == current) { | |
245 | __ctl_set_bit(0, 17); | |
246 | restore_vx_regs(vxrs); | |
247 | } | |
248 | preempt_enable(); | |
249 | return 0; | |
250 | } | |
251 | ||
252 | void vector_exception(struct pt_regs *regs) | |
253 | { | |
254 | int si_code, vic; | |
255 | ||
256 | if (!MACHINE_HAS_VX) { | |
257 | do_trap(regs, SIGILL, ILL_ILLOPN, "illegal operation"); | |
258 | return; | |
259 | } | |
260 | ||
261 | /* get vector interrupt code from fpc */ | |
262 | asm volatile("stfpc %0" : "=m" (current->thread.fp_regs.fpc)); | |
263 | vic = (current->thread.fp_regs.fpc & 0xf00) >> 8; | |
264 | switch (vic) { | |
265 | case 1: /* invalid vector operation */ | |
266 | si_code = FPE_FLTINV; | |
267 | break; | |
268 | case 2: /* division by zero */ | |
269 | si_code = FPE_FLTDIV; | |
270 | break; | |
271 | case 3: /* overflow */ | |
272 | si_code = FPE_FLTOVF; | |
273 | break; | |
274 | case 4: /* underflow */ | |
275 | si_code = FPE_FLTUND; | |
276 | break; | |
277 | case 5: /* inexact */ | |
278 | si_code = FPE_FLTRES; | |
279 | break; | |
280 | default: /* unknown cause */ | |
281 | si_code = 0; | |
282 | } | |
283 | do_trap(regs, SIGFPE, si_code, "vector exception"); | |
284 | } | |
285 | ||
286 | static int __init disable_vector_extension(char *str) | |
287 | { | |
288 | S390_lowcore.machine_flags &= ~MACHINE_FLAG_VX; | |
289 | return 1; | |
290 | } | |
291 | __setup("novx", disable_vector_extension); | |
80703617 | 292 | |
b01a37a7 | 293 | void data_exception(struct pt_regs *regs) |
1da177e4 | 294 | { |
d2c993d8 | 295 | __u16 __user *location; |
1da177e4 LT |
296 | int signal = 0; |
297 | ||
d35339a4 | 298 | location = get_trap_ip(regs); |
1da177e4 | 299 | |
5a79859a | 300 | asm volatile("stfpc %0" : "=m" (current->thread.fp_regs.fpc)); |
80703617 MS |
301 | /* Check for vector register enablement */ |
302 | if (MACHINE_HAS_VX && !current->thread.vxrs && | |
303 | (current->thread.fp_regs.fpc & FPC_DXC_MASK) == 0xfe00) { | |
304 | alloc_vector_registers(current); | |
305 | /* Vector data exception is suppressing, rewind psw. */ | |
306 | regs->psw.addr = __rewind_psw(regs->psw, regs->int_code >> 16); | |
307 | clear_pt_regs_flag(regs, PIF_PER_TRAP); | |
308 | return; | |
309 | } | |
1da177e4 LT |
310 | if (current->thread.fp_regs.fpc & FPC_DXC_MASK) |
311 | signal = SIGFPE; | |
312 | else | |
313 | signal = SIGILL; | |
5a79859a | 314 | if (signal == SIGFPE) |
aa33c8cb MS |
315 | do_fp_trap(regs, current->thread.fp_regs.fpc); |
316 | else if (signal) | |
317 | do_trap(regs, signal, ILL_ILLOPN, "data exception"); | |
1da177e4 LT |
318 | } |
319 | ||
b01a37a7 | 320 | void space_switch_exception(struct pt_regs *regs) |
1da177e4 | 321 | { |
1da177e4 | 322 | /* Set user psw back to home space mode. */ |
7d256175 | 323 | if (user_mode(regs)) |
1da177e4 LT |
324 | regs->psw.mask |= PSW_ASC_HOME; |
325 | /* Send SIGILL. */ | |
aa33c8cb | 326 | do_trap(regs, SIGILL, ILL_PRVOPC, "space switch event"); |
1da177e4 LT |
327 | } |
328 | ||
7a5388de | 329 | void kernel_stack_overflow(struct pt_regs *regs) |
1da177e4 | 330 | { |
77eb65cb HC |
331 | bust_spinlocks(1); |
332 | printk("Kernel stack overflow.\n"); | |
333 | show_regs(regs); | |
334 | bust_spinlocks(0); | |
1da177e4 LT |
335 | panic("Corrupt kernel stack, can't continue."); |
336 | } | |
7a5388de | 337 | NOKPROBE_SYMBOL(kernel_stack_overflow); |
1da177e4 | 338 | |
1da177e4 LT |
339 | void __init trap_init(void) |
340 | { | |
f3e1a273 | 341 | local_mcck_enable(); |
1da177e4 | 342 | } |