ARM: 6166/1: Proper prefetch abort handling on pre-ARMv6
[safe/jmp/linux-2.6] / arch / arm / mm / fault.c
index 5e658a8..cbfb2ed 100644 (file)
  */
 #include <linux/module.h>
 #include <linux/signal.h>
-#include <linux/ptrace.h>
 #include <linux/mm.h>
+#include <linux/hardirq.h>
 #include <linux/init.h>
+#include <linux/kprobes.h>
+#include <linux/uaccess.h>
+#include <linux/page-flags.h>
+#include <linux/sched.h>
+#include <linux/highmem.h>
+#include <linux/perf_event.h>
 
 #include <asm/system.h>
 #include <asm/pgtable.h>
 #include <asm/tlbflush.h>
-#include <asm/uaccess.h>
 
 #include "fault.h"
 
 /*
+ * Fault status register encodings.  We steal bit 31 for our own purposes.
+ */
+#define FSR_LNX_PF             (1 << 31)
+#define FSR_WRITE              (1 << 11)
+#define FSR_FS4                        (1 << 10)
+#define FSR_FS3_0              (15)
+
+static inline int fsr_fs(unsigned int fsr)
+{
+       return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6;
+}
+
+#ifdef CONFIG_MMU
+
+#ifdef CONFIG_KPROBES
+static inline int notify_page_fault(struct pt_regs *regs, unsigned int fsr)
+{
+       int ret = 0;
+
+       if (!user_mode(regs)) {
+               /* kprobe_running() needs smp_processor_id() */
+               preempt_disable();
+               if (kprobe_running() && kprobe_fault_handler(regs, fsr))
+                       ret = 1;
+               preempt_enable();
+       }
+
+       return ret;
+}
+#else
+static inline int notify_page_fault(struct pt_regs *regs, unsigned int fsr)
+{
+       return 0;
+}
+#endif
+
+/*
  * This is useful to dump out the page tables associated with
  * 'addr' in mm 'mm'.
  */
@@ -49,9 +91,8 @@ void show_pte(struct mm_struct *mm, unsigned long addr)
                }
 
                pmd = pmd_offset(pgd, addr);
-#if PTRS_PER_PMD != 1
-               printk(", *pmd=%08lx", pmd_val(*pmd));
-#endif
+               if (PTRS_PER_PMD != 1)
+                       printk(", *pmd=%08lx", pmd_val(*pmd));
 
                if (pmd_none(*pmd))
                        break;
@@ -61,17 +102,22 @@ void show_pte(struct mm_struct *mm, unsigned long addr)
                        break;
                }
 
-#ifndef CONFIG_HIGHMEM
                /* We must not map this if we have highmem enabled */
+               if (PageHighMem(pfn_to_page(pmd_val(*pmd) >> PAGE_SHIFT)))
+                       break;
+
                pte = pte_offset_map(pmd, addr);
                printk(", *pte=%08lx", pte_val(*pte));
                printk(", *ppte=%08lx", pte_val(pte[-PTRS_PER_PTE]));
                pte_unmap(pte);
-#endif
        } while(0);
 
        printk("\n");
 }
+#else                                  /* CONFIG_MMU */
+void show_pte(struct mm_struct *mm, unsigned long addr)
+{ }
+#endif                                 /* CONFIG_MMU */
 
 /*
  * Oops.  The kernel tried to access some page that wasn't present.
@@ -146,21 +192,39 @@ void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
                __do_kernel_fault(mm, addr, fsr, regs);
 }
 
-#define VM_FAULT_BADMAP                (-20)
-#define VM_FAULT_BADACCESS     (-21)
+#ifdef CONFIG_MMU
+#define VM_FAULT_BADMAP                0x010000
+#define VM_FAULT_BADACCESS     0x020000
 
-static int
+/*
+ * Check that the permissions on the VMA allow for the fault which occurred.
+ * If we encountered a write fault, we must have write permission, otherwise
+ * we allow any permission.
+ */
+static inline bool access_error(unsigned int fsr, struct vm_area_struct *vma)
+{
+       unsigned int mask = VM_READ | VM_WRITE | VM_EXEC;
+
+       if (fsr & FSR_WRITE)
+               mask = VM_WRITE;
+       if (fsr & FSR_LNX_PF)
+               mask = VM_EXEC;
+
+       return vma->vm_flags & mask ? false : true;
+}
+
+static int __kprobes
 __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
                struct task_struct *tsk)
 {
        struct vm_area_struct *vma;
-       int fault, mask;
+       int fault;
 
        vma = find_vma(mm, addr);
        fault = VM_FAULT_BADMAP;
-       if (!vma)
+       if (unlikely(!vma))
                goto out;
-       if (vma->vm_start > addr)
+       if (unlikely(vma->vm_start > addr))
                goto check_stack;
 
        /*
@@ -168,46 +232,23 @@ __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
         * memory access, so we can handle it.
         */
 good_area:
-       if (fsr & (1 << 11)) /* write? */
-               mask = VM_WRITE;
-       else
-               mask = VM_READ|VM_EXEC|VM_WRITE;
-
-       fault = VM_FAULT_BADACCESS;
-       if (!(vma->vm_flags & mask))
+       if (access_error(fsr, vma)) {
+               fault = VM_FAULT_BADACCESS;
                goto out;
+       }
 
        /*
-        * If for any reason at all we couldn't handle
-        * the fault, make sure we exit gracefully rather
-        * than endlessly redo the fault.
-        */
-survive:
-       fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, fsr & (1 << 11));
-
-       /*
-        * Handle the "normal" cases first - successful and sigbus
+        * If for any reason at all we couldn't handle the fault, make
+        * sure we exit gracefully rather than endlessly redo the fault.
         */
