/*----------------------------------------------------------------------------
 * Tencent is pleased to support the open source community by making TencentOS
 * available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 * If you have downloaded a copy of the TencentOS binary from Tencent, please
 * note that the TencentOS binary is licensed under the BSD 3-Clause License.
 *
 * If you have downloaded a copy of the TencentOS source code from Tencent,
 * please note that TencentOS source code is licensed under the BSD 3-Clause
 * License, except for the third-party components listed below which are
 * subject to different license terms. Your integration of TencentOS into your
 * own projects may require compliance with the BSD 3-Clause License, as well
 * as the other licenses applicable to the third-party components included
 * within TencentOS.
 *---------------------------------------------------------------------------*/

#ifndef _TOS_FAULT_H_
#define  _TOS_FAULT_H_

#if TOS_CFG_FAULT_BACKTRACE_EN > 0u

typedef int (*k_fault_log_writer_t)(const char *format, ...);

#define K_FAULT_STACK_DUMP_DEPTH                10u

#define K_FAULT_CALL_STACK_BACKTRACE_DEPTH          5u

#if defined (TOS_CFG_CPU_ARM_FPU_EN) && (TOS_CFG_CPU_ARM_FPU_EN == 1U)
typedef struct fault_fpu_frame_st {
    cpu_data_t s0;
    cpu_data_t s1;
    cpu_data_t s2;
    cpu_data_t s3;
    cpu_data_t s4;
    cpu_data_t s5;
    cpu_data_t s6;
    cpu_data_t s7;
    cpu_data_t s8;
    cpu_data_t s9;
    cpu_data_t s10;
    cpu_data_t s11;
    cpu_data_t s12;
    cpu_data_t s13;
    cpu_data_t s14;
    cpu_data_t s15;
    cpu_data_t fpscr;
} fault_fpu_frame_t;
#endif

typedef struct fault_cpu_frame_st {
    cpu_data_t r0;
    cpu_data_t r1;
    cpu_data_t r2;
    cpu_data_t r3;
    cpu_data_t r12;
    cpu_data_t lr;
    cpu_data_t pc;
    cpu_data_t spsr;
} fault_cpu_frame_t;

typedef struct fault_exc_frame_st {
    fault_cpu_frame_t cpu_frame;

#if defined (TOS_CFG_CPU_ARM_FPU_EN) && (TOS_CFG_CPU_ARM_FPU_EN == 1U)
    fault_fpu_frame_t fpu_frame;
#endif
} fault_exc_frame_t;

/**
 * information we need to do fault backtrace
 */
typedef struct fault_information_st {
    int is_thumb : 1;           /**< whether it is thumb we use when we fall into fault? */
    int is_on_task : 1;         /**< whether we are on a task when fall into fault? */
    int is_stk_ovrf : 1;        /**< whether we get a stack overflow */

#if defined (TOS_CFG_CPU_ARM_FPU_EN) && (TOS_CFG_CPU_ARM_FPU_EN == 1U)
    int is_ext_stk_frm : 1;     /**< whether it is a extended stack frame?(whether the cpu has pushed fpu registers onto the stack) */
#endif

    cpu_addr_t pc;              /**< just where fault happens */

    cpu_addr_t sp_before_fault; /**< original sp just before the cpu push the fault exception frame */

    /**
     * we need main_stack_start & main_stack_limit to do call stack backtrace
     * when we fall into fault during a task, we should do the call stack backtrace on the task's stack
     * but if not, which means we are in kernel, we should do the call stack backtrace on the main stack
     * in arm v7-m, this should be the MSP's start and limit
     * in arm v7-a, call stack backtrace is another story(much more elegant because we have FP).
     */
    cpu_addr_t stack_start;     /**< current sp start address we use. if on task, it'll be the task's stack, otherwise it'll be the msp */
    cpu_addr_t stack_limit;     /**< current sp limit address */
    cpu_addr_t code_start;      /**< current code start address */
    cpu_addr_t code_limit;      /**< current code limit address */
} fault_info_t;

#if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050))

#define DEFAULT_CODE_SECTION_NAME       ER_IROM1
#define DEFAULT_CSTACK_SECTION_NAME     STACK

#define SECTION_START(_name_)                _name_##$$Base
#define SECTION_END(_name_)                  _name_##$$Limit
#define IMAGE_SECTION_START(_name_)          Image$$##_name_##$$Base
#define IMAGE_SECTION_END(_name_)            Image$$##_name_##$$Limit
#define CSTACK_BLOCK_START(_name_)           SECTION_START(_name_)
#define CSTACK_BLOCK_END(_name_)             SECTION_END(_name_)
#define CODE_SECTION_START(_name_)           IMAGE_SECTION_START(_name_)
#define CODE_SECTION_END(_name_)             IMAGE_SECTION_END(_name_)

