/*
 * 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          : Xilinx Zynq Hardware Port (Cortex-A9 / A53 / R5)
 * @version        : 1.0.0
 ******************************************************************************
 * @attention
 *
 * One port covering the Zynq family. The target sub-architecture is detected
 * at compile time and the right register primitives are selected:
 *
 *   Zynq-7000        Cortex-A9   ARMv7-A   (32-bit)  global timer / CPSR / MPIDR
 *   ZynqMP APU       Cortex-A53  ARMv8-A   (AArch64) CNTVCT_EL0 / DAIF / MPIDR_EL1
 *   ZynqMP RPU       Cortex-R5F  ARMv7-R   (32-bit)  PMU cycle cnt / CPSR / MPIDR
 *
 * What this port provides (the hw_port contract):
 *   - TRAX_PORT_ENTER/EXIT_CRITICAL_SECTION   local-core IRQ mask save/restore
 *   - TRAX_PORT_GET_CORE_ID()                 MPIDR affinity-0 read
 *   - TRAX_HW_PORT_FREERUN_COUNTER            32-bit free-running counter read
 *   - TRAX_HW_PORT_TIMEPACKED_GET32/PUT32     timestamp read (FREERUN/TICK_TIMER)
 *   - TRAX_CFG_ISR_PRIO_ASCENDING             GIC: lower number = higher priority
 *
 * ─── READ THIS: two things that differ from a single-core MCU ───────────────
 *
 * 1. SMP vs AMP (who writes the ring buffer) — selected by TRAX_CFG_CORE_COUNT
 *    - Single-core, or AMP where each core runs its OWN image + buffer +
 *      transport: keep TRAX_CFG_CORE_COUNT = 1. The critical section masks
 *      interrupts on the calling core only — sufficient, nothing is shared.
 *      The host separates AMP images by session/transport, not by coreId.
 *    - SMP (one image / one shared ring buffer scheduled across cores): set
 *      TRAX_CFG_CORE_COUNT = N. The critical section then automatically adds a
 *      spinlock on top of the IRQ mask, and every frame is tagged with the
 *      runtime core id. You supply the lock word once via
 *      TRAX_ZYNQ_PORT_DEFINE_LOCK (see the critical-section comment).
 *
 * 2. Data caches
 *    Unlike Cortex-M0..M4, Zynq cores have L1/L2 data caches. Inter-core
 *    visibility is automatic once the SCU/coherency is enabled (Xilinx BSP
 *    does this), but anything that reads the ring buffer OUTSIDE the cache
 *    domain — a PL/DMA engine, or a core with the MMU/caches off — needs the
 *    buffer in non-cacheable memory or an explicit cache flush. Prefer placing
 *    the TraxProbe out-buffer in a non-cacheable section when a DMA-based
 *    transport drains it.
 ******************************************************************************
 */

#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 */
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/*=============================================================================
 ====================SUB-ARCHITECTURE DETECTION===============================
 ============================================================================*/

/* Pick exactly one of: AArch64 (A53), ARMv7-R (R5), ARMv7-A (A9). The order
 * matters — test AArch64 first, then the R profile, then default 32-bit to A. */
#if defined(__aarch64__)
  #define TRAX_ZYNQ_AARCH64   1
#elif defined(__ARM_ARCH_7R__) || (defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'R'))
  #define TRAX_ZYNQ_ARMV7R    1
#elif defined(__ARM_ARCH_7A__) || (defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'A')) || defined(__arm__)
  #define TRAX_ZYNQ_ARMV7A    1
#else
  #error "TraxProbe Zynq port: could not detect Cortex-A9/A53/R5. Define the TRAX_PORT_* hooks manually, or use TRAX_HW_PORT_GENERIC."
#endif

/* Data memory barrier (full system). Used by TICK_TIMER packing to keep the
 * tick/fine reads ordered; FREERUN mode is a single atomic read and needs none. */
#if defined(TRAX_ZYNQ_AARCH64)
  #define TRAX_ZYNQ_DMB()   __asm volatile("dmb ish" ::: "memory")
#else
  #define TRAX_ZYNQ_DMB()   __asm volatile("dmb" ::: "memory")
#endif

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

/* ARM GIC (PL390 on Zynq-7000, GIC-400 on ZynqMP) orders priorities the same
 * way the Cortex-M NVIC does: **lower number = higher priority**. This is an
 * architectural fact of the interrupt controller, not a per-app knob. */
