| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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> | 
| Heiko Carstens | 5d3f229 | 2005-08-01 21:11:33 -0700 | [diff] [blame] | 31 | #include <linux/reboot.h> | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 32 | #include <linux/kprobes.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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> | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 43 | #include <asm/kdebug.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| Martin Schwidefsky | 347a8dc | 2006-01-06 00:19:28 -0800 | [diff] [blame] | 71 | #ifndef CONFIG_64BIT | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 72 | #define FOURLONG "%08lx %08lx %08lx %08lx\n" | 
 | 73 | static int kstack_depth_to_print = 12; | 
| Martin Schwidefsky | 347a8dc | 2006-01-06 00:19:28 -0800 | [diff] [blame] | 74 | #else /* CONFIG_64BIT */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 75 | #define FOURLONG "%016lx %016lx %016lx %016lx\n" | 
 | 76 | static int kstack_depth_to_print = 20; | 
| Martin Schwidefsky | 347a8dc | 2006-01-06 00:19:28 -0800 | [diff] [blame] | 77 | #endif /* CONFIG_64BIT */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 78 |  | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 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 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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) | 
| Al Viro | 30af712 | 2006-01-12 01:05:50 -0800 | [diff] [blame] | 154 | 		__show_trace(sp, (unsigned long) task_stack_page(task), | 
 | 155 | 			     (unsigned long) task_stack_page(task) + THREAD_SIZE); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 168 | 	if (!sp) | 
