/*
 * 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_transport_rtt.c
 * @brief          : SEGGER RTT Built-in Transport for TraxProbe
 * @version        : 3.1.0
 ******************************************************************************
 * @attention
 *
 * Built-in SEGGER RTT transport. Compiled and linked when TraxProbe is
 * enabled and TRAX_CFG_TRANSPORT == TRAX_TRANSPORT_RTT. This file DEFINES the
 * trax_transport_rtt_*() functions, which the core reaches through the
 * TRAX_TRANSPORT_* selector macros in <trax_transport.h> — there is no
 * dispatcher and no user action required for the RTT path. The rtt-specific
 * names keep the built-in transport from colliding with a user's custom
 * trax_transport_*() functions if the build is later switched back to RTT.
 *
 * SEGGER RTT source: https://github.com/SEGGERMicro/RTT
 *
 * Required include paths:
 *   - Third_Party/SEGGER_RTT/RTT                  (SEGGER_RTT.c, SEGGER_RTT.h)
 *   - Third_Party/TraxProbe/transports/RTT/include (SEGGER_RTT_Conf.h override)
 *
 * NON-BLOCKING / FRAME-ATOMIC SEMANTICS
 * --------------------------------------
 * The RTT up-channel is configured SEGGER_RTT_MODE_NO_BLOCK_SKIP.
 * trax_transport_rtt_write() pre-checks available ring space; if the bytes do
 * not fit it returns 0 without touching the ring (fail-fast — no internal
 * waiting). A 0 return is SOFT BACKPRESSURE upstream: the core keeps the
 * batch queued in the application ring, rolls its validator back, and
 * retries the identical batch on the next trax_process() pass (see
 * .cursor/rules/trax-transport-transmission.mdc). Only a PARTIAL write
 * (impossible in SKIP mode) would be fatal.
 *
 * Why SKIP and not BLOCK or TRIM:
 *   - BLOCK_IF_FIFO_FULL creates a spin that re-publishes WrOff from
 *     uncached SRAM on every iteration while holding SEGGER_RTT_LOCK.
 *     Under J-Link GDB Server on Windows this causes silent 4-byte drops.
 *   - TRIM is byte-granular and would split frames — never safe here.
 *   - SKIP is all-or-nothing for the NumBytes passed, which is exactly
 *     the frame-atomic contract TraxProbe requires.
 *
 ******************************************************************************
 */

#include "trax_config_default.h"
#if TRAX_ENABLE && (TRAX_CFG_TRANSPORT == TRAX_TRANSPORT_RTT)

#include <stddef.h>
#include <stdint.h>
#include "trax_transport.h"
#include "include/SEGGER_RTT.h"
#include "config/trax_transport_config.h"

/*=============================================================================
 ====================STATIC RTT BUFFERS FOR CHANNEL 1==========================
 ============================================================================*/

/**
 * Dedicated RTT buffers for TraxProbe (channel 1).
 * Channel 0 keeps its SEGGER default buffers for terminal / printf output.
 */
static char s_rtt_up_buf[TRAX_RTT_BUFFER_SIZE_UP];
static char s_rtt_dn_buf[TRAX_RTT_BUFFER_SIZE_DOWN];

/*=============================================================================
 ====================TRANSPORT FUNCTIONS (called by the core)==================
 ============================================================================*/

int trax_transport_rtt_init(void)
{
	SEGGER_RTT_Init();

	SEGGER_RTT_ConfigUpBuffer(TRAX_RTT_UP_CHANNEL, "TraxProbe",
		s_rtt_up_buf, sizeof(s_rtt_up_buf), TRAX_RTT_MODE);

	SEGGER_RTT_ConfigDownBuffer(TRAX_RTT_DOWN_CHANNEL, "TraxCmd",
		s_rtt_dn_buf, sizeof(s_rtt_dn_buf),
		SEGGER_RTT_MODE_NO_BLOCK_SKIP);

	return 0;
}

