fault.c 14.6 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
2
3
4
5
6
7
8
9
10
11
12
/*
 *  arch/s390/mm/fault.c
 *
 *  S390 version
 *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Hartmut Penner (hp@de.ibm.com)
 *               Ulrich Weigand (uweigand@de.ibm.com)
 *
 *  Derived from "arch/i386/mm/fault.c"
 *    Copyright (C) 1995  Linus Torvalds
 */

13
#include <linux/perf_event.h>
Linus Torvalds's avatar
Linus Torvalds committed
14
15
16
17
18
19
20
21
22
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
23
#include <linux/compat.h>
Linus Torvalds's avatar
Linus Torvalds committed
24
#include <linux/smp.h>
25
#include <linux/kdebug.h>
Linus Torvalds's avatar
Linus Torvalds committed
26
27
28
29
#include <linux/init.h>
#include <linux/console.h>
#include <linux/module.h>
#include <linux/hardirq.h>
Michael Grundy's avatar
Michael Grundy committed
30
#include <linux/kprobes.h>
31
#include <linux/uaccess.h>
32
#include <linux/hugetlb.h>
Linus Torvalds's avatar
Linus Torvalds committed
33
34
#include <asm/system.h>
#include <asm/pgtable.h>
Heiko Carstens's avatar
Heiko Carstens committed
35
#include <asm/s390_ext.h>
36
#include <asm/mmu_context.h>
37
#include "../kernel/entry.h"
Linus Torvalds's avatar
Linus Torvalds committed
38

39
#ifndef CONFIG_64BIT
Linus Torvalds's avatar
Linus Torvalds committed
40
41
42
43
#define __FAIL_ADDR_MASK 0x7ffff000
#define __FIXUP_MASK 0x7fffffff
#define __SUBCODE_MASK 0x0200
#define __PF_RES_FIELD 0ULL
44
#else /* CONFIG_64BIT */
Linus Torvalds's avatar
Linus Torvalds committed
45
46
47
48
#define __FAIL_ADDR_MASK -4096L
#define __FIXUP_MASK ~0L
#define __SUBCODE_MASK 0x0600
#define __PF_RES_FIELD 0x8000000000000000ULL
49
#endif /* CONFIG_64BIT */
Linus Torvalds's avatar
Linus Torvalds committed
50
51
52
53
54

#ifdef CONFIG_SYSCTL
extern int sysctl_userprocess_debug;
#endif

55
static inline int notify_page_fault(struct pt_regs *regs)
56
{
57
58
	int ret = 0;

59
#ifdef CONFIG_KPROBES
60
61
62
63
64
65
66
	/* kprobe_running() needs smp_processor_id() */
	if (!user_mode(regs)) {
		preempt_disable();
		if (kprobe_running() && kprobe_fault_handler(regs, 14))
			ret = 1;
		preempt_enable();
	}
67
#endif
68
	return ret;
Michael Grundy's avatar
Michael Grundy committed
69
70
}

Linus Torvalds's avatar
Linus Torvalds committed
71
72
73

/*
 * Unlock any spinlocks which will prevent us from getting the
74
 * message out.
Linus Torvalds's avatar
Linus Torvalds committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
 */
void bust_spinlocks(int yes)
{
	if (yes) {
		oops_in_progress = 1;
	} else {
		int loglevel_save = console_loglevel;
		console_unblank();
		oops_in_progress = 0;
		/*
		 * OK, the message is on the console.  Now we call printk()
		 * without oops_in_progress set so that printk will give klogd
		 * a poke.  Hold onto your hats...
		 */
		console_loglevel = 15;
		printk(" ");
		console_loglevel = loglevel_save;
	}
}

/*
96
 * Returns the address space associated with the fault.
97
 * Returns 0 for kernel space and 1 for user space.
Linus Torvalds's avatar
Linus Torvalds committed
98
 */
