/* $NetBSD: except.c,v 1.31 2014/03/08 15:46:20 skrll Exp $ */ /*- * Copyright (c) 1998, 1999, 2000 Ben Harris * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * except.c -- ARM exception handling. */ #include __KERNEL_RCSID(0, "$NetBSD: except.c,v 1.31 2014/03/08 15:46:20 skrll Exp $"); #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include #endif #ifdef DDB #include #include #endif void syscall(struct trapframe *); static void do_fault(struct trapframe *, struct lwp *, struct vm_map *, vaddr_t, vm_prot_t); static void data_abort_fixup(struct trapframe *); static vaddr_t data_abort_address(struct trapframe *, vsize_t *); static vm_prot_t data_abort_atype(struct trapframe *); static bool data_abort_usrmode(struct trapframe *); #ifdef DEBUG static void printregs(struct trapframe *tf); #endif #ifdef DIAGNOSTIC void checkvectors(void); #endif int want_resched; #ifdef DIAGNOSTIC void checkvectors(void) { uint32_t *ptr; /* Check that the vectors are valid */ for (ptr = (uint32_t *)0; ptr < (uint32_t *)0x1c; ptr++) if (*ptr != 0xe59ff114) panic("CPU vectors mangled"); } #endif void prefetch_abort_handler(struct trapframe *tf) { struct lwp * const l = curlwp; struct proc * const p = l->l_proc; /* Enable interrupts if they were enabled before the trap. */ if ((tf->tf_r15 & R15_IRQ_DISABLE) == 0) int_on(); /* * XXX Not done yet: * Check if the page being requested is already present. If * so, call the undefined instruction handler instead (ARM3 ds * p15). */ curcpu()->ci_data.cpu_ntrap++; if (TRAP_USERMODE(tf)) { lwp_settrapframe(l, tf); LWP_CACHE_CREDS(l, p); } else { #ifdef DDB db_printf("Prefetch abort in kernel mode\n"); kdb_trap(T_FAULT, tf); #else #ifdef DEBUG printf("Prefetch abort:\n"); printregs(tf); #endif panic("prefetch abort in kernel mode"); #endif } /* User-mode prefetch abort */ vaddr_t pc = tf->tf_r15 & R15_PC; do_fault(tf, l, &p->p_vmspace->vm_map, pc, VM_PROT_EXECUTE); userret(l); } void data_abort_handler(struct trapframe *tf) { struct lwp * const l = curlwp; struct proc * const p = l->l_proc; vm_prot_t atype; bool usrmode, twopages; struct vm_map *map; vaddr_t va; vsize_t asize; /* * Data aborts in kernel mode are possible (copyout etc), so * we hope the compiler (or programmer) has ensured that * R14_svc gets saved. * * We may need to fix up an STM or LDM instruction. This * involves seeing if the base was being written back, and if * so resetting it (by counting the number of registers being * transferred) before retrying (ARM 2 ds pp 10 & 33). */ /* Enable interrupts if they were enabled before the trap. */ if ((tf->tf_r15 & R15_IRQ_DISABLE) == 0) int_on(); curcpu()->ci_data.cpu_ntrap++; if ((tf->tf_r15 & R15_MODE) == R15_MODE_USR) { lwp_settrapframe(l, tf); LWP_CACHE_CREDS(l, p); } data_abort_fixup(tf); va = data_abort_address(tf, &asize); atype = data_abort_atype(tf); usrmode = data_abort_usrmode(tf); twopages = (trunc_page(va) != round_page(va + asize) - PAGE_SIZE); if (!usrmode && va >= VM_MIN_KERNEL_ADDRESS) map = kernel_map; else map = &p->p_vmspace->vm_map; do_fault(tf, l, map, va, atype); if (twopages) do_fault(tf, l, map, va + asize - 4, atype); if (TRAP_USERMODE(tf)) userret(l); } /* * General page fault handler. */ void do_fault(struct trapframe *tf, struct lwp *l, struct vm_map *map, vaddr_t va, vm_prot_t atype) { int error; if (pmap_fault(map->pmap, va, atype)) return; struct pcb * const pcb = lwp_getpcb(l); void * const onfault = pcb->pcb_onfault; const bool user = TRAP_USERMODE(tf); if (cpu_intr_p()) { KASSERT(!user); error = EFAULT; } else { pcb->pcb_onfault = NULL; error = uvm_fault(map, va, atype); pcb->pcb_onfault = onfault; } if (error != 0) { ksiginfo_t ksi; if (onfault != NULL) { tf->tf_r0 = error; tf->tf_r15 = (tf->tf_r15 & ~R15_PC) | (register_t)onfault; return; } #ifdef DDB if (db_validating) { db_faulted = true; tf->tf_r15 += INSN_SIZE; return; } #endif if (!user) { #ifdef DDB db_printf("Unhandled data abort in kernel mode\n"); kdb_trap(T_FAULT, tf); #else #ifdef DEBUG printf("Unhandled data abort:\n"); printregs(tf); #endif panic("unhandled data abort in kernel mode"); #endif } KSI_INIT_TRAP(&ksi); if (error == ENOMEM) { printf("UVM: pid %d (%s), uid %d killed: " "out of swap\n", l->l_proc->p_pid, l->l_proc->p_comm, l->l_cred ? kauth_cred_geteuid(l->l_cred) : -1); ksi.ksi_signo = SIGKILL; } else ksi.ksi_signo = SIGSEGV; ksi.ksi_code = (error == EPERM) ? SEGV_ACCERR : SEGV_MAPERR; ksi.ksi_addr = (void *) va; trapsignal(l, &ksi); } else if (!user) { ucas_ras_check(tf); } } /* * In order for the following macro to work, any function using it * must ensure that tf->r15 is copied into getreg(15). This is safe * with the current trapframe layout on arm26, but be careful. */ #define getreg(r) (((register_t *)&tf->tf_r0)[r]) /* * Undo any effects of the aborted instruction that need to be undone * in order for us to restart it. This is just a case of spotting * aborted LDMs and STMs and reversing any base writeback. This code * is derived loosely from the arm32 late-abort fixup. */ static void data_abort_fixup(struct trapframe *tf) { register_t insn; int rn, count, loop; getreg(15) = tf->tf_r15; /* Get the faulting instruction */ insn = *(register_t *)(tf->tf_r15 & R15_PC); if ((insn & 0x0e000000) == 0x08000000 && (insn & 1 << 21)) { /* LDM/STM with writeback*/ rn = (insn >> 16) & 0x0f; if (rn == 15) return; /* No writeback on R15 */ /* Count registers transferred */ count = 0; for (loop = 0; loop < 16; ++loop) { if (insn & (1<tf_r15; /* Get the faulting instruction */ insn = *(register_t *)(tf->tf_r15 & R15_PC); if ((insn & 0x0c000000) == 0x04000000) { /* Single data transfer */ *vsp = 1; /* or 4, but it doesn't really matter */ rn = (insn & 0x000f0000) >> 16; base = getreg(rn); if (rn == 15) base = (base & R15_PC) + 8; p = insn & 1 << 24; if (p == 0) /* Post-indexed, so offset doesn't concern us */ return base; u = insn & 1 << 23; i = insn & 1 << 25; if (i == 0) { /* Immediate offset (str r0, [r1, #42]) */ offset = insn & 0x00000fff; if (u == 0) return base - offset; else return base + offset; } rm = insn & 0x0000000f; offset = getreg(rm); if (rm == 15) offset += 8; if ((insn & 1 << 4) == 0) /* immediate shift */ shift = (insn & 0x00000f80) >> 7; else goto croak; /* Undefined instruction */ switch ((insn & 0x00000060) >> 5) { case 0: /* Logical left */ offset = (int)(((u_int)offset) << shift); break; case 1: /* Logical Right */ if (shift == 0) shift = 32; offset = (int)(((u_int)offset) >> shift); break; case 2: /* Arithmetic Right */ if (shift == 0) shift = 32; offset = (int)(((int)offset) >> shift); break; case 3: if (shift == 0) /* Rotate Right Extended */ offset = (int)((tf->tf_r15 & R15_FLAG_C) << 2 | ((u_int)offset) >> 1); else /* Rotate Right */ offset = (int)((u_int)offset >> shift | (u_int)offset << (32 - shift)); } if (u == 0) return base - offset; else return base + offset; } else if ((insn & 0x0e000000) == 0x08000000) { int loop, count; /* LDM/STM */ rn = (insn >> 16) & 0x0f; p = insn & 1 << 24; u = insn & 1 << 23; /* Count registers transferred */ count = 0; for (loop = 0; loop < 16; ++loop) if (insn & (1< "); disassemble(tf->tf_r15 & R15_PC); #endif panic("data_abort_address"); } /* * We need to know whether the page should be mapped as R or R/W. We * need to disassemble the instruction responsible and determine if it * was a read or write instruction. This code is based on the arm32 * version. */ static vm_prot_t data_abort_atype(struct trapframe *tf) { register_t insn; insn = *(register_t *)(tf->tf_r15 & R15_PC); /* STR instruction ? */ if ((insn & 0x0c100000) == 0x04000000) return VM_PROT_WRITE; /* STM or CDT instruction ? */ else if ((insn & 0x0a100000) == 0x08000000) return VM_PROT_WRITE; #if defined(CPU_ARM250) || defined(CPU_ARM3) /* SWP instruction ? */ else if ((insn & 0x0fb00ff0) == 0x01000090) return VM_PROT_READ | VM_PROT_WRITE; #endif return VM_PROT_READ; } /* * Work out what effective mode was in use when a data abort occurred. */ static bool data_abort_usrmode(struct trapframe *tf) { register_t insn; if (TRAP_USERMODE(tf)) return true; insn = *(register_t *)(tf->tf_r15 & R15_PC); if ((insn & 0x0d200000) == 0x04200000) /* LDR[B]T and STR[B]T */ return true; return false; } void address_exception_handler(struct trapframe *tf) { struct lwp * const l = curlwp; struct pcb * const pcb = lwp_getpcb(l); ksiginfo_t ksi; /* Enable interrupts if they were enabled before the trap. */ if ((tf->tf_r15 & R15_IRQ_DISABLE) == 0) int_on(); curcpu()->ci_data.cpu_ntrap++; if (TRAP_USERMODE(tf)) { lwp_settrapframe(l, tf); LWP_CACHE_CREDS(l, l->l_proc); } if (pcb->pcb_onfault != NULL) { tf->tf_r0 = EFAULT; tf->tf_r15 = (tf->tf_r15 & ~R15_PC) | (uintptr_t)pcb->pcb_onfault; return; } vaddr_t pc = tf->tf_r15 & R15_PC; if (!TRAP_USERMODE(tf)) { #ifdef DDB db_printf("Address exception in kernel mode\n"); kdb_trap(T_FAULT, tf); #else #ifdef DEBUG printf("Address exception:\n"); printregs(tf); printf("pc -> "); disassemble(pc); #endif panic("address exception in kernel mode"); #endif } KSI_INIT_TRAP(&ksi); ksi.ksi_signo = SIGSEGV; ksi.ksi_code = SEGV_MAPERR; ksi.ksi_addr = (void *) pc; trapsignal(l, &ksi); userret(l); } #ifdef DEBUG static void printregs(struct trapframe *tf) { printf("R0 = 0x%08x R1 = 0x%08x R2 = 0x%08x R3 = 0x%08x\n" "R4 = 0x%08x R5 = 0x%08x R6 = 0x%08x R7 = 0x%08x\n" "R8 = 0x%08x R9 = 0x%08x R10 = 0x%08x R11 = 0x%08x\n" "R12 = 0x%08x R13 = 0x%08x R14 = 0x%08x R15 = 0x%08x\n", tf->tf_r0, tf->tf_r1, tf->tf_r2, tf->tf_r3, tf->tf_r4, tf->tf_r5, tf->tf_r6, tf->tf_r7, tf->tf_r8, tf->tf_r9, tf->tf_r10, tf->tf_r11, tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15); } #endif /* irq_handler is over in irq.c */