//*****************************************************************************
//
//! @file am_util_faultisr.c
//!
//! @brief An extended hard-fault handler.
//
// This module is intended to be completely portable with no HAL or BSP
// dependencies.
//
// Further, it is intended to be compiler/platform independent enabling it to
// run on GCC, Keil, IAR, etc.
//
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2017, Ambiq Micro
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// This is part of revision v1.2.10-2-gea660ad-hotfix2 of the AmbiqSuite Development Package.
//
//*****************************************************************************

#include <stdint.h>
#include "am_mcu_apollo.h"

//*****************************************************************************
//
// Macros
//
//*****************************************************************************

//
// Macros used by am_util_faultisr_collect_data().
//
#define AM_REG_SYSCTRL_CFSR_O                        0xE000ED28
#define AM_REG_SYSCTRL_BFAR_O                        0xE000ED38
#define AM_REGVAL(x)               (*((volatile uint32_t *)(x)))

//*****************************************************************************
//
// Data structures
//
//*****************************************************************************

//
// Define a structure for local storage in am_util_faultisr_collect_data().
// Set structure alignment to 1 byte to minimize storage requirements.
//
#pragma pack(1)
typedef struct
{
    //
    // Stacked registers
    //
    volatile uint32_t u32R0;
    volatile uint32_t u32R1;
    volatile uint32_t u32R2;
    volatile uint32_t u32R3;
    volatile uint32_t u32R12;
    volatile uint32_t u32LR;
    volatile uint32_t u32PC;
    volatile uint32_t u32PSR;

    //
    // Other data
    //
    volatile uint32_t u32FaultAddr;
    volatile uint32_t u32BFAR;
    volatile uint32_t u32CFSR;
    volatile uint8_t  u8MMSR;
    volatile uint8_t  u8BFSR;
    volatile uint16_t u16UFSR;

} am_fault_t;

//
// Restore the default structure alignment
//
#pragma pack()

//*****************************************************************************
//
// Prototypes
//
//*****************************************************************************
void am_util_faultisr_collect_data(uint32_t u32IsrSP);

//
// Prototype for printf, if used.
//
extern uint32_t am_util_stdio_printf(char *pui8Fmt, ...);

//*****************************************************************************
//
// am_fault_isr() replaces the weak one defined in the startup code.  This
// simple function captures SP (which is needed by getStackedReg) then calls
// the function that actually decodes the hard fault data,
// am_util_faultisr_collect_data().
//
//*****************************************************************************
//*****************************************************************************
//
// getStackedReg() will retrieve a specified register value, as it was stacked
// by the processor after the fault, from the stack.
//
// The registers are stacked in the following order:
//  R0, R1, R2, R3, R12, LR, PC, PSR.
// To get R0 from the stack, call getStackedReg(0), r1 is getStackedReg(1)...
//
//*****************************************************************************
#if defined(__GNUC_STDC_INLINE__)
uint32_t __attribute__((naked))
am_fault_isr(void)
{
    __asm("    push    {r7,lr}");
    __asm("    mov     r0, sp");
    __asm("    adds    r0, #(2*4)");
    __asm("    bl      am_util_faultisr_collect_data");
    __asm("    pop     {r0,pc}");
}

uint32_t __attribute__((naked))
getStackedReg(uint32_t regnum, uint32_t u32SP)
{
    __asm("    lsls    r0, r0, #2");
    __asm("    adds    r0, r1");
    __asm("    ldr     r0, [r0]");
    __asm("    bx      lr");
}
#elif defined(__ARMCC_VERSION)
__asm uint32_t
am_fault_isr(void)
{
    import  am_util_faultisr_collect_data

    push    {r7, lr}
    mov     r0, sp
    adds    r0, #(2*4)
    bl      am_util_faultisr_collect_data
    pop     {r0, pc}
}

__asm uint32_t
getStackedReg(uint32_t regnum, uint32_t u32SP)
{
    lsls    r0, r0, #2
    adds    r0, r0, r1
    ldr     r0, [r0]
    bx      lr
}
#elif defined(__IAR_SYSTEMS_ICC__)
#pragma diag_suppress = Pe940   // Suppress IAR compiler warning about missing
                                // return statement on a non-void function
__stackless uint32_t
am_fault_isr(void)
{
    __asm("     push    {r7,lr}");
    __asm("     mov     r0, sp");
    __asm("     adds    r0, #(2*4)");
    __asm("     bl      am_util_faultisr_collect_data");
    __asm("     pop     {r0,pc}");
}