#ifndef TRAX_CFG_ISR_PRIO_ASCENDING
  #define TRAX_CFG_ISR_PRIO_ASCENDING   0  /* ARM GIC: lower number wins */
#endif

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

/**
 * @brief Enter / exit critical section — block-style macro pair.
 *
 *     TRAX_PORT_ENTER_CRITICAL_SECTION { ... } TRAX_PORT_EXIT_CRITICAL_SECTION
 *
 * Saves the current interrupt-mask state, masks IRQs on the calling core,
 * opens an inner block; the EXIT macro closes the block and restores the
 * previous mask. AArch64 (A53) uses DAIF; ARMv7-A/R (A9/R5) use the CPSR
 * I-bit. The restore only re-enables IRQs if they were enabled on entry, so
 * nesting is safe and no privileged mode bits are rewritten.
 *
 * Two flavours, selected automatically by TRAX_CFG_CORE_COUNT
 * ----------------------------------------------------------
 *   == 1 : single-core, or AMP where each core runs its own image + buffer.
 *          Mask the LOCAL core only — nothing is shared, no lock needed.
 *   >  1 : SMP — one image / one shared ring buffer scheduled across cores.
 *          Mask IRQs AND take a portable __atomic spinlock (compiles to
 *          LDREX/STREX on A9/R5, LDXR/STXR on A53). IRQs are masked FIRST and
 *          the lock taken SECOND, so a higher-priority IRQ on the lock-holding
 *          core cannot deadlock against itself.
 *
 * SMP lock storage (CORE_COUNT > 1 only)
 * --------------------------------------
 * The spinlock needs exactly one shared word. Define TRAX_ZYNQ_PORT_DEFINE_LOCK
 * in EXACTLY ONE translation unit (before including trax.h) to instantiate it;
 * every other TU sees it as extern:
 *
 *     #define TRAX_ZYNQ_PORT_DEFINE_LOCK
 *     #include "trax.h"
 *
 * You can still override the whole pair in trax_config.h (e.g. to drive a
 * hardware mutex/spinlock peripheral instead of the __atomic word).
 *
 * Toolchain note: the SMP flavour uses the GCC/Clang `__atomic_*` builtins
 * (available in every Xilinx/AMD-supplied toolchain, which is GCC-based).
 * If you build the SMP configuration with a non-GNU compiler (e.g. Arm
 * Compiler 6 works — it is Clang-based — but IAR does not provide these
 * builtins), override TRAX_PORT_ENTER/EXIT_CRITICAL_SECTION in trax_config.h
 * with that toolchain's atomic-exchange primitive. The single-core flavour
 * has no such dependency.
 */
#if (TRAX_CFG_CORE_COUNT > 1)

  #ifdef TRAX_ZYNQ_PORT_DEFINE_LOCK
    volatile uint32_t trax_smp_lock = 0U;
  #else
    extern volatile uint32_t trax_smp_lock;
  #endif

  #ifndef TRAX_PORT_ENTER_CRITICAL_SECTION
    #if defined(TRAX_ZYNQ_AARCH64)
      #define TRAX_PORT_ENTER_CRITICAL_SECTION \
          { \
              uint64_t trax_daif; \
              __asm volatile("mrs %0, DAIF" : "=r"(trax_daif) :: "memory"); \
              __asm volatile("msr DAIFSet, #2" ::: "memory"); \
              while (__atomic_exchange_n(&trax_smp_lock, 1U, __ATOMIC_ACQUIRE)) { } \
              {
    #else /* ARMv7-A / ARMv7-R */
      #define TRAX_PORT_ENTER_CRITICAL_SECTION \
          { \
              uint32_t trax_cpsr; \
              __asm volatile("mrs %0, cpsr" : "=r"(trax_cpsr) :: "memory"); \
              __asm volatile("cpsid i" ::: "memory"); \
              while (__atomic_exchange_n(&trax_smp_lock, 1U, __ATOMIC_ACQUIRE)) { } \
              {
    #endif
  #endif

  #ifndef TRAX_PORT_EXIT_CRITICAL_SECTION
    #if defined(TRAX_ZYNQ_AARCH64)
      #define TRAX_PORT_EXIT_CRITICAL_SECTION \
              } \
              __atomic_store_n(&trax_smp_lock, 0U, __ATOMIC_RELEASE); \
              __asm volatile("msr DAIF, %0" :: "r"(trax_daif) : "memory"); \
          }
    #else /* ARMv7-A / ARMv7-R: re-enable only if IRQs were enabled on entry (I = bit 7) */
      #define TRAX_PORT_EXIT_CRITICAL_SECTION \
              } \
              __atomic_store_n(&trax_smp_lock, 0U, __ATOMIC_RELEASE); \
              if ((trax_cpsr & (1U << 7)) == 0U) { \
                  __asm volatile("cpsie i" ::: "memory"); \
              } \
          }
    #endif
  #endif

