/*
 * This file is part of the MicroPython project, https://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2024 Alessandro Gatti
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "py/runtime.h"

#if defined(__ARM_ARCH_ISA_THUMB)

typedef enum _exception_kind_t {
    RESET = 1,
    NMI = 2,
    HARD_FAULT = 3,
    MEM_MANAGE = 4,
    BUS_FAULT = 5,
    USAGE_FAULT = 6,
    SV_CALL = 11,
    DEBUG_MONITOR = 12,
    PENDING_SV = 13,
    SYSTEM_TICK = 14,
} exception_kind_t;

static const char *EXCEPTION_NAMES_TABLE[] = {
    "Reserved",
    "Reset",
    "NMI",
    "HardFault",
    "MemManage",
    "BusFault",
    "UsageFault",
    "SVCall",
    "DebugMonitor",
    "PendSV",
    "SysTick",
    "External interrupt"
};

// R0-R15, PSR, Kind
uintptr_t registers_copy[18] = { 0 };

#define BIT(v, b) (((v) & (1U << (b))) != 0)

__attribute__((naked)) MP_NORETURN void exception_handler(uintptr_t kind) {
    // Save registers
    __asm volatile (
        "ldr r1, =registers_copy \n"
        "str r0, [r1, #68]       \n" // Kind
        "ldr r0, [sp, #0]        \n" // R0
        "str r0, [r1, #0]        \n"
        "ldr r0, [sp, #4]        \n" // R1
        "str r0, [r1, #4]        \n"
        "ldr r0, [sp, #8]        \n" // R2
        "str r0, [r1, #8]        \n"
        "ldr r0, [sp, #12]       \n" // R3
        "str r0, [r1, #12]       \n"
        "str r4, [r1, #16]       \n"
        "str r5, [r1, #20]       \n"
        "str r6, [r1, #24]       \n"
        "str r7, [r1, #28]       \n"
        "mov r0, r8              \n"
        "str r0, [r1, #32]       \n"
        "mov r0, r9              \n"
        "str r0, [r1, #36]       \n"
        "mov r0, r10             \n"
        "str r0, [r1, #40]       \n"
        "mov r0, r11             \n"
        "str r0, [r1, #44]       \n"
        "ldr r0, [sp, #16]       \n" // R12
        "str r0, [r1, #48]       \n"
        "mov r0, sp              \n"
        "sub r0, #32             \n"
        "str r0, [r1, #52]       \n"
        "ldr r0, [sp, #20]       \n" // R14
        "str r0, [r1, #56]       \n"
        "ldr r0, [sp, #24]       \n" // R15
        "str r0, [r1, #60]       \n"
        "ldr r0, [sp, #28]       \n" // xPSR
        "str r0, [r1, #64]       \n"
        :
        :
        : "memory"
        );

    uintptr_t saved_kind = registers_copy[17];

    switch (saved_kind) {
        case RESET:
        case NMI:
        case HARD_FAULT:
        case MEM_MANAGE:
        case BUS_FAULT:
        case USAGE_FAULT:
        case SV_CALL:
        case DEBUG_MONITOR:
        case PENDING_SV:
        case SYSTEM_TICK:
            printf(EXCEPTION_NAMES_TABLE[saved_kind]);
            break;
        default:
            if (saved_kind >= 16) {
                printf("%s %d", EXCEPTION_NAMES_TABLE[11], saved_kind - 16);
            } else {
                printf(EXCEPTION_NAMES_TABLE[0]);
            }
            break;
    }
    printf(" exception caught:\n\n");
    printf("CPU registers:\n");
    printf("R0:   %08X R1:   %08X R2:   %08X R3:   %08X\n", registers_copy[0], registers_copy[1], registers_copy[2], registers_copy[3]);
    printf("R4:   %08X R5:   %08X R6:   %08X R7:   %08X\n", registers_copy[4], registers_copy[5], registers_copy[6], registers_copy[7]);
    printf("R8:   %08X R9:   %08X R10:  %08X R11:  %08X\n", registers_copy[8], registers_copy[9], registers_copy[10], registers_copy[11]);
    printf("R12:  %08X R13:  %08X R14:  %08X R15:  %08X\n", registers_copy[12], registers_copy[13], registers_copy[14], registers_copy[15]);
    printf("xPSR: %08X\n", registers_copy[16]);

    #if __ARM_ARCH == 7
    uint32_t cfsr = *(volatile uint32_t *)0xE000ED28;
    uint32_t hfsr = *(volatile uint32_t *)0xE000ED2C;
    uint32_t dfsr = *(volatile uint32_t *)0xE000ED30;
    uint32_t mmfar = *(volatile uint32_t *)0xE000ED34;
    uint32_t bfar = *(volatile uint32_t *)0xE000ED38;

    printf("\nDebug registers:\n");
    printf("CFSR: %08lX HFSR: %08lX DFSR: %08lX MMFAR: %08lX BFAR: %08lX\n", cfsr, hfsr, dfsr, mmfar, bfar);
    printf("CFSR.IACCVIOL:    %d CFSR.DACCVIOL:   %d CFSR.MUNSTKERR: %d CFSR.MSTKERR:   %d\n", BIT(cfsr, 0), BIT(cfsr, 1), BIT(cfsr, 3), BIT(cfsr, 4));
    printf("CFSR.MLSPERR:     %d CFSR.MMARVALID:  %d CFSR.IBUSERR:   %d CFSR.PRECISERR: %d\n", BIT(cfsr, 5), BIT(cfsr, 7), BIT(cfsr, 8), BIT(cfsr, 9));
    printf("CFSR.IMPRECISERR: %d CFSR.UNSTKERR:   %d CFSR.STKERR:    %d CFSR.LSPERR:    %d\n", BIT(cfsr, 10), BIT(cfsr, 11), BIT(cfsr, 12), BIT(cfsr, 13));
    printf("CFSR.BFARVALID:   %d CFSR.UNDEFINSTR: %d CFSR.INVSTATE:  %d CFSR.INVPC:     %d\n", BIT(cfsr, 15), BIT(cfsr, 16), BIT(cfsr, 17), BIT(cfsr, 18));
    printf("CFSR.NOCP:        %d CFSR.UNALIGNED:  %d CFSR.DIVBYZERO: %d\n", BIT(cfsr, 19), BIT(cfsr, 24), BIT(cfsr, 25));
    printf("HFSR.VECTTBL:     %d HFSR.FORCED:     %d HFSR.DEBUGEVT:  %d\n", BIT(hfsr, 1), BIT(hfsr, 30), BIT(hfsr, 31));
    printf("DFSR.HALTED:      %d DFSR.BKPT:       %d DFSR.DWTTRAP:   %d DFSR.VCATCH:    %d\n", BIT(dfsr, 0), BIT(dfsr, 1), BIT(dfsr, 2), BIT(dfsr, 3));
    printf("DFSR.EXTERNAL:    %d\n", BIT(dfsr, 4));
    #endif

    for (;;) {}
}

__attribute__((naked)) MP_NORETURN void NMI_Handler(void) {
    exception_handler(NMI);
}

__attribute__((naked)) MP_NORETURN void HardFault_Handler(void) {
    exception_handler(HARD_FAULT);
}

__attribute__((naked)) MP_NORETURN void MemManage_Handler(void) {
    exception_handler(MEM_MANAGE);
}

__attribute__((naked)) MP_NORETURN void BusFault_Handler(void) {
    exception_handler(BUS_FAULT);
}

__attribute__((naked)) MP_NORETURN void UsageFault_Handler(void) {
    exception_handler(USAGE_FAULT);
}

__attribute__((naked)) MP_NORETURN void SVC_Handler(void) {
    exception_handler(SV_CALL);
}

__attribute__((naked)) MP_NORETURN void DebugMon_Handler(void) {
    exception_handler(DEBUG_MONITOR);
}

__attribute__((naked)) MP_NORETURN void PendSV_Handler(void) {
    exception_handler(PENDING_SV);
}

__attribute__((naked, weak)) MP_NORETURN void SysTick_Handler(void) {
    exception_handler(SYSTEM_TICK);
}

#endif
