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