diff options
Diffstat (limited to 'arch/ppc/mm/fault.c')
-rw-r--r-- | arch/ppc/mm/fault.c | 264 |
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(¤t->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(¤t->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(¤t->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); + } } |