/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(C) 2020 Marvell International Ltd.
 */

#ifndef _RTE_TRACE_POINT_H_
#define _RTE_TRACE_POINT_H_

/**
 * @file
 *
 * RTE Tracepoint API
 *
 * This file provides the tracepoint API to RTE applications.
 *
 * @warning
 * @b EXPERIMENTAL: this API may change without prior notice
 */

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>
#include <stdio.h>

#include <rte_branch_prediction.h>
#include <rte_common.h>
#include <rte_compat.h>
#include <rte_cycles.h>
#include <rte_per_lcore.h>
#include <rte_string_fns.h>
#include <rte_uuid.h>

/** The tracepoint object. */
typedef uint64_t rte_trace_point_t;

/**
 * Macro to define the tracepoint arguments in RTE_TRACE_POINT macro.

 * @see RTE_TRACE_POINT, RTE_TRACE_POINT_FP
 */
#define RTE_TRACE_POINT_ARGS

/** @internal Helper macro to support RTE_TRACE_POINT and RTE_TRACE_POINT_FP */
#define __RTE_TRACE_POINT(_mode, _tp, _args, ...) \
extern rte_trace_point_t __##_tp; \
static __rte_always_inline void \
_tp _args \
{ \
	__rte_trace_point_emit_header_##_mode(&__##_tp); \
	__VA_ARGS__ \
}

/**
 * Create a tracepoint.
 *
 * A tracepoint is defined by specifying:
 * - its input arguments: they are the C function style parameters to define
 *   the arguments of tracepoint function. These input arguments are embedded
 *   using the RTE_TRACE_POINT_ARGS macro.
 * - its output event fields: they are the sources of event fields that form
 *   the payload of any event that the execution of the tracepoint macro emits
 *   for this particular tracepoint. The application uses
 *   rte_trace_point_emit_* macros to emit the output event fields.
 *
 * @param tp
 *   Tracepoint object. Before using the tracepoint, an application needs to
 *   define the tracepoint using RTE_TRACE_POINT_REGISTER macro.
 * @param args
 *   C function style input arguments to define the arguments to tracepoint
 *   function.
 * @param ...
 *   Define the payload of trace function. The payload will be formed using
 *   rte_trace_point_emit_* macros. Use ";" delimiter between two payloads.
 *
 * @see RTE_TRACE_POINT_ARGS, RTE_TRACE_POINT_REGISTER, rte_trace_point_emit_*
 */
#define RTE_TRACE_POINT(tp, args, ...) \
	__RTE_TRACE_POINT(generic, tp, args, __VA_ARGS__)

/**
 * Create a tracepoint for fast path.
 *
 * Similar to RTE_TRACE_POINT, except that it is removed at compilation time
 * unless the RTE_ENABLE_TRACE_FP configuration parameter is set.
 *
 * @param tp
 *   Tracepoint object. Before using the tracepoint, an application needs to
 *   define the tracepoint using RTE_TRACE_POINT_REGISTER macro.
 * @param args
 *   C function style input arguments to define the arguments to tracepoint.
 *   function.
 * @param ...
 *   Define the payload of trace function. The payload will be formed using
 *   rte_trace_point_emit_* macros, Use ";" delimiter between two payloads.
 *
 * @see RTE_TRACE_POINT
 */
#define RTE_TRACE_POINT_FP(tp, args, ...) \
	__RTE_TRACE_POINT(fp, tp, args, __VA_ARGS__)

#ifdef __DOXYGEN__

/**
 * Register a tracepoint.
 *
 * @param trace
 *   The tracepoint object created using RTE_TRACE_POINT_REGISTER.
 * @param name
 *   The name of the tracepoint object.
 * @return
 *   - 0: Successfully registered the tracepoint.
 *   - <0: Failure to register the tracepoint.
 */
#define RTE_TRACE_POINT_REGISTER(trace, name)

