/* crash.c
Copyright 2021 Carl John Kugler III

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.
*/

#include <string.h>
#include <time.h>
//
#include "pico/stdlib.h"
#include "hardware/sync.h"
#if !PICO_RISCV
#  if PICO_RP2040
#    include "RP2040.h"
#  endif
#  if PICO_RP2350
#    include "RP2350.h"
#  endif
#else
#  include "hardware/watchdog.h"
#endif
//
#include "crc.h"
#include "my_debug.h"
#include "my_rtc.h"
#include "util.h"
//
#include "crash.h"

#if defined(NDEBUG) || !USE_DBG_PRINTF
#  pragma GCC diagnostic ignored "-Wunused-variable"
#endif

static volatile crash_info_t crash_info_ram __attribute__((section(".uninitialized_data")));

static crash_info_t volatile *crash_info_ram_p = &crash_info_ram;

static crash_info_t previous_crash_info;
static bool _previous_crash_info_valid = false;

__attribute__((noreturn, always_inline))
static inline void reset() {
//    if (debugger_connected()) {
        __breakpoint();
//    } else {
#if !PICO_RISCV
        NVIC_SystemReset();
#else
        watchdog_reboot(0, 0, 0);
        for (;;) {
            __nop();
        }
#endif
//    }
    __builtin_unreachable();
}

void crash_handler_init() {
    if (crash_info_ram.magic == crash_magic_hard_fault ||
        crash_info_ram.magic == crash_magic_stack_overflow ||
        crash_info_ram.magic == crash_magic_reboot_requested ||
        crash_info_ram.magic == crash_magic_assert ||
        crash_info_ram.magic == crash_magic_debug_mon) {
        uint8_t xor_checksum = crc7((uint8_t *)crash_info_ram_p,
                offsetof(crash_info_t, xor_checksum));
        if (xor_checksum == crash_info_ram.xor_checksum) {
            // valid crash record
            memcpy(&previous_crash_info, (void *)crash_info_ram_p, sizeof previous_crash_info);
            _previous_crash_info_valid = true;
        }
    }
    memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
}

const crash_info_t *crash_handler_get_info() {
    if (_previous_crash_info_valid) {
        return &previous_crash_info;
    }
    return NULL;
}


__attribute__((noreturn))
void system_reset_func(char const *const func) {
    memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
    crash_info_ram.magic = crash_magic_reboot_requested;
    crash_info_ram.timestamp = epochtime;
    snprintf((char *)crash_info_ram.calling_func, sizeof crash_info_ram.calling_func, "%s",
             func);
    crash_info_ram.xor_checksum =
        crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
    __dsb();

    reset();
    __builtin_unreachable();
}

__attribute__((noreturn))
void capture_assert(const char *file, int line, const char *func, const char *pred) {
    memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
    crash_info_ram.magic = crash_magic_assert;
    crash_info_ram.timestamp = epochtime;

    // If the filename is too long, take the end:
    size_t full_len = strlen(file) + 1;
    size_t offset = 0;
    if (full_len > sizeof crash_info_ram.assert.file) {
        offset = full_len - sizeof crash_info_ram.assert.file;
    }
    snprintf((char *)crash_info_ram.assert.file, sizeof crash_info_ram.assert.file, "%s",
             file + offset);
    snprintf((char *)crash_info_ram.assert.func, sizeof crash_info_ram.assert.func, "%s", func);
    snprintf((char *)crash_info_ram.assert.pred, sizeof crash_info_ram.assert.pred, "%s", pred);
    crash_info_ram.assert.line = line;
    crash_info_ram.xor_checksum =
        crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
    __dsb();

    reset();
    __builtin_unreachable();
}

#if !PICO_RISCV

__attribute__((used)) extern void DebugMon_HandlerC(uint32_t const *faultStackAddr) {
    memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
    crash_info_ram.magic = crash_magic_debug_mon;
    crash_info_ram.timestamp = epochtime;

    /* Stores general registers */
    crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos];
    crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos];
    crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos];
    crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos];
    crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos];
    crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos];
    crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos];
    crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos];
    ///* Stores the Configurable Fault Status Register state with the fault cause */
    //crash_info_ram.cy_faultFrame.cfsr.cfsrReg = SCB->CFSR;
    ///* Stores the Hard Fault Status Register */
    //crash_info_ram.cy_faultFrame.hfsr.hfsrReg = SCB->HFSR;
    ///* Stores the System Handler Control and State Register */
    //crash_info_ram.cy_faultFrame.shcsr.shcsrReg = SCB->SHCSR;
    ///* Store MemMange fault address */
    //crash_info_ram.cy_faultFrame.mmfar = SCB->MMFAR;
    ///* Store Bus fault address */
    //crash_info_ram.cy_faultFrame.bfar = SCB->BFAR;

    volatile uint8_t __attribute__((unused)) watchpoint_number = 0;
    // if (DWT->FUNCTION0 & DWT_FUNCTION_MATCHED_Msk) {
    //    watchpoint_number = 0;
    //} else if (DWT->FUNCTION1 & DWT_FUNCTION_MATCHED_Msk) {
    //    watchpoint_number = 1;
    //} else if (DWT->FUNCTION2 & DWT_FUNCTION_MATCHED_Msk) {
    //    watchpoint_number = 2;
    //}
    crash_info_ram.xor_checksum =
        crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
    __dsb();  // make sure all data is really written into the memory before
              // doing a reset

    reset();
}

