fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / sparc64 / mm / fault.c
index 49c3dd2..a7372db 100644 (file)
@@ -18,6 +18,8 @@
 #include <linux/smp_lock.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
+#include <linux/kprobes.h>
+#include <linux/kallsyms.h>
 
 #include <asm/page.h>
 #include <asm/pgtable.h>
 #include <asm/asi.h>
 #include <asm/lsu.h>
 #include <asm/sections.h>
+#include <asm/kdebug.h>
+#include <asm/mmu_context.h>
 
-#define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0]))
+#ifdef CONFIG_KPROBES
+ATOMIC_NOTIFIER_HEAD(notify_page_fault_chain);
 
-extern struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS];
+/* Hook to register for page fault notifications */
+int register_page_fault_notifier(struct notifier_block *nb)
+{
+       return atomic_notifier_chain_register(&notify_page_fault_chain, nb);
+}
 
-/*
- * To debug kernel during syscall entry.
- */
-void syscall_trace_entry(struct pt_regs *regs)
+int unregister_page_fault_notifier(struct notifier_block *nb)
 {
-       printk("scall entry: %s[%d]/cpu%d: %d\n", current->comm, current->pid, smp_processor_id(), (int) regs->u_regs[UREG_G1]);
+       return atomic_notifier_chain_unregister(&notify_page_fault_chain, nb);
 }
 
-/*
- * To debug kernel during syscall exit.
- */
-void syscall_trace_exit(struct pt_regs *regs)
+static inline int notify_page_fault(enum die_val val, const char *str,
+                       struct pt_regs *regs, long err, int trap, int sig)
+{
+       struct die_args args = {
+               .regs = regs,
+               .str = str,
+               .err = err,
+               .trapnr = trap,
+               .signr = sig
+       };
+       return atomic_notifier_call_chain(&notify_page_fault_chain, val, &args);
+}
+#else
+static inline int notify_page_fault(enum die_val val, const char *str,
+                       struct pt_regs *regs, long err, int trap, int sig)
 {
-       printk("scall exit: %s[%d]/cpu%d: %d\n", current->comm, current->pid, smp_processor_id(), (int) regs->u_regs[UREG_G1]);
+       return NOTIFY_DONE;
 }
+#endif
 
 /*
  * To debug kernel to catch accesses to certain virtual/physical addresses.
@@ -85,55 +103,9 @@ void set_brkpt(unsigned long addr, unsigned char mask, int flags, int mode)
                             : "memory");
 }
 
-/* Nice, simple, prom library does all the sweating for us. ;) */
-unsigned long __init prom_probe_memory (void)
-{
-       register struct linux_mlist_p1275 *mlist;
-       register unsigned long bytes, base_paddr, tally;
-       register int i;
-
-       i = 0;
-       mlist = *prom_meminfo()->p1275_available;
-       bytes = tally = mlist->num_bytes;
-       base_paddr = mlist->start_adr;
-  
-       sp_banks[0].base_addr = base_paddr;
-       sp_banks[0].num_bytes = bytes;
-
-       while (mlist->theres_more != (void *) 0) {
-               i++;
-               mlist = mlist->theres_more;
-               bytes = mlist->num_bytes;
-               tally += bytes;
-               if (i >= SPARC_PHYS_BANKS-1) {
-                       printk ("The machine has more banks than "
-                               "this kernel can support\n"
-                               "Increase the SPARC_PHYS_BANKS "
-                               "setting (currently %d)\n",
-                               SPARC_PHYS_BANKS);
-                       i = SPARC_PHYS_BANKS-1;
-                       break;
-               }
-    
-               sp_banks[i].base_addr = mlist->start_adr;
-               sp_banks[i].num_bytes = mlist->num_bytes;
-       }
-
-       i++;
-       sp_banks[i].base_addr = 0xdeadbeefbeefdeadUL;
-       sp_banks[i].num_bytes = 0;
-
-       /* Now mask all bank sizes on a page boundary, it is all we can
-        * use anyways.
-        */
-       for (i = 0; sp_banks[i].num_bytes != 0; i++)
-               sp_banks[i].num_bytes &= PAGE_MASK;
-
-       return tally;
-}
-
-static void unhandled_fault(unsigned long address, struct task_struct *tsk,
-                           struct pt_regs *regs)
+static void __kprobes unhandled_fault(unsigned long address,
+                                     struct task_struct *tsk,
+                                     struct pt_regs *regs)
 {
        if ((unsigned long) address < PAGE_SIZE) {
                printk(KERN_ALERT "Unable to handle kernel NULL "
@@ -143,19 +115,27 @@ static void unhandled_fault(unsigned long address, struct task_struct *tsk,
                       "at virtual address %016lx\n", (unsigned long)address);
        }
        printk(KERN_ALERT "tsk->{mm,active_mm}->context = %016lx\n",
-              (tsk->mm ? tsk->mm->context : tsk->active_mm->context));
+              (tsk->mm ?
+               CTX_HWBITS(tsk->mm->context) :
+               CTX_HWBITS(tsk->active_mm->context)));
        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);
 }
 