/** Tracepoint function payload for uint64_t datatype */
#define rte_trace_point_emit_u64(val)
/** Tracepoint function payload for int64_t datatype */
#define rte_trace_point_emit_i64(val)
/** Tracepoint function payload for uint32_t datatype */
#define rte_trace_point_emit_u32(val)
/** Tracepoint function payload for int32_t datatype */
#define rte_trace_point_emit_i32(val)
/** Tracepoint function payload for uint16_t datatype */
#define rte_trace_point_emit_u16(val)
/** Tracepoint function payload for int16_t datatype */
#define rte_trace_point_emit_i16(val)
/** Tracepoint function payload for uint8_t datatype */
#define rte_trace_point_emit_u8(val)
/** Tracepoint function payload for int8_t datatype */
#define rte_trace_point_emit_i8(val)
/** Tracepoint function payload for int datatype */
#define rte_trace_point_emit_int(val)
/** Tracepoint function payload for long datatype */
#define rte_trace_point_emit_long(val)
/** Tracepoint function payload for size_t datatype */
#define rte_trace_point_emit_size_t(val)
/** Tracepoint function payload for float datatype */
#define rte_trace_point_emit_float(val)
/** Tracepoint function payload for double datatype */
#define rte_trace_point_emit_double(val)
/** Tracepoint function payload for pointer datatype */
#define rte_trace_point_emit_ptr(val)
/** Tracepoint function payload for string datatype */
#define rte_trace_point_emit_string(val)
/**
 * Tracepoint function to capture a blob.
 *
 * @param val
 *   Pointer to the array to be captured.
 * @param len
 *   Length to be captured. The maximum supported length is
 *   RTE_TRACE_BLOB_LEN_MAX bytes.
 */
#define rte_trace_point_emit_blob(val, len)

#endif /* __DOXYGEN__ */

/** @internal Macro to define maximum emit length of string datatype. */
#define __RTE_TRACE_EMIT_STRING_LEN_MAX 32
/** @internal Macro to define event header size. */
#define __RTE_TRACE_EVENT_HEADER_SZ sizeof(uint64_t)

/** Macro to define maximum emit length of blob. */
#define RTE_TRACE_BLOB_LEN_MAX 64

/**
 * Enable recording events of the given tracepoint in the trace buffer.
 *
 * @param tp
 *   The tracepoint object to enable.
 * @return
 *   - 0: Success.
 *   - (-ERANGE): Trace object is not registered.
 */
__rte_experimental
int rte_trace_point_enable(rte_trace_point_t *tp);

/**
 * Disable recording events of the given tracepoint in the trace buffer.
 *
 * @param tp
 *   The tracepoint object to disable.
 * @return
 *   - 0: Success.
 *   - (-ERANGE): Trace object is not registered.
 */
__rte_experimental
int rte_trace_point_disable(rte_trace_point_t *tp);

/**
 * Test if recording events from the given tracepoint is enabled.
 *
 * @param tp
 *    The tracepoint object.
 * @return
 *    true if tracepoint is enabled, false otherwise.
 */
__rte_experimental
bool rte_trace_point_is_enabled(rte_trace_point_t *tp);

/**
 * Lookup a tracepoint object from its name.
 *
 * @param name
 *   The name of the tracepoint.
 * @return
 *   The tracepoint object or NULL if not found.
 */
__rte_experimental
rte_trace_point_t *rte_trace_point_lookup(const char *name);

/**
 * @internal
 *
 * Test if the tracepoint fast path compile-time option is enabled.
 *
 * @return
 *   true if tracepoint fast path enabled, false otherwise.
 */
__rte_experimental
static __rte_always_inline bool
__rte_trace_point_fp_is_enabled(void)
{
#ifdef RTE_ENABLE_TRACE_FP
	return true;
#else
	return false;
#endif
}

/**
 * @internal
 *
 * Allocate trace memory buffer per thread.
 */
__rte_experimental
void __rte_trace_mem_per_thread_alloc(void);

/**
 * @internal
 *
 * Helper function to emit field.
 *
 * @param sz
 *   The tracepoint size.
 * @param field
 *   The name of the trace event.
 * @param type
 *   The datatype of the trace event as string.
 * @return
 *   - 0: Success.
 *   - <0: Failure.
 */