extern void DebugMon_Handler(void);
__attribute__((naked)) void DebugMon_Handler(void) {
    __asm volatile(
        " movs r0,#4 \n"
        " movs r1, lr \n"
        " tst r0, r1 \n"
        " beq _MSP2 \n"
        " mrs r0, psp \n"
        " b _HALT2 \n"
        "_MSP2: \n"
        " mrs r0, msp \n"
        "_HALT2: \n"
        " ldr r1,[r0,#20] \n"
        " b DebugMon_HandlerC \n");
}

void Hardfault_HandlerC(uint32_t const *faultStackAddr) {
    memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
    crash_info_ram.magic = crash_magic_hard_fault;
    crash_info_ram.timestamp = epochtime;

    /* Stores general registers */
    crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos];
    crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos];
    crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos];
    crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos];
    crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos];
    crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos];
    crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos];
    crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos];

    crash_info_ram.xor_checksum =
        crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
    __dsb();  // make sure all data is really written into the memory before
              // doing a reset

    reset();
}
__attribute__((naked)) void isr_hardfault(void) {
    __asm volatile(
        " movs r0,#4 \n"
        " movs r1, lr \n"
        " tst r0, r1 \n"
        " beq _MSP3 \n"
        " mrs r0, psp \n"
        " b _HALT3 \n"
        "_MSP3: \n"
        " mrs r0, msp \n"
        "_HALT3: \n"
        " ldr r1,[r0,#20] \n"
        " b Hardfault_HandlerC \n");
}

#endif // !PICO_RISCV

enum {
    crash_info_magic,
    crash_info_hf_lr,
    crash_info_hf_pc,
    crash_info_reset_request_line,
    crash_info_assert
};

int dump_crash_info(crash_info_t const *const crash_info_p, int next, char *const buf,
                    size_t const buf_sz) {
    int nwrit = 0;
    switch (next) {
        case crash_info_magic:
            nwrit = snprintf(buf, buf_sz, "Event: ");
            switch (crash_info_p->magic) {
                case crash_magic_none:
                    nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tNone.");
                    next = 0;
                    break;
                case crash_magic_bootloader_entry:
                    nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tBootloader Entry.");
                    next = 0;
                    break;
                case crash_magic_hard_fault:
                    nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tCM4 Hard Fault.");
                    next = crash_info_hf_lr;
                    break;
                case crash_magic_debug_mon:
                    nwrit += snprintf(buf + nwrit, buf_sz - nwrit,
                                       "\tDebug Monitor Watchpoint Triggered.");
                    next = crash_info_hf_lr;
                    break;
                case crash_magic_reboot_requested:
                    nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tReboot Requested.");
                    next = crash_info_reset_request_line;
                    break;
                case crash_magic_assert:
                    nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tAssertion Failed.");
                    next = crash_info_assert;
                    break;
                default:
                    DBG_ASSERT_CASE_NOT(crash_info_p->magic);
                    next = 0;
            }
            {
                struct tm tmbuf;
                struct tm *ptm = localtime_r(&crash_info_p->timestamp, &tmbuf);
                char tsbuf[32];
                size_t n = strftime(tsbuf, sizeof tsbuf, "\n\tTime: %Y-%m-%d %H:%M:%S\n", ptm);
                myASSERT(n);
                nwrit = snprintf(buf + nwrit, buf_sz - nwrit, "%s", tsbuf);
            }
            break;
        case crash_info_hf_lr:
            nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tLink Register (LR): %p\n",
                              (void *)crash_info_p->cy_faultFrame.lr);
            next = crash_info_hf_pc;
            break;
        case crash_info_hf_pc:
            nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tProgram Counter (PC): %p\n",
                              (void *)crash_info_p->cy_faultFrame.pc);
            next = 0;
            break;
        case crash_info_reset_request_line:
            nwrit +=
                snprintf(buf + nwrit, buf_sz - nwrit, "\tReset request calling function: %s\n",
                         crash_info_p->calling_func);
            next = 0;
            break;
        case crash_info_assert:
            nwrit += snprintf(buf + nwrit, buf_sz - nwrit,
                              "\tAssertion \"%s\" failed: file \"%s\", line "
                              "%d, function: %s\n",
                              crash_info_p->assert.pred, crash_info_p->assert.file,
                              crash_info_p->assert.line, crash_info_p->assert.func);
            next = 0;
            break;
        default:
            ASSERT_CASE_NOT(crash_info_p->magic);
    }
    return next;
}

/* [] END OF FILE */
