/**
 * @file exception.h
 * @author Jennifer Taylor <dragonminded@dragonminded.com>
 * @author Giovanni Bajo <giovannibajo@gmail.com>
 * @author thekovic <https://github.com/thekovic>
 * @brief Exception Handler
 * @ingroup exceptions
 */
#ifndef __LIBDRAGON_EXCEPTION_H
#define __LIBDRAGON_EXCEPTION_H

#include <stdint.h>
#include <stdbool.h>

/**
 * @defgroup exceptions Exception Handler
 * @ingroup lowlevel
 * @brief Handle hardware-generated exceptions.
 *
 * The exception handler traps exceptions generated by hardware.  This could
 * be an invalid instruction or invalid memory access exception or it could
 * be a reset exception.  In both cases, a handler registered with 
 * #register_exception_handler will be passed information regarding the
 * exception type and relevant registers.
 *
 * @{
 */

/**
 * @brief Exception types
 */
enum
{
    /** @brief Unknown exception */
    EXCEPTION_TYPE_UNKNOWN = 0,
    /** @brief Reset exception */
    EXCEPTION_TYPE_RESET,
    /** @brief Critical exception */
    EXCEPTION_TYPE_CRITICAL,
    /** @brief Syscall exception*/
    EXCEPTION_TYPE_SYSCALL,
};

/**
 * @brief Exception codes
 */
typedef enum {
    EXCEPTION_CODE_INTERRUPT = 0,
    EXCEPTION_CODE_TLB_MODIFICATION = 1,
    EXCEPTION_CODE_TLB_LOAD_I_MISS = 2,
    EXCEPTION_CODE_TLB_STORE_MISS = 3,
    EXCEPTION_CODE_LOAD_I_ADDRESS_ERROR = 4,
    EXCEPTION_CODE_STORE_ADDRESS_ERROR = 5,
    EXCEPTION_CODE_I_BUS_ERROR = 6,
    EXCEPTION_CODE_D_BUS_ERROR = 7,
    EXCEPTION_CODE_SYS_CALL = 8,
    EXCEPTION_CODE_BREAKPOINT = 9,
    EXCEPTION_CODE_RESERVED_INSTRUCTION = 10,
    EXCEPTION_CODE_COPROCESSOR_UNUSABLE = 11,
    EXCEPTION_CODE_ARITHMETIC_OVERFLOW = 12,
    EXCEPTION_CODE_TRAP = 13,
    EXCEPTION_CODE_FLOATING_POINT = 15,
    EXCEPTION_CODE_WATCH = 23,
    EXCEPTION_CODE_EMUX = 24,
} exception_code_t;

/**
 * @brief Structure representing a register block
 *
 * DO NOT modify the order unless editing inthandler.S
 */
typedef struct __attribute__((packed))
{
    /** @brief General purpose registers 1-32 */
    union {
        uint64_t gpr[32];
        struct {
            uint64_t zr, at, v0, v1, a0, a1, a2, a3;
            uint64_t t0, t1, t2, t3, t4, t5, t6, t7;
            uint64_t s0, s1, s2, s3, s4, s5, s6, s7;
            uint64_t t8, t9, k0, k1, gp, sp, fp, ra;
        };
    };
    /** @brief HI */
    uint64_t hi;
    /** @brief LO */
    uint64_t lo;
    /** @brief SR */
    uint32_t sr;
    /** @brief CR (NOTE: can't modify this from an exception handler) */
    uint32_t cr;
    /**
     * @brief represents EPC - COP0 register $14
     *
     * The coprocessor 0 (system control coprocessor - COP0) register $14 is the
     * return from exception program counter. For asynchronous exceptions it points
     * to the place to continue execution whereas for synchronous (caused by code)
     * exceptions, point to the instruction causing the fault condition, which
     * needs correction in the exception handler. This member is for reading/writing
     * its value.
     * */
    uint32_t epc;
    /** @brief FC31 */
    uint32_t fc31;
    /** @brief Floating point registers 1-32 */
    uint64_t fpr[32];
} reg_block_t;

/* Make sure the structure has the right size. Please keep this in sync with inthandler.S */
_Static_assert(sizeof(reg_block_t) == 544, "invalid reg_block_t size -- this must match inthandler.S");