__rte_experimental
void __rte_trace_point_emit_field(size_t sz, const char *field,
	const char *type);

/**
 * @internal
 *
 * Helper function to register a dynamic tracepoint.
 * Use RTE_TRACE_POINT_REGISTER macro for tracepoint registration.
 *
 * @param trace
 *   The tracepoint object created using RTE_TRACE_POINT_REGISTER.
 * @param name
 *   The name of the tracepoint object.
 * @param register_fn
 *   Trace registration function.
 * @return
 *   - 0: Successfully registered the tracepoint.
 *   - <0: Failure to register the tracepoint.
 */
__rte_experimental
int __rte_trace_point_register(rte_trace_point_t *trace, const char *name,
	void (*register_fn)(void));

#ifndef __DOXYGEN__

#ifndef _RTE_TRACE_POINT_REGISTER_H_
#ifdef ALLOW_EXPERIMENTAL_API

#define __RTE_TRACE_EVENT_HEADER_ID_SHIFT (48)

#define __RTE_TRACE_FIELD_SIZE_SHIFT 0
#define __RTE_TRACE_FIELD_SIZE_MASK (0xffffULL << __RTE_TRACE_FIELD_SIZE_SHIFT)
#define __RTE_TRACE_FIELD_ID_SHIFT (16)
#define __RTE_TRACE_FIELD_ID_MASK (0xffffULL << __RTE_TRACE_FIELD_ID_SHIFT)
#define __RTE_TRACE_FIELD_ENABLE_MASK (1ULL << 63)
#define __RTE_TRACE_FIELD_ENABLE_DISCARD (1ULL << 62)

struct __rte_trace_stream_header {
	uint32_t magic;
	rte_uuid_t uuid;
	uint32_t lcore_id;
	char thread_name[__RTE_TRACE_EMIT_STRING_LEN_MAX];
} __rte_packed;

struct __rte_trace_header {
	uint32_t offset;
	uint32_t len;
	struct __rte_trace_stream_header stream_header;
	uint8_t mem[];
};

RTE_DECLARE_PER_LCORE(void *, trace_mem);

static __rte_always_inline void *
__rte_trace_mem_get(uint64_t in)
{
	struct __rte_trace_header *trace =
		(struct __rte_trace_header *)(RTE_PER_LCORE(trace_mem));
	const uint16_t sz = in & __RTE_TRACE_FIELD_SIZE_MASK;

	/* Trace memory is not initialized for this thread */
	if (unlikely(trace == NULL)) {
		__rte_trace_mem_per_thread_alloc();
		trace = (struct __rte_trace_header *)(RTE_PER_LCORE(trace_mem));
		if (unlikely(trace == NULL))
			return NULL;
	}
	/* Check the wrap around case */
	uint32_t offset = trace->offset;
	if (unlikely((offset + sz) >= trace->len)) {
		/* Disable the trace event if it in DISCARD mode */
		if (unlikely(in & __RTE_TRACE_FIELD_ENABLE_DISCARD))
			return NULL;

		offset = 0;
	}
	/* Align to event header size */
	offset = RTE_ALIGN_CEIL(offset, __RTE_TRACE_EVENT_HEADER_SZ);
	void *mem = RTE_PTR_ADD(&trace->mem[0], offset);
	offset += sz;
	trace->offset = offset;

	return mem;
}

static __rte_always_inline void *
__rte_trace_point_emit_ev_header(void *mem, uint64_t in)
{
	uint64_t val;

	/* Event header [63:0] = id [63:48] | timestamp [47:0] */
	val = rte_get_tsc_cycles() &
		~(0xffffULL << __RTE_TRACE_EVENT_HEADER_ID_SHIFT);
	val |= ((in & __RTE_TRACE_FIELD_ID_MASK) <<
		(__RTE_TRACE_EVENT_HEADER_ID_SHIFT -
		 __RTE_TRACE_FIELD_ID_SHIFT));

	*(uint64_t *)mem = val;
	return RTE_PTR_ADD(mem, __RTE_TRACE_EVENT_HEADER_SZ);
}

