| /* |
| * Copyright (c) 2020 Stephanos Ioannidis <root@stephanos.io> |
| * Copyright (c) 2018 Lexmark International, Inc. |
| * Copyright 2023 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <kernel_internal.h> |
| #include <zephyr/arch/common/exc_handle.h> |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); |
| |
| #define FAULT_DUMP_VERBOSE (CONFIG_FAULT_DUMP == 2) |
| |
| #if FAULT_DUMP_VERBOSE |
| static const char *get_dbgdscr_moe_string(uint32_t moe) |
| { |
| switch (moe) { |
| case DBGDSCR_MOE_HALT_REQUEST: |
| return "Halt Request"; |
| case DBGDSCR_MOE_BREAKPOINT: |
| return "Breakpoint"; |
| case DBGDSCR_MOE_ASYNC_WATCHPOINT: |
| return "Asynchronous Watchpoint"; |
| case DBGDSCR_MOE_BKPT_INSTRUCTION: |
| return "BKPT Instruction"; |
| case DBGDSCR_MOE_EXT_DEBUG_REQUEST: |
| return "External Debug Request"; |
| case DBGDSCR_MOE_VECTOR_CATCH: |
| return "Vector Catch"; |
| case DBGDSCR_MOE_OS_UNLOCK_CATCH: |
| return "OS Unlock Catch"; |
| case DBGDSCR_MOE_SYNC_WATCHPOINT: |
| return "Synchronous Watchpoint"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| static void dump_debug_event(void) |
| { |
| /* Read and parse debug mode of entry */ |
| uint32_t dbgdscr = __get_DBGDSCR(); |
| uint32_t moe = (dbgdscr & DBGDSCR_MOE_Msk) >> DBGDSCR_MOE_Pos; |
| |
| /* Print debug event information */ |
| LOG_ERR("Debug Event (%s)", get_dbgdscr_moe_string(moe)); |
| } |
| |
| static uint32_t dump_fault(uint32_t status, uint32_t addr) |
| { |
| uint32_t reason = K_ERR_CPU_EXCEPTION; |
| /* |
| * Dump fault status and, if applicable, status-specific information. |
| * Note that the fault address is only displayed for the synchronous |
| * faults because it is unpredictable for asynchronous faults. |
| */ |
| switch (status) { |
| case FSR_FS_ALIGNMENT_FAULT: |
| reason = K_ERR_ARM_ALIGNMENT_FAULT; |
| LOG_ERR("Alignment Fault @ 0x%08x", addr); |
| break; |
| case FSR_FS_PERMISSION_FAULT: |
| reason = K_ERR_ARM_PERMISSION_FAULT; |
| LOG_ERR("Permission Fault @ 0x%08x", addr); |
| break; |
| case FSR_FS_SYNC_EXTERNAL_ABORT: |
| reason = K_ERR_ARM_SYNC_EXTERNAL_ABORT; |
| LOG_ERR("Synchronous External Abort @ 0x%08x", addr); |
| break; |
| case FSR_FS_ASYNC_EXTERNAL_ABORT: |
| reason = K_ERR_ARM_ASYNC_EXTERNAL_ABORT; |
| LOG_ERR("Asynchronous External Abort"); |
| break; |
| case FSR_FS_SYNC_PARITY_ERROR: |
| reason = K_ERR_ARM_SYNC_PARITY_ERROR; |
| LOG_ERR("Synchronous Parity/ECC Error @ 0x%08x", addr); |
| break; |
| case FSR_FS_ASYNC_PARITY_ERROR: |
| reason = K_ERR_ARM_ASYNC_PARITY_ERROR; |
| LOG_ERR("Asynchronous Parity/ECC Error"); |
| break; |
| case FSR_FS_DEBUG_EVENT: |
| reason = K_ERR_ARM_DEBUG_EVENT; |
| dump_debug_event(); |
| break; |
| #if defined(CONFIG_AARCH32_ARMV8_R) |
| case FSR_FS_TRANSLATION_FAULT: |
| reason = K_ERR_ARM_TRANSLATION_FAULT; |
| LOG_ERR("Translation Fault @ 0x%08x", addr); |
| break; |
| case FSR_FS_UNSUPPORTED_EXCLUSIVE_ACCESS_FAULT: |
| reason = K_ERR_ARM_UNSUPPORTED_EXCLUSIVE_ACCESS_FAULT; |
| LOG_ERR("Unsupported Exclusive Access Fault @ 0x%08x", addr); |
| break; |
| #else |
| case FSR_FS_BACKGROUND_FAULT: |
| reason = K_ERR_ARM_BACKGROUND_FAULT; |
| LOG_ERR("Background Fault @ 0x%08x", addr); |
| break; |
| #endif |
| default: |
| LOG_ERR("Unknown (%u)", status); |
| } |
| return reason; |
| } |
| #endif |
| |
| #if defined(CONFIG_FPU_SHARING) |
| |
| static ALWAYS_INLINE void z_arm_fpu_caller_save(struct __fpu_sf *fpu) |
| { |
| __asm__ volatile ( |
| "vstmia %0, {s0-s15};\n" |
| : : "r" (&fpu->s[0]) |
| : "memory" |
| ); |
| #if CONFIG_VFP_FEATURE_REGS_S64_D32 |
| __asm__ volatile ( |
| "vstmia %0, {d16-d31};\n\t" |
| : |
| : "r" (&fpu->d[0]) |
| : "memory" |
| ); |
| #endif |
| } |
| |
| /** |
| * @brief FPU undefined instruction fault handler |
| * |
| * @return Returns true if the FPU is already enabled |
| * implying a true undefined instruction |
| * Returns false if the FPU was disabled |
| */ |
| bool z_arm_fault_undef_instruction_fp(void) |
| { |
| /* |
| * Assume this is a floating point instruction that faulted because |
| * the FP unit was disabled. Enable the FP unit and try again. If |
| * the FP was already enabled then this was an actual undefined |
| * instruction. |
| */ |
| if (__get_FPEXC() & FPEXC_EN) |
| return true; |
| |
| __set_FPEXC(FPEXC_EN); |
| |
| if (_kernel.cpus[0].nested > 1) { |
| /* |
| * If the nested count is greater than 1, the undefined |
| * instruction exception came from an irq/svc context. (The |
| * irq/svc handler would have the nested count at 1 and then |
| * the undef exception would increment it to 2). |
| */ |
| struct __fpu_sf *spill_esf = |
| (struct __fpu_sf *)_kernel.cpus[0].fp_ctx; |
| |
| if (spill_esf == NULL) |
| return false; |
| |
| _kernel.cpus[0].fp_ctx = NULL; |
| |
| /* |
| * If the nested count is 2 and the current thread has used the |
| * VFP (whether or not it was actually using the VFP before the |
| * current exception) OR if the nested count is greater than 2 |
| * and the VFP was enabled on the irq/svc entrance for the |
| * saved exception stack frame, then save the floating point |
| * context because it is about to be overwritten. |
| */ |
| if (((_kernel.cpus[0].nested == 2) |
| && (_current->base.user_options & K_FP_REGS)) |
| || ((_kernel.cpus[0].nested > 2) |
| && (spill_esf->undefined & FPEXC_EN))) { |
| /* |
| * Spill VFP registers to specified exception stack |
| * frame |
| */ |
| spill_esf->undefined |= FPEXC_EN; |
| spill_esf->fpscr = __get_FPSCR(); |
| z_arm_fpu_caller_save(spill_esf); |
| } |
| } else { |
| /* |
| * If the nested count is one, a thread was the faulting |
| * context. Just flag that this thread uses the VFP. This |
| * means that a thread that uses the VFP does not have to, |
| * but should, set K_FP_REGS on thread creation. |
| */ |
| _current->base.user_options |= K_FP_REGS; |
| } |
| |
| return false; |
| } |
| #endif |
| |
| /** |
| * @brief Undefined instruction fault handler |
| * |
| * @return Returns true if the fault is fatal |
| */ |
| bool z_arm_fault_undef_instruction(z_arch_esf_t *esf) |
| { |
| #if defined(CONFIG_FPU_SHARING) |
| /* |
| * This is a true undefined instruction and we will be crashing |
| * so save away the VFP registers. |
| */ |
| esf->fpu.undefined = __get_FPEXC(); |
| esf->fpu.fpscr = __get_FPSCR(); |
| z_arm_fpu_caller_save(&esf->fpu); |
| #endif |
| |
| /* Print fault information */ |
| LOG_ERR("***** UNDEFINED INSTRUCTION ABORT *****"); |
| |
| uint32_t reason = IS_ENABLED(CONFIG_SIMPLIFIED_EXCEPTION_CODES) ? |
| K_ERR_CPU_EXCEPTION : |
| K_ERR_ARM_UNDEFINED_INSTRUCTION; |
| |
| /* Invoke kernel fatal exception handler */ |
| z_arm_fatal_error(reason, esf); |
| |
| /* All undefined instructions are treated as fatal for now */ |
| return true; |
| } |
| |
| /** |
| * @brief Prefetch abort fault handler |
| * |
| * @return Returns true if the fault is fatal |
| */ |
| bool z_arm_fault_prefetch(z_arch_esf_t *esf) |
| { |
| uint32_t reason = K_ERR_CPU_EXCEPTION; |
| |
| /* Read and parse Instruction Fault Status Register (IFSR) */ |
| uint32_t ifsr = __get_IFSR(); |
| #if defined(CONFIG_AARCH32_ARMV8_R) |
| uint32_t fs = ifsr & IFSR_STATUS_Msk; |
| #else |
| uint32_t fs = ((ifsr & IFSR_FS1_Msk) >> 6) | (ifsr & IFSR_FS0_Msk); |
| #endif |
| |
| /* Read Instruction Fault Address Register (IFAR) */ |
| uint32_t ifar = __get_IFAR(); |
| |
| /* Print fault information*/ |
| LOG_ERR("***** PREFETCH ABORT *****"); |
| if (FAULT_DUMP_VERBOSE) { |
| reason = dump_fault(fs, ifar); |
| } |
| |
| /* Simplify exception codes if requested */ |
| if (IS_ENABLED(CONFIG_SIMPLIFIED_EXCEPTION_CODES) && (reason >= K_ERR_ARCH_START)) { |
| reason = K_ERR_CPU_EXCEPTION; |
| } |
| |
| /* Invoke kernel fatal exception handler */ |
| z_arm_fatal_error(reason, esf); |
| |
| /* All prefetch aborts are treated as fatal for now */ |
| return true; |
| } |
| |
| #ifdef CONFIG_USERSPACE |
| Z_EXC_DECLARE(z_arm_user_string_nlen); |
| |
| static const struct z_exc_handle exceptions[] = { |
| Z_EXC_HANDLE(z_arm_user_string_nlen) |
| }; |
| |
| /* Perform an assessment whether an MPU fault shall be |
| * treated as recoverable. |
| * |
| * @return true if error is recoverable, otherwise return false. |
| */ |
| static bool memory_fault_recoverable(z_arch_esf_t *esf) |
| { |
| for (int i = 0; i < ARRAY_SIZE(exceptions); i++) { |
| /* Mask out instruction mode */ |
| uint32_t start = (uint32_t)exceptions[i].start & ~0x1U; |
| uint32_t end = (uint32_t)exceptions[i].end & ~0x1U; |
| |
| if (esf->basic.pc >= start && esf->basic.pc < end) { |
| esf->basic.pc = (uint32_t)(exceptions[i].fixup); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| #endif |
| |
| /** |
| * @brief Data abort fault handler |
| * |
| * @return Returns true if the fault is fatal |
| */ |
| bool z_arm_fault_data(z_arch_esf_t *esf) |
| { |
| uint32_t reason = K_ERR_CPU_EXCEPTION; |
| |
| /* Read and parse Data Fault Status Register (DFSR) */ |
| uint32_t dfsr = __get_DFSR(); |
| #if defined(CONFIG_AARCH32_ARMV8_R) |
| uint32_t fs = dfsr & DFSR_STATUS_Msk; |
| #else |
| uint32_t fs = ((dfsr & DFSR_FS1_Msk) >> 6) | (dfsr & DFSR_FS0_Msk); |
| #endif |
| |
| /* Read Data Fault Address Register (DFAR) */ |
| uint32_t dfar = __get_DFAR(); |
| |
| #if defined(CONFIG_USERSPACE) |
| if ((fs == COND_CODE_1(CONFIG_AARCH32_ARMV8_R, |
| (FSR_FS_TRANSLATION_FAULT), |
| (FSR_FS_BACKGROUND_FAULT))) |
| || (fs == FSR_FS_PERMISSION_FAULT)) { |
| if (memory_fault_recoverable(esf)) { |
| return false; |
| } |
| } |
| #endif |
| |
| /* Print fault information*/ |
| LOG_ERR("***** DATA ABORT *****"); |
| if (FAULT_DUMP_VERBOSE) { |
| reason = dump_fault(fs, dfar); |
| } |
| |
| /* Simplify exception codes if requested */ |
| if (IS_ENABLED(CONFIG_SIMPLIFIED_EXCEPTION_CODES) && (reason >= K_ERR_ARCH_START)) { |
| reason = K_ERR_CPU_EXCEPTION; |
| } |
| |
| /* Invoke kernel fatal exception handler */ |
| z_arm_fatal_error(reason, esf); |
| |
| /* All data aborts are treated as fatal for now */ |
| return true; |
| } |
| |
| /** |
| * @brief Initialisation of fault handling |
| */ |
| void z_arm_fault_init(void) |
| { |
| /* Nothing to do for now */ |
| } |