| Heiko Carstens | 7380534 | 2006-06-29 14:56:23 +0200 | [diff] [blame] | 169 | 		stack = task ? (unsigned long *) task->thread.ksp : __r15; | 
 | 170 | 	else | 
 | 171 | 		stack = sp; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 172 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 | { | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 189 | 	show_stack(NULL, NULL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| Al Viro | c7584fb | 2006-01-12 01:05:49 -0800 | [diff] [blame] | 256 | 	regs = task_pt_regs(task); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 324 | static void __kprobes inline do_trap(long interruption_code, int signr, | 
 | 325 | 					char *str, struct pt_regs *regs, | 
 | 326 | 					siginfo_t *info) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 335 | 	if (notify_die(DIE_TRAP, str, regs, interruption_code, | 
 | 336 | 				interruption_code, signr) == NOTIFY_STOP) | 
 | 337 | 		return; | 
 | 338 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 355 | static inline void __user *get_check_address(struct pt_regs *regs) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 356 | { | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 357 | 	return (void __user *)((regs->psw.addr-S390_lowcore.pgm_ilc) & PSW_ADDR_INSN); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 358 | } | 
 | 359 |  | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 360 | void __kprobes do_single_step(struct pt_regs *regs) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 361 | { | 
| Michael Grundy | 4ba069b | 2006-09-20 15:58:39 +0200 | [diff] [blame] | 362 | 	if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0, | 
 | 363 | 					SIGTRAP) == NOTIFY_STOP){ | 
 | 364 | 		return; | 
 | 365 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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; \ | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 388 | 	info.si_addr = siaddr; \ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 420 | do_fp_trap(struct pt_regs *regs, void __user *location, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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]; | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 452 | 	__u16 __user *location; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 453 | 	int signal = 0; | 
 | 454 |  | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 455 | 	location = get_check_address(regs); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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; | 
| Al Viro | 793af24 | 2006-02-01 06:55:59 -0500 | [diff] [blame] | 511 | 		info.si_addr = (void __user *) location; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 | { | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 587 | 	__u16 __user *location; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 588 | 	int signal = 0; | 
 | 589 |  | 
| Heiko Carstens | d2c993d | 2006-07-12 16:41:55 +0200 | [diff] [blame] | 590 | 	location = get_check_address(regs); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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) | 
 | 600 | 		__asm__ volatile ("stfpc %0\n\t"  | 
 | 601 | 				  : "=m" (current->thread.fp_regs.fpc)); | 
 | 602 |  | 
 | 603 | #ifdef CONFIG_MATHEMU | 
 | 604 |         else if (regs->psw.mask & PSW_MASK_PSTATE) { | 
 | 605 |         	__u8 opcode[6]; | 
 | 606 | 		get_user(*((__u16 *) opcode), location); | 
 | 607 | 		switch (opcode[0]) { | 
 | 608 | 		case 0x28: /* LDR Rx,Ry   */ | 
 | 609 | 			signal = math_emu_ldr(opcode); | 
 | 610 | 			break; | 
 | 611 | 		case 0x38: /* LER Rx,Ry   */ | 
 | 612 | 			signal = math_emu_ler(opcode); | 
 | 613 | 			break; | 
 | 614 | 		case 0x60: /* STD R,D(X,B) */ | 
 | 615 | 			get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 616 | 			signal = math_emu_std(opcode, regs); | 
 | 617 | 			break; | 
 | 618 | 		case 0x68: /* LD R,D(X,B) */ | 
 | 619 | 			get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 620 | 			signal = math_emu_ld(opcode, regs); | 
 | 621 | 			break; | 
 | 622 | 		case 0x70: /* STE R,D(X,B) */ | 
 | 623 | 			get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 624 | 			signal = math_emu_ste(opcode, regs); | 
 | 625 | 			break; | 
 | 626 | 		case 0x78: /* LE R,D(X,B) */ | 
 | 627 | 			get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 628 | 			signal = math_emu_le(opcode, regs); | 
 | 629 | 			break; | 
 | 630 | 		case 0xb3: | 
 | 631 | 			get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 632 | 			signal = math_emu_b3(opcode, regs); | 
 | 633 | 			break; | 
 | 634 |                 case 0xed: | 
 | 635 | 			get_user(*((__u32 *) (opcode+2)), | 
 | 636 | 				 (__u32 *)(location+1)); | 
 | 637 | 			signal = math_emu_ed(opcode, regs); | 
 | 638 | 			break; | 
 | 639 | 	        case 0xb2: | 
 | 640 | 			if (opcode[1] == 0x99) { | 
 | 641 | 				get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 642 | 				signal = math_emu_srnm(opcode, regs); | 
 | 643 | 			} else if (opcode[1] == 0x9c) { | 
 | 644 | 				get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 645 | 				signal = math_emu_stfpc(opcode, regs); | 
 | 646 | 			} else if (opcode[1] == 0x9d) { | 
 | 647 | 				get_user(*((__u16 *) (opcode+2)), location+1); | 
 | 648 | 				signal = math_emu_lfpc(opcode, regs); | 
 | 649 | 			} else | 
 | 650 | 				signal = SIGILL; | 
 | 651 | 			break; | 
 | 652 | 		default: | 
 | 653 | 			signal = SIGILL; | 
 | 654 | 			break; | 
 | 655 |                 } | 
 | 656 |         } | 
 | 657 | #endif  | 
 | 658 | 	if (current->thread.fp_regs.fpc & FPC_DXC_MASK) | 
 | 659 | 		signal = SIGFPE; | 
 | 660 | 	else | 
 | 661 | 		signal = SIGILL; | 
 | 662 |         if (signal == SIGFPE) | 
 | 663 | 		do_fp_trap(regs, location, | 
 | 664 |                            current->thread.fp_regs.fpc, interruption_code); | 
 | 665 |         else if (signal) { | 
 | 666 | 		siginfo_t info; | 
 | 667 | 		info.si_signo = signal; | 
 | 668 | 		info.si_errno = 0; | 
 | 669 | 		info.si_code = ILL_ILLOPN; | 
 | 670 | 		info.si_addr = location; | 
 | 671 | 		do_trap(interruption_code, signal,  | 
 | 672 | 			"data exception", regs, &info); | 
 | 673 | 	} | 
 | 674 | } | 
 | 675 |  | 
 | 676 | asmlinkage void space_switch_exception(struct pt_regs * regs, long int_code) | 
 | 677 | { | 
 | 678 |         siginfo_t info; | 
 | 679 |  | 
 | 680 | 	/* Set user psw back to home space mode. */ | 
 | 681 | 	if (regs->psw.mask & PSW_MASK_PSTATE) | 
 | 682 | 		regs->psw.mask |= PSW_ASC_HOME; | 
 | 683 | 	/* Send SIGILL. */ | 
 | 684 |         info.si_signo = SIGILL; | 
 | 685 |         info.si_errno = 0; | 
 | 686 |         info.si_code = ILL_PRVOPC; | 
 | 687 |         info.si_addr = get_check_address(regs); | 
 | 688 |         do_trap(int_code, SIGILL, "space switch event", regs, &info); | 
 | 689 | } | 
 | 690 |  | 
 | 691 | asmlinkage void kernel_stack_overflow(struct pt_regs * regs) | 
 | 692 | { | 
| Heiko Carstens | 77eb65c | 2005-06-21 17:16:28 -0700 | [diff] [blame] | 693 | 	bust_spinlocks(1); | 
 | 694 | 	printk("Kernel stack overflow.\n"); | 
 | 695 | 	show_regs(regs); | 
 | 696 | 	bust_spinlocks(0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 697 | 	panic("Corrupt kernel stack, can't continue."); | 
 | 698 | } | 
 | 699 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 700 | /* init is done in lowcore.S and head.S */ | 
 | 701 |  | 
 | 702 | void __init trap_init(void) | 
 | 703 | { | 
 | 704 |         int i; | 
 | 705 |  | 
 | 706 |         for (i = 0; i < 128; i++) | 
 | 707 |           pgm_check_table[i] = &default_trap_handler; | 
 | 708 |         pgm_check_table[1] = &illegal_op; | 
 | 709 |         pgm_check_table[2] = &privileged_op; | 
 | 710 |         pgm_check_table[3] = &execute_exception; | 
 | 711 |         pgm_check_table[4] = &do_protection_exception; | 
 | 712 |         pgm_check_table[5] = &addressing_exception; | 
 | 713 |         pgm_check_table[6] = &specification_exception; | 
 | 714 |         pgm_check_table[7] = &data_exception; | 
 | 715 |         pgm_check_table[8] = &overflow_exception; | 
 | 716 |         pgm_check_table[9] = ÷_exception; | 
 | 717 |         pgm_check_table[0x0A] = &overflow_exception; | 
 | 718 |         pgm_check_table[0x0B] = ÷_exception; | 
 | 719 |         pgm_check_table[0x0C] = &hfp_overflow_exception; | 
 | 720 |         pgm_check_table[0x0D] = &hfp_underflow_exception; | 
 | 721 |         pgm_check_table[0x0E] = &hfp_significance_exception; | 
 | 722 |         pgm_check_table[0x0F] = &hfp_divide_exception; | 
 | 723 |         pgm_check_table[0x10] = &do_dat_exception; | 
 | 724 |         pgm_check_table[0x11] = &do_dat_exception; | 
 | 725 |         pgm_check_table[0x12] = &translation_exception; | 
 | 726 |         pgm_check_table[0x13] = &special_op_exception; | 
| Martin Schwidefsky | 347a8dc | 2006-01-06 00:19:28 -0800 | [diff] [blame] | 727 | #ifdef CONFIG_64BIT | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 728 |         pgm_check_table[0x38] = &do_dat_exception; | 
 | 729 | 	pgm_check_table[0x39] = &do_dat_exception; | 
 | 730 | 	pgm_check_table[0x3A] = &do_dat_exception; | 
 | 731 |         pgm_check_table[0x3B] = &do_dat_exception; | 
| Martin Schwidefsky | 347a8dc | 2006-01-06 00:19:28 -0800 | [diff] [blame] | 732 | #endif /* CONFIG_64BIT */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 733 |         pgm_check_table[0x15] = &operand_exception; | 
 | 734 |         pgm_check_table[0x1C] = &space_switch_exception; | 
 | 735 |         pgm_check_table[0x1D] = &hfp_sqrt_exception; | 
 | 736 | 	pgm_check_table[0x40] = &do_monitor_call; | 
 | 737 |  | 
 | 738 | 	if (MACHINE_IS_VM) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 739 | #ifdef CONFIG_PFAULT | 
| Martin Schwidefsky | d4b6899 | 2005-11-07 00:59:06 -0800 | [diff] [blame] | 740 | 		/* | 
 | 741 | 		 * Try to get pfault pseudo page faults going. | 
 | 742 | 		 */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 743 | 		if (register_early_external_interrupt(0x2603, pfault_interrupt, | 
 | 744 | 						      &ext_int_pfault) != 0) | 
 | 745 | 			panic("Couldn't request external interrupt 0x2603"); | 
 | 746 |  | 
 | 747 | 		if (pfault_init() == 0)  | 
 | 748 | 			return; | 
 | 749 | 		 | 
 | 750 | 		/* Tough luck, no pfault. */ | 
 | 751 | 		unregister_early_external_interrupt(0x2603, pfault_interrupt, | 
 | 752 | 						    &ext_int_pfault); | 
 | 753 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 754 | 	} | 
 | 755 | } |