#else /* TRAX_CFG_CORE_COUNT == 1 : single-core / AMP-per-image, IRQ mask only */

  #ifndef TRAX_PORT_ENTER_CRITICAL_SECTION
    #if defined(TRAX_ZYNQ_AARCH64)
      #define TRAX_PORT_ENTER_CRITICAL_SECTION \
          { \
              uint64_t trax_daif; \
              __asm volatile("mrs %0, DAIF" : "=r"(trax_daif) :: "memory"); \
              __asm volatile("msr DAIFSet, #2" ::: "memory"); \
              {
    #else /* ARMv7-A / ARMv7-R */
      #define TRAX_PORT_ENTER_CRITICAL_SECTION \
          { \
              uint32_t trax_cpsr; \
              __asm volatile("mrs %0, cpsr" : "=r"(trax_cpsr) :: "memory"); \
              __asm volatile("cpsid i" ::: "memory"); \
              {
    #endif
  #endif

  #ifndef TRAX_PORT_EXIT_CRITICAL_SECTION
    #if defined(TRAX_ZYNQ_AARCH64)
      #define TRAX_PORT_EXIT_CRITICAL_SECTION \
              } \
              __asm volatile("msr DAIF, %0" :: "r"(trax_daif) : "memory"); \
          }
    #else /* ARMv7-A / ARMv7-R: re-enable only if IRQs were enabled on entry (I = bit 7) */
      #define TRAX_PORT_EXIT_CRITICAL_SECTION \
              } \
              if ((trax_cpsr & (1U << 7)) == 0U) { \
                  __asm volatile("cpsie i" ::: "memory"); \
              } \
          }
    #endif
  #endif

#endif /* TRAX_CFG_CORE_COUNT */

/*=============================================================================
 ====================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 SMP the reader on another core races the writer's critical section:
 * Cortex-A/R cores are weakly ordered, so without a barrier the Word-0
 * store may become visible BEFORE the frame-body stores (timestamp / tid /
 * params) and the reader ships a half-written frame. DMB orders all prior
 * stores before the commit store. Compiled out on single-core / AMP builds.
 */
#ifndef TRAX_PORT_COMMIT_BARRIER
  #if (TRAX_CFG_CORE_COUNT > 1)
    #define TRAX_PORT_COMMIT_BARRIER()   TRAX_ZYNQ_DMB()
  #else
    #define TRAX_PORT_COMMIT_BARRIER()
  #endif
#endif

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

/**
 * @brief Return the index of the CPU core executing this call (0-based).
 *
 * Reads affinity level 0 of MPIDR (CP15 c0,c0,5 on ARMv7; MPIDR_EL1 on
 * AArch64). On Zynq-7000 this is 0/1 (the two A9s); on ZynqMP APU 0..3 (A53);
 * on a split-mode RPU each R5 sees itself as 0. The host extracts coreId from
 * the upper byte of the id wire word, so this value selects the swimlane.
 *
 * Override in trax_config.h if your topology needs a different mapping (e.g.
 * an AMP image hard-coded to a fixed lane).
 */
#ifndef TRAX_PORT_GET_CORE_ID
  #if defined(TRAX_ZYNQ_AARCH64)
    static inline uint8_t trax_zynq_get_core_id(void)
    {
        uint64_t mpidr;
        __asm volatile("mrs %0, MPIDR_EL1" : "=r"(mpidr));
        return (uint8_t)(mpidr & 0xFFU);   /* Aff0 */
    }
  #else
    static inline uint8_t trax_zynq_get_core_id(void)
    {
        uint32_t mpidr;
        __asm volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(mpidr));
        return (uint8_t)(mpidr & 0xFFU);   /* Aff0 */
    }
  #endif
  #define TRAX_PORT_GET_CORE_ID()  trax_zynq_get_core_id()
#endif

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

