diff options
Diffstat (limited to 'arch/sparc/mm/fault.c')
-rw-r--r-- | arch/sparc/mm/fault.c | 226 |
1 files changed, 150 insertions, 76 deletions
diff --git a/arch/sparc/mm/fault.c b/arch/sparc/mm/fault.c index 8c8755ce5..cfac6bcc2 100644 --- a/arch/sparc/mm/fault.c +++ b/arch/sparc/mm/fault.c @@ -1,8 +1,9 @@ -/* $Id: fault.c,v 1.77 1996/10/28 00:56:02 davem Exp $ +/* $Id: fault.c,v 1.91 1997/03/18 17:56:00 jj Exp $ * fault.c: Page fault handlers for the Sparc. * * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) */ #include <asm/head.h> @@ -15,6 +16,8 @@ #include <linux/smp.h> #include <linux/signal.h> #include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> #include <asm/system.h> #include <asm/segment.h> @@ -26,14 +29,13 @@ #include <asm/smp.h> #include <asm/traps.h> #include <asm/kdebug.h> +#include <asm/uaccess.h> #define ELEMENTS(arr) (sizeof (arr)/sizeof (arr[0])) extern struct sparc_phys_banks sp_banks[SPARC_PHYS_BANKS]; extern int prom_node_root; -extern void die_if_kernel(char *,struct pt_regs *); - struct linux_romvec *romvec; /* At boot time we determine these two values necessary for setting @@ -70,8 +72,10 @@ int prom_probe_memory (void) bytes = mlist->num_bytes; tally += bytes; if (i >= SPARC_PHYS_BANKS-1) { - printk ("The machine has more banks that this kernel can support\n" - "Increase the SPARC_PHYS_BANKS setting (currently %d)\n", + 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; @@ -128,49 +132,80 @@ asmlinkage void sparc_lvl15_nmi(struct pt_regs *regs, unsigned long serr, prom_halt(); } +static void unhandled_fault(unsigned long, struct task_struct *, + struct pt_regs *) __attribute__ ((noreturn)); + +static void 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 " + "pointer dereference"); + } else { + printk(KERN_ALERT "Unable to handle kernel paging request " + "at virtual address %08lx\n", address); + } + printk(KERN_ALERT "tsk->mm->context = %08lx\n", + (unsigned long) tsk->mm->context); + printk(KERN_ALERT "tsk->mm->pgd = %08lx\n", + (unsigned long) tsk->mm->pgd); + die_if_kernel("Oops", regs); +} + +asmlinkage int lookup_fault(unsigned long pc, unsigned long ret_pc, + unsigned long address) +{ + unsigned long g2; + int i; + unsigned insn; + struct pt_regs regs; + + i = search_exception_table (ret_pc, &g2); + switch (i) { + /* load & store will be handled by fixup */ + case 3: return 3; + /* store will be handled by fixup, load will bump out */ + /* for _to_ macros */ + case 1: insn = (unsigned)pc; if ((insn >> 21) & 1) return 1; break; + /* load will be handled by fixup, store will bump out */ + /* for _from_ macros */ + case 2: insn = (unsigned)pc; + if (!((insn >> 21) & 1) || ((insn>>19)&0x3f) == 15) return 2; + break; + default: break; + } + memset (®s, 0, sizeof (regs)); + regs.pc = pc; + regs.npc = pc + 4; + __asm__ __volatile__ (" + rd %%psr, %0 + nop + nop + nop" : "=r" (regs.psr)); + unhandled_fault (address, current, ®s); + /* Not reached */ + return 0; +} + asmlinkage void do_sparc_fault(struct pt_regs *regs, int text_fault, int write, unsigned long address) { struct vm_area_struct *vma; struct task_struct *tsk = current; struct mm_struct *mm = tsk->mm; + unsigned int fixup; + unsigned long g2; int from_user = !(regs->psr & PSR_PS); -#if 0 - static unsigned long last_one; -#endif - + lock_kernel(); down(&mm->mmap_sem); if(text_fault) address = regs->pc; -#if 0 - if(current->tss.ex.count) { - printk("f<pid=%d,tf=%d,wr=%d,addr=%08lx,pc=%08lx>\n", - tsk->pid, text_fault, write, address, regs->pc); - printk("EX: count<%d> pc<%08lx> expc<%08lx> address<%08lx>\n", - (int) current->tss.ex.count, current->tss.ex.pc, - current->tss.ex.expc, current->tss.ex.address); -#if 0 - if(last_one == address) { - printk("Twice in a row, AIEEE. Spinning so you can see the dump.\n"); - show_regs(regs); - sti(); - while(1) - barrier(); - } - last_one = address; -#endif - } -#endif - /* Now actually handle the fault. Do kernel faults special, - * because on the sun4c we could have faulted trying to read - * the vma area of the task and without the following code - * we'd fault recursively until all our stack is gone. ;-( + /* The kernel referencing a bad kernel pointer can lock up + * a sun4c machine completely, so we must attempt recovery. */ - if(!from_user && address >= PAGE_OFFSET) { - quick_kernel_fault(address); - return; - } + if(!from_user && address >= PAGE_OFFSET) + goto bad_area; vma = find_vma(mm, address); if(!vma) @@ -196,32 +231,37 @@ good_area: } handle_mm_fault(vma, address, write); up(&mm->mmap_sem); - return; + goto out; /* * Something tried to access memory that isn't in our memory map.. * Fix it, but check if it's kernel or user first.. */ bad_area: up(&mm->mmap_sem); - /* Did we have an exception handler installed? */ - if(current->tss.ex.count == 1) { - if(from_user) { - printk("Yieee, exception signalled from user mode.\n"); - } else { - /* Set pc to %g1, set %g1 to -EFAULT and %g2 to - * the faulting address so we can cleanup. - */ + /* Is this in ex_table? */ + + g2 = regs->u_regs[UREG_G2]; + if (!from_user && (fixup = search_exception_table (regs->pc, &g2))) { + if (fixup > 10) { /* Values below are reserved for other things */ + extern const unsigned __memset_start[]; + extern const unsigned __memset_end[]; + extern const unsigned __csum_partial_copy_start[]; + extern const unsigned __csum_partial_copy_end[]; + printk("Exception: PC<%08lx> faddr<%08lx>\n", regs->pc, address); - printk("EX: count<%d> pc<%08lx> expc<%08lx> address<%08lx>\n", - (int) current->tss.ex.count, current->tss.ex.pc, - current->tss.ex.expc, current->tss.ex.address); - current->tss.ex.count = 0; - regs->pc = current->tss.ex.expc; + printk("EX_TABLE: insn<%08lx> fixup<%08x> g2<%08lx>\n", + regs->pc, fixup, g2); + if ((regs->pc >= (unsigned long)__memset_start && + regs->pc < (unsigned long)__memset_end) || + (regs->pc >= (unsigned long)__csum_partial_copy_start && + regs->pc < (unsigned long)__csum_partial_copy_end)) { + regs->u_regs[UREG_I4] = address; + regs->u_regs[UREG_I5] = regs->pc; + } + regs->u_regs[UREG_G2] = g2; + regs->pc = fixup; regs->npc = regs->pc + 4; - regs->u_regs[UREG_G1] = -EFAULT; - regs->u_regs[UREG_G2] = address - current->tss.ex.address; - regs->u_regs[UREG_G3] = current->tss.ex.pc; - return; + goto out; } } if(from_user) { @@ -232,44 +272,72 @@ bad_area: tsk->tss.sig_address = address; tsk->tss.sig_desc = SUBSIG_NOMAPPING; send_sig(SIGSEGV, tsk, 1); - return; + goto out; } - if((unsigned long) address < PAGE_SIZE) { - printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference"); - } else - printk(KERN_ALERT "Unable to handle kernel paging request"); - printk(KERN_ALERT " at virtual address %08lx\n",address); - printk(KERN_ALERT "tsk->mm->context = %08lx\n", - (unsigned long) tsk->mm->context); - printk(KERN_ALERT "tsk->mm->pgd = %08lx\n", - (unsigned long) tsk->mm->pgd); - die_if_kernel("Oops", regs); + unhandled_fault (address, tsk, regs); +out: + unlock_kernel(); } asmlinkage void do_sun4c_fault(struct pt_regs *regs, int text_fault, int write, unsigned long address) { - extern void sun4c_update_mmu_cache(struct vm_area_struct *,unsigned long,pte_t); + extern void sun4c_update_mmu_cache(struct vm_area_struct *, + unsigned long,pte_t); extern pgd_t *sun4c_pgd_offset(struct mm_struct *,unsigned long); extern pte_t *sun4c_pte_offset(pmd_t *,unsigned long); struct task_struct *tsk = current; struct mm_struct *mm = tsk->mm; - pgd_t *pgd; - pte_t *pte; + pgd_t *pgdp; + pte_t *ptep; - if(text_fault) + if (text_fault) address = regs->pc; - pgd = sun4c_pgd_offset(mm, address); - pte = sun4c_pte_offset((pmd_t *) pgd, address); + pgdp = sun4c_pgd_offset(mm, address); + ptep = sun4c_pte_offset((pmd_t *) pgdp, address); + + if (pgd_val(*pgdp)) { + if (write) { + if ((pte_val(*ptep) & (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_PRESENT)) + == (_SUN4C_PAGE_WRITE|_SUN4C_PAGE_PRESENT)) { + + *ptep = __pte(pte_val(*ptep) | _SUN4C_PAGE_ACCESSED | + _SUN4C_PAGE_MODIFIED | + _SUN4C_PAGE_VALID | + _SUN4C_PAGE_DIRTY); + + if (sun4c_get_segmap(address) != invalid_segment) { + sun4c_put_pte(address, pte_val(*ptep)); + return; + } + } + } else { + if ((pte_val(*ptep) & (_SUN4C_PAGE_READ|_SUN4C_PAGE_PRESENT)) + == (_SUN4C_PAGE_READ|_SUN4C_PAGE_PRESENT)) { + + *ptep = __pte(pte_val(*ptep) | _SUN4C_PAGE_ACCESSED | + _SUN4C_PAGE_VALID); + + if (sun4c_get_segmap(address) != invalid_segment) { + sun4c_put_pte(address, pte_val(*ptep)); + return; + } + } + } + } /* This conditional is 'interesting'. */ - if(pgd_val(*pgd) && !(write && !(pte_val(*pte) & _SUN4C_PAGE_WRITE)) - && (pte_val(*pte) & _SUN4C_PAGE_VALID)) - /* XXX Very bad, can't do this optimization when VMA arg is actually - * XXX used by update_mmu_cache()! + if (pgd_val(*pgdp) && !(write && !(pte_val(*ptep) & _SUN4C_PAGE_WRITE)) + && (pte_val(*ptep) & _SUN4C_PAGE_VALID)) + /* Note: It is safe to not grab the MMAP semaphore here because + * we know that update_mmu_cache() will not sleep for + * any reason (at least not in the current implementation) + * and therefore there is no danger of another thread getting + * on the CPU and doing a shrink_mmap() on this vma. */ - sun4c_update_mmu_cache((struct vm_area_struct *) 0, address, *pte); + sun4c_update_mmu_cache (find_vma(current->mm, address), address, + *ptep); else do_sparc_fault(regs, text_fault, write, address); } @@ -321,25 +389,31 @@ void window_overflow_fault(void) { unsigned long sp; + lock_kernel(); sp = current->tss.rwbuf_stkptrs[0]; if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) force_user_fault(sp + 0x38, 1); force_user_fault(sp, 1); + unlock_kernel(); } void window_underflow_fault(unsigned long sp) { + lock_kernel(); if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) force_user_fault(sp + 0x38, 0); force_user_fault(sp, 0); + unlock_kernel(); } void window_ret_fault(struct pt_regs *regs) { unsigned long sp; + lock_kernel(); sp = regs->u_regs[UREG_FP]; if(((sp + 0x38) & PAGE_MASK) != (sp & PAGE_MASK)) force_user_fault(sp + 0x38, 0); force_user_fault(sp, 0); + unlock_kernel(); } |