-static void bad_kernel_pc(struct pt_regs *regs)
+static void bad_kernel_pc(struct pt_regs *regs, unsigned long vaddr)
 {
        unsigned long *ksp;
 
        printk(KERN_CRIT "OOPS: Bogus kernel PC [%016lx] in fault handler\n",
               regs->tpc);
+       printk(KERN_CRIT "OOPS: RPC [%016lx]\n", regs->u_regs[15]);
+       print_symbol("RPC: <%s>\n", regs->u_regs[15]);
+       printk(KERN_CRIT "OOPS: Fault was to vaddr[%lx]\n", vaddr);
        __asm__("mov %%sp, %0" : "=r" (ksp));
        show_stack(current, ksp);
        unhandled_fault(regs->tpc, current, regs);
@@ -166,11 +146,12 @@ static void bad_kernel_pc(struct pt_regs *regs)
  * this. Additionally, to prevent kswapd from ripping ptes from
  * under us, raise interrupts around the time that we look at the
  * pte, kswapd will have to wait to get his smp ipi response from
- * us. This saves us having to get page_table_lock.
+ * us. vmtruncate likewise. This saves us having to get pte lock.
  */
 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 +160,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;
 
@@ -192,7 +176,7 @@ static unsigned int get_user_insn(unsigned long tpc)
        if (!pte_present(pte))
                goto out;
 
-       pa  = (pte_val(pte) & _PAGE_PADDR);
+       pa  = (pte_pfn(pte) << PAGE_SHIFT);
        pa += (tpc & ~PAGE_MASK);
 
        /* Use phys bypass so we don't pollute dtlb/dcache. */
@@ -246,7 +230,6 @@ static unsigned int get_fault_insn(struct pt_regs *regs, unsigned int insn)
 static void do_kernel_fault(struct pt_regs *regs, int si_code, int fault_code,
                            unsigned int insn, unsigned long address)
 {
-       unsigned long g2;
        unsigned char asi = ASI_P;
  
        if ((!insn) && (regs->tstate & TSTATE_PRIV))
@@ -277,11 +260,9 @@ static void do_kernel_fault(struct pt_regs *regs, int si_code, int fault_code,
                }
        }
                
-       g2 = regs->u_regs[UREG_G2];
-
        /* Is this in ex_table? */
        if (regs->tstate & TSTATE_PRIV) {
-               unsigned long fixup;
+               const struct exception_table_entry *entry;
 
                if (asi == ASI_P && (insn & 0xc0800000) == 0xc0800000) {
                        if (insn & 0x2000)
@@ -292,10 +273,9 @@ static void do_kernel_fault(struct pt_regs *regs, int si_code, int fault_code,
        
                /* Look in asi.h: All _S asis have LS bit set */
                if ((asi & 0x1) &&
-                   (fixup = search_extables_range(regs->tpc, &g2))) {
-                       regs->tpc = fixup;
+                   (entry = search_exception_tables(regs->tpc))) {
+                       regs->tpc = entry->fixup;
                        regs->tnpc = regs->tpc + 4;
-                       regs->u_regs[UREG_G2] = g2;
                        return;
                }
        } else {
@@ -310,16 +290,21 @@ cannot_handle:
        unhandled_fault (address, current, regs);
 }
 
-asmlinkage void do_sparc64_fault(struct pt_regs *regs)
+asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)
 {
        struct mm_struct *mm = current->mm;
        struct vm_area_struct *vma;
        unsigned int insn = 0;
        int si_code, fault_code;
-       unsigned long address;
+       unsigned long address, mm_rss;
 
-       si_code = SEGV_MAPERR;
        fault_code = get_thread_fault_code();
+
+       if (notify_page_fault(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) &&
@@ -334,7 +319,7 @@ asmlinkage void do_sparc64_fault(struct pt_regs *regs)
                    (tpc >= MODULES_VADDR && tpc < MODULES_END)) {
                        /* Valid, no problems... */
                } else {
-                       bad_kernel_pc(regs);
+                       bad_kernel_pc(regs, address);
                        return;
                }
        }
@@ -343,7 +328,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 +337,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;
@@ -371,8 +364,12 @@ asmlinkage void do_sparc64_fault(struct pt_regs *regs)
                insn = get_fault_insn(regs, 0);
                if (!insn)
                        goto continue_fault;
+               /* All loads, stores and atomics have bits 30 and 31 both set
+                * in the instruction.  Bit 21 is set in all stores, but we
+                * have to avoid prefetches which also have bit 21 set.
+                */
                if ((insn & 0xc0200000) == 0xc0200000 &&
-                   (insn & 0x1780000) != 0x1680000) {
+                   (insn & 0x01780000) != 0x01680000) {
                        /* Don't bother updating thread struct value,
                         * because update_mmu_cache only cares which tlb
                         * the access came from.
@@ -437,20 +434,36 @@ 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);
-       goto fault_done;
+
+       mm_rss = get_mm_rss(mm);
+#ifdef CONFIG_HUGETLB_PAGE
+       mm_rss -= (mm->context.huge_pte_count * (HPAGE_SIZE / PAGE_SIZE));
+#endif
+       if (unlikely(mm_rss >
+                    mm->context.tsb_block[MM_TSB_BASE].tsb_rss_limit))
+               tsb_grow(mm, MM_TSB_BASE, mm_rss);
+#ifdef CONFIG_HUGETLB_PAGE
+       mm_rss = mm->context.huge_pte_count;
+       if (unlikely(mm_rss >
+                    mm->context.tsb_block[MM_TSB_HUGE].tsb_rss_limit))
+               tsb_grow(mm, MM_TSB_HUGE, mm_rss);
+#endif
+       return;
 
        /*
         * Something tried to access memory that isn't in our memory map..
@@ -462,8 +475,7 @@ bad_area:
 
 handle_kernel_fault:
        do_kernel_fault(regs, si_code, fault_code, insn, address);
-
-       goto fault_done;
+       return;
 
 /*
  * We ran out of memory, or some other thing happened to us that made
@@ -472,7 +484,8 @@ handle_kernel_fault:
 out_of_memory:
        insn = get_fault_insn(regs, insn);
        up_read(&mm->mmap_sem);
-       printk("VM: killing process %s\n", current->comm);
+       printk("VM: killing process %s(%d:#%u)\n",
+               current->comm, current->pid, current->xid);
        if (!(regs->tstate & TSTATE_PRIV))
                do_exit(SIGKILL);
        goto handle_kernel_fault;
@@ -494,9 +507,4 @@ do_sigbus:
        /* Kernel mode? Handle exceptions or die */
        if (regs->tstate & TSTATE_PRIV)
                goto handle_kernel_fault;
-
-fault_done:
-       /* These values are no longer needed, clear them. */
-       set_thread_fault_code(0);
-       current_thread_info()->fault_address = 0;
 }