/* =============================================================================
 * rtos_hooks.c - FreeRTOS 후크/정적 메모리 제공자
 *
 * FreeRTOSConfig.h 에서 켜진 후크들을 구현한다:
 *   - configCHECK_FOR_STACK_OVERFLOW=2 -> vApplicationStackOverflowHook
 *   - configUSE_MALLOC_FAILED_HOOK=1   -> vApplicationMallocFailedHook
 *   - configUSE_IDLE_HOOK=1            -> vApplicationIdleHook (IWDG refresh)
 *   - configSUPPORT_STATIC_ALLOCATION=1-> vApplicationGetIdleTaskMemory /
 *                                          vApplicationGetTimerTaskMemory
 *   - configASSERT -> vAssertCalled
 *
 * 치명적 오류(스택 오버플로/malloc 실패/assert) 처리 전략:
 *   인터럽트를 끄고, 디버거가 붙어 있으면 정지(BKPT), 아니면 무한 루프에 머문다.
 *   이때 IWDG refresh 가 멈추므로 워치독이 MCU 를 안전하게 리셋한다.
 *   가능하면 bsp_fatal() 로 LED 점멸/로그를 남긴다(약결합 weak fallback 제공).
 * ===========================================================================*/

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include <stdint.h>

#include "bsp.h"        /* bsp_fatal(const char*) — 안전한 오류 표시기 */
#include "watchdog.h"   /* watchdog_refresh() — idle 후크에서 호출 */

/* -----------------------------------------------------------------------------
 * bsp_fatal 약결합 fallback.
 *  bsp.c 가 실제 구현(LED 점멸 + 디버그 정지)을 제공한다. 단위/호스트 테스트나
 *  bsp 미링크 환경에서도 본 파일이 단독으로 빌드되도록 weak 기본 구현을 둔다.
 *  실제 펌웨어 링크 시에는 bsp.c 의 강한 심볼이 이 정의를 대체한다.
 * ---------------------------------------------------------------------------*/
__attribute__((weak)) void bsp_fatal(const char *reason)
{
    (void)reason;

    /* 모든 인터럽트 차단 (FromISR 경로 포함). 이후 워치독만이 복구 경로. */
    taskDISABLE_INTERRUPTS();

#if defined(__GNUC__) && (defined(__ARM_ARCH) || defined(__arm__))
    /* 디버거가 붙어 있으면 정지(C_DEBUGEN 비트). 아니면 그냥 통과 후 루프. */
    volatile uint32_t *const dhcsr = (volatile uint32_t *)0xE000EDF0u;
    if ((*dhcsr & 0x1u) != 0u) {
        __asm volatile ("bkpt #0");
    }
#endif

    /* 무한 루프 — watchdog_refresh() 가 호출되지 않으므로 IWDG 가 리셋시킨다. */
    for (;;) {
        /* 의도적으로 비움. */
    }
}

/* =============================================================================
 * 스택 오버플로 후크 (configCHECK_FOR_STACK_OVERFLOW=2)
 *  스케줄러가 컨텍스트 스위치 시 스택 한계를 넘었다고 판단하면 호출한다.
 *  태스크 이름을 reason 으로 넘겨 bsp_fatal 로 이관.
 * ===========================================================================*/
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    (void)xTask;
    bsp_fatal(pcTaskName ? pcTaskName : "stack-overflow");
}

/* =============================================================================
 * malloc 실패 후크 (configUSE_MALLOC_FAILED_HOOK=1)
 *  heap_4 의 pvPortMalloc 이 실패하면 호출. 힙 고갈/단편화 -> 안전 리셋.
 * ===========================================================================*/
void vApplicationMallocFailedHook(void)
{
    bsp_fatal("malloc-failed");
}

/* =============================================================================
 * Idle 후크 (configUSE_IDLE_HOOK=1)
 *  Idle 태스크가 돌 때마다 호출. 모든 애플리케이션 태스크가 정상적으로 양보하여
 *  Idle 까지 도달했다는 신호이므로 여기서 IWDG 를 갱신한다.
 *  (헬스 루프 데드락/기아 상태면 Idle 에 도달하지 못해 워치독이 리셋.)
 *
 *  주의: Idle 후크 안에서는 블로킹 API(vTaskDelay 등)를 호출하면 안 된다.
 *        watchdog_refresh() 는 IWDG_KR 레지스터 한 번 쓰기로 논블로킹.
 * ===========================================================================*/
void vApplicationIdleHook(void)
{
    watchdog_refresh();
    /* WFI 로 코어를 잠재워 소비전력/발열을 줄인다(다음 인터럽트까지 대기). */
    __asm volatile ("wfi");
}

/* =============================================================================
 * configASSERT 처리기
 *  파일/라인 정보를 보존하기 위해 지역 정적에 저장한 뒤 bsp_fatal 로 정지.
 * ===========================================================================*/
void vAssertCalled(const char *pcFile, unsigned long ulLine)
{
    /* 디버거에서 검사할 수 있도록 컨텍스트 보존(최적화 제거 방지: volatile). */
    static volatile const char *file;
    static volatile unsigned long line;
    file = pcFile;
    line = ulLine;
    (void)file;
    (void)line;

    bsp_fatal("assert");
}

/* =============================================================================
 * 정적 할당 콜백 (Idle/Timer 태스크 메모리)
 *
 * FreeRTOSConfig.h 는 configSUPPORT_STATIC_ALLOCATION=1 +
 * configKERNEL_PROVIDED_STATIC_MEMORY=1 (FreeRTOS-Kernel V11.1.0, 벤더 고정)로
 * 설정되어 있다. 이 조합에서 커널의 tasks.c/timers.c 가 vApplicationGetIdleTaskMemory
 * / vApplicationGetTimerTaskMemory 를 **강한(non-weak) 심볼로 직접 제공**한다.
 * 따라서 여기서 같은 함수를 정의하면 다중 정의(multiple definition) 링크 오류가 난다.
 *  -> 이 파일에서는 두 콜백을 정의하지 않는다(커널이 소유).
 *
 * 만약 더 낮은 커널(예: V10.x, configKERNEL_PROVIDED_STATIC_MEMORY 미지원)으로
 * 교체하면 위 두 콜백이 미정의(undefined reference)가 되므로, 그때 이 자리에
 * 명시적 제공자를 다시 추가한다. (커널 프로토타입의 stack-size 인자 타입은
 * configSTACK_DEPTH_TYPE* 임에 유의.)
 * ===========================================================================*/