/**
 * @brief 32-bit free-running counter read. Default source per sub-arch:
 *
 *   A53 (AArch64) : CNTVCT_EL0, the ARM generic timer virtual count. Its
 *                   frequency is CNTFRQ_EL0 (≈100 MHz on ZynqMP, board-set).
 *                   -> set TRAX_CFG_TIMER_FREQ_HZ = CNTFRQ_EL0.
 *
 *   A9  (ARMv7-A) : the MPCore *global timer* low 32 bits at 0xF8F00200. It is
 *                   shared by both A9s (good for coherent timestamps) and runs
 *                   at CPU_3x2x = half the CPU clock (e.g. 333.333 MHz for a
 *                   666.666 MHz CPU). The Xilinx standalone BSP enables it
 *                   (XTime_GetTime uses it); otherwise set the enable bit in
 *                   the global-timer control register (base + 0x08).
 *                   -> set TRAX_CFG_TIMER_FREQ_HZ = CPU clock / 2.
 *
 *   R5  (ARMv7-R) : the PMU cycle counter PMCCNTR (CP15 c9,c13,0). It counts
 *                   CPU cycles and must be enabled once at startup:
 *                       MRC/MCR p15,0,Rt,c9,c12,0  ; PMCR.E = 1, .C = reset
 *                       MCR     p15,0,Rt,c9,c12,1  ; PMCNTENSET = cycle counter
 *                   Beware PMCR.D (the /64 divider) — keep it 0 for cycle
 *                   resolution. -> set TRAX_CFG_TIMER_FREQ_HZ = R5 CPU clock.
 *
 * Override TRAX_HW_PORT_FREERUN_COUNTER (or, for A9, just the base address
 * TRAX_PORT_ZYNQ_GLOBAL_TIMER_BASE) if your design uses a different source
 * (e.g. a TTC, or the PL's own free-running counter).
 */

#if defined(TRAX_ZYNQ_AARCH64)

  static inline uint32_t trax_zynq_freerun(void)
  {
      uint64_t cnt;
      __asm volatile("mrs %0, CNTVCT_EL0" : "=r"(cnt));
      return (uint32_t)cnt;
  }

#elif defined(TRAX_ZYNQ_ARMV7R)

  static inline uint32_t trax_zynq_freerun(void)
  {
      uint32_t cyc;
      __asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(cyc));  /* PMCCNTR */
      return cyc;
  }

#else /* TRAX_ZYNQ_ARMV7A — Zynq-7000 global timer */

  #ifndef TRAX_PORT_ZYNQ_GLOBAL_TIMER_BASE
    #define TRAX_PORT_ZYNQ_GLOBAL_TIMER_BASE   0xF8F00200U
  #endif

  static inline uint32_t trax_zynq_freerun(void)
  {
      return *(volatile uint32_t *)(TRAX_PORT_ZYNQ_GLOBAL_TIMER_BASE + 0x00U);
  }

#endif

#ifndef TRAX_HW_PORT_FREERUN_COUNTER
  #define TRAX_HW_PORT_FREERUN_COUNTER  (trax_zynq_freerun())
#endif

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

#if (TRAX_CFG_TIMESTAMP_MODE == TRAX_TIMESTAMP_TICK_TIMER)
  #if !defined(TRAX_CFG_TIMESTAMP_TIMER_VAL)
    #error "TICK_TIMER mode needs TRAX_CFG_TIMESTAMP_TIMER_VAL (a down/up timer register read, e.g. a TTC count). On Zynq the free-running counters make TRAX_TIMESTAMP_FREERUN the natural choice."
  #endif
#endif

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

#if (TRAX_CFG_TIMESTAMP_MODE == TRAX_TIMESTAMP_FREERUN)

/* ─── FREERUN MODE ───────────────────────────────────────────────────
 * Single atomic read of the 32-bit free-running counter — its upper
 * TICK_BITS map to the tick field and the lower bits to the fine field.
 * No packing, no barrier.
 */
#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) + a timer register (fine), packed into
 * [tick:TICK_BITS][fine:(32-TICK_BITS)]. The DMB keeps the tick and fine
 * reads from being reordered; the host (MessageDecoder) handles the
 * tick-boundary race, so no IRQ-pending probe is needed here.
 */
/* Static inline instead of a GNU statement expression (`({ ... })`) — the
 * extension is GCC/Clang-only; the inline generates identical code while
 * staying standard C. */
static inline uint32_t trax_hw_port_timepacked_get32(void)
{
    uint32_t tick_cntr = trax_timebase.tick_cntr;
    TRAX_ZYNQ_DMB();
    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_ */
