summaryrefslogtreecommitdiffstats
path: root/arch/ppc/mm/fault.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/ppc/mm/fault.c')
-rw-r--r--arch/ppc/mm/fault.c264
1 files changed, 154 insertions, 110 deletions
diff --git a/arch/ppc/mm/fault.c b/arch/ppc/mm/fault.c
index 7104d6bbb..fea722780 100644
--- a/arch/ppc/mm/fault.c
+++ b/arch/ppc/mm/fault.c
@@ -1,9 +1,14 @@
/*
- * ARCH/ppc/mm/fault.c
+ * arch/ppc/mm/fault.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
* Ported to PPC by Gary Thomas
- * Modified by Cort Dougan (cort@cs.nmt.edu)
+ * Modified by Cort Dougan and Paul Mackerras.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
*/
#include <linux/config.h>
@@ -17,132 +22,151 @@
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
+#include <linux/interrupt.h>
#include <asm/page.h>
#include <asm/pgtable.h>
+#include <asm/mmu_context.h>
-extern void die_if_kernel(char *, struct pt_regs *, long);
-extern void do_page_fault(struct pt_regs *, unsigned long, unsigned long);
-void new_page_fault(unsigned long address, unsigned long code, unsigned long text,
- struct pt_regs *regs);
-
+#ifdef CONFIG_PMAC
+extern void (*xmon_fault_handler)(void);
+#endif
-#undef SHOW_FAULTS
-#undef NOISY_INSTRFAULT
-#undef NOISY_DATAFAULT
+/* the linux norm for the function name is show_regs() so
+ make it call dump_regs() on the mac -- Cort */
+#ifdef CONFIG_PMAC
+#define show_regs dump_regs
+#endif
-unsigned int probingmem = 0;
-#define NEWMM 1
+extern void die_if_kernel(char *, struct pt_regs *, long);
+void bad_page_fault(struct pt_regs *, unsigned long);
+void do_page_fault(struct pt_regs *, unsigned long, unsigned long);
+void print_pte(struct _PTE);
-void new_page_fault(unsigned long address, unsigned long ppc_code,
- unsigned long text, struct pt_regs *regs)
+void do_page_fault(struct pt_regs *regs, unsigned long address, unsigned long error_code)
{
- struct vm_area_struct * vma;
- struct mm_struct *mm = current->mm;
-
- int intel_code = 0;
- pgd_t *dir;
- pmd_t *pmd;
- pte_t *pte;
-
- /*
- * bit 0 == 0 means no page found, 1 means protection fault
- * bit 1 == 0 means read, 1 means write
- * bit 2 == 0 means kernel, 1 means user-mode
- */
- if (user_mode(regs)) intel_code |= 0x04;
- if (!text && (ppc_code & 0x02000000)) intel_code |= 0x02; /* Load/store */
- if (!text && (ppc_code & 0x08000000))
- {
- intel_code |= 0x01; /* prot viol */
- goto do_page;
- }
-
- dir = pgd_offset(mm, address & PAGE_MASK);
- if (dir)
- {
- pmd = pmd_offset(dir, address & PAGE_MASK);
- if (pmd && pmd_present(*pmd))
- {
- pte = pte_offset(pmd, address & PAGE_MASK);
- if (pte && pte_present(*pte))
- {
- MMU_hash_page(&current->tss, address & PAGE_MASK, pte);
- return;
- }
- }
- }
-
+ struct task_struct *tsk = current;
+ extern unsigned _end[];
+ struct vm_area_struct * vma;
+ struct mm_struct *mm = current->mm;
+ pgd_t *dir;
+ pmd_t *pmd;
+ pte_t *pte;
+
+ /*printk("do_page_fault() %s/%d addr %x nip %x regs %x error %x\n",
+ current->comm,current->pid,address,regs->nip,regs,error_code);*/
+#ifdef CONFIG_PMAC
+ if (xmon_fault_handler && regs->trap == 0x300) {
+ xmon_fault_handler();
+ return;
+ }
+#endif
+ if (in_interrupt()) {
+ static int complained;
+ if (complained < 20) {
+ ++complained;
+ printk("page fault in interrupt handler, addr=%lx\n",
+ address);
+ show_regs(regs);
+ }
+ }
+ if (current == NULL)
+ goto bad_area;
+
do_page:
- down(&mm->mmap_sem);
- vma = find_vma(current->mm, address);
- if (!vma)
- goto bad_area;
- if (vma->vm_start <= address)
- goto good_area;
- if (!(vma->vm_flags & VM_GROWSDOWN))
- goto bad_area;
- if (expand_stack(vma, address))
- goto bad_area;
+ down(&mm->mmap_sem);
+ vma = find_vma(tsk->mm, address);
+ if (!vma)
+ goto bad_area;
+ if (vma->vm_start <= address)
+ goto good_area;
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+ if (expand_stack(vma, address))
+ goto bad_area;
good_area:
- /* a write */
- if (intel_code & 2) {
- if (!(vma->vm_flags & VM_WRITE))
- {
- goto bad_area;
- }
- /* a read */
- } else {
- /* protection fault */
- if (intel_code & 1)
- {
- printk("prot fault\n");
- goto bad_area;
- }
- if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
- {
- printk("no read or exec\n");
- goto bad_area;
- }
- }
- handle_mm_fault(vma, address, intel_code & 2);
- up(&mm->mmap_sem); flush_page(address); /* Flush & Invalidate cache - note: address is OK now */
- return;
+ if (error_code & 0xb5700000)
+ /* an error such as lwarx to I/O controller space,
+ address matching DABR, eciwx, etc. */
+ goto bad_area;
+
+ /* a write */
+ if (error_code & 0x02000000) {
+ if (!(vma->vm_flags & VM_WRITE))
+ goto bad_area;
+ /* a read */
+ } else {
+ /* protection fault */
+ if ( error_code & 0x08000000 )
+ goto bad_area;
+ if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
+ goto bad_area;
+ }
+ handle_mm_fault(current, vma, address, error_code & 0x02000000);
+ up(&mm->mmap_sem);
+ /*printk("do_page_fault() return %s/%d addr %x msr %x\n",
+ current->comm,current->pid,address,regs->msr);*/
+ /* not needed since flush_page_to_ram() works */
+#if 0
+ flush_page(address);
+#endif
+ return;
bad_area:
- up(&mm->mmap_sem);
+ up(&current->mm->mmap_sem);
+ bad_page_fault(regs, address);
+}
+
+
+void
+bad_page_fault(struct pt_regs *regs, unsigned long address)
+{
+ extern unsigned int probingmem;
+ struct task_struct *tsk = current;
+ unsigned long fixup;
+
+
+ /* Are we prepared to handle this fault? */
+ if ((fixup = search_exception_table(regs->nip)) != 0) {
+ if ( user_mode(regs) )
+ printk("Exception from user mode\n");
+#if 0
+ printk(KERN_DEBUG "Exception at %lx (%lx)\n", regs->nip, fixup);
+#endif
+ regs->nip = fixup;
+ return;
+ }
- /* Did we have an exception handler installed? */
- if(current->tss.excount != 0) {
- if(user_mode(regs)) {
- printk("Exception signalled from user mode!\n");
- } else {
-#if 0
- printk("Exception from kernel mode. pc %x expc %x count %d\n",
- regs->nip,current->tss.expc,current->tss.excount);
-#endif
- current->tss.excount = 0;
- regs->gpr[3] = -EFAULT;
- regs->nip = current->tss.expc;
- return;
- }
- }
+ if ( user_mode(regs) )
+ {
+ force_sig(SIGSEGV, tsk);
+ return;
+ }
- if (user_mode(regs))
- {
- force_sig(SIGSEGV, current);
- return;
- }
- panic("KERNEL access of bad area PC %x address %x vm_flags %x\n",
- regs->nip,address,vma->vm_flags);
+bad_kernel_access:
+ /* make sure it's not a bootup probe test */
+ if ( probingmem )
+ {
+ probingmem = 0;
+ return;
+ }
+ /* kernel has accessed a bad area */
+ show_regs(regs);
+ print_backtrace( regs->gpr[1] );
+#ifdef CONFIG_PMAC
+ xmon(regs);
+#endif
+ panic("kernel access of bad area\n pc %x address %X tsk %s/%d",
+ regs->nip,address,tsk->comm,tsk->pid);
}
-va_to_phys(unsigned long address)
+unsigned long va_to_phys(unsigned long address)
{
pgd_t *dir;
pmd_t *pmd;
pte_t *pte;
+
dir = pgd_offset(current->mm, address & PAGE_MASK);
if (dir)
{
@@ -165,8 +189,28 @@ va_to_phys(unsigned long address)
return (0);
}
-inline void
-update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t _pte)
+void print_pte(struct _PTE p)
{
- MMU_hash_page(&current->tss, address & PAGE_MASK, (pte *)&_pte);
+ printk(
+"%08x %08x vsid: %06x h: %01x api: %02x rpn: %05x rcwimg: %d%d%d%d%d%d pp: %02x\n",
+ *((unsigned long *)(&p)), *((long *)&p+1),
+ p.vsid, p.h, p.api, p.rpn,
+ p.r,p.c,p.w,p.i,p.m,p.g,p.pp);
+}
+
+/*
+ * Search the hw hash table for a mapping to the given physical
+ * address. -- Cort
+ */
+unsigned long htab_phys_to_va(unsigned long address)
+{
+ extern PTE *Hash, *Hash_end;
+ PTE *ptr;
+
+ for ( ptr = Hash ; ptr < Hash_end ; ptr++ )
+ {
+ if ( ptr->rpn == (address>>12) )
+ printk("phys %08X -> va ???\n",
+ address);
+ }
}