99
static inline int user_space_fault(unsigned long trans_exc_code)
Linus Torvalds's avatar
Linus Torvalds committed
100
101
{
	/*
102
103
	 * The lowest two bits of the translation exception
	 * identification indicate which paging table was used.
Linus Torvalds's avatar
Linus Torvalds committed
104
	 */
105
106
107
108
	trans_exc_code &= 3;
	if (trans_exc_code == 2)
		/* Access via secondary space, set_fs setting decides */
		return current->thread.mm_segment.ar4;
109
	if (user_mode == HOME_SPACE_MODE)
110
111
112
113
114
115
116
117
118
		/* User space if the access has been done via home space. */
		return trans_exc_code == 3;
	/*
	 * If the user space is not the home space the kernel runs in home
	 * space. Access via secondary space has already been covered,
	 * access via primary space or access register is from user space
	 * and access via home space is from the kernel.
	 */
	return trans_exc_code != 3;
Linus Torvalds's avatar
Linus Torvalds committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
}

/*
 * Send SIGSEGV to task.  This is an external routine
 * to keep the stack usage of do_page_fault small.
 */
static void do_sigsegv(struct pt_regs *regs, unsigned long error_code,
		       int si_code, unsigned long address)
{
	struct siginfo si;

#if defined(CONFIG_SYSCTL) || defined(CONFIG_PROCESS_DEBUG)
#if defined(CONFIG_SYSCTL)
	if (sysctl_userprocess_debug)
#endif
	{
		printk("User process fault: interruption code 0x%lX\n",
		       error_code);
		printk("failing address: %lX\n", address);
		show_regs(regs);
	}
#endif
	si.si_signo = SIGSEGV;
	si.si_code = si_code;
Heiko Carstens's avatar
Heiko Carstens committed
143
	si.si_addr = (void __user *) address;
Linus Torvalds's avatar
Linus Torvalds committed
144
145
146
	force_sig_info(SIGSEGV, &si, current);
}

147
static void do_no_context(struct pt_regs *regs, unsigned long error_code,
148
			  unsigned long trans_exc_code)
149
150
{
	const struct exception_table_entry *fixup;
151
	unsigned long address;
152
153
154
155
156
157
158
159
160
161
162
163

	/* Are we prepared to handle this kernel fault?  */
	fixup = search_exception_tables(regs->psw.addr & __FIXUP_MASK);
	if (fixup) {
		regs->psw.addr = fixup->fixup | PSW_ADDR_AMODE;
		return;
	}

	/*
	 * Oops. The kernel tried to access some bad page. We'll have to
	 * terminate things with extreme prejudice.
	 */
164
	address = trans_exc_code & __FAIL_ADDR_MASK;
165
	if (!user_space_fault(trans_exc_code))
166
167
168
169
170
171
172
173
174
175
		printk(KERN_ALERT "Unable to handle kernel pointer dereference"
		       " at virtual kernel address %p\n", (void *)address);
	else
		printk(KERN_ALERT "Unable to handle kernel paging request"
		       " at virtual user address %p\n", (void *)address);

	die("Oops", regs, error_code);
	do_exit(SIGKILL);
}

176
177
static void do_low_address(struct pt_regs *regs, unsigned long error_code,
			   unsigned long trans_exc_code)
178
179
180
181
182
183
184
185
186
{
	/* Low-address protection hit in kernel mode means
	   NULL pointer write access in kernel mode.  */
	if (regs->psw.mask & PSW_MASK_PSTATE) {
		/* Low-address protection hit in user mode 'cannot happen'. */
		die ("Low-address protection", regs, error_code);
		do_exit(SIGKILL);
	}

187
	do_no_context(regs, error_code, trans_exc_code);
188
189
190
}

static void do_sigbus(struct pt_regs *regs, unsigned long error_code,
191
		      unsigned long trans_exc_code)
192
193
194
195
196
197
198
199
200
{
	struct task_struct *tsk = current;
	struct mm_struct *mm = tsk->mm;

	up_read(&mm->mmap_sem);
	/*
	 * Send a sigbus, regardless of whether we were in kernel
	 * or user mode.
	 */
201
	tsk->thread.prot_addr = trans_exc_code & __FAIL_ADDR_MASK;
202
203
204
205
206
	tsk->thread.trap_no = error_code;
	force_sig(SIGBUS, tsk);

	/* Kernel mode? Handle exceptions or die */
	if (!(regs->psw.mask & PSW_MASK_PSTATE))
207
		do_no_context(regs, error_code, trans_exc_code);
208
209
}

