summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2012-05-14 10:27:34 +0900
committerPaul Mundt <lethal@linux-sh.org>2012-05-14 10:27:34 +0900
commitdbdb4e9f3fd4914caba6f102e62eef23920ab1f4 (patch)
treebf5d09cc2d744348c71a33e9b447cbae7d0297c2
parentc4f10e5cd79ce09ef94be0924395c62350bf262d (diff)
sh: Tidy up and generalize page fault error paths.
This follows the x86 changes for tidying up the page fault error paths. We'll build on top of this for _32/_64 unification. Signed-off-by: Paul Mundt <lethal@linux-sh.org>
-rw-r--r--arch/sh/include/asm/kdebug.h2
-rw-r--r--arch/sh/mm/fault_32.c325
2 files changed, 222 insertions, 105 deletions
diff --git a/arch/sh/include/asm/kdebug.h b/arch/sh/include/asm/kdebug.h
index 5f6d2e9ccb7c..a6201f10c273 100644
--- a/arch/sh/include/asm/kdebug.h
+++ b/arch/sh/include/asm/kdebug.h
@@ -10,4 +10,6 @@ enum die_val {
DIE_SSTEP,
};
+extern void printk_address(unsigned long address, int reliable);
+
#endif /* __ASM_SH_KDEBUG_H */
diff --git a/arch/sh/mm/fault_32.c b/arch/sh/mm/fault_32.c
index 8ce856d1572d..889e83b5ff22 100644
--- a/arch/sh/mm/fault_32.c
+++ b/arch/sh/mm/fault_32.c
@@ -2,7 +2,7 @@
* Page fault handler for SH with an MMU.
*
* Copyright (C) 1999 Niibe Yutaka
- * Copyright (C) 2003 - 2009 Paul Mundt
+ * Copyright (C) 2003 - 2012 Paul Mundt
*
* Based on linux/arch/i386/mm/fault.c:
* Copyright (C) 1995 Linus Torvalds
@@ -16,6 +16,7 @@
#include <linux/hardirq.h>
#include <linux/kprobes.h>
#include <linux/perf_event.h>
+#include <linux/kdebug.h>
#include <asm/io_trapped.h>
#include <asm/mmu_context.h>
#include <asm/tlbflush.h>
@@ -35,6 +36,20 @@ static inline int notify_page_fault(struct pt_regs *regs, int trap)
return ret;
}
+static void
+force_sig_info_fault(int si_signo, int si_code, unsigned long address,
+ struct task_struct *tsk)
+{
+ siginfo_t info;
+
+ info.si_signo = si_signo;
+ info.si_errno = 0;
+ info.si_code = si_code;
+ info.si_addr = (void __user *)address;
+
+ force_sig_info(si_signo, &info, tsk);
+}
+
/*
* This is useful to dump out the page tables associated with
* 'addr' in mm 'mm'.
@@ -176,6 +191,185 @@ static noinline int vmalloc_fault(unsigned long address)
return 0;
}
+static void
+show_fault_oops(struct pt_regs *regs, unsigned long address)
+{
+ if (!oops_may_print())
+ return;
+
+ printk(KERN_ALERT "BUG: unable to handle kernel ");
+ if (address < PAGE_SIZE)
+ printk(KERN_CONT "NULL pointer dereference");
+ else
+ printk(KERN_CONT "paging request");
+
+ printk(KERN_CONT " at %08lx\n", address);
+ printk(KERN_ALERT "PC:");
+ printk_address(regs->pc, 1);
+
+ show_pte(NULL, address);
+}
+
+static noinline void
+no_context(struct pt_regs *regs, unsigned long writeaccess,
+ unsigned long address)
+{
+ /* Are we prepared to handle this kernel fault? */
+ if (fixup_exception(regs))
+ return;
+
+ if (handle_trapped_io(regs, address))
+ return;
+
+ /*
+ * Oops. The kernel tried to access some bad page. We'll have to
+ * terminate things with extreme prejudice.
+ */
+ bust_spinlocks(1);
+
+ show_fault_oops(regs, address);
+
+ die("Oops", regs, writeaccess);
+ bust_spinlocks(0);
+ do_exit(SIGKILL);
+}
+
+static void
+__bad_area_nosemaphore(struct pt_regs *regs, unsigned long writeaccess,
+ unsigned long address, int si_code)
+{
+ struct task_struct *tsk = current;
+
+ /* User mode accesses just cause a SIGSEGV */
+ if (user_mode(regs)) {
+ /*
+ * It's possible to have interrupts off here:
+ */
+ local_irq_enable();
+
+ force_sig_info_fault(SIGSEGV, si_code, address, tsk);
+
+ return;
+ }
+
+ no_context(regs, writeaccess, address);
+}
+
+static noinline void
+bad_area_nosemaphore(struct pt_regs *regs, unsigned long writeaccess,
+ unsigned long address)
+{
+ __bad_area_nosemaphore(regs, writeaccess, address, SEGV_MAPERR);
+}
+
+static void
+__bad_area(struct pt_regs *regs, unsigned long writeaccess,
+ unsigned long address, int si_code)
+{
+ struct mm_struct *mm = current->mm;
+
+ /*
+ * Something tried to access memory that isn't in our memory map..
+ * Fix it, but check if it's kernel or user first..
+ */
+ up_read(&mm->mmap_sem);
+
+ __bad_area_nosemaphore(regs, writeaccess, address, si_code);
+}
+
+static noinline void
+bad_area(struct pt_regs *regs, unsigned long writeaccess, unsigned long address)
+{
+ __bad_area(regs, writeaccess, address, SEGV_MAPERR);
+}
+
+static noinline void
+bad_area_access_error(struct pt_regs *regs, unsigned long writeaccess,
+ unsigned long address)
+{
+ __bad_area(regs, writeaccess, address, SEGV_ACCERR);
+}
+
+static void out_of_memory(void)
+{
+ /*
+ * We ran out of memory, call the OOM killer, and return the userspace
+ * (which will retry the fault, or kill us if we got oom-killed):
+ */
+ up_read(&current->mm->mmap_sem);
+
+ pagefault_out_of_memory();
+}
+
+static void
+do_sigbus(struct pt_regs *regs, unsigned long writeaccess, unsigned long address)
+{
+ struct task_struct *tsk = current;
+ struct mm_struct *mm = tsk->mm;
+
+ up_read(&mm->mmap_sem);
+
+ /* Kernel mode? Handle exceptions or die: */
+ if (!user_mode(regs))
+ no_context(regs, writeaccess, address);
+
+ force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk);
+}
+
+static noinline int
+mm_fault_error(struct pt_regs *regs, unsigned long writeaccess,
+ unsigned long address, unsigned int fault)
+{
+ /*
+ * Pagefault was interrupted by SIGKILL. We have no reason to
+ * continue pagefault.
+ */
+ if (fatal_signal_pending(current)) {
+ if (!(fault & VM_FAULT_RETRY))
+ up_read(&current->mm->mmap_sem);
+ if (!user_mode(regs))
+ no_context(regs, writeaccess, address);
+ return 1;
+ }
+
+ if (!(fault & VM_FAULT_ERROR))
+ return 0;
+
+ if (fault & VM_FAULT_OOM) {
+ /* Kernel mode? Handle exceptions or die: */
+ if (!user_mode(regs)) {
+ up_read(&current->mm->mmap_sem);
+ no_context(regs, writeaccess, address);
+ return 1;
+ }
+
+ out_of_memory();
+ } else {
+ if (fault & VM_FAULT_SIGBUS)
+ do_sigbus(regs, writeaccess, address);
+ else
+ BUG();
+ }
+
+ return 1;
+}
+
+static inline int access_error(int write, struct vm_area_struct *vma)
+{
+ if (write) {
+ /* write, present and write, not present: */
+ if (unlikely(!(vma->vm_flags & VM_WRITE)))
+ return 1;
+ return 0;
+ }
+
+ /* read, not present: */
+ if (unlikely(!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE))))
+ return 1;
+
+ return 0;
+}
+
static int fault_in_kernel_space(unsigned long address)
{
return address >= TASK_SIZE;
@@ -194,15 +388,12 @@ asmlinkage void __kprobes do_page_fault(struct pt_regs *regs,
struct task_struct *tsk;
struct mm_struct *mm;
struct vm_area_struct * vma;
- int si_code;
int fault;
- siginfo_t info;
unsigned int flags = (FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE |
(writeaccess ? FAULT_FLAG_WRITE : 0));
tsk = current;
mm = tsk->mm;
- si_code = SEGV_MAPERR;
vec = lookup_exception_vector();
/*
@@ -220,7 +411,8 @@ asmlinkage void __kprobes do_page_fault(struct pt_regs *regs,
if (notify_page_fault(regs, vec))
return;
- goto bad_area_nosemaphore;
+ bad_area_nosemaphore(regs, writeaccess, address);
+ return;
}
if (unlikely(notify_page_fault(regs, vec)))
@@ -236,34 +428,38 @@ asmlinkage void __kprobes do_page_fault(struct pt_regs *regs,
* If we're in an interrupt, have no user context or are running
* in an atomic region then we must not take the fault:
*/
- if (in_atomic() || !mm)
- goto no_context;
+ if (unlikely(in_atomic() || !mm)) {
+ bad_area_nosemaphore(regs, writeaccess, address);
+ return;
+ }
retry:
down_read(&mm->mmap_sem);
vma = find_vma(mm, address);
- if (!vma)
- goto bad_area;
- if (vma->vm_start <= address)
+ if (unlikely(!vma)) {
+ bad_area(regs, writeaccess, address);
+ return;
+ }
+ if (likely(vma->vm_start <= address))
goto good_area;
- if (!(vma->vm_flags & VM_GROWSDOWN))
- goto bad_area;
- if (expand_stack(vma, address))
- goto bad_area;
+ if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
+ bad_area(regs, writeaccess, address);
+ return;
+ }
+ if (unlikely(expand_stack(vma, address))) {
+ bad_area(regs, writeaccess, address);
+ return;
+ }
/*
* Ok, we have a good vm_area for this memory access, so
* we can handle it..
*/
good_area:
- si_code = SEGV_ACCERR;
- if (writeaccess) {
- if (!(vma->vm_flags & VM_WRITE))
- goto bad_area;
- } else {
- if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
- goto bad_area;
+ if (unlikely(access_error(writeaccess, vma))) {
+ bad_area_access_error(regs, writeaccess, address);
+ return;
}
/*
@@ -273,16 +469,9 @@ good_area:
*/
fault = handle_mm_fault(mm, vma, address, flags);
- if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current))
- return;
-
- if (unlikely(fault & VM_FAULT_ERROR)) {
- if (fault & VM_FAULT_OOM)
- goto out_of_memory;
- else if (fault & VM_FAULT_SIGBUS)
- goto do_sigbus;
- BUG();
- }
+ if (unlikely(fault & (VM_FAULT_RETRY | VM_FAULT_ERROR)))
+ if (mm_fault_error(regs, writeaccess, address, fault))
+ return;
if (flags & FAULT_FLAG_ALLOW_RETRY) {
if (fault & VM_FAULT_MAJOR) {
@@ -307,80 +496,6 @@ good_area:
}
up_read(&mm->mmap_sem);
- 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:
- up_read(&mm->mmap_sem);
-
-bad_area_nosemaphore:
- if (user_mode(regs)) {
- info.si_signo = SIGSEGV;
- info.si_errno = 0;
- info.si_code = si_code;
- info.si_addr = (void *) address;
- force_sig_info(SIGSEGV, &info, tsk);
- return;
- }
-
-no_context:
- /* Are we prepared to handle this kernel fault? */
- if (fixup_exception(regs))
- return;
-
- if (handle_trapped_io(regs, address))
- return;
-/*
- * Oops. The kernel tried to access some bad page. We'll have to
- * terminate things with extreme prejudice.
- *
- */
-
- bust_spinlocks(1);
-
- if (oops_may_print()) {
- printk(KERN_ALERT
- "Unable to handle kernel %s at virtual address %08lx\n",
- (address < PAGE_SIZE) ? "NULL pointer dereference" :
- "paging request", address);
-
- show_pte(mm, address);
- }
-
- die("Oops", regs, writeaccess);
- bust_spinlocks(0);
- do_exit(SIGKILL);
-
-/*
- * We ran out of memory, or some other thing happened to us that made
- * us unable to handle the page fault gracefully.
- */
-out_of_memory:
- up_read(&mm->mmap_sem);
- if (!user_mode(regs))
- goto no_context;
- pagefault_out_of_memory();
- return;
-
-do_sigbus:
- up_read(&mm->mmap_sem);
-
- /*
- * Send a sigbus, regardless of whether we were in kernel
- * or user mode.
- */
- info.si_signo = SIGBUS;
- info.si_errno = 0;
- info.si_code = BUS_ADRERR;
- info.si_addr = (void *)address;
- force_sig_info(SIGBUS, &info, tsk);
-
- /* Kernel mode? Handle exceptions or die */
- if (!user_mode(regs))
- goto no_context;
}
/*