diff options
author | Paul Mundt <lethal@linux-sh.org> | 2012-05-14 10:27:34 +0900 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2012-05-14 10:27:34 +0900 |
commit | dbdb4e9f3fd4914caba6f102e62eef23920ab1f4 (patch) | |
tree | bf5d09cc2d744348c71a33e9b447cbae7d0297c2 /arch | |
parent | c4f10e5cd79ce09ef94be0924395c62350bf262d (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>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/sh/include/asm/kdebug.h | 2 | ||||
-rw-r--r-- | arch/sh/mm/fault_32.c | 325 |
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(¤t->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(¤t->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(¤t->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; } /* |