/* $NetBSD: trap.c,v 1.17.4.5 2021/01/01 13:14:29 martin Exp $ */ /*- * Copyright (c) 2014 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Matt Thomas of 3am Software Foundry. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION OR CONTRIBUTORS * 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. */ #include __KERNEL_RCSID(1, "$NetBSD: trap.c,v 1.17.4.5 2021/01/01 13:14:29 martin Exp $"); #include "opt_arm_intr_impl.h" #include "opt_compat_netbsd32.h" #include "opt_dtrace.h" #include #include #include #include #include #ifdef KDB #include #endif #include #include #include #include #include #ifdef ARM_INTR_IMPL #include ARM_INTR_IMPL #else #error ARM_INTR_IMPL not defined #endif #ifndef ARM_IRQ_HANDLER #error ARM_IRQ_HANDLER not defined #endif #include #include #include #include #include #ifdef KDB #include #endif #ifdef DDB #include #include #endif #ifdef KDTRACE_HOOKS #include #endif #ifdef DDB int sigill_debug = 0; #endif #ifdef KDTRACE_HOOKS dtrace_doubletrap_func_t dtrace_doubletrap_func = NULL; dtrace_trap_func_t dtrace_trap_func = NULL; int (*dtrace_invop_jump_addr)(struct trapframe *); #endif enum emul_arm_result { EMUL_ARM_SUCCESS = 0, EMUL_ARM_UNKNOWN, EMUL_ARM_FAULT, }; const char * const trap_names[] = { [ESR_EC_UNKNOWN] = "Unknown Reason (Illegal Instruction)", [ESR_EC_SERROR] = "SError Interrupt", [ESR_EC_WFX] = "WFI or WFE instruction execution", [ESR_EC_ILL_STATE] = "Illegal Execution State", [ESR_EC_SYS_REG] = "MSR/MRS/SYS instruction", [ESR_EC_SVC_A64] = "SVC Instruction Execution", [ESR_EC_HVC_A64] = "HVC Instruction Execution", [ESR_EC_SMC_A64] = "SMC Instruction Execution", [ESR_EC_INSN_ABT_EL0] = "Instruction Abort (EL0)", [ESR_EC_INSN_ABT_EL1] = "Instruction Abort (EL1)", [ESR_EC_DATA_ABT_EL0] = "Data Abort (EL0)", [ESR_EC_DATA_ABT_EL1] = "Data Abort (EL1)", [ESR_EC_PC_ALIGNMENT] = "Misaligned PC", [ESR_EC_SP_ALIGNMENT] = "Misaligned SP", [ESR_EC_FP_ACCESS] = "Access to SIMD/FP Registers", [ESR_EC_FP_TRAP_A64] = "FP Exception", [ESR_EC_BRKPNT_EL0] = "Breakpoint Exception (EL0)", [ESR_EC_BRKPNT_EL1] = "Breakpoint Exception (EL1)", [ESR_EC_SW_STEP_EL0] = "Software Step (EL0)", [ESR_EC_SW_STEP_EL1] = "Software Step (EL1)", [ESR_EC_WTCHPNT_EL0] = "Watchpoint (EL0)", [ESR_EC_WTCHPNT_EL1] = "Watchpoint (EL1)", [ESR_EC_BKPT_INSN_A64] = "BKPT Instruction Execution", [ESR_EC_CP15_RT] = "A32: MCR/MRC access to CP15", [ESR_EC_CP15_RRT] = "A32: MCRR/MRRC access to CP15", [ESR_EC_CP14_RT] = "A32: MCR/MRC access to CP14", [ESR_EC_CP14_DT] = "A32: LDC/STC access to CP14", [ESR_EC_CP14_RRT] = "A32: MRRC access to CP14", [ESR_EC_SVC_A32] = "A32: SVC Instruction Execution", [ESR_EC_HVC_A32] = "A32: HVC Instruction Execution", [ESR_EC_SMC_A32] = "A32: SMC Instruction Execution", [ESR_EC_FPID] = "A32: MCR/MRC access to CP10", [ESR_EC_FP_TRAP_A32] = "A32: FP Exception", [ESR_EC_BKPT_INSN_A32] = "A32: BKPT Instruction Execution", [ESR_EC_VECTOR_CATCH] = "A32: Vector Catch Exception" }; const char * eclass_trapname(uint32_t eclass) { static char trapnamebuf[sizeof("Unknown trap 0x????????")]; if (eclass >= __arraycount(trap_names) || trap_names[eclass] == NULL) { snprintf(trapnamebuf, sizeof(trapnamebuf), "Unknown trap %#02x", eclass); return trapnamebuf; } return trap_names[eclass]; } void userret(struct lwp *l) { mi_userret(l); } void trap_doast(struct trapframe *tf) { struct lwp * const l = curlwp; /* * allow to have a chance of context switch just prior to user * exception return. */ #ifdef __HAVE_PREEMPTION kpreempt_disable(); #endif struct cpu_info * const ci = curcpu(); ci->ci_data.cpu_ntrap++; KDASSERT(ci->ci_cpl == IPL_NONE); const int want_resched = ci->ci_want_resched; #ifdef __HAVE_PREEMPTION kpreempt_enable(); #endif if (l->l_pflag & LP_OWEUPC) { l->l_pflag &= ~LP_OWEUPC; ADDUPROF(l); } /* Allow a forced task switch. */ if (want_resched) preempt(); userret(l); } void trap_el1h_sync(struct trapframe *tf) { const uint32_t esr = tf->tf_esr; const uint32_t eclass = __SHIFTOUT(esr, ESR_EC); /* exception class */ /* re-enable traps and interrupts */ if (!(tf->tf_spsr & SPSR_I)) daif_enable(DAIF_D|DAIF_A|DAIF_I|DAIF_F); else daif_enable(DAIF_D|DAIF_A); #ifdef KDTRACE_HOOKS if (dtrace_trap_func != NULL && (*dtrace_trap_func)(tf, eclass)) return; #endif switch (eclass) { case ESR_EC_INSN_ABT_EL1: case ESR_EC_DATA_ABT_EL1: data_abort_handler(tf, eclass); break; case ESR_EC_BKPT_INSN_A64: #ifdef KDTRACE_HOOKS if (__SHIFTOUT(esr, ESR_ISS) == 0x40d && dtrace_invop_jump_addr != 0) { (*dtrace_invop_jump_addr)(tf); break; } /* FALLTHROUGH */ #endif case ESR_EC_BRKPNT_EL1: case ESR_EC_SW_STEP_EL1: case ESR_EC_WTCHPNT_EL1: #ifdef DDB if (eclass == ESR_EC_BRKPNT_EL1) kdb_trap(DB_TRAP_BREAKPOINT, tf); else if (eclass == ESR_EC_BKPT_INSN_A64) kdb_trap(DB_TRAP_BKPT_INSN, tf); else if (eclass == ESR_EC_WTCHPNT_EL1) kdb_trap(DB_TRAP_WATCHPOINT, tf); else if (eclass == ESR_EC_SW_STEP_EL1) kdb_trap(DB_TRAP_SW_STEP, tf); else kdb_trap(DB_TRAP_UNKNOWN, tf); #else panic("No debugger in kernel"); #endif break; case ESR_EC_FP_ACCESS: case ESR_EC_FP_TRAP_A64: case ESR_EC_PC_ALIGNMENT: case ESR_EC_SP_ALIGNMENT: case ESR_EC_ILL_STATE: default: panic("Trap: fatal %s: pc=%016" PRIx64 " sp=%016" PRIx64 " esr=%08x", eclass_trapname(eclass), tf->tf_pc, tf->tf_sp, esr); break; } } void trap_el0_sync(struct trapframe *tf) { struct lwp * const l = curlwp; const uint32_t esr = tf->tf_esr; const uint32_t eclass = __SHIFTOUT(esr, ESR_EC); /* exception class */ /* disable trace */ reg_mdscr_el1_write(reg_mdscr_el1_read() & ~MDSCR_SS); /* enable traps and interrupts */ daif_enable(DAIF_D|DAIF_A|DAIF_I|DAIF_F); switch (eclass) { case ESR_EC_INSN_ABT_EL0: case ESR_EC_DATA_ABT_EL0: data_abort_handler(tf, eclass); userret(l); break; case ESR_EC_SVC_A64: (*l->l_proc->p_md.md_syscall)(tf); break; case ESR_EC_FP_ACCESS: fpu_load(l); userret(l); break; case ESR_EC_FP_TRAP_A64: do_trapsignal(l, SIGFPE, FPE_FLTUND, NULL, esr); /* XXX */ userret(l); break; case ESR_EC_PC_ALIGNMENT: do_trapsignal(l, SIGBUS, BUS_ADRALN, (void *)tf->tf_pc, esr); userret(l); break; case ESR_EC_SP_ALIGNMENT: do_trapsignal(l, SIGBUS, BUS_ADRALN, (void *)tf->tf_sp, esr); userret(l); break; case ESR_EC_BKPT_INSN_A64: case ESR_EC_BRKPNT_EL0: case ESR_EC_WTCHPNT_EL0: do_trapsignal(l, SIGTRAP, TRAP_BRKPT, (void *)tf->tf_pc, esr); userret(l); break; case ESR_EC_SW_STEP_EL0: /* disable trace, and send trace trap */ tf->tf_spsr &= ~SPSR_SS; do_trapsignal(l, SIGTRAP, TRAP_TRACE, (void *)tf->tf_pc, esr); userret(l); break; default: case ESR_EC_UNKNOWN: #ifdef DDB if (sigill_debug) { /* show illegal instruction */ printf("TRAP: pid %d (%s), uid %d: %s:" " esr=0x%lx: pc=0x%lx: %s\n", curlwp->l_proc->p_pid, curlwp->l_proc->p_comm, l->l_cred ? kauth_cred_geteuid(l->l_cred) : -1, eclass_trapname(eclass), tf->tf_esr, tf->tf_pc, strdisasm(tf->tf_pc)); } #endif /* illegal or not implemented instruction */ do_trapsignal(l, SIGILL, ILL_ILLTRP, (void *)tf->tf_pc, esr); userret(l); break; } } void interrupt(struct trapframe *tf) { struct cpu_info * const ci = curcpu(); #ifdef STACKCHECKS struct lwp *l = curlwp; void *sp = (void *)reg_sp_read(); if (l->l_addr >= sp) { panic("lwp/interrupt stack overflow detected." " lwp=%p, sp=%p, l_addr=%p", l, sp, l->l_addr); } #endif /* disable trace */ reg_mdscr_el1_write(reg_mdscr_el1_read() & ~MDSCR_SS); /* enable traps */ daif_enable(DAIF_D|DAIF_A); ci->ci_intr_depth++; ARM_IRQ_HANDLER(tf); ci->ci_intr_depth--; cpu_dosoftints(); } #ifdef COMPAT_NETBSD32 /* * 32-bit length Thumb instruction. See ARMv7 DDI0406A A6.3. */ #define THUMB_32BIT(hi) (((hi) & 0xe000) == 0xe000 && ((hi) & 0x1800)) static int fetch_arm_insn(uint64_t pc, uint64_t spsr, uint32_t *insn) { /* THUMB? */ if (spsr & SPSR_A32_T) { uint16_t *p = (uint16_t *)(pc & ~1UL); /* XXX */ uint16_t hi, lo; if (ufetch_16(p, &hi)) return -1; if (!THUMB_32BIT(hi)) { /* 16-bit Thumb instruction */ *insn = hi; return 2; } /* 32-bit Thumb instruction */ if (ufetch_16(p + 1, &lo)) return -1; *insn = ((uint32_t)hi << 16) | lo; return 4; } if (ufetch_32((uint32_t *)pc, insn)) return -1; return 4; } static bool arm_cond_match(uint32_t insn, uint64_t spsr) { bool invert = (insn >> 28) & 1; bool match; switch (insn >> 29) { case 0: /* EQ or NE */ match = spsr & SPSR_Z; break; case 1: /* CS/HI or CC/LO */ match = spsr & SPSR_C; break; case 2: /* MI or PL */ match = spsr & SPSR_N; break; case 3: /* VS or VC */ match = spsr & SPSR_V; break; case 4: /* HI or LS */ match = ((spsr & (SPSR_C | SPSR_Z)) == SPSR_C); break; case 5: /* GE or LT */ match = (!(spsr & SPSR_N) == !(spsr & SPSR_V)); break; case 6: /* GT or LE */ match = !(spsr & SPSR_Z) && (!(spsr & SPSR_N) == !(spsr & SPSR_V)); break; case 7: /* AL */ match = true; break; } return (!match != !invert); } static enum emul_arm_result emul_thumb_insn(struct trapframe *tf, uint32_t insn, int insn_size) { /* T32-16bit or 32bit instructions */ switch (insn_size) { case 2: /* Breakpoint used by GDB */ if (insn == 0xdefe) { do_trapsignal(curlwp, SIGTRAP, TRAP_BRKPT, (void *)tf->tf_pc, 0); return EMUL_ARM_SUCCESS; } /* XXX: some T32 IT instruction deprecated should be emulated */ break; case 4: break; default: return EMUL_ARM_FAULT; } return EMUL_ARM_UNKNOWN; } static enum emul_arm_result emul_arm_insn(struct trapframe *tf) { uint32_t insn; int insn_size; insn_size = fetch_arm_insn(tf->tf_pc, tf->tf_spsr, &insn); tf->tf_far = reg_far_el1_read(); if (tf->tf_spsr & SPSR_A32_T) return emul_thumb_insn(tf, insn, insn_size); if (insn_size != 4) return EMUL_ARM_FAULT; /* Breakpoint used by GDB */ if (insn == 0xe6000011 || insn == 0xe7ffdefe) { do_trapsignal(curlwp, SIGTRAP, TRAP_BRKPT, (void *)tf->tf_pc, 0); return EMUL_ARM_SUCCESS; } /* Unconditional instruction extension space? */ if ((insn & 0xf0000000) == 0xf0000000) goto unknown_insn; /* * Emulate ARMv6 instructions with cache operations * register (c7), that can be used in user mode. */ switch (insn & 0x0fff0fff) { case 0x0e070f95: if (arm_cond_match(insn, tf->tf_spsr)) { /* * mcr p15, 0, , c7, c5, 4 * (flush prefetch buffer) */ __asm __volatile("isb sy" ::: "memory"); } goto emulated; case 0x0e070f9a: if (arm_cond_match(insn, tf->tf_spsr)) { /* * mcr p15, 0, , c7, c10, 4 * (data synchronization barrier) */ __asm __volatile("dsb sy" ::: "memory"); } goto emulated; case 0x0e070fba: if (arm_cond_match(insn, tf->tf_spsr)) { /* * mcr p15, 0, , c7, c10, 5 * (data memory barrier) */ __asm __volatile("dmb sy" ::: "memory"); } goto emulated; default: break; } unknown_insn: /* unknown, or unsupported instruction */ return EMUL_ARM_UNKNOWN; emulated: tf->tf_pc += insn_size; return EMUL_ARM_SUCCESS; } #endif /* COMPAT_NETBSD32 */ void trap_el0_32sync(struct trapframe *tf) { struct lwp * const l = curlwp; const uint32_t esr = tf->tf_esr; const uint32_t eclass = __SHIFTOUT(esr, ESR_EC); /* exception class */ /* disable trace */ reg_mdscr_el1_write(reg_mdscr_el1_read() & ~MDSCR_SS); /* enable traps and interrupts */ daif_enable(DAIF_D|DAIF_A|DAIF_I|DAIF_F); switch (eclass) { #ifdef COMPAT_NETBSD32 case ESR_EC_INSN_ABT_EL0: case ESR_EC_DATA_ABT_EL0: data_abort_handler(tf, eclass); userret(l); break; case ESR_EC_SVC_A32: (*l->l_proc->p_md.md_syscall)(tf); break; case ESR_EC_FP_ACCESS: fpu_load(l); userret(l); break; case ESR_EC_FP_TRAP_A32: do_trapsignal(l, SIGFPE, FPE_FLTUND, NULL, esr); /* XXX */ userret(l); break; case ESR_EC_PC_ALIGNMENT: do_trapsignal(l, SIGBUS, BUS_ADRALN, (void *)tf->tf_pc, esr); userret(l); break; case ESR_EC_SP_ALIGNMENT: do_trapsignal(l, SIGBUS, BUS_ADRALN, (void *)tf->tf_reg[13], esr); /* sp is r13 on AArch32 */ userret(l); break; case ESR_EC_BKPT_INSN_A32: do_trapsignal(l, SIGTRAP, TRAP_BRKPT, (void *)tf->tf_pc, esr); userret(l); break; case ESR_EC_UNKNOWN: switch (emul_arm_insn(tf)) { case EMUL_ARM_SUCCESS: break; case EMUL_ARM_UNKNOWN: goto unknown; case EMUL_ARM_FAULT: do_trapsignal(l, SIGSEGV, SEGV_MAPERR, (void *)tf->tf_far, esr); break; } userret(l); break; case ESR_EC_CP15_RT: case ESR_EC_CP15_RRT: case ESR_EC_CP14_RT: case ESR_EC_CP14_DT: case ESR_EC_CP14_RRT: unknown: #endif /* COMPAT_NETBSD32 */ default: #ifdef DDB if (sigill_debug) { /* show illegal instruction */ printf("TRAP: pid %d (%s), uid %d: %s:" " esr=0x%lx: pc=0x%lx: %s\n", curlwp->l_proc->p_pid, curlwp->l_proc->p_comm, l->l_cred ? kauth_cred_geteuid(l->l_cred) : -1, eclass_trapname(eclass), tf->tf_esr, tf->tf_pc, strdisasm_aarch32(tf->tf_pc)); } #endif /* illegal or not implemented instruction */ do_trapsignal(l, SIGILL, ILL_ILLTRP, (void *)tf->tf_pc, esr); userret(l); break; } } #define bad_trap_panic(trapfunc) \ void \ trapfunc(struct trapframe *tf) \ { \ panic("%s", __func__); \ } bad_trap_panic(trap_el1t_sync) bad_trap_panic(trap_el1t_irq) bad_trap_panic(trap_el1t_fiq) bad_trap_panic(trap_el1t_error) bad_trap_panic(trap_el1h_fiq) bad_trap_panic(trap_el1h_error) bad_trap_panic(trap_el0_fiq) bad_trap_panic(trap_el0_error) bad_trap_panic(trap_el0_32fiq) bad_trap_panic(trap_el0_32error) void cpu_jump_onfault(struct trapframe *tf, const struct faultbuf *fb, int val) { tf->tf_reg[19] = fb->fb_reg[FB_X19]; tf->tf_reg[20] = fb->fb_reg[FB_X20]; tf->tf_reg[21] = fb->fb_reg[FB_X21]; tf->tf_reg[22] = fb->fb_reg[FB_X22]; tf->tf_reg[23] = fb->fb_reg[FB_X23]; tf->tf_reg[24] = fb->fb_reg[FB_X24]; tf->tf_reg[25] = fb->fb_reg[FB_X25]; tf->tf_reg[26] = fb->fb_reg[FB_X26]; tf->tf_reg[27] = fb->fb_reg[FB_X27]; tf->tf_reg[28] = fb->fb_reg[FB_X28]; tf->tf_reg[29] = fb->fb_reg[FB_X29]; tf->tf_sp = fb->fb_reg[FB_SP]; tf->tf_pc = fb->fb_reg[FB_LR]; tf->tf_reg[0] = val; } #ifdef TRAP_SIGDEBUG static void frame_dump(const struct trapframe *tf) { const struct reg *r = &tf->tf_regs; printf("trapframe %p\n", tf); for (size_t i = 0; i < __arraycount(r->r_reg); i++) { printf(" r%.2zu %#018" PRIx64 "%c", i, r->r_reg[i], " \n"[i && (i & 1) == 0]); } printf("\n"); printf(" sp %#018" PRIx64 " pc %#018" PRIx64 "\n", r->r_sp, r->r_pc); printf(" spsr %#018" PRIx64 " tpidr %#018" PRIx64 "\n", r->r_spsr, r->r_tpidr); printf(" esr %#018" PRIx64 " far %#018" PRIx64 "\n", tf->tf_esr, tf->tf_far); printf("\n"); hexdump(printf, "Stack dump", tf, 256); } static void sigdebug(const struct trapframe *tf, const ksiginfo_t *ksi) { struct lwp *l = curlwp; struct proc *p = l->l_proc; const uint32_t eclass = __SHIFTOUT(ksi->ksi_trap, ESR_EC); printf("pid %d.%d (%s): signal %d (trap %#x) " "@pc %#" PRIx64 ", addr %p, error=%s\n", p->p_pid, l->l_lid, p->p_comm, ksi->ksi_signo, ksi->ksi_trap, tf->tf_regs.r_pc, ksi->ksi_addr, eclass_trapname(eclass)); frame_dump(tf); } #endif void do_trapsignal1( #ifdef TRAP_SIGDEBUG const char *func, size_t line, struct trapframe *tf, #endif struct lwp *l, int signo, int code, void *addr, int trap) { ksiginfo_t ksi; KSI_INIT_TRAP(&ksi); ksi.ksi_signo = signo; ksi.ksi_code = code; ksi.ksi_addr = addr; ksi.ksi_trap = trap; #ifdef TRAP_SIGDEBUG printf("%s, %zu: ", func, line); sigdebug(tf, &ksi); #endif (*l->l_proc->p_emul->e_trapsignal)(l, &ksi); }