// Copyright 2024 The Pigweed Authors
//
// 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
//
//     https://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.

#include "pw_cpu_exception_cortex_m/crash.h"

#include <cinttypes>

#include "pw_cpu_exception_cortex_m/cpu_state.h"
#include "pw_cpu_exception_cortex_m/util.h"
#include "pw_cpu_exception_cortex_m_private/config.h"
#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
#include "pw_preprocessor/arch.h"

namespace pw::cpu_exception::cortex_m {

void AnalyzeCpuStateAndCrash(const pw_cpu_exception_State& cpu_state,
                             const char* optional_thread_name) {
  const char* thread_name =
      optional_thread_name == nullptr ? "?" : optional_thread_name;
  const bool is_nested_fault = cpu_state.extended.hfsr & kHfsrForcedMask;
  const uint32_t active_faults =
      cpu_state.extended.cfsr &
      (kCfsrMemAllErrorsMask | kCfsrBusAllErrorsMask | kCfsrUsageAllErrorsMask);
  // The expression (n & (n - 1)) is 0 if n is a power of 2, i.e. n has
  // only 1 bit (fault flag) set. So if it is != 0 there are multiple faults.
  const bool has_multiple_faults = (active_faults & (active_faults - 1)) != 0;

  // This provides a high-level assessment of the cause of the exception.
  // These conditionals are ordered by priority to ensure the most critical
  // issues are highlighted first. These are not mutually exclusive; a bus fault
  // could occur during the handling of a MPU violation, causing a nested fault.
#if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
  if (cpu_state.extended.cfsr & kCfsrStkofMask) {
    if (ProcessStackActive(cpu_state)) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "PSP stack overflow. Thread=%s PC=0x%08" PRIx32 " LR=0x%08" PRIx32
          " CFSR=0x%08" PRIx32 " PSP=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          cpu_state.extended.psp,
          is_nested_fault,
          has_multiple_faults);
    } else {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "MSP stack overflow. Thread=%s PC=0x%08" PRIx32 " LR=0x%08" PRIx32
          " CFSR=0x%08" PRIx32 " MSP=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          cpu_state.extended.msp,
          is_nested_fault,
          has_multiple_faults);
    }
    return;
  }
