/* * Copyright (c) 2017, Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_XTENSA_GEN_HANDLERS #include #else #include <_soc_inthandlers.h> #endif #include #include #include LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); extern char xtensa_arch_except_epc[]; extern char xtensa_arch_kernel_oops_epc[]; bool xtensa_is_outside_stack_bounds(uintptr_t addr, size_t sz, uint32_t ps) { uintptr_t start, end; struct k_thread *thread = _current; bool was_in_isr, invalid; /* Without userspace, there is no privileged stack so the thread stack * is the whole stack (minus reserved area). So there is no need to * check for PS == UINT32_MAX for special treatment. */ ARG_UNUSED(ps); /* Since both level 1 interrupts and exceptions go through * the same interrupt vector, both of them increase the nested * counter in the CPU struct. The architecture vector handler * moves execution to the interrupt stack when nested goes from * zero to one. Afterwards, any nested interrupts/exceptions will * continue running in interrupt stack. Therefore, only when * nested > 1, then it was running in the interrupt stack, and * we should check bounds against the interrupt stack. */ was_in_isr = arch_curr_cpu()->nested > 1; if ((thread == NULL) || was_in_isr) { /* We were servicing an interrupt or in early boot environment * and are supposed to be on the interrupt stack. */ int cpu_id; #ifdef CONFIG_SMP cpu_id = arch_curr_cpu()->id; #else cpu_id = 0; #endif start = (uintptr_t)K_KERNEL_STACK_BUFFER(z_interrupt_stacks[cpu_id]); end = start + CONFIG_ISR_STACK_SIZE; #ifdef CONFIG_USERSPACE } else if (ps == UINT32_MAX) { /* Since the stashed PS is inside struct pointed by frame->ptr_to_bsa, * we need to verify that both frame and frame->ptr_to_bsa are valid * pointer within the thread stack. Also without PS, we have no idea * whether we were in kernel mode (using privileged stack) or user * mode (normal thread stack). So we need to check the whole stack * area. * * And... we cannot account for reserved area since we have no idea * which to use: ARCH_KERNEL_STACK_RESERVED or ARCH_THREAD_STACK_RESERVED * as we don't know whether we were in kernel or user mode. */ start = (uintptr_t)thread->stack_obj; end = Z_STACK_PTR_ALIGN(thread->stack_info.start + thread->stack_info.size); } else if (((ps & PS_RING_MASK) == 0U) && ((thread->base.user_options & K_USER) == K_USER)) { /* Check if this is a user thread, and that it was running in * kernel mode. If so, we must have been doing a syscall, so * check with privileged stack bounds. */ start = thread->stack_info.start - CONFIG_PRIVILEGED_STACK_SIZE; end = thread->stack_info.start; #endif } else { start = thread->stack_info.start; end = Z_STACK_PTR_ALIGN(thread->stack_info.start + thread->stack_info.size); } invalid = (addr <= start) || ((addr + sz) >= end); return invalid; } bool xtensa_is_frame_pointer_valid(_xtensa_irq_stack_frame_raw_t *frame) { _xtensa_irq_bsa_t *bsa; /* Check if the pointer to the frame is within stack bounds. If not, there is no * need to test if the BSA (base save area) pointer is also valid as it is * possibly invalid. */ if (xtensa_is_outside_stack_bounds((uintptr_t)frame, sizeof(*frame), UINT32_MAX)) { return false; } /* Need to test if the BSA area is also within stack bounds. The information * contained within the BSA is only valid if within stack bounds. */ bsa = frame->ptr_to_bsa; if (xtensa_is_outside_stack_bounds((uintptr_t)bsa, sizeof(*bsa), UINT32_MAX)) { return false; } #ifdef CONFIG_USERSPACE /* With usespace, we have privileged stack and normal thread stack within * one stack object. So we need to further test whether the frame pointer * resides in the correct stack based on kernel/user mode. */ if (xtensa_is_outside_stack_bounds((uintptr_t)frame, sizeof(*frame), bsa->ps)) { return false; } #endif return true; } void xtensa_dump_stack(const void *stack) { _xtensa_irq_stack_frame_raw_t *frame = (void *)stack; _xtensa_irq_bsa_t *bsa; uintptr_t num_high_regs; int reg_blks_remaining; /* Don't dump stack if the stack pointer is invalid as any frame elements * obtained via de-referencing the frame pointer are probably also invalid. * Or worse, cause another access violation. */ if (!xtensa_is_frame_pointer_valid(frame)) { return; } bsa = frame->ptr_to_bsa; /* Calculate number of high registers. */ num_high_regs = (uint8_t *)bsa - (uint8_t *)frame + sizeof(void *); num_high_regs /= sizeof(uintptr_t); /* And high registers are always comes in 4 in a block. */ reg_blks_remaining = (int)num_high_regs / 4; LOG_ERR(" ** A0 %p SP %p A2 %p A3 %p", (void *)bsa->a0, (void *)((char *)bsa + sizeof(*bsa)), (void *)bsa->a2, (void *)bsa->a3); if (reg_blks_remaining > 0) { reg_blks_remaining--; LOG_ERR(" ** A4 %p A5 %p A6 %p A7 %p", (void *)frame->blks[reg_blks_remaining].r0, (void *)frame->blks[reg_blks_remaining].r1, (void *)frame->blks[reg_blks_remaining].r2, (void *)frame->blks[reg_blks_remaining].r3); } if (reg_blks_remaining > 0) { reg_blks_remaining--; LOG_ERR(" ** A8 %p A9 %p A10 %p A11 %p", (void *)frame->blks[reg_blks_remaining].r0, (void *)frame->blks[reg_blks_remaining].r1, (void *)frame->blks[reg_blks_remaining].r2, (void *)frame->blks[reg_blks_remaining].r3); } if (reg_blks_remaining > 0) { reg_blks_remaining--; LOG_ERR(" ** A12 %p A13 %p A14 %p A15 %p", (void *)frame->blks[reg_blks_remaining].r0, (void *)frame->blks[reg_blks_remaining].r1, (void *)frame->blks[reg_blks_remaining].r2, (void *)frame->blks[reg_blks_remaining].r3); } #if XCHAL_HAVE_LOOPS LOG_ERR(" ** LBEG %p LEND %p LCOUNT %p", (void *)bsa->lbeg, (void *)bsa->lend, (void *)bsa->lcount); #endif LOG_ERR(" ** SAR %p", (void *)bsa->sar); #if XCHAL_HAVE_THREADPTR LOG_ERR(" ** THREADPTR %p", (void *)bsa->threadptr); #endif } static inline unsigned int get_bits(int offset, int num_bits, unsigned int val) { int mask; mask = BIT(num_bits) - 1; val = val >> offset; return val & mask; } static void print_fatal_exception(void *print_stack, int cause, bool is_dblexc, uint32_t depc) { void *pc; uint32_t ps, vaddr; _xtensa_irq_bsa_t *bsa = (void *)*(int **)print_stack; __asm__ volatile("rsr.excvaddr %0" : "=r"(vaddr)); if (is_dblexc) { LOG_ERR(" ** FATAL EXCEPTION (DOUBLE)"); } else { LOG_ERR(" ** FATAL EXCEPTION"); } LOG_ERR(" ** CPU %d EXCCAUSE %d (%s)", arch_curr_cpu()->id, cause, xtensa_exccause(cause)); /* Don't print information if the BSA area is invalid as any elements * obtained via de-referencing the pointer are probably also invalid. * Or worse, cause another access violation. */ if (xtensa_is_outside_stack_bounds((uintptr_t)bsa, sizeof(*bsa), UINT32_MAX)) { LOG_ERR(" ** VADDR %p Invalid SP %p", (void *)vaddr, print_stack); return; } ps = bsa->ps; pc = (void *)bsa->pc; LOG_ERR(" ** PC %p VADDR %p", pc, (void *)vaddr); if (is_dblexc) { LOG_ERR(" ** DEPC %p", (void *)depc); } LOG_ERR(" ** PS %p", (void *)bsa->ps); LOG_ERR(" ** (INTLEVEL:%d EXCM: %d UM:%d RING:%d WOE:%d OWB:%d CALLINC:%d)", get_bits(0, 4, ps), get_bits(4, 1, ps), get_bits(5, 1, ps), get_bits(6, 2, ps), get_bits(18, 1, ps), get_bits(8, 4, ps), get_bits(16, 2, ps)); } static ALWAYS_INLINE void usage_stop(void) { #ifdef CONFIG_SCHED_THREAD_USAGE z_sched_usage_stop(); #endif } static inline void *return_to(void *interrupted) { #ifdef CONFIG_MULTITHREADING return _current_cpu->nested <= 1 ? z_get_next_switch_handle(interrupted) : interrupted; #else return interrupted; #endif /* CONFIG_MULTITHREADING */ } /* The wrapper code lives here instead of in the python script that * generates _xtensa_handle_one_int*(). Seems cleaner, still kind of * ugly. * * This may be unused depending on number of interrupt levels * supported by the SoC. */ #define DEF_INT_C_HANDLER(l) \ __unused void *xtensa_int##l##_c(void *interrupted_stack) \ { \ uint32_t irqs, intenable, m; \ usage_stop(); \ __asm__ volatile("rsr.interrupt %0" : "=r"(irqs)); \ __asm__ volatile("rsr.intenable %0" : "=r"(intenable)); \ irqs &= intenable; \ while ((m = _xtensa_handle_one_int##l(irqs))) { \ irqs ^= m; \ __asm__ volatile("wsr.intclear %0" : : "r"(m)); \ } \ return return_to(interrupted_stack); \ } #if XCHAL_HAVE_NMI #define MAX_INTR_LEVEL XCHAL_NMILEVEL #elif XCHAL_HAVE_INTERRUPTS #define MAX_INTR_LEVEL XCHAL_NUM_INTLEVELS #else #error Xtensa core with no interrupt support is used #define MAX_INTR_LEVEL 0 #endif #if MAX_INTR_LEVEL >= 2 DEF_INT_C_HANDLER(2) #endif #if MAX_INTR_LEVEL >= 3 DEF_INT_C_HANDLER(3) #endif #if MAX_INTR_LEVEL >= 4 DEF_INT_C_HANDLER(4) #endif #if MAX_INTR_LEVEL >= 5 DEF_INT_C_HANDLER(5) #endif #if MAX_INTR_LEVEL >= 6 DEF_INT_C_HANDLER(6) #endif #if MAX_INTR_LEVEL >= 7 DEF_INT_C_HANDLER(7) #endif static inline DEF_INT_C_HANDLER(1) /* C handler for level 1 exceptions/interrupts. Hooked from the * DEF_EXCINT 1 vector declaration in assembly code. This one looks * different because exceptions and interrupts land at the same * vector; other interrupt levels have their own vectors. */ void *xtensa_excint1_c(void *esf) { int cause, reason; int *interrupted_stack = &((struct arch_esf *)esf)->dummy; _xtensa_irq_bsa_t *bsa = (void *)*(int **)interrupted_stack; bool is_fatal_error = false; bool is_dblexc = false; uint32_t ps; void *pc, *print_stack = (void *)interrupted_stack; uint32_t depc = 0; #ifdef CONFIG_XTENSA_MMU depc = XTENSA_RSR(ZSR_DEPC_SAVE_STR); cause = XTENSA_RSR(ZSR_EXCCAUSE_SAVE_STR); is_dblexc = (depc != 0U); #else /* CONFIG_XTENSA_MMU */ __asm__ volatile("rsr.exccause %0" : "=r"(cause)); #endif /* CONFIG_XTENSA_MMU */ switch (cause) { case EXCCAUSE_LEVEL1_INTERRUPT: #ifdef CONFIG_XTENSA_MMU if (!is_dblexc) { return xtensa_int1_c(interrupted_stack); } #else return xtensa_int1_c(interrupted_stack); #endif /* CONFIG_XTENSA_MMU */ break; #ifndef CONFIG_USERSPACE /* Syscalls are handled earlier in assembly if MMU is enabled. * So we don't need this here. */ case EXCCAUSE_SYSCALL: /* Just report it to the console for now */ LOG_ERR(" ** SYSCALL PS %p PC %p", (void *)bsa->ps, (void *)bsa->pc); xtensa_dump_stack(interrupted_stack); /* Xtensa exceptions don't automatically advance PC, * have to skip the SYSCALL instruction manually or * else it will just loop forever */ bsa->pc += 3; break; #endif /* !CONFIG_USERSPACE */ default: reason = K_ERR_CPU_EXCEPTION; /* If the BSA area is invalid, we cannot trust anything coming out of it. */ if (xtensa_is_outside_stack_bounds((uintptr_t)bsa, sizeof(*bsa), UINT32_MAX)) { goto skip_checks; } ps = bsa->ps; pc = (void *)bsa->pc; /* Default for exception */ is_fatal_error = true; /* We need to distinguish between an ill in xtensa_arch_except, * e.g for k_panic, and any other ill. For exceptions caused by * xtensa_arch_except calls, we also need to pass the reason_p * to xtensa_fatal_error. Since the ARCH_EXCEPT frame is in the * BSA, the first arg reason_p is stored at the A2 offset. * We assign EXCCAUSE the unused, reserved code 63; this may be * problematic if the app or new boards also decide to repurpose * this code. * * Another intentionally ill is from xtensa_arch_kernel_oops. * Kernel OOPS has to be explicity raised so we can simply * set the reason and continue. */ if (cause == EXCCAUSE_ILLEGAL) { if (pc == (void *)&xtensa_arch_except_epc) { cause = 63; __asm__ volatile("wsr.exccause %0" : : "r"(cause)); reason = bsa->a2; } else if (pc == (void *)&xtensa_arch_kernel_oops_epc) { cause = 64; /* kernel oops */ reason = K_ERR_KERNEL_OOPS; /* A3 contains the second argument to * xtensa_arch_kernel_oops(reason, ssf) * where ssf is the stack frame causing * the kernel oops. */ print_stack = (void *)bsa->a3; } } skip_checks: if (reason != K_ERR_KERNEL_OOPS) { print_fatal_exception(print_stack, cause, is_dblexc, depc); } /* FIXME: legacy xtensa port reported "HW" exception * for all unhandled exceptions, which seems incorrect * as these are software errors. Should clean this * up. */ xtensa_fatal_error(reason, (void *)print_stack); break; } #ifdef CONFIG_XTENSA_MMU switch (cause) { case EXCCAUSE_LEVEL1_INTERRUPT: #ifndef CONFIG_USERSPACE case EXCCAUSE_SYSCALL: #endif /* !CONFIG_USERSPACE */ is_fatal_error = false; break; default: is_fatal_error = true; break; } #endif /* CONFIG_XTENSA_MMU */ if (is_dblexc || is_fatal_error) { uint32_t ignore; /* We are going to manipulate _current_cpu->nested manually. * Since the error is fatal, for recoverable errors, code * execution must not return back to the current thread as * it is being terminated (via above xtensa_fatal_error()). * So we need to prevent more interrupts coming in which * will affect the nested value as we are going outside of * normal interrupt handling procedure. * * Setting nested to 1 has two effects: * 1. Force return_to() to choose a new thread. * Since the current thread is being terminated, it will * not be chosen again. * 2. When context switches to the newly chosen thread, * nested must be zero for normal code execution, * as that is not in interrupt context at all. * After returning from this function, the rest of * interrupt handling code will decrement nested, * resulting it being zero before switching to another * thread. */ __asm__ volatile("rsil %0, %1" : "=r" (ignore) : "i"(XCHAL_EXCM_LEVEL)); _current_cpu->nested = 1; } #if defined(CONFIG_XTENSA_MMU) if (is_dblexc) { XTENSA_WSR(ZSR_DEPC_SAVE_STR, 0); } #endif /* CONFIG_XTENSA_MMU */ return return_to(interrupted_stack); } #if defined(CONFIG_GDBSTUB) void *xtensa_debugint_c(int *interrupted_stack) { extern void z_gdb_isr(struct arch_esf *esf); z_gdb_isr((void *)interrupted_stack); return return_to(interrupted_stack); } #endif