From e2ff2fdd91d3f9ab43d777a8f78882e2bbeb02d0 Mon Sep 17 00:00:00 2001 From: Andrew Boie Date: Tue, 21 Jun 2016 12:15:33 -0700 Subject: [PATCH] nios2: add base exception handling code Change-Id: I56b0ec1a3576a77ca7bd6f2c0217de8053406927 Signed-off-by: Andrew Boie --- arch/nios2/core/Makefile | 4 +- arch/nios2/core/exception.S | 244 ++++++++++++++++++++++++++++++ arch/nios2/core/fatal.c | 126 ++++++++++++--- arch/nios2/include/nano_private.h | 4 +- include/arch/nios2/arch.h | 66 +++++++- 5 files changed, 420 insertions(+), 24 deletions(-) create mode 100644 arch/nios2/core/exception.S diff --git a/arch/nios2/core/Makefile b/arch/nios2/core/Makefile index 12a41278b26..5d55e05e13a 100644 --- a/arch/nios2/core/Makefile +++ b/arch/nios2/core/Makefile @@ -3,7 +3,7 @@ ccflags-y +=-I$(srctree)/arch/$(ARCH)/include ccflags-y += -I$(srctree)/kernel/microkernel/include obj-y += reset.o irq_manage.o fatal.o swap.o thread.o \ - cpu_idle.o irq_offload.o prep_c.o crt0.o + cpu_idle.o irq_offload.o prep_c.o crt0.o \ + exception.o obj-$(CONFIG_IRQ_OFFLOAD) += irq_offload.o - diff --git a/arch/nios2/core/exception.S b/arch/nios2/core/exception.S new file mode 100644 index 00000000000..e38785076f4 --- /dev/null +++ b/arch/nios2/core/exception.S @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _ASMLANGUAGE +#include +#include + +/* exports */ +GTEXT(_exception) +GTEXT(_exception_exit) +GTEXT(_exception_enter_fault) + +/* import */ +GTEXT(_Fault) +GTEXT(_Swap) +GTEXT(_exception_try_muldiv) + +/* Allows use of r1/at register, otherwise reserved for assembler use */ +.set noat + +/* Placed into special 'exception' section so that the linker can put this code + * at ALT_CPU_EXCEPTION_ADDR defined in system.h + * + * This is the common entry point for processor exceptions and interrupts from + * the Internal Interrupt Controller (IIC). + * + * If the External (EIC) controller is in use, then we will never get here on + * behalf of an interrupt, instead the EIC driver will have set up a vector + * table and the processor will jump directly into the appropriate table + * entry. + */ +SECTION_FUNC(exception.entry, _exception) + /* Reserve thread stack space for saving context */ + addi sp, sp, -76 + + /* Preserve all caller-saved registers onto the thread's stack */ + stw ra, 0(sp) + /* Gap here for muldiv handler to store zero register */ + stw r1, 8(sp) + stw r2, 12(sp) + stw r3, 16(sp) + stw r4, 20(sp) + stw r5, 24(sp) + stw r6, 28(sp) + stw r7, 32(sp) + stw r8, 36(sp) + stw r9, 40(sp) + stw r10, 44(sp) + stw r11, 48(sp) + stw r12, 52(sp) + stw r13, 56(sp) + stw r14, 60(sp) + stw r15, 64(sp) + + /* Store value of estatus control register */ + rdctl et, estatus + stw et, 68(sp) + + /* ea-4 is the address of the instruction when the exception happened, + * put this in the stack frame as well + */ + addi r15, ea, -4 + stw r15, 72(sp) + + /* Figure out whether we are here because of an interrupt or an + * exception. If an interrupt, switch stacks and enter IRQ handling + * code. If an exception, remain on current stack and enter exception + * handing code. From the CPU manual, ipending must be nonzero and + * estatis.PIE must be enabled for this to be considered an interrupt. + * + * Stick ipending in r4 since it will be an arg for _enter_irq + */ + rdctl r4, ipending + beq r4, zero, not_interrupt + /* We stashed estatus in et earlier */ + andi r15, et, 1 + beq r15, zero, not_interrupt + + /* If we get here, this is an interrupt */ + + /* Grab a reference to _nanokernel in r10 so we can determine the + * current irq stack pointer + */ + movhi r10, %hi(_nanokernel) + ori r10, r10, %lo(_nanokernel) + + /* Stash a copy of thread's sp in r12 so that we can put it on the IRQ + * stack + */ + mov r12, sp + + /* Switch to interrupt stack */ + ldw sp, __tNANO_irq_sp_OFFSET(r10) + + /* Store thread stack pointer onto IRQ stack */ + addi sp, sp, -4 + stw r12, 0(sp) + +BRANCH_LABEL(on_irq_stack) + +#if 0 /* TODO enable interrupt handling code */ + /* Enter C interrupt handling code. Value of ipending will be the + * function parameter since we put it in r4 + */ + call _enter_irq +#endif + + /* Interrupt handler finished and the interrupt should be serviced + * now, the appropriate bits in ipending should be cleared */ + + /* Get a reference to _nanokernel again in r10 */ + movhi r10, %hi(_nanokernel) + ori r10, r10, %lo(_nanokernel) + + /* Determine whether the execution of the ISR requires a context + * switch. If the interrupted thread is PREEMPTIBLE (a task) and + * _nanokernel.fiber is non-NULL, a _Swap() needs to occur. + */ + + /* Check (_nanokernel.current->flags & PREEMPTIBLE), if not + * goto no_reschedule + */ + ldw r11, __tNANO_current_OFFSET(r10) + ldw r12, __tTCS_flags_OFFSET(r11) + movi r13, PREEMPTIBLE + and r12, r13, r12 + beq r12, zero, no_reschedule + + /* Check _nanokernel.fiber != NULL, if NULL goto no_reschedule */ + ldw r11, __tNANO_fiber_OFFSET(r10) + beq r11, zero, no_reschedule + + /* + * A context reschedule is required: keep the volatile registers of + * the interrupted thread on the context's stack. Utilize + * the existing _Swap() primitive to save the remaining + * thread's registers (including floating point) and perform + * a switch to the new thread. + */ + + /* We put the thread stack pointer on top of the IRQ stack before + * we switched stacks. Restore it to go back to thread stack + */ + ldw sp, 0(sp) + + /* Argument to Swap() is estatus since that's the state of the + * status register before the exception happened. When coming + * out of the context switch we need this info to restore + * IRQ lock state. We put this value in et earlier. + */ + mov r4, et + + call _Swap + jmpi _exception_exit + +BRANCH_LABEL(not_interrupt) + +#if 0 /* TODO enable multiply / divide exception handing */ + + /* Since this wasn't an interrupt we're not going to restart the + * faulting instruction. If it's an unimplemented math instruction, + * the muldiv code will handle it, else we just give up and _Fault. + * + * We earlier put ea - 4 in the stack frame, replace it with just ea + */ + stw ea, 72(sp) + + /* Could be an unimplemented instruction we have to emulate. + * Smaller Nios II cores don't have multiply or divide instructions. + * This code comes back to either _exception_enter_fault or + * _exception_exit + */ + jmpi _exception_try_muldiv +#endif + +SECTION_FUNC(exception.entry, _exception_enter_fault) + /* If we get here, the exception wasn't in interrupt or an + * unimplemented math instruction. Let _Fault() handle it in + * C domain + */ + + ldw r4, 0(sp) + call _Fault + jmpi _exception_exit + +BRANCH_LABEL(no_reschedule) + + /* We put the thread stack pointer on top of the IRQ stack before + * we switched stacks. Restore it to go back to thread stack + */ + ldw sp, 0(sp) + + /* Fall through */ + +SECTION_FUNC(exception.entry, _exception_exit) + /* We are on the thread stack. Restore all saved registers + * and return to the interrupted context */ + + /* Return address from the exception */ + ldw ea, 72(sp) + + /* Restore estatus + * XXX is this right??? */ + ldw r5, 68(sp) + wrctl estatus, r5 + + /* Restore caller-saved registers */ + ldw ra, 0(sp) + ldw r1, 8(sp) + ldw r2, 12(sp) + ldw r3, 16(sp) + ldw r4, 20(sp) + ldw r5, 24(sp) + ldw r6, 28(sp) + ldw r7, 32(sp) + ldw r8, 36(sp) + ldw r9, 40(sp) + ldw r10, 44(sp) + ldw r11, 48(sp) + ldw r12, 52(sp) + ldw r13, 56(sp) + ldw r14, 60(sp) + ldw r15, 64(sp) + + /* Put the stack pointer back where it was when we entered + * exception state + */ + addi sp, sp, 76 + + /* All done, copy estatus into status and transfer to ea */ + eret diff --git a/arch/nios2/core/fatal.c b/arch/nios2/core/fatal.c index 99a21caf00d..517b6868fb4 100644 --- a/arch/nios2/core/fatal.c +++ b/arch/nios2/core/fatal.c @@ -19,9 +19,26 @@ #include #include -/* TODO initialize with sentinel values */ -const NANO_ESF _default_esf; - +const NANO_ESF _default_esf = { + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad, + 0xdeadbaad +}; /** * @@ -42,22 +59,73 @@ const NANO_ESF _default_esf; FUNC_NORETURN void _NanoFatalErrorHandler(unsigned int reason, const NANO_ESF *esf) { - /* STUB TODO: dump out reason for the error and any interesting - * info in esf - */ +#ifdef CONFIG_PRINTK + switch (reason) { + case _NANO_ERR_CPU_EXCEPTION: + case _NANO_ERR_SPURIOUS_INT: + break; + + case _NANO_ERR_INVALID_TASK_EXIT: + printk("***** Invalid Exit Software Error! *****\n"); + break; + + + case _NANO_ERR_ALLOCATION_FAIL: + printk("**** Kernel Allocation Failure! ****\n"); + break; + + default: + printk("**** Unknown Fatal Error %d! ****\n", reason); + break; + } + + printk("Current thread ID: 0x%x\n" + "Faulting instruction: 0x%x\n" + " r1: 0x%x r2: 0x%x r3: 0x%x r4: 0x%x\n" + " r5: 0x%x r6: 0x%x r7: 0x%x r8: 0x%x\n" + " r9: 0x%x r10: 0x%x r11: 0x%x r12: 0x%x\n" + "r13: 0x%x r14: 0x%x r15: 0x%x ra: 0x%x\n" + "estatus: %x\n", sys_thread_self_get(), esf->instr, + esf->r1, esf->r2, esf->r3, esf->r4, + esf->r5, esf->r6, esf->r7, esf->r8, + esf->r9, esf->r10, esf->r11, esf->r12, + esf->r13, esf->r14, esf->r15, esf->ra, + esf->estatus); +#endif _SysFatalErrorHandler(reason, esf); } -FUNC_NORETURN void _Fault(void) +FUNC_NORETURN void _Fault(const NANO_ESF *esf) { - /* STUB TODO dump out reason for this exception */ +#ifdef CONFIG_PRINTK + /* Unfortunately, completely unavailable on Nios II/e cores */ +#ifdef NIOS2_HAS_EXTRA_EXCEPTION_INFO + uint32_t exc_reg, badaddr_reg, eccftl; + enum nios2_exception_cause cause; - _NanoFatalErrorHandler(_NANO_ERR_HW_EXCEPTION, &_default_esf); + exc_reg = _nios2_creg_read(NIOS2_CR_EXCEPTION); + + /* Bit 31 indicates potentially fatal ECC error */ + eccftl = (exc_reg & NIOS2_EXCEPTION_REG_ECCFTL_MASK) != 0; + + /* Bits 2-6 contain the cause code */ + cause = (exc_reg & NIOS2_EXCEPTION_REG_CAUSE_MASK) + >> NIOS2_EXCEPTION_REG_CAUSE_OFST; + printk("Exception cause: 0x%x ECCFTL: %d\n", exc_reg, eccftl); + if (BIT(cause) & NIOS2_BADADDR_CAUSE_MASK) { + badaddr_reg = _nios2_creg_read(NIOS2_CR_BADADDR); + printk("Badaddr: 0x%x\n", badaddr_reg); + } +#endif /* NIOS2_HAS_EXTRA_EXCEPTION_INFO */ +#endif /* CONFIG_PRINTK */ + + _NanoFatalErrorHandler(_NANO_ERR_CPU_EXCEPTION, esf); } + /** * * @brief Fatal error handler @@ -74,20 +142,42 @@ FUNC_NORETURN void _Fault(void) * information to a persistent repository and/or rebooting the system. * * @param reason the fatal error reason - * @param pEsf the pointer to the exception stack frame + * @param pEsf pointer to exception stack frame * - * @return This function does not return. + * @return N/A */ FUNC_NORETURN void _SysFatalErrorHandler(unsigned int reason, - const NANO_ESF *esf) + const NANO_ESF *pEsf) { - /* STUB TODO try to abort task/fibers like in the x86 implementation */ - printk("Fatal error!\n"); + nano_context_type_t curCtx = sys_execution_context_type_get(); - while (1) { - /* whee! */ + ARG_UNUSED(reason); + ARG_UNUSED(pEsf); + + if ((curCtx != NANO_CTX_ISR) && !_is_thread_essential()) { +#ifdef CONFIG_MICROKERNEL + if (curCtx == NANO_CTX_TASK) { + extern FUNC_NORETURN void _TaskAbort(void); + printk("Fatal task error! Aborting task.\n"); + _TaskAbort(); + } else +#endif /* CONFIG_MICROKERNEL */ + { + printk("Fatal fiber error! Aborting fiber.\n"); + fiber_abort(); + } + CODE_UNREACHABLE; } + + printk("Fatal fault in %s ! Spinning...\n", + curCtx == NANO_CTX_ISR + ? "ISR" + : curCtx == NANO_CTX_FIBER ? "essential fiber" + : "essential task"); +#ifdef NIOS2_HAS_DEBUG_STUB + _nios2_break(); +#endif + for (;;) + ; /* Spin forever */ } - - diff --git a/arch/nios2/include/nano_private.h b/arch/nios2/include/nano_private.h index d5534a3b295..d0fe8b6caf6 100644 --- a/arch/nios2/include/nano_private.h +++ b/arch/nios2/include/nano_private.h @@ -154,6 +154,7 @@ struct s_NANO { typedef struct s_NANO tNANO; extern tNANO _nanokernel; +extern char _interrupt_stack[CONFIG_ISR_STACK_SIZE]; /* Arch-specific nanokernel APIs */ @@ -162,7 +163,8 @@ void nano_cpu_atomic_idle(unsigned int key); static ALWAYS_INLINE void nanoArchInit(void) { - /* STUB */ + _nanokernel.irq_sp = (char *)STACK_ROUND_DOWN(_interrupt_stack + + CONFIG_ISR_STACK_SIZE); } static ALWAYS_INLINE void fiberRtnValueSet(struct tcs *fiber, diff --git a/include/arch/nios2/arch.h b/include/arch/nios2/arch.h index ee76e7102e4..5f0021163b4 100644 --- a/include/arch/nios2/arch.h +++ b/include/arch/nios2/arch.h @@ -34,10 +34,11 @@ extern "C" { #define STACK_ALIGN 4 -#define _NANO_ERR_HW_EXCEPTION (0) /* MPU/Bus/Usage fault */ +#define _NANO_ERR_CPU_EXCEPTION (0) /* Any unhandled exception */ #define _NANO_ERR_INVALID_TASK_EXIT (1) /* Invalid task exit */ #define _NANO_ERR_STACK_CHK_FAIL (2) /* Stack corruption detected */ #define _NANO_ERR_ALLOCATION_FAIL (3) /* Kernel Allocation Failure */ +#define _NANO_ERR_SPURIOUS_INT (4) /* Spurious interrupt */ #ifndef _ASMLANGUAGE #include @@ -93,8 +94,25 @@ void _arch_irq_enable(unsigned int irq); void _arch_irq_disable(unsigned int irq); struct __esf { - /* XXX - not defined yet */ - uint32_t placeholder; + uint32_t ra; /* return address r31 */ + uint32_t r0; /* zero register */ + uint32_t r1; /* at */ + uint32_t r2; /* return value */ + uint32_t r3; /* return value */ + uint32_t r4; /* register args */ + uint32_t r5; /* register args */ + uint32_t r6; /* register args */ + uint32_t r7; /* register args */ + uint32_t r8; /* Caller-saved general purpose */ + uint32_t r9; /* Caller-saved general purpose */ + uint32_t r10; /* Caller-saved general purpose */ + uint32_t r11; /* Caller-saved general purpose */ + uint32_t r12; /* Caller-saved general purpose */ + uint32_t r13; /* Caller-saved general purpose */ + uint32_t r14; /* Caller-saved general purpose */ + uint32_t r15; /* Caller-saved general purpose */ + uint32_t estatus; + uint32_t instr; /* Instruction being executed when exc occurred */ }; typedef struct __esf NANO_ESF; @@ -103,6 +121,48 @@ extern const NANO_ESF _default_esf; FUNC_NORETURN void _SysFatalErrorHandler(unsigned int reason, const NANO_ESF *esf); + +enum nios2_exception_cause { + NIOS2_EXCEPTION_UNKNOWN = -1, + NIOS2_EXCEPTION_RESET = 0, + NIOS2_EXCEPTION_CPU_ONLY_RESET_REQUEST = 1, + NIOS2_EXCEPTION_INTERRUPT = 2, + NIOS2_EXCEPTION_TRAP_INST = 3, + NIOS2_EXCEPTION_UNIMPLEMENTED_INST = 4, + NIOS2_EXCEPTION_ILLEGAL_INST = 5, + NIOS2_EXCEPTION_MISALIGNED_DATA_ADDR = 6, + NIOS2_EXCEPTION_MISALIGNED_TARGET_PC = 7, + NIOS2_EXCEPTION_DIVISION_ERROR = 8, + NIOS2_EXCEPTION_SUPERVISOR_ONLY_INST_ADDR = 9, + NIOS2_EXCEPTION_SUPERVISOR_ONLY_INST = 10, + NIOS2_EXCEPTION_SUPERVISOR_ONLY_DATA_ADDR = 11, + NIOS2_EXCEPTION_TLB_MISS = 12, + NIOS2_EXCEPTION_TLB_EXECUTE_PERM_VIOLATION = 13, + NIOS2_EXCEPTION_TLB_READ_PERM_VIOLATION = 14, + NIOS2_EXCEPTION_TLB_WRITE_PERM_VIOLATION = 15, + NIOS2_EXCEPTION_MPU_INST_REGION_VIOLATION = 16, + NIOS2_EXCEPTION_MPU_DATA_REGION_VIOLATION = 17, + NIOS2_EXCEPTION_ECC_TLB_ERR = 18, + NIOS2_EXCEPTION_ECC_FETCH_ERR = 19, + NIOS2_EXCEPTION_ECC_REGISTER_FILE_ERR = 20, + NIOS2_EXCEPTION_ECC_DATA_ERR = 21, + NIOS2_EXCEPTION_ECC_DATA_CACHE_WRITEBACK_ERR = 22 +}; + +/* Bitfield indicating which exception cause codes report a valid + * badaddr register. NIOS2_EXCEPTION_TLB_MISS and NIOS2_EXCEPTION_ECC_TLB_ERR + * are deliberately not included here, you need to check if TLBMISC.D=1 + */ +#define NIOS2_BADADDR_CAUSE_MASK \ + (BIT(NIOS2_EXCEPTION_SUPERVISOR_ONLY_DATA_ADDR) | \ + BIT(NIOS2_EXCEPTION_MISALIGNED_DATA_ADDR) | \ + BIT(NIOS2_EXCEPTION_MISALIGNED_TARGET_PC) | \ + BIT(NIOS2_EXCEPTION_TLB_READ_PERM_VIOLATION) | \ + BIT(NIOS2_EXCEPTION_TLB_WRITE_PERM_VIOLATION) | \ + BIT(NIOS2_EXCEPTION_MPU_DATA_REGION_VIOLATION) | \ + BIT(NIOS2_EXCEPTION_ECC_DATA_ERR)) + + #endif /* _ASMLANGUAGE */ #ifdef __cplusplus