Gerald Schaefer's avatar
Gerald Schaefer committed
210
#ifdef CONFIG_S390_EXEC_PROTECT
211
212
static int signal_return(struct mm_struct *mm, struct pt_regs *regs,
			 unsigned long address, unsigned long error_code)
Gerald Schaefer's avatar
Gerald Schaefer committed
213
{
214
	u16 instruction;
Heiko Carstens's avatar
Heiko Carstens committed
215
216
217
218
	int rc;
#ifdef CONFIG_COMPAT
	int compat;
#endif
219
220
221
222
223
224
225

	pagefault_disable();
	rc = __get_user(instruction, (u16 __user *) regs->psw.addr);
	pagefault_enable();
	if (rc)
		return -EFAULT;

Gerald Schaefer's avatar
Gerald Schaefer committed
226
227
228
	up_read(&mm->mmap_sem);
	clear_tsk_thread_flag(current, TIF_SINGLE_STEP);
#ifdef CONFIG_COMPAT
229
	compat = is_compat_task();
230
	if (compat && instruction == 0x0a77)
231
		sys32_sigreturn();
232
	else if (compat && instruction == 0x0aad)
233
		sys32_rt_sigreturn();
Gerald Schaefer's avatar
Gerald Schaefer committed
234
	else
235
236
#endif
	if (instruction == 0x0a77)
237
		sys_sigreturn();
238
	else if (instruction == 0x0aad)
239
		sys_rt_sigreturn();
Gerald Schaefer's avatar
Gerald Schaefer committed
240
241
242
243
244
245
246
247
248
	else {
		current->thread.prot_addr = address;
		current->thread.trap_no = error_code;
		do_sigsegv(regs, error_code, SEGV_MAPERR, address);
	}
	return 0;
}
#endif /* CONFIG_S390_EXEC_PROTECT */

Linus Torvalds's avatar
Linus Torvalds committed
249
250
251
252
253
254
255
256
257
258
259
/*
 * This routine handles page faults.  It determines the address,
 * and the problem, and then passes it off to one of the appropriate
 * routines.
 *
 * error_code:
 *   04       Protection           ->  Write-Protection  (suprression)
 *   10       Segment translation  ->  Not present       (nullification)
 *   11       Page translation     ->  Not present       (nullification)
 *   3b       Region third trans.  ->  Not present       (nullification)
 */
260
static inline void
261
262
do_exception(struct pt_regs *regs, unsigned long error_code, int write,
	     unsigned long trans_exc_code)
Linus Torvalds's avatar
Linus Torvalds committed
263
{
264
265
266
267
268
	struct task_struct *tsk;
	struct mm_struct *mm;
	struct vm_area_struct *vma;
	unsigned long address;
	int si_code;
Nick Piggin's avatar
Nick Piggin committed
269
	int fault;
Linus Torvalds's avatar
Linus Torvalds committed
270

271
	if (notify_page_fault(regs))
Michael Grundy's avatar
Michael Grundy committed
272
273
		return;

274
275
	tsk = current;
	mm = tsk->mm;
Linus Torvalds's avatar
Linus Torvalds committed
276
277
278
279
280
281

	/*
	 * Verify that the fault happened in user space, that
	 * we are not in an interrupt and that there is a 
	 * user context.
	 */
282
	if (unlikely(!user_space_fault(trans_exc_code) || in_atomic() || !mm))
283
		goto no_context;
Linus Torvalds's avatar
Linus Torvalds committed
284

285
	address = trans_exc_code & __FAIL_ADDR_MASK;
Linus Torvalds's avatar
Linus Torvalds committed
286
287
288
289
290
291
	/*
	 * When we get here, the fault happened in the current
	 * task's user address space, so we can switch on the
	 * interrupts again and then search the VMAs
	 */
	local_irq_enable();
292
	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address);
