Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion tests/unit/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ unit_include_dirs = [

gtest_dep = dependency('gtest', required : true)
libpthread_dep = meson.get_compiler('c').find_library('pthread', required : true)
unit_libdl_dep = meson.get_compiler('c').find_library('dl', required : true)

unit_sources = [
'common/ut_common.c',
Expand Down Expand Up @@ -50,6 +51,8 @@ unit_sources = [
'pipeline/st20p_test.cpp',
'pipeline/st30p_harness.c',
'pipeline/st30p_test.cpp',
'ptp/ptp_harness.c',
'ptp/dynfield_rx_timestamp_test.cpp',
'main.cpp',
]

Expand All @@ -59,5 +62,5 @@ executable('UnitTest', unit_sources,
link_args : unit_link_args,
include_directories : unit_include_dirs,
dependencies : [mtl_internal_dep, dpdk_dep, libm_dep, gtest_dep, libpthread_dep,
usdt_provider_header_dep],
unit_libdl_dep, usdt_provider_header_dep],
install : false)
61 changes: 61 additions & 0 deletions tests/unit/ptp/dynfield_rx_timestamp_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2026 Intel Corporation
*
* Proves that mt_mbuf_time_stamp() derives a correct RX hw-timestamp from
* the generic RTE_ETH_RX_OFFLOAD_TIMESTAMP mbuf dynfield alone, even when
* none of the ice IEEE1588-specific markers (packet_type L2_ETHER_TIMESYNC,
* mb->timesync, RX_IEEE1588_PTP/_TMST ol_flags) are present — i.e. exactly
* the state produced by unpatched upstream ice.
*
* Build: meson setup build_unit -Denable_unit_tests=true && ninja -C build_unit
* Run: ./build_unit/tests/unit/UnitTest --gtest_filter='PtpDynfieldTimestamp.*'
*/

#include <gtest/gtest.h>

#include "ptp/ptp_harness.h"

class PtpDynfieldTimestamp : public ::testing::Test {
protected:
ut_ptp_test_ctx* ctx_ = nullptr;

void SetUp() override {
ASSERT_EQ(ut_ptp_init(), 0) << "EAL init failed";
ctx_ = ut_ptp_ctx_create();
ASSERT_NE(ctx_, nullptr);
}

void TearDown() override {
ut_ptp_ctx_destroy(ctx_);
ctx_ = nullptr;
}
};

TEST_F(PtpDynfieldTimestamp, CorrectWithoutIeee1588Markers) {
const uint64_t kTestNs = 123456789000ULL;

void* mbuf = ut_ptp_alloc_mbuf(ctx_, kTestNs);
ASSERT_NE(mbuf, nullptr);
ASSERT_TRUE(ut_ptp_mbuf_is_unpatched_ice_state(mbuf));

uint64_t ts = ut_ptp_mbuf_time_stamp(ctx_, mbuf);
ASSERT_EQ(ts, kTestNs);

ut_ptp_free_mbuf(mbuf);
}

TEST_F(PtpDynfieldTimestamp, ZeroWithoutOffloadTimestampFeature) {
const uint64_t kTestNs = 123456789000ULL;

ut_ptp_ctx_destroy(ctx_);
ctx_ = ut_ptp_ctx_create_no_offload_timestamp();
ASSERT_NE(ctx_, nullptr);

void* mbuf = ut_ptp_alloc_mbuf(ctx_, kTestNs);
ASSERT_NE(mbuf, nullptr);

uint64_t ts = ut_ptp_mbuf_time_stamp(ctx_, mbuf);
ASSERT_EQ(ts, 0u);

ut_ptp_free_mbuf(mbuf);
}
120 changes: 120 additions & 0 deletions tests/unit/ptp/ptp_harness.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2026 Intel Corporation
*
* C harness for the generic RX hw-timestamp (mt_mbuf_time_stamp) unit tests.
*
* tests/unit/pipeline/st40p_harness.c defines its own mt_mbuf_time_stamp()
* stub (needed to isolate st40p pipeline tests from the PTP stack). Since
* both harnesses link into the single UnitTest binary, the executable's own
* copy of that symbol takes precedence over libmtl.so's real definition for
* every caller in the process (standard ELF symbol interposition). This
* harness resolves the real symbol directly from libmtl.so via dlsym() on
* its own handle, bypassing that interposition, so it exercises the actual
* production implementation rather than the st40p stub.
*/

#include "ptp/ptp_harness.h"

#include <dlfcn.h>
#include <rte_mbuf_dyn.h>
#include <stdlib.h>

#include "common/ut_common.h"
#include "mt_ptp.h"

struct ut_ptp_test_ctx {
struct mtl_main_impl impl;
struct mt_ptp_impl ptp;
};

typedef uint64_t (*mt_mbuf_time_stamp_fn)(struct mtl_main_impl* impl,
struct rte_mbuf* mbuf, enum mtl_port port);

static bool g_dynfield_ready;
static int g_dynfield_offset = -1;
static mt_mbuf_time_stamp_fn g_real_mt_mbuf_time_stamp;

int ut_ptp_init(void) {
int ret = ut_eal_init();
if (ret < 0) return ret;

if (!g_dynfield_ready) {
ret = rte_mbuf_dyn_rx_timestamp_register(&g_dynfield_offset, NULL);
if (ret < 0) return ret;

void* handle = dlopen("libmtl.so", RTLD_NOW | RTLD_NOLOAD);
if (!handle) return -1;
g_real_mt_mbuf_time_stamp =
(mt_mbuf_time_stamp_fn)dlsym(handle, "mt_mbuf_time_stamp");
if (!g_real_mt_mbuf_time_stamp) return -1;

g_dynfield_ready = true;
}

return 0;
}

static ut_ptp_test_ctx* ut_ptp_ctx_create_internal(bool enable_offload_timestamp) {
if (!g_dynfield_ready) return NULL;

ut_ptp_test_ctx* ctx = calloc(1, sizeof(*ctx));
if (!ctx) return NULL;

ctx->impl.tsc_hz = rte_get_tsc_hz();
ctx->impl.dynfield_offset = g_dynfield_offset;
ctx->impl.ptp[MTL_PORT_P] = &ctx->ptp;
if (enable_offload_timestamp)
ctx->impl.inf[MTL_PORT_P].feature |= MT_IF_FEATURE_RX_OFFLOAD_TIMESTAMP;

ctx->ptp.impl = &ctx->impl;
ctx->ptp.port = MTL_PORT_P;
ctx->ptp.coefficient = 1.0;
ctx->ptp.last_sync_ts = 0;
ctx->ptp.ptp_delta = 0;

return ctx;
}

ut_ptp_test_ctx* ut_ptp_ctx_create(void) {
return ut_ptp_ctx_create_internal(true);
}

ut_ptp_test_ctx* ut_ptp_ctx_create_no_offload_timestamp(void) {
return ut_ptp_ctx_create_internal(false);
}

void ut_ptp_ctx_destroy(ut_ptp_test_ctx* ctx) {
free(ctx);
}

void* ut_ptp_alloc_mbuf(ut_ptp_test_ctx* ctx, uint64_t ns) {
struct rte_mbuf* mbuf = rte_pktmbuf_alloc(ut_pool());
if (!mbuf) return NULL;

mbuf->packet_type = 0;
mbuf->timesync = 0;
mbuf->ol_flags &= ~(RTE_MBUF_F_RX_IEEE1588_PTP | RTE_MBUF_F_RX_IEEE1588_TMST);
*RTE_MBUF_DYNFIELD(mbuf, ctx->impl.dynfield_offset, rte_mbuf_timestamp_t*) = ns;

return mbuf;
}

void ut_ptp_free_mbuf(void* mbuf) {
rte_pktmbuf_free((struct rte_mbuf*)mbuf);
}

bool ut_ptp_mbuf_is_unpatched_ice_state(const void* mbuf_ptr) {
const struct rte_mbuf* mbuf = mbuf_ptr;

if ((mbuf->packet_type & RTE_PTYPE_L2_MASK) == RTE_PTYPE_L2_ETHER_TIMESYNC)
return false;
if (mbuf->timesync != 0) return false;
if (mbuf->ol_flags & (RTE_MBUF_F_RX_IEEE1588_PTP | RTE_MBUF_F_RX_IEEE1588_TMST))
return false;

return true;
}

uint64_t ut_ptp_mbuf_time_stamp(ut_ptp_test_ctx* ctx, void* mbuf) {
return g_real_mt_mbuf_time_stamp(&ctx->impl, (struct rte_mbuf*)mbuf, MTL_PORT_P);
}
59 changes: 59 additions & 0 deletions tests/unit/ptp/ptp_harness.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2026 Intel Corporation
*
* C harness for the generic RX hw-timestamp (mt_mbuf_time_stamp) unit tests.
*
* Keeps DPDK mbuf and MTL internal types opaque so the C++ test layer only
* deals with plain values.
*/

#ifndef _PTP_HARNESS_H_
#define _PTP_HARNESS_H_

#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct ut_ptp_test_ctx ut_ptp_test_ctx;

/* Initialise the shared DPDK EAL/mempool and register the generic RX
* hw-timestamp dynfield. Idempotent — safe to call once per fixture SetUp().
* Returns 0 on success, < 0 on failure. */
int ut_ptp_init(void);

/* Create a context with one PTP instance on MTL_PORT_P, RX_OFFLOAD_TIMESTAMP
* enabled, and an identity ptp_correct_ts() (coefficient=1.0, last_sync_ts=0,
* ptp_delta=0). Caller owns the pointer; free with ut_ptp_ctx_destroy(). */
ut_ptp_test_ctx* ut_ptp_ctx_create(void);

/* Same as ut_ptp_ctx_create() but without MT_IF_FEATURE_RX_OFFLOAD_TIMESTAMP,
* exercising mt_mbuf_time_stamp()'s fallback path. Caller owns the pointer;
* free with ut_ptp_ctx_destroy(). */
ut_ptp_test_ctx* ut_ptp_ctx_create_no_offload_timestamp(void);

void ut_ptp_ctx_destroy(ut_ptp_test_ctx* ctx);

/* Allocate an mbuf from the shared pool with packet_type/timesync/ol_flags
* all cleared (none of the ice IEEE1588-specific markers set) and the
* generic hw-timestamp dynfield set to `ns`. Returns NULL on pool
* exhaustion. */
void* ut_ptp_alloc_mbuf(ut_ptp_test_ctx* ctx, uint64_t ns);

void ut_ptp_free_mbuf(void* mbuf);

/* True iff the mbuf carries none of the ice IEEE1588-specific markers:
* packet_type != RTE_PTYPE_L2_ETHER_TIMESYNC, timesync == 0, and neither
* RX_IEEE1588_PTP nor RX_IEEE1588_TMST is set in ol_flags. */
bool ut_ptp_mbuf_is_unpatched_ice_state(const void* mbuf);

/* Wraps mt_mbuf_time_stamp(impl, mbuf, MTL_PORT_P). */
uint64_t ut_ptp_mbuf_time_stamp(ut_ptp_test_ctx* ctx, void* mbuf);

#ifdef __cplusplus
}
#endif

#endif /* _PTP_HARNESS_H_ */
Loading