__stackless uint32_t
getStackedReg(uint32_t regnum, uint32_t u32SP)
{
    __asm("     lsls    r0, r0, #2");
    __asm("     adds    r0, r0, r1");
    __asm("     ldr     r0, [r0]");
    __asm("     bx      lr");
}
#pragma diag_default = Pe940    // Restore IAR compiler warning
#endif

//*****************************************************************************
//
// am_util_faultisr_collect_data(uint32_t u32IsrSP);
//
// This function is intended to be called by am_fault_isr(), which is called
// when the processor receives a hard fault interrupt.  This part of the
// handler parses through the various fault codes and saves them into a data
// structure so they can be readily examined by the user in the debugger.
//
// The input u32IsrSP is expected to be the value of the stack pointer when
// am_fault_isr() was called.
//
//*****************************************************************************
void
am_util_faultisr_collect_data(uint32_t u32IsrSP)
{
    volatile am_fault_t sFaultData;
    am_hal_mcuctrl_fault_t sHalFaultData = {0};
    
    uint32_t u32Mask = 0;

    //
    // Following is a brief overview of fault information provided by the M4.
    // More details can be found in the Cortex M4 User Guide.
    //
    // CFSR (Configurable Fault Status Reg) contains MMSR, BFSR, and UFSR:
    //   7:0    MMSR (MemManage)
    //          [0] IACCVIOL    Instr fetch from a location that does not
    //                          permit execution.
    //          [1] DACCVIOL    Data access violation flag. MMAR contains
    //                          address of the attempted access.
    //          [2] Reserved
    //          [3] MUNSTKERR   MemMange fault on unstacking for a return
    //                          from exception.
    //          [4] MSTKERR     MemMange fault on stacking for exception
    //                          entry.
    //          [5] MLSPERR     MemMange fault during FP lazy state
    //                          preservation.
    //          [6] Reserved
    //          [7] MMARVALID   MemManage Fault Addr Reg (MMFAR) valid flag.
    //  15:8    BusFault
    //          [0] IBUSERR     If set, instruction bus error.
    //          [1] PRECISERR   Data bus error. Stacked PC points to instr
    //                          that caused the fault.
    //          [2] IMPRECISERR Data bus error, but stacked return addr is not
    //                          related to the instr that caused the error and
    //                          BFAR is not valid.
    //          [3] UNSTKERR    Bus fault on unstacking for a return from
    //                          exception.
    //          [4] STKERR      Bus fault on stacking for exception entry.
    //          [5] LSPERR      Bus fault during FP lazy state preservation.
    //          [6] Reserved
    //          [7] BFARVALID   BFAR valid.
    //  31:16   UFSR (UsageFault)
    //          [0] UNDEFINSTR  Undefined instruction.
    //          [1] INVSTATE    Invalid state.
    //          [2] INVPC       Invalid PC load.
    //          [3] NOCP        No coprocessor.
    //        [7:4] Reserved
    //          [8] UNALIGNED   Unaligned access.
    //          [9] DIVBYZERO   Divide by zero.
    //      [15:10] Reserved
    //

    //
    // u32Mask is used for 2 things: 1) in the print loop, 2) as a spot to set
    // a breakpoint at the end of the routine.  If the printing is not used,
    // we'll get a compiler warning; so to avoid that warning, we'll use it
    // in a dummy assignment here.
    //
    sFaultData.u32CFSR = u32Mask;       // Avoid compiler warning
    sFaultData.u32CFSR = AM_REGVAL(AM_REG_SYSCTRL_CFSR_O);
    sFaultData.u8MMSR  = (sFaultData.u32CFSR >> 0)  & 0xff;
    sFaultData.u8BFSR  = (sFaultData.u32CFSR >> 8)  & 0xff;
    sFaultData.u16UFSR = (sFaultData.u32CFSR >> 16) & 0xffff;

    //
    // The address of the location that caused the fault.  e.g. if accessing an
    // invalid data location caused the fault, that address will appear here.
    //
    sFaultData.u32BFAR = AM_REGVAL(AM_REG_SYSCTRL_BFAR_O);

    //
    // The address of the instruction that caused the fault is the stacked PC
    // if BFSR bit1 is set.
    //
    sFaultData.u32FaultAddr = (sFaultData.u8BFSR & 0x02) ? getStackedReg(6, u32IsrSP) : 0xffffffff;

    //
    // Get the stacked registers.
    // Note - the address of the instruction that caused the fault is u32PC.
    //
    sFaultData.u32R0  = getStackedReg(0, u32IsrSP);
    sFaultData.u32R1  = getStackedReg(1, u32IsrSP);
    sFaultData.u32R2  = getStackedReg(2, u32IsrSP);
    sFaultData.u32R3  = getStackedReg(3, u32IsrSP);
    sFaultData.u32R12 = getStackedReg(4, u32IsrSP);
    sFaultData.u32LR  = getStackedReg(5, u32IsrSP);
    sFaultData.u32PC  = getStackedReg(6, u32IsrSP);
    sFaultData.u32PSR = getStackedReg(7, u32IsrSP);

    //
    // Use the HAL MCUCTRL functions to read the fault data.
    //
    am_hal_mcuctrl_fault_status(&sHalFaultData);
    

#ifdef AM_UTIL_FAULTISR_PRINT
    //
    // If printf has previously been initialized in the application, we should
    // be able to print out the fault information.
    //
    am_util_stdio_printf("Hard Fault stacked data:\n");
    am_util_stdio_printf("    R0  = 0x%08X\n", sFaultData.u32R0);
    am_util_stdio_printf("    R1  = 0x%08X\n", sFaultData.u32R1);
    am_util_stdio_printf("    R2  = 0x%08X\n", sFaultData.u32R2);
    am_util_stdio_printf("    R3  = 0x%08X\n", sFaultData.u32R3);
    am_util_stdio_printf("    R12 = 0x%08X\n", sFaultData.u32R12);
    am_util_stdio_printf("    LR  = 0x%08X\n", sFaultData.u32LR);
    am_util_stdio_printf("    PC  = 0x%08X\n", sFaultData.u32PC);
    am_util_stdio_printf("    PSR = 0x%08X\n", sFaultData.u32PSR);
    am_util_stdio_printf("Other Hard Fault data:\n");
    am_util_stdio_printf("    Fault address = 0x%08X\n", sFaultData.u32FaultAddr);
    am_util_stdio_printf("    BFAR (Bus Fault Addr Reg) = 0x%08X\n", sFaultData.u32BFAR);
    am_util_stdio_printf("    MMSR (Mem Mgmt Fault Status Reg) = 0x%02X\n", sFaultData.u8MMSR);
    am_util_stdio_printf("    BFSR (Bus Fault Status Reg) = 0x%02X\n", sFaultData.u8BFSR);
    am_util_stdio_printf("    UFSR (Usage Fault Status Reg) = 0x%04X\n", sFaultData.u16UFSR);

    //
    // Print out any bits set in the BFSR.
    //
    u32Mask = 0x80;
    while (u32Mask)
    {
        switch (sFaultData.u8BFSR & u32Mask)
        {
            case 0x80:
                am_util_stdio_printf("        BFSR bit7: BFARVALID\n");
                break;
            case 0x40:
                am_util_stdio_printf("        BFSR bit6: RESERVED\n");
                break;
            case 0x20:
                am_util_stdio_printf("        BFSR bit5: LSPERR\n");
                break;
            case 0x10:
                am_util_stdio_printf("        BFSR bit4: STKERR\n");
                break;
            case 0x08:
                am_util_stdio_printf("        BFSR bit3: UNSTKERR\n");
                break;
            case 0x04:
                am_util_stdio_printf("        BFSR bit2: IMPRECISERR\n");
                break;
            case 0x02:
                am_util_stdio_printf("        BFSR bit1: PRECISEERR\n");
                break;
            case 0x01:
                am_util_stdio_printf("        BFSR bit0: IBUSERR\n");
                break;
            default:
                break;
        }
        u32Mask >>= 1;
    }
    
    //
    // Print out any Apollo2 Internal fault information.
    //
    am_util_stdio_printf("Apollo2 Fault data:\n");
    if (sHalFaultData.bICODE)
    {
      am_util_stdio_printf("   ICODE Fault Address: 0x%08X\n", sHalFaultData.ui32ICODE);
    }
    if (sHalFaultData.bDCODE)
    {
      am_util_stdio_printf("   DCODE Fault Address: 0x%08X\n", sHalFaultData.ui32DCODE);
    }
    if (sHalFaultData.bSYS)
    {
      am_util_stdio_printf("   SYS Fault Address: 0x%08X\n", sHalFaultData.ui32SYS);
    }


#endif

    u32Mask = 0;

    //
    // Spin in an infinite loop.
    // We need to spin here inside the function so that we have access to
    // local data, i.e. sFaultData.
    //
    while(1)
    {
    }
}