293
	down_read(&mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
294

295
296
297
298
	si_code = SEGV_MAPERR;
	vma = find_vma(mm, address);
	if (!vma)
		goto bad_area;
Gerald Schaefer's avatar
Gerald Schaefer committed
299
300

#ifdef CONFIG_S390_EXEC_PROTECT
301
302
	if (unlikely((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_SECONDARY &&
		     (trans_exc_code & 3) == 0 && !(vma->vm_flags & VM_EXEC)))
Gerald Schaefer's avatar
Gerald Schaefer committed
303
304
305
306
307
308
309
310
		if (!signal_return(mm, regs, address, error_code))
			/*
			 * signal_return() has done an up_read(&mm->mmap_sem)
			 * if it returns 0.
			 */
			return;
#endif

311
312
313
314
315
316
	if (vma->vm_start <= address)
		goto good_area;
	if (!(vma->vm_flags & VM_GROWSDOWN))
		goto bad_area;
	if (expand_stack(vma, address))
		goto bad_area;
Linus Torvalds's avatar
Linus Torvalds committed
317
318
319
320
321
322
/*
 * Ok, we have a good vm_area for this memory access, so
 * we can handle it..
 */
good_area:
	si_code = SEGV_ACCERR;
323
	if (!write) {
Linus Torvalds's avatar
Linus Torvalds committed
324
325
326
327
328
329
330
331
		/* page not present, check vm flags */
		if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
			goto bad_area;
	} else {
		if (!(vma->vm_flags & VM_WRITE))
			goto bad_area;
	}

332
333
	if (is_vm_hugetlb_page(vma))
		address &= HPAGE_MASK;
Linus Torvalds's avatar
Linus Torvalds committed
334
335
336
337
338
	/*
	 * If for any reason at all we couldn't handle the fault,
	 * make sure we exit gracefully rather than endlessly redo
	 * the fault.
	 */
339
	fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0);
Nick Piggin's avatar
Nick Piggin committed
340
341
	if (unlikely(fault & VM_FAULT_ERROR)) {
		if (fault & VM_FAULT_OOM) {
342
343
			up_read(&mm->mmap_sem);
			pagefault_out_of_memory();
Nick Piggin's avatar
Nick Piggin committed
344
345
346
347
348
			return;
		} else if (fault & VM_FAULT_SIGBUS) {
			do_sigbus(regs, error_code, address);
			return;
		}
Linus Torvalds's avatar
Linus Torvalds committed
349
350
		BUG();
	}
351
	if (fault & VM_FAULT_MAJOR) {
Nick Piggin's avatar
Nick Piggin committed
352
		tsk->maj_flt++;
353
		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0,
354
355
				     regs, address);
	} else {
Nick Piggin's avatar
Nick Piggin committed
356
		tsk->min_flt++;
357
		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0,
358
359
				     regs, address);
	}
Linus Torvalds's avatar
Linus Torvalds committed
360
361
362
363
364
        up_read(&mm->mmap_sem);
	/*
	 * The instruction that caused the program check will
	 * be repeated. Don't signal single step via SIGTRAP.
	 */
365
	clear_tsk_thread_flag(tsk, TIF_SINGLE_STEP);
Linus Torvalds's avatar
Linus Torvalds committed
366
367
368
369
370
371
372
        return;

/*
 * Something tried to access memory that isn't in our memory map..
 * Fix it, but check if it's kernel or user first..
 */
bad_area:
373
	up_read(&mm->mmap_sem);
Linus Torvalds's avatar
Linus Torvalds committed
374

375
376
377
378
	/* User mode accesses just cause a SIGSEGV */
	if (regs->psw.mask & PSW_MASK_PSTATE) {
		tsk->thread.prot_addr = address;
		tsk->thread.trap_no = error_code;
Linus Torvalds's avatar
Linus Torvalds committed
379
		do_sigsegv(regs, error_code, si_code, address);
380
		return;
Linus Torvalds's avatar
Linus Torvalds committed
381
382
383
	}

no_context:
384
	do_no_context(regs, error_code, trans_exc_code);