-       switch (fault) {
-       case VM_FAULT_MAJOR:
-               tsk->maj_flt++;
+       fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, (fsr & FSR_WRITE) ? FAULT_FLAG_WRITE : 0);
+       if (unlikely(fault & VM_FAULT_ERROR))
                return fault;
-       case VM_FAULT_MINOR:
+       if (fault & VM_FAULT_MAJOR)
+               tsk->maj_flt++;
+       else
                tsk->min_flt++;
-       case VM_FAULT_SIGBUS:
-               return fault;
-       }
-
-       if (!is_init(tsk))
-               goto out;
-
-       /*
-        * If we are out of memory for pid1, sleep for a while and retry
-        */
-       up_read(&mm->mmap_sem);
-       yield();
-       down_read(&mm->mmap_sem);
-       goto survive;
+       return fault;
 
 check_stack:
        if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))
@@ -216,13 +257,16 @@ out:
        return fault;
 }
 
-static int
+static int __kprobes
 do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 {
        struct task_struct *tsk;
        struct mm_struct *mm;
        int fault, sig, code;
 
+       if (notify_page_fault(regs, fsr))
+               return 0;
+
        tsk = current;
        mm  = tsk->mm;
 
@@ -230,7 +274,7 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
         * If we're in an interrupt or have no user
         * context, we must not take the fault..
         */
-       if (in_interrupt() || !mm)
+       if (in_atomic() || !mm)
                goto no_context;
 
        /*
@@ -242,16 +286,44 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
                if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc))
                        goto no_context;
                down_read(&mm->mmap_sem);
+       } else {
+               /*
+                * The above down_read_trylock() might have succeeded in
+                * which case, we'll have missed the might_sleep() from
+                * down_read()
+                */
+               might_sleep();
+#ifdef CONFIG_DEBUG_VM
+               if (!user_mode(regs) &&
+                   !search_exception_tables(regs->ARM_pc))
+                       goto no_context;
+#endif
        }
 
        fault = __do_page_fault(mm, addr, fsr, tsk);
        up_read(&mm->mmap_sem);
 
+       perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, addr);
+       if (fault & VM_FAULT_MAJOR)
+               perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0, regs, addr);
+       else if (fault & VM_FAULT_MINOR)
+               perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0, regs, addr);
+
        /*
         * Handle the "normal" case first - VM_FAULT_MAJOR / VM_FAULT_MINOR
         */
