Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * arch/s390/kernel/traps.c | |
3 | * | |
4 | * S390 version | |
5 | * Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation | |
6 | * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com), | |
7 | * Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com), | |
8 | * | |
9 | * Derived from "arch/i386/kernel/traps.c" | |
10 | * Copyright (C) 1991, 1992 Linus Torvalds | |
11 | */ | |
12 | ||
13 | /* | |
14 | * 'Traps.c' handles hardware traps and faults after we have saved some | |
15 | * state in 'asm.s'. | |
16 | */ | |
1da177e4 LT |
17 | #include <linux/sched.h> |
18 | #include <linux/kernel.h> | |
19 | #include <linux/string.h> | |
20 | #include <linux/errno.h> | |
21 | #include <linux/ptrace.h> | |
22 | #include <linux/timer.h> | |
23 | #include <linux/mm.h> | |
24 | #include <linux/smp.h> | |
25 | #include <linux/smp_lock.h> | |
26 | #include <linux/init.h> | |
27 | #include <linux/interrupt.h> | |
28 | #include <linux/delay.h> | |
29 | #include <linux/module.h> | |
30 | #include <linux/kallsyms.h> | |
5d3f229f | 31 | #include <linux/reboot.h> |
4ba069b8 | 32 | #include <linux/kprobes.h> |
1da177e4 LT |
33 | |
34 | #include <asm/system.h> | |
35 | #include <asm/uaccess.h> | |
36 | #include <asm/io.h> | |
37 | #include <asm/atomic.h> | |
38 | #include <asm/mathemu.h> | |
39 | #include <asm/cpcmd.h> | |
40 | #include <asm/s390_ext.h> | |
41 | #include <asm/lowcore.h> | |
42 | #include <asm/debug.h> | |
4ba069b8 | 43 | #include <asm/kdebug.h> |
1da177e4 LT |
44 | |
45 | /* Called from entry.S only */ | |
46 | extern void handle_per_exception(struct pt_regs *regs); | |
47 | ||
48 | typedef void pgm_check_handler_t(struct pt_regs *, long); | |
49 | pgm_check_handler_t *pgm_check_table[128]; | |
50 | ||
51 | #ifdef CONFIG_SYSCTL | |
52 | #ifdef CONFIG_PROCESS_DEBUG | |
53 | int sysctl_userprocess_debug = 1; | |
54 | #else | |
55 | int sysctl_userprocess_debug = 0; | |
56 | #endif | |
57 | #endif | |
58 | ||
59 | extern pgm_check_handler_t do_protection_exception; | |
60 | extern pgm_check_handler_t do_dat_exception; | |
1da177e4 LT |
61 | #ifdef CONFIG_PFAULT |
62 | extern int pfault_init(void); | |
63 | extern void pfault_fini(void); | |
64 | extern void pfault_interrupt(struct pt_regs *regs, __u16 error_code); | |
65 | static ext_int_info_t ext_int_pfault; | |
66 | #endif | |
67 | extern pgm_check_handler_t do_monitor_call; | |
68 | ||
69 | #define stack_pointer ({ void **sp; asm("la %0,0(15)" : "=&d" (sp)); sp; }) | |
70 | ||
347a8dc3 | 71 | #ifndef CONFIG_64BIT |
1da177e4 LT |
72 | #define FOURLONG "%08lx %08lx %08lx %08lx\n" |
73 | static int kstack_depth_to_print = 12; | |
347a8dc3 | 74 | #else /* CONFIG_64BIT */ |
1da177e4 LT |
75 | #define FOURLONG "%016lx %016lx %016lx %016lx\n" |
76 | static int kstack_depth_to_print = 20; | |
347a8dc3 | 77 | #endif /* CONFIG_64BIT */ |
1da177e4 | 78 | |
4ba069b8 MG |
79 | ATOMIC_NOTIFIER_HEAD(s390die_chain); |
80 | ||
81 | int register_die_notifier(struct notifier_block *nb) | |
82 | { | |
83 | return atomic_notifier_chain_register(&s390die_chain, nb); | |
84 | } | |
85 | EXPORT_SYMBOL(register_die_notifier); | |
86 | ||
87 | int unregister_die_notifier(struct notifier_block *nb) | |
88 | { | |
89 | return atomic_notifier_chain_unregister(&s390die_chain, nb); | |
90 | } | |
91 | EXPORT_SYMBOL(unregister_die_notifier); | |
92 | ||
1da177e4 LT |
93 | /* |
94 | * For show_trace we have tree different stack to consider: | |
95 | * - the panic stack which is used if the kernel stack has overflown | |
96 | * - the asynchronous interrupt stack (cpu related) | |
97 | * - the synchronous kernel stack (process related) | |
98 | * The stack trace can start at any of the three stack and can potentially | |
99 | * touch all of them. The order is: panic stack, async stack, sync stack. | |
100 | */ | |
101 | static unsigned long | |
102 | __show_trace(unsigned long sp, unsigned long low, unsigned long high) | |
103 | { | |
104 | struct stack_frame *sf; | |
105 | struct pt_regs *regs; | |
106 | ||
107 | while (1) { | |
108 | sp = sp & PSW_ADDR_INSN; | |
109 | if (sp < low || sp > high - sizeof(*sf)) | |
110 | return sp; | |
111 | sf = (struct stack_frame *) sp; | |
112 | printk("([<%016lx>] ", sf->gprs[8] & PSW_ADDR_INSN); | |
113 | print_symbol("%s)\n", sf->gprs[8] & PSW_ADDR_INSN); | |
114 | /* Follow the backchain. */ | |
115 | while (1) { | |
116 | low = sp; | |
117 | sp = sf->back_chain & PSW_ADDR_INSN; | |
118 | if (!sp) | |
119 | break; | |
120 | if (sp <= low || sp > high - sizeof(*sf)) | |
121 | return sp; | |
122 | sf = (struct stack_frame *) sp; | |
123 | printk(" [<%016lx>] ", sf->gprs[8] & PSW_ADDR_INSN); | |
124 | print_symbol("%s\n", sf->gprs[8] & PSW_ADDR_INSN); | |
125 | } | |
126 | /* Zero backchain detected, check for interrupt frame. */ | |
127 | sp = (unsigned long) (sf + 1); | |
128 | if (sp <= low || sp > high - sizeof(*regs)) | |
129 | return sp; | |
130 | regs = (struct pt_regs *) sp; | |
131 | printk(" [<%016lx>] ", regs->psw.addr & PSW_ADDR_INSN); | |
132 | print_symbol("%s\n", regs->psw.addr & PSW_ADDR_INSN); | |
133 | low = sp; | |
134 | sp = regs->gprs[15]; | |
135 | } | |
136 | } | |
137 | ||
138 | void show_trace(struct task_struct *task, unsigned long * stack) | |
139 | { | |
140 | register unsigned long __r15 asm ("15"); | |
141 | unsigned long sp; | |
142 | ||
143 | sp = (unsigned long) stack; | |
144 | if (!sp) | |
145 | sp = task ? task->thread.ksp : __r15; | |
146 | printk("Call Trace:\n"); | |
147 | #ifdef CONFIG_CHECK_STACK | |
148 | sp = __show_trace(sp, S390_lowcore.panic_stack - 4096, | |
149 | S390_lowcore.panic_stack); | |
150 | #endif | |
151 | sp = __show_trace(sp, S390_lowcore.async_stack - ASYNC_SIZE, | |
152 | S390_lowcore.async_stack); | |
153 | if (task) | |
30af7120 AV |
154 | __show_trace(sp, (unsigned long) task_stack_page(task), |
155 | (unsigned long) task_stack_page(task) + THREAD_SIZE); | |
1da177e4 LT |
156 | else |
157 | __show_trace(sp, S390_lowcore.thread_info, | |
158 | S390_lowcore.thread_info + THREAD_SIZE); | |
159 | printk("\n"); | |
160 | } | |
161 | ||
162 | void show_stack(struct task_struct *task, unsigned long *sp) | |
163 | { | |
164 | register unsigned long * __r15 asm ("15"); | |
165 | unsigned long *stack; | |
166 | int i; | |
167 | ||
1da177e4 | 168 | if (!sp) |
73805343 HC |
169 | stack = task ? (unsigned long *) task->thread.ksp : __r15; |
170 | else | |
171 | stack = sp; | |
1da177e4 | 172 | |
1da177e4 LT |
173 | for (i = 0; i < kstack_depth_to_print; i++) { |
174 | if (((addr_t) stack & (THREAD_SIZE-1)) == 0) | |
175 | break; | |
176 | if (i && ((i * sizeof (long) % 32) == 0)) | |
177 | printk("\n "); | |
178 | printk("%p ", (void *)*stack++); | |
179 | } | |
180 | printk("\n"); | |
181 | show_trace(task, sp); | |
182 | } | |
183 | ||
184 | /* | |
185 | * The architecture-independent dump_stack generator | |
186 | */ | |
187 | void dump_stack(void) | |
188 | { | |
d2c993d8 | 189 | show_stack(NULL, NULL); |
1da177e4 LT |
190 | } |
191 | ||
192 | EXPORT_SYMBOL(dump_stack); | |
193 | ||
194 | void show_registers(struct pt_regs *regs) | |
195 | { | |
196 | mm_segment_t old_fs; | |
197 | char *mode; | |
198 | int i; | |
199 | ||
200 | mode = (regs->psw.mask & PSW_MASK_PSTATE) ? "User" : "Krnl"; | |
201 | printk("%s PSW : %p %p", | |
202 | mode, (void *) regs->psw.mask, | |
203 | (void *) regs->psw.addr); | |
204 | print_symbol(" (%s)\n", regs->psw.addr & PSW_ADDR_INSN); | |
205 | printk("%s GPRS: " FOURLONG, mode, | |
206 | regs->gprs[0], regs->gprs[1], regs->gprs[2], regs->gprs[3]); | |
207 | printk(" " FOURLONG, | |
208 | regs->gprs[4], regs->gprs[5], regs->gprs[6], regs->gprs[7]); | |
209 | printk(" " FOURLONG, | |
210 | regs->gprs[8], regs->gprs[9], regs->gprs[10], regs->gprs[11]); | |
211 | printk(" " FOURLONG, | |
212 | regs->gprs[12], regs->gprs[13], regs->gprs[14], regs->gprs[15]); | |
213 | ||
214 | #if 0 | |
215 | /* FIXME: this isn't needed any more but it changes the ksymoops | |
216 | * input. To remove or not to remove ... */ | |
217 | save_access_regs(regs->acrs); | |
218 | printk("%s ACRS: %08x %08x %08x %08x\n", mode, | |
219 | regs->acrs[0], regs->acrs[1], regs->acrs[2], regs->acrs[3]); | |
220 | printk(" %08x %08x %08x %08x\n", | |
221 | regs->acrs[4], regs->acrs[5], regs->acrs[6], regs->acrs[7]); | |
222 | printk(" %08x %08x %08x %08x\n", | |
223 | regs->acrs[8], regs->acrs[9], regs->acrs[10], regs->acrs[11]); | |
224 | printk(" %08x %08x %08x %08x\n", | |
225 | regs->acrs[12], regs->acrs[13], regs->acrs[14], regs->acrs[15]); | |
226 | #endif | |
227 | ||
228 | /* | |
229 | * Print the first 20 byte of the instruction stream at the | |
230 | * time of the fault. | |
231 | */ | |
232 | old_fs = get_fs(); | |
233 | if (regs->psw.mask & PSW_MASK_PSTATE) | |
234 | set_fs(USER_DS); | |
235 | else | |
236 | set_fs(KERNEL_DS); | |
237 | printk("%s Code: ", mode); | |
238 | for (i = 0; i < 20; i++) { | |
239 | unsigned char c; | |
240 | if (__get_user(c, (char __user *)(regs->psw.addr + i))) { | |
241 | printk(" Bad PSW."); | |
242 | break; | |
243 | } | |
244 | printk("%02x ", c); | |
245 | } | |
246 | set_fs(old_fs); | |
247 | ||
248 | printk("\n"); | |
249 | } | |
250 | ||
251 | /* This is called from fs/proc/array.c */ | |
252 | char *task_show_regs(struct task_struct *task, char *buffer) | |
253 | { | |
254 | struct pt_regs *regs; | |
255 | ||
c7584fb6 | 256 | regs = task_pt_regs(task); |
1da177e4 LT |
257 | buffer += sprintf(buffer, "task: %p, ksp: %p\n", |
258 | task, (void *)task->thread.ksp); | |
259 | buffer += sprintf(buffer, "User PSW : %p %p\n", | |
260 | (void *) regs->psw.mask, (void *)regs->psw.addr); | |
261 | ||
262 | buffer += sprintf(buffer, "User GPRS: " FOURLONG, | |
263 | regs->gprs[0], regs->gprs[1], | |
264 | regs->gprs[2], regs->gprs[3]); | |
265 | buffer += sprintf(buffer, " " FOURLONG, | |
266 | regs->gprs[4], regs->gprs[5], | |
267 | regs->gprs[6], regs->gprs[7]); | |
268 | buffer += sprintf(buffer, " " FOURLONG, | |
269 | regs->gprs[8], regs->gprs[9], | |
270 | regs->gprs[10], regs->gprs[11]); | |
271 | buffer += sprintf(buffer, " " FOURLONG, | |
272 | regs->gprs[12], regs->gprs[13], | |
273 | regs->gprs[14], regs->gprs[15]); | |
274 | buffer += sprintf(buffer, "User ACRS: %08x %08x %08x %08x\n", | |
275 | task->thread.acrs[0], task->thread.acrs[1], | |
276 | task->thread.acrs[2], task->thread.acrs[3]); | |
277 | buffer += sprintf(buffer, " %08x %08x %08x %08x\n", | |
278 | task->thread.acrs[4], task->thread.acrs[5], | |
279 | task->thread.acrs[6], task->thread.acrs[7]); | |
280 | buffer += sprintf(buffer, " %08x %08x %08x %08x\n", | |
281 | task->thread.acrs[8], task->thread.acrs[9], | |
282 | task->thread.acrs[10], task->thread.acrs[11]); | |
283 | buffer += sprintf(buffer, " %08x %08x %08x %08x\n", | |
284 | task->thread.acrs[12], task->thread.acrs[13], | |
285 | task->thread.acrs[14], task->thread.acrs[15]); | |
286 | return buffer; | |
287 | } | |
288 | ||
289 | DEFINE_SPINLOCK(die_lock); | |
290 | ||
291 | void die(const char * str, struct pt_regs * regs, long err) | |
292 | { | |
293 | static int die_counter; | |
294 | ||
295 | debug_stop_all(); | |
296 | console_verbose(); | |
297 | spin_lock_irq(&die_lock); | |
298 | bust_spinlocks(1); | |
299 | printk("%s: %04lx [#%d]\n", str, err & 0xffff, ++die_counter); | |
300 | show_regs(regs); | |
301 | bust_spinlocks(0); | |
302 | spin_unlock_irq(&die_lock); | |
303 | if (in_interrupt()) | |
304 | panic("Fatal exception in interrupt"); | |
305 | if (panic_on_oops) | |
306 | panic("Fatal exception: panic_on_oops"); | |
307 | do_exit(SIGSEGV); | |
308 | } | |
309 | ||
310 | static void inline | |
311 | report_user_fault(long interruption_code, struct pt_regs *regs) | |
312 | { | |
313 | #if defined(CONFIG_SYSCTL) | |
314 | if (!sysctl_userprocess_debug) | |
315 | return; | |
316 | #endif | |
317 | #if defined(CONFIG_SYSCTL) || defined(CONFIG_PROCESS_DEBUG) | |
318 | printk("User process fault: interruption code 0x%lX\n", | |
319 | interruption_code); | |
320 | show_regs(regs); | |
321 | #endif | |
322 | } | |
323 | ||
4ba069b8 MG |
324 | static void __kprobes inline do_trap(long interruption_code, int signr, |
325 | char *str, struct pt_regs *regs, | |
326 | siginfo_t *info) | |
1da177e4 LT |
327 | { |
328 | /* | |
329 | * We got all needed information from the lowcore and can | |
330 | * now safely switch on interrupts. | |
331 | */ | |
332 | if (regs->psw.mask & PSW_MASK_PSTATE) | |
333 | local_irq_enable(); | |
334 | ||
4ba069b8 MG |
335 | if (notify_die(DIE_TRAP, str, regs, interruption_code, |
336 | interruption_code, signr) == NOTIFY_STOP) | |
337 | return; | |
338 | ||
1da177e4 LT |
339 | if (regs->psw.mask & PSW_MASK_PSTATE) { |
340 | struct task_struct *tsk = current; | |
341 | ||
342 | tsk->thread.trap_no = interruption_code & 0xffff; | |
343 | force_sig_info(signr, info, tsk); | |
344 | report_user_fault(interruption_code, regs); | |
345 | } else { | |
346 | const struct exception_table_entry *fixup; | |
347 | fixup = search_exception_tables(regs->psw.addr & PSW_ADDR_INSN); | |
348 | if (fixup) | |
349 | regs->psw.addr = fixup->fixup | PSW_ADDR_AMODE; | |
350 | else | |
351 | die(str, regs, interruption_code); | |
352 | } | |
353 | } | |
354 | ||
d2c993d8 | 355 | static inline void __user *get_check_address(struct pt_regs *regs) |
1da177e4 | 356 | { |
d2c993d8 | 357 | return (void __user *)((regs->psw.addr-S390_lowcore.pgm_ilc) & PSW_ADDR_INSN); |
1da177e4 LT |
358 | } |
359 | ||
4ba069b8 | 360 | void __kprobes do_single_step(struct pt_regs *regs) |
1da177e4 | 361 | { |
4ba069b8 MG |
362 | if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, |
363 | SIGTRAP) == NOTIFY_STOP){ | |
364 | return; | |
365 | } | |
1da177e4 LT |
366 | if ((current->ptrace & PT_PTRACED) != 0) |
367 | force_sig(SIGTRAP, current); | |
368 | } | |
369 | ||
370 | asmlinkage void | |
371 | default_trap_handler(struct pt_regs * regs, long interruption_code) | |
372 | { | |
373 | if (regs->psw.mask & PSW_MASK_PSTATE) { | |
374 | local_irq_enable(); | |
375 | do_exit(SIGSEGV); | |
376 | report_user_fault(interruption_code, regs); | |
377 | } else | |
378 | die("Unknown program exception", regs, interruption_code); | |
379 | } | |
380 | ||
381 | #define DO_ERROR_INFO(signr, str, name, sicode, siaddr) \ | |
382 | asmlinkage void name(struct pt_regs * regs, long interruption_code) \ | |
383 | { \ | |
384 | siginfo_t info; \ | |
385 | info.si_signo = signr; \ | |
386 | info.si_errno = 0; \ | |
387 | info.si_code = sicode; \ | |
d2c993d8 | 388 | info.si_addr = siaddr; \ |
1da177e4 LT |
389 | do_trap(interruption_code, signr, str, regs, &info); \ |
390 | } | |
391 | ||
392 | DO_ERROR_INFO(SIGILL, "addressing exception", addressing_exception, | |
393 | ILL_ILLADR, get_check_address(regs)) | |
394 | DO_ERROR_INFO(SIGILL, "execute exception", execute_exception, | |
395 | ILL_ILLOPN, get_check_address(regs)) | |
396 | DO_ERROR_INFO(SIGFPE, "fixpoint divide exception", divide_exception, | |
397 | FPE_INTDIV, get_check_address(regs)) | |
398 | DO_ERROR_INFO(SIGFPE, "fixpoint overflow exception", overflow_exception, | |
399 | FPE_INTOVF, get_check_address(regs)) | |
400 | DO_ERROR_INFO(SIGFPE, "HFP overflow exception", hfp_overflow_exception, | |
401 | FPE_FLTOVF, get_check_address(regs)) | |
402 | DO_ERROR_INFO(SIGFPE, "HFP underflow exception", hfp_underflow_exception, | |
403 | FPE_FLTUND, get_check_address(regs)) | |
404 | DO_ERROR_INFO(SIGFPE, "HFP significance exception", hfp_significance_exception, | |
405 | FPE_FLTRES, get_check_address(regs)) | |
406 | DO_ERROR_INFO(SIGFPE, "HFP divide exception", hfp_divide_exception, | |
407 | FPE_FLTDIV, get_check_address(regs)) | |
408 | DO_ERROR_INFO(SIGFPE, "HFP square root exception", hfp_sqrt_exception, | |
409 | FPE_FLTINV, get_check_address(regs)) | |
410 | DO_ERROR_INFO(SIGILL, "operand exception", operand_exception, | |
411 | ILL_ILLOPN, get_check_address(regs)) | |
412 | DO_ERROR_INFO(SIGILL, "privileged operation", privileged_op, | |
413 | ILL_PRVOPC, get_check_address(regs)) | |
414 | DO_ERROR_INFO(SIGILL, "special operation exception", special_op_exception, | |
415 | ILL_ILLOPN, get_check_address(regs)) | |
416 | DO_ERROR_INFO(SIGILL, "translation exception", translation_exception, | |
417 | ILL_ILLOPN, get_check_address(regs)) | |
418 | ||
419 | static inline void | |
d2c993d8 | 420 | do_fp_trap(struct pt_regs *regs, void __user *location, |
1da177e4 LT |
421 | int fpc, long interruption_code) |
422 | { | |
423 | siginfo_t si; | |
424 | ||
425 | si.si_signo = SIGFPE; | |
426 | si.si_errno = 0; | |
427 | si.si_addr = location; | |
428 | si.si_code = 0; | |
429 | /* FPC[2] is Data Exception Code */ | |
430 | if ((fpc & 0x00000300) == 0) { | |
431 | /* bits 6 and 7 of DXC are 0 iff IEEE exception */ | |
432 | if (fpc & 0x8000) /* invalid fp operation */ | |
433 | si.si_code = FPE_FLTINV; | |
434 | else if (fpc & 0x4000) /* div by 0 */ | |
435 | si.si_code = FPE_FLTDIV; | |
436 | else if (fpc & 0x2000) /* overflow */ | |
437 | si.si_code = FPE_FLTOVF; | |
438 | else if (fpc & 0x1000) /* underflow */ | |
439 | si.si_code = FPE_FLTUND; | |
440 | else if (fpc & 0x0800) /* inexact */ | |
441 | si.si_code = FPE_FLTRES; | |
442 | } | |
443 | current->thread.ieee_instruction_pointer = (addr_t) location; | |
444 | do_trap(interruption_code, SIGFPE, | |
445 | "floating point exception", regs, &si); | |
446 | } | |
447 | ||
448 | asmlinkage void illegal_op(struct pt_regs * regs, long interruption_code) | |
449 | { | |
450 | siginfo_t info; | |
451 | __u8 opcode[6]; | |
d2c993d8 | 452 | __u16 __user *location; |
1da177e4 LT |
453 | int signal = 0; |
454 | ||
d2c993d8 | 455 | location = get_check_address(regs); |
1da177e4 LT |
456 | |
457 | /* | |
458 | * We got all needed information from the lowcore and can | |
459 | * now safely switch on interrupts. | |
460 | */ | |
461 | if (regs->psw.mask & PSW_MASK_PSTATE) | |
462 | local_irq_enable(); | |
463 | ||
464 | if (regs->psw.mask & PSW_MASK_PSTATE) { | |
465 | get_user(*((__u16 *) opcode), (__u16 __user *) location); | |
466 | if (*((__u16 *) opcode) == S390_BREAKPOINT_U16) { | |
467 | if (current->ptrace & PT_PTRACED) | |
468 | force_sig(SIGTRAP, current); | |
469 | else | |
470 | signal = SIGILL; | |
471 | #ifdef CONFIG_MATHEMU | |
472 | } else if (opcode[0] == 0xb3) { | |
473 | get_user(*((__u16 *) (opcode+2)), location+1); | |
474 | signal = math_emu_b3(opcode, regs); | |
475 | } else if (opcode[0] == 0xed) { | |
476 | get_user(*((__u32 *) (opcode+2)), | |
477 | (__u32 *)(location+1)); | |
478 | signal = math_emu_ed(opcode, regs); | |
479 | } else if (*((__u16 *) opcode) == 0xb299) { | |
480 | get_user(*((__u16 *) (opcode+2)), location+1); | |
481 | signal = math_emu_srnm(opcode, regs); | |
482 | } else if (*((__u16 *) opcode) == 0xb29c) { | |
483 | get_user(*((__u16 *) (opcode+2)), location+1); | |
484 | signal = math_emu_stfpc(opcode, regs); | |
485 | } else if (*((__u16 *) opcode) == 0xb29d) { | |
486 | get_user(*((__u16 *) (opcode+2)), location+1); | |
487 | signal = math_emu_lfpc(opcode, regs); | |
488 | #endif | |
489 | } else | |
490 | signal = SIGILL; | |
491 | } else | |
492 | signal = SIGILL; | |
493 | ||
494 | #ifdef CONFIG_MATHEMU | |
495 | if (signal == SIGFPE) | |
496 | do_fp_trap(regs, location, | |
497 | current->thread.fp_regs.fpc, interruption_code); | |
498 | else if (signal == SIGSEGV) { | |
499 | info.si_signo = signal; | |
500 | info.si_errno = 0; | |
501 | info.si_code = SEGV_MAPERR; | |
502 | info.si_addr = (void *) location; | |
503 | do_trap(interruption_code, signal, | |
504 | "user address fault", regs, &info); | |
505 | } else | |
506 | #endif | |
507 | if (signal) { | |
508 | info.si_signo = signal; | |
509 | info.si_errno = 0; | |
510 | info.si_code = ILL_ILLOPC; | |
793af244 | 511 | info.si_addr = (void __user *) location; |
1da177e4 LT |
512 | do_trap(interruption_code, signal, |
513 | "illegal operation", regs, &info); | |
514 | } | |
515 | } | |
516 | ||
517 | ||
518 | #ifdef CONFIG_MATHEMU | |
519 | asmlinkage void | |
520 | specification_exception(struct pt_regs * regs, long interruption_code) | |
521 | { | |
522 | __u8 opcode[6]; | |
523 | __u16 *location = NULL; | |
524 | int signal = 0; | |
525 | ||
526 | location = (__u16 *) get_check_address(regs); | |
527 | ||
528 | /* | |
529 | * We got all needed information from the lowcore and can | |
530 | * now safely switch on interrupts. | |
531 | */ | |
532 | if (regs->psw.mask & PSW_MASK_PSTATE) | |
533 | local_irq_enable(); | |
534 | ||
535 | if (regs->psw.mask & PSW_MASK_PSTATE) { | |
536 | get_user(*((__u16 *) opcode), location); | |
537 | switch (opcode[0]) { | |
538 | case 0x28: /* LDR Rx,Ry */ | |
539 | signal = math_emu_ldr(opcode); | |
540 | break; | |
541 | case 0x38: /* LER Rx,Ry */ | |
542 | signal = math_emu_ler(opcode); | |
543 | break; | |
544 | case 0x60: /* STD R,D(X,B) */ | |
545 | get_user(*((__u16 *) (opcode+2)), location+1); | |
546 | signal = math_emu_std(opcode, regs); | |
547 | break; | |
548 | case 0x68: /* LD R,D(X,B) */ | |
549 | get_user(*((__u16 *) (opcode+2)), location+1); | |
550 | signal = math_emu_ld(opcode, regs); | |
551 | break; | |
552 | case 0x70: /* STE R,D(X,B) */ | |
553 | get_user(*((__u16 *) (opcode+2)), location+1); | |
554 | signal = math_emu_ste(opcode, regs); | |
555 | break; | |
556 | case 0x78: /* LE R,D(X,B) */ | |
557 | get_user(*((__u16 *) (opcode+2)), location+1); | |
558 | signal = math_emu_le(opcode, regs); | |
559 | break; | |
560 | default: | |
561 | signal = SIGILL; | |
562 | break; | |
563 | } | |
564 | } else | |
565 | signal = SIGILL; | |
566 | ||
567 | if (signal == SIGFPE) | |
568 | do_fp_trap(regs, location, | |
569 | current->thread.fp_regs.fpc, interruption_code); | |
570 | else if (signal) { | |
571 | siginfo_t info; | |
572 | info.si_signo = signal; | |
573 | info.si_errno = 0; | |
574 | info.si_code = ILL_ILLOPN; | |
575 | info.si_addr = location; | |
576 | do_trap(interruption_code, signal, | |
577 | "specification exception", regs, &info); | |
578 | } | |
579 | } | |
580 | #else | |
581 | DO_ERROR_INFO(SIGILL, "specification exception", specification_exception, | |
582 | ILL_ILLOPN, get_check_address(regs)); | |
583 | #endif | |
584 | ||
585 | asmlinkage void data_exception(struct pt_regs * regs, long interruption_code) | |
586 | { | |
d2c993d8 | 587 | __u16 __user *location; |
1da177e4 LT |
588 | int signal = 0; |
589 | ||
d2c993d8 | 590 | location = get_check_address(regs); |
1da177e4 LT |
591 | |
592 | /* | |
593 | * We got all needed information from the lowcore and can | |
594 | * now safely switch on interrupts. | |
595 | */ | |
596 | if (regs->psw.mask & PSW_MASK_PSTATE) | |
597 | local_irq_enable(); | |
598 | ||
599 | if (MACHINE_HAS_IEEE) | |
94c12cc7 | 600 | asm volatile("stfpc %0" : "=m" (current->thread.fp_regs.fpc)); |
1da177e4 LT |
601 | |
602 | #ifdef CONFIG_MATHEMU | |
603 | else if (regs->psw.mask & PSW_MASK_PSTATE) { | |
604 | __u8 opcode[6]; | |
605 | get_user(*((__u16 *) opcode), location); | |
606 | switch (opcode[0]) { | |
607 | case 0x28: /* LDR Rx,Ry */ | |
608 | signal = math_emu_ldr(opcode); | |
609 | break; | |
610 | case 0x38: /* LER Rx,Ry */ | |
611 | signal = math_emu_ler(opcode); | |
612 | break; | |
613 | case 0x60: /* STD R,D(X,B) */ | |
614 | get_user(*((__u16 *) (opcode+2)), location+1); | |
615 | signal = math_emu_std(opcode, regs); | |
616 | break; | |
617 | case 0x68: /* LD R,D(X,B) */ | |
618 | get_user(*((__u16 *) (opcode+2)), location+1); | |
619 | signal = math_emu_ld(opcode, regs); | |
620 | break; | |
621 | case 0x70: /* STE R,D(X,B) */ | |
622 | get_user(*((__u16 *) (opcode+2)), location+1); | |
623 | signal = math_emu_ste(opcode, regs); | |
624 | break; | |
625 | case 0x78: /* LE R,D(X,B) */ | |
626 | get_user(*((__u16 *) (opcode+2)), location+1); | |
627 | signal = math_emu_le(opcode, regs); | |
628 | break; | |
629 | case 0xb3: | |
630 | get_user(*((__u16 *) (opcode+2)), location+1); | |
631 | signal = math_emu_b3(opcode, regs); | |
632 | break; | |
633 | case 0xed: | |
634 | get_user(*((__u32 *) (opcode+2)), | |
635 | (__u32 *)(location+1)); | |
636 | signal = math_emu_ed(opcode, regs); | |
637 | break; | |
638 | case 0xb2: | |
639 | if (opcode[1] == 0x99) { | |
640 | get_user(*((__u16 *) (opcode+2)), location+1); | |
641 | signal = math_emu_srnm(opcode, regs); | |
642 | } else if (opcode[1] == 0x9c) { | |
643 | get_user(*((__u16 *) (opcode+2)), location+1); | |
644 | signal = math_emu_stfpc(opcode, regs); | |
645 | } else if (opcode[1] == 0x9d) { | |
646 | get_user(*((__u16 *) (opcode+2)), location+1); | |
647 | signal = math_emu_lfpc(opcode, regs); | |
648 | } else | |
649 | signal = SIGILL; | |
650 | break; | |
651 | default: | |
652 | signal = SIGILL; | |
653 | break; | |
654 | } | |
655 | } | |
656 | #endif | |
657 | if (current->thread.fp_regs.fpc & FPC_DXC_MASK) | |
658 | signal = SIGFPE; | |
659 | else | |
660 | signal = SIGILL; | |
661 | if (signal == SIGFPE) | |
662 | do_fp_trap(regs, location, | |
663 | current->thread.fp_regs.fpc, interruption_code); | |
664 | else if (signal) { | |
665 | siginfo_t info; | |
666 | info.si_signo = signal; | |
667 | info.si_errno = 0; | |
668 | info.si_code = ILL_ILLOPN; | |
669 | info.si_addr = location; | |
670 | do_trap(interruption_code, signal, | |
671 | "data exception", regs, &info); | |
672 | } | |
673 | } | |
674 | ||
675 | asmlinkage void space_switch_exception(struct pt_regs * regs, long int_code) | |
676 | { | |
677 | siginfo_t info; | |
678 | ||
679 | /* Set user psw back to home space mode. */ | |
680 | if (regs->psw.mask & PSW_MASK_PSTATE) | |
681 | regs->psw.mask |= PSW_ASC_HOME; | |
682 | /* Send SIGILL. */ | |
683 | info.si_signo = SIGILL; | |
684 | info.si_errno = 0; | |
685 | info.si_code = ILL_PRVOPC; | |
686 | info.si_addr = get_check_address(regs); | |
687 | do_trap(int_code, SIGILL, "space switch event", regs, &info); | |
688 | } | |
689 | ||
690 | asmlinkage void kernel_stack_overflow(struct pt_regs * regs) | |
691 | { | |
77eb65cb HC |
692 | bust_spinlocks(1); |
693 | printk("Kernel stack overflow.\n"); | |
694 | show_regs(regs); | |
695 | bust_spinlocks(0); | |
1da177e4 LT |
696 | panic("Corrupt kernel stack, can't continue."); |
697 | } | |
698 | ||
1da177e4 LT |
699 | /* init is done in lowcore.S and head.S */ |
700 | ||
701 | void __init trap_init(void) | |
702 | { | |
703 | int i; | |
704 | ||
705 | for (i = 0; i < 128; i++) | |
706 | pgm_check_table[i] = &default_trap_handler; | |
707 | pgm_check_table[1] = &illegal_op; | |
708 | pgm_check_table[2] = &privileged_op; | |
709 | pgm_check_table[3] = &execute_exception; | |
710 | pgm_check_table[4] = &do_protection_exception; | |
711 | pgm_check_table[5] = &addressing_exception; | |
712 | pgm_check_table[6] = &specification_exception; | |
713 | pgm_check_table[7] = &data_exception; | |
714 | pgm_check_table[8] = &overflow_exception; | |
715 | pgm_check_table[9] = ÷_exception; | |
716 | pgm_check_table[0x0A] = &overflow_exception; | |
717 | pgm_check_table[0x0B] = ÷_exception; | |
718 | pgm_check_table[0x0C] = &hfp_overflow_exception; | |
719 | pgm_check_table[0x0D] = &hfp_underflow_exception; | |
720 | pgm_check_table[0x0E] = &hfp_significance_exception; | |
721 | pgm_check_table[0x0F] = &hfp_divide_exception; | |
722 | pgm_check_table[0x10] = &do_dat_exception; | |
723 | pgm_check_table[0x11] = &do_dat_exception; | |
724 | pgm_check_table[0x12] = &translation_exception; | |
725 | pgm_check_table[0x13] = &special_op_exception; | |
347a8dc3 | 726 | #ifdef CONFIG_64BIT |
1da177e4 LT |
727 | pgm_check_table[0x38] = &do_dat_exception; |
728 | pgm_check_table[0x39] = &do_dat_exception; | |
729 | pgm_check_table[0x3A] = &do_dat_exception; | |
730 | pgm_check_table[0x3B] = &do_dat_exception; | |
347a8dc3 | 731 | #endif /* CONFIG_64BIT */ |
1da177e4 LT |
732 | pgm_check_table[0x15] = &operand_exception; |
733 | pgm_check_table[0x1C] = &space_switch_exception; | |
734 | pgm_check_table[0x1D] = &hfp_sqrt_exception; | |
735 | pgm_check_table[0x40] = &do_monitor_call; | |
736 | ||
737 | if (MACHINE_IS_VM) { | |
d4b68996 | 738 | #ifdef CONFIG_PFAULT |
1da177e4 | 739 | /* |
d4b68996 | 740 | * Try to get pfault pseudo page faults going. |
1da177e4 | 741 | */ |
1da177e4 LT |
742 | if (register_early_external_interrupt(0x2603, pfault_interrupt, |
743 | &ext_int_pfault) != 0) | |
744 | panic("Couldn't request external interrupt 0x2603"); | |
745 | ||
746 | if (pfault_init() == 0) | |
747 | return; | |
748 | ||
749 | /* Tough luck, no pfault. */ | |
750 | unregister_early_external_interrupt(0x2603, pfault_interrupt, | |
751 | &ext_int_pfault); | |
1da177e4 LT |
752 | #endif |
753 | } | |
754 | } |