Linus Torvalds's avatar
Linus Torvalds committed
385
386
}

387
void __kprobes do_protection_exception(struct pt_regs *regs,
388
				       long error_code)
Linus Torvalds's avatar
Linus Torvalds committed
389
{
390
391
	unsigned long trans_exc_code = S390_lowcore.trans_exc_code;

392
	/* Protection exception is supressing, decrement psw address. */
Linus Torvalds's avatar
Linus Torvalds committed
393
	regs->psw.addr -= (error_code >> 16);
394
395
396
397
398
	/*
	 * Check for low-address protection.  This needs to be treated
	 * as a special case because the translation exception code
	 * field is not guaranteed to contain valid data in this case.
	 */
399
400
	if (unlikely(!(trans_exc_code & 4))) {
		do_low_address(regs, error_code, trans_exc_code);
401
402
		return;
	}
403
	do_exception(regs, 4, 1, trans_exc_code);
Linus Torvalds's avatar
Linus Torvalds committed
404
405
}

406
void __kprobes do_dat_exception(struct pt_regs *regs, long error_code)
Linus Torvalds's avatar
Linus Torvalds committed
407
{
408
	do_exception(regs, error_code & 0xff, 0, S390_lowcore.trans_exc_code);
Linus Torvalds's avatar
Linus Torvalds committed
409
410
}

411
412
413
#ifdef CONFIG_64BIT
void __kprobes do_asce_exception(struct pt_regs *regs, unsigned long error_code)
{
414
	unsigned long trans_exc_code = S390_lowcore.trans_exc_code;
415
416
417
418
419
	struct mm_struct *mm;
	struct vm_area_struct *vma;
	unsigned long address;

	mm = current->mm;
420
	address = trans_exc_code & __FAIL_ADDR_MASK;
421

422
	if (unlikely(!user_space_fault(trans_exc_code) || in_atomic() || !mm))
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
		goto no_context;

	local_irq_enable();

	down_read(&mm->mmap_sem);
	vma = find_vma(mm, address);
	up_read(&mm->mmap_sem);

	if (vma) {
		update_mm(mm, current);
		return;
	}

	/* User mode accesses just cause a SIGSEGV */
	if (regs->psw.mask & PSW_MASK_PSTATE) {
		current->thread.prot_addr = address;
		current->thread.trap_no = error_code;
		do_sigsegv(regs, error_code, SEGV_MAPERR, address);
		return;
	}

no_context:
445
	do_no_context(regs, error_code, trans_exc_code);
446
447
448
}
#endif

Linus Torvalds's avatar
Linus Torvalds committed
449
450
451
452
#ifdef CONFIG_PFAULT 
/*
 * 'pfault' pseudo page faults routines.
 */
Heiko Carstens's avatar
Heiko Carstens committed
453
static ext_int_info_t ext_int_pfault;
Linus Torvalds's avatar
Linus Torvalds committed
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
static int pfault_disable = 0;

static int __init nopfault(char *str)
{
	pfault_disable = 1;
	return 1;
}

__setup("nopfault", nopfault);

typedef struct {
	__u16 refdiagc;
	__u16 reffcode;
	__u16 refdwlen;
	__u16 refversn;
	__u64 refgaddr;
	__u64 refselmk;
	__u64 refcmpmk;
	__u64 reserved;
473
} __attribute__ ((packed, aligned(8))) pfault_refbk_t;
Linus Torvalds's avatar
Linus Torvalds committed
474
475
476
477
478
479
480
481

int pfault_init(void)
{
	pfault_refbk_t refbk =
		{ 0x258, 0, 5, 2, __LC_CURRENT, 1ULL << 48, 1ULL << 48,
		  __PF_RES_FIELD };
        int rc;

Heiko Carstens's avatar
Heiko Carstens committed
482
	if (!MACHINE_IS_VM || pfault_disable)
Linus Torvalds's avatar
Linus Torvalds committed
483
		return -1;
484
485
486
487
	asm volatile(
		"	diag	%1,%0,0x258\n"
		"0:	j	2f\n"
		"1:	la	%0,8\n"
Linus Torvalds's avatar
Linus Torvalds committed
488
		"2:\n"
489
490
		EX_TABLE(0b,1b)
		: "=d" (rc) : "a" (&refbk), "m" (refbk) : "cc");
Linus Torvalds's avatar
Linus Torvalds committed
491
492
493
494
495
496
497
498
499
        __ctl_set_bit(0, 9);
        return rc;
}