extern const int CSTACK_BLOCK_START(DEFAULT_CSTACK_SECTION_NAME);
extern const int CSTACK_BLOCK_END(DEFAULT_CSTACK_SECTION_NAME);
extern const int CODE_SECTION_START(DEFAULT_CODE_SECTION_NAME);
extern const int CODE_SECTION_END(DEFAULT_CODE_SECTION_NAME);

__STATIC_INLINE__ cpu_addr_t fault_code_start(void)
{
    return (cpu_addr_t)&CODE_SECTION_START(DEFAULT_CODE_SECTION_NAME);
}

__STATIC_INLINE__ cpu_addr_t fault_code_limit(void)
{
    return (cpu_addr_t)&CODE_SECTION_END(DEFAULT_CODE_SECTION_NAME);
}

__STATIC_INLINE__ cpu_addr_t fault_msp_start(void)
{
    return (cpu_addr_t)&CSTACK_BLOCK_START(DEFAULT_CSTACK_SECTION_NAME);
}

__STATIC_INLINE__ cpu_addr_t fault_msp_limit(void)
{
    return (cpu_addr_t)&CSTACK_BLOCK_END(DEFAULT_CSTACK_SECTION_NAME);
}

#elif defined(__ICCARM__)

#define DEFAULT_CODE_SECTION_NAME       ".text"
#define DEFAULT_CSTACK_SECTION_NAME     "CSTACK"

#pragma section=DEFAULT_CSTACK_SECTION_NAME
#pragma section=DEFAULT_CODE_SECTION_NAME

__STATIC_INLINE__ cpu_addr_t fault_code_start(void)
{
    return (cpu_addr_t)__section_begin(DEFAULT_CODE_SECTION_NAME);
}

__STATIC_INLINE__ cpu_addr_t fault_code_limit(void)
{
    return (cpu_addr_t)__section_end(DEFAULT_CODE_SECTION_NAME);
}

__STATIC_INLINE__ cpu_addr_t fault_msp_start(void)
{
    return (cpu_addr_t)__section_begin(DEFAULT_CSTACK_SECTION_NAME);
}

__STATIC_INLINE__ cpu_addr_t fault_msp_limit(void)
{
    return (cpu_addr_t)__section_end(DEFAULT_CSTACK_SECTION_NAME);
}

#elif defined(__GNUC__)

/**
 * if we are using keil(armcc) or mdk(iccarm), we probably use the defult link script supplied by the IDE.
 * the way to locate the text/stack section start and limit is to find them in default link script.
 * but if we build our project by makefile(or something like scons, cmake, etc), we probably need to write
 * our own link scrpit, if so, we should do like this(just a demo):
 *
    _stext = .;
    .text : {
        *(.text.startup)
        *(.text)
        *(.text.*)
    }
    _etext = .;

     __bss_start = .;
    .bss : {
        *(.bss)
        *(.bss.*)
        *(COMMON)
        _sstack = .;
        *(.cstack)
        _estack = .;
    }
    __bss_end = .;
 * by this, we can locate text/stack section start and limit by _stext/_etext and _sstack/_estack
 */
#define DEFAULT_CODE_SECTION_START      _stext
#define DEFAULT_CODE_SECTION_END        _etext
#define DEFAULT_CSTACK_SECTION_START    _sstack
#define DEFAULT_CSTACK_SECTION_END      _estack

extern const int DEFAULT_CODE_SECTION_START;
extern const int DEFAULT_CODE_SECTION_END;

extern const int DEFAULT_CSTACK_SECTION_START;
extern const int DEFAULT_CSTACK_SECTION_END;

__STATIC_INLINE__ cpu_addr_t fault_code_start(void)
{
    return (cpu_addr_t)(&(DEFAULT_CODE_SECTION_START));
}

__STATIC_INLINE__ cpu_addr_t fault_code_limit(void)
{
    return (cpu_addr_t)(&(DEFAULT_CODE_SECTION_END));
}

__STATIC_INLINE__ cpu_addr_t fault_msp_start(void)
{
    return (cpu_addr_t)(&(DEFAULT_CSTACK_SECTION_START));
}

__STATIC_INLINE__ cpu_addr_t fault_msp_limit(void)
{
    return (cpu_addr_t)(&(DEFAULT_CSTACK_SECTION_END));
}

#endif

__API__ void tos_fault_log_writer_set(k_fault_log_writer_t log_writer);

__KNL__ int fault_default_log_writer(const char *format, ...);

__KNL__ void fault_backtrace(cpu_addr_t lr, fault_exc_frame_t *frame);

#endif

#endif /* _TOS_FAULT_H_ */

