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> |
a806170e | 21 | #include "entry.h" |
1da177e4 | 22 | |
02834eec | 23 | int show_unhandled_signals = 1; |
1da177e4 | 24 | |
d35339a4 MS |
25 | static inline void __user *get_trap_ip(struct pt_regs *regs) |
26 | { | |
27 | #ifdef CONFIG_64BIT | |
28 | unsigned long address; | |
29 | ||
30 | if (regs->int_code & 0x200) | |
31 | address = *(unsigned long *)(current->thread.trap_tdb + 24); | |
32 | else | |
33 | address = regs->psw.addr; | |
34 | return (void __user *) | |
35 | ((address - (regs->int_code >> 16)) & PSW_ADDR_INSN); | |
36 | #else | |
37 | return (void __user *) | |
38 | ((regs->psw.addr - (regs->int_code >> 16)) & PSW_ADDR_INSN); | |
39 | #endif | |
40 | } | |
41 | ||
aa33c8cb | 42 | static inline void report_user_fault(struct pt_regs *regs, int signr) |
1da177e4 | 43 | { |
ab3c68ee | 44 | if ((task_pid_nr(current) > 1) && !show_unhandled_signals) |
1da177e4 | 45 | return; |
ab3c68ee HC |
46 | if (!unhandled_signal(current, signr)) |
47 | return; | |
48 | if (!printk_ratelimit()) | |
49 | return; | |
aa33c8cb | 50 | printk("User process fault: interruption code 0x%X ", regs->int_code); |
ab3c68ee HC |
51 | print_vma_addr("in ", regs->psw.addr & PSW_ADDR_INSN); |
52 | printk("\n"); | |
1da177e4 | 53 | show_regs(regs); |
1da177e4 LT |
54 | } |
55 | ||
c0007f1a HC |
56 | int is_valid_bugaddr(unsigned long addr) |
57 | { | |
58 | return 1; | |
59 | } | |
60 | ||
aa33c8cb MS |
61 | static void __kprobes do_trap(struct pt_regs *regs, |
62 | int si_signo, int si_code, char *str) | |
63 | { | |
64 | siginfo_t info; | |
65 | ||
66 | if (notify_die(DIE_TRAP, str, regs, 0, | |
67 | regs->int_code, si_signo) == NOTIFY_STOP) | |
4ba069b8 MG |
68 | return; |
69 | ||
7d256175 | 70 | if (user_mode(regs)) { |
aa33c8cb MS |
71 | info.si_signo = si_signo; |
72 | info.si_errno = 0; | |
73 | info.si_code = si_code; | |
d35339a4 | 74 | info.si_addr = get_trap_ip(regs); |
aa33c8cb MS |
75 | force_sig_info(si_signo, &info, current); |
76 | report_user_fault(regs, si_signo); | |
1da177e4 LT |
77 | } else { |
78 | const struct exception_table_entry *fixup; | |
79 | fixup = search_exception_tables(regs->psw.addr & PSW_ADDR_INSN); | |
80 | if (fixup) | |
eb608fb3 | 81 | regs->psw.addr = extable_fixup(fixup) | PSW_ADDR_AMODE; |
c0007f1a HC |
82 | else { |
83 | enum bug_trap_type btt; | |
84 | ||
608e2619 | 85 | btt = report_bug(regs->psw.addr & PSW_ADDR_INSN, regs); |
c0007f1a HC |
86 | if (btt == BUG_TRAP_TYPE_WARN) |
87 | return; | |
aa33c8cb | 88 | die(regs, str); |
c0007f1a | 89 | } |
1da177e4 LT |
90 | } |
91 | } | |
92 | ||
5e9a2692 | 93 | void __kprobes do_per_trap(struct pt_regs *regs) |
1da177e4 | 94 | { |
73b7d40f MS |
95 | siginfo_t info; |
96 | ||
5e9a2692 | 97 | if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, SIGTRAP) == NOTIFY_STOP) |
4ba069b8 | 98 | return; |
73b7d40f MS |
99 | if (!current->ptrace) |
100 | return; | |
101 | info.si_signo = SIGTRAP; | |
102 | info.si_errno = 0; | |
103 | info.si_code = TRAP_HWBKPT; | |
3c52e49d MS |
104 | info.si_addr = |
105 | (void __force __user *) current->thread.per_event.address; | |
73b7d40f | 106 | force_sig_info(SIGTRAP, &info, current); |
1da177e4 LT |
107 | } |
108 | ||
b01a37a7 | 109 | void default_trap_handler(struct pt_regs *regs) |
1da177e4 | 110 | { |
7d256175 | 111 | if (user_mode(regs)) { |
aa33c8cb | 112 | report_user_fault(regs, SIGSEGV); |
6ea50968 | 113 | do_exit(SIGSEGV); |
1da177e4 | 114 | } else |
aa33c8cb | 115 | die(regs, "Unknown program exception"); |
1da177e4 LT |
116 | } |
117 | ||
1e54622e | 118 | #define DO_ERROR_INFO(name, signr, sicode, str) \ |
b01a37a7 HC |
119 | void name(struct pt_regs *regs) \ |
120 | { \ | |
121 | do_trap(regs, signr, sicode, str); \ | |
1da177e4 LT |
122 | } |
123 | ||
1e54622e MS |
124 | DO_ERROR_INFO(addressing_exception, SIGILL, ILL_ILLADR, |
125 | "addressing exception") | |
126 | DO_ERROR_INFO(execute_exception, SIGILL, ILL_ILLOPN, | |
127 | "execute exception") | |
128 | DO_ERROR_INFO(divide_exception, SIGFPE, FPE_INTDIV, | |
129 | "fixpoint divide exception") | |
130 | DO_ERROR_INFO(overflow_exception, SIGFPE, FPE_INTOVF, | |
131 | "fixpoint overflow exception") | |
132 | DO_ERROR_INFO(hfp_overflow_exception, SIGFPE, FPE_FLTOVF, | |
133 | "HFP overflow exception") | |
134 | DO_ERROR_INFO(hfp_underflow_exception, SIGFPE, FPE_FLTUND, | |
135 | "HFP underflow exception") | |
136 | DO_ERROR_INFO(hfp_significance_exception, SIGFPE, FPE_FLTRES, | |
137 | "HFP significance exception") | |
138 | DO_ERROR_INFO(hfp_divide_exception, SIGFPE, FPE_FLTDIV, | |
139 | "HFP divide exception") | |
140 | DO_ERROR_INFO(hfp_sqrt_exception, SIGFPE, FPE_FLTINV, | |
141 | "HFP square root exception") | |
142 | DO_ERROR_INFO(operand_exception, SIGILL, ILL_ILLOPN, | |
143 | "operand exception") | |
144 | DO_ERROR_INFO(privileged_op, SIGILL, ILL_PRVOPC, | |
145 | "privileged operation") | |
146 | DO_ERROR_INFO(special_op_exception, SIGILL, ILL_ILLOPN, | |
147 | "special operation exception") | |
148 | DO_ERROR_INFO(translation_exception, SIGILL, ILL_ILLOPN, | |
149 | "translation exception") | |
150 | ||
d35339a4 MS |
151 | #ifdef CONFIG_64BIT |
152 | DO_ERROR_INFO(transaction_exception, SIGILL, ILL_ILLOPN, | |
153 | "transaction constraint exception") | |
154 | #endif | |
155 | ||
aa33c8cb | 156 | static inline void do_fp_trap(struct pt_regs *regs, int fpc) |
1da177e4 | 157 | { |
aa33c8cb | 158 | int si_code = 0; |
1da177e4 LT |
159 | /* FPC[2] is Data Exception Code */ |
160 | if ((fpc & 0x00000300) == 0) { | |
161 | /* bits 6 and 7 of DXC are 0 iff IEEE exception */ | |
162 | if (fpc & 0x8000) /* invalid fp operation */ | |
aa33c8cb | 163 | si_code = FPE_FLTINV; |
1da177e4 | 164 | else if (fpc & 0x4000) /* div by 0 */ |
aa33c8cb | 165 | si_code = FPE_FLTDIV; |
1da177e4 | 166 | else if (fpc & 0x2000) /* overflow */ |
aa33c8cb | 167 | si_code = FPE_FLTOVF; |
1da177e4 | 168 | else if (fpc & 0x1000) /* underflow */ |
aa33c8cb | 169 | si_code = FPE_FLTUND; |
1da177e4 | 170 | else if (fpc & 0x0800) /* inexact */ |
aa33c8cb | 171 | si_code = FPE_FLTRES; |
1da177e4 | 172 | } |
aa33c8cb | 173 | do_trap(regs, SIGFPE, si_code, "floating point exception"); |
1da177e4 LT |
174 | } |
175 | ||
b01a37a7 | 176 | void __kprobes illegal_op(struct pt_regs *regs) |
1da177e4 LT |
177 | { |
178 | siginfo_t info; | |
179 | __u8 opcode[6]; | |
d2c993d8 | 180 | __u16 __user *location; |
1da177e4 LT |
181 | int signal = 0; |
182 | ||
d35339a4 | 183 | location = get_trap_ip(regs); |
1da177e4 | 184 | |
7d256175 | 185 | if (user_mode(regs)) { |
12bae235 HC |
186 | if (get_user(*((__u16 *) opcode), (__u16 __user *) location)) |
187 | return; | |
1da177e4 | 188 | if (*((__u16 *) opcode) == S390_BREAKPOINT_U16) { |
73b7d40f MS |
189 | if (current->ptrace) { |
190 | info.si_signo = SIGTRAP; | |
191 | info.si_errno = 0; | |
192 | info.si_code = TRAP_BRKPT; | |
193 | info.si_addr = location; | |
194 | force_sig_info(SIGTRAP, &info, current); | |
195 | } else | |
1da177e4 LT |
196 | signal = SIGILL; |
197 | #ifdef CONFIG_MATHEMU | |
198 | } else if (opcode[0] == 0xb3) { | |
12bae235 HC |
199 | if (get_user(*((__u16 *) (opcode+2)), location+1)) |
200 | return; | |
1da177e4 LT |
201 | signal = math_emu_b3(opcode, regs); |
202 | } else if (opcode[0] == 0xed) { | |
12bae235 HC |
203 | if (get_user(*((__u32 *) (opcode+2)), |
204 | (__u32 __user *)(location+1))) | |
205 | return; | |
1da177e4 LT |
206 | signal = math_emu_ed(opcode, regs); |
207 | } else if (*((__u16 *) opcode) == 0xb299) { | |
12bae235 HC |
208 | if (get_user(*((__u16 *) (opcode+2)), location+1)) |
209 | return; | |
1da177e4 LT |
210 | signal = math_emu_srnm(opcode, regs); |
211 | } else if (*((__u16 *) opcode) == 0xb29c) { | |
12bae235 HC |
212 | if (get_user(*((__u16 *) (opcode+2)), location+1)) |
213 | return; | |
1da177e4 LT |
214 | signal = math_emu_stfpc(opcode, regs); |
215 | } else if (*((__u16 *) opcode) == 0xb29d) { | |
12bae235 HC |
216 | if (get_user(*((__u16 *) (opcode+2)), location+1)) |
217 | return; | |
1da177e4 LT |
218 | signal = math_emu_lfpc(opcode, regs); |
219 | #endif | |
220 | } else | |
221 | signal = SIGILL; | |
35df8d53 HC |
222 | } else { |
223 | /* | |
224 | * If we get an illegal op in kernel mode, send it through the | |
225 | * kprobes notifier. If kprobes doesn't pick it up, SIGILL | |
226 | */ | |
aa33c8cb | 227 | if (notify_die(DIE_BPT, "bpt", regs, 0, |
35df8d53 HC |
228 | 3, SIGTRAP) != NOTIFY_STOP) |
229 | signal = SIGILL; | |
230 | } | |
1da177e4 LT |
231 | |
232 | #ifdef CONFIG_MATHEMU | |
233 | if (signal == SIGFPE) | |
aa33c8cb MS |
234 | do_fp_trap(regs, current->thread.fp_regs.fpc); |
235 | else if (signal == SIGSEGV) | |
236 | do_trap(regs, signal, SEGV_MAPERR, "user address fault"); | |
237 | else | |
1da177e4 | 238 | #endif |
aa33c8cb MS |
239 | if (signal) |
240 | do_trap(regs, signal, ILL_ILLOPC, "illegal operation"); | |
1da177e4 LT |
241 | } |
242 | ||
243 | ||
244 | #ifdef CONFIG_MATHEMU | |
aa33c8cb | 245 | void specification_exception(struct pt_regs *regs) |
1da177e4 LT |
246 | { |
247 | __u8 opcode[6]; | |
5a42b81f | 248 | __u16 __user *location = NULL; |
1da177e4 LT |
249 | int signal = 0; |
250 | ||
d35339a4 | 251 | location = (__u16 __user *) get_trap_ip(regs); |
1da177e4 | 252 | |
7d256175 | 253 | if (user_mode(regs)) { |
1da177e4 LT |
254 | get_user(*((__u16 *) opcode), location); |
255 | switch (opcode[0]) { | |
256 | case 0x28: /* LDR Rx,Ry */ | |
257 | signal = math_emu_ldr(opcode); | |
258 | break; | |
259 | case 0x38: /* LER Rx,Ry */ | |
260 | signal = math_emu_ler(opcode); | |
261 | break; | |
262 | case 0x60: /* STD R,D(X,B) */ | |
263 | get_user(*((__u16 *) (opcode+2)), location+1); | |
264 | signal = math_emu_std(opcode, regs); | |
265 | break; | |
266 | case 0x68: /* LD R,D(X,B) */ | |
267 | get_user(*((__u16 *) (opcode+2)), location+1); | |
268 | signal = math_emu_ld(opcode, regs); | |
269 | break; | |
270 | case 0x70: /* STE R,D(X,B) */ | |
271 | get_user(*((__u16 *) (opcode+2)), location+1); | |
272 | signal = math_emu_ste(opcode, regs); | |
273 | break; | |
274 | case 0x78: /* LE R,D(X,B) */ | |
275 | get_user(*((__u16 *) (opcode+2)), location+1); | |
276 | signal = math_emu_le(opcode, regs); | |
277 | break; | |
278 | default: | |
279 | signal = SIGILL; | |
280 | break; | |
281 | } | |
282 | } else | |
283 | signal = SIGILL; | |
284 | ||
285 | if (signal == SIGFPE) | |
aa33c8cb MS |
286 | do_fp_trap(regs, current->thread.fp_regs.fpc); |
287 | else if (signal) | |
288 | do_trap(regs, signal, ILL_ILLOPN, "specification exception"); | |
1da177e4 LT |
289 | } |
290 | #else | |
1e54622e MS |
291 | DO_ERROR_INFO(specification_exception, SIGILL, ILL_ILLOPN, |
292 | "specification exception"); | |
1da177e4 LT |
293 | #endif |
294 | ||
b01a37a7 | 295 | void data_exception(struct pt_regs *regs) |
1da177e4 | 296 | { |
d2c993d8 | 297 | __u16 __user *location; |
1da177e4 LT |
298 | int signal = 0; |
299 | ||
d35339a4 | 300 | location = get_trap_ip(regs); |
1da177e4 LT |
301 | |
302 | if (MACHINE_HAS_IEEE) | |
94c12cc7 | 303 | asm volatile("stfpc %0" : "=m" (current->thread.fp_regs.fpc)); |
1da177e4 LT |
304 | |
305 | #ifdef CONFIG_MATHEMU | |
7d256175 | 306 | else if (user_mode(regs)) { |
1da177e4 LT |
307 | __u8 opcode[6]; |
308 | get_user(*((__u16 *) opcode), location); | |
309 | switch (opcode[0]) { | |
310 | case 0x28: /* LDR Rx,Ry */ | |
311 | signal = math_emu_ldr(opcode); | |
312 | break; | |
313 | case 0x38: /* LER Rx,Ry */ | |
314 | signal = math_emu_ler(opcode); | |
315 | break; | |
316 | case 0x60: /* STD R,D(X,B) */ | |
317 | get_user(*((__u16 *) (opcode+2)), location+1); | |
318 | signal = math_emu_std(opcode, regs); | |
319 | break; | |
320 | case 0x68: /* LD R,D(X,B) */ | |
321 | get_user(*((__u16 *) (opcode+2)), location+1); | |
322 | signal = math_emu_ld(opcode, regs); | |
323 | break; | |
324 | case 0x70: /* STE R,D(X,B) */ | |
325 | get_user(*((__u16 *) (opcode+2)), location+1); | |
326 | signal = math_emu_ste(opcode, regs); | |
327 | break; | |
328 | case 0x78: /* LE R,D(X,B) */ | |
329 | get_user(*((__u16 *) (opcode+2)), location+1); | |
330 | signal = math_emu_le(opcode, regs); | |
331 | break; | |
332 | case 0xb3: | |
333 | get_user(*((__u16 *) (opcode+2)), location+1); | |
334 | signal = math_emu_b3(opcode, regs); | |
335 | break; | |
336 | case 0xed: | |
337 | get_user(*((__u32 *) (opcode+2)), | |
5a42b81f | 338 | (__u32 __user *)(location+1)); |
1da177e4 LT |
339 | signal = math_emu_ed(opcode, regs); |
340 | break; | |
341 | case 0xb2: | |
342 | if (opcode[1] == 0x99) { | |
343 | get_user(*((__u16 *) (opcode+2)), location+1); | |
344 | signal = math_emu_srnm(opcode, regs); | |
345 | } else if (opcode[1] == 0x9c) { | |
346 | get_user(*((__u16 *) (opcode+2)), location+1); | |
347 | signal = math_emu_stfpc(opcode, regs); | |
348 | } else if (opcode[1] == 0x9d) { | |
349 | get_user(*((__u16 *) (opcode+2)), location+1); | |
350 | signal = math_emu_lfpc(opcode, regs); | |
351 | } else | |
352 | signal = SIGILL; | |
353 | break; | |
354 | default: | |
355 | signal = SIGILL; | |
356 | break; | |
357 | } | |
358 | } | |
359 | #endif | |
360 | if (current->thread.fp_regs.fpc & FPC_DXC_MASK) | |
361 | signal = SIGFPE; | |
362 | else | |
363 | signal = SIGILL; | |
364 | if (signal == SIGFPE) | |
aa33c8cb MS |
365 | do_fp_trap(regs, current->thread.fp_regs.fpc); |
366 | else if (signal) | |
367 | do_trap(regs, signal, ILL_ILLOPN, "data exception"); | |
1da177e4 LT |
368 | } |
369 | ||
b01a37a7 | 370 | void space_switch_exception(struct pt_regs *regs) |
1da177e4 | 371 | { |
1da177e4 | 372 | /* Set user psw back to home space mode. */ |
7d256175 | 373 | if (user_mode(regs)) |
1da177e4 LT |
374 | regs->psw.mask |= PSW_ASC_HOME; |
375 | /* Send SIGILL. */ | |
aa33c8cb | 376 | do_trap(regs, SIGILL, ILL_PRVOPC, "space switch event"); |
1da177e4 LT |
377 | } |
378 | ||
fdb204d1 | 379 | void __kprobes kernel_stack_overflow(struct pt_regs * regs) |
1da177e4 | 380 | { |
77eb65cb HC |
381 | bust_spinlocks(1); |
382 | printk("Kernel stack overflow.\n"); | |
383 | show_regs(regs); | |
384 | bust_spinlocks(0); | |
1da177e4 LT |
385 | panic("Corrupt kernel stack, can't continue."); |
386 | } | |
387 | ||
1da177e4 LT |
388 | void __init trap_init(void) |
389 | { | |
f3e1a273 | 390 | local_mcck_enable(); |
1da177e4 | 391 | } |