/* * Copyright (c) 2017, Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include <_soc_inthandlers.h> #include #include #include #include LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); extern char xtensa_arch_except_epc[]; void *xtensa_init_stack(struct k_thread *thread, int *stack_top, void (*entry)(void *, void *, void *), void *arg1, void *arg2, void *arg3) { void *ret; _xtensa_irq_stack_frame_a11_t *frame; /* Not-a-cpu ID Ensures that the first time this is run, the * stack will be invalidated. That covers the edge case of * restarting a thread on a stack that had previously been run * on one CPU, but then initialized on this one, and * potentially run THERE and not HERE. */ thread->arch.last_cpu = -1; /* We cheat and shave 16 bytes off, the top four words are the * A0-A3 spill area for the caller of the entry function, * which doesn't exist. It will never be touched, so we * arrange to enter the function with a CALLINC of 1 and a * stack pointer 16 bytes above the top, so its ENTRY at the * start will decrement the stack pointer by 16. */ const int bsasz = sizeof(*frame) - 16; frame = (void *)(((char *) stack_top) - bsasz); (void)memset(frame, 0, bsasz); frame->bsa.pc = (uintptr_t)z_thread_entry; frame->bsa.ps = PS_WOE | PS_UM | PS_CALLINC(1); #if XCHAL_HAVE_THREADPTR && defined(CONFIG_THREAD_LOCAL_STORAGE) frame->bsa.threadptr = thread->tls; #endif /* Arguments to z_thread_entry(). Remember these start at A6, * which will be rotated into A2 by the ENTRY instruction that * begins the C function. And A4-A7 and A8-A11 are optional * quads that live below the BSA! */ frame->a7 = (uintptr_t)arg1; /* a7 */ frame->a6 = (uintptr_t)entry; /* a6 */ frame->a5 = 0; /* a5 */ frame->a4 = 0; /* a4 */ frame->a11 = 0; /* a11 */ frame->a10 = 0; /* a10 */ frame->a9 = (uintptr_t)arg3; /* a9 */ frame->a8 = (uintptr_t)arg2; /* a8 */ /* Finally push the BSA pointer and return the stack pointer * as the handle */ frame->ptr_to_bsa = (void *)&frame->bsa; ret = &frame->ptr_to_bsa; return ret; } void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack, char *stack_ptr, k_thread_entry_t entry, void *p1, void *p2, void *p3) { thread->switch_handle = xtensa_init_stack(thread, (int *)stack_ptr, entry, p1, p2, p3); #ifdef CONFIG_KERNEL_COHERENCE __ASSERT((((size_t)stack) % XCHAL_DCACHE_LINESIZE) == 0, ""); __ASSERT((((size_t)stack_ptr) % XCHAL_DCACHE_LINESIZE) == 0, ""); sys_cache_data_flush_and_invd_range(stack, (char *)stack_ptr - (char *)stack); #endif } void z_irq_spurious(const void *arg) { int irqs, ie; ARG_UNUSED(arg); __asm__ volatile("rsr.interrupt %0" : "=r"(irqs)); __asm__ volatile("rsr.intenable %0" : "=r"(ie)); LOG_ERR(" ** Spurious INTERRUPT(s) %p, INTENABLE = %p", (void *)irqs, (void *)ie); z_xtensa_fatal_error(K_ERR_SPURIOUS_IRQ, NULL); } void z_xtensa_dump_stack(const z_arch_esf_t *stack) { _xtensa_irq_stack_frame_raw_t *frame = (void *)stack; _xtensa_irq_bsa_t *bsa = frame->ptr_to_bsa; uintptr_t num_high_regs; int reg_blks_remaining; /* 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, ((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); #ifdef CONFIG_XTENSA_MMU uint32_t vaddrstatus, vaddr0, vaddr1; __asm__ volatile("rsr.vaddrstatus %0" : "=r"(vaddrstatus)); __asm__ volatile("rsr.vaddr0 %0" : "=r"(vaddr0)); __asm__ volatile("rsr.vaddr1 %0" : "=r"(vaddr1)); LOG_ERR(" ** VADDRSTATUS %p VADDR0 %p VADDR1 %p", (void *)vaddrstatus, (void *)vaddr0, (void *)vaddr1); #endif /* CONFIG_XTENSA_MMU */ } 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 ALWAYS_INLINE void usage_stop(void) { #ifdef CONFIG_SCHED_THREAD_USAGE z_sched_usage_stop(); #endif } #ifdef CONFIG_MULTITHREADING void *z_arch_get_next_switch_handle(struct k_thread *interrupted) { return _current_cpu->nested <= 1 ? z_get_next_switch_handle(interrupted) : interrupted; } #else void *z_arch_get_next_switch_handle(struct k_thread *interrupted) { return interrupted; } #endif /* CONFIG_MULTITHREADING */ static inline void *return_to(void *interrupted) { return z_arch_get_next_switch_handle(interrupted); } /* 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_NMILEVEL >= 2 DEF_INT_C_HANDLER(2) #endif #if XCHAL_NMILEVEL >= 3 DEF_INT_C_HANDLER(3) #endif #if XCHAL_NMILEVEL >= 4 DEF_INT_C_HANDLER(4) #endif #if XCHAL_NMILEVEL >= 5 DEF_INT_C_HANDLER(5) #endif #if XCHAL_NMILEVEL >= 6 DEF_INT_C_HANDLER(6) #endif #if XCHAL_NMILEVEL >= 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(int *interrupted_stack) { int cause, vaddr; _xtensa_irq_bsa_t *bsa = (void *)*(int **)interrupted_stack; bool is_fatal_error = false; uint32_t ps; void *pc; __asm__ volatile("rsr.exccause %0" : "=r"(cause)); #ifdef CONFIG_XTENSA_MMU /* TLB miss exception comes through level 1 interrupt also. * We need to preserve execution context after we have handled * the TLB miss, so we cannot unconditionally unmask interrupts. * For other cause, we can unmask interrupts so this would act * the same as if there is no MMU. */ switch (cause) { case EXCCAUSE_ITLB_MISS: /* Instruction TLB miss */ __fallthrough; case EXCCAUSE_DTLB_MISS: /* Data TLB miss */ /* Do not unmask interrupt while handling TLB misses. */ break; default: /* For others, we can unmask interrupts. */ bsa->ps &= ~PS_INTLEVEL_MASK; break; } #endif /* CONFIG_XTENSA_MMU */ switch (cause) { case EXCCAUSE_LEVEL1_INTERRUPT: return xtensa_int1_c(interrupted_stack); case EXCCAUSE_SYSCALL: /* Just report it to the console for now */ LOG_ERR(" ** SYSCALL PS %p PC %p", (void *)bsa->ps, (void *)bsa->pc); z_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; #ifdef CONFIG_XTENSA_MMU case EXCCAUSE_ITLB_MISS: /* Instruction TLB miss */ __fallthrough; case EXCCAUSE_DTLB_MISS: /* Data TLB miss */ /** * The way it works is, when we try to access an address * that is not mapped, we will have a miss. The HW then * will try to get the correspondent memory in the page * table. As the page table is not mapped in memory we will * have a second miss, which will trigger an exception. * In the exception (here) what we do is to exploit this * hardware capability just trying to load the page table * (not mapped address), which will cause a miss, but then * the hardware will automatically map it again from * the page table. This time it will work since the page * necessary to map the page table itself are wired map. */ __asm__ volatile("wsr a0, " ZSR_EXTRA0_STR "\n\t" "rsr.ptevaddr a0\n\t" "l32i a0, a0, 0\n\t" "rsr a0, " ZSR_EXTRA0_STR "\n\t" "rsync" : : : "a0", "memory"); /* Since we are dealing with TLB misses, we will probably not * want to switch to another thread. */ return interrupted_stack; #endif /* CONFIG_XTENSA_MMU */ default: ps = bsa->ps; pc = (void *)bsa->pc; __asm__ volatile("rsr.excvaddr %0" : "=r"(vaddr)); /* Default for exception */ int reason = K_ERR_CPU_EXCEPTION; /* 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 z_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. */ if ((pc == (void *) &xtensa_arch_except_epc) && (cause == 0)) { cause = 63; __asm__ volatile("wsr.exccause %0" : : "r"(cause)); reason = bsa->a2; } LOG_ERR(" ** FATAL EXCEPTION"); LOG_ERR(" ** CPU %d EXCCAUSE %d (%s)", arch_curr_cpu()->id, cause, z_xtensa_exccause(cause)); LOG_ERR(" ** PC %p VADDR %p", pc, (void *)vaddr); 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)); /* FIXME: legacy xtensa port reported "HW" exception * for all unhandled exceptions, which seems incorrect * as these are software errors. Should clean this * up. */ z_xtensa_fatal_error(reason, (void *)interrupted_stack); break; } switch (cause) { case EXCCAUSE_SYSCALL: case EXCCAUSE_LEVEL1_INTERRUPT: case EXCCAUSE_ALLOCA: case EXCCAUSE_ITLB_MISS: case EXCCAUSE_DTLB_MISS: is_fatal_error = false; break; default: is_fatal_error = true; break; } if (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 z_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_NMILEVEL)); _current_cpu->nested = 1; } return return_to(interrupted_stack); } #if defined(CONFIG_GDBSTUB) void *xtensa_debugint_c(int *interrupted_stack) { extern void z_gdb_isr(z_arch_esf_t *esf); z_gdb_isr((void *)interrupted_stack); return return_to(interrupted_stack); } #endif int z_xtensa_irq_is_enabled(unsigned int irq) { uint32_t ie; __asm__ volatile("rsr.intenable %0" : "=r"(ie)); return (ie & (1 << irq)) != 0U; } #ifdef CONFIG_XTENSA_MORE_SPIN_RELAX_NOPS /* Some compilers might "optimize out" (i.e. remove) continuous NOPs. * So force no optimization to avoid that. */ __no_optimization void arch_spin_relax(void) { #define NOP1(_, __) __asm__ volatile("nop.n;"); LISTIFY(CONFIG_XTENSA_NUM_SPIN_RELAX_NOPS, NOP1, (;)) #undef NOP1 } #endif /* CONFIG_XTENSA_MORE_SPIN_RELAX_NOPS */