size_t trax_transport_rtt_write(const void *p_src, size_t size)
{
	/* The core only sends non-empty segments (NULL/zero are filtered before
	 * the per-segment write loop), so no re-check here.
	 *
	 * Fail-fast: if the bytes do not fit we return 0 without touching the
	 * ring. SKIP is all-or-nothing, so this never emits a partial frame.
	 * The core treats 0 as soft backpressure and retries the same batch
	 * next pass — the application ring absorbs the burst. Keep
	 * TRAX_CFG_TX_BATCH_MAX_BYTES below TRAX_RTT_BUFFER_SIZE_UP so every
	 * batch fits an emptied-out RTT buffer. */
	if ((size_t)SEGGER_RTT_GetAvailWriteSpace(TRAX_RTT_UP_CHANNEL) < size) {
		return 0;
	}

	/* NoLock is deliberate. SEGGER_RTT_LOCK exists to serialize MULTIPLE
	 * target-side writers on the same up buffer; this channel has exactly
	 * one writer — trax_send_frames() in trax_process() context. ISRs and
	 * the fault path never touch RTT (they write the application ring),
	 * and the host-side J-Link reader is lock-free by design (target owns
	 * WrOff, host owns RdOff). The locked variant would mask ALL IRQs
	 * (PRIMASK on M0/M0+, BASEPRI on M3+) for the whole batch memcpy —
	 * ~500 us for a 4 KB batch at 64 MHz — blacking out every interrupt
	 * in the system once per drain. SKIP-mode space check is done inside
	 * WriteNoLock as well, so the all-or-nothing contract still holds. */
	return (size_t)SEGGER_RTT_WriteNoLock(TRAX_RTT_UP_CHANNEL, p_src,
		(unsigned int)size);
}

size_t trax_transport_rtt_read(void *p_dst, size_t size)
{
	/* The core passes a valid buffer and non-zero size — no re-check here.
	 * Called on every trax_process() pass, idle ones included: RTT_Read
	 * returns 0 when the down buffer is empty, which is the core's drain
	 * signal, so no separate has-data probe is needed. */
	return (size_t)SEGGER_RTT_Read(TRAX_RTT_DOWN_CHANNEL, p_dst,
		(unsigned int)size);
}

size_t trax_transport_rtt_tx_free(void)
{
	/* Reported to the core BEFORE batch collection so the batch is sized
	 * to what the RTT ring can accept — the all-or-nothing write below
	 * then never has to reject. The host-side reader only FREES space
	 * concurrently (target owns WrOff, host owns RdOff), so the value
	 * can only grow between this call and the write. */
	return (size_t)SEGGER_RTT_GetAvailWriteSpace(TRAX_RTT_UP_CHANNEL);
}

size_t trax_transport_rtt_bytes_used(void)
{
	return (size_t)SEGGER_RTT_GetBytesInBuffer(TRAX_RTT_UP_CHANNEL);
}

size_t trax_transport_rtt_buf_capacity(void)
{
	/* Reported from the actual buffer allocation (not the config macro at
	 * some other include site) so the diag snapshot can never disagree
	 * with what was really linked in. */
	return sizeof(s_rtt_up_buf);
}

void trax_transport_rtt_clear_tx(void)
{
	/* Target-side discard of everything still parked in the up buffer.
	 * Called by the session-restart path (CMD_START_STREAMING) so a new
	 * session never starts against an up ring pre-loaded with stale
	 * frames from a previous session (e.g. a boot-time
	 * trax_start_stream() the host never drained). Discarding is safe
	 * there: the host parser resynchronizes on the SESSION_START
	 * keyword, so undrained pre-START bytes carry no value.
	 *
	 * Publishing WrOff = RdOff marks the ring empty to the host. RdOff
	 * is owned by the host and can advance concurrently — the J-Link
	 * memory interface is NOT stopped by SEGGER_RTT_LOCK — so re-sample
	 * until stable: if the host consumed bytes between our read and our
	 * write, WrOff would land behind RdOff and the host would see a
	 * bogus wrap-around of stale bytes. The loop converges as soon as
	 * the host observes an empty ring (it stops advancing RdOff). */
	SEGGER_RTT_BUFFER_UP *p_ring = (SEGGER_RTT_BUFFER_UP *)
		((uintptr_t)&_SEGGER_RTT.aUp[TRAX_RTT_UP_CHANNEL]
		 + SEGGER_RTT_UNCACHED_OFF);
	unsigned rd;

	SEGGER_RTT_LOCK();
	do {
		rd = p_ring->RdOff;
		p_ring->WrOff = rd;
	} while (p_ring->RdOff != rd);
	SEGGER_RTT_UNLOCK();
}

#endif /* TRAX_ENABLE && (TRAX_CFG_TRANSPORT == TRAX_TRANSPORT_RTT) */