#define __rte_trace_point_emit_header_generic(t) \
void *mem; \
do { \
	const uint64_t val = __atomic_load_n(t, __ATOMIC_ACQUIRE); \
	if (likely(!(val & __RTE_TRACE_FIELD_ENABLE_MASK))) \
		return; \
	mem = __rte_trace_mem_get(val); \
	if (unlikely(mem == NULL)) \
		return; \
	mem = __rte_trace_point_emit_ev_header(mem, val); \
} while (0)

#define __rte_trace_point_emit_header_fp(t) \
	if (!__rte_trace_point_fp_is_enabled()) \
		return; \
	__rte_trace_point_emit_header_generic(t)

#define __rte_trace_point_emit(in, type) \
do { \
	memcpy(mem, &(in), sizeof(in)); \
	mem = RTE_PTR_ADD(mem, sizeof(in)); \
} while (0)

#define rte_trace_point_emit_string(in) \
do { \
	if (unlikely(in == NULL)) \
		return; \
	rte_strscpy((char *)mem, in, __RTE_TRACE_EMIT_STRING_LEN_MAX); \
	mem = RTE_PTR_ADD(mem, __RTE_TRACE_EMIT_STRING_LEN_MAX); \
} while (0)

#define rte_trace_point_emit_blob(in, len) \
do { \
	if (unlikely(in == NULL)) \
		return; \
	if (len > RTE_TRACE_BLOB_LEN_MAX) \
		len = RTE_TRACE_BLOB_LEN_MAX; \
	__rte_trace_point_emit(len, uint8_t); \
	memcpy(mem, in, len); \
	memset(RTE_PTR_ADD(mem, len), 0, RTE_TRACE_BLOB_LEN_MAX - len); \
	mem = RTE_PTR_ADD(mem, RTE_TRACE_BLOB_LEN_MAX); \
} while (0)

#else

#define __rte_trace_point_emit_header_generic(t) RTE_SET_USED(t)
#define __rte_trace_point_emit_header_fp(t) RTE_SET_USED(t)
#define __rte_trace_point_emit(in, type) RTE_SET_USED(in)
#define rte_trace_point_emit_string(in) RTE_SET_USED(in)
#define rte_trace_point_emit_blob(in, len) \
do { \
	RTE_SET_USED(in); \
	RTE_SET_USED(len); \
} while (0)


#endif /* ALLOW_EXPERIMENTAL_API */
#endif /* _RTE_TRACE_POINT_REGISTER_H_ */

#define rte_trace_point_emit_u64(in) __rte_trace_point_emit(in, uint64_t)
#define rte_trace_point_emit_i64(in) __rte_trace_point_emit(in, int64_t)
#define rte_trace_point_emit_u32(in) __rte_trace_point_emit(in, uint32_t)
#define rte_trace_point_emit_i32(in) __rte_trace_point_emit(in, int32_t)
#define rte_trace_point_emit_u16(in) __rte_trace_point_emit(in, uint16_t)
#define rte_trace_point_emit_i16(in) __rte_trace_point_emit(in, int16_t)
#define rte_trace_point_emit_u8(in) __rte_trace_point_emit(in, uint8_t)
#define rte_trace_point_emit_i8(in) __rte_trace_point_emit(in, int8_t)
#define rte_trace_point_emit_int(in) __rte_trace_point_emit(in, int32_t)
#define rte_trace_point_emit_long(in) __rte_trace_point_emit(in, long)
#define rte_trace_point_emit_size_t(in) __rte_trace_point_emit(in, size_t)
#define rte_trace_point_emit_float(in) __rte_trace_point_emit(in, float)
#define rte_trace_point_emit_double(in) __rte_trace_point_emit(in, double)
#define rte_trace_point_emit_ptr(in) __rte_trace_point_emit(in, uintptr_t)

#endif /* __DOXYGEN__ */

#ifdef __cplusplus
}
#endif

#endif /* _RTE_TRACE_POINT_H_ */