-       if (fault >= VM_FAULT_MINOR)
+       if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS))))
+               return 0;
+
+       if (fault & VM_FAULT_OOM) {
+               /*
+                * We ran out of memory, call the OOM killer, and return to
+                * userspace (which will retry the fault, or kill us if we
+                * got oom-killed)
+                */
+               pagefault_out_of_memory();
                return 0;
+       }
 
        /*
         * If we are in kernel mode at this point, we
@@ -260,27 +332,14 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
        if (!user_mode(regs))
                goto no_context;
 
-       switch (fault) {
-       case VM_FAULT_OOM:
-               /*
-                * We ran out of memory, or some other thing
-                * happened to us that made us unable to handle
-                * the page fault gracefully.
-                */
-               printk("VM: killing process %s\n", tsk->comm);
-               do_exit(SIGKILL);
-               return 0;
-
-       case VM_FAULT_SIGBUS:
+       if (fault & VM_FAULT_SIGBUS) {
                /*
                 * We had some memory, but were unable to
                 * successfully fix up this page fault.
                 */
                sig = SIGBUS;
                code = BUS_ADRERR;
-               break;
-
-       default:
+       } else {
                /*
                 * Something tried to access memory that
                 * isn't in our memory map..
@@ -288,7 +347,6 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
                sig = SIGSEGV;
                code = fault == VM_FAULT_BADACCESS ?
                        SEGV_ACCERR : SEGV_MAPERR;
-               break;
        }
 
        __do_user_fault(tsk, addr, fsr, sig, code, regs);
@@ -298,6 +356,13 @@ no_context:
        __do_kernel_fault(mm, addr, fsr, regs);
        return 0;
 }
+#else                                  /* CONFIG_MMU */
+static int
+do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
+{
+       return 0;
+}
+#endif                                 /* CONFIG_MMU */
 
 /*
  * First Level Translation Fault Handler
@@ -316,7 +381,8 @@ no_context:
  * interrupt or a critical region, and should only copy the information
  * from the master page table, nothing more.
  */
-static int
+#ifdef CONFIG_MMU
+static int __kprobes
 do_translation_fault(unsigned long addr, unsigned int fsr,
                     struct pt_regs *regs)
 {
@@ -327,6 +393,9 @@ do_translation_fault(unsigned long addr, unsigned int fsr,
        if (addr < TASK_SIZE)
                return do_page_fault(addr, fsr, regs);
 
+       if (user_mode(regs))
+               goto bad_area;
+
        index = pgd_index(addr);
 
        /*
@@ -354,6 +423,14 @@ bad_area:
        do_bad_area(addr, fsr, regs);
        return 0;
 }
+#else                                  /* CONFIG_MMU */
+static int
+do_translation_fault(unsigned long addr, unsigned int fsr,
+                    struct pt_regs *regs)
+{
+       return 0;
+}
+#endif                                 /* CONFIG_MMU */
 
 /*
  * Some section permission faults need to be handled gracefully.
@@ -389,7 +466,12 @@ static struct fsr_info {
        { do_bad,               SIGILL,  BUS_ADRALN,    "alignment exception"              },
        { do_bad,               SIGKILL, 0,             "terminal exception"               },
        { do_bad,               SIGILL,  BUS_ADRALN,    "alignment exception"              },
+/* Do we need runtime check ? */
+#if __LINUX_ARM_ARCH__ < 6
        { do_bad,               SIGBUS,  0,             "external abort on linefetch"      },
+#else
+       { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "I-cache maintenance fault"        },
+#endif
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "section translation fault"        },
        { do_bad,               SIGBUS,  0,             "external abort on linefetch"      },
        { do_page_fault,        SIGSEGV, SEGV_MAPERR,   "page translation fault"           },
@@ -438,13 +520,13 @@ hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *)
 /*
  * Dispatch a data abort to the relevant handler.
  */
-asmlinkage void
+asmlinkage void __exception
 do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 {
-       const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6);
+       const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
        struct siginfo info;
 
-       if (!inf->fn(addr, fsr, regs))
+       if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
                return;
 
        printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
@@ -454,12 +536,61 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
        info.si_errno = 0;
        info.si_code  = inf->code;
        info.si_addr  = (void __user *)addr;
-       notify_die("", regs, &info, fsr, 0);
+       arm_notify_die("", regs, &info, fsr, 0);
 }
 
-asmlinkage void
-do_PrefetchAbort(unsigned long addr, struct pt_regs *regs)
+
+static struct fsr_info ifsr_info[] = {
+       { do_bad,               SIGBUS,  0,             "unknown 0"                        },
+       { do_bad,               SIGBUS,  0,             "unknown 1"                        },
+       { do_bad,               SIGBUS,  0,             "debug event"                      },
+       { do_bad,               SIGSEGV, SEGV_ACCERR,   "section access flag fault"        },
+       { do_bad,               SIGBUS,  0,             "unknown 4"                        },
+       { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "section translation fault"        },
+       { do_bad,               SIGSEGV, SEGV_ACCERR,   "page access flag fault"           },
+       { do_page_fault,        SIGSEGV, SEGV_MAPERR,   "page translation fault"           },
+       { do_bad,               SIGBUS,  0,             "external abort on non-linefetch"  },
+       { do_bad,               SIGSEGV, SEGV_ACCERR,   "section domain fault"             },
+       { do_bad,               SIGBUS,  0,             "unknown 10"                       },
+       { do_bad,               SIGSEGV, SEGV_ACCERR,   "page domain fault"                },
+       { do_bad,               SIGBUS,  0,             "external abort on translation"    },
+       { do_sect_fault,        SIGSEGV, SEGV_ACCERR,   "section permission fault"         },
+       { do_bad,               SIGBUS,  0,             "external abort on translation"    },
+       { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "page permission fault"            },
+       { do_bad,               SIGBUS,  0,             "unknown 16"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 17"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 18"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 19"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 20"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 21"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 22"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 23"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 24"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 25"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 26"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 27"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 28"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 29"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 30"                       },
+       { do_bad,               SIGBUS,  0,             "unknown 31"                       },
+};
+
+asmlinkage void __exception
+do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs)
 {
-       do_translation_fault(addr, 0, regs);
+       const struct fsr_info *inf = ifsr_info + fsr_fs(ifsr);
+       struct siginfo info;
+
+       if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs))
+               return;
+
+       printk(KERN_ALERT "Unhandled prefetch abort: %s (0x%03x) at 0x%08lx\n",
+               inf->name, ifsr, addr);
+
+       info.si_signo = inf->sig;
+       info.si_errno = 0;
+       info.si_code  = inf->code;
+       info.si_addr  = (void __user *)addr;
+       arm_notify_die("", regs, &info, ifsr, 0);
 }