summaryrefslogtreecommitdiffstats
path: root/arch/arm/mm/fault-armv.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mm/fault-armv.c')
-rw-r--r--arch/arm/mm/fault-armv.c525
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;
}
-