diff options
Diffstat (limited to 'arch/arm/mm/fault-armv.c')
-rw-r--r-- | arch/arm/mm/fault-armv.c | 525 |
1 files changed, 353 insertions, 172 deletions
diff --git a/arch/arm/mm/fault-armv.c b/arch/arm/mm/fault-armv.c index f090c5f2c..d57d4fb20 100644 --- a/arch/arm/mm/fault-armv.c +++ b/arch/arm/mm/fault-armv.c @@ -1,10 +1,11 @@ /* - * linux/arch/arm/mm/fault.c + * linux/arch/arm/mm/fault-armv.c * * Copyright (C) 1995 Linus Torvalds - * Modifications for ARM processor (c) 1995, 1996 Russell King + * Modifications for ARM processor (c) 1995-1999 Russell King */ +#include <linux/config.h> #include <linux/signal.h> #include <linux/sched.h> #include <linux/kernel.h> @@ -14,43 +15,37 @@ #include <linux/ptrace.h> #include <linux/mman.h> #include <linux/mm.h> -#include <linux/smp.h> -#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/init.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/pgtable.h> +#include <asm/unaligned.h> #define FAULT_CODE_READ 0x02 #define FAULT_CODE_USER 0x01 -struct pgtable_cache_struct quicklists; +#define DO_COW(m) (!((m) & FAULT_CODE_READ)) +#define READ_FAULT(m) ((m) & FAULT_CODE_READ) -void __bad_pmd(pmd_t *pmd) -{ - printk("Bad pmd in pte_alloc: %08lx\n", pmd_val(*pmd)); - set_pmd(pmd, mk_user_pmd(BAD_PAGETABLE)); -} - -void __bad_pmd_kernel(pmd_t *pmd) -{ - printk("Bad pmd in pte_alloc: %08lx\n", pmd_val(*pmd)); - set_pmd(pmd, mk_kernel_pmd(BAD_PAGETABLE)); -} +#include "fault-common.c" pgd_t *get_pgd_slow(void) { /* * need to get a 16k page for level 1 */ - pgd_t *pgd = (pgd_t *) __get_free_pages(GFP_KERNEL,2); + pgd_t *pgd = (pgd_t *)__get_free_pages(GFP_KERNEL,2); pgd_t *init; - + if (pgd) { init = pgd_offset(&init_mm, 0); - memzero ((void *)pgd, USER_PTRS_PER_PGD * BYTES_PER_PTR); - memcpy (pgd + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, + memzero(pgd, USER_PTRS_PER_PGD * BYTES_PER_PTR); + memcpy(pgd + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, (PTRS_PER_PGD - USER_PTRS_PER_PGD) * BYTES_PER_PTR); + clean_cache_area(pgd, PTRS_PER_PGD * BYTES_PER_PTR); } return pgd; } @@ -59,17 +54,19 @@ pte_t *get_pte_slow(pmd_t *pmd, unsigned long offset) { pte_t *pte; - pte = (pte_t *) get_small_page(GFP_KERNEL); + pte = (pte_t *)get_page_2k(GFP_KERNEL); if (pmd_none(*pmd)) { if (pte) { - memzero (pte, PTRS_PER_PTE * BYTES_PER_PTR); + memzero(pte, 2 * PTRS_PER_PTE * BYTES_PER_PTR); + clean_cache_area(pte, PTRS_PER_PTE * BYTES_PER_PTR); + pte += PTRS_PER_PTE; set_pmd(pmd, mk_user_pmd(pte)); return pte + offset; } set_pmd(pmd, mk_user_pmd(BAD_PAGETABLE)); return NULL; } - free_small_page ((unsigned long) pte); + free_page_2k((unsigned long)pte); if (pmd_bad(*pmd)) { __bad_pmd(pmd); return NULL; @@ -81,17 +78,19 @@ pte_t *get_pte_kernel_slow(pmd_t *pmd, unsigned long offset) { pte_t *pte; - pte = (pte_t *) get_small_page(GFP_KERNEL); + pte = (pte_t *)get_page_2k(GFP_KERNEL); if (pmd_none(*pmd)) { if (pte) { - memzero (pte, PTRS_PER_PTE * BYTES_PER_PTR); + memzero(pte, 2 * PTRS_PER_PTE * BYTES_PER_PTR); + clean_cache_area(pte, PTRS_PER_PTE * BYTES_PER_PTR); + pte += PTRS_PER_PTE; set_pmd(pmd, mk_kernel_pmd(pte)); return pte + offset; } set_pmd(pmd, mk_kernel_pmd(BAD_PAGETABLE)); return NULL; } - free_small_page ((unsigned long) pte); + free_page_2k((unsigned long)pte); if (pmd_bad(*pmd)) { __bad_pmd_kernel(pmd); return NULL; @@ -99,10 +98,8 @@ pte_t *get_pte_kernel_slow(pmd_t *pmd, unsigned long offset) return (pte_t *) pmd_page(*pmd) + offset; } -extern void die_if_kernel(char *msg, struct pt_regs *regs, unsigned int err, unsigned int ret); - #ifdef DEBUG -static int sp_valid (unsigned long *sp) +static int sp_valid(unsigned long *sp) { unsigned long addr = (unsigned long) sp; @@ -114,187 +111,371 @@ static int sp_valid (unsigned long *sp) } #endif -static void kernel_page_fault (unsigned long addr, int mode, struct pt_regs *regs, - struct task_struct *tsk, struct mm_struct *mm) +#ifdef CONFIG_ALIGNMENT_TRAP +/* + * 32-bit misaligned trap handler (c) 1998 San Mehat (CCC) -July 1998 + * /proc/sys/debug/alignment, modified and integrated into + * Linux 2.1 by Russell King + * + * NOTE!!! This is not portable onto the ARM6/ARM7 processors yet. Also, + * it seems to give a severe performance impact (1 abort/ms - NW runs at + * ARM6 speeds) with GCC 2.7.2.2 - needs checking with a later GCC/EGCS. + * + * IMHO, I don't think that the trap handler is advantageous on ARM6,7 + * processors (they'll run like an ARM3). We'll see. + */ +#define CODING_BITS(i) (i & 0x0e000000) + +#define LDST_I_BIT(i) (i & (1 << 26)) /* Immediate constant */ +#define LDST_P_BIT(i) (i & (1 << 24)) /* Preindex */ +#define LDST_U_BIT(i) (i & (1 << 23)) /* Add offset */ +#define LDST_W_BIT(i) (i & (1 << 21)) /* Writeback */ +#define LDST_L_BIT(i) (i & (1 << 20)) /* Load */ + +#define LDSTH_I_BIT(i) (i & (1 << 22)) /* half-word immed */ +#define LDM_S_BIT(i) (i & (1 << 22)) /* write CPSR from SPSR */ + +#define RN_BITS(i) ((i >> 16) & 15) /* Rn */ +#define RD_BITS(i) ((i >> 12) & 15) /* Rd */ +#define RM_BITS(i) (i & 15) /* Rm */ + +#define REGMASK_BITS(i) (i & 0xffff) +#define OFFSET_BITS(i) (i & 0x0fff) + +#define IS_SHIFT(i) (i & 0x0ff0) +#define SHIFT_BITS(i) ((i >> 7) & 0x1f) +#define SHIFT_TYPE(i) (i & 0x60) +#define SHIFT_LSL 0x00 +#define SHIFT_LSR 0x20 +#define SHIFT_ASR 0x40 +#define SHIFT_RORRRX 0x60 + +static unsigned long ai_user; +static unsigned long ai_sys; +static unsigned long ai_skipped; +static unsigned long ai_half; +static unsigned long ai_word; +static unsigned long ai_multi; + +static int proc_alignment_read(char *page, char **start, off_t off, + int count, int *eof, void *data) { - /* - * Oops. The kernel tried to access some bad page. We'll have to - * terminate things with extreme prejudice. - */ - pgd_t *pgd; - if (addr < PAGE_SIZE) - printk (KERN_ALERT "Unable to handle kernel NULL pointer dereference"); - else - printk (KERN_ALERT "Unable to handle kernel paging request"); - printk (" at virtual address %08lx\n", addr); - printk (KERN_ALERT "current->tss.memmap = %08lX\n", tsk->tss.memmap); - pgd = pgd_offset (mm, addr); - printk (KERN_ALERT "*pgd = %08lx", pgd_val (*pgd)); - if (!pgd_none (*pgd)) { - pmd_t *pmd; - pmd = pmd_offset (pgd, addr); - printk (", *pmd = %08lx", pmd_val (*pmd)); - if (!pmd_none (*pmd)) - printk (", *pte = %08lx", pte_val (*pte_offset (pmd, addr))); - } - printk ("\n"); - die_if_kernel ("Oops", regs, mode, SIGKILL); - do_exit (SIGKILL); + char *p = page; + int len; + + p += sprintf(p, "User:\t\t%li\n", ai_user); + p += sprintf(p, "System:\t\t%li\n", ai_sys); + p += sprintf(p, "Skipped:\t%li\n", ai_skipped); + p += sprintf(p, "Half:\t\t%li\n", ai_half); + p += sprintf(p, "Word:\t\t%li\n", ai_word); + p += sprintf(p, "Multi:\t\t%li\n", ai_multi); + + len = (p - page) - off; + if (len < 0) + len = 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return len; } -static void page_fault (unsigned long addr, int mode, struct pt_regs *regs) +/* + * This needs to be done after sysctl_init, otherwise sys/ + * will be overwritten. + */ +void __init alignment_init(void) { - struct task_struct *tsk; - struct mm_struct *mm; - struct vm_area_struct *vma; - unsigned long fixup; - - lock_kernel(); - tsk = current; - mm = tsk->mm; - - down(&mm->mmap_sem); - vma = find_vma (mm, addr); - if (!vma) - goto bad_area; - if (vma->vm_start <= addr) - goto good_area; - if (!(vma->vm_flags & VM_GROWSDOWN) || expand_stack (vma, addr)) - goto bad_area; + struct proc_dir_entry *e; - /* - * Ok, we have a good vm_area for this memory access, so - * we can handle it.. - */ -good_area: - if (mode & FAULT_CODE_READ) { /* read? */ - if (!(vma->vm_flags & (VM_READ|VM_EXEC))) - goto bad_area; - } else { - if (!(vma->vm_flags & VM_WRITE)) - goto bad_area; + e = create_proc_entry("sys/debug/alignment", S_IFREG | S_IRUGO, NULL); + + if (e) + e->read_proc = proc_alignment_read; +} + +static int +do_alignment_exception(struct pt_regs *regs) +{ + unsigned int instr, rd, rn, correction, nr_regs, regbits; + unsigned long eaddr; + union { unsigned long un; signed long sn; } offset; + + if (user_mode(regs)) { + set_cr(cr_no_alignment); + ai_user += 1; + return 0; } - handle_mm_fault (tsk, vma, addr & PAGE_MASK, !(mode & FAULT_CODE_READ)); - up(&mm->mmap_sem); - 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); - if (mode & FAULT_CODE_USER) { - tsk->tss.error_code = mode; - tsk->tss.trap_no = 14; - printk ("%s: memory violation at pc=0x%08lx, lr=0x%08lx (bad address=0x%08lx, code %d)\n", - tsk->comm, regs->ARM_pc, regs->ARM_lr, addr, mode); -#ifdef DEBUG - { - unsigned int i, j; - unsigned long *sp = (unsigned long *) (regs->ARM_sp - 128); - for (j = 0; j < 20 && sp_valid (sp); j++) { - printk ("%p: ", sp); - for (i = 0; i < 8 && sp_valid (sp); i += 1, sp++) - printk ("%08lx ", *sp); - printk ("\n"); + ai_sys += 1; + + instr = *(unsigned long *)instruction_pointer(regs); + correction = 4; /* sometimes 8 on ARMv3 */ + regs->ARM_pc += correction + 4; + + rd = RD_BITS(instr); + rn = RN_BITS(instr); + eaddr = regs->uregs[rn]; + + switch(CODING_BITS(instr)) { + case 0x00000000: + if ((instr & 0x0ff00ff0) == 0x01000090) { + ai_skipped += 1; + printk(KERN_ERR "Unaligned trap: not handling swp instruction\n"); + return 1; + } + + if (((instr & 0x0e000090) == 0x00000090) && (instr & 0x60) != 0) { + ai_half += 1; + if (LDSTH_I_BIT(instr)) + offset.un = (instr & 0xf00) >> 4 | (instr & 15); + else + offset.un = regs->uregs[RM_BITS(instr)]; + + if (LDST_P_BIT(instr)) { + if (LDST_U_BIT(instr)) + eaddr += offset.un; + else + eaddr -= offset.un; } + + if (LDST_L_BIT(instr)) + regs->uregs[rd] = get_unaligned((unsigned short *)eaddr); + else + put_unaligned(regs->uregs[rd], (unsigned short *)eaddr); + + /* signed half-word? */ + if (instr & 0x40) + regs->uregs[rd] = (long)((short) regs->uregs[rd]); + + if (!LDST_P_BIT(instr)) { + if (LDST_U_BIT(instr)) + eaddr += offset.un; + else + eaddr -= offset.un; + regs->uregs[rn] = eaddr; + } else if (LDST_W_BIT(instr)) + regs->uregs[rn] = eaddr; + break; } - show_regs (regs); - c_backtrace (regs->ARM_fp, regs->ARM_cpsr); -#endif - force_sig(SIGSEGV, tsk); - goto out; - } - /* Are we prepared to handle this kernel fault? */ - if ((fixup = search_exception_table(instruction_pointer(regs))) != 0) { - printk(KERN_DEBUG "%s: Exception at [<%lx>] addr=%lx (fixup: %lx)\n", - tsk->comm, regs->ARM_pc, addr, fixup); - regs->ARM_pc = fixup; - goto out; + default: + ai_skipped += 1; + panic("Alignment trap: not handling instruction %08X at %08lX", + instr, regs->ARM_pc - correction - 4); + break; + + case 0x04000000: + offset.un = OFFSET_BITS(instr); + goto ldr_str; + + case 0x06000000: + offset.un = regs->uregs[RM_BITS(instr)]; + + if (IS_SHIFT(instr)) { + unsigned int shiftval = SHIFT_BITS(instr); + + switch(SHIFT_TYPE(instr)) { + case SHIFT_LSL: + offset.un <<= shiftval; + break; + + case SHIFT_LSR: + offset.un >>= shiftval; + break; + + case SHIFT_ASR: + offset.sn >>= shiftval; + break; + + case SHIFT_RORRRX: + if (shiftval == 0) { + offset.un >>= 1; + if (regs->ARM_cpsr & CC_C_BIT) + offset.un |= 1 << 31; + } else + offset.un = offset.un >> shiftval | + offset.un << (32 - shiftval); + break; + } + } + + ldr_str: + ai_word += 1; + if (LDST_P_BIT(instr)) { + if (LDST_U_BIT(instr)) + eaddr += offset.un; + else + eaddr -= offset.un; + } else { + if (LDST_W_BIT(instr)) { + printk(KERN_ERR "Not handling ldrt/strt correctly\n"); + return 1; + } + } + + if (LDST_L_BIT(instr)) { + regs->uregs[rd] = get_unaligned((unsigned long *)eaddr); + if (rd == 15) + correction = 0; + } else + put_unaligned(regs->uregs[rd], (unsigned long *)eaddr); + + if (!LDST_P_BIT(instr)) { + if (LDST_U_BIT(instr)) + eaddr += offset.un; + else + eaddr -= offset.un; + + regs->uregs[rn] = eaddr; + } else if (LDST_W_BIT(instr)) + regs->uregs[rn] = eaddr; + break; + + case 0x08000000: + if (LDM_S_BIT(instr)) + panic("Alignment trap: not handling LDM with s-bit\n"); + ai_multi += 1; + + for (regbits = REGMASK_BITS(instr), nr_regs = 0; regbits; regbits >>= 1) + nr_regs += 4; + + if (!LDST_U_BIT(instr)) + eaddr -= nr_regs; + + if ((LDST_U_BIT(instr) == 0 && LDST_P_BIT(instr) == 0) || + (LDST_U_BIT(instr) && LDST_P_BIT(instr))) + eaddr += 4; + + for (regbits = REGMASK_BITS(instr), rd = 0; regbits; regbits >>= 1, rd += 1) + if (regbits & 1) { + if (LDST_L_BIT(instr)) { + regs->uregs[rd] = get_unaligned((unsigned long *)eaddr); + if (rd == 15) + correction = 0; + } else + put_unaligned(regs->uregs[rd], (unsigned long *)eaddr); + eaddr += 4; + } + + if (LDST_W_BIT(instr)) { + if (LDST_P_BIT(instr) && !LDST_U_BIT(instr)) + eaddr -= nr_regs; + else if (LDST_P_BIT(instr)) + eaddr -= 4; + else if (!LDST_U_BIT(instr)) + eaddr -= 4 + nr_regs; + regs->uregs[rn] = eaddr; + } + break; } - kernel_page_fault (addr, mode, regs, tsk, mm); -out: - unlock_kernel(); + regs->ARM_pc -= correction; + + return 0; } -/* - * Handle a data abort. Note that we have to handle a range of addresses - * on ARM2/3 for ldm. If both pages are zero-mapped, then we have to force - * a copy-on-write - */ +#endif + asmlinkage void -do_DataAbort (unsigned long addr, int fsr, int error_code, struct pt_regs *regs) +do_DataAbort(unsigned long addr, int fsr, int error_code, struct pt_regs *regs) { if (user_mode(regs)) error_code |= FAULT_CODE_USER; + #define DIE(signr,nam)\ force_sig(signr, current);\ - die_if_kernel(nam, regs, fsr, signr);\ - break; + die(nam, regs, fsr);\ + do_exit(signr);\ + break switch (fsr & 15) { - case 2: - DIE(SIGKILL, "Terminal exception") + /* + * 0 - vector exception + */ case 0: - DIE(SIGSEGV, "Vector exception") + force_sig(SIGSEGV, current); + if (!user_mode(regs)) { + die("vector exception", regs, fsr); + do_exit(SIGSEGV); + } + break; + + /* + * 15 - permission fault on page + * 5 - page-table entry descriptor fault + * 7 - first-level descriptor fault + */ + case 15: case 5: case 7: + do_page_fault(addr, error_code, regs); + break; + + /* + * 13 - permission fault on section + */ + case 13: + force_sig(SIGSEGV, current); + if (!user_mode(regs)) { + die("section permission fault", regs, fsr); + do_exit(SIGSEGV); + } else { +#ifdef CONFIG_DEBUG_USER + printk("%s: permission fault on section, " + "address=0x%08lx, code %d\n", + current->comm, addr, error_code); +#ifdef DEBUG + { + unsigned int i, j; + unsigned long *sp; + + sp = (unsigned long *) (regs->ARM_sp - 128); + for (j = 0; j < 20 && sp_valid(sp); j++) { + printk("%p: ", sp); + for (i = 0; i < 8 && sp_valid(sp); i += 1, sp++) + printk("%08lx ", *sp); + printk("\n"); + } + show_regs(regs); + c_backtrace(regs->ARM_fp, regs->ARM_cpsr); + } +#endif +#endif + } + break; + case 1: case 3: - DIE(SIGBUS, "Alignment exception") +#ifdef CONFIG_ALIGNMENT_TRAP + if (!do_alignment_exception(regs)) + break; +#endif + /* + * this should never happen + */ + DIE(SIGBUS, "Alignment exception"); + break; + + case 2: + DIE(SIGKILL, "Terminal exception"); case 12: case 14: - DIE(SIGBUS, "External abort on translation") + DIE(SIGBUS, "External abort on translation"); case 9: case 11: - DIE(SIGSEGV, "Domain fault") - case 13:/* permission fault on section */ -#ifdef DEBUG - { - unsigned int i, j; - unsigned long *sp; - - printk ("%s: section permission fault (bad address=0x%08lx, code %d)\n", - current->comm, addr, error_code); - sp = (unsigned long *) (regs->ARM_sp - 128); - for (j = 0; j < 20 && sp_valid (sp); j++) { - printk ("%p: ", sp); - for (i = 0; i < 8 && sp_valid (sp); i += 1, sp++) - printk ("%08lx ", *sp); - printk ("\n"); - } - show_regs (regs); - c_backtrace(regs->ARM_fp, regs->ARM_cpsr); - } -#endif - DIE(SIGSEGV, "Permission fault") + DIE(SIGSEGV, "Domain fault"); - case 15:/* permission fault on page */ - case 5: /* page-table entry descriptor fault */ - case 7: /* first-level descriptor fault */ - page_fault (addr, error_code, regs); - break; case 4: case 6: - DIE(SIGBUS, "External abort on linefetch") + DIE(SIGBUS, "External abort on linefetch"); case 8: case 10: - DIE(SIGBUS, "External abort on non-linefetch") + DIE(SIGBUS, "External abort on non-linefetch"); } } asmlinkage int -do_PrefetchAbort (unsigned long addr, struct pt_regs *regs) +do_PrefetchAbort(unsigned long addr, struct pt_regs *regs) { -#if 0 - /* does this still apply ? */ - if (the memc mapping for this page exists - can check now...) { - printk ("Page in, but got abort (undefined instruction?)\n"); - return 0; - } -#endif - page_fault (addr, FAULT_CODE_USER|FAULT_CODE_READ, regs); + do_page_fault(addr, FAULT_CODE_USER|FAULT_CODE_READ, regs); return 1; } - |