void pfault_fini(void)
{
	pfault_refbk_t refbk =
	{ 0x258, 1, 5, 2, 0ULL, 0ULL, 0ULL, 0ULL };

Heiko Carstens's avatar
Heiko Carstens committed
500
	if (!MACHINE_IS_VM || pfault_disable)
Linus Torvalds's avatar
Linus Torvalds committed
501
502
		return;
	__ctl_clear_bit(0,9);
503
504
	asm volatile(
		"	diag	%0,0,0x258\n"
Linus Torvalds's avatar
Linus Torvalds committed
505
		"0:\n"
506
507
		EX_TABLE(0b,0b)
		: : "a" (&refbk), "m" (refbk) : "cc");
Linus Torvalds's avatar
Linus Torvalds committed
508
509
}

510
static void pfault_interrupt(__u16 error_code)
Linus Torvalds's avatar
Linus Torvalds committed
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
{
	struct task_struct *tsk;
	__u16 subcode;

	/*
	 * Get the external interruption subcode & pfault
	 * initial/completion signal bit. VM stores this 
	 * in the 'cpu address' field associated with the
         * external interrupt. 
	 */
	subcode = S390_lowcore.cpu_addr;
	if ((subcode & 0xff00) != __SUBCODE_MASK)
		return;

	/*
	 * Get the token (= address of the task structure of the affected task).
	 */
	tsk = *(struct task_struct **) __LC_PFAULT_INTPARM;

	if (subcode & 0x0080) {
		/* signal bit is set -> a page has been swapped in by VM */
		if (xchg(&tsk->thread.pfault_wait, -1) != 0) {
			/* Initial interrupt was faster than the completion
			 * interrupt. pfault_wait is valid. Set pfault_wait
			 * back to zero and wake up the process. This can
			 * safely be done because the task is still sleeping
537
			 * and can't produce new pfaults. */
Linus Torvalds's avatar
Linus Torvalds committed
538
539
			tsk->thread.pfault_wait = 0;
			wake_up_process(tsk);
540
			put_task_struct(tsk);
Linus Torvalds's avatar
Linus Torvalds committed
541
542
543
		}
	} else {
		/* signal bit not set -> a real page is missing. */
544
		get_task_struct(tsk);
Linus Torvalds's avatar
Linus Torvalds committed
545
546
547
548
549
550
551
552
553
		set_task_state(tsk, TASK_UNINTERRUPTIBLE);
		if (xchg(&tsk->thread.pfault_wait, 1) != 0) {
			/* Completion interrupt was faster than the initial
			 * interrupt (swapped in a -1 for pfault_wait). Set
			 * pfault_wait back to zero and exit. This can be
			 * done safely because tsk is running in kernel 
			 * mode and can't produce new pfaults. */
			tsk->thread.pfault_wait = 0;
			set_task_state(tsk, TASK_RUNNING);
554
			put_task_struct(tsk);
Linus Torvalds's avatar
Linus Torvalds committed
555
556
557
558
559
		} else
			set_tsk_need_resched(tsk);
	}
}

Heiko Carstens's avatar
Heiko Carstens committed
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
void __init pfault_irq_init(void)
{
	if (!MACHINE_IS_VM)
		return;

	/*
	 * Try to get pfault pseudo page faults going.
	 */
	if (register_early_external_interrupt(0x2603, pfault_interrupt,
					      &ext_int_pfault) != 0)
		panic("Couldn't request external interrupt 0x2603");

	if (pfault_init() == 0)
		return;

	/* Tough luck, no pfault. */
	pfault_disable = 1;
	unregister_early_external_interrupt(0x2603, pfault_interrupt,
					    &ext_int_pfault);
}
#endif