/**
 * @brief Structure representing an exception
 */
typedef struct
{
    /** 
     * @brief Exception type
     * @see #EXCEPTION_TYPE_RESET, #EXCEPTION_TYPE_CRITICAL
     */
    int32_t type;
    /**
     * @brief Underlying exception code
     */
    exception_code_t code;
    /** @brief String information of exception */
    const char* info;
    /** @brief Registers at point of exception */
    reg_block_t* regs;
} exception_t;

/** @} */

#ifdef __cplusplus
extern "C" {
#endif

/** 
 * @brief Generic exception handler
 * 
 * This is the type of a handler that can be registered using #register_exception_handler.
 * It is associated to all unhandled exceptions that are not otherwise handled by libdragon.
 * 
 * @param exc Exception information
 */
typedef void (*exception_handler_t)(exception_t *exc);

/** 
 * @brief Syscall handler
 * 
 * This is the type of a handler of a syscall exception.
 * 
 * @param exc Exception information
 * @param code Syscall code
 */
typedef void (*syscall_handler_t)(exception_t *exc, uint32_t code);

/**
 * @brief Register an exception handler to handle exceptions
 *
 * The registered handle is responsible for clearing any bits that may cause
 * a re-trigger of the same exception and updating the EPC. An important
 * example is the cause bits (12-17) of FCR31 from cop1. To prevent
 * re-triggering the exception they should be cleared by the handler.
 *
 * To manipulate the registers, update the values in the exception_t struct.
 * They will be restored to appropriate locations when returning from the
 * handler. Setting them directly will not work as expected as they will get
 * overwritten with the values pointed by the struct.
 *
 * There is only one exception to this, cr (cause register) which is also
 * modified by the int handler before the saved values are restored thus it
 * is only possible to update it through C0_WRITE_CR macro if it is needed.
 * This shouldn't be necessary though as they are already handled by the
 * library.
 *
 * k0 ($26), k1 ($27) are not saved/restored and will not be available in the
 * handler. Theoretically we can exclude s0-s7 ($16-$23), and gp ($28) to gain
 * some performance as they are already saved by GCC when necessary. The same
 * is true for sp ($29) and ra ($31) but current interrupt handler manipulates
 * them via allocating a new stack and doing a jal. Similarly floating point
 * registers f21-f31 are callee-saved. In the future we may consider removing
 * them from the save state for interrupts (but not for exceptions)
 *
 * @param[in] cb
 *            Callback function to call when exceptions happen
 */
exception_handler_t register_exception_handler( exception_handler_t cb );

/**
 * @brief Default exception handler.
 * 
 * This handler is installed by default for all exceptions. It initializes
 * the console and dump the exception state to the screen, including the value
 * of all GPR/FPR registers. It then calls abort() to abort execution.
 */
void exception_default_handler( exception_t* ex );

/**
 * @brief Check if we are currently running within an exception handler
 * 
 * @return true if an exception handler is currently running, false otherwise.
 */
inline bool exception_is_running(void) 
{
	extern void* interrupt_exception_frame;
	return interrupt_exception_frame != 0;
}

/**
 * @brief Register a handler that will be called when a syscall exception
 * 
 * This function allows to register a handler to be invoked in response to a
 * syscall exception, generated by the SYSCALL opcode. The opcode allows to
 * specify a 20-bit code which, in a more traditional operating system architecture,
 * corresponds to the "service" to be called.
 * 
 * When the registered handler returns, the execution will resume from the
 * instruction following the syscall one.
 * 
 * To allow for different usages of the code field, this function accepts
 * a range of codes to associated with the handler. This allows a single handler
 * to be invoked for multiple different codes, to specialize services.
 * 
 * @note Syscall codes in the range 0x00000 - 0x0FFFF are reserved to libdragon
 * itself. Use a code outside that range to avoid conflicts with future versions
 * of libdragon.
 * 
 * @param handler  		Handler to invoke when a syscall exception is triggered
 * @param first_code 	First syscall code to associate with this handler (begin of range)
 * @param last_code 	Last syscall code to associate with this handler (end of range)
 */
void register_syscall_handler( syscall_handler_t handler, uint32_t first_code, uint32_t last_code );

#ifdef __cplusplus
}
#endif

#endif
