/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) 2026 Embedya.
 *
 * TraxProbe porting layer (reference port). Licensed under the Apache License,
 * Version 2.0 (see LICENSE-Apache-2.0.txt). You may copy and modify this file
 * to support additional hardware or RTOS targets.
 *
 * This file depends on TraxProbe core headers, which remain licensed under the
 * TraxProbe Commercial License and are NOT relicensed by this notice.
 */

/**
 ******************************************************************************
 * @file           : trax_hw_port.h
 * @brief          : Espressif Xtensa (ESP32 / -S2 / -S3) Hardware Port
 * @version        : 1.0.0
 ******************************************************************************
 * @attention
 *
 * Hardware abstraction for Espressif Xtensa LX6/LX7 SoCs running under
 * ESP-IDF (FreeRTOS). Unlike a single-core Cortex-M, the ESP32-S3 is a
 * dual-core SMP part, so this port differs from the ARM port in three ways:
 *
 *   1. Critical section  — a FreeRTOS portMUX spinlock (NOT just an
 *      interrupt-disable). On SMP, masking interrupts on the local core is
 *      insufficient: the other core can still race the shared trace
 *      ringbuffer. portENTER_CRITICAL_SAFE() disables local interrupts AND
 *      takes the spinlock, and is safe from both task and ISR context.
 *      The spinlock object is a single shared instance defined in
 *      hw_port/Xtensa/src/trax_hw_port.c (a header-local static would give
 *      each translation unit its own lock and defeat mutual exclusion).
 *
 *   2. Core id           — TRAX_PORT_GET_CORE_ID() returns xPortGetCoreID()
 *      so frames from core 0 / core 1 are tagged correctly and land on the
 *      right host swimlane. Pair this with TRAX_CFG_CORE_COUNT = 2 in
 *      trax_config.h.
 *
 *   3. Timestamp source  — FREERUN mode reads esp_timer_get_time(), which is
 *      backed by the SYSTIMER. SYSTIMER is a single system-wide counter
 *      shared by both cores, so it is coherent across the SMP pair. The
 *      per-core Xtensa CCOUNT cycle counter is deliberately NOT used: it is
 *      not synchronized between cores and would scramble a two-core timeline.
 *
 ******************************************************************************
 */

#ifndef TRAX_HW_PORT_H_
#define TRAX_HW_PORT_H_

#include "trax_config_default.h"
#include "trax_timebase.h"   /* trax_timebase.tick_cntr for TICK_TIMER packing */

/* Lightweight, FreeRTOS-independent SoC headers — always safe to include, even
 * when this header is pulled in reentrantly from FreeRTOSConfig.h (see below).
 *   - spinlock.h : spinlock_t, the underlying type of portMUX_TYPE
 *   - esp_timer.h: esp_timer_get_time() for the FREERUN timestamp source */
#include "spinlock.h"
#include "esp_timer.h"

/*
 * FreeRTOS kernel headers — conditionally included.
 *
 * This port header is reached in two situations:
 *
 *   1. The ordinary case: a TraxProbe translation unit (trax_core.c, ...) that
 *      has NOT otherwise pulled in FreeRTOS includes trax.h -> trax_hw.h ->
 *      here.  We must include FreeRTOS.h so the critical-section / core-id
 *      macros below resolve when they expand in that TU.
 *
 *   2. The FreeRTOS trace-hook case: ESP-IDF's FreeRTOSConfig.h pulls in
 *      esp_trace_freertos.h -> esp_trace_freertos_impl.h -> trax.h -> here
 *      while FreeRTOSConfig.h is still being processed (and FreeRTOSConfig.h is
 *      itself reached either from FreeRTOS.h OR directly from port headers such
 *      as xtensa_rtos.h).  At that point portable.h / portmacro.h have NOT run,
 *      so TickType_t, portMUX_TYPE, portNUM_PROCESSORS and the port macros do
 *      not exist yet, and pulling in <freertos/FreeRTOS.h> here would either be
 *      a no-op or — worse, on the xtensa_rtos.h path where INC_FREERTOS_H is
 *      not yet set — recurse into a half-initialised FreeRTOS.h and fail to
 *      compile (e.g. "portNUM_PROCESSORS undeclared"), while also installing
 *      FreeRTOS's empty default traceXXX() macros ahead of ours.  We therefore
 *      skip the FreeRTOS headers in this case: the trace macros only *expand*
 *      later, inside the kernel translation units (tasks.c, queue.c, ...),
 *      where the full FreeRTOS API is in scope.
 *
 * TRAX_FROM_FREERTOS_CONFIG is set by esp_trace_freertos_impl.h (the sole
 * config-time entry point) for the duration of its trax.h include; it is the
 * reliable signal for case 2, since INC_FREERTOS_H alone is not (the config can
 * be entered without FreeRTOS.h having run).  INC_FREERTOS_H is kept as a
 * secondary guard: when FreeRTOS.h is already fully included, re-including it is
 * pointless.
 */
