vserver 1.9.5.x5
[linux-2.6.git] / arch / sparc64 / mm / fault.c
index 5fc4644..45edb94 100644 (file)
@@ -27,6 +27,7 @@
 #include <asm/asi.h>
 #include <asm/lsu.h>
 #include <asm/sections.h>
+#include <asm/kdebug.h>
 
 #define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0]))
 
@@ -147,6 +148,9 @@ static void unhandled_fault(unsigned long address, struct task_struct *tsk,
        printk(KERN_ALERT "tsk->{mm,active_mm}->pgd = %016lx\n",
               (tsk->mm ? (unsigned long) tsk->mm->pgd :
                          (unsigned long) tsk->active_mm->pgd));
+       if (notify_die(DIE_GPF, "general protection fault", regs,
+                      0, 0, SIGSEGV) == NOTIFY_STOP)
+               return;
        die_if_kernel("Oops", regs);
 }
 
@@ -171,6 +175,7 @@ static void bad_kernel_pc(struct pt_regs *regs)
 static unsigned int get_user_insn(unsigned long tpc)
 {
        pgd_t *pgdp = pgd_offset(current->mm, tpc);
+       pud_t *pudp;
        pmd_t *pmdp;
        pte_t *ptep, pte;
        unsigned long pa;
@@ -179,7 +184,10 @@ static unsigned int get_user_insn(unsigned long tpc)
 
        if (pgd_none(*pgdp))
                goto outret;
-       pmdp = pmd_offset(pgdp, tpc);
+       pudp = pud_offset(pgdp, tpc);
+       if (pud_none(*pudp))
+               goto outret;
+       pmdp = pmd_offset(pudp, tpc);
        if (pmd_none(*pmdp))
                goto outret;
 
@@ -218,9 +226,9 @@ static void do_fault_siginfo(int code, int sig, struct pt_regs *regs,
        info.si_signo = sig;
        info.si_errno = 0;
        if (fault_code & FAULT_CODE_ITLB)
-               info.si_addr = (void *) regs->tpc;
+               info.si_addr = (void __user *) regs->tpc;
        else
-               info.si_addr = (void *)
+               info.si_addr = (void __user *)
                        compute_effective_address(regs, insn, 0);
        info.si_trapno = 0;
        force_sig_info(sig, &info, current);
@@ -257,7 +265,7 @@ static void do_kernel_fault(struct pt_regs *regs, int si_code, int fault_code,
         * in that case.
         */
 
-       if (!(fault_code & FAULT_CODE_WRITE) &&
+       if (!(fault_code & (FAULT_CODE_WRITE|FAULT_CODE_ITLB)) &&
            (insn & 0xc0800000) == 0xc0800000) {
                if (insn & 0x2000)
                        asi = (regs->tstate >> 24);
@@ -318,8 +326,13 @@ asmlinkage void do_sparc64_fault(struct pt_regs *regs)
        int si_code, fault_code;
        unsigned long address;
 
-       si_code = SEGV_MAPERR;
        fault_code = get_thread_fault_code();
+
+       if (notify_die(DIE_PAGE_FAULT, "page_fault", regs,
+                      fault_code, 0, SIGSEGV) == NOTIFY_STOP)
+               return;
+
+       si_code = SEGV_MAPERR;
        address = current_thread_info()->fault_address;
 
        if ((fault_code & FAULT_CODE_ITLB) &&
@@ -343,7 +356,7 @@ asmlinkage void do_sparc64_fault(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 intr_or_no_mm;
 
        if (test_thread_flag(TIF_32BIT)) {
@@ -352,7 +365,15 @@ asmlinkage void do_sparc64_fault(struct pt_regs *regs)
                address &= 0xffffffff;
        }
 
-       down_read(&mm->mmap_sem);
+       if (!down_read_trylock(&mm->mmap_sem)) {
+               if ((regs->tstate & TSTATE_PRIV) &&
+                   !search_exception_tables(regs->tpc)) {
+                       insn = get_fault_insn(regs, insn);
+                       goto handle_kernel_fault;
+               }
+               down_read(&mm->mmap_sem);
+       }
+
        vma = find_vma(mm, address);
        if (!vma)
                goto bad_area;
@@ -408,6 +429,16 @@ continue_fault:
         */
 good_area:
        si_code = SEGV_ACCERR;
+
+       /* If we took a ITLB miss on a non-executable page, catch
+        * that here.
+        */
+       if ((fault_code & FAULT_CODE_ITLB) && !(vma->vm_flags & VM_EXEC)) {
+               BUG_ON(address != regs->tpc);
+               BUG_ON(regs->tstate & TSTATE_PRIV);
+               goto bad_area;
+       }
+
        if (fault_code & FAULT_CODE_WRITE) {
                if (!(vma->vm_flags & VM_WRITE))
                        goto bad_area;
@@ -418,7 +449,8 @@ good_area:
                if (tlb_type == spitfire &&
                    (vma->vm_flags & VM_EXEC) != 0 &&
                    vma->vm_file != NULL)
-                       set_thread_flag(TIF_BLKCOMMIT);
+                       set_thread_fault_code(fault_code |
+                                             FAULT_CODE_BLKCOMMIT);
        } else {
                /* Allow reads even for write-only mappings */
                if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
@@ -426,16 +458,18 @@ good_area:
        }
 
        switch (handle_mm_fault(mm, vma, address, (fault_code & FAULT_CODE_WRITE))) {
-       case 1:
+       case VM_FAULT_MINOR:
                current->min_flt++;
                break;
-       case 2:
+       case VM_FAULT_MAJOR:
                current->maj_flt++;
                break;
-       case 0:
+       case VM_FAULT_SIGBUS:
                goto do_sigbus;
-       default:
+       case VM_FAULT_OOM:
                goto out_of_memory;
+       default:
+               BUG();
        }
 
        up_read(&mm->mmap_sem);
@@ -487,6 +521,5 @@ do_sigbus:
 fault_done:
        /* These values are no longer needed, clear them. */
        set_thread_fault_code(0);
-       clear_thread_flag(TIF_BLKCOMMIT);
        current_thread_info()->fault_address = 0;
 }