#endif  // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE

  // Memory management fault.
  if (cpu_state.extended.cfsr & kCfsrMemFaultMask) {
    const bool is_mmfar_valid = cpu_state.extended.cfsr & kCfsrMmarvalidMask;
#if PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
    if (cpu_state.extended.cfsr & kCfsrIaccviolMask) {
      // The PC value stacked for the exception return points to the faulting
      // instruction.
      // The processor does not write the fault address to the MMFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "IACCVIOL: MPU violation on instruction fetch. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrDaccviolMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "DACCVIOL: MPU violation on memory read/write. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " ValidMmfar=%d MMFAR=0x%08" PRIx32 "Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_mmfar_valid,
          cpu_state.extended.mmfar,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrMunstkerrMask) {
      // The processor does not write the fault address to the MMFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "MUNSTKERR: MPU violation on exception return. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrMstkerrMask) {
      // The processor does not write the fault address to the MMFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "MSTKERR: MPU violation on exception entry. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrMlsperrMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "MLSPERR: MPU violation on lazy FPU state preservation. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " ValidMmfar=%d MMFAR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_mmfar_valid,
          cpu_state.extended.mmfar,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
#endif  // PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS

    PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
                                    "MPU fault. Thread=%s PC=0x%08" PRIx32
                                    " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
                                    " ValidMmfar=%d MMFAR=0x%08" PRIx32
                                    " Nested=%d Multiple=%d",
                                    thread_name,
                                    cpu_state.base.pc,
                                    cpu_state.base.lr,
                                    cpu_state.extended.cfsr,
                                    is_mmfar_valid,
                                    cpu_state.extended.mmfar,
                                    is_nested_fault,
                                    has_multiple_faults);
    return;
  }

  // Bus fault.
  if (cpu_state.extended.cfsr & kCfsrBusFaultMask) {
    const bool is_bfar_valid = cpu_state.extended.cfsr & kCfsrBfarvalidMask;
#if PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
    if (cpu_state.extended.cfsr & kCfsrIbuserrMask) {
      // The processor does not write the fault address to the BFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "IBUSERR: Bus fault on instruction fetch. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrPreciserrMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "PRECISERR: Precise bus fault. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " ValidBfar=%d BFAR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_bfar_valid,
          cpu_state.extended.bfar,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrImpreciserrMask) {
      // The processor does not write the fault address to the BFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "IMPRECISERR: Imprecise bus fault. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrUnstkerrMask) {
      // The processor does not write the fault address to the BFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "UNSTKERR: Derived bus fault on exception context save. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrStkerrMask) {
      // The processor does not write the fault address to the BFAR.
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "STKERR: Derived bus fault on exception context restore. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrLsperrMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "LSPERR: Derived bus fault on lazy FPU state preservation. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " ValidBfar=%d BFAR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_bfar_valid,
          cpu_state.extended.bfar,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
#endif  // PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS

    PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
                                    "Bus Fault. Thread=%s PC=0x%08" PRIx32
                                    " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
                                    " ValidBfar=%d BFAR=0x%08" PRIx32
                                    " Nested=%d Multiple=%d",
                                    thread_name,
                                    cpu_state.base.pc,
                                    cpu_state.base.lr,
                                    cpu_state.extended.cfsr,
                                    is_bfar_valid,
                                    cpu_state.extended.bfar,
                                    is_nested_fault,
                                    has_multiple_faults);
    return;
  }

  // Usage fault.
  if (cpu_state.extended.cfsr & kCfsrUsageFaultMask) {
#if PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
    if (cpu_state.extended.cfsr & kCfsrUndefinstrMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
                                      "UNDEFINSTR: Encountered invalid "
                                      "instruction. Thread=%s PC=0x%08" PRIx32
                                      " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
                                      "  Nested=%d Multiple=%d",
                                      thread_name,
                                      cpu_state.base.pc,
                                      cpu_state.base.lr,
                                      cpu_state.extended.cfsr,
                                      is_nested_fault,
                                      has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrInvstateMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "INVSTATE: Attempted instruction with invalid EPSR value. Thread=%s "
          "PC=0x%08" PRIx32 " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
          " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrInvpcMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "INVPC: Invalid program counter. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrNocpMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "NOCP: Coprocessor disabled or not present. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrUnalignedMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "UNALIGNED: Unaligned memory access. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
    if (cpu_state.extended.cfsr & kCfsrDivbyzeroMask) {
      PW_CPU_EXCEPTION_CORTEX_M_CRASH(
          cpu_state,
          "DIVBYZERO: Division by zero. Thread=%s PC=0x%08" PRIx32
          " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
          thread_name,
          cpu_state.base.pc,
          cpu_state.base.lr,
          cpu_state.extended.cfsr,
          is_nested_fault,
          has_multiple_faults);
      return;
    }
#endif  // PW_CPU_EXCEPTION_CORTEX_M_CRASH_EXTENDED_CPU_ANALYSIS
    PW_CPU_EXCEPTION_CORTEX_M_CRASH(
        cpu_state,
        "Fault=Usage Nested=%d Multiple=%d Thread=%s PC=0x%08" PRIx32
        " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32,
        is_nested_fault,
        has_multiple_faults,
        thread_name,
        cpu_state.base.pc,
        cpu_state.base.lr,
        cpu_state.extended.cfsr);
    return;
  }
  if ((cpu_state.extended.icsr & kIcsrVectactiveMask) == kNmiIsrNum) {
    PW_CPU_EXCEPTION_CORTEX_M_CRASH(
        cpu_state,
        "Non-Maskable Interrupt triggered. Thread=%s PC=0x%08" PRIx32
        " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32 " Nested=%d Multiple=%d",
        thread_name,
        cpu_state.base.pc,
        cpu_state.base.lr,
        cpu_state.extended.cfsr,
        is_nested_fault,
        has_multiple_faults);
    return;
  }

  PW_CPU_EXCEPTION_CORTEX_M_CRASH(cpu_state,
                                  "Unknown fault. Thread=%s PC=0x%08" PRIx32
                                  " LR=0x%08" PRIx32 " CFSR=0x%08" PRIx32
                                  " Nested=%d Multiple=%d",
                                  thread_name,
                                  cpu_state.base.pc,
                                  cpu_state.base.lr,
                                  cpu_state.extended.cfsr,
                                  is_nested_fault,
                                  has_multiple_faults);
  return;
}

}  // namespace pw::cpu_exception::cortex_m