#if !defined(TRAX_FROM_FREERTOS_CONFIG) && !defined(INC_FREERTOS_H)
#include "freertos/FreeRTOS.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

/*=============================================================================
 ====================ISR PRIORITY ORDERING CONVENTION==========================
 ============================================================================*/

/* Xtensa interrupt levels: **higher number = higher priority** (level 1 is the
 * lowest user-controllable level, level 7 is NMI). This is the opposite of the
 * ARM NVIC convention, so the host must be told that priority numbers ascend
 * with priority. This is an architectural fact, not a per-app knob, so the
 * macro is a hard #define (no #ifndef guard) and the build fails loudly if
 * anyone tries to redefine it. */
#define TRAX_CFG_ISR_PRIO_ASCENDING   1  /* Xtensa: higher number wins */

/*=============================================================================
 ====================CRITICAL SECTION MACROS===================================
 ============================================================================*/

/**
 * @brief Single shared SMP spinlock guarding the trace ringbuffer.
 *
 * Defined once in hw_port/Xtensa/src/trax_hw_port.c. Declared extern here so
 * every translation unit that opens a TraxProbe critical section contends on
 * the SAME lock — a header-local `static portMUX_TYPE` would give each .c file
 * its own lock and silently break mutual exclusion between the two cores.
 */
/* Declared as spinlock_t (not portMUX_TYPE) so this line parses even in the
 * FreeRTOSConfig.h reentrant case where portMUX_TYPE is not yet typedef'd.
 * portMUX_TYPE is `typedef spinlock_t portMUX_TYPE`, so this is the same object
 * as the `portMUX_TYPE trax_port_spinlock` definition in trax_hw_port.c. */
extern spinlock_t trax_port_spinlock;

/**
 * @brief Enter / exit critical section — block-style macro pair.
 *
 *     TRAX_PORT_ENTER_CRITICAL_SECTION { ... } TRAX_PORT_EXIT_CRITICAL_SECTION
 *
 * portENTER_CRITICAL_SAFE / portEXIT_CRITICAL_SAFE auto-detect task vs ISR
 * context (TraxProbe opens critical sections from both), disable interrupts on
 * the calling core, and take/release the shared spinlock so the other core is
 * excluded for the duration. These nest correctly on the same mux.
 */
#ifndef TRAX_PORT_ENTER_CRITICAL_SECTION
#define TRAX_PORT_ENTER_CRITICAL_SECTION \
    { \
        portENTER_CRITICAL_SAFE(&trax_port_spinlock); \
        {
#endif

#ifndef TRAX_PORT_EXIT_CRITICAL_SECTION
#define TRAX_PORT_EXIT_CRITICAL_SECTION \
        } \
        portEXIT_CRITICAL_SAFE(&trax_port_spinlock); \
    }
#endif

/*=============================================================================
 ====================FRAME COMMIT BARRIER (SMP)================================
 ============================================================================*/

/**
 * @brief Store barrier issued immediately before the Word-0 commit store.
 *
 * The ring reader (trax_collect_iov) walks the buffer LOCK-FREE — it never
 * takes the spinlock, it only tests Word 0 (the commit marker) for non-zero.
 * On the dual-core ESP32/-S3 the reader on the other core therefore races
 * the writer's critical section: without a barrier the Word-0 store may
 * become visible to the other core BEFORE the frame-body stores (timestamp /
 * tid / params), letting the reader ship a half-written frame. `memw` orders
 * all prior memory accesses before the commit store. Compiled out on
 * single-core builds (ESP32-S2).
 */
#ifndef TRAX_PORT_COMMIT_BARRIER
  #if (TRAX_CFG_CORE_COUNT > 1)
    #define TRAX_PORT_COMMIT_BARRIER()   __asm__ __volatile__("memw" ::: "memory")
  #else
    #define TRAX_PORT_COMMIT_BARRIER()
  #endif
#endif

/*=============================================================================
 ====================CORE ID (SMP) ============================================
 ============================================================================*/

/**
 * @brief Index of the CPU core executing this call (0 or 1 on ESP32-S3).
 *
 * The host extracts coreId from the upper byte of the id wire word, so this
 * value travels verbatim to the swimlane the host picks for the event. On a
 * single-core ESP32-S2 this still compiles correctly (always returns 0).
 */
#ifndef TRAX_PORT_GET_CORE_ID
    #define TRAX_PORT_GET_CORE_ID()  ((uint8_t)xPortGetCoreID())
#endif

/*=============================================================================
 ====================FREE-RUNNING COUNTER======================================
 ============================================================================*/

/**
 * @brief 32-bit free-running counter: low 32 bits of the SYSTIMER microsecond
 *        clock (esp_timer_get_time()).
 *
 * SYSTIMER is system-wide (coherent across both cores) and monotonic, which is
 * exactly what an SMP timeline needs. At 1 MHz the 32-bit value wraps every
 * ~4295 s; trax_timestamp_poll() (called from the main loop / a periodic task)
 * tracks those wraps and must run faster than that — trivially satisfied.
 *
 * esp_timer_get_time() is lock-free, so it is safe to read while the trace
 * critical section above is held (interrupts disabled + spinlock taken).
 *
 * Configure the matching frequency in trax_config.h:
 *     #define TRAX_CFG_TIMESTAMP_MODE       TRAX_TIMESTAMP_FREERUN
 *     #define TRAX_CFG_TIMESTAMP_TIMER_DIR  TRAX_TIMER_DIR_UP
 *     #define TRAX_CFG_TIMER_FREQ_HZ        1000000U
 *
 * Note: 1 us resolution is ample for logs/vars and for the baseline. If you
 * later need sub-microsecond ISR timing, swap this for a direct 16 MHz
 * SYSTIMER unit read and set TRAX_CFG_TIMER_FREQ_HZ to 16000000U.
 */
#ifndef TRAX_HW_PORT_FREERUN_COUNTER
#define TRAX_HW_PORT_FREERUN_COUNTER  ((uint32_t)esp_timer_get_time())
#endif

/*=============================================================================
 ====================TIMESTAMP VALIDATION======================================
 ============================================================================*/

#if (TRAX_CFG_TIMESTAMP_MODE == TRAX_TIMESTAMP_TICK_TIMER)
    #if !defined(TRAX_CFG_TIMESTAMP_TIMER_VAL)
        #error "TRAX_CFG_TIMESTAMP_TIMER_VAL must be defined for TICK_TIMER mode on the Xtensa port"
    #endif
#endif

/*=============================================================================
 ====================TIMETRACK MACROS==========================================
 ============================================================================*/

#if (TRAX_CFG_TIMESTAMP_MODE == TRAX_TIMESTAMP_FREERUN)

/* ─── FREERUN MODE ───────────────────────────────────────────────────
 * The 32-bit free-running counter's upper TICK_BITS map to the tick field
 * and the lower fine_bits to the fine field. Single atomic read, no packing.
 */

#define TRAX_HW_PORT_TIMEPACKED_GET32() \
    (TRAX_HW_PORT_FREERUN_COUNTER)

#define TRAX_HW_PORT_TIMEPACKED_PUT32(p_wr) \
    do { *p_wr++ = TRAX_HW_PORT_FREERUN_COUNTER; } while (0)

#else /* TRAX_TIMESTAMP_TICK_TIMER */

/* ─── TICK_TIMER MODE ────────────────────────────────────────────────
 * Software tick counter (coarse) + timer register (fine), packed into
 * [tick:TICK_BITS][fine:(32-TICK_BITS)]. Provided for completeness; the
 * recommended ESP32 configuration is FREERUN above.
 */

/* Static inline instead of a GNU statement expression (`({ ... })`) — the
 * extension is GCC/Clang-only and would block any future non-GNU toolchain;
 * the inline generates identical code. The reads stay ordered: tick_cntr is
 * volatile and TRAX_CFG_TIMESTAMP_TIMER_VAL is expected to be a volatile
 * hardware register read, and the compiler may not reorder volatile
 * accesses with each other. */
static inline uint32_t trax_hw_port_timepacked_get32(void)
{
    uint32_t tick_cntr = trax_timebase.tick_cntr;
    uint32_t timer_val = TRAX_CFG_TIMESTAMP_TIMER_VAL;
    return (tick_cntr << TRAX_TIMESTAMP_TICK_SHIFT) |
           (timer_val & TRAX_TIMESTAMP_VAL_MASK);
}

#define TRAX_HW_PORT_TIMEPACKED_GET32()  trax_hw_port_timepacked_get32()

#define TRAX_HW_PORT_TIMEPACKED_PUT32(p_wr) \
    do { *p_wr++ = trax_hw_port_timepacked_get32(); } while (0)

#endif /* TRAX_CFG_TIMESTAMP_MODE */

#ifdef __cplusplus
}
#endif

#endif /* TRAX_HW_PORT_H_ */
