DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* RE: [PATCH v3 04/27] bpf: replace atomic op macro with typed helpers
From: Marat Khalili @ 2026-05-25 10:49 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: Konstantin Ananyev, dev@dpdk.org
In-Reply-To: <20260523195839.454952-5-stephen@networkplumber.org>

I absolutely welcome getting rid of macros in this file, but may I request
splitting the rte_atomicNN deprecation and the file refactoring into separate
commits? I am not convinced refactoring was necessary for the deprecation here,
one-line change to `rte_atomic_fetch_add_explicit((type __rte_atomic *)...)` or
like would probably do the job.

If refactor, other examples should also be considered, although we of course
can do it macro by macro. But in any case we should follow some standard
template to reduce number of ways we do stuff in this file if possible.

Regardless of the process, please see some stylistic comments below.
(Technically, the code looks fine.)


> -----Original Message-----
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Saturday 23 May 2026 20:56
> To: dev@dpdk.org
> Cc: Stephen Hemminger <stephen@networkplumber.org>; Konstantin Ananyev <konstantin.ananyev@huawei.com>;
> Marat Khalili <marat.khalili@huawei.com>
> Subject: [PATCH v3 04/27] bpf: replace atomic op macro with typed helpers
> 
> The BPF_ST_ATOMIC_REG macro token-pasted the legacy rte_atomicNN_*()
> API names. It also stacked three casts on the destination pointer
> and reached a 'return 0' out of the macro into the caller's control
> flow.
> 
> Replace it with two small static-inline helpers, bpf_atomic32() and
> bpf_atomic64(), that dispatch on ins->imm internally and use the C11
> atomic intrinsics directly. The destination is cast once, to a
> properly __rte_atomic-qualified pointer. [...]

nit: Not sure number of casts is a relevant metric, but I don't see any
improvement anyway, probably not worth talking about it.

// snip

> @@ -105,6 +83,69 @@
>  	reg[EBPF_REG_0] = op(p[0]); \
>  } while (0)
> 
> +/*
> + * Atomic ops on the BPF target memory.
> + *
> + * BPF atomic instructions encode the destination as base register +
> + * signed offset, with the value to combine taken from src_reg.

nit: This applies to any eBPF instruction. And the part that is unusual about
them that they can modify the source register is not mentioned.

> + *
> + * Memory order: seq_cst preserves the previous behavior of
> + * rte_atomicNN_add() / rte_atomicNN_exchange() and matches what the
> + * Linux kernel BPF interpreter does for these opcodes.

Not sure we should refer to the macros we are removing from the code in the
comment, this information belongs to the commit message.

> + *
> + * Returns 0 on unsupported sub-op (validator should have rejected it),
> + * 1 otherwise.

This is an unusual convention, we typically use negative value for errors, or
booleans in some cases.

To clarify, the original `return 0` was specifying return value from the
program, not an error code. For historical reasons in rare cases when the VM
cannot continue the program returns zero (maybe we should reconsider it).

> + */
> +static inline int
> +bpf_atomic32(const struct rte_bpf *bpf, uint64_t reg[EBPF_REG_NUM],
> +	     const struct ebpf_insn *ins)
> +{
> +	/* need to casts to make bpf memory suitable for C11 atomic */

Typo in "need to casts"? (Also not sure what we warn about here.)

> +	uint32_t __rte_atomic *dst
> +		= (uint32_t __rte_atomic *)(uintptr_t)(reg[ins->dst_reg] + ins->off);

nit: Is it `uint32_t __rte_atomic *` or `RTE_ATOMIC(uint32_t) *`? I honestly
don't know why we have both, but the latter seems more popular in the codebase.

> +	uint32_t val = (uint32_t)reg[ins->src_reg];
> +
> +	switch (ins->imm) {
> +	case BPF_ATOMIC_ADD:
> +		rte_atomic_fetch_add_explicit(dst, val, rte_memory_order_seq_cst);
> +		return 1;
> +	case BPF_ATOMIC_XCHG:
> +		reg[ins->src_reg] = rte_atomic_exchange_explicit(dst, val,
> +								 rte_memory_order_seq_cst);

nit: This is not a typical style of indentation for this file.

> +		return 1;
> +	default:
> +		RTE_BPF_LOG_LINE(ERR,
> +			"%s(%p): unsupported atomic operation at pc: %#zx;",
> +			__func__, bpf,
> +			(uintptr_t)ins - (uintptr_t)bpf->prm.ins);
> +		return 0;

This was an optional defensive programming. Since other functions like
`bpf_alu_be` do not have any default label we can arguably just remove it here
and return void (also not accept bpf argument). With the macro it all was at
least transparent to the caller.

> +	}
> +}

// snip the rest

^ permalink raw reply

* [RFC 3/3] app/test: add fastmem test suite
From: Mattias Rönnblom @ 2026-05-25 10:36 UTC (permalink / raw)
  To: dev
  Cc: Morten Brørup, Konstantin Ananyev, Mattias Rönnblom,
	Yogaraj Baskaravel, Mattias Rönnblom
In-Reply-To: <20260525103642.55255-1-hofors@lysator.liu.se>

From: Mattias Rönnblom <mattias.ronnblom@ericsson.com>

Add functional, performance, and profiling test suites for the
fastmem library.

Signed-off-by: Mattias Rönnblom <mattias.ronnblom@ericsson.com>
---
 app/test/meson.build            |    3 +
 app/test/test_fastmem.c         | 1682 +++++++++++++++++++++++++++++++
 app/test/test_fastmem_perf.c    |  997 ++++++++++++++++++
 app/test/test_fastmem_profile.c |  157 +++
 4 files changed, 2839 insertions(+)
 create mode 100644 app/test/test_fastmem.c
 create mode 100644 app/test/test_fastmem_perf.c
 create mode 100644 app/test/test_fastmem_profile.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c07..d11c63be6f 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -82,6 +82,9 @@ source_file_deps = {
     'test_event_vector_adapter.c': ['eventdev', 'bus_vdev'],
     'test_eventdev.c': ['eventdev', 'bus_vdev'],
     'test_external_mem.c': [],
+    'test_fastmem.c': ['fastmem'],
+    'test_fastmem_perf.c': ['fastmem', 'mempool'],
+    'test_fastmem_profile.c': ['fastmem'],
     'test_fbarray.c': [],
     'test_fib.c': ['net', 'fib'],
     'test_fib6.c': ['rib', 'fib'],
diff --git a/app/test/test_fastmem.c b/app/test/test_fastmem.c
new file mode 100644
index 0000000000..c79ea95481
--- /dev/null
+++ b/app/test/test_fastmem.c
@@ -0,0 +1,1682 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Ericsson AB
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdalign.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_errno.h>
+#include <rte_lcore.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_thread.h>
+
+#include <rte_fastmem.h>
+
+#include "test.h"
+
+#define FASTMEM_MEMZONE_SIZE (128U << 20)
+
+/*
+ * Count memzones whose names begin with the fastmem prefix.
+ * Used to verify that rte_fastmem_reserve() really did reserve
+ * backing memzones.
+ */
+static int fastmem_memzone_count;
+
+static void
+count_fastmem_memzones_walk(const struct rte_memzone *mz, void *arg)
+{
+	RTE_SET_USED(arg);
+
+	if (strncmp(mz->name, "fastmem_", strlen("fastmem_")) == 0)
+		fastmem_memzone_count++;
+}
+
+static unsigned int
+count_fastmem_memzones(void)
+{
+	fastmem_memzone_count = 0;
+	rte_memzone_walk(count_fastmem_memzones_walk, NULL);
+	return fastmem_memzone_count;
+}
+
+static int
+test_init_deinit(void)
+{
+	int rc;
+
+	rc = rte_fastmem_init();
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_init() failed: %d", rc);
+
+	rte_fastmem_deinit();
+
+	/* A subsequent init/deinit cycle must succeed. */
+	rc = rte_fastmem_init();
+	TEST_ASSERT_EQUAL(rc, 0, "second rte_fastmem_init() failed: %d", rc);
+
+	rte_fastmem_deinit();
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_init_is_not_idempotent(void)
+{
+	int rc;
+
+	rc = rte_fastmem_init();
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_init() failed: %d", rc);
+
+	rc = rte_fastmem_init();
+	TEST_ASSERT_EQUAL(rc, -EBUSY,
+		"expected -EBUSY on re-init, got %d", rc);
+
+	rte_fastmem_deinit();
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_deinit_without_init(void)
+{
+	/* Must be a no-op, not a crash. */
+	rte_fastmem_deinit();
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_max_size(void)
+{
+	size_t max;
+
+	max = rte_fastmem_max_size();
+	TEST_ASSERT(max >= (1U << 20),
+		"max_size=%zu below required 1 MiB minimum", max);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_reserve_small(void)
+{
+	int socket_id;
+	unsigned int before, after;
+	int rc;
+
+	socket_id = rte_socket_id_by_idx(0);
+	TEST_ASSERT(socket_id >= 0, "no available sockets");
+
+	before = count_fastmem_memzones();
+
+	/*
+	 * A small reserve request (1 byte) must result in exactly
+	 * one memzone reservation: the internal rounding is to
+	 * memzone granularity.
+	 */
+	rc = rte_fastmem_reserve(1, socket_id);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_reserve() failed: %d", rc);
+
+	after = count_fastmem_memzones();
+	TEST_ASSERT_EQUAL(after - before, 1,
+		"expected 1 new memzone, got %u", after - before);
+
+	rte_fastmem_deinit();
+
+	/* After deinit the memzones must be released. */
+	TEST_ASSERT_EQUAL(count_fastmem_memzones(), 0,
+		"%u fastmem memzones leaked after deinit",
+		count_fastmem_memzones());
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_reserve_multiple_memzones(void)
+{
+	int socket_id;
+	unsigned int before, after;
+	size_t reserve_size;
+	int rc;
+
+	socket_id = rte_socket_id_by_idx(0);
+	TEST_ASSERT(socket_id >= 0, "no available sockets");
+
+	before = count_fastmem_memzones();
+
+	/*
+	 * Request just over one memzone's worth; this must force
+	 * a second memzone to be reserved.
+	 */
+	reserve_size = FASTMEM_MEMZONE_SIZE + 1;
+	rc = rte_fastmem_reserve(reserve_size, socket_id);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_reserve(%zu) failed: %d",
+		reserve_size, rc);
+
+	after = count_fastmem_memzones();
+	TEST_ASSERT_EQUAL(after - before, 2,
+		"expected 2 new memzones for %zu-byte reserve, got %u",
+		reserve_size, after - before);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_reserve_cumulative(void)
+{
+	int socket_id;
+	unsigned int after_first, after_second;
+	int rc;
+
+	socket_id = rte_socket_id_by_idx(0);
+	TEST_ASSERT(socket_id >= 0, "no available sockets");
+
+	rc = rte_fastmem_reserve(FASTMEM_MEMZONE_SIZE, socket_id);
+	TEST_ASSERT_EQUAL(rc, 0, "first reserve failed: %d", rc);
+
+	after_first = count_fastmem_memzones();
+
+	/*
+	 * A second call requesting the same amount that's already
+	 * reserved must not trigger any new memzone reservation.
+	 */
+	rc = rte_fastmem_reserve(FASTMEM_MEMZONE_SIZE, socket_id);
+	TEST_ASSERT_EQUAL(rc, 0, "second reserve failed: %d", rc);
+
+	after_second = count_fastmem_memzones();
+	TEST_ASSERT_EQUAL(after_first, after_second,
+		"reserve of already-reserved amount added memzones (%u -> %u)",
+		after_first, after_second);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_reserve_invalid_socket(void)
+{
+	int rc;
+
+	rc = rte_fastmem_reserve(1, RTE_MAX_NUMA_NODES);
+	TEST_ASSERT_EQUAL(rc, -EINVAL,
+		"expected -EINVAL for out-of-range socket, got %d", rc);
+
+	rc = rte_fastmem_reserve(1, -2);
+	TEST_ASSERT_EQUAL(rc, -EINVAL,
+		"expected -EINVAL for negative socket, got %d", rc);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_reserve_without_init(void)
+{
+	int rc;
+
+	rc = rte_fastmem_reserve(1, SOCKET_ID_ANY);
+	TEST_ASSERT(rc < 0,
+		"expected failure without init, got %d", rc);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_reserve_any_socket(void)
+{
+	unsigned int before, after;
+	int rc;
+
+	before = count_fastmem_memzones();
+
+	/*
+	 * SOCKET_ID_ANY should succeed on any system with at least
+	 * one configured socket. The allocator picks the caller's
+	 * socket first and falls back to other sockets if needed.
+	 */
+	rc = rte_fastmem_reserve(1, SOCKET_ID_ANY);
+	TEST_ASSERT_EQUAL(rc, 0,
+		"rte_fastmem_reserve(SOCKET_ID_ANY) failed: %d", rc);
+
+	after = count_fastmem_memzones();
+	TEST_ASSERT_EQUAL(after - before, 1,
+		"expected 1 new memzone, got %u", after - before);
+
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Stage 2 tests: allocation and free.
+ */
+
+static int
+test_alloc_too_big(void)
+{
+	void *p;
+	int rc;
+
+	rte_errno = 0;
+	p = rte_fastmem_alloc(rte_fastmem_max_size() + 1, 0, 0);
+	TEST_ASSERT_NULL(p, "alloc above max_size returned non-NULL");
+	TEST_ASSERT_EQUAL(rte_errno, E2BIG,
+		"expected rte_errno=E2BIG, got %d", rte_errno);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_invalid_align(void)
+{
+	void *p;
+	int rc;
+
+	rte_errno = 0;
+	p = rte_fastmem_alloc(16, 3, 0); /* 3 is not a power of 2 */
+	TEST_ASSERT_NULL(p, "alloc with align=3 returned non-NULL");
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL,
+		"expected rte_errno=EINVAL, got %d", rte_errno);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_free_small(void)
+{
+	void *p;
+	int rc;
+
+	p = rte_fastmem_alloc(8, 0, 0);
+	TEST_ASSERT_NOT_NULL(p, "alloc(8) failed: rte_errno=%d", rte_errno);
+
+	/* Writing into the object must not crash. */
+	memset(p, 0xa5, 8);
+
+	rte_fastmem_free(p);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_free_various_sizes(void)
+{
+	static const size_t sizes[] = {
+		1, 8, 16, 17, 63, 64, 128, 1024, 4096,
+		64 * 1024, 256 * 1024, 1024 * 1024,
+	};
+	void *ptrs[RTE_DIM(sizes)];
+	unsigned int i;
+	int rc;
+
+	for (i = 0; i < RTE_DIM(sizes); i++) {
+		ptrs[i] = rte_fastmem_alloc(sizes[i], 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i],
+			"alloc(%zu) failed: rte_errno=%d",
+			sizes[i], rte_errno);
+		memset(ptrs[i], 0x5a, sizes[i]);
+	}
+
+	for (i = 0; i < RTE_DIM(sizes); i++)
+		rte_fastmem_free(ptrs[i]);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_alignment(void)
+{
+	static const size_t aligns[] = {
+		8, 16, 64, 256, 4096, 65536,
+	};
+	unsigned int i;
+	int rc;
+
+	for (i = 0; i < RTE_DIM(aligns); i++) {
+		void *p = rte_fastmem_alloc(1, aligns[i], 0);
+
+		TEST_ASSERT_NOT_NULL(p,
+			"alloc(1, align=%zu) failed: rte_errno=%d",
+			aligns[i], rte_errno);
+		TEST_ASSERT((uintptr_t)p % aligns[i] == 0,
+			"pointer %p not aligned on %zu",
+			p, aligns[i]);
+		rte_fastmem_free(p);
+	}
+
+	/* Default (align=0) gives at least RTE_CACHE_LINE_SIZE. */
+	{
+		void *p = rte_fastmem_alloc(1, 0, 0);
+
+		TEST_ASSERT_NOT_NULL(p,
+			"alloc(1, align=0) failed: rte_errno=%d", rte_errno);
+		TEST_ASSERT((uintptr_t)p % RTE_CACHE_LINE_SIZE == 0,
+			"default-align pointer %p not cache-line aligned",
+			p);
+		rte_fastmem_free(p);
+	}
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_zero_flag(void)
+{
+	uint8_t *p;
+	unsigned int i;
+	int rc;
+	bool all_zero = true;
+
+	/*
+	 * Dirty a slab first by allocating without F_ZERO, writing
+	 * a non-zero pattern, and freeing. A subsequent F_ZERO
+	 * allocation on the same slab must return zeroed memory.
+	 */
+	p = rte_fastmem_alloc(128, 0, 0);
+	TEST_ASSERT_NOT_NULL(p, "priming alloc failed");
+	memset(p, 0xff, 128);
+	rte_fastmem_free(p);
+
+	p = rte_fastmem_alloc(128, 0, RTE_FASTMEM_F_ZERO);
+	TEST_ASSERT_NOT_NULL(p, "F_ZERO alloc failed");
+	for (i = 0; i < 128; i++) {
+		if (p[i] != 0) {
+			all_zero = false;
+			break;
+		}
+	}
+	TEST_ASSERT(all_zero, "F_ZERO returned non-zero byte at offset %u", i);
+
+	rte_fastmem_free(p);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_reuse(void)
+{
+	void *first, *second;
+	int rc;
+
+	first = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NOT_NULL(first, "first alloc failed");
+	rte_fastmem_free(first);
+
+	second = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NOT_NULL(second, "second alloc failed");
+
+	/*
+	 * The slab's free list is LIFO, so the most recently freed
+	 * object is at the head of the list. A subsequent alloc in
+	 * the same class returns it.
+	 */
+	TEST_ASSERT_EQUAL(first, second,
+		"free + alloc did not reuse: first=%p second=%p",
+		first, second);
+
+	rte_fastmem_free(second);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_many_in_class(void)
+{
+	/*
+	 * Allocate more objects in one class than fit in a single
+	 * slab, forcing the bin to pull a second block. This
+	 * exercises the partial->full transition and the cross-slab
+	 * allocation path.
+	 */
+	enum { CLASS_SIZE = 8, COUNT = 300000 };
+	void **ptrs;
+	unsigned int i;
+	int rc;
+
+	ptrs = calloc(COUNT, sizeof(*ptrs));
+	TEST_ASSERT_NOT_NULL(ptrs, "calloc for test ptrs failed");
+
+	for (i = 0; i < COUNT; i++) {
+		ptrs[i] = rte_fastmem_alloc(CLASS_SIZE, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i],
+			"alloc[%u] failed: rte_errno=%d",
+			i, rte_errno);
+	}
+
+	for (i = 0; i < COUNT; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	free(ptrs);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_socket(void)
+{
+	void *p;
+	int socket_id;
+	int rc;
+
+	socket_id = rte_socket_id_by_idx(0);
+	TEST_ASSERT(socket_id >= 0, "no available sockets");
+
+	p = rte_fastmem_alloc_socket(64, 0, 0, socket_id);
+	TEST_ASSERT_NOT_NULL(p,
+		"alloc_socket(%d) failed: rte_errno=%d",
+		socket_id, rte_errno);
+
+	rte_fastmem_free(p);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_block_repurposing(void)
+{
+	void *small, *large;
+	int rc;
+
+	/*
+	 * Allocate and free a small object, forcing a block to be
+	 * assigned to the small class and then returned to the
+	 * free-block pool. A subsequent allocation in a different
+	 * class must be able to reuse that block.
+	 */
+	small = rte_fastmem_alloc(8, 0, 0);
+	TEST_ASSERT_NOT_NULL(small, "small alloc failed");
+	rte_fastmem_free(small);
+
+	large = rte_fastmem_alloc(256 * 1024, 0, 0);
+	TEST_ASSERT_NOT_NULL(large, "large alloc failed");
+	rte_fastmem_free(large);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_block_repurposing_no_growth(void)
+{
+	struct rte_fastmem_stats stats;
+	void *small, *large;
+	uint64_t after_small;
+	int rc;
+
+	/*
+	 * Stronger version of test_alloc_block_repurposing: assert
+	 * that the cross-class allocation does not grow the
+	 * backing memory (bytes_backing stays flat). Because the
+	 * free-block pool is shared across size classes — not
+	 * partitioned per class — the block freed from the small
+	 * class must serve the large allocation without triggering
+	 * a new memzone reservation.
+	 */
+	rc = rte_fastmem_stats(&stats);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_stats() failed: %d", rc);
+	TEST_ASSERT_EQUAL(stats.bytes_backing, (uint64_t)0,
+		"unexpected pre-alloc bytes_backing: %" PRIu64,
+		stats.bytes_backing);
+
+	small = rte_fastmem_alloc(8, 0, 0);
+	TEST_ASSERT_NOT_NULL(small, "small alloc failed");
+
+	rc = rte_fastmem_stats(&stats);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_stats() failed: %d", rc);
+	TEST_ASSERT(stats.bytes_backing > 0,
+		"bytes_backing did not grow on first alloc");
+	after_small = stats.bytes_backing;
+
+	rte_fastmem_free(small);
+	rte_fastmem_cache_flush();
+
+	large = rte_fastmem_alloc(256 * 1024, 0, 0);
+	TEST_ASSERT_NOT_NULL(large,
+		"large alloc failed: rte_errno=%d", rte_errno);
+
+	rc = rte_fastmem_stats(&stats);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_fastmem_stats() failed: %d", rc);
+	TEST_ASSERT_EQUAL(stats.bytes_backing, after_small,
+		"cross-class alloc grew backing memory from %" PRIu64
+		" to %" PRIu64,
+		after_small, stats.bytes_backing);
+
+	rte_fastmem_free(large);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	/* Must be a no-op, not a crash. */
+	rte_fastmem_free(NULL);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_content_integrity(void)
+{
+	/*
+	 * Allocate a batch of objects, fill each with a distinct
+	 * byte pattern, then verify none of the patterns overlap.
+	 * This catches header overwrites (slab header corrupted by
+	 * object access) and slot-overlap bugs (two pointers pointing
+	 * at overlapping slots).
+	 */
+	enum { N = 256, SIZE = 128 };
+	uint8_t *ptrs[N];
+	unsigned int i, j;
+	int rc;
+
+	for (i = 0; i < N; i++) {
+		ptrs[i] = rte_fastmem_alloc(SIZE, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "alloc[%u] failed", i);
+		memset(ptrs[i], (int)i, SIZE);
+	}
+
+	for (i = 0; i < N; i++)
+		for (j = 0; j < SIZE; j++)
+			TEST_ASSERT_EQUAL(ptrs[i][j], (uint8_t)i,
+				"corruption at ptrs[%u][%u]: got 0x%x, want 0x%x",
+				i, j, ptrs[i][j], (uint8_t)i);
+
+	for (i = 0; i < N; i++)
+		rte_fastmem_free(ptrs[i]);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_align_too_big(void)
+{
+	void *p;
+	int rc;
+
+	/*
+	 * A small size with an alignment larger than the maximum
+	 * size class cannot be served. The class selected must be
+	 * large enough for the alignment, but no such class exists.
+	 */
+	rte_errno = 0;
+	p = rte_fastmem_alloc(1, rte_fastmem_max_size() * 2, 0);
+	TEST_ASSERT_NULL(p,
+		"alloc with align>max_size returned non-NULL");
+	TEST_ASSERT_EQUAL(rte_errno, E2BIG,
+		"expected rte_errno=E2BIG, got %d", rte_errno);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_align_one(void)
+{
+	void *p;
+	int rc;
+
+	/* align=1 is a valid power of 2 and must be accepted. */
+	p = rte_fastmem_alloc(8, 1, 0);
+	TEST_ASSERT_NOT_NULL(p, "alloc(8, 1) failed: rte_errno=%d",
+		rte_errno);
+	rte_fastmem_free(p);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_socket_numa_placement(void)
+{
+	void *p;
+	int socket_id;
+	struct rte_memseg *ms;
+	int rc;
+
+	socket_id = rte_socket_id_by_idx(0);
+	TEST_ASSERT(socket_id >= 0, "no available sockets");
+
+	p = rte_fastmem_alloc_socket(64, 0, 0, socket_id);
+	TEST_ASSERT_NOT_NULL(p,
+		"alloc_socket(%d) failed: rte_errno=%d",
+		socket_id, rte_errno);
+
+	/*
+	 * Walk the memory to find the memseg for this pointer and
+	 * verify its socket. Skip the check if lookup fails (e.g.,
+	 * --no-huge mode may not populate memsegs for fastmem's
+	 * allocations in a way that rte_mem_virt2memseg can find).
+	 */
+	ms = rte_mem_virt2memseg(p, NULL);
+	if (ms != NULL) {
+		TEST_ASSERT_EQUAL(ms->socket_id, socket_id,
+			"alloc on socket %d landed on socket %d",
+			socket_id, ms->socket_id);
+	}
+
+	rte_fastmem_free(p);
+
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Stage 3 tests: per-lcore caches.
+ */
+
+static int
+test_cache_flush(void)
+{
+	void *p;
+	int rc;
+
+	/*
+	 * Alloc and free one object, leaving it in the cache. Then
+	 * flush and verify that a subsequent alloc may or may not
+	 * return the same pointer (not asserting same/different —
+	 * just checking that flush does not crash and a follow-up
+	 * alloc still works).
+	 */
+	p = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NOT_NULL(p, "first alloc failed");
+	rte_fastmem_free(p);
+
+	rte_fastmem_cache_flush();
+
+	/* Flush again — must be idempotent. */
+	rte_fastmem_cache_flush();
+
+	p = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NOT_NULL(p, "post-flush alloc failed");
+	rte_fastmem_free(p);
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_cache_flush_without_init(void)
+{
+	/* Must be a no-op, not a crash. */
+	rte_fastmem_cache_flush();
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_cache_exceeds_capacity(void)
+{
+	/*
+	 * Free more objects at a single size class than the cache
+	 * capacity (64 for classes <= 4 KiB). This forces the
+	 * cache-drain slow path and verifies no corruption.
+	 */
+	enum { COUNT = 200, SIZE = 64 };
+	void *ptrs[COUNT];
+	unsigned int i;
+	int rc;
+
+	for (i = 0; i < COUNT; i++) {
+		ptrs[i] = rte_fastmem_alloc(SIZE, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i],
+			"alloc[%u] failed: rte_errno=%d", i, rte_errno);
+	}
+
+	for (i = 0; i < COUNT; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	/* Re-alloc the same count should still work. */
+	for (i = 0; i < COUNT; i++) {
+		ptrs[i] = rte_fastmem_alloc(SIZE, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i],
+			"re-alloc[%u] failed: rte_errno=%d", i, rte_errno);
+	}
+
+	for (i = 0; i < COUNT; i++)
+		rte_fastmem_free(ptrs[i]);
+
+
+	return TEST_SUCCESS;
+}
+
+struct non_eal_args {
+	int ok;
+	char pad[64];
+};
+
+static uint32_t
+non_eal_thread_main(void *arg)
+{
+	struct non_eal_args *args = arg;
+	uint8_t *p;
+
+	p = rte_fastmem_alloc(128, 0, 0);
+	if (p == NULL)
+		return 1;
+
+	memset(p, 0x7e, 128);
+
+	rte_fastmem_free(p);
+
+	args->ok = 1;
+	return 0;
+}
+
+static int
+test_non_eal_thread(void)
+{
+	rte_thread_t thread_id;
+	struct non_eal_args args = { 0 };
+	int rc;
+
+	rc = rte_thread_create(&thread_id, NULL, non_eal_thread_main, &args);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_thread_create() failed: %d", rc);
+
+	rc = rte_thread_join(thread_id, NULL);
+	TEST_ASSERT_EQUAL(rc, 0, "rte_thread_join() failed: %d", rc);
+
+	TEST_ASSERT_EQUAL(args.ok, 1,
+		"non-EAL thread did not complete alloc/free successfully");
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_cache_flush_returns_memory(void)
+{
+	/*
+	 * When an entire slab's worth of objects is freed, the
+	 * slab's block is returned to the free-block pool and can
+	 * be reassigned to another size class. Verify the cache
+	 * does not permanently hold objects that prevent this.
+	 *
+	 * Allocate enough objects in one class to force multiple
+	 * slabs, free them all, then flush the cache. After the
+	 * flush, all cached objects are drained to their bins and
+	 * empty slabs are returned to the block pool.
+	 */
+	enum { N = 200, SIZE = 64 };
+	void *ptrs[N];
+	unsigned int i;
+	int rc;
+
+	for (i = 0; i < N; i++) {
+		ptrs[i] = rte_fastmem_alloc(SIZE, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "alloc[%u] failed", i);
+	}
+	for (i = 0; i < N; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	rte_fastmem_cache_flush();
+
+	/*
+	 * An allocation in a completely different class should
+	 * succeed now, having access to any blocks freed by the
+	 * flush.
+	 */
+	{
+		void *other = rte_fastmem_alloc(65536, 0, 0);
+
+		TEST_ASSERT_NOT_NULL(other,
+			"post-flush cross-class alloc failed");
+		rte_fastmem_free(other);
+	}
+
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_bulk_basic(void)
+{
+	enum { N = 32 };
+	void *ptrs[N];
+	int rc;
+
+	rc = rte_fastmem_alloc_bulk(ptrs, N, 64, 0, 0);
+	TEST_ASSERT_EQUAL(rc, 0, "alloc_bulk failed: %d", rc);
+
+	/* Verify all pointers are non-NULL and distinct. */
+	for (unsigned int i = 0; i < N; i++) {
+		TEST_ASSERT_NOT_NULL(ptrs[i], "ptrs[%u] is NULL", i);
+		for (unsigned int j = 0; j < i; j++)
+			TEST_ASSERT(ptrs[i] != ptrs[j],
+				"ptrs[%u] == ptrs[%u]", i, j);
+	}
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_bulk_zero_flag(void)
+{
+	enum { N = 8, SIZE = 128 };
+	void *ptrs[N];
+	int rc;
+
+	rc = rte_fastmem_alloc_bulk(ptrs, N, SIZE, 0, RTE_FASTMEM_F_ZERO);
+	TEST_ASSERT_EQUAL(rc, 0, "alloc_bulk failed: %d", rc);
+
+	for (unsigned int i = 0; i < N; i++) {
+		uint8_t *p = ptrs[i];
+
+		for (unsigned int b = 0; b < SIZE; b++)
+			TEST_ASSERT_EQUAL(p[b], 0,
+				"ptrs[%u][%u] != 0", i, b);
+	}
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_bulk_exceeds_cache(void)
+{
+	/* Allocate more than cache capacity (64) in one bulk call. */
+	enum { N = 128 };
+	void *ptrs[N];
+	int rc;
+
+	rc = rte_fastmem_alloc_bulk(ptrs, N, 64, 0, 0);
+	TEST_ASSERT_EQUAL(rc, 0, "alloc_bulk(%u) failed: %d", N, rc);
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_alloc_bulk_socket(void)
+{
+	enum { N = 16 };
+	void *ptrs[N];
+	int socket_id;
+	int rc;
+
+	socket_id = rte_socket_id_by_idx(0);
+	TEST_ASSERT(socket_id >= 0, "no sockets");
+
+	rc = rte_fastmem_alloc_bulk_socket(ptrs, N, 64, 0, 0, socket_id);
+	TEST_ASSERT_EQUAL(rc, 0, "alloc_bulk_socket failed: %d", rc);
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	/* SOCKET_ID_ANY */
+	rc = rte_fastmem_alloc_bulk_socket(ptrs, N, 64, 0, 0, SOCKET_ID_ANY);
+	TEST_ASSERT_EQUAL(rc, 0, "alloc_bulk_socket(ANY) failed: %d", rc);
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_bulk(void)
+{
+	enum { N = 64 };
+	void *ptrs[N];
+	int rc;
+
+	/* Allocate individually, free in bulk. */
+	for (unsigned int i = 0; i < N; i++) {
+		ptrs[i] = rte_fastmem_alloc(64, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "alloc[%u] failed", i);
+	}
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	/* Verify memory is reusable. */
+	for (unsigned int i = 0; i < N; i++) {
+		ptrs[i] = rte_fastmem_alloc(64, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "re-alloc[%u] failed", i);
+	}
+
+	rte_fastmem_free_bulk(ptrs, N);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_classes(void)
+{
+	size_t sizes[32];
+	unsigned int n;
+
+	n = rte_fastmem_classes(NULL);
+	TEST_ASSERT_EQUAL(n, 18u, "expected 18 classes, got %u", n);
+
+	n = rte_fastmem_classes(sizes);
+	TEST_ASSERT_EQUAL(n, 18u, "expected 18 classes, got %u", n);
+	TEST_ASSERT_EQUAL(sizes[0], (size_t)8, "class 0 != 8");
+	TEST_ASSERT_EQUAL(sizes[n - 1], (size_t)(1 << 20),
+		"last class != 1 MiB");
+
+	for (unsigned int i = 0; i < n; i++) {
+		TEST_ASSERT(sizes[i] != 0 && (sizes[i] & (sizes[i] - 1)) == 0,
+			"class %u size %zu not power of 2", i, sizes[i]);
+		if (i > 0)
+			TEST_ASSERT(sizes[i] > sizes[i - 1],
+				"classes not ascending at %u", i);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_stats_class(void)
+{
+	enum { N = 10 };
+	struct rte_fastmem_class_stats cs;
+	void *ptrs[N];
+	int rc;
+
+	for (unsigned int i = 0; i < N; i++) {
+		ptrs[i] = rte_fastmem_alloc(64, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "alloc[%u] failed", i);
+	}
+
+	rc = rte_fastmem_stats_class(64, &cs);
+	TEST_ASSERT_EQUAL(rc, 0, "stats_class failed: %d", rc);
+	TEST_ASSERT_EQUAL(cs.class_size, (size_t)64, "wrong class_size");
+	TEST_ASSERT(cs.alloc_cache_hits + cs.alloc_cache_misses == N,
+		"alloc count != N: hits=%" PRIu64 " misses=%" PRIu64,
+		cs.alloc_cache_hits, cs.alloc_cache_misses);
+	TEST_ASSERT_EQUAL(cs.in_use, (uint64_t)N, "in_use != N");
+
+	for (unsigned int i = 0; i < N; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	rc = rte_fastmem_stats_class(64, &cs);
+	TEST_ASSERT_EQUAL(rc, 0, "stats_class after free failed: %d", rc);
+	TEST_ASSERT_EQUAL(cs.in_use, (uint64_t)0, "in_use != 0 after free");
+
+	/* Invalid class size. */
+	rc = rte_fastmem_stats_class(13, &cs);
+	TEST_ASSERT_EQUAL(rc, -EINVAL, "expected -EINVAL for bad size");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_stats_lcore(void)
+{
+	struct rte_fastmem_lcore_stats ls;
+	void *ptr;
+	int rc;
+
+	ptr = rte_fastmem_alloc(128, 0, 0);
+	TEST_ASSERT_NOT_NULL(ptr, "alloc failed");
+
+	rc = rte_fastmem_stats_lcore(rte_lcore_id(), &ls);
+	TEST_ASSERT_EQUAL(rc, 0, "stats_lcore failed: %d", rc);
+	TEST_ASSERT(ls.alloc_cache_hits + ls.alloc_cache_misses > 0,
+		"no alloc activity on this lcore");
+
+	rte_fastmem_free(ptr);
+
+	rc = rte_fastmem_stats_lcore(rte_lcore_id(), &ls);
+	TEST_ASSERT_EQUAL(rc, 0, "stats_lcore after free failed: %d", rc);
+	TEST_ASSERT(ls.free_cache_hits + ls.free_cache_misses > 0,
+		"no free activity on this lcore");
+
+	/* Invalid lcore. */
+	rc = rte_fastmem_stats_lcore(RTE_MAX_LCORE, &ls);
+	TEST_ASSERT_EQUAL(rc, -EINVAL, "expected -EINVAL for bad lcore");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_stats_lcore_class(void)
+{
+	struct rte_fastmem_lcore_class_stats lcs;
+	void *ptr;
+	int rc;
+
+	ptr = rte_fastmem_alloc(256, 0, 0);
+	TEST_ASSERT_NOT_NULL(ptr, "alloc failed");
+
+	rc = rte_fastmem_stats_lcore_class(rte_lcore_id(), 256, &lcs);
+	TEST_ASSERT_EQUAL(rc, 0, "stats_lcore_class failed: %d", rc);
+	TEST_ASSERT_EQUAL(lcs.class_size, (size_t)256, "wrong class_size");
+	TEST_ASSERT(lcs.alloc_cache_hits + lcs.alloc_cache_misses > 0,
+		"no alloc activity");
+
+	rte_fastmem_free(ptr);
+	return TEST_SUCCESS;
+}
+
+static int
+test_stats_reset(void)
+{
+	struct rte_fastmem_stats gs;
+	void *ptr;
+	int rc;
+
+	ptr = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NOT_NULL(ptr, "alloc failed");
+	rte_fastmem_free(ptr);
+
+	rte_fastmem_stats_reset();
+
+	rc = rte_fastmem_stats(&gs);
+	TEST_ASSERT_EQUAL(rc, 0, "stats failed: %d", rc);
+	TEST_ASSERT_EQUAL(gs.alloc_total, (uint64_t)0,
+		"alloc_total not zero after reset");
+	TEST_ASSERT_EQUAL(gs.free_total, (uint64_t)0,
+		"free_total not zero after reset");
+
+	return TEST_SUCCESS;
+}
+
+
+#define MIXED_LONG_LIVED_COUNT 25
+#define MIXED_SHORT_LIVED_ITERS 1000
+#define MIXED_MIN_LCORES 3
+
+static const size_t mixed_long_sizes[] = { 64, 256, 4096 };
+static const size_t mixed_short_sizes[] = { 8, 16, 32, 64, 128, 256, 512, 1024 };
+
+struct mixed_worker_args {
+	uint32_t seed;
+	int result;
+};
+
+static uint32_t
+xorshift32(uint32_t *state)
+{
+	uint32_t x = *state;
+
+	x ^= x << 13;
+	x ^= x >> 17;
+	x ^= x << 5;
+	*state = x;
+	return x;
+}
+
+static int
+mixed_worker(void *arg)
+{
+	struct mixed_worker_args *args = arg;
+	uint32_t seed = args->seed;
+	void *long_lived[MIXED_LONG_LIVED_COUNT];
+	size_t long_sizes[MIXED_LONG_LIVED_COUNT];
+	unsigned int i;
+
+	/* Allocate long-lived objects of mixed sizes. */
+	for (i = 0; i < MIXED_LONG_LIVED_COUNT; i++) {
+		long_sizes[i] = mixed_long_sizes[i % RTE_DIM(mixed_long_sizes)];
+		long_lived[i] = rte_fastmem_alloc(long_sizes[i], 0, 0);
+		if (long_lived[i] == NULL) {
+			args->result = TEST_FAILED;
+			return -1;
+		}
+		memset(long_lived[i], (int)(i + 1), long_sizes[i]);
+	}
+
+	/* Rapidly cycle short-lived objects. */
+	for (i = 0; i < MIXED_SHORT_LIVED_ITERS; i++) {
+		size_t sz = mixed_short_sizes[xorshift32(&seed) %
+					      RTE_DIM(mixed_short_sizes)];
+		uint8_t pattern = (uint8_t)(i & 0xff);
+		uint8_t *p;
+
+		p = rte_fastmem_alloc(sz, 0, 0);
+		if (p == NULL) {
+			args->result = TEST_FAILED;
+			return -1;
+		}
+		memset(p, pattern, sz);
+
+		/* Verify before freeing. */
+		for (size_t j = 0; j < sz; j++) {
+			if (p[j] != pattern) {
+				args->result = TEST_FAILED;
+				return -1;
+			}
+		}
+		rte_fastmem_free(p);
+	}
+
+	/* Verify long-lived objects are still intact. */
+	for (i = 0; i < MIXED_LONG_LIVED_COUNT; i++) {
+		uint8_t *bytes = long_lived[i];
+		uint8_t expected = (uint8_t)(i + 1);
+
+		for (size_t j = 0; j < long_sizes[i]; j++) {
+			if (bytes[j] != expected) {
+				args->result = TEST_FAILED;
+				return -1;
+			}
+		}
+		rte_fastmem_free(long_lived[i]);
+	}
+
+	args->result = TEST_SUCCESS;
+	return 0;
+}
+
+static int
+test_mixed_lifetimes_multi_lcore(void)
+{
+	struct mixed_worker_args args[RTE_MAX_LCORE];
+	unsigned int lcore_id;
+	unsigned int count = 0;
+	struct rte_fastmem_stats stats;
+	int rc;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id)
+		count++;
+
+	if (count < MIXED_MIN_LCORES) {
+		printf("Not enough worker lcores (%u < %u), skipping\n",
+		       count, MIXED_MIN_LCORES);
+		return TEST_SKIPPED;
+	}
+
+	/* Launch workers with distinct seeds. */
+	uint32_t seed = 0xdeadbeef;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id) {
+		args[lcore_id].seed = seed;
+		args[lcore_id].result = TEST_FAILED;
+		seed += 0x12345678;
+		rte_eal_remote_launch(mixed_worker, &args[lcore_id], lcore_id);
+	}
+
+	rte_eal_mp_wait_lcore();
+
+	/* Check all workers succeeded. */
+	RTE_LCORE_FOREACH_WORKER(lcore_id) {
+		TEST_ASSERT_EQUAL(args[lcore_id].result, TEST_SUCCESS,
+			"worker on lcore %u failed", lcore_id);
+	}
+
+	/* Verify no memory leak. */
+	rc = rte_fastmem_stats(&stats);
+	TEST_ASSERT_EQUAL(rc, 0, "stats failed: %d", rc);
+	TEST_ASSERT_EQUAL(stats.bytes_in_use, (uint64_t)0,
+		"bytes_in_use not zero after test: %" PRIu64,
+		stats.bytes_in_use);
+
+
+	return TEST_SUCCESS;
+}
+
+
+/*
+ * Memory limit tests.
+ *
+ * FASTMEM_MEMZONE_SIZE is 128 MiB. We use a limit of 128 MiB
+ * (one memzone) for most tests, and large objects (256 KiB) to
+ * exhaust slabs quickly.
+ */
+
+#define LIMIT_ONE_MZ ((size_t)128 << 20)
+#define LIMIT_OBJ_SIZE ((size_t)256 * 1024)
+
+static int
+test_memory_limit_basic(void)
+{
+	int rc;
+
+	rc = rte_fastmem_set_limit(SOCKET_ID_ANY, LIMIT_ONE_MZ);
+	TEST_ASSERT_EQUAL(rc, 0, "set_memory_limit failed: %d", rc);
+
+	const size_t got = rte_fastmem_get_limit(0);
+	TEST_ASSERT_EQUAL(got, LIMIT_ONE_MZ,
+		"get_memory_limit mismatch: %zu", got);
+
+	rc = rte_fastmem_reserve(LIMIT_ONE_MZ, SOCKET_ID_ANY);
+	TEST_ASSERT_EQUAL(rc, 0, "first reserve failed: %d", rc);
+
+	rc = rte_fastmem_reserve(LIMIT_ONE_MZ + 1, SOCKET_ID_ANY);
+	TEST_ASSERT(rc < 0, "second reserve should have failed");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_alloc_exhaustion(void)
+{
+	const unsigned int max_ptrs = 1024;
+	void *ptrs[max_ptrs];
+	unsigned int count = 0;
+	int rc;
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, LIMIT_ONE_MZ);
+
+	for (count = 0; count < max_ptrs; count++) {
+		ptrs[count] = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+		if (ptrs[count] == NULL)
+			break;
+	}
+
+	TEST_ASSERT(count > 0, "should have allocated at least one");
+	TEST_ASSERT(count < max_ptrs, "should have hit the limit");
+	TEST_ASSERT_EQUAL(rte_errno, ENOMEM, "expected ENOMEM, got %d", rte_errno);
+
+	rte_fastmem_free(ptrs[count - 1]);
+	void *p = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+	TEST_ASSERT_NOT_NULL(p, "alloc after free should succeed");
+	rte_fastmem_free(p);
+
+	for (unsigned int i = 0; i < count - 1; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_zero_blocks_growth(void)
+{
+	int rc;
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, 0);
+
+	rc = rte_fastmem_reserve(1, SOCKET_ID_ANY);
+	TEST_ASSERT(rc < 0, "reserve with limit=0 should fail");
+
+	void *p = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NULL(p, "alloc with limit=0 should fail");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_below_current(void)
+{
+	int rc;
+
+	rc = rte_fastmem_reserve(LIMIT_ONE_MZ, SOCKET_ID_ANY);
+	TEST_ASSERT_EQUAL(rc, 0, "reserve failed: %d", rc);
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, 1);
+
+	void *p = rte_fastmem_alloc(64, 0, 0);
+	TEST_ASSERT_NOT_NULL(p, "alloc from existing backing should work");
+	rte_fastmem_free(p);
+
+	rc = rte_fastmem_reserve(LIMIT_ONE_MZ * 2, SOCKET_ID_ANY);
+	TEST_ASSERT(rc < 0, "growth beyond limit should fail");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_socket_id_any(void)
+{
+	rte_fastmem_set_limit(SOCKET_ID_ANY, 42);
+
+	for (unsigned int i = 0; i < rte_socket_count(); i++) {
+		const int sid = rte_socket_id_by_idx(i);
+		const size_t lim = rte_fastmem_get_limit(sid);
+
+		TEST_ASSERT_EQUAL(lim, (size_t)42,
+			"socket %d limit mismatch: %zu", sid, lim);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_unlimited(void)
+{
+	int rc;
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, 0);
+	rte_fastmem_set_limit(SOCKET_ID_ANY, SIZE_MAX);
+
+	rc = rte_fastmem_reserve(LIMIT_ONE_MZ, SOCKET_ID_ANY);
+	TEST_ASSERT_EQUAL(rc, 0, "reserve after reset failed: %d", rc);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_alloc_integrity_under_oom(void)
+{
+	const unsigned int n = 128;
+	const size_t obj_size = 1024;
+	uint8_t *ptrs[n];
+	const unsigned int extra_max = 1024;
+	void *extra[extra_max];
+	unsigned int n_extra = 0;
+	unsigned int i;
+	int rc;
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, LIMIT_ONE_MZ);
+
+	for (i = 0; i < n; i++) {
+		ptrs[i] = rte_fastmem_alloc(obj_size, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "alloc[%u] failed", i);
+		memset(ptrs[i], (int)(i & 0xff), obj_size);
+	}
+
+	/* Exhaust remaining backing with large objects. */
+	for (n_extra = 0; n_extra < extra_max; n_extra++) {
+		extra[n_extra] = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+		if (extra[n_extra] == NULL)
+			break;
+	}
+
+	/* Verify original objects are intact. */
+	for (i = 0; i < n; i++) {
+		const uint8_t expected = (uint8_t)(i & 0xff);
+		for (unsigned int j = 0; j < obj_size; j++)
+			TEST_ASSERT_EQUAL(ptrs[i][j], expected,
+				"corruption at [%u][%u]", i, j);
+	}
+
+	for (i = 0; i < n; i++)
+		rte_fastmem_free(ptrs[i]);
+	for (i = 0; i < n_extra; i++)
+		rte_fastmem_free(extra[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_bulk_alloc_oom(void)
+{
+	const unsigned int bulk_n = 64;
+	const unsigned int drain_max = 512;
+	void *ptrs[bulk_n];
+	void *drain[drain_max];
+	unsigned int drained = 0;
+	int rc;
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, LIMIT_ONE_MZ);
+
+	for (drained = 0; drained < drain_max; drained++) {
+		drain[drained] = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+		if (drain[drained] == NULL)
+			break;
+	}
+
+	/* Free a few — enough for some but not bulk_n objects. */
+	const unsigned int freed = RTE_MIN(drained, 4u);
+	for (unsigned int i = 0; i < freed; i++)
+		rte_fastmem_free(drain[--drained]);
+
+	rc = rte_fastmem_alloc_bulk(ptrs, bulk_n, LIMIT_OBJ_SIZE, 0, 0);
+	TEST_ASSERT(rc < 0, "bulk alloc should fail");
+
+	for (unsigned int i = 0; i < drained; i++)
+		rte_fastmem_free(drain[i]);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_memory_limit_recovery_after_free(void)
+{
+	const unsigned int max_ptrs = 512;
+	void *ptrs[max_ptrs];
+	unsigned int count = 0;
+	int rc;
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, LIMIT_ONE_MZ);
+
+	for (count = 0; count < max_ptrs; count++) {
+		ptrs[count] = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+		if (ptrs[count] == NULL)
+			break;
+	}
+	TEST_ASSERT(count > 0 && count < max_ptrs,
+		"expected partial fill, got %u", count);
+
+	const unsigned int half = count / 2;
+	for (unsigned int i = 0; i < half; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	for (unsigned int i = 0; i < half; i++) {
+		ptrs[i] = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+		TEST_ASSERT_NOT_NULL(ptrs[i], "recovery alloc[%u] failed", i);
+	}
+
+	for (unsigned int i = 0; i < count; i++)
+		rte_fastmem_free(ptrs[i]);
+
+	return TEST_SUCCESS;
+}
+
+struct limit_worker_args {
+	unsigned int alloc_count;
+	int result;
+};
+
+static int
+limit_worker(void *arg)
+{
+	struct limit_worker_args *args = arg;
+	const unsigned int max_ptrs = 128;
+	void *ptrs[max_ptrs];
+	unsigned int i;
+
+	args->alloc_count = 0;
+
+	for (i = 0; i < max_ptrs; i++) {
+		ptrs[i] = rte_fastmem_alloc(LIMIT_OBJ_SIZE, 0, 0);
+		if (ptrs[i] == NULL)
+			break;
+		memset(ptrs[i], 0xab, LIMIT_OBJ_SIZE);
+		args->alloc_count++;
+	}
+
+	for (unsigned int j = 0; j < args->alloc_count; j++) {
+		uint8_t *bytes = ptrs[j];
+		for (size_t k = 0; k < LIMIT_OBJ_SIZE; k++) {
+			if (bytes[k] != 0xab) {
+				args->result = TEST_FAILED;
+				return -1;
+			}
+		}
+		rte_fastmem_free(ptrs[j]);
+	}
+
+	args->result = TEST_SUCCESS;
+	return 0;
+}
+
+static int
+test_memory_limit_multi_lcore_oom(void)
+{
+	struct limit_worker_args args[RTE_MAX_LCORE];
+	unsigned int lcore_id;
+	unsigned int worker_count = 0;
+	int rc;
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id)
+		worker_count++;
+
+	if (worker_count < 2) {
+		printf("Not enough workers (%u < 2), skipping\n", worker_count);
+		return TEST_SKIPPED;
+	}
+
+	rte_fastmem_set_limit(SOCKET_ID_ANY, LIMIT_ONE_MZ);
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id) {
+		args[lcore_id].result = TEST_FAILED;
+		rte_eal_remote_launch(limit_worker, &args[lcore_id], lcore_id);
+	}
+
+	rte_eal_mp_wait_lcore();
+
+	RTE_LCORE_FOREACH_WORKER(lcore_id) {
+		TEST_ASSERT_EQUAL(args[lcore_id].result, TEST_SUCCESS,
+			"worker on lcore %u failed", lcore_id);
+	}
+
+	struct rte_fastmem_stats stats;
+	rte_fastmem_stats(&stats);
+	TEST_ASSERT_EQUAL(stats.bytes_in_use, (uint64_t)0,
+		"bytes_in_use not zero: %" PRIu64, stats.bytes_in_use);
+
+	return TEST_SUCCESS;
+}
+
+static int
+fastmem_setup(void)
+{
+	return rte_fastmem_init();
+}
+
+static void
+fastmem_teardown(void)
+{
+	rte_fastmem_deinit();
+}
+
+static struct unit_test_suite fastmem_lifecycle_testsuite = {
+	.suite_name = "fastmem lifecycle tests",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(test_init_deinit),
+		TEST_CASE(test_init_is_not_idempotent),
+		TEST_CASE(test_deinit_without_init),
+		TEST_CASE(test_max_size),
+		TEST_CASE(test_reserve_without_init),
+		TEST_CASE(test_cache_flush_without_init),
+		TEST_CASE(test_classes),
+		TEST_CASES_END()
+	}
+};
+
+static struct unit_test_suite fastmem_functional_testsuite = {
+	.suite_name = "fastmem functional tests",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_reserve_small),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_reserve_multiple_memzones),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_reserve_cumulative),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_reserve_invalid_socket),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_reserve_any_socket),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_too_big),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_invalid_align),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_free_small),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_free_various_sizes),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_alignment),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_zero_flag),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_reuse),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_many_in_class),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_socket),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_block_repurposing),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_block_repurposing_no_growth),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_free_null),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_content_integrity),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_align_too_big),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_align_one),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_socket_numa_placement),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_cache_flush),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_cache_exceeds_capacity),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_non_eal_thread),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_cache_flush_returns_memory),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_bulk_basic),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_bulk_zero_flag),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_bulk_exceeds_cache),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_alloc_bulk_socket),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_free_bulk),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_stats_class),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_stats_lcore),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_stats_lcore_class),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_stats_reset),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_mixed_lifetimes_multi_lcore),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_basic),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_alloc_exhaustion),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_zero_blocks_growth),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_below_current),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_socket_id_any),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_unlimited),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_alloc_integrity_under_oom),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_bulk_alloc_oom),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_recovery_after_free),
+		TEST_CASE_ST(fastmem_setup, fastmem_teardown,
+			test_memory_limit_multi_lcore_oom),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_fastmem(void)
+{
+	int rc;
+
+	rc = unit_test_suite_runner(&fastmem_lifecycle_testsuite);
+	if (rc != 0)
+		return rc;
+
+	return unit_test_suite_runner(&fastmem_functional_testsuite);
+}
+
+REGISTER_FAST_TEST(fastmem_autotest, NOHUGE_OK, ASAN_OK, test_fastmem);
diff --git a/app/test/test_fastmem_perf.c b/app/test/test_fastmem_perf.c
new file mode 100644
index 0000000000..9200847847
--- /dev/null
+++ b/app/test/test_fastmem_perf.c
@@ -0,0 +1,997 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Ericsson AB
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_malloc.h>
+#include <rte_mempool.h>
+#include <rte_stdatomic.h>
+
+#include <rte_fastmem.h>
+
+#include "test.h"
+
+#define TEST_LOG(...) printf(__VA_ARGS__)
+
+static const size_t SIZES[] = { 8, 64, 256, 1024, 4096 };
+#define N_SIZES RTE_DIM(SIZES)
+
+/* Number of ops for warmup and measurement. */
+#define WARMUP_OPS 20000u
+#define MEASURE_OPS 2000000u
+
+/* Buffer for scenarios that allocate N then free N. */
+#define BATCH_N 256
+
+/*
+ * Allocator vtable: a thin adapter exposing alloc / free /
+ * per-allocator setup/teardown. Each scenario calls these
+ * indirectly so the same timing loop serves all allocators.
+ */
+struct allocator {
+	const char *name;
+	int (*setup)(size_t size, unsigned int n_max);
+	void (*teardown)(void);
+	void *(*alloc)(void);
+	void (*free_obj)(void *ptr);
+	int (*alloc_bulk)(void **ptrs, unsigned int n);
+	void (*free_bulk)(void **ptrs, unsigned int n);
+};
+
+/* Fastmem adapter -------------------------------------------------- */
+
+static size_t fastmem_size;
+
+static int
+fastmem_setup(size_t size, unsigned int n_max __rte_unused)
+{
+	fastmem_size = size;
+	return 0;
+}
+
+static void
+fastmem_teardown(void)
+{
+	rte_fastmem_cache_flush();
+}
+
+static void * __rte_noinline
+fastmem_alloc(void)
+{
+	return rte_fastmem_alloc(fastmem_size, 0, 0);
+}
+
+static void __rte_noinline
+fastmem_free(void *ptr)
+{
+	rte_fastmem_free(ptr);
+}
+
+/* Mempool adapter -------------------------------------------------- */
+
+static struct rte_mempool *mempool_pool;
+
+static int
+mempool_setup(size_t size, unsigned int n_max)
+{
+	char name[RTE_MEMPOOL_NAMESIZE];
+	unsigned int cache_size;
+
+	/*
+	 * Pool size must accommodate the full batch burst plus
+	 * per-lcore cache capacity. Use mempool's default cache
+	 * size so we're measuring its standard hot path.
+	 */
+	cache_size = RTE_MEMPOOL_CACHE_MAX_SIZE;
+
+	snprintf(name, sizeof(name), "fmperf_mp_%zu", size);
+	mempool_pool = rte_mempool_create(name, n_max + cache_size * 2,
+			size, cache_size, 0, NULL, NULL, NULL, NULL,
+			SOCKET_ID_ANY, 0);
+	if (mempool_pool == NULL) {
+		TEST_LOG("mempool_create(%zu) failed\n", size);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void
+mempool_teardown(void)
+{
+	rte_mempool_free(mempool_pool);
+	mempool_pool = NULL;
+}
+
+static void * __rte_noinline
+mempool_alloc_one(void)
+{
+	void *obj = NULL;
+
+	if (rte_mempool_get(mempool_pool, &obj) < 0)
+		return NULL;
+	return obj;
+}
+
+static void __rte_noinline
+mempool_free_one(void *ptr)
+{
+	rte_mempool_put(mempool_pool, ptr);
+}
+
+/* rte_malloc adapter ----------------------------------------------- */
+
+static size_t malloc_size;
+
+static int
+malloc_setup(size_t size, unsigned int n_max __rte_unused)
+{
+	malloc_size = size;
+	return 0;
+}
+
+static void
+malloc_teardown(void)
+{
+}
+
+static void * __rte_noinline
+malloc_alloc(void)
+{
+	return rte_malloc(NULL, malloc_size, 0);
+}
+
+static void __rte_noinline
+malloc_free(void *ptr)
+{
+	rte_free(ptr);
+}
+
+/* libc (glibc) malloc adapter -------------------------------------- */
+
+static size_t libc_size;
+
+static int
+libc_setup(size_t size, unsigned int n_max __rte_unused)
+{
+	/*
+	 * Round up to cache-line alignment to match the other
+	 * allocators' default alignment guarantees and keep the
+	 * comparison honest. aligned_alloc() requires size to be
+	 * a multiple of the alignment.
+	 */
+	libc_size = RTE_ALIGN_CEIL(size, RTE_CACHE_LINE_SIZE);
+	return 0;
+}
+
+static void
+libc_teardown(void)
+{
+}
+
+static void * __rte_noinline
+libc_alloc(void)
+{
+	return aligned_alloc(RTE_CACHE_LINE_SIZE, libc_size);
+}
+
+static void __rte_noinline
+libc_free(void *ptr)
+{
+	free(ptr);
+}
+
+/* Bulk adapters ---------------------------------------------------- */
+
+static int __rte_noinline
+fastmem_alloc_bulk(void **ptrs, unsigned int n)
+{
+	return rte_fastmem_alloc_bulk(ptrs, n, fastmem_size, 0, 0);
+}
+
+static void __rte_noinline
+fastmem_free_bulk(void **ptrs, unsigned int n)
+{
+	rte_fastmem_free_bulk(ptrs, n);
+}
+
+static int __rte_noinline
+mempool_alloc_bulk(void **ptrs, unsigned int n)
+{
+	return rte_mempool_get_bulk(mempool_pool, ptrs, n);
+}
+
+static void __rte_noinline
+mempool_free_bulk(void **ptrs, unsigned int n)
+{
+	rte_mempool_put_bulk(mempool_pool, ptrs, n);
+}
+
+static int __rte_noinline
+generic_alloc_bulk(void **ptrs, unsigned int n, void *(*alloc_fn)(void))
+{
+	unsigned int i;
+
+	for (i = 0; i < n; i++) {
+		ptrs[i] = alloc_fn();
+		if (ptrs[i] == NULL)
+			return -1;
+	}
+	return 0;
+}
+
+static int __rte_noinline
+malloc_alloc_bulk(void **ptrs, unsigned int n)
+{
+	return generic_alloc_bulk(ptrs, n, malloc_alloc);
+}
+
+static void __rte_noinline
+malloc_free_bulk(void **ptrs, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n; i++)
+		malloc_free(ptrs[i]);
+}
+
+static int __rte_noinline
+libc_alloc_bulk(void **ptrs, unsigned int n)
+{
+	return generic_alloc_bulk(ptrs, n, libc_alloc);
+}
+
+static void __rte_noinline
+libc_free_bulk(void **ptrs, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n; i++)
+		libc_free(ptrs[i]);
+}
+
+/* Adapter table ---------------------------------------------------- */
+
+static const struct allocator allocators[] = {
+	{ "fastmem",    fastmem_setup, fastmem_teardown, fastmem_alloc,     fastmem_free,     fastmem_alloc_bulk, fastmem_free_bulk },
+	{ "mempool",    mempool_setup, mempool_teardown, mempool_alloc_one, mempool_free_one, mempool_alloc_bulk, mempool_free_bulk },
+	{ "rte_malloc", malloc_setup,  malloc_teardown,  malloc_alloc,      malloc_free,      malloc_alloc_bulk,  malloc_free_bulk },
+	{ "libc",       libc_setup,    libc_teardown,    libc_alloc,        libc_free,        libc_alloc_bulk,    libc_free_bulk },
+};
+#define N_ALLOCATORS RTE_DIM(allocators)
+
+/*
+ * Scenario 1: tight alloc+free loop. A single object is cycled
+ * repeatedly. The LIFO path keeps the same pointer hot, giving
+ * a best-case measurement.
+ */
+static double
+run_tight(const struct allocator *alloc, size_t size)
+{
+	void *p;
+	uint64_t tsc;
+	unsigned int i;
+
+	if (alloc->setup(size, 1) < 0)
+		return -1.0;
+
+	/* Warmup. */
+	for (i = 0; i < WARMUP_OPS; i++) {
+		p = alloc->alloc();
+		if (p == NULL)
+			goto err;
+		alloc->free_obj(p);
+	}
+
+	tsc = rte_rdtsc_precise();
+	for (i = 0; i < MEASURE_OPS; i++) {
+		p = alloc->alloc();
+		if (p == NULL)
+			goto err;
+		alloc->free_obj(p);
+	}
+	tsc = rte_rdtsc_precise() - tsc;
+
+	alloc->teardown();
+
+	return (double)tsc / MEASURE_OPS;
+err:
+	alloc->teardown();
+	return -1.0;
+}
+
+/*
+ * Scenario 2: allocate N, free N (FIFO free order). Exercises
+ * cache refill and drain paths when N exceeds cache capacity.
+ */
+static void
+run_batch(const struct allocator *alloc, size_t size,
+		double *cycles_alloc, double *cycles_free)
+{
+	void *ptrs[BATCH_N];
+	uint64_t tsc_alloc = 0, tsc_free = 0;
+	unsigned int iter, i;
+	unsigned int iters;
+
+	*cycles_alloc = -1.0;
+	*cycles_free = -1.0;
+
+	if (alloc->setup(size, BATCH_N) < 0)
+		return;
+
+	/* Pick iteration count so total ops ~= MEASURE_OPS. */
+	iters = MEASURE_OPS / BATCH_N;
+
+	/* Warmup. */
+	for (iter = 0; iter < WARMUP_OPS / BATCH_N; iter++) {
+		for (i = 0; i < BATCH_N; i++) {
+			ptrs[i] = alloc->alloc();
+			if (ptrs[i] == NULL)
+				goto err;
+		}
+		for (i = 0; i < BATCH_N; i++)
+			alloc->free_obj(ptrs[i]);
+	}
+
+	for (iter = 0; iter < iters; iter++) {
+		uint64_t t0;
+
+		t0 = rte_rdtsc_precise();
+		for (i = 0; i < BATCH_N; i++) {
+			ptrs[i] = alloc->alloc();
+			if (ptrs[i] == NULL)
+				goto err;
+		}
+		tsc_alloc += rte_rdtsc_precise() - t0;
+
+		t0 = rte_rdtsc_precise();
+		for (i = 0; i < BATCH_N; i++)
+			alloc->free_obj(ptrs[i]);
+		tsc_free += rte_rdtsc_precise() - t0;
+	}
+
+	alloc->teardown();
+
+	*cycles_alloc = (double)tsc_alloc / (iters * BATCH_N);
+	*cycles_free = (double)tsc_free / (iters * BATCH_N);
+	return;
+err:
+	alloc->teardown();
+}
+
+/*
+ * Scenario 3: allocate N, free N in reverse order.
+ */
+static void
+run_batch_reverse(const struct allocator *alloc, size_t size,
+		double *cycles_alloc, double *cycles_free)
+{
+	void *ptrs[BATCH_N];
+	uint64_t tsc_alloc = 0, tsc_free = 0;
+	unsigned int iter, i;
+	unsigned int iters;
+
+	*cycles_alloc = -1.0;
+	*cycles_free = -1.0;
+
+	if (alloc->setup(size, BATCH_N) < 0)
+		return;
+
+	iters = MEASURE_OPS / BATCH_N;
+
+	for (iter = 0; iter < WARMUP_OPS / BATCH_N; iter++) {
+		for (i = 0; i < BATCH_N; i++) {
+			ptrs[i] = alloc->alloc();
+			if (ptrs[i] == NULL)
+				goto err;
+		}
+		for (i = BATCH_N; i > 0; i--)
+			alloc->free_obj(ptrs[i - 1]);
+	}
+
+	for (iter = 0; iter < iters; iter++) {
+		uint64_t t0;
+
+		t0 = rte_rdtsc_precise();
+		for (i = 0; i < BATCH_N; i++) {
+			ptrs[i] = alloc->alloc();
+			if (ptrs[i] == NULL)
+				goto err;
+		}
+		tsc_alloc += rte_rdtsc_precise() - t0;
+
+		t0 = rte_rdtsc_precise();
+		for (i = BATCH_N; i > 0; i--)
+			alloc->free_obj(ptrs[i - 1]);
+		tsc_free += rte_rdtsc_precise() - t0;
+	}
+
+	alloc->teardown();
+
+	*cycles_alloc = (double)tsc_alloc / (iters * BATCH_N);
+	*cycles_free = (double)tsc_free / (iters * BATCH_N);
+	return;
+err:
+	alloc->teardown();
+}
+
+/*
+ * Scenario 4: multi-lcore alloc/work/free with a dummy-work
+ * baseline. Each worker runs a tight alloc → touch → free loop
+ * on its own lcore. A second run with the same dummy work but
+ * no allocator traffic establishes a baseline; the per-op
+ * allocator cost is reported as (alloc_run - baseline_run).
+ *
+ * Fixed size class and a fixed amount of dummy work per op —
+ * this scenario sweeps lcore count rather than size.
+ */
+#define MULTI_SIZE 256u
+#define MULTI_WORK_BYTES 64u
+#define MULTI_WORK_PASSES 8u   /* RMW passes over the work region. */
+#define MULTI_OPS 200000u
+#define MULTI_WARMUP 2000u
+#define MAX_MULTI_LCORES 32u
+
+/*
+ * Per-worker volatile sink. Each worker writes to its own
+ * slot, preventing dead-code elimination of touch_buffer() and
+ * avoiding cross-lcore cache-line sharing on the hot path.
+ * Padded to cache-line stride to prevent false sharing between
+ * neighboring workers' slots.
+ */
+struct worker_sink {
+	volatile uint64_t value;
+} __rte_cache_aligned;
+
+static struct worker_sink worker_sinks[RTE_MAX_LCORE];
+
+/*
+ * Out-of-line dummy workload: run MULTI_WORK_PASSES
+ * read-modify-write passes over the first 'bytes' of the
+ * buffer. Each pass reads what the previous pass wrote, so the
+ * compiler cannot unroll or parallelize across passes — the
+ * work scales linearly with MULTI_WORK_PASSES. Returns an
+ * accumulator so the caller can feed it into a volatile sink;
+ * without that, the compiler could elide the whole function.
+ *
+ * __rte_noinline so it looks identical to the compiler in both
+ * the baseline (pre-allocated scratch buffer) and alloc-path
+ * runs, making the cycle-delta subtraction valid.
+ *
+ * The purpose of this being tunably expensive is to keep
+ * worker-per-iteration cost high relative to the allocator's
+ * critical section, so that even serialized allocators like
+ * rte_malloc spend most of their time outside the lock and the
+ * measured per-op allocator cost reflects its own work rather
+ * than its contention queue.
+ */
+static uint64_t __rte_noinline
+touch_buffer(void *buf, size_t bytes)
+{
+	uint64_t *p = buf;
+	size_t n = bytes / sizeof(uint64_t);
+	uint64_t acc = 0;
+	unsigned int pass;
+	size_t i;
+
+	/* Prime the buffer with a known pattern. */
+	for (i = 0; i < n; i++)
+		p[i] = i * 0x9E3779B97F4A7C15ULL;
+
+	/*
+	 * Dependent RMW passes: each pass reads p[i] written by
+	 * the previous pass, mixes the pass index in, and writes
+	 * back. The XOR into acc keeps the chain live.
+	 */
+	for (pass = 0; pass < MULTI_WORK_PASSES; pass++) {
+		for (i = 0; i < n; i++) {
+			uint64_t v = p[i];
+
+			v = v * 0xC2B2AE3D27D4EB4FULL + pass;
+			v ^= v >> 33;
+			p[i] = v;
+			acc ^= v;
+		}
+	}
+
+	return acc;
+}
+
+struct worker_args {
+	const struct allocator *alloc;
+	void *scratch;            /* baseline only; NULL => alloc path */
+	unsigned int iters;
+	unsigned int warmup;
+	unsigned int bulk_n;      /* 0 = single-object, >0 = bulk */
+	RTE_ATOMIC(bool) start_flag; /* barrier at worker entry */
+	uint64_t cycles;          /* out */
+	unsigned int ops;         /* out */
+	int err;                  /* out */
+};
+
+static int
+worker_run(void *arg)
+{
+	struct worker_args *wa = arg;
+	unsigned int lcore = rte_lcore_id();
+	uint64_t acc = 0;
+	uint64_t t0;
+	unsigned int i;
+
+	wa->err = 0;
+	wa->ops = 0;
+	wa->cycles = 0;
+
+	/* Wait for start flag (spin-barrier set by main). */
+	while (!rte_atomic_load_explicit(&wa->start_flag,
+			rte_memory_order_acquire))
+		rte_pause();
+
+	/* Warmup. */
+	for (i = 0; i < wa->warmup; i++) {
+		void *p;
+
+		if (wa->scratch != NULL)
+			p = wa->scratch;
+		else {
+			p = wa->alloc->alloc();
+			if (p == NULL) {
+				wa->err = -1;
+				return -1;
+			}
+		}
+		acc ^= touch_buffer(p, MULTI_WORK_BYTES);
+		if (wa->scratch == NULL)
+			wa->alloc->free_obj(p);
+	}
+
+	/* Measured loop. */
+	t0 = rte_rdtsc_precise();
+	for (i = 0; i < wa->iters; i++) {
+		void *p;
+
+		if (wa->scratch != NULL)
+			p = wa->scratch;
+		else {
+			p = wa->alloc->alloc();
+			if (p == NULL) {
+				wa->err = -1;
+				break;
+			}
+		}
+		acc ^= touch_buffer(p, MULTI_WORK_BYTES);
+		if (wa->scratch == NULL)
+			wa->alloc->free_obj(p);
+	}
+	wa->cycles = rte_rdtsc_precise() - t0;
+	wa->ops = i;
+
+	/* Publish accumulator to defeat dead-code elimination. */
+	worker_sinks[lcore].value ^= acc;
+
+	return 0;
+}
+
+static int
+worker_run_bulk(void *arg)
+{
+	struct worker_args *wa = arg;
+	unsigned int lcore = rte_lcore_id();
+	void *ptrs[BATCH_N];
+	uint64_t acc = 0;
+	uint64_t t0;
+	unsigned int i, j;
+	unsigned int bulk_n = wa->bulk_n;
+
+	wa->err = 0;
+	wa->ops = 0;
+	wa->cycles = 0;
+
+	while (!rte_atomic_load_explicit(&wa->start_flag,
+			rte_memory_order_acquire))
+		rte_pause();
+
+	/* Warmup. */
+	for (i = 0; i < wa->warmup; i++) {
+		if (wa->alloc->alloc_bulk(ptrs, bulk_n) < 0) {
+			wa->err = -1;
+			return -1;
+		}
+		for (j = 0; j < bulk_n; j++)
+			acc ^= touch_buffer(ptrs[j], MULTI_WORK_BYTES);
+		wa->alloc->free_bulk(ptrs, bulk_n);
+	}
+
+	t0 = rte_rdtsc_precise();
+	for (i = 0; i < wa->iters; i++) {
+		if (wa->alloc->alloc_bulk(ptrs, bulk_n) < 0) {
+			wa->err = -1;
+			break;
+		}
+		for (j = 0; j < bulk_n; j++)
+			acc ^= touch_buffer(ptrs[j], MULTI_WORK_BYTES);
+		wa->alloc->free_bulk(ptrs, bulk_n);
+	}
+	wa->cycles = rte_rdtsc_precise() - t0;
+	wa->ops = i * bulk_n;
+
+	worker_sinks[lcore].value ^= acc;
+
+	return 0;
+}
+
+/*
+ * Launch workers on the first 'n_workers' worker lcores, run
+ * either the baseline (scratch != NULL) or the alloc path
+ * (scratch == NULL), and return the mean per-op cycle cost
+ * averaged across participating workers.
+ *
+ * On any worker error, returns -1.0.
+ */
+static double
+run_multi_workers(const struct allocator *alloc, unsigned int n_workers,
+		void *const *scratches, unsigned int bulk_n)
+{
+	struct worker_args wargs[RTE_MAX_LCORE];
+	unsigned int worker_lcores[MAX_MULTI_LCORES];
+	unsigned int n = 0;
+	unsigned int lcore_id;
+	unsigned int i;
+	lcore_function_t *fn = bulk_n > 0 ? worker_run_bulk : worker_run;
+
+	/* Collect the first n_workers worker lcores. */
+	RTE_LCORE_FOREACH_WORKER(lcore_id) {
+		if (n >= n_workers)
+			break;
+		worker_lcores[n++] = lcore_id;
+	}
+	if (n < n_workers)
+		return -1.0;
+
+	/* Prepare per-worker args. */
+	for (i = 0; i < n_workers; i++) {
+		struct worker_args *wa = &wargs[worker_lcores[i]];
+
+		wa->alloc = alloc;
+		wa->scratch = scratches != NULL ? scratches[i] : NULL;
+		wa->iters = MULTI_OPS;
+		wa->warmup = MULTI_WARMUP;
+		wa->bulk_n = bulk_n;
+		rte_atomic_store_explicit(&wa->start_flag, false,
+				rte_memory_order_relaxed);
+	}
+
+	/* Launch workers. They spin on start_flag until released. */
+	for (i = 0; i < n_workers; i++)
+		rte_eal_remote_launch(fn, &wargs[worker_lcores[i]],
+				worker_lcores[i]);
+
+	/* Release all workers roughly simultaneously. */
+	for (i = 0; i < n_workers; i++)
+		rte_atomic_store_explicit(
+			&wargs[worker_lcores[i]].start_flag, true,
+			rte_memory_order_release);
+
+	/* Wait for completion. */
+	for (i = 0; i < n_workers; i++)
+		rte_eal_wait_lcore(worker_lcores[i]);
+
+	/* Aggregate: mean cycles per op across workers. */
+	{
+		double sum_cycles_per_op = 0.0;
+		unsigned int n_ok = 0;
+
+		for (i = 0; i < n_workers; i++) {
+			struct worker_args *wa = &wargs[worker_lcores[i]];
+
+			if (wa->err != 0 || wa->ops == 0)
+				return -1.0;
+			sum_cycles_per_op +=
+				(double)wa->cycles / (double)wa->ops;
+			n_ok++;
+		}
+		return sum_cycles_per_op / n_ok;
+	}
+}
+
+/*
+ * One sub-run of Scenario 4: given an allocator and a worker
+ * count, return (baseline, alloc_path) mean cycles per op.
+ */
+static void
+run_multi_lcore(const struct allocator *alloc, unsigned int n_workers,
+		unsigned int bulk_n, double *baseline, double *alloc_path)
+{
+	void *scratches[MAX_MULTI_LCORES] = {0};
+	unsigned int n_alloced = 0;
+	unsigned int i;
+
+	*baseline = -1.0;
+	*alloc_path = -1.0;
+
+	if (alloc->setup(MULTI_SIZE, n_workers * 64) < 0)
+		return;
+
+	/* Baseline: pre-allocate one scratch per worker. */
+	for (i = 0; i < n_workers; i++) {
+		scratches[i] = alloc->alloc();
+		if (scratches[i] == NULL)
+			goto err;
+		n_alloced++;
+	}
+
+	*baseline = run_multi_workers(alloc, n_workers, scratches, 0);
+
+	for (i = 0; i < n_alloced; i++)
+		alloc->free_obj(scratches[i]);
+	n_alloced = 0;
+
+	/* Alloc path: workers alloc+free each iter. */
+	*alloc_path = run_multi_workers(alloc, n_workers, NULL, bulk_n);
+
+	alloc->teardown();
+	return;
+err:
+	for (i = 0; i < n_alloced; i++)
+		alloc->free_obj(scratches[i]);
+	alloc->teardown();
+}
+
+/* Reporting -------------------------------------------------------- */
+
+static void
+print_header(const char *title)
+{
+	size_t i;
+
+	TEST_LOG("\n=== %s ===\n", title);
+	TEST_LOG("%-12s", "allocator");
+	for (i = 0; i < N_SIZES; i++)
+		TEST_LOG(" %10zu B", SIZES[i]);
+	TEST_LOG("\n");
+}
+
+static void
+print_row(const char *name, const double *values)
+{
+	size_t i;
+
+	TEST_LOG("%-12s", name);
+	for (i = 0; i < N_SIZES; i++) {
+		if (values[i] < 0)
+			TEST_LOG(" %12s", "--");
+		else
+			TEST_LOG(" %12.1f", values[i]);
+	}
+	TEST_LOG("\n");
+}
+
+static void
+print_multi_header(const char *title, const unsigned int *lcore_counts,
+		unsigned int n_counts)
+{
+	unsigned int i;
+
+	TEST_LOG("\n=== %s ===\n", title);
+	TEST_LOG("%-12s", "allocator");
+	for (i = 0; i < n_counts; i++)
+		TEST_LOG(" %8u lcore%c", lcore_counts[i],
+				lcore_counts[i] == 1 ? ' ' : 's');
+	TEST_LOG("\n");
+}
+
+static void
+print_multi_row(const char *name, const double *values, unsigned int n_counts)
+{
+	unsigned int i;
+
+	TEST_LOG("%-12s", name);
+	for (i = 0; i < n_counts; i++) {
+		if (values[i] < 0)
+			TEST_LOG(" %14s", "--");
+		else
+			TEST_LOG(" %14.1f", values[i]);
+	}
+	TEST_LOG("\n");
+}
+
+/* Driver ----------------------------------------------------------- */
+
+static int
+test_fastmem_perf(void)
+{
+	size_t i;
+	size_t a;
+	int rc;
+
+	rc = rte_fastmem_init();
+	if (rc < 0) {
+		TEST_LOG("rte_fastmem_init() failed: %d\n", rc);
+		return -1;
+	}
+
+	rc = rte_fastmem_reserve(128 * 1024 * 1024, SOCKET_ID_ANY);
+	if (rc < 0) {
+		TEST_LOG("rte_fastmem_reserve() failed: %d\n", rc);
+		rte_fastmem_deinit();
+		return -1;
+	}
+
+	TEST_LOG("\nfastmem performance — single-lcore, fixed-size\n");
+	TEST_LOG("All numbers are TSC cycles.\n");
+
+	/* Scenario 1: tight alloc+free. */
+	print_header("Scenario 1: Single-object hot path — cycles per (alloc + free)");
+	for (a = 0; a < N_ALLOCATORS; a++) {
+		double vals[N_SIZES];
+
+		for (i = 0; i < N_SIZES; i++)
+			vals[i] = run_tight(&allocators[a], SIZES[i]);
+		print_row(allocators[a].name, vals);
+	}
+
+	/* Scenario 2: batched, FIFO free. */
+	print_header("Scenario 2: Batch alloc, FIFO free — cycles per alloc");
+	for (a = 0; a < N_ALLOCATORS; a++) {
+		double vals_alloc[N_SIZES], vals_free[N_SIZES];
+
+		for (i = 0; i < N_SIZES; i++)
+			run_batch(&allocators[a], SIZES[i],
+				&vals_alloc[i], &vals_free[i]);
+		print_row(allocators[a].name, vals_alloc);
+	}
+	print_header("Scenario 2: Batch alloc, FIFO free — cycles per free");
+	for (a = 0; a < N_ALLOCATORS; a++) {
+		double vals_alloc[N_SIZES], vals_free[N_SIZES];
+
+		for (i = 0; i < N_SIZES; i++)
+			run_batch(&allocators[a], SIZES[i],
+				&vals_alloc[i], &vals_free[i]);
+		print_row(allocators[a].name, vals_free);
+	}
+
+	/* Scenario 3: batched, reverse free. */
+	print_header("Scenario 3: Batch alloc, LIFO free — cycles per alloc");
+	for (a = 0; a < N_ALLOCATORS; a++) {
+		double vals_alloc[N_SIZES], vals_free[N_SIZES];
+
+		for (i = 0; i < N_SIZES; i++)
+			run_batch_reverse(&allocators[a], SIZES[i],
+				&vals_alloc[i], &vals_free[i]);
+		print_row(allocators[a].name, vals_alloc);
+	}
+	print_header("Scenario 3: Batch alloc, LIFO free — cycles per free");
+	for (a = 0; a < N_ALLOCATORS; a++) {
+		double vals_alloc[N_SIZES], vals_free[N_SIZES];
+
+		for (i = 0; i < N_SIZES; i++)
+			run_batch_reverse(&allocators[a], SIZES[i],
+				&vals_alloc[i], &vals_free[i]);
+		print_row(allocators[a].name, vals_free);
+	}
+
+	/* Scenario 4: multi-lcore alloc/work/free with baseline. */
+	{
+		unsigned int max_workers = rte_lcore_count() - 1;
+		unsigned int lcore_counts[8];
+		unsigned int n_counts = 0;
+		unsigned int w;
+		double base_vals[N_ALLOCATORS][8];
+		double alloc_vals[N_ALLOCATORS][8];
+		double delta_vals[N_ALLOCATORS][8];
+
+		if (max_workers > MAX_MULTI_LCORES)
+			max_workers = MAX_MULTI_LCORES;
+
+		/* Sweep lcore counts: 1, 2, 4, 8, ... up to max_workers. */
+		for (w = 1; w <= max_workers && n_counts < RTE_DIM(lcore_counts); w *= 2)
+			lcore_counts[n_counts++] = w;
+		/* Ensure max_workers is the final column if not power of two. */
+		if (n_counts > 0 && lcore_counts[n_counts - 1] != max_workers &&
+				n_counts < RTE_DIM(lcore_counts) && max_workers >= 1)
+			lcore_counts[n_counts++] = max_workers;
+
+		if (n_counts == 0) {
+			TEST_LOG("\nScenario 4 (Multi-lcore contention) skipped: no worker lcores available.\n");
+		} else {
+			TEST_LOG("\nScenario 4 parameters: size=%u B\n",
+				MULTI_SIZE);
+
+			for (a = 0; a < N_ALLOCATORS; a++) {
+				unsigned int c;
+
+				for (c = 0; c < n_counts; c++)
+					run_multi_lcore(&allocators[a], lcore_counts[c],
+							0, &base_vals[a][c],
+							&alloc_vals[a][c]);
+				for (c = 0; c < n_counts; c++) {
+					if (base_vals[a][c] < 0 || alloc_vals[a][c] < 0)
+						delta_vals[a][c] = -1.0;
+					else
+						delta_vals[a][c] = alloc_vals[a][c] -
+							base_vals[a][c];
+				}
+			}
+
+			TEST_LOG("Baseline (domain logic only): %.1f cycles/op\n",
+					base_vals[0][0]);
+
+			print_multi_header("Scenario 4: Multi-lcore contention — allocator overhead (cycles/op)",
+					lcore_counts, n_counts);
+			for (a = 0; a < N_ALLOCATORS; a++)
+				print_multi_row(allocators[a].name,
+						delta_vals[a], n_counts);
+		}
+	}
+
+	/* Scenario 5: multi-lcore bulk alloc/work/free. */
+	{
+		unsigned int max_workers = rte_lcore_count() - 1;
+		unsigned int lcore_counts[8];
+		unsigned int n_counts = 0;
+		unsigned int w;
+		double base_vals[N_ALLOCATORS][8];
+		double alloc_vals[N_ALLOCATORS][8];
+		double delta_vals[N_ALLOCATORS][8];
+		unsigned int bulk_n = 8;
+
+		if (max_workers > MAX_MULTI_LCORES)
+			max_workers = MAX_MULTI_LCORES;
+
+		for (w = 1; w <= max_workers && n_counts < RTE_DIM(lcore_counts); w *= 2)
+			lcore_counts[n_counts++] = w;
+		if (n_counts > 0 && lcore_counts[n_counts - 1] != max_workers &&
+				n_counts < RTE_DIM(lcore_counts) && max_workers >= 1)
+			lcore_counts[n_counts++] = max_workers;
+
+		if (n_counts == 0) {
+			TEST_LOG("\nScenario 5 (Multi-lcore bulk contention) skipped: no worker lcores available.\n");
+		} else {
+			TEST_LOG("\nScenario 5 parameters: size=%u B, "
+				"bulk=%u\n",
+				MULTI_SIZE, bulk_n);
+
+			for (size_t a = 0; a < N_ALLOCATORS; a++) {
+				unsigned int c;
+
+				for (c = 0; c < n_counts; c++)
+					run_multi_lcore(&allocators[a],
+							lcore_counts[c], bulk_n,
+							&base_vals[a][c],
+							&alloc_vals[a][c]);
+				for (c = 0; c < n_counts; c++) {
+					if (base_vals[a][c] < 0 || alloc_vals[a][c] < 0)
+						delta_vals[a][c] = -1.0;
+					else
+						delta_vals[a][c] = alloc_vals[a][c] -
+							base_vals[a][c];
+				}
+			}
+
+			TEST_LOG("Baseline (domain logic only): %.1f cycles/op\n",
+					base_vals[0][0]);
+
+			print_multi_header("Scenario 5: Multi-lcore bulk contention — allocator overhead (cycles/op)",
+					lcore_counts, n_counts);
+			for (size_t a = 0; a < N_ALLOCATORS; a++)
+				print_multi_row(allocators[a].name,
+						delta_vals[a], n_counts);
+		}
+	}
+
+	TEST_LOG("\n");
+	rte_fastmem_deinit();
+	return 0;
+}
+
+REGISTER_PERF_TEST(fastmem_perf_autotest, test_fastmem_perf);
diff --git a/app/test/test_fastmem_profile.c b/app/test/test_fastmem_profile.c
new file mode 100644
index 0000000000..9a5dc94018
--- /dev/null
+++ b/app/test/test_fastmem_profile.c
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Ericsson AB
+ */
+
+/*
+ * A minimal fastmem workload intended for use with perf record /
+ * perf report. Runs a tight alloc/free loop for a fixed duration
+ * so that sampling profilers can attribute cycles to individual
+ * functions and instructions within the fastmem hot path.
+ *
+ * Usage:
+ *   perf record -g -- dpdk-test --no-huge --no-pci -m 8192 \
+ *       -l 0 <<< fastmem_profile_autotest
+ *   perf report
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_lcore.h>
+#include <rte_memory.h>
+
+#include <rte_fastmem.h>
+
+#include "test.h"
+
+/* Duration of each sub-test in TSC cycles (~3 seconds at 3 GHz). */
+#define PROFILE_DURATION_CYCLES (3ULL * rte_get_tsc_hz())
+
+/* Allocation size for the profiling workload. */
+#define PROFILE_SIZE 256u
+
+/*
+ * Sub-test 1: tight alloc+free, exercises only the per-lcore
+ * cache (no bin interaction after warmup).
+ */
+static int
+profile_cache_hit(void)
+{
+	uint64_t deadline;
+	uint64_t ops = 0;
+
+	deadline = rte_rdtsc() + PROFILE_DURATION_CYCLES;
+
+	while (rte_rdtsc() < deadline) {
+		void *p = rte_fastmem_alloc(PROFILE_SIZE, 0, 0);
+
+		if (p == NULL)
+			return -1;
+		rte_fastmem_free(p);
+		ops++;
+	}
+
+	printf("  cache_hit: %" PRIu64 " ops\n", ops);
+	return 0;
+}
+
+/*
+ * Sub-test 2: alloc N then free N, where N exceeds the cache
+ * capacity. This forces repeated cache refills and drains,
+ * exercising the bin lock and slab free-list traversal.
+ */
+#define PROFILE_BATCH 256u
+
+static int
+profile_cache_miss(void)
+{
+	void *ptrs[PROFILE_BATCH];
+	uint64_t deadline;
+	uint64_t ops = 0;
+	unsigned int i;
+
+	deadline = rte_rdtsc() + PROFILE_DURATION_CYCLES;
+
+	while (rte_rdtsc() < deadline) {
+		for (i = 0; i < PROFILE_BATCH; i++) {
+			ptrs[i] = rte_fastmem_alloc(PROFILE_SIZE, 0, 0);
+			if (ptrs[i] == NULL)
+				return -1;
+		}
+		for (i = 0; i < PROFILE_BATCH; i++)
+			rte_fastmem_free(ptrs[i]);
+		ops += PROFILE_BATCH;
+	}
+
+	printf("  cache_miss: %" PRIu64 " ops\n", ops);
+	return 0;
+}
+
+static int
+test_fastmem_profile_cache_hit(void)
+{
+	int rc;
+
+	rc = rte_fastmem_init();
+	if (rc < 0) {
+		printf("rte_fastmem_init() failed: %d\n", rc);
+		return -1;
+	}
+
+	rc = rte_fastmem_reserve(128 * 1024 * 1024, SOCKET_ID_ANY);
+	if (rc < 0) {
+		printf("rte_fastmem_reserve() failed: %d\n", rc);
+		rte_fastmem_deinit();
+		return -1;
+	}
+
+	printf("fastmem profile: cache-hit workload (size=%u, ~%u s)\n",
+		PROFILE_SIZE, 3);
+
+	if (profile_cache_hit() < 0) {
+		rte_fastmem_deinit();
+		return -1;
+	}
+
+	rte_fastmem_deinit();
+	return 0;
+}
+
+static int
+test_fastmem_profile_cache_miss(void)
+{
+	int rc;
+
+	rc = rte_fastmem_init();
+	if (rc < 0) {
+		printf("rte_fastmem_init() failed: %d\n", rc);
+		return -1;
+	}
+
+	rc = rte_fastmem_reserve(128 * 1024 * 1024, SOCKET_ID_ANY);
+	if (rc < 0) {
+		printf("rte_fastmem_reserve() failed: %d\n", rc);
+		rte_fastmem_deinit();
+		return -1;
+	}
+
+	printf("fastmem profile: cache-miss workload (size=%u, ~%u s)\n",
+		PROFILE_SIZE, 3);
+
+	if (profile_cache_miss() < 0) {
+		rte_fastmem_deinit();
+		return -1;
+	}
+
+	rte_fastmem_deinit();
+	return 0;
+}
+
+REGISTER_PERF_TEST(fastmem_profile_cache_hit_autotest,
+		test_fastmem_profile_cache_hit);
+REGISTER_PERF_TEST(fastmem_profile_cache_miss_autotest,
+		test_fastmem_profile_cache_miss);
-- 
2.43.0


^ permalink raw reply related

* [RFC 2/3] lib: add fastmem library
From: Mattias Rönnblom @ 2026-05-25 10:36 UTC (permalink / raw)
  To: dev
  Cc: Morten Brørup, Konstantin Ananyev, Mattias Rönnblom,
	Yogaraj Baskaravel, Mattias Rönnblom
In-Reply-To: <20260525103642.55255-1-hofors@lysator.liu.se>

From: Mattias Rönnblom <mattias.ronnblom@ericsson.com>

Introduce fastmem, a fast general-purpose small-object allocator
for DPDK applications. It allows an application to replace its
many per-type mempools with a single allocator that handles
arbitrary sizes, grows on demand, and offers mempool-level
performance on the hot path.

Applications that manage many object types (connections, sessions,
work items, timers) currently maintain a separate mempool for each,
requiring upfront sizing and wasting memory on over-provisioned
pools. Fastmem removes both constraints.

Key properties:

 * Huge-page-backed, NUMA-aware, DMA-usable.
 * Per-lcore caches for lock-free alloc/free on EAL threads.
 * Bulk alloc and free APIs.
 * Power-of-two size classes from 8 B to 1 MiB.
 * Backing memory grows lazily; rte_fastmem_reserve() allows
   upfront reservation to avoid latency spikes.
 * Always-on per-lcore and per-class statistics.

Bounded to small objects; requests above rte_fastmem_max_size()
are rejected. Replacing rte_malloc is currently not a goal.

Signed-off-by: Mattias Rönnblom <mattias.ronnblom@ericsson.com>
---
 doc/api/doxy-api-index.md |    1 +
 doc/api/doxy-api.conf.in  |    1 +
 lib/fastmem/meson.build   |    6 +
 lib/fastmem/rte_fastmem.c | 1486 +++++++++++++++++++++++++++++++++++++
 lib/fastmem/rte_fastmem.h |  644 ++++++++++++++++
 lib/meson.build           |    1 +
 6 files changed, 2139 insertions(+)
 create mode 100644 lib/fastmem/meson.build
 create mode 100644 lib/fastmem/rte_fastmem.c
 create mode 100644 lib/fastmem/rte_fastmem.h

diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md
index 9296042119..7ebf1201ce 100644
--- a/doc/api/doxy-api-index.md
+++ b/doc/api/doxy-api-index.md
@@ -70,6 +70,7 @@ The public API headers are grouped by topics:
   [memzone](@ref rte_memzone.h),
   [mempool](@ref rte_mempool.h),
   [malloc](@ref rte_malloc.h),
+  [fastmem](@ref rte_fastmem.h),
   [memcpy](@ref rte_memcpy.h)
 
 - **timers**:
diff --git a/doc/api/doxy-api.conf.in b/doc/api/doxy-api.conf.in
index bedd944681..4355e9fb2d 100644
--- a/doc/api/doxy-api.conf.in
+++ b/doc/api/doxy-api.conf.in
@@ -43,6 +43,7 @@ INPUT                   = @TOPDIR@/doc/api/doxy-api-index.md \
                           @TOPDIR@/lib/efd \
                           @TOPDIR@/lib/ethdev \
                           @TOPDIR@/lib/eventdev \
+                          @TOPDIR@/lib/fastmem \
                           @TOPDIR@/lib/fib \
                           @TOPDIR@/lib/gpudev \
                           @TOPDIR@/lib/graph \
diff --git a/lib/fastmem/meson.build b/lib/fastmem/meson.build
new file mode 100644
index 0000000000..6c7834608f
--- /dev/null
+++ b/lib/fastmem/meson.build
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Ericsson AB
+
+sources = files('rte_fastmem.c')
+headers = files('rte_fastmem.h')
+deps += ['eal']
diff --git a/lib/fastmem/rte_fastmem.c b/lib/fastmem/rte_fastmem.c
new file mode 100644
index 0000000000..f605c538fc
--- /dev/null
+++ b/lib/fastmem/rte_fastmem.c
@@ -0,0 +1,1486 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Ericsson AB
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+
+#include <rte_common.h>
+#include <rte_debug.h>
+#include <eal_export.h>
+#include <rte_errno.h>
+#include <rte_lcore.h>
+#include <rte_log.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_spinlock.h>
+
+#include <rte_fastmem.h>
+
+RTE_LOG_REGISTER_DEFAULT(fastmem_logtype, NOTICE);
+
+#define RTE_LOGTYPE_FASTMEM fastmem_logtype
+
+#define FASTMEM_LOG(level, ...) \
+	RTE_LOG_LINE(level, FASTMEM, "" __VA_ARGS__)
+
+#define FASTMEM_MEMZONE_SIZE_LOG2 27                            /* 128 MiB */
+#define FASTMEM_MEMZONE_SIZE ((size_t)1 << FASTMEM_MEMZONE_SIZE_LOG2)
+
+#define FASTMEM_SLAB_SIZE_LOG2 21                               /*   2 MiB */
+#define FASTMEM_SLAB_SIZE ((size_t)1 << FASTMEM_SLAB_SIZE_LOG2)
+#define FASTMEM_SLAB_MASK (FASTMEM_SLAB_SIZE - 1)
+
+#define FASTMEM_SLABS_PER_MEMZONE (FASTMEM_MEMZONE_SIZE / FASTMEM_SLAB_SIZE)
+
+#define FASTMEM_MAX_MEMZONES_PER_SOCKET 64
+
+#define FASTMEM_MIN_CLASS_LOG2 3                                /*   8 B */
+#define FASTMEM_MAX_CLASS_LOG2 20                               /*   1 MiB */
+#define FASTMEM_N_CLASSES (FASTMEM_MAX_CLASS_LOG2 - FASTMEM_MIN_CLASS_LOG2 + 1)
+
+#define FASTMEM_MIN_SIZE ((size_t)1 << FASTMEM_MIN_CLASS_LOG2)
+#define FASTMEM_MAX_ALLOC_SIZE ((size_t)1 << FASTMEM_MAX_CLASS_LOG2)
+
+#define FASTMEM_SLAB_HEADER_SIZE RTE_CACHE_LINE_SIZE
+
+#define FASTMEM_CACHE_BASE_CAPACITY 64
+#define FASTMEM_CACHE_FLOOR_CAPACITY 4
+#define FASTMEM_CACHE_BASE_CLASS_LOG2 12                        /* 4 KiB */
+
+struct fastmem_bin;
+
+/*
+ * Slab header at offset 0 of each 2 MiB slab. Either free (linked
+ * via next_free) or assigned to a bin (linked via list).
+ */
+struct fastmem_slab {
+	struct fastmem_bin *bin;
+	void *free_head;
+	uint32_t free_count;
+	uint32_t n_slots;
+	struct fastmem_slab *next_free;
+	TAILQ_ENTRY(fastmem_slab) list;
+	rte_iova_t iova_base;
+} __rte_aligned(FASTMEM_SLAB_HEADER_SIZE);
+
+TAILQ_HEAD(fastmem_slab_list, fastmem_slab);
+
+struct fastmem_bin {
+	rte_spinlock_t lock;
+	uint32_t slot_size;
+	uint32_t slots_per_slab;
+	uint32_t class_idx;
+	struct fastmem_slab_list partial;
+	struct fastmem_slab_list full;
+	int socket_id;
+	uint64_t slab_acquires;
+	uint64_t slab_releases;
+	uint32_t slabs_partial;
+	uint32_t slabs_full;
+};
+
+/* Per-(lcore, class, socket) bounded LIFO of free object pointers. */
+struct fastmem_cache {
+	uint32_t count;
+	uint32_t capacity;
+	uint32_t target;
+	uint64_t alloc_cache_hits;
+	uint64_t alloc_cache_misses;
+	uint64_t alloc_nomem;
+	uint64_t free_cache_hits;
+	uint64_t free_cache_misses;
+	void *objs[];
+} __rte_cache_aligned;
+
+struct fastmem_socket_state {
+	rte_spinlock_t lock;
+	struct fastmem_slab *free_head;
+	size_t reserved_bytes;
+	size_t memory_limit;
+	unsigned int n_memzones;
+	unsigned int memzone_seq;
+	const struct rte_memzone *memzones[FASTMEM_MAX_MEMZONES_PER_SOCKET];
+	struct fastmem_bin bins[FASTMEM_N_CLASSES];
+	struct fastmem_cache *caches[RTE_MAX_LCORE][FASTMEM_N_CLASSES];
+};
+
+struct fastmem {
+	struct fastmem_socket_state sockets[RTE_MAX_NUMA_NODES];
+};
+
+static struct fastmem *fastmem;
+static const struct rte_memzone *fastmem_mz;
+
+static inline unsigned int
+size_to_class(size_t size, size_t align)
+{
+	size_t effective;
+	unsigned int log2;
+
+	effective = size < FASTMEM_MIN_SIZE ? FASTMEM_MIN_SIZE : size;
+	if (align > effective)
+		effective = align;
+
+	log2 = 64u - rte_clz64(effective - 1);
+
+	if (log2 < FASTMEM_MIN_CLASS_LOG2)
+		log2 = FASTMEM_MIN_CLASS_LOG2;
+	if (log2 > FASTMEM_MAX_CLASS_LOG2)
+		return FASTMEM_N_CLASSES;
+
+	return log2 - FASTMEM_MIN_CLASS_LOG2;
+}
+
+static inline size_t
+class_size(unsigned int class_idx)
+{
+	return (size_t)1 << (class_idx + FASTMEM_MIN_CLASS_LOG2);
+}
+
+static_assert(sizeof(struct fastmem_slab) == FASTMEM_SLAB_HEADER_SIZE,
+	"fastmem slab header must fit in exactly one cache line");
+static_assert(sizeof(struct fastmem_slab) <= FASTMEM_SLAB_SIZE,
+	"slab header larger than a slab makes no sense");
+
+static __rte_always_inline struct fastmem_slab *
+slab_of(void *obj)
+{
+	return (struct fastmem_slab *)
+		((uintptr_t)obj & ~(uintptr_t)FASTMEM_SLAB_MASK);
+}
+
+static inline size_t
+slab_slot0_offset(size_t class_size)
+{
+	return class_size < FASTMEM_SLAB_HEADER_SIZE ?
+		FASTMEM_SLAB_HEADER_SIZE : class_size;
+}
+
+static inline uint32_t
+slab_slot_count(size_t class_size)
+{
+	size_t offset = slab_slot0_offset(class_size);
+
+	return (uint32_t)((FASTMEM_SLAB_SIZE - offset) / class_size);
+}
+
+/* Must be called with bin->lock held. */
+static void
+slab_init(struct fastmem_bin *bin, struct fastmem_slab *slab)
+{
+	size_t slot_size = bin->slot_size;
+	size_t offset = slab_slot0_offset(slot_size);
+	uint32_t n = bin->slots_per_slab;
+	void *prev = NULL;
+	uint32_t i;
+
+	slab->bin = bin;
+	slab->n_slots = n;
+	slab->free_count = n;
+
+	/* Build in reverse so pops yield sequential addresses. */
+	for (i = 0; i < n; i++) {
+		void *slot = RTE_PTR_ADD(slab, offset + i * slot_size);
+		*(void **)slot = prev;
+		prev = slot;
+	}
+	slab->free_head = prev;
+}
+
+static int
+grow_socket(struct fastmem_socket_state *socket, int socket_id)
+{
+	char name[RTE_MEMZONE_NAMESIZE];
+	const struct rte_memzone *mz;
+	unsigned int i;
+
+	if (socket->reserved_bytes + FASTMEM_MEMZONE_SIZE > socket->memory_limit) {
+		FASTMEM_LOG(ERR,
+			"reserve would exceed memory_limit (%zu) on socket %d",
+			socket->memory_limit, socket_id);
+		return -ENOMEM;
+	}
+
+	if (socket->n_memzones == FASTMEM_MAX_MEMZONES_PER_SOCKET) {
+		FASTMEM_LOG(ERR,
+			"reached per-socket memzone cap (%u) on socket %d",
+			FASTMEM_MAX_MEMZONES_PER_SOCKET, socket_id);
+		return -ENOMEM;
+	}
+
+	snprintf(name, sizeof(name), "fastmem_%d_%u", socket_id,
+			socket->memzone_seq++);
+
+	mz = rte_memzone_reserve_aligned(name, FASTMEM_MEMZONE_SIZE,
+			socket_id, RTE_MEMZONE_IOVA_CONTIG,
+			FASTMEM_SLAB_SIZE);
+	if (mz == NULL) {
+		FASTMEM_LOG(ERR,
+			"failed to reserve %zu-byte memzone '%s' on socket %d: %s",
+			(size_t)FASTMEM_MEMZONE_SIZE, name, socket_id,
+			rte_strerror(rte_errno));
+		return -ENOMEM;
+	}
+
+	socket->memzones[socket->n_memzones++] = mz;
+	socket->reserved_bytes += FASTMEM_MEMZONE_SIZE;
+
+	for (i = 0; i < FASTMEM_SLABS_PER_MEMZONE; i++) {
+		struct fastmem_slab *slab = RTE_PTR_ADD(mz->addr,
+				i * FASTMEM_SLAB_SIZE);
+
+		slab->iova_base = mz->iova + i * FASTMEM_SLAB_SIZE;
+		slab->next_free = socket->free_head;
+		socket->free_head = slab;
+	}
+
+	FASTMEM_LOG(DEBUG,
+		"reserved memzone '%s' (%zu bytes) on socket %d; %zu slabs added",
+		name, (size_t)FASTMEM_MEMZONE_SIZE, socket_id,
+		(size_t)FASTMEM_SLABS_PER_MEMZONE);
+
+	return 0;
+}
+
+static struct fastmem_slab *
+slab_acquire(struct fastmem_socket_state *socket, int socket_id)
+{
+	struct fastmem_slab *slab;
+
+	rte_spinlock_lock(&socket->lock);
+
+	if (socket->free_head == NULL) {
+		int rc = grow_socket(socket, socket_id);
+
+		if (rc < 0) {
+			rte_spinlock_unlock(&socket->lock);
+			return NULL;
+		}
+	}
+
+	slab = socket->free_head;
+	socket->free_head = slab->next_free;
+	slab->next_free = NULL;
+
+	rte_spinlock_unlock(&socket->lock);
+
+	return slab;
+}
+
+static void
+slab_release(struct fastmem_socket_state *socket,
+		struct fastmem_slab *slab)
+{
+	rte_spinlock_lock(&socket->lock);
+
+	slab->next_free = socket->free_head;
+	socket->free_head = slab;
+
+	rte_spinlock_unlock(&socket->lock);
+}
+
+static void
+bin_init(struct fastmem_bin *bin, unsigned int class_idx, int socket_id)
+{
+	size_t slot_size = class_size(class_idx);
+
+	rte_spinlock_init(&bin->lock);
+	bin->slot_size = (uint32_t)slot_size;
+	bin->slots_per_slab = slab_slot_count(slot_size);
+	bin->class_idx = class_idx;
+	TAILQ_INIT(&bin->partial);
+	TAILQ_INIT(&bin->full);
+	bin->socket_id = socket_id;
+	bin->slab_acquires = 0;
+	bin->slab_releases = 0;
+	bin->slabs_partial = 0;
+	bin->slabs_full = 0;
+}
+
+static void
+bin_release(struct fastmem_bin *bin, struct fastmem_socket_state *socket)
+{
+	struct fastmem_slab *slab;
+
+	while ((slab = TAILQ_FIRST(&bin->partial)) != NULL) {
+		TAILQ_REMOVE(&bin->partial, slab, list);
+		slab_release(socket, slab);
+	}
+	while ((slab = TAILQ_FIRST(&bin->full)) != NULL) {
+		TAILQ_REMOVE(&bin->full, slab, list);
+		slab_release(socket, slab);
+	}
+}
+
+static unsigned int
+bin_pop_locked(struct fastmem_bin *bin, void **objs, unsigned int n)
+{
+	unsigned int got = 0;
+
+	while (got < n) {
+		struct fastmem_slab *slab = TAILQ_FIRST(&bin->partial);
+		void *obj;
+
+		if (slab == NULL)
+			break;
+
+		obj = slab->free_head;
+		slab->free_head = *(void **)obj;
+		slab->free_count--;
+		objs[got++] = obj;
+
+		if (slab->free_count == 0) {
+			TAILQ_REMOVE(&bin->partial, slab, list);
+			TAILQ_INSERT_HEAD(&bin->full, slab, list);
+			bin->slabs_partial--;
+			bin->slabs_full++;
+		}
+	}
+
+	return got;
+}
+
+/*
+ * Fully-drained slabs are accumulated in @p to_release for the
+ * caller to return after dropping the lock.
+ */
+static unsigned int
+bin_push_locked(struct fastmem_bin *bin, void **objs, unsigned int n,
+		struct fastmem_slab **to_release)
+{
+	unsigned int n_release = 0;
+	unsigned int i;
+
+	for (i = 0; i < n; i++) {
+		void *obj = objs[i];
+		struct fastmem_slab *slab = (struct fastmem_slab *)
+			((uintptr_t)obj & ~(uintptr_t)FASTMEM_SLAB_MASK);
+		bool was_full = slab->free_count == 0;
+
+		*(void **)obj = slab->free_head;
+		slab->free_head = obj;
+		slab->free_count++;
+
+		if (was_full) {
+			TAILQ_REMOVE(&bin->full, slab, list);
+			TAILQ_INSERT_HEAD(&bin->partial, slab, list);
+			bin->slabs_full--;
+			bin->slabs_partial++;
+		}
+
+		if (slab->free_count == slab->n_slots) {
+			TAILQ_REMOVE(&bin->partial, slab, list);
+			bin->slabs_partial--;
+			bin->slab_releases++;
+			to_release[n_release++] = slab;
+		}
+	}
+
+	return n_release;
+}
+
+/*
+ * The lock may be dropped and re-acquired internally.
+ */
+static int
+bin_ensure_partial_locked(struct fastmem_bin *bin,
+		struct fastmem_socket_state *socket)
+{
+	struct fastmem_slab *slab;
+
+	if (TAILQ_FIRST(&bin->partial) != NULL)
+		return 0;
+
+	rte_spinlock_unlock(&bin->lock);
+
+	slab = slab_acquire(socket, bin->socket_id);
+	if (slab == NULL) {
+		rte_spinlock_lock(&bin->lock);
+		return -ENOMEM;
+	}
+
+	rte_spinlock_lock(&bin->lock);
+
+	/* Another thread may have added a partial slab meanwhile. */
+	if (TAILQ_FIRST(&bin->partial) != NULL) {
+		rte_spinlock_unlock(&bin->lock);
+		slab_release(socket, slab);
+		rte_spinlock_lock(&bin->lock);
+		return 0;
+	}
+
+	slab_init(bin, slab);
+	TAILQ_INSERT_HEAD(&bin->partial, slab, list);
+	bin->slabs_partial++;
+	bin->slab_acquires++;
+
+	return 0;
+}
+
+static void *
+bin_alloc_one(struct fastmem_bin *bin)
+{
+	struct fastmem_socket_state *socket = &fastmem->sockets[bin->socket_id];
+	void *obj;
+
+	rte_spinlock_lock(&bin->lock);
+
+	while (bin_pop_locked(bin, &obj, 1) == 0) {
+		int rc = bin_ensure_partial_locked(bin, socket);
+
+		if (rc < 0) {
+			rte_spinlock_unlock(&bin->lock);
+			rte_errno = -rc;
+			return NULL;
+		}
+	}
+
+	rte_spinlock_unlock(&bin->lock);
+
+	return obj;
+}
+
+static unsigned int
+bin_alloc_bulk(struct fastmem_bin *bin, void **objs, unsigned int n)
+{
+	struct fastmem_socket_state *socket = &fastmem->sockets[bin->socket_id];
+	unsigned int got = 0;
+
+	rte_spinlock_lock(&bin->lock);
+
+	while (got < n) {
+		got += bin_pop_locked(bin, objs + got, n - got);
+		if (got == n)
+			break;
+
+		if (bin_ensure_partial_locked(bin, socket) < 0)
+			break;
+	}
+
+	rte_spinlock_unlock(&bin->lock);
+
+	return got;
+}
+
+static void
+bin_free_one(struct fastmem_bin *bin, void *obj)
+{
+	unsigned int n_release;
+	struct fastmem_slab *slab_to_release = NULL;
+	struct fastmem_socket_state *socket;
+
+	rte_spinlock_lock(&bin->lock);
+	n_release = bin_push_locked(bin, &obj, 1, &slab_to_release);
+	rte_spinlock_unlock(&bin->lock);
+
+	if (n_release > 0) {
+		socket = &fastmem->sockets[bin->socket_id];
+		slab_release(socket, slab_to_release);
+	}
+}
+
+static void
+bin_free_bulk(struct fastmem_bin *bin, void **objs, unsigned int n)
+{
+	struct fastmem_socket_state *socket = &fastmem->sockets[bin->socket_id];
+	struct fastmem_slab *to_release[FASTMEM_CACHE_BASE_CAPACITY];
+	unsigned int n_release;
+	unsigned int i;
+
+	RTE_VERIFY(n <= RTE_DIM(to_release));
+
+	rte_spinlock_lock(&bin->lock);
+	n_release = bin_push_locked(bin, objs, n, to_release);
+	rte_spinlock_unlock(&bin->lock);
+
+	for (i = 0; i < n_release; i++)
+		slab_release(socket, to_release[i]);
+}
+
+static inline unsigned int
+cache_capacity(unsigned int class_idx)
+{
+	unsigned int class_log2 = class_idx + FASTMEM_MIN_CLASS_LOG2;
+	unsigned int shift;
+	unsigned int cap;
+
+	if (class_log2 <= FASTMEM_CACHE_BASE_CLASS_LOG2)
+		return FASTMEM_CACHE_BASE_CAPACITY;
+
+	shift = class_log2 - FASTMEM_CACHE_BASE_CLASS_LOG2;
+	cap = FASTMEM_CACHE_BASE_CAPACITY >> shift;
+
+	return cap < FASTMEM_CACHE_FLOOR_CAPACITY ?
+		FASTMEM_CACHE_FLOOR_CAPACITY : cap;
+}
+
+static inline struct fastmem_cache **
+cache_slot(struct fastmem_socket_state *socket, unsigned int class_idx,
+		unsigned int lcore_id)
+{
+	if (lcore_id >= RTE_MAX_LCORE)
+		return NULL;
+	return &socket->caches[lcore_id][class_idx];
+}
+
+static struct fastmem_cache *
+cache_create(struct fastmem_socket_state *socket,
+		unsigned int class_idx, unsigned int lcore_id)
+{
+	struct fastmem_cache **slot = cache_slot(socket, class_idx, lcore_id);
+	struct fastmem_cache *cache;
+	unsigned int capacity;
+	size_t cache_size;
+
+	if (slot == NULL)
+		return NULL;
+
+	cache = *slot;
+	if (cache != NULL)
+		return cache;
+
+	capacity = cache_capacity(class_idx);
+	cache_size = sizeof(*cache) + capacity * sizeof(void *);
+
+	/*
+	 * Allocate the cache struct from fastmem on the calling
+	 * lcore's socket (NUMA-local to the writer). Bypasses the
+	 * cache layer to avoid recursion.
+	 */
+	{
+		unsigned int cache_class =
+			size_to_class(cache_size, RTE_CACHE_LINE_SIZE);
+		unsigned int own_socket = rte_socket_id();
+		struct fastmem_socket_state *alloc_socket;
+
+		if (cache_class >= FASTMEM_N_CLASSES) {
+			FASTMEM_LOG(ERR,
+				"cache size %zu exceeds max size class",
+				cache_size);
+			return NULL;
+		}
+
+		if (own_socket >= RTE_MAX_NUMA_NODES)
+			own_socket = (unsigned int)socket->bins[0].socket_id;
+
+		alloc_socket = &fastmem->sockets[own_socket];
+
+		cache = bin_alloc_one(&alloc_socket->bins[cache_class]);
+		if (cache == NULL) {
+			FASTMEM_LOG(ERR,
+				"failed to allocate cache for class %u on socket %u",
+				class_idx, own_socket);
+			return NULL;
+		}
+	}
+
+	cache->count = 0;
+	cache->capacity = capacity;
+	cache->target = capacity / 2;
+	cache->alloc_cache_hits = 0;
+	cache->alloc_cache_misses = 0;
+	cache->alloc_nomem = 0;
+	cache->free_cache_hits = 0;
+	cache->free_cache_misses = 0;
+
+	*slot = cache;
+
+	return cache;
+}
+
+static __rte_always_inline struct fastmem_cache *
+cache_get(struct fastmem_socket_state *socket, unsigned int class_idx,
+		unsigned int lcore_id)
+{
+	struct fastmem_cache **slot = cache_slot(socket, class_idx, lcore_id);
+	struct fastmem_cache *cache;
+
+	if (slot == NULL)
+		return NULL;
+
+	cache = *slot;
+	if (cache != NULL)
+		return cache;
+
+	return cache_create(socket, class_idx, lcore_id);
+}
+
+static __rte_always_inline void *
+cache_pop(struct fastmem_cache *cache, struct fastmem_bin *bin)
+{
+	if (cache->count > 0) {
+		cache->alloc_cache_hits++;
+		return cache->objs[--cache->count];
+	}
+
+	cache->count = bin_alloc_bulk(bin, cache->objs, cache->target);
+	if (cache->count == 0)
+		return NULL;
+
+	cache->alloc_cache_misses++;
+	return cache->objs[--cache->count];
+}
+
+static __rte_always_inline void
+cache_push(struct fastmem_cache *cache, struct fastmem_bin *bin, void *obj)
+{
+	unsigned int drain;
+
+	if (cache->count < cache->capacity) {
+		cache->free_cache_hits++;
+		cache->objs[cache->count++] = obj;
+		return;
+	}
+
+	cache->free_cache_misses++;
+
+	/*
+	 * Drain the oldest (bottom) half to the bin, keeping the
+	 * newest (top) half for temporal reuse.
+	 */
+	drain = cache->count - cache->target;
+	bin_free_bulk(bin, cache->objs, drain);
+	memmove(cache->objs, cache->objs + drain,
+		cache->target * sizeof(cache->objs[0]));
+	cache->count = cache->target;
+
+	cache->objs[cache->count++] = obj;
+}
+
+static void
+socket_release_caches(struct fastmem_socket_state *socket)
+{
+	unsigned int lcore;
+	unsigned int c;
+
+	for (lcore = 0; lcore < RTE_MAX_LCORE; lcore++) {
+		for (c = 0; c < FASTMEM_N_CLASSES; c++) {
+			struct fastmem_cache *cache = socket->caches[lcore][c];
+			struct fastmem_slab *cache_slab;
+
+			if (cache == NULL)
+				continue;
+
+			if (cache->count > 0) {
+				bin_free_bulk(&socket->bins[c],
+					cache->objs, cache->count);
+				cache->count = 0;
+			}
+
+			cache_slab = slab_of(cache);
+			bin_free_one(cache_slab->bin, cache);
+
+			socket->caches[lcore][c] = NULL;
+		}
+	}
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_init, 25.07)
+int
+rte_fastmem_init(void)
+{
+	unsigned int s, c;
+
+	if (fastmem != NULL)
+		return -EBUSY;
+
+	fastmem_mz = rte_memzone_reserve_aligned("fastmem_state",
+			sizeof(*fastmem), SOCKET_ID_ANY, 0,
+			RTE_CACHE_LINE_SIZE);
+	if (fastmem_mz == NULL)
+		return -ENOMEM;
+
+	fastmem = fastmem_mz->addr;
+	memset(fastmem, 0, sizeof(*fastmem));
+
+	for (s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_socket_state *socket = &fastmem->sockets[s];
+
+		rte_spinlock_init(&socket->lock);
+		socket->memory_limit = SIZE_MAX;
+
+		for (c = 0; c < FASTMEM_N_CLASSES; c++)
+			bin_init(&socket->bins[c], c, (int)s);
+	}
+
+	return 0;
+}
+
+static void
+release_socket(struct fastmem_socket_state *socket)
+{
+	unsigned int c;
+	unsigned int i;
+
+	socket_release_caches(socket);
+
+	for (c = 0; c < FASTMEM_N_CLASSES; c++)
+		bin_release(&socket->bins[c], socket);
+
+	for (i = 0; i < socket->n_memzones; i++)
+		rte_memzone_free(socket->memzones[i]);
+
+	socket->free_head = NULL;
+	socket->reserved_bytes = 0;
+	socket->n_memzones = 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_deinit, 25.07)
+void
+rte_fastmem_deinit(void)
+{
+	unsigned int i;
+
+	if (fastmem == NULL)
+		return;
+
+	for (i = 0; i < RTE_MAX_NUMA_NODES; i++)
+		release_socket(&fastmem->sockets[i]);
+
+	rte_memzone_free(fastmem_mz);
+	fastmem_mz = NULL;
+	fastmem = NULL;
+}
+
+/* Same resolution order as rte_malloc's malloc_get_numa_socket(). */
+static __rte_always_inline unsigned int
+local_socket_id(void)
+{
+	unsigned int sid = rte_socket_id();
+
+	if (likely(sid < RTE_MAX_NUMA_NODES))
+		return sid;
+
+	sid = rte_lcore_to_socket_id(rte_get_main_lcore());
+	if (likely(sid < RTE_MAX_NUMA_NODES))
+		return sid;
+
+	return (unsigned int)rte_socket_id_by_idx(0);
+}
+
+static int
+reserve_on_socket(int sid, size_t size)
+{
+	struct fastmem_socket_state *socket = &fastmem->sockets[sid];
+	int rc = 0;
+
+	rte_spinlock_lock(&socket->lock);
+
+	while (socket->reserved_bytes < size) {
+		rc = grow_socket(socket, sid);
+		if (rc < 0)
+			break;
+	}
+
+	rte_spinlock_unlock(&socket->lock);
+
+	return rc;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_reserve, 25.07)
+int
+rte_fastmem_reserve(size_t size, int socket_id)
+{
+	unsigned int i;
+	int rc;
+
+	if (fastmem == NULL)
+		return -EINVAL;
+
+	if (socket_id != SOCKET_ID_ANY) {
+		if (socket_id < 0 || socket_id >= RTE_MAX_NUMA_NODES)
+			return -EINVAL;
+		return reserve_on_socket(socket_id, size);
+	}
+
+	rc = reserve_on_socket(local_socket_id(), size);
+	if (rc == 0)
+		return 0;
+
+	for (i = 0; i < rte_socket_count(); i++) {
+		int sid = rte_socket_id_by_idx(i);
+
+		if (sid < 0 || (unsigned int)sid == local_socket_id())
+			continue;
+
+		rc = reserve_on_socket(sid, size);
+		if (rc == 0)
+			return 0;
+	}
+
+	return rc;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_set_limit, 25.07)
+int
+rte_fastmem_set_limit(int socket_id, size_t max_bytes)
+{
+	if (fastmem == NULL)
+		return -EINVAL;
+
+	if (socket_id == SOCKET_ID_ANY) {
+		for (unsigned int i = 0; i < RTE_MAX_NUMA_NODES; i++)
+			fastmem->sockets[i].memory_limit = max_bytes;
+		return 0;
+	}
+
+	if (socket_id < 0 || socket_id >= RTE_MAX_NUMA_NODES)
+		return -EINVAL;
+
+	fastmem->sockets[socket_id].memory_limit = max_bytes;
+	return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_get_limit, 25.07)
+size_t
+rte_fastmem_get_limit(int socket_id)
+{
+	if (fastmem == NULL || socket_id < 0 || socket_id >= RTE_MAX_NUMA_NODES)
+		return 0;
+
+	return fastmem->sockets[socket_id].memory_limit;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_max_size, 25.07)
+size_t
+rte_fastmem_max_size(void)
+{
+	return FASTMEM_MAX_ALLOC_SIZE;
+}
+
+static __rte_always_inline void *
+alloc_from_socket(struct fastmem_socket_state *socket,
+		unsigned int class_idx, unsigned int lcore_id)
+{
+	struct fastmem_cache *cache;
+
+	cache = cache_get(socket, class_idx, lcore_id);
+	if (likely(cache != NULL))
+		return cache_pop(cache, &socket->bins[class_idx]);
+	return bin_alloc_one(&socket->bins[class_idx]);
+}
+
+static __rte_always_inline void
+do_free(void *ptr)
+{
+	struct fastmem_slab *slab;
+	struct fastmem_bin *bin;
+	struct fastmem_socket_state *socket;
+	unsigned int lcore_id;
+	struct fastmem_cache *cache;
+
+	slab = slab_of(ptr);
+	bin = slab->bin;
+	socket = &fastmem->sockets[bin->socket_id];
+
+	lcore_id = rte_lcore_id();
+	cache = cache_get(socket, bin->class_idx, lcore_id);
+	if (likely(cache != NULL))
+		cache_push(cache, bin, ptr);
+	else
+		bin_free_one(bin, ptr);
+}
+
+static __rte_always_inline int
+do_alloc_bulk(void **ptrs, unsigned int n, size_t size, size_t align,
+		unsigned int flags, unsigned int lcore_id,
+		int socket_id, bool fallback)
+{
+	unsigned int class_idx;
+	struct fastmem_socket_state *socket;
+	struct fastmem_cache *cache;
+	unsigned int got = 0;
+
+	RTE_ASSERT(fastmem != NULL);
+
+	if (align == 0)
+		align = RTE_CACHE_LINE_SIZE;
+	else if (unlikely((align & (align - 1)) != 0)) {
+		rte_errno = EINVAL;
+		return -EINVAL;
+	}
+
+	class_idx = size_to_class(size, align);
+	if (unlikely(class_idx >= FASTMEM_N_CLASSES)) {
+		rte_errno = E2BIG;
+		return -E2BIG;
+	}
+
+	socket = &fastmem->sockets[socket_id];
+	cache = cache_get(socket, class_idx, lcore_id);
+
+	if (likely(cache != NULL)) {
+		/* Drain from cache. */
+		unsigned int avail = RTE_MIN(cache->count, n);
+
+		cache->count -= avail;
+		memcpy(ptrs, &cache->objs[cache->count],
+			avail * sizeof(void *));
+		got = avail;
+		cache->alloc_cache_hits += avail;
+
+		if (got < n) {
+			unsigned int need = n - got;
+			unsigned int want = RTE_MAX(need, cache->target);
+			unsigned int filled;
+
+			if (want <= cache->capacity) {
+				/* Refill into cache, give caller their share. */
+				filled = bin_alloc_bulk(
+					&socket->bins[class_idx],
+					cache->objs, want);
+				if (filled > 0) {
+					cache->alloc_cache_misses += RTE_MIN(filled, need);
+				}
+				if (filled >= need) {
+					memcpy(ptrs + got,
+						cache->objs + filled - need,
+						need * sizeof(void *));
+					cache->count = filled - need;
+					got = n;
+				} else {
+					memcpy(ptrs + got, cache->objs,
+						filled * sizeof(void *));
+					got += filled;
+					cache->count = 0;
+				}
+			} else {
+				/* n exceeds cache capacity; pull directly. */
+				unsigned int direct = bin_alloc_bulk(
+					&socket->bins[class_idx],
+					ptrs + got, need);
+				if (direct > 0)
+					cache->alloc_cache_misses += direct;
+				got += direct;
+			}
+		}
+	} else {
+		got = bin_alloc_bulk(&socket->bins[class_idx], ptrs, n);
+	}
+
+	if (unlikely(got < n) && fallback) {
+		unsigned int i;
+
+		for (i = 0; i < rte_socket_count() && got < n; i++) {
+			int sid = rte_socket_id_by_idx(i);
+
+			if (sid < 0 || sid == socket_id)
+				continue;
+
+			socket = &fastmem->sockets[sid];
+			cache = cache_get(socket, class_idx, lcore_id);
+			if (likely(cache != NULL)) {
+				unsigned int avail =
+					RTE_MIN(cache->count, n - got);
+				cache->count -= avail;
+				memcpy(ptrs + got,
+					&cache->objs[cache->count],
+					avail * sizeof(void *));
+				cache->alloc_cache_hits += avail;
+				got += avail;
+			}
+			if (got < n) {
+				unsigned int direct = bin_alloc_bulk(
+					&socket->bins[class_idx],
+					ptrs + got, n - got);
+				if (direct > 0 && cache != NULL)
+					cache->alloc_cache_misses += direct;
+				got += direct;
+			}
+		}
+	}
+
+	if (unlikely(got < n)) {
+		/* All-or-nothing: return what we got. */
+		struct fastmem_cache **slot;
+		unsigned int i;
+
+		for (i = 0; i < got; i++)
+			do_free(ptrs[i]);
+
+		slot = cache_slot(
+			&fastmem->sockets[socket_id], class_idx,
+			lcore_id);
+		if (slot != NULL && *slot != NULL)
+			(*slot)->alloc_nomem++;
+		rte_errno = ENOMEM;
+		return -ENOMEM;
+	}
+
+	if (flags & RTE_FASTMEM_F_ZERO) {
+		size_t cs = class_size(class_idx);
+		unsigned int i;
+
+		for (i = 0; i < n; i++)
+			memset(ptrs[i], 0, cs);
+	}
+
+	return 0;
+}
+
+static __rte_always_inline void *
+do_alloc(size_t size, size_t align, unsigned int flags,
+		unsigned int lcore_id, int socket_id, bool fallback)
+{
+	unsigned int class_idx;
+	struct fastmem_cache **slot;
+	void *obj;
+
+	RTE_ASSERT(fastmem != NULL);
+
+	if (align == 0)
+		align = RTE_CACHE_LINE_SIZE;
+	else if (unlikely((align & (align - 1)) != 0)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	class_idx = size_to_class(size, align);
+	if (unlikely(class_idx >= FASTMEM_N_CLASSES)) {
+		rte_errno = E2BIG;
+		return NULL;
+	}
+
+	obj = alloc_from_socket(&fastmem->sockets[socket_id],
+			class_idx, lcore_id);
+
+	if (likely(obj != NULL))
+		goto out;
+
+	if (fallback) {
+		unsigned int i;
+
+		for (i = 0; i < rte_socket_count(); i++) {
+			int sid = rte_socket_id_by_idx(i);
+
+			if (sid < 0 || sid == socket_id)
+				continue;
+
+			obj = alloc_from_socket(&fastmem->sockets[sid],
+					class_idx, lcore_id);
+			if (obj != NULL)
+				goto out;
+		}
+	}
+
+	slot = cache_slot(
+		&fastmem->sockets[socket_id], class_idx, lcore_id);
+	if (slot != NULL && *slot != NULL)
+		(*slot)->alloc_nomem++;
+	rte_errno = ENOMEM;
+	return NULL;
+
+out:
+	if (flags & RTE_FASTMEM_F_ZERO)
+		memset(obj, 0, class_size(class_idx));
+
+	return obj;
+}
+
+void *
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_alloc, 25.07)
+rte_fastmem_alloc(size_t size, size_t align, unsigned int flags)
+{
+	return do_alloc(size, align, flags, rte_lcore_id(),
+			local_socket_id(), false);
+}
+
+void *
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_alloc_socket, 25.07)
+rte_fastmem_alloc_socket(size_t size, size_t align, unsigned int flags,
+		int socket_id)
+{
+	if (socket_id == SOCKET_ID_ANY)
+		return do_alloc(size, align, flags, rte_lcore_id(),
+				local_socket_id(), true);
+
+	if (unlikely(socket_id < 0 || socket_id >= RTE_MAX_NUMA_NODES)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	return do_alloc(size, align, flags, rte_lcore_id(), socket_id, false);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_free, 25.07)
+void
+rte_fastmem_free(void *ptr)
+{
+	if (unlikely(ptr == NULL))
+		return;
+
+	do_free(ptr);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_alloc_bulk, 25.07)
+int
+rte_fastmem_alloc_bulk(void **ptrs, unsigned int n, size_t size, size_t align,
+		unsigned int flags)
+{
+	return do_alloc_bulk(ptrs, n, size, align, flags,
+			rte_lcore_id(), local_socket_id(), false);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_alloc_bulk_socket, 25.07)
+int
+rte_fastmem_alloc_bulk_socket(void **ptrs, unsigned int n, size_t size,
+		size_t align, unsigned int flags, int socket_id)
+{
+	if (socket_id == SOCKET_ID_ANY)
+		return do_alloc_bulk(ptrs, n, size, align, flags,
+				rte_lcore_id(), local_socket_id(), true);
+
+	if (unlikely(socket_id < 0 || socket_id >= RTE_MAX_NUMA_NODES)) {
+		rte_errno = EINVAL;
+		return -EINVAL;
+	}
+
+	return do_alloc_bulk(ptrs, n, size, align, flags,
+			rte_lcore_id(), socket_id, false);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_free_bulk, 25.07)
+void
+rte_fastmem_free_bulk(void **ptrs, unsigned int n)
+{
+	unsigned int lcore_id;
+	struct fastmem_slab *slab;
+	struct fastmem_bin *bin;
+	struct fastmem_socket_state *socket;
+	struct fastmem_cache *cache;
+	unsigned int space;
+	unsigned int i;
+
+	if (unlikely(n == 0))
+		return;
+
+	lcore_id = rte_lcore_id();
+
+	/* Fast path: check if first object gives us the bin. */
+	slab = slab_of(ptrs[0]);
+	bin = slab->bin;
+	socket = &fastmem->sockets[bin->socket_id];
+	cache = cache_get(socket, bin->class_idx, lcore_id);
+
+	if (unlikely(cache == NULL)) {
+		for (i = 0; i < n; i++)
+			do_free(ptrs[i]);
+		return;
+	}
+
+	/*
+	 * Try to push all objects into the cache in one memcpy.
+	 * If any object belongs to a different bin, fall back to
+	 * per-object free for the remainder.
+	 */
+	space = cache->capacity - cache->count;
+	if (likely(n <= space)) {
+		/* Verify all same bin (common case). */
+		for (i = 1; i < n; i++) {
+			if (slab_of(ptrs[i])->bin != bin)
+				goto slow;
+		}
+		cache->free_cache_hits += n;
+		memcpy(&cache->objs[cache->count], ptrs,
+			n * sizeof(void *));
+		cache->count += n;
+		return;
+	}
+
+	/* Would overflow cache — drain first, then push. */
+	if (n <= cache->capacity) {
+		unsigned int drain;
+
+		for (i = 1; i < n; i++) {
+			if (slab_of(ptrs[i])->bin != bin)
+				goto slow;
+		}
+
+		cache->free_cache_misses += n;
+		drain = cache->count - cache->target + n;
+		if (drain > cache->count)
+			drain = cache->count;
+		if (drain > 0) {
+			bin_free_bulk(bin, cache->objs, drain);
+			cache->count -= drain;
+			memmove(cache->objs, cache->objs + drain,
+				cache->count * sizeof(cache->objs[0]));
+		}
+		memcpy(&cache->objs[cache->count], ptrs,
+			n * sizeof(void *));
+		cache->count += n;
+		return;
+	}
+
+slow:
+	for (i = 0; i < n; i++)
+		do_free(ptrs[i]);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_virt2iova, 25.07)
+rte_iova_t
+rte_fastmem_virt2iova(const void *ptr)
+{
+	struct fastmem_slab *slab;
+
+	RTE_ASSERT(fastmem != NULL);
+
+	slab = slab_of((void *)(uintptr_t)ptr);
+
+	return slab->iova_base + ((uintptr_t)ptr - (uintptr_t)slab);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_cache_flush, 25.07)
+void
+rte_fastmem_cache_flush(void)
+{
+	unsigned int lcore_id;
+	unsigned int s, c;
+
+	if (fastmem == NULL)
+		return;
+
+	lcore_id = rte_lcore_id();
+	if (lcore_id >= RTE_MAX_LCORE)
+		return;
+
+	for (s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_socket_state *socket = &fastmem->sockets[s];
+
+		for (c = 0; c < FASTMEM_N_CLASSES; c++) {
+			struct fastmem_cache *cache =
+				socket->caches[lcore_id][c];
+			struct fastmem_slab *cache_slab;
+
+			if (cache == NULL)
+				continue;
+
+			if (cache->count > 0) {
+				bin_free_bulk(&socket->bins[c],
+					cache->objs, cache->count);
+				cache->count = 0;
+			}
+
+			cache_slab = slab_of(cache);
+			bin_free_one(cache_slab->bin, cache);
+
+			socket->caches[lcore_id][c] = NULL;
+		}
+	}
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_stats, 25.07)
+int
+rte_fastmem_stats(struct rte_fastmem_stats *stats)
+{
+	if (stats == NULL || fastmem == NULL)
+		return -EINVAL;
+
+	*stats = (struct rte_fastmem_stats){0};
+	stats->n_classes = FASTMEM_N_CLASSES;
+
+	for (unsigned int s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_socket_state *socket = &fastmem->sockets[s];
+
+		stats->bytes_backing += socket->reserved_bytes;
+
+		for (unsigned int c = 0; c < FASTMEM_N_CLASSES; c++) {
+			uint64_t class_allocs = 0, class_frees = 0;
+
+			for (unsigned int l = 0; l < RTE_MAX_LCORE; l++) {
+				struct fastmem_cache *cache =
+					socket->caches[l][c];
+				if (cache == NULL)
+					continue;
+				class_allocs += cache->alloc_cache_hits +
+					cache->alloc_cache_misses;
+				class_frees += cache->free_cache_hits +
+					cache->free_cache_misses;
+				stats->alloc_nomem += cache->alloc_nomem;
+			}
+			stats->alloc_total += class_allocs;
+			stats->free_total += class_frees;
+			if (class_allocs > class_frees)
+				stats->bytes_in_use += class_size(c) *
+					(class_allocs - class_frees);
+		}
+	}
+
+	return 0;
+}
+
+static inline unsigned int
+exact_class_idx(size_t sz)
+{
+	unsigned int log2;
+
+	if (sz < FASTMEM_MIN_SIZE || sz > FASTMEM_MAX_ALLOC_SIZE)
+		return FASTMEM_N_CLASSES;
+	if ((sz & (sz - 1)) != 0)
+		return FASTMEM_N_CLASSES;
+
+	log2 = (unsigned int)rte_ctz64(sz);
+	if (log2 < FASTMEM_MIN_CLASS_LOG2 || log2 > FASTMEM_MAX_CLASS_LOG2)
+		return FASTMEM_N_CLASSES;
+
+	return log2 - FASTMEM_MIN_CLASS_LOG2;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_stats_class, 25.07)
+int
+rte_fastmem_stats_class(size_t class_size_arg,
+		struct rte_fastmem_class_stats *stats)
+{
+	unsigned int c;
+	uint64_t allocs, frees;
+
+	if (stats == NULL || fastmem == NULL)
+		return -EINVAL;
+
+	c = exact_class_idx(class_size_arg);
+	if (c >= FASTMEM_N_CLASSES)
+		return -EINVAL;
+
+	*stats = (struct rte_fastmem_class_stats){0};
+	stats->class_size = class_size(c);
+
+	for (unsigned int s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_socket_state *socket = &fastmem->sockets[s];
+		struct fastmem_bin *bin = &socket->bins[c];
+
+		for (unsigned int l = 0; l < RTE_MAX_LCORE; l++) {
+			struct fastmem_cache *cache = socket->caches[l][c];
+			if (cache == NULL)
+				continue;
+			stats->alloc_cache_hits += cache->alloc_cache_hits;
+			stats->alloc_cache_misses += cache->alloc_cache_misses;
+			stats->alloc_nomem += cache->alloc_nomem;
+			stats->free_cache_hits += cache->free_cache_hits;
+			stats->free_cache_misses += cache->free_cache_misses;
+		}
+
+		stats->slab_acquires += bin->slab_acquires;
+		stats->slab_releases += bin->slab_releases;
+		stats->slabs_partial += bin->slabs_partial;
+		stats->slabs_full += bin->slabs_full;
+	}
+
+	allocs = stats->alloc_cache_hits + stats->alloc_cache_misses;
+	frees = stats->free_cache_hits + stats->free_cache_misses;
+	if (allocs > frees)
+		stats->in_use = allocs - frees;
+
+	return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_stats_lcore, 25.07)
+int
+rte_fastmem_stats_lcore(unsigned int lcore_id,
+		struct rte_fastmem_lcore_stats *stats)
+{
+	if (stats == NULL || fastmem == NULL)
+		return -EINVAL;
+	if (lcore_id >= RTE_MAX_LCORE)
+		return -EINVAL;
+
+	*stats = (struct rte_fastmem_lcore_stats){0};
+
+	for (unsigned int s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_socket_state *socket = &fastmem->sockets[s];
+
+		for (unsigned int c = 0; c < FASTMEM_N_CLASSES; c++) {
+			struct fastmem_cache *cache =
+				socket->caches[lcore_id][c];
+			if (cache == NULL)
+				continue;
+			stats->alloc_cache_hits += cache->alloc_cache_hits;
+			stats->alloc_cache_misses += cache->alloc_cache_misses;
+			stats->alloc_nomem += cache->alloc_nomem;
+			stats->free_cache_hits += cache->free_cache_hits;
+			stats->free_cache_misses += cache->free_cache_misses;
+		}
+	}
+
+	return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_stats_lcore_class, 25.07)
+int
+rte_fastmem_stats_lcore_class(unsigned int lcore_id, size_t class_size_arg,
+		struct rte_fastmem_lcore_class_stats *stats)
+{
+	unsigned int c;
+
+	if (stats == NULL || fastmem == NULL)
+		return -EINVAL;
+	if (lcore_id >= RTE_MAX_LCORE)
+		return -EINVAL;
+
+	c = exact_class_idx(class_size_arg);
+	if (c >= FASTMEM_N_CLASSES)
+		return -EINVAL;
+
+	*stats = (struct rte_fastmem_lcore_class_stats){0};
+	stats->class_size = class_size(c);
+
+	for (unsigned int s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_cache *cache =
+			fastmem->sockets[s].caches[lcore_id][c];
+		if (cache == NULL)
+			continue;
+		stats->alloc_cache_hits += cache->alloc_cache_hits;
+		stats->alloc_cache_misses += cache->alloc_cache_misses;
+		stats->alloc_nomem += cache->alloc_nomem;
+		stats->free_cache_hits += cache->free_cache_hits;
+		stats->free_cache_misses += cache->free_cache_misses;
+	}
+
+	return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_stats_reset, 25.07)
+void
+rte_fastmem_stats_reset(void)
+{
+	if (fastmem == NULL)
+		return;
+
+	for (unsigned int s = 0; s < RTE_MAX_NUMA_NODES; s++) {
+		struct fastmem_socket_state *socket = &fastmem->sockets[s];
+
+		for (unsigned int c = 0; c < FASTMEM_N_CLASSES; c++) {
+			struct fastmem_bin *bin = &socket->bins[c];
+
+			bin->slab_acquires = 0;
+			bin->slab_releases = 0;
+
+			for (unsigned int l = 0; l < RTE_MAX_LCORE; l++) {
+				struct fastmem_cache *cache =
+					socket->caches[l][c];
+				if (cache == NULL)
+					continue;
+				cache->alloc_cache_hits = 0;
+				cache->alloc_cache_misses = 0;
+				cache->alloc_nomem = 0;
+				cache->free_cache_hits = 0;
+				cache->free_cache_misses = 0;
+			}
+		}
+	}
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fastmem_classes, 25.07)
+unsigned int
+rte_fastmem_classes(size_t *sizes)
+{
+	if (sizes != NULL)
+		for (unsigned int i = 0; i < FASTMEM_N_CLASSES; i++)
+			sizes[i] = class_size(i);
+	return FASTMEM_N_CLASSES;
+}
diff --git a/lib/fastmem/rte_fastmem.h b/lib/fastmem/rte_fastmem.h
new file mode 100644
index 0000000000..cd1abf9844
--- /dev/null
+++ b/lib/fastmem/rte_fastmem.h
@@ -0,0 +1,644 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Ericsson AB
+ */
+
+#ifndef _RTE_FASTMEM_H_
+#define _RTE_FASTMEM_H_
+
+/**
+ * @file
+ *
+ * RTE Fastmem
+ *
+ * @warning
+ * @b EXPERIMENTAL:
+ * All functions in this file may be changed or removed without prior notice.
+ *
+ * The fastmem library is a fast, general-purpose small-object
+ * allocator for DPDK applications. It is intended to allow an
+ * application to replace its many per-type mempools — each sized
+ * for a single object type (a connection, a session, a work item,
+ * a timer, etc.) — with a single allocator that handles arbitrary
+ * object sizes, grows on demand, and offers mempool-level
+ * performance for the common allocation and free paths.
+ *
+ * Like mempool, fastmem is backed by huge pages, is NUMA-aware,
+ * supports bulk operations, and uses per-lcore caches to reduce
+ * shared-state contention. Unlike mempool, it does not require the
+ * caller to declare object sizes or counts up front.
+ *
+ * There is a single, global fastmem instance per process. The
+ * instance is brought up with rte_fastmem_init() and torn down with
+ * rte_fastmem_deinit(). Allocations are made with
+ * rte_fastmem_alloc() and freed with rte_fastmem_free().
+ *
+ * The allocator is bounded to small-object allocations. Requests
+ * larger than rte_fastmem_max_size() are rejected; callers with
+ * such needs should use rte_malloc() directly.
+ *
+ * Backing memory is reserved from DPDK memzones. Once reserved,
+ * backing memory is not returned to the system during the
+ * allocator's lifetime. Callers that need predictable latency may
+ * pre-reserve backing memory up front using rte_fastmem_reserve(),
+ * avoiding memzone-reservation overhead during steady-state
+ * operation.
+ *
+ * Alignment argument, @c align:
+ *   If non-zero, @c align specifies an exact minimum alignment and
+ *   must be a power of 2. If zero, the default alignment is
+ *   @c RTE_CACHE_LINE_SIZE, so that objects obtained from distinct
+ *   calls cannot false-share a cache line.
+ *
+ * Threads and per-lcore caches:
+ *   Allocate and free calls from EAL threads are served through a
+ *   per-lcore cache, which makes the common path lock-free.
+ *   Unregistered non-EAL threads do not use a cache; their
+ *   allocate and free calls go directly to shared state, take an
+ *   internal lock, and cost more per call.
+ *
+ * Non-preemptible caller:
+ *   Callers should not be preemptible while inside a fastmem call.
+ *   Fastmem uses internal spinlocks; if a caller is preempted
+ *   while holding one, any other thread that subsequently needs
+ *   the same lock stalls until the preempted caller resumes.
+ */
+
+#include <stddef.h>
+
+#include <rte_bitops.h>
+#include <rte_common.h>
+#include <rte_compat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Flag for rte_fastmem_alloc() and its variants: initialize the
+ * returned memory to zero before returning it to the caller.
+ */
+#define RTE_FASTMEM_F_ZERO RTE_BIT32(0)
+
+/**
+ * Initialize the fastmem allocator.
+ *
+ * Sets up the library's internal state. Must be called before any
+ * allocation call. Typically called once per process, after
+ * rte_eal_init() and before the application's worker threads begin
+ * making allocations.
+ *
+ * Initialization does not pre-reserve any backing memory; memzones
+ * are reserved lazily as allocations require. An application that
+ * wants to avoid memzone-reservation latency on the allocation
+ * path should follow rte_fastmem_init() with one or more calls to
+ * rte_fastmem_reserve().
+ *
+ * This function is not thread-safe and must not be called
+ * concurrently with any other fastmem function.
+ *
+ * @return
+ *  - 0: Success.
+ *  - -EBUSY: The allocator is already initialized.
+ *  - -ENOMEM: Unable to allocate internal state.
+ */
+__rte_experimental
+int
+rte_fastmem_init(void);
+
+/**
+ * Tear down the fastmem allocator.
+ *
+ * Releases the library's internal state and frees all backing
+ * memzones. After this call, no fastmem allocations or frees may
+ * be made until rte_fastmem_init() is called again.
+ *
+ * The caller is responsible for ensuring that no fastmem-allocated
+ * objects remain in use. Outstanding allocations at deinit time
+ * result in undefined behavior.
+ *
+ * This function is not thread-safe and must not be called
+ * concurrently with any other fastmem function.
+ */
+__rte_experimental
+void
+rte_fastmem_deinit(void);
+
+/**
+ * Pre-reserve backing memory.
+ *
+ * Ensures that at least @p size bytes of memzone-backed memory are
+ * available to the allocator on @p socket_id, reserving additional
+ * memzones from EAL as needed to reach that total. Subsequent
+ * allocations served from the pre-reserved memory do not incur
+ * memzone-reservation cost.
+ *
+ * The reservation is cumulative: repeated calls to
+ * rte_fastmem_reserve() with the same @p socket_id grow the
+ * reservation monotonically. Reserved memory is never returned to
+ * the system during the allocator's lifetime.
+ *
+ * A typical use is to call rte_fastmem_reserve() once at
+ * application startup, with a size chosen to cover the expected
+ * steady-state working set. Allocations and frees during
+ * steady-state operation then avoid memzone reservations entirely.
+ *
+ * @param size
+ *  The minimum amount of backing memory, in bytes, to make
+ *  available on @p socket_id. The allocator may reserve more than
+ *  the requested amount due to internal rounding (e.g., to memzone
+ *  or block granularity).
+ *
+ * @param socket_id
+ *  The NUMA socket on which to reserve memory, or SOCKET_ID_ANY
+ *  to leave the choice to the allocator. With SOCKET_ID_ANY, the
+ *  allocator starts on the calling lcore's socket (or the first
+ *  configured socket if the caller is not bound to one) and falls
+ *  back to other sockets if the preferred socket cannot satisfy
+ *  the reservation.
+ *
+ * @return
+ *  - 0: Success.
+ *  - -ENOMEM: Insufficient huge-page memory to satisfy the request.
+ *  - -EINVAL: Invalid @p socket_id.
+ */
+__rte_experimental
+int
+rte_fastmem_reserve(size_t size, int socket_id);
+
+/**
+ * Set the maximum backing memory that may be reserved on a socket.
+ *
+ * Once the limit is reached, allocations that would require new
+ * backing memory on the constrained socket fail with ENOMEM.
+ * Already-reserved memory is not released.
+ *
+ * Setting a limit below the current reserved amount is allowed and
+ * prevents further growth.
+ *
+ * @param socket_id
+ *  The NUMA socket to constrain, or SOCKET_ID_ANY to apply the
+ *  limit to all sockets.
+ * @param max_bytes
+ *  Maximum backing memory in bytes, or SIZE_MAX for unlimited (the default).
+ * @return
+ *  - 0: Success.
+ *  - -EINVAL: Fastmem not initialized, or invalid @p socket_id.
+ */
+__rte_experimental
+int
+rte_fastmem_set_limit(int socket_id, size_t max_bytes);
+
+/**
+ * Get the maximum backing memory limit for a socket.
+ *
+ * @param socket_id
+ *  The NUMA socket to query.
+ * @return
+ *  The limit in bytes, or SIZE_MAX if unlimited.
+ */
+__rte_experimental
+size_t
+rte_fastmem_get_limit(int socket_id);
+
+/**
+ * Retrieve the largest allocation size the allocator supports.
+ *
+ * Requests larger than this size are rejected by the allocation
+ * functions. The returned value is a property of the allocator
+ * implementation and does not change across the lifetime of the
+ * process.
+ *
+ * @return
+ *  The largest supported allocation size, in bytes.
+ */
+__rte_experimental
+size_t
+rte_fastmem_max_size(void);
+
+/**
+ * Allocate an object from the fastmem allocator.
+ *
+ * Allocates at least @p size bytes, aligned to at least @p align
+ * bytes. The returned memory is backed by huge pages and is
+ * DMA-usable; its IOVA can be obtained via rte_fastmem_virt2iova().
+ *
+ * On NUMA systems, the memory is allocated on the socket of the
+ * calling lcore. Use rte_fastmem_alloc_socket() to target a
+ * specific socket.
+ *
+ * The allocated memory must be freed with rte_fastmem_free(). An
+ * allocation may be freed from any lcore, not only the lcore that
+ * made the allocation.
+ *
+ * This function is MT-safe.
+ *
+ * @param size
+ *  Requested allocation size, in bytes. Must not exceed
+ *  rte_fastmem_max_size().
+ *
+ * @param align
+ *  If 0, the returned pointer will be aligned to at least
+ *  @c RTE_CACHE_LINE_SIZE. Otherwise, the returned pointer will
+ *  be aligned on a multiple of @p align, which must be a power of
+ *  2.
+ *
+ * @param flags
+ *  A bitwise OR of zero or more RTE_FASTMEM_F_* flags. Use
+ *  RTE_FASTMEM_F_ZERO to obtain zero-initialized memory.
+ *
+ * @return
+ *  - A pointer to the allocated object on success.
+ *  - NULL on failure, with @c rte_errno set:
+ *    - E2BIG: @p size exceeds rte_fastmem_max_size().
+ *    - EINVAL: Invalid @p align (not a power of two).
+ *    - ENOMEM: Allocation could not be served from existing
+ *      backing memory and no additional memzone could be reserved.
+ */
+__rte_experimental
+void *
+rte_fastmem_alloc(size_t size, size_t align, unsigned int flags)
+	__rte_alloc_size(1) __rte_alloc_align(2);
+
+/**
+ * Allocate an object on a specific NUMA socket.
+ *
+ * Like rte_fastmem_alloc(), but targets the specified NUMA socket
+ * rather than the socket of the calling lcore. Use this variant
+ * when the lifetime or access pattern of the allocation is not
+ * tied to the calling lcore's socket.
+ *
+ * This function is MT-safe.
+ *
+ * @param size
+ *  Requested allocation size, in bytes. Must not exceed
+ *  rte_fastmem_max_size().
+ *
+ * @param align
+ *  If 0, the returned pointer will be aligned to at least
+ *  @c RTE_CACHE_LINE_SIZE. Otherwise, the returned pointer will
+ *  be aligned on a multiple of @p align, which must be a power of
+ *  2.
+ *
+ * @param flags
+ *  A bitwise OR of zero or more RTE_FASTMEM_F_* flags.
+ *
+ * @param socket_id
+ *  The NUMA socket on which to allocate, or SOCKET_ID_ANY to
+ *  leave the choice to the allocator. With SOCKET_ID_ANY, the
+ *  allocator starts on the calling lcore's socket (or the first
+ *  configured socket if the caller is not bound to one) and falls
+ *  back to other sockets if the preferred socket cannot satisfy
+ *  the request.
+ *
+ * @return
+ *  - A pointer to the allocated object on success.
+ *  - NULL on failure, with @c rte_errno set (see rte_fastmem_alloc()).
+ */
+__rte_experimental
+void *
+rte_fastmem_alloc_socket(size_t size, size_t align, unsigned int flags,
+		int socket_id)
+	__rte_alloc_size(1) __rte_alloc_align(2);
+
+/**
+ * Free an object previously allocated by the fastmem allocator.
+ *
+ * @p ptr must have been returned by a prior call to any fastmem
+ * allocation function, or be NULL. If @p ptr is NULL, no operation
+ * is performed.
+ *
+ * Free may be called from any lcore, regardless of which lcore
+ * made the original allocation.
+ *
+ * This function is MT-safe.
+ *
+ * @param ptr
+ *  Pointer to an object previously allocated by fastmem, or NULL.
+ */
+__rte_experimental
+void
+rte_fastmem_free(void *ptr);
+
+/**
+ * Allocate multiple objects in bulk.
+ *
+ * Allocates @p n objects, each of size at least @p size and aligned
+ * to at least @p align bytes, and stores the resulting pointers
+ * into @p ptrs. All @p n objects have the same size and alignment.
+ *
+ * On NUMA systems, the memory is allocated on the socket of the
+ * calling lcore. Use rte_fastmem_alloc_bulk_socket() to target a
+ * specific socket.
+ *
+ * The bulk path amortizes per-object overhead and is typically
+ * faster than @p n individual calls to rte_fastmem_alloc().
+ *
+ * On failure no objects are allocated and @p ptrs is left
+ * untouched.
+ *
+ * This function is MT-safe.
+ *
+ * @param ptrs
+ *  An array of at least @p n pointers into which the newly
+ *  allocated object pointers are written.
+ *
+ * @param n
+ *  The number of objects to allocate.
+ *
+ * @param size
+ *  Requested size of each object, in bytes. Must not exceed
+ *  rte_fastmem_max_size().
+ *
+ * @param align
+ *  If 0, returned pointers will be aligned to at least
+ *  @c RTE_CACHE_LINE_SIZE. Otherwise, returned pointers will be
+ *  aligned on a multiple of @p align, which must be a power of 2.
+ *
+ * @param flags
+ *  A bitwise OR of zero or more RTE_FASTMEM_F_* flags.
+ *
+ * @return
+ *  - 0: All @p n objects were allocated and stored in @p ptrs.
+ *  - -E2BIG: @p size exceeds rte_fastmem_max_size().
+ *  - -EINVAL: Invalid @p align.
+ *  - -ENOMEM: Not enough objects could be allocated to fill the
+ *    request.
+ */
+__rte_experimental
+int
+rte_fastmem_alloc_bulk(void **ptrs, unsigned int n, size_t size, size_t align,
+		unsigned int flags);
+
+/**
+ * Allocate multiple objects in bulk on a specific NUMA socket.
+ *
+ * Like rte_fastmem_alloc_bulk(), but targets the specified NUMA
+ * socket rather than the socket of the calling lcore.
+ *
+ * This function is MT-safe.
+ *
+ * @param ptrs
+ *  An array of at least @p n pointers into which the newly
+ *  allocated object pointers are written.
+ *
+ * @param n
+ *  The number of objects to allocate.
+ *
+ * @param size
+ *  Requested size of each object, in bytes. Must not exceed
+ *  rte_fastmem_max_size().
+ *
+ * @param align
+ *  If 0, returned pointers will be aligned to at least
+ *  @c RTE_CACHE_LINE_SIZE. Otherwise, returned pointers will be
+ *  aligned on a multiple of @p align, which must be a power of 2.
+ *
+ * @param flags
+ *  A bitwise OR of zero or more RTE_FASTMEM_F_* flags.
+ *
+ * @param socket_id
+ *  The NUMA socket on which to allocate, or SOCKET_ID_ANY to
+ *  leave the choice to the allocator. With SOCKET_ID_ANY, the
+ *  allocator starts on the calling lcore's socket (or the first
+ *  configured socket if the caller is not bound to one) and falls
+ *  back to other sockets if the preferred socket cannot satisfy
+ *  the request.
+ *
+ * @return
+ *  - 0: All @p n objects were allocated and stored in @p ptrs.
+ *  - Negative errno on failure (see rte_fastmem_alloc_bulk()).
+ */
+__rte_experimental
+int
+rte_fastmem_alloc_bulk_socket(void **ptrs, unsigned int n, size_t size,
+		size_t align, unsigned int flags, int socket_id);
+
+/**
+ * Free multiple objects in bulk.
+ *
+ * Frees the @p n objects pointed to by @p ptrs. Each pointer in
+ * the array must have been returned by a prior fastmem allocation
+ * call and must not have been freed. The objects need not have
+ * the same size, alignment, or socket.
+ *
+ * The bulk path amortizes per-object overhead and is typically
+ * faster than @p n individual calls to rte_fastmem_free().
+ *
+ * This function is MT-safe.
+ *
+ * @param ptrs
+ *  An array of @p n pointers to fastmem-allocated objects.
+ *
+ * @param n
+ *  The number of objects to free.
+ */
+__rte_experimental
+void
+rte_fastmem_free_bulk(void **ptrs, unsigned int n);
+
+/**
+ * Obtain the IOVA for a fastmem-allocated pointer.
+ *
+ * Translates a virtual address returned by a fastmem allocation
+ * function into the corresponding IOVA, suitable for use in device
+ * DMA descriptors.
+ *
+ * The returned IOVA is valid for the lifetime of the allocation.
+ *
+ * @p ptr must have been returned by a prior fastmem allocation
+ * function. Passing any other pointer results in undefined
+ * behavior.
+ *
+ * @param ptr
+ *  A pointer previously returned by a fastmem allocation
+ *  function.
+ *
+ * @return
+ *  The IOVA corresponding to @p ptr.
+ */
+__rte_experimental
+rte_iova_t
+rte_fastmem_virt2iova(const void *ptr);
+
+/**
+ * Flush the calling lcore's per-lcore caches.
+ *
+ * Drains every cached object from the calling lcore's
+ * per-(size class, NUMA socket) caches back to their shared
+ * bins, and releases the cache state itself. A subsequent
+ * allocation or free on this lcore lazily recreates any caches
+ * it needs.
+ *
+ * This is useful in applications that have finished a bursty
+ * phase and want to release memory that would otherwise sit idle
+ * in caches. It is also useful in tests that want to observe
+ * bin-level state without per-lcore caching hiding activity.
+ *
+ * The call has no effect when invoked from a non-EAL thread.
+ *
+ * This function is not thread-safe with respect to concurrent
+ * allocations or frees on the calling lcore; call it only when
+ * the calling lcore is not making other fastmem calls.
+ */
+__rte_experimental
+void
+rte_fastmem_cache_flush(void);
+
+/**
+ * Global summary statistics.
+ */
+struct rte_fastmem_stats {
+	uint64_t bytes_backing;  /**< Bytes of backing memory (memzones) reserved from EAL. */
+	uint64_t bytes_in_use;   /**< Approximate bytes in live objects. */
+	uint64_t alloc_total;    /**< Total successful alloc operations (hits + misses). */
+	uint64_t free_total;     /**< Total free operations (hits + misses). */
+	uint64_t alloc_nomem;    /**< Alloc attempts that failed with ENOMEM. */
+	unsigned int n_classes;  /**< Number of size classes. */
+};
+
+/**
+ * Per-size-class statistics (aggregated across all lcores).
+ *
+ * Allocation and free counters count individual objects, not
+ * operations. A bulk allocation of 32 objects that hits the cache
+ * increments alloc_cache_hits by 32.
+ */
+struct rte_fastmem_class_stats {
+	size_t class_size;             /**< Usable size of this class (bytes). */
+	uint64_t in_use;               /**< Objects currently live (allocs - frees). */
+	uint64_t alloc_cache_hits;     /**< Allocs served from a per-lcore cache. */
+	uint64_t alloc_cache_misses;   /**< Allocs that triggered a bin refill. */
+	uint64_t alloc_nomem;          /**< Alloc attempts that failed with ENOMEM. */
+	uint64_t free_cache_hits;      /**< Frees absorbed by a per-lcore cache. */
+	uint64_t free_cache_misses;    /**< Frees that triggered a bin drain. */
+	uint64_t slab_acquires;        /**< Slabs pulled from the free pool. */
+	uint64_t slab_releases;        /**< Slabs returned to the free pool. */
+	uint32_t slabs_partial;        /**< Current partial slab count. */
+	uint32_t slabs_full;           /**< Current full slab count. */
+};
+
+/**
+ * Per-lcore statistics (aggregated across all classes).
+ */
+struct rte_fastmem_lcore_stats {
+	uint64_t alloc_cache_hits;     /**< Allocs served from this lcore's caches. */
+	uint64_t alloc_cache_misses;   /**< Allocs that missed this lcore's caches. */
+	uint64_t alloc_nomem;          /**< Alloc attempts that failed with ENOMEM. */
+	uint64_t free_cache_hits;      /**< Frees absorbed by this lcore's caches. */
+	uint64_t free_cache_misses;    /**< Frees that bypassed this lcore's caches. */
+};
+
+/**
+ * Per-lcore, per-class statistics (no aggregation).
+ */
+struct rte_fastmem_lcore_class_stats {
+	size_t class_size;             /**< Usable size of this class (bytes). */
+	uint64_t alloc_cache_hits;     /**< Allocs served from cache. */
+	uint64_t alloc_cache_misses;   /**< Allocs that triggered a bin refill. */
+	uint64_t alloc_nomem;          /**< Alloc attempts that failed with ENOMEM. */
+	uint64_t free_cache_hits;      /**< Frees absorbed by cache. */
+	uint64_t free_cache_misses;    /**< Frees that triggered a bin drain. */
+};
+
+/**
+ * Get the number of size classes and optionally their sizes.
+ *
+ * @param[out] sizes
+ *   If non-NULL, filled with the size (in bytes) of each class.
+ *   The caller must provide space for at least the returned number
+ *   of entries.
+ *
+ * @return
+ *   The number of size classes.
+ */
+__rte_experimental
+unsigned int
+rte_fastmem_classes(size_t *sizes);
+
+/**
+ * Retrieve global summary statistics.
+ *
+ * @param[out] stats
+ *   Structure to fill.
+ *
+ * @return
+ *  - 0: Success.
+ *  - -EINVAL: @p stats is NULL or fastmem is not initialized.
+ */
+__rte_experimental
+int
+rte_fastmem_stats(struct rte_fastmem_stats *stats);
+
+/**
+ * Retrieve statistics for a single size class.
+ *
+ * @param class_size
+ *   Exact size of the class to query (must match one of the values
+ *   returned by rte_fastmem_classes()).
+ * @param[out] stats
+ *   Structure to fill.
+ *
+ * @return
+ *  - 0: Success.
+ *  - -EINVAL: @p stats is NULL, fastmem is not initialized, or
+ *    @p class_size does not match any size class.
+ */
+__rte_experimental
+int
+rte_fastmem_stats_class(size_t class_size,
+		struct rte_fastmem_class_stats *stats);
+
+/**
+ * Retrieve per-lcore statistics (aggregated across all classes).
+ *
+ * @param lcore_id
+ *   The lcore to query.
+ * @param[out] stats
+ *   Structure to fill.
+ *
+ * @return
+ *  - 0: Success.
+ *  - -EINVAL: @p stats is NULL, fastmem is not initialized, or
+ *    @p lcore_id is invalid.
+ */
+__rte_experimental
+int
+rte_fastmem_stats_lcore(unsigned int lcore_id,
+		struct rte_fastmem_lcore_stats *stats);
+
+/**
+ * Retrieve per-lcore, per-class statistics.
+ *
+ * @param lcore_id
+ *   The lcore to query.
+ * @param class_size
+ *   Exact size of the class to query.
+ * @param[out] stats
+ *   Structure to fill.
+ *
+ * @return
+ *  - 0: Success.
+ *  - -EINVAL: @p stats is NULL, fastmem is not initialized,
+ *    @p lcore_id is invalid, or @p class_size does not match any
+ *    size class.
+ */
+__rte_experimental
+int
+rte_fastmem_stats_lcore_class(unsigned int lcore_id, size_t class_size,
+		struct rte_fastmem_lcore_class_stats *stats);
+
+/**
+ * Reset all statistics counters to zero.
+ *
+ * Zeroes per-lcore cache counters and per-bin counters. Does not
+ * affect the allocator's operational state.
+ */
+__rte_experimental
+void
+rte_fastmem_stats_reset(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_FASTMEM_H_ */
diff --git a/lib/meson.build b/lib/meson.build
index 8f5cfd28a5..98ec28a901 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -40,6 +40,7 @@ libraries = [
         'efd',
         'eventdev',
         'dispatcher', # dispatcher depends on eventdev
+        'fastmem',
         'gpudev',
         'gro',
         'gso',
-- 
2.43.0


^ permalink raw reply related

* [RFC 1/3] doc: add fastmem programming guide
From: Mattias Rönnblom @ 2026-05-25 10:36 UTC (permalink / raw)
  To: dev
  Cc: Morten Brørup, Konstantin Ananyev, Mattias Rönnblom,
	Yogaraj Baskaravel, Mattias Rönnblom
In-Reply-To: <20260525103642.55255-1-hofors@lysator.liu.se>

From: Mattias Rönnblom <mattias.ronnblom@ericsson.com>

Add a programming guide for the fastmem library covering usage,
API overview, design, and implementation details.

Signed-off-by: Mattias Rönnblom <mattias.ronnblom@ericsson.com>
---
 doc/guides/prog_guide/fastmem_lib.rst | 301 ++++++++++++++++++++++++++
 doc/guides/prog_guide/index.rst       |   1 +
 2 files changed, 302 insertions(+)
 create mode 100644 doc/guides/prog_guide/fastmem_lib.rst

diff --git a/doc/guides/prog_guide/fastmem_lib.rst b/doc/guides/prog_guide/fastmem_lib.rst
new file mode 100644
index 0000000000..142408c3c2
--- /dev/null
+++ b/doc/guides/prog_guide/fastmem_lib.rst
@@ -0,0 +1,301 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Ericsson AB
+
+Fastmem Library
+===============
+
+The fastmem library is a fast, general-purpose small-object
+allocator for DPDK applications. It lets an application replace
+its many per-type mempools — each sized for a single object type
+— with a single allocator that handles arbitrary object sizes,
+grows on demand, and offers mempool-level performance for the
+common allocation and free paths.
+
+Like mempool, fastmem is backed by huge pages, is NUMA-aware,
+supports bulk operations, and uses per-lcore caches to reduce
+shared-state contention. Unlike mempool, it does not require the
+caller to declare object sizes or counts up front.
+
+
+When to use fastmem
+-------------------
+
+Use fastmem when:
+
+* Small objects (up to 1 MiB) are allocated and freed on the
+  data path with low, predictable latency requirements.
+
+* Many object types of varying sizes exist and maintaining a
+  separate mempool for each is impractical.
+
+* DMA-usable memory with efficient virtual-to-IOVA translation
+  is needed.
+
+Do not use fastmem for allocations larger than 1 MiB. Use
+``rte_malloc()`` instead.
+
+
+Initialization and teardown
+----------------------------
+
+.. code-block:: c
+
+   /* At startup, after rte_eal_init(). */
+   rte_fastmem_init();
+
+   /* Optional: pre-reserve backing memory to avoid latency
+    * spikes from on-demand memzone reservation. */
+   rte_fastmem_reserve(64 * 1024 * 1024, SOCKET_ID_ANY);
+
+   /* ... application runs ... */
+
+   /* At shutdown, after all allocations have been freed. */
+   rte_fastmem_deinit();
+
+Neither ``rte_fastmem_init()`` nor ``rte_fastmem_deinit()`` is
+thread-safe; call them from the main lcore during startup and
+shutdown.
+
+
+Allocation and free
+-------------------
+
+.. code-block:: c
+
+   void *obj = rte_fastmem_alloc(128, 0, 0);
+   /* Use obj... */
+   rte_fastmem_free(obj);
+
+``rte_fastmem_alloc()`` allocates on the calling lcore's NUMA
+socket. Use ``rte_fastmem_alloc_socket()`` to target a specific
+socket or to enable cross-socket fallback with ``SOCKET_ID_ANY``.
+
+Alignment
+~~~~~~~~~
+
+When ``align`` is 0, the returned pointer is aligned to at least
+``RTE_CACHE_LINE_SIZE``. A non-zero ``align`` must be a power of
+two. Specifying an alignment smaller than ``RTE_CACHE_LINE_SIZE``
+is permitted but the returned object may then share a cache line
+with an adjacent allocation, risking false sharing.
+
+Zeroing
+~~~~~~~
+
+Pass ``RTE_FASTMEM_F_ZERO`` to receive zero-initialized memory:
+
+.. code-block:: c
+
+   void *obj = rte_fastmem_alloc(256, 0, RTE_FASTMEM_F_ZERO);
+
+
+Bulk allocation and free
+-------------------------
+
+.. code-block:: c
+
+   void *ptrs[32];
+
+   if (rte_fastmem_alloc_bulk(ptrs, 32, 64, 0, 0) < 0)
+       /* handle error */;
+
+   /* Use objects... */
+
+   rte_fastmem_free_bulk(ptrs, 32);
+
+Bulk allocation has all-or-nothing semantics: either all
+requested objects are returned, or none are (and ``rte_errno``
+is set to ``ENOMEM``).
+
+Bulk free is most efficient when all objects belong to the same
+size class; in that case the objects are pushed into the
+per-lcore cache in a single operation.
+
+
+IOVA translation
+----------------
+
+Memory returned by fastmem is DMA-usable. To obtain the IOVA
+for use in device descriptors:
+
+.. code-block:: c
+
+   rte_iova_t iova = rte_fastmem_virt2iova(obj);
+
+The translation is O(1). The returned IOVA is valid for the
+lifetime of the allocation.
+
+
+NUMA awareness
+--------------
+
+``rte_fastmem_alloc()`` allocates on the calling lcore's socket.
+``rte_fastmem_alloc_socket()`` accepts an explicit socket ID or
+``SOCKET_ID_ANY``:
+
+* Explicit socket: allocate only from that socket; fail with
+  ``ENOMEM`` if exhausted.
+
+* ``SOCKET_ID_ANY``: try the caller's local socket first, then
+  fall back to other sockets.
+
+
+Per-lcore caches
+----------------
+
+Each EAL thread has a private cache per size class. The common
+allocation and free paths operate entirely within this cache,
+avoiding locks. Cache misses (empty on alloc, full on free)
+trigger a bulk transfer to/from the shared bin under a lock.
+
+Non-EAL threads bypass the cache and take the bin lock on every
+operation.
+
+``rte_fastmem_cache_flush()`` drains the calling lcore's caches
+back to the shared bins. This is useful after bursty phases to
+release idle cached memory.
+
+
+Threading
+---------
+
+All allocation and free functions are thread-safe and may be
+called from any thread. An allocation made on one thread may be
+freed on any other.
+
+Fastmem uses internal spinlocks. A thread preempted while
+holding one delays other threads contending for the same lock
+(correctness is not affected, only latency).
+
+
+Pre-reserving memory
+--------------------
+
+By default, fastmem reserves backing memory lazily on first
+allocation. ``rte_fastmem_reserve(size, socket_id)`` forces
+reservation up front, ensuring subsequent allocations do not
+incur memzone-reservation latency:
+
+.. code-block:: c
+
+   /* Reserve 128 MiB on socket 0. */
+   rte_fastmem_reserve(128 * 1024 * 1024, 0);
+
+Once reserved, backing memory is never returned to the system
+during the allocator's lifetime.
+
+Memory limits
+~~~~~~~~~~~~~
+
+``rte_fastmem_set_limit(socket_id, max_bytes)`` caps how much
+backing memory may be reserved on a given socket. Once the limit is
+reached, allocations that would require new backing memory fail with
+``ENOMEM``. The default is ``SIZE_MAX`` (unlimited).
+``rte_fastmem_get_limit()`` returns the current limit for a socket.
+
+.. code-block:: c
+
+   /* Allow at most 256 MiB on socket 0. */
+   rte_fastmem_set_limit(0, 256 * 1024 * 1024);
+
+   /* Block all growth on socket 1. */
+   rte_fastmem_set_limit(1, 0);
+
+Pass ``SOCKET_ID_ANY`` to apply the same limit to all sockets.
+
+
+Size classes
+------------
+
+Fastmem uses power-of-two size classes from 8 bytes to 1 MiB
+(18 classes). A request for N bytes is served from the smallest
+class >= N. The maximum supported size is queryable via
+``rte_fastmem_max_size()``.
+
+With power-of-two classes, worst-case internal fragmentation is
+just under 50% (e.g., a 33-byte request occupies a 64-byte
+slot). Assuming a uniform distribution of request sizes, the
+average waste is 25%. In practice, DPDK workloads tend to
+cluster at or near powers of two, so typical waste is lower.
+
+Requests exceeding the maximum are rejected with ``E2BIG``.
+
+
+Implementation
+--------------
+
+Fastmem organizes memory in three layers: backing memzones, slabs,
+and per-lcore caches.
+
+Backing memory and slabs
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Backing memory is obtained from EAL as 128 MiB IOVA-contiguous
+memzones, each aligned to 2 MiB. A memzone is partitioned into
+64 fixed-size, 2 MiB **slabs**. Slabs are the unit of memory
+that moves between size classes: a free slab can be assigned to
+any bin on demand, and an empty slab (all objects freed) returns
+to the free-slab pool for reuse by another size class.
+
+The 2 MiB slab alignment is the key structural property. Given
+any object pointer, the allocator recovers the owning slab by
+masking off the low 21 bits — no radix tree, hash table, or
+memzone lookup is needed. This makes the free path fast: a
+single pointer-mask load reaches the slab header, which
+identifies the size class and bin.
+
+Each slab reserves 64 bytes at offset 0 for its header. The
+remaining space is divided into fixed-size slots equal to the
+size class. Allocated objects carry no per-object metadata; the
+full slot is available to the caller.
+
+Three-level allocation hierarchy
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+1. **Per-lcore cache** — a bounded LIFO stack of free object
+   pointers, one per (lcore, size class, socket). Allocation
+   pops; free pushes. No lock is needed because only the owning
+   lcore accesses its cache.
+
+2. **Bin** — one per (size class, socket). Owns the partial and
+   full slab lists. A spinlock serializes bulk transfers between
+   the bin and per-lcore caches. Most traffic is absorbed by the
+   caches, so bin-lock contention is low.
+
+3. **Free-slab pool** — one per socket. A spinlock protects slab
+   acquisition and release. These events are rare relative to
+   object-level operations (a single small-object slab serves
+   thousands of allocations).
+
+On a cache miss (empty on alloc, full on free), the cache
+exchanges objects with the bin in bulk, targeting half-full to
+maximize headroom in both directions.
+
+Cache sizing
+~~~~~~~~~~~~
+
+Cache capacity varies by size class to bound per-lcore memory
+footprint:
+
+* Classes 8 B through 4 KiB: capacity 64.
+* Larger classes: capacity halves per class (32, 16, 8, 4),
+  flooring at 4.
+
+Even the largest classes remain cached. The capacity curve
+ensures that small, frequent allocations get the highest cache
+hit rate, while large allocations still avoid the bin lock on
+most operations.
+
+
+Statistics
+----------
+
+Fastmem maintains always-on, per-lcore counters that track
+allocation and free activity. Statistics are queryable at four
+levels of granularity: global summary, per size class, per lcore,
+and per lcore per class.
+
+``rte_fastmem_classes()`` returns the number of size classes and
+optionally fills an array with their sizes.
+
+See ``rte_fastmem.h`` for the full statistics API.
\ No newline at end of file
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index e6f24945b0..c85196c85e 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -28,6 +28,7 @@ Memory Management
     mempool_lib
     mbuf_lib
     multi_proc_support
+    fastmem_lib
 
 
 CPU Management
-- 
2.43.0


^ permalink raw reply related

* [RFC 0/3] lib/fastmem: fast small-object allocator
From: Mattias Rönnblom @ 2026-05-25 10:36 UTC (permalink / raw)
  To: dev
  Cc: Morten Brørup, Konstantin Ananyev, Mattias Rönnblom,
	Yogaraj Baskaravel, Mattias Rönnblom

This RFC introduces fastmem, a general-purpose small-object allocator
for DPDK. It is intended to replace per-type mempools with a single
allocator that handles arbitrary sizes, grows on demand, and matches
mempool-level performance on the hot path.

Motivation
----------

DPDK applications commonly maintain many mempools — one per object
type (connections, sessions, timers, work items). Each must be sized
up front, wastes memory when over-provisioned, and cannot serve
objects of a different size. Fastmem eliminates this by accepting
arbitrary sizes at runtime, backed by a slab allocator that
repurposes memory across size classes as demand shifts.

Design
------

Three-layer architecture:

1. Backing memory: 128 MiB IOVA-contiguous memzones from EAL,
   reserved lazily (or pre-reserved for deterministic latency).

2. Slabs: 2 MiB, 2 MiB-aligned regions carved from memzones.
   The alignment enables O(1) slab lookup from any object pointer
   via bitmask — no radix tree or index structure. Slabs move
   freely between 18 power-of-2 size classes (8 B to 1 MiB).

3. Per-lcore caches: bounded LIFO stacks (no locks on the hot
   path). Cache misses trigger bulk transfers to/from the shared
   bin under a spinlock.

Key properties:

- Zero per-object metadata in the production build.
- NUMA-aware, with per-socket bins and free-slab pools.
- DMA-usable memory with O(1) virt-to-IOVA translation.
- Bulk alloc/free with all-or-nothing semantics.
- Backing memory never returned during lifetime (slabs recycled).
- Non-EAL threads supported (bypass cache, take bin lock).

API surface
-----------

  rte_fastmem_init / deinit
  rte_fastmem_reserve
  rte_fastmem_set_limit / get_limit
  rte_fastmem_alloc / alloc_socket
  rte_fastmem_alloc_bulk / alloc_bulk_socket
  rte_fastmem_free / free_bulk
  rte_fastmem_virt2iova
  rte_fastmem_cache_flush
  rte_fastmem_max_size / classes
  rte_fastmem_stats / stats_class / stats_lcore / stats_lcore_class
  rte_fastmem_stats_reset

All APIs are marked __rte_experimental.

Performance
-----------

The single-object hot path is roughly 2-3x the cost of mempool
and an order of magnitude faster than rte_malloc. Under
multi-lcore contention, fastmem scales similarly to mempool,
while rte_malloc collapses.

Limitations
-----------

- Maximum allocation: 1 MiB. Larger requests should use rte_malloc.
- Power-of-2 classes only; worst-case internal fragmentation ~50%.
- Backing memory not reclaimable short of deinit.

Future work
-----------

- Lcore-affine allocations (false-sharing-free by construction).
- Mempool ops driver for transparent drop-in use.
- Pre-resolved allocator handle binding size class and socket,
  eliminating per-call class lookup and enabling an inline
  cache-hit fast path.
- Debug mode (cookies, double-free detection, poison-on-free).
- Telemetry integration.
- EAL integration, allowing EAL-internal subsystems to use
  fastmem for their small-object allocations.

Mattias Rönnblom (3):
  doc: add fastmem programming guide
  lib: add fastmem library
  app/test: add fastmem test suite

 app/test/meson.build                  |    3 +
 app/test/test_fastmem.c               | 1682 +++++++++++++++++++++++++
 app/test/test_fastmem_perf.c          |  997 +++++++++++++++
 app/test/test_fastmem_profile.c       |  157 +++
 doc/api/doxy-api-index.md             |    1 +
 doc/api/doxy-api.conf.in              |    1 +
 doc/guides/prog_guide/fastmem_lib.rst |  301 +++++
 doc/guides/prog_guide/index.rst       |    1 +
 lib/fastmem/meson.build               |    6 +
 lib/fastmem/rte_fastmem.c             | 1486 ++++++++++++++++++++++
 lib/fastmem/rte_fastmem.h             |  644 ++++++++++
 lib/meson.build                       |    1 +
 12 files changed, 5280 insertions(+)
 create mode 100644 app/test/test_fastmem.c
 create mode 100644 app/test/test_fastmem_perf.c
 create mode 100644 app/test/test_fastmem_profile.c
 create mode 100644 doc/guides/prog_guide/fastmem_lib.rst
 create mode 100644 lib/fastmem/meson.build
 create mode 100644 lib/fastmem/rte_fastmem.c
 create mode 100644 lib/fastmem/rte_fastmem.h

-- 
2.43.0


^ permalink raw reply

* [PATCH v2] net/mlx5: use port index as representor index
From: Dariusz Sosnowski @ 2026-05-25  9:17 UTC (permalink / raw)
  To: Viacheslav Ovsiienko, Bing Zhao, Ori Kam, Suanming Mou,
	Matan Azrad
  Cc: dev, stable
In-Reply-To: <20260522101933.540423-1-dsosnowski@nvidia.com>

Since the offending commit, mlx5 driver supports probing
representors on BlueField DPUs with Socket Direct (SD).
Such card can be connected to 2 different CPUs on the host system.
On DPU, user would see the following network devices:

- p0 and p1 - physical ports
- pf0hpf and pf2hpf - PF0 on CPU 0 and CPU 1 respectively
- pf1hpf and pf3hpf - PF1 on CPU 0 and CPU 1 respectively

mlx5 driver finds the relevant netdev by matching information
provided in representor devarg to phys_port_name
reported by Linux kernel.
For the above interfaces phys_port_name's would be reported
and probed as:

- p0 -> p0, no need for representor devarg
- p1 -> p1, with representor=pf1
- pf0hpf -> c1pf0, with representor=c1pf0vf65535
- pf1hpf -> c1pf1, with representor=c1pf1vf65535
- pf2hpf -> c2pf0, with representor=c2pf0vf65535
- pf3hpf -> c2pf1, with representor=c2pf1vf65535

Although hot-plugging all these representors is successful,
RTE_ETH_FOREACH_MATCHING_DEV() macro would not find DPDK ports.
This is caused missing information reported by mlx5 driver,
through rte_eth_representor_info_get() API.
Specifically, mlx5 driver did not report controller index for all
representor ranges.

Until now mlx5 driver used static encoding for 16-bit representor_id:

- 2 bits for representor type
- 2 bits for PF index
- 12 bits for representor index (either VF or SF number)

Controller index was not encoded. This caused the mentioned issue
and on top of that:

- limits the number of PFs
- limits the number of SFs

This patch changes the mlx5 driver logic for
rte_eth_representor_info_get().
Instead of static encoding:

- representor_id's will be dynamically assigned
  to each probed representor.
- rte_eth_representor_info_get() will report N ranges:
    - N == number of probed ports on single embedded switch
    - Each range will define single representor_id
      for given controller/PF/VF/SF.

Fixes: 2f7cdd821b1b ("net/mlx5: fix probing to allow BlueField Socket Direct")
Cc: stable@dpdk.org

Signed-off-by: Dariusz Sosnowski <dsosnowski@nvidia.com>
Acked-by: Bing Zhao <bingz@nvidia.com>
---
v2:
- Added missing "not" in "RTE_ETH_FOREACH_MATCHING_DEV() macro would not find DPDK ports"
  in the commit message.
- Fixed typo in number of bits for representor index.
  Should be 12, not 2.

 drivers/net/mlx5/linux/mlx5_os.c |   6 +-
 drivers/net/mlx5/mlx5.h          |  19 +++
 drivers/net/mlx5/mlx5_ethdev.c   | 284 +++++++++++++++++++------------
 3 files changed, 199 insertions(+), 110 deletions(-)

diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
index 0fc721592b..5305523c1b 100644
--- a/drivers/net/mlx5/linux/mlx5_os.c
+++ b/drivers/net/mlx5/linux/mlx5_os.c
@@ -1677,9 +1677,13 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 		err = ENOMEM;
 		goto error;
 	}
+	priv->port_info.type = spawn->info.name_type;
+	priv->port_info.ctrl_num = spawn->info.ctrl_num;
+	priv->port_info.pf_num = spawn->info.pf_num;
+	priv->port_info.port_num = spawn->info.port_name;
 	if (priv->representor) {
 		eth_dev->data->dev_flags |= RTE_ETH_DEV_REPRESENTOR;
-		eth_dev->data->representor_id = priv->representor_id;
+		eth_dev->data->representor_id = eth_dev->data->port_id;
 		MLX5_ETH_FOREACH_DEV(port_id, dpdk_dev) {
 			struct mlx5_priv *opriv =
 				rte_eth_devices[port_id].data->dev_private;
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 49a0c03544..23803b450b 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -1984,6 +1984,24 @@ struct mlx5_quota_ctx {
 	struct mlx5_indexed_pool *quota_ipool; /* Manage quota objects */
 };

+/* Stores info parsed from phys_port_name related to given DPDK port. */
+struct mlx5_representor_info {
+	enum mlx5_nl_phys_port_name_type type;
+	/* PCI controller index. 0 if no controller was reported in phys_port_name. */
+	int32_t ctrl_num;
+	/* PF index. */
+	int32_t pf_num;
+	/*
+	 * Representor number:
+	 *
+	 * - For VF/SF - VF/SF index.
+	 * - For PFHPF - -1.
+	 * - For uplink - physical port index.
+	 * - For others - VF representor is assumed, so VF index.
+	 */
+	int32_t port_num;
+};
+
 struct mlx5_nta_sample_ctx;
 struct mlx5_priv {
 	struct rte_eth_dev_data *dev_data;  /* Pointer to device data. */
@@ -2019,6 +2037,7 @@ struct mlx5_priv {
 	uint32_t vport_meta_tag; /* Used for vport index match ove VF LAG. */
 	uint32_t vport_meta_mask; /* Used for vport index field match mask. */
 	uint16_t representor_id; /* UINT16_MAX if not a representor. */
+	struct mlx5_representor_info port_info;
 	int32_t pf_bond; /* >=0, representor owner PF index in bonding. */
 	int32_t mpesw_owner; /* >=0, representor owner PF index in MPESW. */
 	int32_t mpesw_port; /* Related port index of MPESW device. < 0 - no MPESW. */
diff --git a/drivers/net/mlx5/mlx5_ethdev.c b/drivers/net/mlx5/mlx5_ethdev.c
index a29cdeeb50..e14b7f148b 100644
--- a/drivers/net/mlx5/mlx5_ethdev.c
+++ b/drivers/net/mlx5/mlx5_ethdev.c
@@ -345,6 +345,23 @@ mlx5_dev_get_max_wq_size(struct mlx5_dev_ctx_shared *sh)
 	return max_wqe;
 }

+/**
+ * Get switch port ID for given DPDK port.
+ *
+ * @param dev
+ *   Pointer to Ethernet device structure.
+ * @return
+ *   Switch port ID reported through rte_eth_dev_info_get().
+ */
+static uint16_t
+mlx5_dev_switch_info_port_id_get(struct rte_eth_dev *dev)
+{
+	if (rte_eth_dev_is_repr(dev))
+		return dev->data->port_id;
+
+	return UINT16_MAX;
+}
+
 /**
  * DPDK callback to get information about the device.
  *
@@ -401,7 +418,7 @@ mlx5_dev_infos_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *info)
 		info->dev_capa |= RTE_ETH_DEV_CAPA_RXQ_SHARE;
 	info->switch_info.name = dev->data->name;
 	info->switch_info.domain_id = priv->domain_id;
-	info->switch_info.port_id = priv->representor_id;
+	info->switch_info.port_id = mlx5_dev_switch_info_port_id_get(dev);
 	info->switch_info.rx_domain = 0; /* No sub Rx domains. */
 	if (priv->representor) {
 		uint16_t port_id;
@@ -472,14 +489,162 @@ mlx5_representor_id_encode(const struct mlx5_switch_info *info,
 	return MLX5_REPRESENTOR_ID(pf, type, repr);
 }

+static unsigned int
+mlx5_representor_info_count_one(struct mlx5_priv *priv)
+{
+	switch (priv->port_info.type) {
+	case MLX5_PHYS_PORT_NAME_TYPE_PFHPF:
+		return 2;
+	case MLX5_PHYS_PORT_NAME_TYPE_UPLINK:
+		/* Only representor uplinks should be reported */
+		if (!priv->representor)
+			return 0;
+		return 1;
+	case MLX5_PHYS_PORT_NAME_TYPE_NOTSET:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_LEGACY:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_PFVF:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_PFSF:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_UNKNOWN:
+		/* FALLTHROUGH */
+	default:
+		return 1;
+	}
+}
+
+static unsigned int
+mlx5_representor_info_count(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	uint16_t port_id;
+	unsigned int count = 0;
+
+	MLX5_ETH_FOREACH_DEV(port_id, dev->device) {
+		struct mlx5_priv *opriv = rte_eth_devices[port_id].data->dev_private;
+
+		if (!opriv ||
+		    opriv->sh != priv->sh ||
+		    opriv->domain_id != priv->domain_id)
+			continue;
+
+		count += mlx5_representor_info_count_one(opriv);
+	}
+
+	return count;
+}
+
+static void
+mlx5_representor_info_fill_one(struct mlx5_priv *priv,
+			       struct rte_eth_representor_info *info)
+{
+	struct rte_eth_representor_range *range;
+	unsigned int count;
+
+	count = mlx5_representor_info_count_one(priv);
+	if (count == 0)
+		return;
+
+	if (info->nb_ranges + count > info->nb_ranges_alloc) {
+		DRV_LOG(ERR, "port %u representor info already full", priv->dev_data->port_id);
+		return;
+	}
+
+	range = &info->ranges[info->nb_ranges];
+
+	switch (priv->port_info.type) {
+	case MLX5_PHYS_PORT_NAME_TYPE_UPLINK:
+		range->type = RTE_ETH_REPRESENTOR_PF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.port_num;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%d", range->pf);
+		break;
+	case MLX5_PHYS_PORT_NAME_TYPE_PFSF:
+		/* Secondly, fill in SF variant. */
+		range->type = RTE_ETH_REPRESENTOR_SF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->sf = priv->port_info.port_num;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dsf", range->pf);
+		break;
+	case MLX5_PHYS_PORT_NAME_TYPE_PFHPF:
+		/*
+		 * Host PF can be probed either through VF(0xffff) or SF(0xffff).
+		 * Firstly fill in VF variant.
+		 */
+		range->type = RTE_ETH_REPRESENTOR_VF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->vf = UINT16_MAX;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dvf", range->pf);
+
+		/* Move the SF variant. */
+		range++;
+
+		/* Fill in SF variant. */
+		range->type = RTE_ETH_REPRESENTOR_SF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->sf = UINT16_MAX;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dsf", range->pf);
+		break;
+	case MLX5_PHYS_PORT_NAME_TYPE_PFVF:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_NOTSET:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_LEGACY:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_UNKNOWN:
+		range->type = RTE_ETH_REPRESENTOR_VF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->vf = priv->port_info.port_num;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dvf", range->pf);
+		break;
+	}
+
+	info->nb_ranges += count;
+}
+
+static unsigned int
+mlx5_representor_info_fill(struct rte_eth_dev *dev,
+			   struct rte_eth_representor_info *info)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	uint16_t port_id;
+
+	info->controller = priv->port_info.ctrl_num;
+	info->pf = RTE_DEV_TO_PCI(dev->device)->addr.function;
+
+	MLX5_ETH_FOREACH_DEV(port_id, dev->device) {
+		struct mlx5_priv *opriv = rte_eth_devices[port_id].data->dev_private;
+
+		if (!opriv ||
+		    opriv->sh != priv->sh ||
+		    opriv->domain_id != priv->domain_id)
+			continue;
+
+		mlx5_representor_info_fill_one(opriv, info);
+	}
+
+	return info->nb_ranges;
+}
+
 /**
  * DPDK callback to get information about representor.
  *
- * Representor ID bits definition:
- *   vf/sf: 12
- *   type: 2
- *   pf: 2
- *
  * @param dev
  *   Pointer to Ethernet device structure.
  * @param[out] info
@@ -492,110 +657,11 @@ int
 mlx5_representor_info_get(struct rte_eth_dev *dev,
 			  struct rte_eth_representor_info *info)
 {
-	struct mlx5_priv *priv = dev->data->dev_private;
-	/* Representor types: PF, VF, HPF@VF, SF and HPF@SF, total 5. */
-	int n_type = RTE_ETH_REPRESENTOR_PF + 2; /* Maximal type + 2 for HPFs. */
-	int n_pf = 8; /* Maximal number of PFs. */
-	int i = 0, pf;
-	int n_entries;
-
 	if (info == NULL)
-		goto out;
-
-	n_entries = n_type * n_pf;
-	if ((uint32_t)n_entries > info->nb_ranges_alloc)
-		n_entries = info->nb_ranges_alloc;
-
-	info->controller = 0;
-	info->pf = 0;
-	if (mlx5_is_port_on_mpesw_device(priv)) {
-		info->pf = priv->mpesw_port;
-		for (i = 0; i < n_pf; i++) {
-			/* PF range, both ports will show the same information. */
-			info->ranges[i].type = RTE_ETH_REPRESENTOR_PF;
-			info->ranges[i].controller = 0;
-			info->ranges[i].pf = priv->mpesw_owner + i + 1;
-			info->ranges[i].vf = 0;
-			/*
-			 * The representor indexes should be the values set of "priv->mpesw_port".
-			 * In the real case now, only 1 PF/UPLINK representor is supported.
-			 * The port index will always be the value of "owner + 1".
-			 */
-			info->ranges[i].id_base =
-				MLX5_REPRESENTOR_ID(priv->mpesw_owner,
-						    info->ranges[i].type,
-						    info->ranges[i].pf);
-			info->ranges[i].id_end =
-				MLX5_REPRESENTOR_ID(priv->mpesw_owner,
-						    info->ranges[i].type,
-						    info->ranges[i].pf);
-			snprintf(info->ranges[i].name,
-				 sizeof(info->ranges[i].name),
-				 "pf%d", info->ranges[i].pf);
-		}
-	} else if (priv->pf_bond >= 0)
-		info->pf = priv->pf_bond;
-	for (pf = 0; pf < n_pf; ++pf) {
-		/* VF range. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_VF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = 0;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, 0);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dvf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-		/* HPF range of VF type. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_VF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = UINT16_MAX;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dvf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-		/* SF range. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_SF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = 0;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, 0);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dsf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-		/* HPF range of SF type. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_SF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = UINT16_MAX;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dsf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-	}
-	info->nb_ranges = i;
-out:
-	return n_type * n_pf;
+		return mlx5_representor_info_count(dev);
+
+	return mlx5_representor_info_fill(dev, info);
+
 }

 /**
--
2.47.3


^ permalink raw reply related

* RE: [PATCH v3 03/27] ring: use compare-and-swap wrapper
From: Konstantin Ananyev @ 2026-05-25  7:41 UTC (permalink / raw)
  To: Stephen Hemminger, dev@dpdk.org
In-Reply-To: <20260523195604.441947-4-stephen@networkplumber.org>


Hi Stephen,

> The rte_atomic32_cmpset is deprecated. Initial attempts at
> changing this with direct conversion to
> rte_atomic_compare_exchange_weak_explicit()
> regressed MP/MC contended performance on x86 by 10-30%,
> because the C11 builtin's failure-writeback semantic forces
> GCC to emit extra instructions on the CAS critical path.
> 
> Add an internal __rte_ring_compare_and_swap() wrapper that calls
> __sync_bool_compare_and_swap() directly, which keeps the original
> instruction sequence. Add equivalent function for MSVC.

In fact, in rte_ring we do have 2 implementations of the same core functions:
lib/ring/rte_ring_c11_pvt.h  - uses C11 atomics
lib/ring/rte_ring_generic_pvt.h - uses legacy instructions (smp_mb, extra), 
If we going remove these legacy instructions anyway (or reimplementing them using C11 atomics),
then there is probably no point to keep rte_ring_generic_pvt.h.
Konstantin

> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  lib/ring/rte_ring_generic_pvt.h | 32 ++++++++++++++++++++++++++++----
>  1 file changed, 28 insertions(+), 4 deletions(-)
> 
> diff --git a/lib/ring/rte_ring_generic_pvt.h b/lib/ring/rte_ring_generic_pvt.h
> index affd2d5ba7..0fb972de9e 100644
> --- a/lib/ring/rte_ring_generic_pvt.h
> +++ b/lib/ring/rte_ring_generic_pvt.h
> @@ -18,6 +18,30 @@
>   * For more information please refer to <rte_ring.h>.
>   */
> 
> +/**
> + * @internal optimized version of compare exchange
> + *
> + * The C11 builtin's failure-writeback semantic generates worse code on x86.
> + * Unlike rte_atomic_compare_exchange_*_explicit(), this wrapper does NOT
> + * write the actual value back to a pointer on failure. Callers in a retry
> + * loop must reload the expected value explicitly on the next iteration.
> + *
> + * Full memory barrier, equivalent to rte_memory_order_seq_cst on both
> + * success and failure.
> + */
> +static __rte_always_inline bool
> +__rte_ring_compare_and_swap(volatile uint32_t *dst,
> +			    uint32_t expected, uint32_t desired)
> +{
> +#if defined(RTE_TOOLCHAIN_MSVC)
> +	return _InterlockedCompareExchange((volatile long *)dst,
> +					   (long)desired, (long)expected)
> +		== (long)expected;
> +#else
> +	return __sync_bool_compare_and_swap(dst, expected, desired);
> +#endif
> +}
> +
>  /**
>   * @internal This function updates tail values.
>   */
> @@ -108,10 +132,10 @@ __rte_ring_headtail_move_head(struct
> rte_ring_headtail *d,
>  		if (is_st) {
>  			d->head = *new_head;
>  			success = 1;
> -		} else
> -			success = rte_atomic32_cmpset(
> -					(uint32_t *)(uintptr_t)&d->head,
> -					*old_head, *new_head);
> +		} else {
> +			success = __rte_ring_compare_and_swap(
> +					&d->head, *old_head, *new_head);
> +		}
>  	} while (unlikely(success == 0));
>  	return n;
>  }
> --
> 2.53.0


^ permalink raw reply

* 回复: [V1 1/1] net/hinic3: Add VXLAN TSO function
From: wangfeifei (J) @ 2026-05-25  3:30 UTC (permalink / raw)
  To: Stephen Hemminger, Feifei Wang
  Cc: dev@dpdk.org, chenyi (CY), huangbingkang,
	zengweiliang zengweiliang, Wangxin(kunpeng)
In-Reply-To: <20260520080649.67af0f2c@phoenix.local>



> -----邮件原件-----
> 发件人: Stephen Hemminger <stephen@networkplumber.org>
> 发送时间: 2026年5月20日 23:07
> 收件人: Feifei Wang <wff_light@vip.163.com>
> 抄送: dev@dpdk.org; wangfeifei (J) <wangfeifei40@huawei.com>
> 主题: Re: [V1 1/1] net/hinic3: Add VXLAN TSO function
> 
> On Wed, 20 May 2026 14:58:15 +0800
> Feifei Wang <wff_light@vip.163.com> wrote:
> 
> > The RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO flag is added to support the
> > VXLAN TSO function
> >
> > Signed-off-by: Feifei Wang <wangfeifei40@huawei.com>
> > ---
> 
> You need to do more than just advertise the capability to get the driver to
> actually work right.
> 
> AI explanation:
> 
> Error: VXLAN TSO is advertised unconditionally but the underlying hardware
> feature is not. The patch adds RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO to the
> unconditional tx_offload_capa assignment in hinic3_dev_infos_get(), but the Tx
> path in drivers/net/hinic3/hinic3_tx.c:306-313 rejects every VXLAN tunnel mbuf
> when the hardware does not have NIC_F_VXLAN_OFFLOAD:
[Feifei] Thanks for your reviewing. Above comment is valuable, we ignore this.
In the next version, we will change as below:

if (HINIC3_SUPPORT_VXLAN_OFFLOAD(nic_dev))
	info->tx_offload_capa |= RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO

^ permalink raw reply

* [PATCH v1 01/23] net/sxe2: support AVX512 vectorized path for Rx and Tx
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

Add AVX512 vector data path for Rx and Tx burst functions.
The decision to use AVX512 is based on:
1. CPU hardware flags (AVX512F, AVX512BW).
2. Compiler support (CC_AVX512_SUPPORT).
3. Max SIMD bitwidth configuration.

Performance shows approximately X% improvement in small packet
forwarding scenarios.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build            |  24 +
 drivers/net/sxe2/sxe2_ethdev.c          |   2 +-
 drivers/net/sxe2/sxe2_txrx.c            |  91 ++-
 drivers/net/sxe2/sxe2_txrx_vec.c        |  46 +-
 drivers/net/sxe2/sxe2_txrx_vec.h        |  18 +-
 drivers/net/sxe2/sxe2_txrx_vec_avx512.c | 897 ++++++++++++++++++++++++
 6 files changed, 1063 insertions(+), 15 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_txrx_vec_avx512.c

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index 3df57aee8c..30f2c7d816 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -15,6 +15,30 @@ includes += include_directories('../../common/sxe2')
 
 if arch_subdir == 'x86'
         sources += files('sxe2_txrx_vec_sse.c')
+
+        sxe2_avx512_cpu_support =(
+                cc.get_define('__AVX512F__', args: machine_args) != '' and
+                cc.get_define('__AVX512BW__', args: machine_args) != '')
+
+        sxe2_avx512_cc_support = (
+                not machine_args.contains('-mno-avx512f') and
+                cc.has_argument('-mavx512f') and
+                cc.has_argument('-mavx512bw'))
+
+        if sxe2_avx512_cpu_support == true or sxe2_avx512_cc_support == true
+                cflags += ['-DCC_AVX512_SUPPORT']
+                avx512_args = [cflags, '-mavx512f', '-mavx512bw']
+                if cc.has_argument('-march=skylake-avx512')
+                        avx512_args += '-march=skylake-avx512'
+                endif
+                sxe2_avx512_lib = static_library('sxe2_avx512_lib', 'sxe2_txrx_vec_avx512.c',
+                        dependencies: [static_rte_ethdev,
+                        static_rte_kvargs, static_rte_hash,
+                        static_rte_security, static_rte_cryptodev, static_rte_bus_pci],
+                        include_directories: includes,
+                        c_args: avx512_args)
+                objs += sxe2_avx512_lib.extract_objects('sxe2_txrx_vec_avx512.c')
+        endif
 endif
 
 sources += files(
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 8d66e5d8c5..e0f7002138 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -891,7 +891,7 @@ static int32_t sxe2_eth_pmd_probe_pf(struct sxe2_common_device *cdev,
 static int32_t sxe2_parse_eth_devargs(struct rte_device *dev,
 			  struct rte_eth_devargs *eth_da)
 {
-	int ret = 0;
+	int32_t ret = 0;
 
 	if (dev->devargs == NULL)
 		return 0;
diff --git a/drivers/net/sxe2/sxe2_txrx.c b/drivers/net/sxe2/sxe2_txrx.c
index 8bd5f2eca4..ee70a2a431 100644
--- a/drivers/net/sxe2/sxe2_txrx.c
+++ b/drivers/net/sxe2/sxe2_txrx.c
@@ -158,6 +158,19 @@ void sxe2_tx_mode_func_set(struct rte_eth_dev *dev)
 		if (ret == 0 &&
 		    rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_128) {
 			tx_mode_flags = vec_flags;
+#ifdef RTE_ARCH_X86
+			if ((rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_512) &&
+			    (rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512F) == 1) &&
+			    (rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512BW) == 1)) {
+#ifdef CC_AVX512_SUPPORT
+				tx_mode_flags |= SXE2_TX_MODE_VEC_AVX512;
+#else
+				PMD_LOG_INFO(TX, "AVX512 is not supported in build env.");
+#endif
+			}
+			if ((tx_mode_flags & SXE2_TX_MODE_VEC_SET_MASK) == 0)
+				tx_mode_flags |= SXE2_TX_MODE_VEC_SSE;
+#endif
 			if (tx_mode_flags & SXE2_TX_MODE_VEC_SET_MASK) {
 				ret = sxe2_tx_queues_vec_prepare(dev);
 				if (ret != 0)
@@ -173,7 +186,6 @@ void sxe2_tx_mode_func_set(struct rte_eth_dev *dev)
 		tx_mode_flags = adapter->q_ctxt.tx_mode_flags;
 	}
 
-#ifdef RTE_ARCH_X86
 	if (tx_mode_flags & SXE2_TX_MODE_VEC_SET_MASK) {
 		if (tx_mode_flags & SXE2_TX_MODE_VEC_OFFLOAD) {
 			dev->tx_pkt_prepare = sxe2_tx_pkts_prepare;
@@ -182,6 +194,27 @@ void sxe2_tx_mode_func_set(struct rte_eth_dev *dev)
 			dev->tx_pkt_prepare = NULL;
 			dev->tx_pkt_burst = sxe2_tx_pkts_vec_sse_simple;
 		}
+	}
+	if (tx_mode_flags & SXE2_TX_MODE_VEC_SET_MASK) {
+		dev->tx_pkt_prepare = NULL;
+#ifdef RTE_ARCH_X86
+		if (tx_mode_flags & SXE2_TX_MODE_VEC_AVX512) {
+#ifdef CC_AVX512_SUPPORT
+			if (tx_mode_flags & SXE2_TX_MODE_VEC_OFFLOAD) {
+				dev->tx_pkt_prepare = sxe2_tx_pkts_prepare;
+				dev->tx_pkt_burst = sxe2_tx_pkts_vec_avx512;
+			} else {
+				dev->tx_pkt_burst = sxe2_tx_pkts_vec_avx512_simple;
+			}
+#endif
+		} else {
+			if (tx_mode_flags & SXE2_TX_MODE_VEC_OFFLOAD) {
+				dev->tx_pkt_prepare = sxe2_tx_pkts_prepare;
+				dev->tx_pkt_burst = sxe2_tx_pkts_vec_sse;
+			} else {
+				dev->tx_pkt_burst = sxe2_tx_pkts_vec_sse_simple;
+			}
+		}
 	} else {
 #endif
 		if (tx_mode_flags & SXE2_TX_MODE_SIMPLE_BATCH) {
@@ -202,8 +235,16 @@ static const struct {
 } sxe2_tx_burst_infos[] = {
 	{ sxe2_tx_pkts,   "Scalar" },
 #ifdef RTE_ARCH_X86
-	{ sxe2_tx_pkts_vec_sse,        "Vector SSE" },
-	{ sxe2_tx_pkts_vec_sse_simple, "Vector SSE Simple" },
+#ifdef CC_AVX512_SUPPORT
+	{ sxe2_tx_pkts_vec_avx512,
+	      "Vector AVX512" },
+	{ sxe2_tx_pkts_vec_avx512_simple,
+	      "Vector AVX512 Simple" },
+#endif
+	{ sxe2_tx_pkts_vec_sse,
+	      "Vector SSE" },
+	{ sxe2_tx_pkts_vec_sse_simple,
+	      "Vector SSE Simple" },
 #endif
 };
 
@@ -289,6 +330,20 @@ void sxe2_rx_mode_func_set(struct rte_eth_dev *dev)
 		if (ret == 0 &&
 		    rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_128) {
 			rx_mode_flags = vec_flags;
+#ifdef RTE_ARCH_X86
+			if ((rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_512) &&
+				(rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512F) == 1) &&
+				(rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX512BW) == 1)) {
+#ifdef CC_AVX512_SUPPORT
+				rx_mode_flags |= SXE2_RX_MODE_VEC_AVX512;
+#else
+				PMD_LOG_INFO(RX, "AVX512 support detected but not enabled");
+#endif
+			}
+			if ((rx_mode_flags & SXE2_RX_MODE_VEC_SET_MASK) == 0 &&
+				rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_128)
+				rx_mode_flags |= SXE2_RX_MODE_VEC_SSE;
+#endif
 			if ((rx_mode_flags & SXE2_RX_MODE_VEC_SET_MASK) != 0) {
 				ret = sxe2_rx_queues_vec_prepare(dev);
 				if (ret != 0)
@@ -302,7 +357,16 @@ void sxe2_rx_mode_func_set(struct rte_eth_dev *dev)
 
 #ifdef RTE_ARCH_X86
 	if (rx_mode_flags & SXE2_RX_MODE_VEC_SET_MASK) {
-		dev->rx_pkt_burst = sxe2_rx_pkts_scattered_vec_sse_offload;
+		if (rx_mode_flags & SXE2_RX_MODE_VEC_AVX512) {
+#ifdef CC_AVX512_SUPPORT
+			if (rx_mode_flags & SXE2_RX_MODE_VEC_OFFLOAD)
+				dev->rx_pkt_burst = sxe2_rx_pkts_scattered_vec_avx512_offload;
+			else
+				dev->rx_pkt_burst = sxe2_rx_pkts_scattered_vec_avx512;
+#endif
+		} else {
+			dev->rx_pkt_burst = sxe2_rx_pkts_scattered_vec_sse_offload;
+		}
 		return;
 	}
 #endif
@@ -316,19 +380,30 @@ static const struct {
 	eth_rx_burst_t rx_burst;
 	const char *info;
 } sxe2_rx_burst_infos[] = {
-	{ sxe2_rx_pkts_scattered,          "Scalar Scattered" },
-	{ sxe2_rx_pkts_scattered_split,          "Scalar Scattered split" },
+	{ sxe2_rx_pkts_scattered,
+	      "Scalar Scattered" },
+	{ sxe2_rx_pkts_scattered_split,
+	      "Scalar Scattered split" },
 #ifdef RTE_ARCH_X86
-	{ sxe2_rx_pkts_scattered_vec_sse_offload,      "Vector SSE Scattered" },
+#ifdef CC_AVX512_SUPPORT
+	{ sxe2_rx_pkts_scattered_vec_avx512,
+	      "Vector AVX512 Scattered" },
+	{ sxe2_rx_pkts_scattered_vec_avx512_offload,
+	      "Offload Vector AVX512 Scattered" },
+#endif
+	{ sxe2_rx_pkts_scattered_vec_sse_offload,
+	      "Vector SSE Scattered" },
 #endif
 };
 
 int32_t sxe2_rx_burst_mode_get(struct rte_eth_dev *dev,
-			__rte_unused uint16_t queue_id, struct rte_eth_burst_mode *mode)
+			       __rte_unused uint16_t queue_id,
+			       struct rte_eth_burst_mode *mode)
 {
 	eth_rx_burst_t pkt_burst = dev->rx_pkt_burst;
 	int32_t ret = -EINVAL;
 	uint32_t i, size;
+
 	size = RTE_DIM(sxe2_rx_burst_infos);
 	for (i = 0; i < size; ++i) {
 		if (pkt_burst == sxe2_rx_burst_infos[i].rx_burst) {
diff --git a/drivers/net/sxe2/sxe2_txrx_vec.c b/drivers/net/sxe2/sxe2_txrx_vec.c
index 8df4954d86..cf004f5eb2 100644
--- a/drivers/net/sxe2/sxe2_txrx_vec.c
+++ b/drivers/net/sxe2/sxe2_txrx_vec.c
@@ -165,16 +165,54 @@ static void sxe2_tx_queue_mbufs_release_vec(struct sxe2_tx_queue *txq)
 		return;
 	}
 	i = txq->next_dd - (txq->rs_thresh - 1);
-	buffer = txq->buffer_ring;
-	if (txq->next_use < i) {
-		for ( ; i < txq->ring_depth; ++i) {
+#ifdef CC_AVX512_SUPPORT
+	struct rte_eth_dev *dev;
+	struct sxe2_tx_buffer_vec *buffer_vec;
+
+	dev = &rte_eth_devices[txq->port_id];
+
+	if (dev->tx_pkt_burst == sxe2_tx_pkts_vec_avx512 ||
+		dev->tx_pkt_burst == sxe2_tx_pkts_vec_avx512_simple) {
+		buffer_vec = (struct sxe2_tx_buffer_vec *)txq->buffer_ring;
+
+		if (txq->next_use < i) {
+			for ( ; i < txq->ring_depth; ++i) {
+				if (buffer_vec[i].mbuf != NULL) {
+					rte_pktmbuf_free_seg(buffer_vec[i].mbuf);
+					buffer_vec[i].mbuf = NULL;
+				}
+			}
+			i = 0;
+		}
+		for ( ; i < txq->next_use; ++i) {
+			if (buffer_vec[i].mbuf != NULL) {
+				rte_pktmbuf_free_seg(buffer_vec[i].mbuf);
+				buffer_vec[i].mbuf = NULL;
+			}
+		}
+	} else {
+#endif
+		buffer = txq->buffer_ring;
+		buffer = txq->buffer_ring;
+		if (txq->next_use < i) {
+			for ( ; i < txq->ring_depth; ++i) {
+				if (buffer[i].mbuf != NULL) {
+					rte_pktmbuf_free_seg(buffer[i].mbuf);
+					buffer[i].mbuf = NULL;
+				}
+			}
+			i = 0;
+		}
+		for (; i < txq->next_use; ++i) {
 			if (buffer[i].mbuf != NULL) {
 				rte_pktmbuf_free_seg(buffer[i].mbuf);
 				buffer[i].mbuf = NULL;
 			}
 		}
-		i = 0;
+#ifdef CC_AVX512_SUPPORT
 	}
+#endif
+
 	for (; i < txq->next_use; ++i) {
 		if (buffer[i].mbuf != NULL) {
 			rte_pktmbuf_free_seg(buffer[i].mbuf);
diff --git a/drivers/net/sxe2/sxe2_txrx_vec.h b/drivers/net/sxe2/sxe2_txrx_vec.h
index 4aef93d140..62a5b1f3f5 100644
--- a/drivers/net/sxe2/sxe2_txrx_vec.h
+++ b/drivers/net/sxe2/sxe2_txrx_vec.h
@@ -10,15 +10,19 @@
 #define SXE2_RX_MODE_VEC_SIMPLE    RTE_BIT32(0)
 #define SXE2_RX_MODE_VEC_OFFLOAD   RTE_BIT32(1)
 #define SXE2_RX_MODE_VEC_SSE       RTE_BIT32(2)
+#define SXE2_RX_MODE_VEC_AVX512    RTE_BIT32(4)
 #define SXE2_RX_MODE_BATCH_ALLOC   RTE_BIT32(10)
 #define SXE2_RX_MODE_VEC_SET_MASK	(SXE2_RX_MODE_VEC_SIMPLE | \
-			SXE2_RX_MODE_VEC_OFFLOAD | SXE2_RX_MODE_VEC_SSE)
+			SXE2_RX_MODE_VEC_OFFLOAD | SXE2_RX_MODE_VEC_SSE | \
+			SXE2_RX_MODE_VEC_AVX512)
 #define SXE2_TX_MODE_VEC_SIMPLE   RTE_BIT32(0)
 #define SXE2_TX_MODE_VEC_OFFLOAD  RTE_BIT32(1)
 #define SXE2_TX_MODE_VEC_SSE      RTE_BIT32(2)
+#define SXE2_TX_MODE_VEC_AVX512   RTE_BIT32(4)
 #define SXE2_TX_MODE_SIMPLE_BATCH RTE_BIT32(10)
 #define SXE2_TX_MODE_VEC_SET_MASK	(SXE2_TX_MODE_VEC_SIMPLE | \
-			SXE2_TX_MODE_VEC_OFFLOAD | SXE2_TX_MODE_VEC_SSE)
+			SXE2_TX_MODE_VEC_OFFLOAD | SXE2_TX_MODE_VEC_SSE | \
+			SXE2_TX_MODE_VEC_AVX512)
 #define SXE2_TX_VEC_NO_SUPPORT_OFFLOAD (		  \
 			RTE_ETH_TX_OFFLOAD_MULTI_SEGS |		  \
 			RTE_ETH_TX_OFFLOAD_QINQ_INSERT |	  \
@@ -53,6 +57,16 @@ uint16_t sxe2_tx_pkts_vec_sse(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_
 uint16_t sxe2_tx_pkts_vec_sse_simple(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 uint16_t sxe2_rx_pkts_scattered_vec_sse_offload(void *rx_queue,
 		struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
+uint16_t sxe2_tx_pkts_vec_avx512_simple(void *tx_queue,
+		struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
+uint16_t sxe2_tx_pkts_vec_avx512(void *tx_queue,
+		struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
+uint16_t sxe2_tx_pkts_vec_avx512_ctx_offload(void *tx_queue,
+		struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
+uint16_t sxe2_rx_pkts_scattered_vec_avx512(void *rx_queue,
+		struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
+uint16_t sxe2_rx_pkts_scattered_vec_avx512_offload(void *rx_queue,
+		struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
 #endif
 int32_t __rte_cold sxe2_tx_vec_support_check(struct rte_eth_dev *dev, uint32_t *vec_flags);
 int32_t __rte_cold sxe2_tx_queues_vec_prepare(struct rte_eth_dev *dev);
diff --git a/drivers/net/sxe2/sxe2_txrx_vec_avx512.c b/drivers/net/sxe2/sxe2_txrx_vec_avx512.c
new file mode 100644
index 0000000000..2aec8037dd
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_txrx_vec_avx512.c
@@ -0,0 +1,897 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef SXE2_TEST
+#include <rte_vect.h>
+
+#include "sxe2_ethdev.h"
+#include "sxe2_common_log.h"
+#include "sxe2_queue.h"
+#include "sxe2_txrx_vec.h"
+#include "sxe2_txrx_vec_common.h"
+#include "sxe2_vsi.h"
+
+static __rte_always_inline int32_t sxe2_tx_bufs_free_vec_avx512(struct sxe2_tx_queue *txq)
+{
+	struct sxe2_tx_buffer_vec *buffer;
+	struct rte_mbuf *mbuf;
+	struct rte_mbuf *mbuf_free_arr[SXE2_TX_FREE_BUFFER_SIZE_MAX_VEC];
+	struct rte_mempool *mp;
+	struct rte_mempool_cache *cache;
+	void **cache_objs;
+	uint32_t copied;
+	uint32_t i;
+	int32_t ret;
+	uint16_t rs_thresh;
+	uint16_t free_num;
+
+	if (rte_cpu_to_le_64(SXE2_TX_DESC_DTYPE_DESC_DONE) !=
+		(txq->desc_ring[txq->next_dd].wb.dd &
+			rte_cpu_to_le_64(SXE2_TX_DESC_DTYPE_MASK))) {
+		ret = 0;
+		goto l_end;
+	}
+
+	rs_thresh = txq->rs_thresh;
+
+	buffer = (struct sxe2_tx_buffer_vec *)txq->buffer_ring;
+	buffer += txq->next_dd - (rs_thresh - 1);
+
+	if ((txq->offloads & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) &&
+			(rs_thresh & 31) == 0) {
+		mp = buffer[0].mbuf->pool;
+		cache = rte_mempool_default_cache(mp, rte_lcore_id());
+
+		if (cache == NULL || cache->len)
+			goto normal;
+
+		if (rs_thresh > RTE_MEMPOOL_CACHE_MAX_SIZE) {
+			(void)rte_mempool_ops_enqueue_bulk(mp, (void *)buffer, rs_thresh);
+			goto done;
+		}
+		cache_objs = &cache->objs[cache->len];
+
+		copied = 0;
+		while (copied < rs_thresh) {
+			const __m512i objs0 = _mm512_loadu_si512(&buffer[copied]);
+			const __m512i objs1 = _mm512_loadu_si512(&buffer[copied + 8]);
+			const __m512i objs2 = _mm512_loadu_si512(&buffer[copied + 16]);
+			const __m512i objs3 = _mm512_loadu_si512(&buffer[copied + 24]);
+
+			_mm512_storeu_si512(&cache_objs[copied], objs0);
+			_mm512_storeu_si512(&cache_objs[copied + 8], objs1);
+			_mm512_storeu_si512(&cache_objs[copied + 16], objs2);
+			_mm512_storeu_si512(&cache_objs[copied + 24], objs3);
+			copied += 32;
+		}
+		cache->len += rs_thresh;
+
+		if (cache->len >= cache->flushthresh) {
+			(void)rte_mempool_ops_enqueue_bulk(mp,
+					&cache->objs[cache->size], cache->len - cache->size);
+			cache->len = cache->size;
+		}
+		goto done;
+	}
+
+normal:
+	mbuf = rte_pktmbuf_prefree_seg(buffer[0].mbuf);
+
+	if (likely(mbuf)) {
+		mbuf_free_arr[0] = mbuf;
+		free_num = 1;
+
+		for (i = 1; i < rs_thresh; ++i) {
+			mbuf = rte_pktmbuf_prefree_seg(buffer[i].mbuf);
+
+			if (likely(mbuf)) {
+				if (likely(mbuf->pool == mbuf_free_arr[0]->pool)) {
+					mbuf_free_arr[free_num] = mbuf;
+					free_num++;
+				} else {
+					rte_mempool_put_bulk(mbuf_free_arr[0]->pool,
+						(void *)mbuf_free_arr, free_num);
+
+				mbuf_free_arr[0] = mbuf;
+				free_num = 1;
+			}
+			}
+		}
+
+		rte_mempool_put_bulk(mbuf_free_arr[0]->pool,
+						(void *)mbuf_free_arr, free_num);
+	} else {
+		for (i = 1; i < rs_thresh; ++i) {
+			mbuf = rte_pktmbuf_prefree_seg(buffer[i].mbuf);
+			if (mbuf != NULL)
+				rte_mempool_put(mbuf->pool, mbuf);
+		}
+	}
+
+done:
+	txq->desc_free_num += txq->rs_thresh;
+	txq->next_dd       += txq->rs_thresh;
+	if (txq->next_dd >= txq->ring_depth)
+		txq->next_dd = txq->rs_thresh - 1;
+	ret = rs_thresh;
+
+l_end:
+	return ret;
+}
+
+static __rte_always_inline void
+sxe2_tx_desc_fill_one_avx512(volatile union sxe2_tx_data_desc *desc, struct rte_mbuf *pkt,
+	uint64_t desc_cmd, bool with_offloads)
+{
+	__m128i data_desc;
+	uint64_t desc_qw1;
+	uint32_t desc_offset;
+
+	desc_qw1 = (SXE2_TX_DESC_DTYPE_DATA |
+				((uint64_t)desc_cmd) << SXE2_TX_DATA_DESC_CMD_SHIFT |
+				((uint64_t)pkt->data_len) << SXE2_TX_DATA_DESC_BUF_SZ_SHIFT);
+	desc_offset = SXE2_TX_DATA_DESC_MACLEN_VAL(pkt->l2_len);
+	desc_qw1 |= ((uint64_t)desc_offset) << SXE2_TX_DATA_DESC_OFFSET_SHIFT;
+	if (with_offloads)
+		sxe2_tx_desc_fill_offloads(pkt, &desc_qw1);
+
+	data_desc = _mm_set_epi64x(desc_qw1, rte_pktmbuf_iova(pkt));
+
+	_mm_store_si128(RTE_CAST_PTR(__m128i *, desc), data_desc);
+}
+
+static __rte_always_inline
+void sxe2_tx_desc_fill_avx512(volatile union sxe2_tx_data_desc *desc, struct rte_mbuf **pkts,
+	uint16_t pkts_num, uint64_t desc_cmd, bool with_offloads)
+{
+	__m512i desc_group;
+	uint64_t desc0_qw1;
+	uint64_t desc1_qw1;
+	uint64_t desc2_qw1;
+	uint64_t desc3_qw1;
+
+	const uint64_t desc_qw1_com = (SXE2_TX_DESC_DTYPE_DATA |
+					((uint64_t)desc_cmd) << SXE2_TX_DATA_DESC_CMD_SHIFT);
+	uint32_t desc_offset[4] = {0};
+
+	while (pkts_num > 3) {
+		desc3_qw1 = desc_qw1_com |
+				((uint64_t)pkts[3]->data_len) << SXE2_TX_DATA_DESC_BUF_SZ_SHIFT;
+
+		desc_offset[3] = SXE2_TX_DATA_DESC_MACLEN_VAL(pkts[3]->l2_len);
+		desc3_qw1 |= ((uint64_t)desc_offset[3]) << SXE2_TX_DATA_DESC_OFFSET_SHIFT;
+		if (with_offloads)
+			sxe2_tx_desc_fill_offloads(pkts[3], &desc3_qw1);
+
+		desc2_qw1 = desc_qw1_com |
+				((uint64_t)pkts[2]->data_len) << SXE2_TX_DATA_DESC_BUF_SZ_SHIFT;
+		desc_offset[2] = SXE2_TX_DATA_DESC_MACLEN_VAL(pkts[2]->l2_len);
+		desc2_qw1 |= ((uint64_t)desc_offset[2]) << SXE2_TX_DATA_DESC_OFFSET_SHIFT;
+		if (with_offloads)
+			sxe2_tx_desc_fill_offloads(pkts[2], &desc2_qw1);
+
+		desc1_qw1 = (desc_qw1_com |
+				((uint64_t)pkts[1]->data_len) << SXE2_TX_DATA_DESC_BUF_SZ_SHIFT);
+		desc_offset[1] = SXE2_TX_DATA_DESC_MACLEN_VAL(pkts[1]->l2_len);
+		desc1_qw1 |= ((uint64_t)desc_offset[1]) << SXE2_TX_DATA_DESC_OFFSET_SHIFT;
+		if (with_offloads)
+			sxe2_tx_desc_fill_offloads(pkts[1], &desc1_qw1);
+
+		desc0_qw1 = (desc_qw1_com |
+				((uint64_t)pkts[0]->data_len) << SXE2_TX_DATA_DESC_BUF_SZ_SHIFT);
+		desc_offset[0] = SXE2_TX_DATA_DESC_MACLEN_VAL(pkts[0]->l2_len);
+		desc0_qw1 |= ((uint64_t)desc_offset[0]) << SXE2_TX_DATA_DESC_OFFSET_SHIFT;
+		if (with_offloads)
+			sxe2_tx_desc_fill_offloads(pkts[0], &desc0_qw1);
+
+		desc_group =
+			_mm512_set_epi64(desc3_qw1, rte_pktmbuf_iova(pkts[3]),
+					 desc2_qw1, rte_pktmbuf_iova(pkts[2]),
+					 desc1_qw1, rte_pktmbuf_iova(pkts[1]),
+					 desc0_qw1, rte_pktmbuf_iova(pkts[0]));
+
+		_mm512_storeu_si512(RTE_CAST_PTR(void *, desc), desc_group);
+
+		pkts_num -= 4;
+		desc     += 4;
+		pkts     += 4;
+	}
+
+	while (pkts_num) {
+		sxe2_tx_desc_fill_one_avx512(desc, *pkts, desc_cmd, with_offloads);
+
+		pkts_num--;
+		desc++;
+		pkts++;
+	}
+}
+
+static __rte_always_inline void
+sxe2_tx_pkts_mbuf_fill_avx512(struct sxe2_tx_buffer_vec *buffer,
+	struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; ++i)
+		buffer[i].mbuf = tx_pkts[i];
+}
+
+static __rte_always_inline uint16_t
+sxe2_tx_pkts_vec_avx512_batch(struct sxe2_tx_queue *txq, struct rte_mbuf **tx_pkts,
+	uint16_t nb_pkts, bool with_offloads)
+{
+	volatile union sxe2_tx_data_desc *desc;
+	struct sxe2_tx_buffer_vec *buffer;
+	uint16_t next_use;
+	uint16_t res_num;
+	uint16_t tx_num;
+
+	if (txq->desc_free_num < txq->free_thresh)
+		(void)sxe2_tx_bufs_free_vec_avx512(txq);
+
+	nb_pkts = RTE_MIN(txq->desc_free_num, nb_pkts);
+	if (unlikely(nb_pkts == 0)) {
+		PMD_LOG_DEBUG(TX, "Tx pkts avx512 batch: may not enough free desc, "
+				"free_desc=%u, need_tx_pkts=%u",
+				txq->desc_free_num, nb_pkts);
+		goto l_end;
+	}
+	tx_num = nb_pkts;
+
+	next_use = txq->next_use;
+	desc     = &txq->desc_ring[next_use];
+	buffer   = (struct sxe2_tx_buffer_vec *)txq->buffer_ring;
+	buffer  += next_use;
+
+	txq->desc_free_num -= nb_pkts;
+
+	res_num = txq->ring_depth - txq->next_use;
+
+	if (tx_num >= res_num) {
+		sxe2_tx_pkts_mbuf_fill_avx512(buffer, tx_pkts, res_num);
+
+		sxe2_tx_desc_fill_avx512(desc, tx_pkts, res_num,
+					SXE2_TX_DATA_DESC_CMD_EOP, with_offloads);
+		tx_pkts += (res_num - 1);
+		desc    += (res_num - 1);
+
+		sxe2_tx_desc_fill_one_avx512(desc, *tx_pkts++,
+					(SXE2_TX_DATA_DESC_CMD_EOP | SXE2_TX_DATA_DESC_CMD_RS),
+					with_offloads);
+
+		tx_num -= res_num;
+
+		next_use     = 0;
+		txq->next_rs = txq->rs_thresh - 1;
+		desc         = txq->desc_ring;
+		buffer       = (struct sxe2_tx_buffer_vec *)txq->buffer_ring;
+	}
+
+	sxe2_tx_pkts_mbuf_fill_avx512(buffer, tx_pkts, tx_num);
+
+	sxe2_tx_desc_fill_avx512(desc, tx_pkts, tx_num,
+			SXE2_TX_DATA_DESC_CMD_EOP, with_offloads);
+
+	next_use += tx_num;
+	if (next_use > txq->next_rs) {
+		txq->desc_ring[txq->next_rs].read.type_cmd_off_bsz_l2t |=
+			rte_cpu_to_le_64(SXE2_TX_DATA_DESC_CMD_RS_MASK);
+
+		txq->next_rs += txq->rs_thresh;
+	}
+	txq->next_use = next_use;
+
+	SXE2_PCI_REG_WRITE_WC(txq->tdt_reg_addr, next_use);
+	PMD_LOG_DEBUG(TX, "port_id=%u queue_id=%u next_use=%u send_pkts=%u",
+			txq->port_id, txq->queue_id, next_use, nb_pkts);
+l_end:
+	return nb_pkts;
+}
+
+static __rte_always_inline uint16_t
+sxe2_tx_pkts_vec_avx512_common(struct sxe2_tx_queue *txq, struct rte_mbuf **tx_pkts,
+	uint16_t nb_pkts, bool with_offloads)
+{
+	uint16_t tx_done_num = 0;
+	uint16_t tx_once_num;
+	uint16_t tx_need_num;
+
+	while (nb_pkts) {
+		tx_need_num = RTE_MIN(nb_pkts, txq->rs_thresh);
+		tx_once_num =
+			sxe2_tx_pkts_vec_avx512_batch(txq, tx_pkts + tx_done_num,
+					     tx_need_num, with_offloads);
+		nb_pkts     -= tx_once_num;
+		tx_done_num += tx_once_num;
+		if (tx_once_num < tx_need_num)
+			break;
+	}
+
+	return tx_done_num;
+}
+
+uint16_t sxe2_tx_pkts_vec_avx512_simple(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	return sxe2_tx_pkts_vec_avx512_common((struct sxe2_tx_queue *)tx_queue,
+					      tx_pkts, nb_pkts, false);
+}
+
+uint16_t sxe2_tx_pkts_vec_avx512(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	return sxe2_tx_pkts_vec_avx512_common((struct sxe2_tx_queue *)tx_queue,
+					      tx_pkts, nb_pkts, true);
+}
+
+static inline void sxe2_rx_queue_rearm_avx512(struct sxe2_rx_queue *rxq)
+{
+	volatile union sxe2_rx_desc *desc;
+	struct rte_mbuf **buffer;
+	struct rte_mbuf *mbuf0, *mbuf1;
+	__m128i dma_addr0, dma_addr1;
+	__m128i virt_addr0, virt_addr1;
+	__m128i hdr_room = _mm_set_epi64x(RTE_PKTMBUF_HEADROOM, RTE_PKTMBUF_HEADROOM);
+	int32_t ret;
+	uint16_t i;
+	uint16_t new_tail;
+
+	buffer = &rxq->buffer_ring[rxq->realloc_start];
+	desc   = &rxq->desc_ring[rxq->realloc_start];
+
+	ret = rte_mempool_get_bulk(rxq->mb_pool, (void *)buffer, SXE2_RX_REARM_THRESH_VEC);
+	if (ret != 0) {
+		if ((rxq->realloc_num + SXE2_RX_REARM_THRESH_VEC) >= rxq->ring_depth) {
+			dma_addr0 = _mm_setzero_si128();
+			for (i = 0; i < SXE2_RX_NUM_PER_LOOP_AVX; ++i) {
+				buffer[i] = &rxq->fake_mbuf;
+				_mm_store_si128(RTE_CAST_PTR(__m128i *, &desc[i].read),
+						dma_addr0);
+			}
+		}
+
+		rxq->vsi->adapter->dev_info.dev_data->rx_mbuf_alloc_failed +=
+			SXE2_RX_REARM_THRESH_VEC;
+		goto l_end;
+	}
+
+	for (i = 0; i < SXE2_RX_REARM_THRESH_VEC; i += 2, buffer += 2) {
+		mbuf0 = buffer[0];
+		mbuf1 = buffer[1];
+
+#if RTE_IOVA_IN_MBUF
+
+		RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, buf_iova) !=
+				 offsetof(struct rte_mbuf, buf_addr) + 8);
+#endif
+		virt_addr0 = _mm_loadu_si128((__m128i *)&mbuf0->buf_addr);
+		virt_addr1 = _mm_loadu_si128((__m128i *)&mbuf1->buf_addr);
+
+#if RTE_IOVA_IN_MBUF
+
+		dma_addr0 = _mm_unpackhi_epi64(virt_addr0, virt_addr0);
+		dma_addr1 = _mm_unpackhi_epi64(virt_addr1, virt_addr1);
+#else
+
+		dma_addr0 = _mm_unpacklo_epi64(virt_addr0, virt_addr0);
+		dma_addr1 = _mm_unpacklo_epi64(virt_addr1, virt_addr1);
+#endif
+
+		dma_addr0 = _mm_add_epi64(dma_addr0, hdr_room);
+		dma_addr1 = _mm_add_epi64(dma_addr1, hdr_room);
+
+		_mm_store_si128(RTE_CAST_PTR(__m128i *, &desc++->read), dma_addr0);
+		_mm_store_si128(RTE_CAST_PTR(__m128i *, &desc++->read), dma_addr1);
+	}
+
+	rxq->realloc_start += SXE2_RX_REARM_THRESH_VEC;
+	if (rxq->realloc_start >= rxq->ring_depth)
+		rxq->realloc_start = 0;
+	rxq->realloc_num -= SXE2_RX_REARM_THRESH_VEC;
+
+	new_tail = (rxq->realloc_start == 0) ? (rxq->ring_depth - 1) :
+		(rxq->realloc_start - 1);
+
+	SXE2_PCI_REG_WRITE_WC(rxq->rdt_reg_addr, new_tail);
+
+l_end:
+	return;
+}
+
+static __rte_always_inline uint16_t
+sxe2_rx_pkts_common_vec_avx512(struct sxe2_rx_queue *rxq, struct rte_mbuf **rx_pkts,
+	uint16_t nb_pkts, uint8_t *split_rxe_flags,
+	uint8_t *umbcast_flags, bool do_offload)
+{
+	const uint32_t *ptype_tbl = rxq->vsi->adapter->ptype_tbl;
+	const __m256i mbuf_init = _mm256_set_epi64x(0, 0, 0, rxq->mbuf_init_value);
+	struct rte_mbuf **buffer;
+	volatile union sxe2_rx_desc *desc;
+	__m512i mbufs4_7;
+	__m512i mbufs0_3;
+	__m256i mbufs6_7;
+	__m256i mbufs4_5;
+	__m256i mbufs2_3;
+	__m256i mbufs0_1;
+	uint32_t bit_num  = 0;
+	uint16_t done_num = 0;
+	uint16_t i = 0;
+	uint16_t j = 0;
+
+	buffer   = &rxq->buffer_ring[rxq->processing_idx];
+	desc     = &rxq->desc_ring[rxq->processing_idx];
+
+	rte_prefetch0(desc);
+
+	nb_pkts = RTE_ALIGN_FLOOR(nb_pkts, SXE2_RX_NUM_PER_LOOP_AVX);
+
+	if (rxq->realloc_num > SXE2_RX_REARM_THRESH_VEC)
+		sxe2_rx_queue_rearm_avx512(rxq);
+
+	if (0 == (rte_le_to_cpu_64(desc->wb.status_err_ptype_len) & SXE2_RX_DESC_STATUS_DD_MASK))
+		goto l_end;
+
+	const __m512i crc_adjust =
+			_mm512_set4_epi32(0, -rxq->crc_len, -rxq->crc_len, 0);
+
+	const __m256i dd_mask = _mm256_set1_epi32(1);
+
+	const __m512i rvp_shuf_mask =
+			_mm512_set4_epi32((7 << 24) | (6 << 16) | (5 << 8) | 4,
+					  (3 << 24) | (2 << 16) | (13 << 8) | 12,
+					  (0xFFU << 24) | (0xFF << 16) | (13 << 8) | 12,
+					  0xFFFFFFFF);
+
+	const __m128i eop_shuf_mask =
+		_mm_set_epi8(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+			     0xFF, 0xFF, 8, 0, 10, 2, 12, 4, 14, 6);
+
+	RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, pkt_len) !=
+			offsetof(struct rte_mbuf, rx_descriptor_fields1) + 4);
+	RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, data_len) !=
+			offsetof(struct rte_mbuf, rx_descriptor_fields1) + 8);
+	RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, vlan_tci) !=
+			offsetof(struct rte_mbuf, rx_descriptor_fields1) + 10);
+	RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, hash) !=
+			offsetof(struct rte_mbuf, rx_descriptor_fields1) + 12);
+
+	for (i = 0; i < nb_pkts; i += SXE2_RX_NUM_PER_LOOP_AVX,
+				desc += SXE2_RX_NUM_PER_LOOP_AVX) {
+		_mm256_storeu_si256((void *)&rx_pkts[i],
+			_mm256_loadu_si256((void *)&buffer[i]));
+#ifdef RTE_ARCH_X86_64
+		_mm256_storeu_si256((void *)&rx_pkts[i + 4],
+			_mm256_loadu_si256((void *)&buffer[i + 4]));
+#endif
+
+		const __m128i desc7 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 7));
+		rte_compiler_barrier();
+		const __m128i desc6 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 6));
+		rte_compiler_barrier();
+		const __m128i desc5 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 5));
+		rte_compiler_barrier();
+		const __m128i desc4 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 4));
+		rte_compiler_barrier();
+		const __m128i desc3 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 3));
+		rte_compiler_barrier();
+		const __m128i desc2 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 2));
+		rte_compiler_barrier();
+		const __m128i desc1 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 1));
+		rte_compiler_barrier();
+		const __m128i desc0 = _mm_loadu_si128(RTE_CAST_PTR(const __m128i *, desc + 0));
+
+		const __m256i descs6_7 =
+			_mm256_inserti128_si256(_mm256_castsi128_si256(desc6), desc7, 1);
+		const __m256i descs4_5 =
+			_mm256_inserti128_si256(_mm256_castsi128_si256(desc4), desc5, 1);
+		const __m256i descs2_3 =
+			_mm256_inserti128_si256(_mm256_castsi128_si256(desc2), desc3, 1);
+		const __m256i descs0_1 =
+			_mm256_inserti128_si256(_mm256_castsi128_si256(desc0), desc1, 1);
+
+		const __m512i descs4_7 =
+			_mm512_inserti64x4(_mm512_castsi256_si512(descs4_5), descs6_7, 1);
+		const __m512i descs0_3 =
+			_mm512_inserti64x4(_mm512_castsi256_si512(descs0_1), descs2_3, 1);
+
+		if (split_rxe_flags != NULL) {
+			for (j = 0; j < SXE2_RX_NUM_PER_LOOP_AVX; j++)
+				rte_mbuf_prefetch_part2(rx_pkts[i + j]);
+		}
+
+		mbufs4_7 = _mm512_shuffle_epi8(descs4_7, rvp_shuf_mask);
+		mbufs0_3 = _mm512_shuffle_epi8(descs0_3, rvp_shuf_mask);
+
+		mbufs4_7 = _mm512_add_epi32(mbufs4_7, crc_adjust);
+		mbufs0_3 = _mm512_add_epi32(mbufs0_3, crc_adjust);
+
+		const __m512i ptype_mask = _mm512_set1_epi64(SXE2_RX_FLEX_DESC_PTYPE_M <<
+					SXE2_RX_FLEX_DESC_PTYPE_S);
+
+		__m512i ptypes4_7 = _mm512_and_si512(descs4_7, ptype_mask);
+		__m512i ptypes0_3 = _mm512_and_si512(descs0_3, ptype_mask);
+
+		const __m256i ptypes6_7 = _mm512_extracti64x4_epi64(ptypes4_7, 1);
+		const __m256i ptypes4_5 = _mm512_extracti64x4_epi64(ptypes4_7, 0);
+		const __m256i ptypes2_3 = _mm512_extracti64x4_epi64(ptypes0_3, 1);
+		const __m256i ptypes0_1 = _mm512_extracti64x4_epi64(ptypes0_3, 0);
+
+		const uint16_t ptype7 = _mm256_extract_epi16(ptypes6_7, 13);
+		const uint16_t ptype6 = _mm256_extract_epi16(ptypes6_7, 5);
+		const uint16_t ptype5 = _mm256_extract_epi16(ptypes4_5, 13);
+		const uint16_t ptype4 = _mm256_extract_epi16(ptypes4_5, 5);
+		const uint16_t ptype3 = _mm256_extract_epi16(ptypes2_3, 13);
+		const uint16_t ptype2 = _mm256_extract_epi16(ptypes2_3, 5);
+		const uint16_t ptype1 = _mm256_extract_epi16(ptypes0_1, 13);
+		const uint16_t ptype0 = _mm256_extract_epi16(ptypes0_1, 5);
+
+		const __m512i ptype_mask4_7 =
+				_mm512_set_epi32(0, 0, 0, ptype_tbl[ptype7],
+						 0, 0, 0, ptype_tbl[ptype6],
+						 0, 0, 0, ptype_tbl[ptype5],
+						 0, 0, 0, ptype_tbl[ptype4]);
+		const __m512i ptype_mask0_3 =
+				_mm512_set_epi32(0, 0, 0, ptype_tbl[ptype3],
+						 0, 0, 0, ptype_tbl[ptype2],
+						 0, 0, 0, ptype_tbl[ptype1],
+						 0, 0, 0, ptype_tbl[ptype0]);
+
+		mbufs4_7 = _mm512_or_si512(mbufs4_7, ptype_mask4_7);
+		mbufs0_3 = _mm512_or_si512(mbufs0_3, ptype_mask0_3);
+
+		mbufs6_7 = _mm512_extracti64x4_epi64(mbufs4_7, 1);
+		mbufs4_5 = _mm512_extracti64x4_epi64(mbufs4_7, 0);
+		mbufs2_3 = _mm512_extracti64x4_epi64(mbufs0_3, 1);
+		mbufs0_1 = _mm512_extracti64x4_epi64(mbufs0_3, 0);
+
+		const __m512i staterr_per_mask =
+			_mm512_set_epi32(0x17, 0x1F, 0x07, 0x0F,
+					 0x13, 0x1B, 0x03, 0x0B,
+					 0x16, 0x1E, 0x06, 0x0E,
+					 0x12, 0x1A, 0x02, 0x0A);
+		__m512i qw1_0_7 = _mm512_permutex2var_epi32(descs4_7,
+							    staterr_per_mask,
+							    descs0_3);
+
+		__m256i staterrs0_7 = _mm512_extracti64x4_epi64(qw1_0_7, 0);
+
+		__m256i stu_len0_7 = _mm512_extracti64x4_epi64(qw1_0_7, 1);
+		__m256i mbuf_flags = _mm256_setzero_si256();
+
+		if (do_offload) {
+			const __m256i desc_flags_mask = _mm256_set1_epi32(0xC0001C04);
+			const __m256i desc_flags_rss_mask = _mm256_set1_epi32(0x20000000);
+			const __m256i vlan_flags =
+				_mm256_set_epi8(0, 0, 0, 0,
+					0, 0, 0, 0,
+					0, 0, 0, RTE_MBUF_F_RX_VLAN |
+						RTE_MBUF_F_RX_VLAN_STRIPPED,
+					0, 0, 0, 0,
+					0, 0, 0, 0,
+					0, 0, 0, 0,
+					0, 0, 0, RTE_MBUF_F_RX_VLAN |
+						RTE_MBUF_F_RX_VLAN_STRIPPED,
+					0, 0, 0, 0);
+
+			const __m256i rss_flags =
+				_mm256_set_epi8(0, 0, 0, 0,
+					0, 0, 0, 0,
+					0, 0, 0, RTE_MBUF_F_RX_RSS_HASH,
+					0, 0, 0, 0,
+					0, 0, 0, 0,
+					0, 0, 0, 0,
+					0, 0, 0, RTE_MBUF_F_RX_RSS_HASH,
+					0, 0, 0, 0);
+
+			const __m256i cksum_flags =
+			_mm256_set_epi8(0, 0, 0, 0, 0, 0, 0,
+			0,
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD |
+				RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD |
+				RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD |
+				RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD |
+				RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_GOOD | RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_GOOD | RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			0, 0, 0, 0, 0, 0, 0, 0,
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD |
+				RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD |
+				RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD |
+				RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD |
+				RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_GOOD | RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+			((RTE_MBUF_F_RX_L4_CKSUM_GOOD | RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1));
+
+			const __m256i cksum_mask =
+				_mm256_set1_epi32(RTE_MBUF_F_RX_IP_CKSUM_MASK |
+						  RTE_MBUF_F_RX_L4_CKSUM_MASK |
+						  RTE_MBUF_F_RX_OUTER_L4_CKSUM_MASK |
+						  RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD);
+
+			const __m256i vlan_mask =
+				_mm256_set1_epi32(RTE_MBUF_F_RX_VLAN |
+						  RTE_MBUF_F_RX_VLAN_STRIPPED);
+
+			__m256i tmp_flags;
+			__m256i descs_flags = _mm256_and_si256(staterrs0_7, desc_flags_mask);
+			stu_len0_7 = _mm256_and_si256(stu_len0_7, desc_flags_rss_mask);
+
+			tmp_flags = _mm256_shuffle_epi8(vlan_flags, descs_flags);
+			mbuf_flags = _mm256_and_si256(tmp_flags, vlan_mask);
+
+			descs_flags = _mm256_srli_epi32(descs_flags, 10);
+			tmp_flags   = _mm256_shuffle_epi8(cksum_flags, descs_flags);
+			tmp_flags   = _mm256_slli_epi32(tmp_flags, 1);
+			tmp_flags   = _mm256_and_si256(tmp_flags, cksum_mask);
+			mbuf_flags  = _mm256_or_si256(mbuf_flags, tmp_flags);
+
+			descs_flags = _mm256_srli_epi32(stu_len0_7, 27);
+			tmp_flags   = _mm256_shuffle_epi8(rss_flags, descs_flags);
+			mbuf_flags  = _mm256_or_si256(mbuf_flags, tmp_flags);
+
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+			if (rxq->fnav_enable) {
+				__m256i fnav_vld0_3, fnav_vld4_7;
+				__m256i fnav_vld0_7;
+				__m256i v_zeros, v_ffff, v_u32_one;
+				const __m256i fdir_flags =
+					_mm256_set1_epi32(RTE_MBUF_F_RX_FDIR |
+							  RTE_MBUF_F_RX_FDIR_ID);
+				fnav_vld0_3 = _mm256_unpacklo_epi32(descs2_3, descs0_1);
+				fnav_vld4_7 = _mm256_unpacklo_epi32(descs6_7, descs4_5);
+
+				fnav_vld0_7 = _mm256_unpacklo_epi64(fnav_vld4_7, fnav_vld0_3);
+
+				fnav_vld0_7 = _mm256_slli_epi32(fnav_vld0_7, 26);
+				fnav_vld0_7 = _mm256_srli_epi32(fnav_vld0_7, 31);
+
+				v_zeros = _mm256_setzero_si256();
+				v_ffff = _mm256_cmpeq_epi32(v_zeros, v_zeros);
+				v_u32_one = _mm256_srli_epi32(v_ffff, 31);
+
+				tmp_flags = _mm256_cmpeq_epi32(fnav_vld0_7, v_u32_one);
+
+				tmp_flags = _mm256_and_si256(tmp_flags, fdir_flags);
+
+				mbuf_flags = _mm256_or_si256(mbuf_flags, tmp_flags);
+
+				rx_pkts[i + 0]->hash.fdir.hi = desc[0].wb.fd_filter_id;
+				rx_pkts[i + 1]->hash.fdir.hi = desc[1].wb.fd_filter_id;
+				rx_pkts[i + 2]->hash.fdir.hi = desc[2].wb.fd_filter_id;
+				rx_pkts[i + 3]->hash.fdir.hi = desc[3].wb.fd_filter_id;
+				rx_pkts[i + 4]->hash.fdir.hi = desc[4].wb.fd_filter_id;
+				rx_pkts[i + 5]->hash.fdir.hi = desc[5].wb.fd_filter_id;
+				rx_pkts[i + 6]->hash.fdir.hi = desc[6].wb.fd_filter_id;
+				rx_pkts[i + 7]->hash.fdir.hi = desc[7].wb.fd_filter_id;
+			}
+#endif
+		}
+
+		RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, ol_flags) !=
+				offsetof(struct rte_mbuf, rearm_data) + 8);
+		RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, rx_descriptor_fields1) !=
+				offsetof(struct rte_mbuf, rearm_data) + 16);
+		RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, rearm_data) !=
+				RTE_ALIGN(offsetof(struct rte_mbuf, rearm_data), 16));
+
+		__m256i rearm_arr[8];
+
+		rearm_arr[6] = _mm256_blend_epi32(mbuf_init,
+					_mm256_slli_si256(mbuf_flags, 8), 0x04);
+		rearm_arr[4] = _mm256_blend_epi32(mbuf_init,
+					_mm256_slli_si256(mbuf_flags, 4), 0x04);
+		rearm_arr[2] = _mm256_blend_epi32(mbuf_init, mbuf_flags, 0x04);
+		rearm_arr[0] = _mm256_blend_epi32(mbuf_init,
+					_mm256_srli_si256(mbuf_flags, 4), 0x04);
+
+		rearm_arr[6] = _mm256_permute2f128_si256(rearm_arr[6], mbufs6_7, 0x20);
+		rearm_arr[4] = _mm256_permute2f128_si256(rearm_arr[4], mbufs4_5, 0x20);
+		rearm_arr[2] = _mm256_permute2f128_si256(rearm_arr[2], mbufs2_3, 0x20);
+		rearm_arr[0] = _mm256_permute2f128_si256(rearm_arr[0], mbufs0_1, 0x20);
+
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 6]->rearm_data, rearm_arr[6]);
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 4]->rearm_data, rearm_arr[4]);
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 2]->rearm_data, rearm_arr[2]);
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 0]->rearm_data, rearm_arr[0]);
+
+		const __m256i tmp_mbuf_flags =
+				_mm256_castsi128_si256(_mm256_extracti128_si256(mbuf_flags, 1));
+
+		rearm_arr[7] = _mm256_blend_epi32(mbuf_init,
+					_mm256_slli_si256(tmp_mbuf_flags, 8), 4);
+		rearm_arr[5] = _mm256_blend_epi32(mbuf_init,
+					_mm256_slli_si256(tmp_mbuf_flags, 4), 4);
+		rearm_arr[3] = _mm256_blend_epi32(mbuf_init, tmp_mbuf_flags, 4);
+		rearm_arr[1] = _mm256_blend_epi32(mbuf_init,
+					_mm256_srli_si256(tmp_mbuf_flags, 4), 4);
+
+		rearm_arr[7] = _mm256_blend_epi32(rearm_arr[7], mbufs6_7, 0XF0);
+		rearm_arr[5] = _mm256_blend_epi32(rearm_arr[5], mbufs4_5, 0XF0);
+		rearm_arr[3] = _mm256_blend_epi32(rearm_arr[3], mbufs2_3, 0XF0);
+		rearm_arr[1] = _mm256_blend_epi32(rearm_arr[1], mbufs0_1, 0XF0);
+
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 7]->rearm_data, rearm_arr[7]);
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 5]->rearm_data, rearm_arr[5]);
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 3]->rearm_data, rearm_arr[3]);
+		_mm256_storeu_si256((__m256i *)&rx_pkts[i + 1]->rearm_data, rearm_arr[1]);
+
+		if (umbcast_flags) {
+			const __m256i umbcast_mask =
+				_mm256_set1_epi32(SXE2_RX_DESC_STATUS_UMBCAST_MASK);
+			__m256i umbcast_bits_256 =
+				_mm256_and_si256(staterrs0_7, umbcast_mask);
+
+			umbcast_bits_256 = _mm256_srli_epi32(umbcast_bits_256, 24);
+			__m128i umbcast_bits_128 =
+				_mm_packs_epi32(_mm256_castsi256_si128(umbcast_bits_256),
+						_mm256_extractf128_si256(umbcast_bits_256, 1));
+
+			umbcast_bits_128 = _mm_shuffle_epi8(umbcast_bits_128, eop_shuf_mask);
+
+			*(uint64_t *)umbcast_flags = _mm_cvtsi128_si64(umbcast_bits_128);
+			umbcast_flags += SXE2_RX_NUM_PER_LOOP_AVX;
+		}
+
+		if (split_rxe_flags) {
+			const __m256i eop_rxe_mask =
+					_mm256_set1_epi32(SXE2_RX_DESC_STATUS_EOP_MASK |
+								SXE2_RX_DESC_ERROR_RXE_MASK |
+								SXE2_RX_DESC_ERROR_OVERSIZE_MASK);
+			const __m128i eop_mask_128 =
+					_mm_set1_epi16(SXE2_RX_DESC_STATUS_EOP_MASK);
+			const __m128i rxe_mask_128 =
+					_mm_set1_epi16(SXE2_RX_DESC_ERROR_RXE_MASK |
+							SXE2_RX_DESC_ERROR_OVERSIZE_MASK);
+
+			const __m256i tmp_stats = _mm256_and_si256(staterrs0_7, eop_rxe_mask);
+
+			const __m128i eop_rxe_bits = _mm_packs_epi32
+							(_mm256_castsi256_si128(tmp_stats),
+							 _mm256_extractf128_si256(tmp_stats, 1));
+
+			__m128i not_eop_bits = _mm_andnot_si128(eop_rxe_bits, eop_mask_128);
+
+			not_eop_bits =
+				_mm_or_si128(not_eop_bits,
+					     _mm_srli_epi16(_mm_and_si128(eop_rxe_bits,
+									       rxe_mask_128),
+							      7));
+
+			not_eop_bits = _mm_shuffle_epi8(not_eop_bits, eop_shuf_mask);
+
+			*(uint64_t *)split_rxe_flags = _mm_cvtsi128_si64(not_eop_bits);
+			split_rxe_flags += SXE2_RX_NUM_PER_LOOP_AVX;
+		}
+
+		staterrs0_7 = _mm256_and_si256(staterrs0_7, dd_mask);
+
+		staterrs0_7 = _mm256_packs_epi32(staterrs0_7, _mm256_setzero_si256());
+
+		bit_num = rte_popcount64
+				(_mm_cvtsi128_si64(_mm256_extracti128_si256(staterrs0_7, 1)));
+		bit_num += rte_popcount64
+				(_mm_cvtsi128_si64(_mm256_castsi256_si128(staterrs0_7)));
+		done_num += bit_num;
+
+		if (bit_num != SXE2_RX_NUM_PER_LOOP_AVX)
+			break;
+	}
+
+	rxq->processing_idx += done_num;
+	rxq->processing_idx &= (rxq->ring_depth - 1);
+	if ((rxq->processing_idx & 1) == 1 && done_num > 1) {
+		rxq->processing_idx--;
+		done_num--;
+	}
+	rxq->realloc_num     += done_num;
+
+l_end:
+	PMD_LOG_DEBUG(RX, "port_id=%u queue_id=%u last_id=%u recv_pkts=%d",
+			rxq->port_id, rxq->queue_id, rxq->processing_idx, done_num);
+	return done_num;
+}
+
+static __rte_always_inline uint16_t
+sxe2_rx_pkts_scattered_batch_vec_avx512(struct sxe2_rx_queue *rxq, struct rte_mbuf **rx_pkts,
+	uint16_t nb_pkts, bool do_offload)
+{
+	const uint64_t *split_rxe_flags64;
+	uint8_t split_rxe_flags[SXE2_RX_PKTS_BURST_BATCH_NUM_VEC] = {0};
+	uint8_t umbcast_flags[SXE2_RX_PKTS_BURST_BATCH_NUM_VEC] = {0};
+	uint16_t rx_done_num;
+	uint16_t rx_pkt_done_num;
+
+	rx_pkt_done_num = 0;
+
+	if (rxq->vsi->adapter->devargs.sw_stats_en) {
+		rx_done_num = sxe2_rx_pkts_common_vec_avx512(rxq, rx_pkts,
+				nb_pkts, split_rxe_flags,
+				umbcast_flags, do_offload);
+	} else {
+		rx_done_num = sxe2_rx_pkts_common_vec_avx512(rxq, rx_pkts,
+				nb_pkts, split_rxe_flags,
+			    NULL, do_offload);
+	}
+	if (rx_done_num == 0)
+		goto l_end;
+
+	if (!rxq->vsi->adapter->devargs.sw_stats_en) {
+		split_rxe_flags64 = (uint64_t *)split_rxe_flags;
+
+		if (rxq->pkt_first_seg == NULL &&
+				!split_rxe_flags64[0] && !split_rxe_flags64[1] &&
+				!split_rxe_flags64[2] && !split_rxe_flags64[3]) {
+			rx_pkt_done_num = rx_done_num;
+			goto l_end;
+		}
+
+		if (rxq->pkt_first_seg == NULL) {
+			while (rx_pkt_done_num < rx_done_num &&
+			       split_rxe_flags[rx_pkt_done_num] == 0)
+				rx_pkt_done_num++;
+
+			if (rx_pkt_done_num == rx_done_num)
+				goto l_end;
+
+			rxq->pkt_first_seg = rx_pkts[rx_pkt_done_num];
+		}
+	}
+
+	rx_pkt_done_num += sxe2_rx_pkts_refactor(rxq, &rx_pkts[rx_pkt_done_num],
+			rx_done_num - rx_pkt_done_num, &split_rxe_flags[rx_pkt_done_num],
+			&umbcast_flags[rx_pkt_done_num]);
+
+l_end:
+
+	return rx_pkt_done_num;
+}
+
+static __rte_always_inline uint16_t
+sxe2_rx_pkts_scattered_common_vec_avx512(void *rx_queue,
+	struct rte_mbuf **rx_pkts, uint16_t nb_pkts, bool offload)
+{
+	uint16_t done_num = 0;
+	uint16_t once_num  = 0;
+
+	while (nb_pkts > SXE2_RX_PKTS_BURST_BATCH_NUM) {
+		once_num = sxe2_rx_pkts_scattered_batch_vec_avx512(rx_queue, rx_pkts + done_num,
+			SXE2_RX_PKTS_BURST_BATCH_NUM, offload);
+
+		done_num  += once_num;
+		nb_pkts -= once_num;
+
+		if (once_num < SXE2_RX_PKTS_BURST_BATCH_NUM)
+			goto end;
+	}
+
+	done_num += sxe2_rx_pkts_scattered_batch_vec_avx512(rx_queue,
+		rx_pkts + done_num, nb_pkts, offload);
+
+end:
+	return done_num;
+}
+
+uint16_t sxe2_rx_pkts_scattered_vec_avx512(void *rx_queue,
+		struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
+{
+	return sxe2_rx_pkts_scattered_common_vec_avx512(rx_queue,
+			rx_pkts, nb_pkts, false);
+}
+
+uint16_t sxe2_rx_pkts_scattered_vec_avx512_offload(void *rx_queue,
+		struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
+{
+	return sxe2_rx_pkts_scattered_common_vec_avx512(rx_queue,
+			rx_pkts, nb_pkts, true);
+}
+
+#endif
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 07/23] net/sxe2: support IPsec inline protocol offload
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch adds support for IPsec inline protocol offload for both
inbound and outbound traffic.

- Implement rte_security_ops: session_create, session_destroy.
- Add hardware SA table management.
- Update Rx/Tx data path to handle security offload flags.

The hardware offloads the ESP encapsulation/decapsulation and
cryptographic processing.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build      |    2 +
 drivers/net/sxe2/sxe2_cmd_chnl.c  |  197 ++++
 drivers/net/sxe2/sxe2_cmd_chnl.h  |   20 +
 drivers/net/sxe2/sxe2_drv_cmd.h   |   61 ++
 drivers/net/sxe2/sxe2_ethdev.c    |   14 +
 drivers/net/sxe2/sxe2_ethdev.h    |    3 +
 drivers/net/sxe2/sxe2_ipsec.c     | 1565 +++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_ipsec.h     |  254 +++++
 drivers/net/sxe2/sxe2_rx.c        |    5 +
 drivers/net/sxe2/sxe2_security.c  |  335 ++++++
 drivers/net/sxe2/sxe2_security.h  |   77 ++
 drivers/net/sxe2/sxe2_tx.c        |    8 +
 drivers/net/sxe2/sxe2_txrx_poll.c |   55 +
 13 files changed, 2596 insertions(+)
 create mode 100644 drivers/net/sxe2/sxe2_ipsec.c
 create mode 100644 drivers/net/sxe2/sxe2_ipsec.h
 create mode 100644 drivers/net/sxe2/sxe2_security.c
 create mode 100644 drivers/net/sxe2/sxe2_security.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index d0aa7fecf0..e3bcfc2876 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -64,6 +64,8 @@ sources += files(
         'sxe2_filter.c',
         'sxe2_rss.c',
         'sxe2_tm.c',
+        'sxe2_ipsec.c',
+        'sxe2_security.c',
 )
 
 allow_internal_get_api = true
\ No newline at end of file
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index 19323ffcc4..7711e8e57d 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -877,3 +877,200 @@ int32_t sxe2_drv_tm_commit(struct sxe2_adapter *adapter)
 l_end:
 	return ret;
 }
+
+
+int32_t sxe2_drv_ipsec_get_capa(struct sxe2_adapter *adapter)
+{
+	int32_t ret = -1;
+	struct sxe2_drv_cmd_params cmd = { 0 };
+	struct sxe2_drv_ipsec_capa_resq resp;
+	struct sxe2_common_device *cdev = adapter->cdev;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_CAP_GET,
+				 NULL, 0,
+				 &resp, sizeof(resp));
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to get ipsec specifications, ret=%d", ret);
+		goto l_end;
+	}
+
+	adapter->security_ctx.ipsec_ctx.max_tx_sa = rte_le_to_cpu_16(resp.tx_sa_cnt);
+	adapter->security_ctx.ipsec_ctx.max_rx_sa = rte_le_to_cpu_16(resp.rx_sa_cnt);
+	adapter->security_ctx.ipsec_ctx.max_tcam = rte_le_to_cpu_16(resp.ip_id_cnt);
+	adapter->security_ctx.ipsec_ctx.max_udp_group = rte_le_to_cpu_16(resp.udp_group_cnt);
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "Max tx sa:%u, max rx sa:%u, max tcam:%u, udp group:%u.",
+			 rte_le_to_cpu_16(resp.tx_sa_cnt),
+			 rte_le_to_cpu_16(resp.rx_sa_cnt),
+			 rte_le_to_cpu_16(resp.ip_id_cnt),
+			 rte_le_to_cpu_16(resp.udp_group_cnt));
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_resource_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = -1;
+	struct sxe2_drv_cmd_params cmd = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_RESOURCE_CLEAR,
+				 NULL, 0,
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to clear ipsec resource, ret=%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_txsa_add(struct sxe2_adapter *adapter,
+		struct sxe2_ipsec_tx_sa *tx_sa)
+{
+	struct sxe2_drv_cmd_params cmd               = { 0 };
+	struct sxe2_drv_ipsec_txsa_add_req req   = { 0 };
+	struct sxe2_drv_ipsec_txsa_add_resp resp = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                   = -1;
+	uint32_t mode                                  = 0;
+	uint32_t i                                     = 0;
+
+	if (tx_sa->algo == SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC)
+		mode |= IPSEC_TX_ENGINE_SM4;
+	if (tx_sa->mode == SXE2_IPSEC_MODE_ENC_AND_AUTH)
+		mode |= IPSEC_TX_ENCRYPT;
+	req.mode = rte_cpu_to_le_32(mode);
+	for (i = 0; i < SXE2_IPSEC_KEY_LEN; i++) {
+		req.encrypt_keys[i] = tx_sa->enc_key[i];
+		req.auth_keys[i] = tx_sa->auth_key[i];
+	}
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_TXSA_ADD,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "failed to add tx sa, ret=%d", ret);
+		goto l_end;
+	}
+	tx_sa->hw_sa_id = rte_le_to_cpu_16(resp.index);
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_rxsa_add(struct sxe2_adapter *adapter,
+		struct sxe2_ipsec_rx_sa *rx_sa,
+		struct sxe2_ipsec_rx_tcam *rx_tcam,
+		struct sxe2_ipsec_rx_udp_group *rx_udp_group)
+{
+	struct sxe2_drv_cmd_params cmd               = { 0 };
+	struct sxe2_drv_ipsec_rxsa_add_req req   = { 0 };
+	struct sxe2_drv_ipsec_rxsa_add_resp resp = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                   = -1;
+	uint32_t mode                                  = 0;
+	uint32_t i                                     = 0;
+
+	if (rx_sa->algo == SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC)
+		mode |= IPSEC_RX_ENGINE_SM4;
+	if (rx_sa->mode == SXE2_IPSEC_MODE_ENC_AND_AUTH)
+		mode |= IPSEC_RX_DECRYPT;
+	if (rx_tcam->ip_addr.type == RTE_SECURITY_IPSEC_TUNNEL_IPV6) {
+		mode |= IPSEC_RX_IPV6;
+		memcpy(req.ipaddr, rx_tcam->ip_addr.dst_ipv6, sizeof(req.ipaddr));
+	} else {
+		req.ipaddr[0] = rx_tcam->ip_addr.dst_ipv4;
+	}
+	req.mode = rte_cpu_to_le_32(mode);
+	req.spi = rte_cpu_to_le_32(rx_sa->spi);
+	if (rx_udp_group != NULL) {
+		req.udp_port = rte_cpu_to_le_32((uint32_t)rx_udp_group->udp_port);
+		req.sport_en = rx_udp_group->sport_en;
+		req.dport_en = rx_udp_group->dport_en;
+	}
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "Add rx sa, mode: 0x%x, spi: 0x%x, udp_port: %u, "
+			 "sport_en: %u, dport_en: %u.",
+			 req.mode, req.spi, req.udp_port, req.sport_en, req.dport_en);
+
+	/* encrypt and auth keys */
+	for (i = 0; i < SXE2_IPSEC_KEY_LEN; i++) {
+		req.encrypt_keys[i] = rx_sa->enc_key[i];
+		req.auth_keys[i] = rx_sa->auth_key[i];
+	}
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_RXSA_ADD,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add rx sa, ret=%d", ret);
+		goto l_end;
+	}
+	rx_sa->hw_sa_id = rte_le_to_cpu_16(resp.sa_idx);
+	rx_sa->hw_ip_id = resp.ip_id;
+	rx_tcam->hw_ip_id = resp.ip_id;
+	rx_sa->hw_udp_group_id = resp.udp_group_id;
+	if (rx_udp_group != NULL)
+		rx_udp_group->hw_group_id = resp.udp_group_id;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_rxsa_delete(struct sxe2_adapter *adapter,
+					struct sxe2_ipsec_rx_sa *rx_sa)
+{
+	struct sxe2_drv_ipsec_rxsa_del_req req = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	req.sa_idx = rte_cpu_to_le_16(rx_sa->hw_sa_id);
+	req.spi = rte_cpu_to_le_32(rx_sa->spi);
+	req.ip_id = rx_sa->hw_ip_id;
+	req.group_id = rx_sa->hw_udp_group_id;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_RXSA_DEL,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV,
+				"Failed to delete rx sa, sa id: %u, spi: %u, "
+				"ip id: %u, udp group id: %u, ret: %d.",
+				rx_sa->hw_sa_id, rx_sa->spi, rx_sa->hw_ip_id,
+				rx_sa->hw_udp_group_id, ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
+					   uint16_t sa_id)
+{
+	struct sxe2_drv_ipsec_txsa_del_req req = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	req.sa_idx = rte_cpu_to_le_16(sa_id);
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_TXSA_DEL,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV,
+				"Failed to delete tx sa, sa id: %u, ret: %d.",
+				sa_id, ret);
+
+	return ret;
+}
+
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index 6e209377c7..d8e09a4453 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -44,6 +44,26 @@ int32_t sxe2_drv_root_tree_alloc(struct rte_eth_dev *dev);
 
 int32_t sxe2_drv_tm_commit(struct sxe2_adapter *adapter);
 
+int32_t sxe2_drv_ipsec_resource_clear(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_ipsec_get_capa(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_ipsec_rxsa_add(struct sxe2_adapter *adapter,
+			    struct sxe2_ipsec_rx_sa *rx_sa,
+			    struct sxe2_ipsec_rx_tcam *rx_tcam,
+			    struct sxe2_ipsec_rx_udp_group *rx_udp_group);
+
+int32_t sxe2_drv_ipsec_txsa_add(struct sxe2_adapter *adapter,
+			    struct sxe2_ipsec_tx_sa *tx_sa);
+
+int32_t sxe2_drv_ipsec_rxsa_delete(struct sxe2_adapter *adapter,
+			       struct sxe2_ipsec_rx_sa *rx_sa);
+
+int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
+			       uint16_t sa_id);
+
+int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set);
+
 int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set);
 
 int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index 07f083644c..4d9c2e05a8 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -373,6 +373,67 @@ struct sxe2_tm_add_queue_msg {
 	struct sxe2_tm_info info;
 };
 
+struct sxe2_drv_ipsec_capa_resq {
+	__le16 tx_sa_cnt;
+	__le16 rx_sa_cnt;
+	__le16 ip_id_cnt;
+	__le16 udp_group_cnt;
+};
+
+#define SXE2_IPSEC_KEY_LEN (32)
+#define SXE2_IPV6_ADDR_LEN (4)
+struct sxe2_drv_ipsec_txsa_add_req {
+	__le32 mode;
+	uint8_t encrypt_keys[SXE2_IPSEC_KEY_LEN];
+	uint8_t auth_keys[SXE2_IPSEC_KEY_LEN];
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+};
+
+struct sxe2_drv_ipsec_txsa_add_resp {
+	__le16 index;
+};
+
+struct sxe2_drv_ipsec_rxsa_add_req {
+	__le32 mode;
+	__le32 spi;
+	__le32 ipaddr[SXE2_IPV6_ADDR_LEN];
+	__le32 udp_port;
+	uint8_t sport_en;
+	uint8_t dport_en;
+	uint8_t is_over_sdn;
+	uint8_t sdn_group_id;
+	uint8_t encrypt_keys[SXE2_IPSEC_KEY_LEN];
+	uint8_t auth_keys[SXE2_IPSEC_KEY_LEN];
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+};
+
+struct sxe2_drv_ipsec_rxsa_add_resp {
+	uint8_t ip_id;
+	uint8_t udp_group_id;
+	__le16 sa_idx;
+};
+
+struct sxe2_drv_ipsec_txsa_del_req {
+	__le16 sa_idx;
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+};
+
+struct sxe2_drv_ipsec_rxsa_del_req {
+	uint8_t ip_id;
+	uint8_t group_id;
+	__le16 sa_idx;
+	__le32 spi;
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+};
+
 enum sxe2_drv_cmd_module {
 	SXE2_DRV_CMD_MODULE_HANDSHAKE = 0,
 	SXE2_DRV_CMD_MODULE_DEV = 1,
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index a095888c00..00c0552d4a 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -298,6 +298,11 @@ static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev,
 	if (adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_PTP)
 		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_TIMESTAMP;
 
+	if (sxe2_ipsec_supported(adapter)) {
+		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_SECURITY;
+		dev_info->tx_offload_capa |= RTE_ETH_TX_OFFLOAD_SECURITY;
+	}
+
 	if (adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_RSS) {
 		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_RSS_HASH;
 		dev_info->flow_type_rss_offloads  |= SXE2_RSS_HF_SUPPORT_ALL;
@@ -1053,6 +1058,12 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_eth_err;
 	}
 
+	ret = sxe2_security_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize security, ret=%d", ret);
+		goto init_security_err;
+	}
+
 	ret = sxe2_rss_disable(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to disable rss, ret=%d", ret);
@@ -1067,6 +1078,8 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 
 	goto l_end;
 
+init_security_err:
+	sxe2_eth_uinit(dev);
 init_sched_err:
 init_rss_err:
 init_eth_err:
@@ -1085,6 +1098,7 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 	(void)sxe2_rss_disable(dev);
 	(void)sxe2_sched_uinit(dev);
 	sxe2_vsi_uninit(dev);
+	sxe2_security_uinit(dev);
 	sxe2_dev_pci_map_uinit(dev);
 	sxe2_eth_uinit(dev);
 
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 609e1e92ba..32a67ed344 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -19,6 +19,8 @@
 #include "sxe2_queue.h"
 #include "sxe2_mac.h"
 #include "sxe2_osal.h"
+#include "sxe2_security.h"
+#include "sxe2_ipsec.h"
 #include "sxe2_tm.h"
 #include "sxe2_filter.h"
 
@@ -312,6 +314,7 @@ struct sxe2_adapter {
 	struct sxe2_sched_hw_cap      sched_ctxt;
 	struct sxe2_tm_context        tm_ctxt;
 	struct sxe2_devargs           devargs;
+	struct sxe2_security_ctx      security_ctx;
 	struct sxe2_switchdev_info    switchdev_info;
 	bool                          rule_started;
 	bool                          flow_isolated;
diff --git a/drivers/net/sxe2/sxe2_ipsec.c b/drivers/net/sxe2/sxe2_ipsec.c
new file mode 100644
index 0000000000..9fd29bd6e5
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_ipsec.c
@@ -0,0 +1,1565 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_malloc.h>
+#include <rte_bitmap.h>
+
+#include "sxe2_ethdev.h"
+#include "sxe2_security.h"
+#include "sxe2_ipsec.h"
+#include "sxe2_cmd_chnl.h"
+#include "sxe2_common_log.h"
+
+bool sxe2_ipsec_supported(struct sxe2_adapter *adapter)
+{
+	uint64_t cap = adapter->cap_flags;
+
+	return !!(cap & SXE2_DEV_CAPS_OFFLOAD_IPSEC);
+}
+
+bool sxe2_ipsec_valid_tx_offloads(uint64_t offloads)
+{
+	bool ret = true;
+	uint64_t tso_features = 0;
+	uint64_t cksum_features = 0;
+
+	if (offloads & RTE_ETH_TX_OFFLOAD_SECURITY) {
+		tso_features = RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			RTE_ETH_TX_OFFLOAD_UDP_TSO |
+			RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO |
+			RTE_ETH_TX_OFFLOAD_GRE_TNL_TSO |
+			RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO |
+			RTE_ETH_TX_OFFLOAD_GENEVE_TNL_TSO;
+		if (offloads & tso_features) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with TSO offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		cksum_features = RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+			RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+			RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+			RTE_ETH_TX_OFFLOAD_SCTP_CKSUM |
+			RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM |
+			RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM;
+		if (offloads & cksum_features) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with checksum offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & (RTE_ETH_TX_OFFLOAD_VLAN_INSERT | RTE_ETH_TX_OFFLOAD_QINQ_INSERT)) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with vlan offload.");
+			ret = false;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+bool sxe2_ipsec_valid_rx_offloads(uint64_t offloads)
+{
+	bool ret = true;
+
+	if (offloads & RTE_ETH_RX_OFFLOAD_SECURITY) {
+		if (offloads & RTE_ETH_RX_OFFLOAD_TCP_LRO) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with LRO offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with checksum offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with keep CRC offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & RTE_ETH_RX_OFFLOAD_VLAN) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with vlan offload.");
+			ret = false;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_ipsec_bitmap_mem_init(struct rte_bitmap **d_bmp, void **d_mem, uint32_t bits)
+{
+	struct rte_bitmap *bmp = NULL;
+	uint32_t bmp_size           = 0;
+	void *mem              = NULL;
+	int32_t ret                = -1;
+
+	bmp_size = rte_bitmap_get_memory_footprint(bits);
+
+	mem = rte_zmalloc("ipsec bitmap", bmp_size, RTE_CACHE_LINE_SIZE);
+	if (mem == NULL) {
+		PMD_LOG_ERR(DRV, "Alloc ipsec bitmap memory failed.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	bmp = rte_bitmap_init(bits, mem, bmp_size);
+	if (bmp == NULL) {
+		PMD_LOG_ERR(DRV, "Failed to init ipsec bitmap.");
+		rte_free(mem);
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	*d_bmp = bmp;
+	*d_mem = mem;
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_ipsec_bitmap_init(struct sxe2_security_ctx *sxe2_sctx)
+{
+	int32_t ret  = -1;
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem, sxe2_sctx->ipsec_ctx.max_tx_sa);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem, sxe2_sctx->ipsec_ctx.max_rx_sa);
+	if (ret) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem, sxe2_sctx->ipsec_ctx.max_tcam);
+	if (ret) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem, sxe2_sctx->ipsec_ctx.max_udp_group);
+	if (ret) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+		sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem = NULL;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static uint16_t sxe2_ipsec_id_alloc(struct rte_bitmap *bmp, uint16_t bits)
+{
+	uint16_t i = 0;
+	uint16_t index = 0XFFFF;
+
+	for (i = 0; i < bits; i++) {
+		if (!rte_bitmap_get(bmp, i)) {
+			index = i;
+			rte_bitmap_set(bmp, i);
+			break;
+		}
+	}
+
+	return index;
+}
+
+static void sxe2_ipsec_id_free(struct rte_bitmap *bmp, uint16_t pos)
+{
+	rte_bitmap_clear(bmp, pos);
+}
+
+static struct rte_cryptodev_symmetric_capability *
+sxe2_ipsec_cipher_cap_get(struct rte_cryptodev_capabilities *crypto_cap,
+			enum rte_crypto_cipher_algorithm algo)
+{
+	struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint8_t index                                              = 0;
+
+	for (index = 0; index < SXE2_IPSEC_CAP_MAX; index++) {
+		if (crypto_cap[index].sym.xform_type == RTE_CRYPTO_SYM_XFORM_CIPHER &&
+			crypto_cap[index].sym.cipher.algo == algo) {
+			capability = &crypto_cap[index].sym;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return capability;
+}
+
+static struct rte_cryptodev_symmetric_capability *
+sxe2_ipsec_auth_cap_get(struct rte_cryptodev_capabilities *crypto_cap,
+			enum rte_crypto_auth_algorithm algo)
+{
+	struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint8_t index                                              = 0;
+
+	for (index = 0; index < SXE2_IPSEC_CAP_MAX; index++) {
+		if (crypto_cap[index].sym.xform_type == RTE_CRYPTO_SYM_XFORM_AUTH &&
+			crypto_cap[index].sym.auth.algo == algo) {
+			capability = &crypto_cap[index].sym;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return capability;
+}
+
+static bool sxe2_security_valid_key(uint16_t src_key, uint16_t max_key,
+				    uint16_t min_key, uint16_t increment)
+{
+	bool is_valid = false;
+
+	if (src_key < min_key || src_key > max_key) {
+		is_valid = false;
+		goto l_end;
+	}
+
+	if (increment == 0) {
+		is_valid = true;
+		goto l_end;
+	}
+
+	if ((uint16_t)(src_key - min_key) % increment) {
+		is_valid = false;
+		goto l_end;
+	}
+
+	if (src_key > SXE2_IPSEC_MAX_KEY_LEN) {
+		is_valid = false;
+		goto l_end;
+	}
+
+	is_valid = true;
+
+l_end:
+	return is_valid;
+}
+
+static int32_t
+sxe2_ipsec_valid_cipher(enum rte_crypto_cipher_operation cipher_op,
+			struct rte_cryptodev_capabilities *crypto_cap,
+			struct rte_crypto_sym_xform *xform)
+{
+	const struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint16_t src_key                                = 0;
+	uint16_t max_key                                = 0;
+	uint16_t min_key                                = 0;
+	uint16_t increment                              = 0;
+	int32_t ret                                    = -1;
+
+	if (xform->cipher.op != cipher_op) {
+		PMD_LOG_ERR(DRV, "Invalid cipher direction specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	capability = sxe2_ipsec_cipher_cap_get(crypto_cap, xform->cipher.algo);
+	if (!capability) {
+		PMD_LOG_ERR(DRV, "Invalid cipher algo specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	src_key = xform->cipher.key.length;
+	min_key = capability->cipher.key_size.min;
+	max_key = capability->cipher.key_size.max;
+	increment = capability->cipher.key_size.increment;
+	if (!sxe2_security_valid_key(src_key, max_key, min_key, increment)) {
+		PMD_LOG_ERR(DRV, "Invalid cipher key size specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_valid_auth(enum rte_crypto_auth_operation auth_op,
+		      struct rte_cryptodev_capabilities *crypto_cap,
+		      struct rte_crypto_sym_xform *xform)
+{
+	const struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint16_t src_key                                = 0;
+	uint16_t max_key                                = 0;
+	uint16_t min_key                                = 0;
+	uint16_t increment                              = 0;
+	int32_t ret                                    = -1;
+
+	if (xform->auth.op != auth_op) {
+		PMD_LOG_ERR(DRV, "Invalid auth direction specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	capability = sxe2_ipsec_auth_cap_get(crypto_cap, xform->auth.algo);
+	if (!capability) {
+		PMD_LOG_ERR(DRV, "Invalid auth algo specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	src_key = xform->auth.key.length;
+	min_key = capability->auth.key_size.min;
+	max_key = capability->auth.key_size.max;
+	increment = capability->auth.key_size.increment;
+	if (!sxe2_security_valid_key(src_key, max_key, min_key, increment)) {
+		PMD_LOG_ERR(DRV, "Invalid auth key size specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static bool
+sxe2_ipsec_valid_algo(enum rte_crypto_auth_algorithm auth_algo,
+		      enum rte_crypto_cipher_algorithm cipher_algo)
+{
+	bool ret = false;
+
+	if ((cipher_algo == SXE2_RTE_CRYPTO_CIPHER_AES_CBC &&
+		 auth_algo == SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC) ||
+		(cipher_algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC &&
+		 auth_algo == SXE2_RTE_CRYPTO_AUTH_SM3_HMAC)) {
+		ret = true;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static enum sxe2_ipsec_algorithm
+sxe2_ipsec_algo_gen(enum rte_crypto_cipher_algorithm cipher_algo)
+{
+	enum sxe2_ipsec_algorithm algo = SXE2_IPSEC_ALGO_INVALID;
+
+	if (cipher_algo == SXE2_RTE_CRYPTO_CIPHER_AES_CBC)
+		algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+	else if (cipher_algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC)
+		algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+
+	return algo;
+}
+
+static int32_t
+	sxe2_ipsec_valid_xform(struct sxe2_security_ctx *sxe2_sctx,
+			       struct rte_security_session_conf *conf)
+{
+	struct rte_crypto_sym_xform *xform = NULL;
+	struct rte_cryptodev_capabilities *crypto_cap =
+		sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].crypto_capabilities;
+	enum rte_crypto_auth_algorithm auth_algo = RTE_CRYPTO_AUTH_NULL;
+	enum rte_crypto_cipher_algorithm cipher_algo = RTE_CRYPTO_CIPHER_NULL;
+	int32_t ret = -1;
+
+	if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_EGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		xform = conf->crypto_xform;
+		cipher_algo = xform->cipher.algo;
+		ret = sxe2_ipsec_valid_cipher(RTE_CRYPTO_CIPHER_OP_ENCRYPT,
+					      crypto_cap, xform);
+		if (ret)
+			goto l_end;
+
+		if (conf->crypto_xform->next) {
+			if (conf->crypto_xform->next->type == RTE_CRYPTO_SYM_XFORM_AUTH) {
+				auth_algo = conf->crypto_xform->next->auth.algo;
+				if (!sxe2_ipsec_valid_algo(auth_algo, cipher_algo)) {
+					PMD_LOG_ERR(DRV, "Invalid algo group.");
+					ret = -EINVAL;
+					goto l_end;
+				}
+				xform = conf->crypto_xform->next;
+				ret = sxe2_ipsec_valid_auth(RTE_CRYPTO_AUTH_OP_GENERATE,
+									crypto_cap, xform);
+				if (ret)
+					goto l_end;
+			} else {
+				PMD_LOG_ERR(DRV, "Encrypt direction next xform only verify.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+		}
+	} else if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_INGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		xform = conf->crypto_xform;
+		ret = sxe2_ipsec_valid_cipher(RTE_CRYPTO_CIPHER_OP_DECRYPT,
+										crypto_cap, xform);
+		if (ret)
+			goto l_end;
+
+	} else if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_INGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_AUTH) {
+		xform = conf->crypto_xform;
+		ret = sxe2_ipsec_valid_auth(RTE_CRYPTO_AUTH_OP_VERIFY, crypto_cap, xform);
+		if (ret)
+			goto l_end;
+
+		if (conf->crypto_xform->next &&
+			conf->crypto_xform->next->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+			auth_algo = conf->crypto_xform->auth.algo;
+			cipher_algo = conf->crypto_xform->next->cipher.algo;
+			if (!sxe2_ipsec_valid_algo(auth_algo, cipher_algo)) {
+				PMD_LOG_ERR(DRV, "Invalid algo group.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			xform = conf->crypto_xform->next;
+			ret = sxe2_ipsec_valid_cipher(RTE_CRYPTO_CIPHER_OP_DECRYPT,
+										crypto_cap, xform);
+			if (ret)
+				goto l_end;
+		} else {
+			PMD_LOG_ERR(DRV, "Not support decrypt direction only verify, but not decrypt.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	} else {
+		PMD_LOG_ERR(DRV, "Encrypt/decrypt xform invalid.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_valid_udp(struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+	uint16_t sport = conf->ipsec.udp.sport;
+	uint16_t dport = conf->ipsec.udp.dport;
+
+	if (conf->ipsec.options.udp_encap == 0) {
+		ret = 0;
+		goto l_end;
+	}
+
+	if (sport == 0 && dport == 0) {
+		PMD_LOG_ERR(DRV, "Invalid udp port, cannot be zero.");
+		ret = -1;
+		goto l_end;
+	}
+
+	if (sport != 0 && dport != 0 && sport != dport) {
+		PMD_LOG_ERR(DRV, "Invalid udp port, if sport and dport is not zero, must be equal.");
+		ret = -1;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_session_conf_valid(struct sxe2_security_ctx *sxe2_sctx,
+			      struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+
+	if (sxe2_sctx == NULL) {
+		PMD_LOG_ERR(DRV, "Invalid  security ctx.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->action_type !=
+		sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].action) {
+		PMD_LOG_ERR(DRV, "Invalid action specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.mode !=
+		sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].ipsec.mode) {
+		PMD_LOG_ERR(DRV, "Invalid IPsec mode specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.proto !=
+	    sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].ipsec.proto) {
+		PMD_LOG_ERR(DRV, "Invalid IPsec protocol specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.options.esn) {
+		PMD_LOG_ERR(DRV, "Not support esn.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_INGRESS &&
+		conf->ipsec.spi == 0) {
+		PMD_LOG_ERR(DRV, "spi cannot be zero.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->crypto_xform == NULL) {
+		PMD_LOG_ERR(DRV, "Invalid ipsec xform specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_valid_udp(conf);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_ipsec_valid_xform(sxe2_sctx, conf);
+	if (ret)
+		goto l_end;
+
+l_end:
+	return ret;
+}
+
+static void
+sxe2_ipsec_session_save(struct sxe2_security_ctx *sxe2_sctx,
+			struct rte_security_session_conf *conf,
+			struct sxe2_security_session *sxe2_sess, uint16_t sa_id, uint16_t index)
+{
+	enum rte_crypto_cipher_algorithm cipher_algo   = RTE_CRYPTO_CIPHER_NULL;
+
+	sxe2_sess->adapter = sxe2_sctx->adapter;
+	sxe2_sess->direction = conf->ipsec.direction;
+	sxe2_sess->protocol = conf->protocol;
+	sxe2_sess->mode = conf->ipsec.mode;
+	sxe2_sess->sa_proto = conf->ipsec.proto;
+	sxe2_sess->sa.spi = conf->ipsec.spi;
+	sxe2_sess->sa.hw_idx = sa_id;
+	sxe2_sess->sa.sw_idx = index;
+
+	if (conf->ipsec.options.esn) {
+		sxe2_sess->esn.enabled = true;
+		sxe2_sess->esn.value = conf->ipsec.esn.value;
+	}
+
+	if (sxe2_sess->mode == RTE_SECURITY_IPSEC_SA_MODE_TUNNEL)
+		sxe2_sess->type = conf->ipsec.tunnel.type;
+
+	if (conf->ipsec.options.udp_encap) {
+		sxe2_sess->udp_cap.enabled = true;
+		memcpy(&sxe2_sess->udp_cap.value, &conf->ipsec.udp,
+			sizeof(struct rte_security_ipsec_udp_param));
+	}
+
+	sxe2_sess->pkt_metadata_template.sa_idx = sa_id;
+	sxe2_sess->pkt_metadata_template.ol_flags |= SXE2_IPSEC_OL_FLAGS_IS_TUN;
+	sxe2_sess->pkt_metadata_template.ol_flags |= SXE2_IPSEC_OL_FLAGS_IS_ESP;
+
+	if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_EGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		cipher_algo = conf->crypto_xform->cipher.algo;
+		sxe2_sess->pkt_metadata_template.algo = sxe2_ipsec_algo_gen(cipher_algo);
+		if (conf->crypto_xform->next)
+			sxe2_sess->pkt_metadata_template.mode = SXE2_IPSEC_MODE_ENC_AND_AUTH;
+		else
+			sxe2_sess->pkt_metadata_template.mode = SXE2_IPSEC_MODE_ONLY_ENCRYPT;
+	}
+
+	PMD_LOG_INFO(DRV,
+		"Save security info to session ctx, said:%u, spi:%u, mode:%u, algo:%u",
+		sa_id, sxe2_sess->sa.spi,
+		sxe2_sess->pkt_metadata_template.mode,
+		sxe2_sess->pkt_metadata_template.algo);
+}
+
+static void
+sxe2_ipsec_tx_sa_fill(struct sxe2_ipsec_tx_sa *tx_sa,
+		      struct rte_security_session_conf *conf)
+{
+	uint8_t *dst = NULL;
+	uint8_t len  = 0;
+
+	memcpy(&tx_sa->xform, &conf->ipsec, sizeof(struct rte_security_ipsec_xform));
+
+	if (conf->crypto_xform->next)
+		tx_sa->mode = SXE2_IPSEC_MODE_ENC_AND_AUTH;
+	else
+		tx_sa->mode = SXE2_IPSEC_MODE_ONLY_ENCRYPT;
+
+	if (conf->crypto_xform->cipher.algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC)
+		tx_sa->algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+	else
+		tx_sa->algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+
+	dst = tx_sa->enc_key;
+	len = conf->crypto_xform->cipher.key.length;
+	memcpy(dst, conf->crypto_xform->cipher.key.data, len);
+
+	if (conf->crypto_xform->next) {
+		dst = tx_sa->auth_key;
+		len = conf->crypto_xform->next->auth.key.length;
+		memcpy(dst, conf->crypto_xform->next->auth.key.data, len);
+	}
+}
+
+static int32_t
+sxe2_ipsec_tx_sa_add(struct sxe2_security_ctx *sxe2_sctx,
+		     struct rte_security_session_conf *conf,
+		     struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_tx_sa *tx_sa = NULL;
+	struct rte_bitmap *bmp          = sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp;
+	uint16_t bits                        = sxe2_sctx->ipsec_ctx.max_tx_sa;
+	uint16_t index                       = 0xFFFF;
+	int32_t ret                         = -1;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	index = sxe2_ipsec_id_alloc(bmp, bits);
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+	if (index == 0xFFFF) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec tx sa index.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	tx_sa = &sxe2_sctx->ipsec_ctx.tx_sa[index];
+
+	sxe2_ipsec_tx_sa_fill(tx_sa, conf);
+
+	ret = sxe2_drv_ipsec_txsa_add(sxe2_sctx->adapter, tx_sa);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to add tx sa.");
+		ret = -EIO;
+		rte_spinlock_lock(&sxe2_sctx->security_lock);
+		sxe2_ipsec_id_free(bmp, index);
+		rte_spinlock_unlock(&sxe2_sctx->security_lock);
+		goto l_end;
+	}
+
+	sxe2_ipsec_session_save(sxe2_sctx, conf, sxe2_sess, tx_sa->hw_sa_id, tx_sa->id);
+
+	PMD_LOG_INFO(DRV, "Add tx sa success, tx sa id: %u, index: %u.",
+		tx_sa->hw_sa_id, tx_sa->id);
+
+l_end:
+	return ret;
+}
+
+static uint16_t
+sxe2_ipsec_tcam_id_find(struct sxe2_ipsec_rx_tcam *rx_tcam,
+			struct rte_security_ipsec_tunnel_param tunnel, uint16_t len)
+{
+	struct sxe2_ipsec_rx_tcam *per = NULL;
+	uint16_t tcam_id = 0XFFFF;
+	uint16_t i       = 0;
+
+	for (i = 0; i < len; i++) {
+		per = &rx_tcam[i];
+		if (per->ip_addr.type == tunnel.type) {
+			if (tunnel.type == RTE_SECURITY_IPSEC_TUNNEL_IPV4 &&
+			per->ip_addr.dst_ipv4 == (uint32_t)tunnel.ipv4.dst_ip.s_addr) {
+				tcam_id = i;
+				goto l_end;
+			}
+			if (tunnel.type == RTE_SECURITY_IPSEC_TUNNEL_IPV6) {
+				if (!memcmp(&tunnel.ipv6, &per->ip_addr.dst_ipv6,
+				sizeof(tunnel.ipv6))) {
+					tcam_id = i;
+					goto l_end;
+				}
+			}
+		}
+	}
+
+l_end:
+	return tcam_id;
+}
+
+static uint16_t
+sxe2_ipsec_group_id_find(struct sxe2_ipsec_rx_udp_group *rx_udp_group,
+			 uint16_t udp_port, uint8_t sport_en, uint8_t dport_en, uint16_t len)
+{
+	struct sxe2_ipsec_rx_udp_group *per = NULL;
+	uint16_t group_id = 0XFFFF;
+	uint16_t i;
+
+	for (i = 0; i < len; i++) {
+		per = &rx_udp_group[i];
+		if (per->udp_port == udp_port && per->sport_en == sport_en &&
+			per->dport_en == dport_en) {
+			group_id = i;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return group_id;
+}
+
+static void
+sxe2_ipsec_rx_sa_fill(struct sxe2_ipsec_rx_sa *rx_sa,
+		      struct rte_security_session_conf *conf)
+{
+	uint8_t *dst = NULL;
+	uint8_t len = 0;
+
+	memcpy(&rx_sa->xform, &conf->ipsec, sizeof(struct rte_security_ipsec_xform));
+
+	if (conf->crypto_xform->next)
+		rx_sa->mode = SXE2_IPSEC_MODE_ENC_AND_AUTH;
+	else
+		rx_sa->mode = SXE2_IPSEC_MODE_ONLY_ENCRYPT;
+
+	if (conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		if (conf->crypto_xform->cipher.algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC)
+			rx_sa->algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+		else
+			rx_sa->algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+	} else {
+		if (conf->crypto_xform->auth.algo == SXE2_RTE_CRYPTO_AUTH_SM3_HMAC)
+			rx_sa->algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+		else
+			rx_sa->algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+	}
+
+	if (conf->crypto_xform->next) {
+		dst = rx_sa->auth_key;
+		len = conf->crypto_xform->auth.key.length;
+		memcpy(dst, conf->crypto_xform->auth.key.data, len);
+
+		dst = rx_sa->enc_key;
+		len = conf->crypto_xform->next->cipher.key.length;
+		memcpy(dst, conf->crypto_xform->next->cipher.key.data, len);
+	} else {
+		dst = rx_sa->enc_key;
+		len = conf->crypto_xform->cipher.key.length;
+		memcpy(dst, conf->crypto_xform->cipher.key.data, len);
+	}
+
+	rx_sa->spi = conf->ipsec.spi;
+}
+
+static int32_t
+sxe2_ipsec_rx_tcam_fill(struct sxe2_security_ctx *sxe2_sctx, uint16_t *tcam_id,
+			struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+	uint16_t len = sxe2_sctx->ipsec_ctx.max_tcam;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+
+	*tcam_id = sxe2_ipsec_tcam_id_find(sxe2_sctx->ipsec_ctx.rx_tcam,
+			conf->ipsec.tunnel, len);
+	if (*tcam_id == 0XFFFF) {
+		*tcam_id = sxe2_ipsec_id_alloc(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp, len);
+		if (*tcam_id == 0xFFFF) {
+			ret = -ENOMEM;
+			goto l_end;
+		}
+		rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[*tcam_id];
+
+		rx_tcam->ip_addr.type = conf->ipsec.tunnel.type;
+		if (rx_tcam->ip_addr.type == RTE_SECURITY_IPSEC_TUNNEL_IPV4) {
+			rx_tcam->ip_addr.dst_ipv4 = (uint32_t)conf->ipsec.tunnel.ipv4.dst_ip.s_addr;
+		} else {
+			memcpy(&rx_tcam->ip_addr.dst_ipv6, &conf->ipsec.tunnel.ipv6.dst_addr,
+				sizeof(rx_tcam->ip_addr.dst_ipv6));
+		}
+	} else {
+		rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[*tcam_id];
+	}
+	rx_tcam->ref_cnt++;
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_rx_udp_group_fill(struct sxe2_security_ctx *sxe2_sctx, uint16_t *udp_group_id,
+			     struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+	uint16_t len = sxe2_sctx->ipsec_ctx.max_udp_group;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = NULL;
+	uint8_t sport_en = 0;
+	uint8_t dport_en = 0;
+	uint16_t udp_port = 0;
+
+	if (!conf->ipsec.options.udp_encap) {
+		ret = 0;
+		goto l_end;
+	}
+
+	if (conf->ipsec.udp.sport) {
+		sport_en = 1;
+		udp_port = conf->ipsec.udp.sport;
+	} else {
+		sport_en = 0;
+	}
+	if (conf->ipsec.udp.dport) {
+		dport_en = 1;
+		udp_port = conf->ipsec.udp.dport;
+	} else {
+		dport_en = 0;
+	}
+
+	*udp_group_id = sxe2_ipsec_group_id_find(sxe2_sctx->ipsec_ctx.rx_udp_group,
+			udp_port, sport_en, dport_en, len);
+	if (*udp_group_id == 0XFFFF) {
+		*udp_group_id = sxe2_ipsec_id_alloc(sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp, len);
+		if (*udp_group_id == 0xFFFF) {
+			ret = -ENOMEM;
+			goto l_end;
+		}
+		rx_udp_group = &sxe2_sctx->ipsec_ctx.rx_udp_group[*udp_group_id];
+		rx_udp_group->sport_en = sport_en;
+		rx_udp_group->dport_en = dport_en;
+		rx_udp_group->udp_port = udp_port;
+	} else {
+		rx_udp_group = &sxe2_sctx->ipsec_ctx.rx_udp_group[*udp_group_id];
+	}
+	rx_udp_group->ref_cnt++;
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_rx_sa_add(struct sxe2_security_ctx *sxe2_sctx,
+		     struct rte_security_session_conf *conf,
+		     struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+	struct sxe2_ipsec_rx_sa *rx_sa     = NULL;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = NULL;
+	struct rte_bitmap *rx_sa_bmp        = sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp;
+	struct rte_bitmap *rx_tcam_bmp      = sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp;
+	uint16_t sa_bits                         = sxe2_sctx->ipsec_ctx.max_rx_sa;
+	uint16_t sa_id                           = 0xFFFF;
+	uint16_t tcam_id                         = 0xFFFF;
+	uint16_t udp_group_id                    = 0xFFFF;
+	int32_t ret                             = -1;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	sa_id = sxe2_ipsec_id_alloc(rx_sa_bmp, sa_bits);
+	if (sa_id == 0xFFFF) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec rx sa index.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	rx_sa = &sxe2_sctx->ipsec_ctx.rx_sa[sa_id];
+	sxe2_ipsec_rx_sa_fill(rx_sa, conf);
+
+	ret = sxe2_ipsec_rx_tcam_fill(sxe2_sctx, &tcam_id, conf);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec rx tcam index.");
+		sxe2_ipsec_id_free(rx_sa_bmp, sa_id);
+		goto l_end;
+	}
+	rx_sa->tcam_id = tcam_id;
+	rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[tcam_id];
+
+	ret = sxe2_ipsec_rx_udp_group_fill(sxe2_sctx, &udp_group_id, conf);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec rx udp group index.");
+		sxe2_ipsec_id_free(rx_sa_bmp, sa_id);
+		sxe2_ipsec_id_free(rx_tcam_bmp, tcam_id);
+		goto l_end;
+	}
+
+	if (udp_group_id != 0XFFFF) {
+		rx_sa->udp_group_id = (uint8_t)udp_group_id;
+		rx_udp_group = &sxe2_sctx->ipsec_ctx.rx_udp_group[udp_group_id];
+	} else {
+		rx_sa->udp_group_id = 0XFF;
+	}
+
+	ret = sxe2_drv_ipsec_rxsa_add(sxe2_sctx->adapter, rx_sa, rx_tcam, rx_udp_group);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to add rx sa.");
+		sxe2_ipsec_id_free(rx_sa_bmp, sa_id);
+		rx_tcam->ref_cnt--;
+		if (rx_tcam->ref_cnt == 0)
+			sxe2_ipsec_id_free(rx_tcam_bmp, tcam_id);
+
+		if (rx_udp_group != NULL) {
+			rx_udp_group->ref_cnt--;
+			if (rx_udp_group->ref_cnt == 0)
+				sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp,
+						   udp_group_id);
+		}
+
+		ret = -EIO;
+		goto l_end;
+	}
+
+	sxe2_ipsec_session_save(sxe2_sctx, conf, sxe2_sess, rx_sa->hw_sa_id, rx_sa->id);
+
+	PMD_LOG_INFO(DRV, "Add rx sa success, rx sa id: %u, rx ip id: %u, group id: %u, index: %u.",
+				rx_sa->hw_sa_id, rx_sa->hw_ip_id, rx_sa->udp_group_id, rx_sa->id);
+
+l_end:
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_hw_table_add(struct sxe2_security_ctx *sxe2_sctx,
+			struct rte_security_session_conf *conf,
+			struct sxe2_security_session *sxe2_sess)
+{
+	int32_t ret = -1;
+
+	switch (conf->ipsec.direction) {
+	case RTE_SECURITY_IPSEC_SA_DIR_EGRESS:
+		ret = sxe2_ipsec_tx_sa_add(sxe2_sctx, conf, sxe2_sess);
+		break;
+	case RTE_SECURITY_IPSEC_SA_DIR_INGRESS:
+		ret = sxe2_ipsec_rx_sa_add(sxe2_sctx, conf, sxe2_sess);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid sa direction.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+int sxe2_ipsec_session_create(void *device,
+			      struct rte_security_session_conf *conf,
+			      struct sxe2_security_session *sxe2_sess)
+{
+	struct rte_eth_dev *eth_dev = (struct rte_eth_dev *)device;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(eth_dev);
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	int32_t ret = -1;
+
+	ret = sxe2_ipsec_session_conf_valid(sxe2_sctx, conf);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Input ipsec session conf invalid.");
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_hw_table_add(sxe2_sctx, conf, sxe2_sess);
+	if (ret)
+		goto l_end;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_tx_sa_delete(struct sxe2_security_ctx *sxe2_sctx,
+			struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_tx_sa *tx_sa = NULL;
+	uint16_t sa_id = sxe2_sess->sa.hw_idx;
+	uint16_t sw_sa_id = sxe2_sess->sa.sw_idx;
+	int32_t ret   = -1;
+
+	if (sw_sa_id >= sxe2_sctx->ipsec_ctx.max_tx_sa) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid sw sa id: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	if (!rte_bitmap_get(sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp, sw_sa_id)) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "bitmap not set, index: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	tx_sa = &sxe2_sctx->ipsec_ctx.tx_sa[sw_sa_id];
+
+	if (tx_sa->hw_sa_id != sa_id) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid hw sa id: %u != %u.", sa_id, tx_sa->hw_sa_id);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_ipsec_txsa_delete(sxe2_sctx->adapter, sa_id);
+	if (ret)
+		goto l_end;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp, sw_sa_id);
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_rx_sa_delete(struct sxe2_security_ctx *sxe2_sctx,
+			struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_rx_udp_group *rx_udp = NULL;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+	struct sxe2_ipsec_rx_sa *rx_sa = NULL;
+	uint16_t sa_id                            = sxe2_sess->sa.hw_idx;
+	uint16_t sw_sa_id                         = sxe2_sess->sa.sw_idx;
+	int32_t ret                              = -1;
+
+	if (sw_sa_id >= sxe2_sctx->ipsec_ctx.max_rx_sa) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid sw sa id: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	if (!rte_bitmap_get(sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp, sw_sa_id)) {
+		ret = 0;
+		PMD_LOG_INFO(DRV, "bitmap not set, id: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	rx_sa = &sxe2_sctx->ipsec_ctx.rx_sa[sw_sa_id];
+
+	if (rx_sa->hw_sa_id != sa_id) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid hw sa id: %u != %u.", sa_id, rx_sa->hw_sa_id);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_ipsec_rxsa_delete(sxe2_sctx->adapter, rx_sa);
+	if (ret)
+		goto l_end;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp, sw_sa_id);
+
+	rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[rx_sa->tcam_id];
+	rx_tcam->ref_cnt--;
+	if (rx_tcam->ref_cnt == 0)
+		sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp, rx_sa->tcam_id);
+
+	if (rx_sa->udp_group_id == 0xFF) {
+		PMD_LOG_INFO(DRV, "Not need to release udp group resource.");
+		rte_spinlock_unlock(&sxe2_sctx->security_lock);
+		goto l_end;
+	}
+	rx_udp = &sxe2_sctx->ipsec_ctx.rx_udp_group[rx_sa->udp_group_id];
+	rx_udp->ref_cnt--;
+	if (rx_udp->ref_cnt == 0)
+		sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp, rx_sa->udp_group_id);
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_hw_table_delete(struct sxe2_security_ctx *sxe2_sctx,
+			   struct sxe2_security_session *sxe2_sess)
+{
+	int32_t ret = -1;
+
+	switch (sxe2_sess->direction) {
+	case RTE_SECURITY_IPSEC_SA_DIR_EGRESS:
+		ret = sxe2_ipsec_tx_sa_delete(sxe2_sctx, sxe2_sess);
+		break;
+	case RTE_SECURITY_IPSEC_SA_DIR_INGRESS:
+		ret = sxe2_ipsec_rx_sa_delete(sxe2_sctx, sxe2_sess);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid sa direction.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+int sxe2_ipsec_session_destroy(void *device, struct rte_security_session *session)
+{
+	struct rte_eth_dev *eth_dev = (struct rte_eth_dev *)device;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(eth_dev);
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+	int32_t ret = -1;
+
+	if (unlikely(sxe2_sess == NULL || sxe2_sess->adapter != adapter)) {
+		PMD_LOG_ERR(DRV, "Invalid device adapter.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_hw_table_delete(sxe2_sctx, sxe2_sess);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to delete ipsec hw tables.");
+		goto l_end;
+	}
+
+	memset(sxe2_sess, 0, sizeof(struct sxe2_security_session));
+
+	PMD_LOG_INFO(DRV, "Delete ipsec session success, sa_id: %u, spi: %u.",
+			sxe2_sess->sa.hw_idx, sxe2_sess->sa.spi);
+
+l_end:
+	return ret;
+}
+
+int sxe2_ipsec_pkt_metadata_set(void *device, struct rte_security_session *session,
+				struct rte_mbuf *m, void *params)
+{
+	struct rte_eth_dev *eth_dev = (struct rte_eth_dev *)device;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(eth_dev);
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	struct sxe2_ipsec_pkt_metadata *md             = NULL;
+	uint16_t offset                                      = 0;
+	int32_t ret                                         = -1;
+
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+	if (unlikely(sxe2_sess == NULL || sxe2_sess->adapter != adapter)) {
+		PMD_LOG_ERR(DRV, "Invalid parameters.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	offset = ((struct sxe2_ipsec_metadata_params *)params)->esp_header_offset;
+	if (offset <= IPSEC_ESP_OFFSET_MIN || offset >= IPSEC_ESP_OFFSET_MAX) {
+		PMD_LOG_ERR(DRV, "Invalid esp header offset.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	md = RTE_MBUF_DYNFIELD(m, sxe2_sctx->ipsec_ctx.md_offset, struct sxe2_ipsec_pkt_metadata *);
+
+	memcpy(md, &sxe2_sess->pkt_metadata_template, sizeof(struct sxe2_ipsec_pkt_metadata));
+	md->esp_head_offset = offset;
+
+	PMD_LOG_INFO(DRV, "ipsec metadata set, offset:%u, said:%u, mode:%u, algo:%u.", offset,
+		sxe2_sess->pkt_metadata_template.sa_idx, sxe2_sess->pkt_metadata_template.mode,
+		sxe2_sess->pkt_metadata_template.algo);
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+int sxe2_ipsec_pkt_md_offset_get(struct sxe2_adapter *adapter)
+{
+	return adapter->security_ctx.ipsec_ctx.md_offset;
+}
+
+static void sxe2_ipsec_enc_aes_cbc_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+
+	cap->sym.cipher.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC;
+
+	cap->sym.cipher.block_size = SXE2_SECURITY_BLOCK_SIZE_16;
+
+	cap->sym.cipher.key_size.min = SXE2_IPSEC_AES_KEY_MIN;
+	cap->sym.cipher.key_size.max = SXE2_IPSEC_AES_KEY_MAX;
+	cap->sym.cipher.key_size.increment = SXE2_IPSEC_AES_KEY_INC;
+
+	cap->sym.cipher.iv_size.min = SXE2_IPSEC_AES_IV_MIN;
+	cap->sym.cipher.iv_size.max = SXE2_IPSEC_AES_IV_MAX;
+	cap->sym.cipher.iv_size.increment = SXE2_IPSEC_AES_IV_INC;
+
+	cap->sym.cipher.dataunit_set |= RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES;
+}
+
+static void sxe2_ipsec_enc_sm4_cbc_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+
+	cap->sym.cipher.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC;
+
+	cap->sym.cipher.block_size = SXE2_SECURITY_BLOCK_SIZE_16;
+
+	cap->sym.cipher.key_size.min = SXE2_IPSEC_SM4_KEY_MIN;
+	cap->sym.cipher.key_size.max = SXE2_IPSEC_SM4_KEY_MAX;
+	cap->sym.cipher.key_size.increment = SXE2_IPSEC_SM4_KEY_INC;
+
+	cap->sym.cipher.iv_size.min = SXE2_IPSEC_SM4_IV_MIN;
+	cap->sym.cipher.iv_size.max = SXE2_IPSEC_SM4_IV_MAX;
+	cap->sym.cipher.iv_size.increment = SXE2_IPSEC_SM4_IV_INC;
+
+	cap->sym.cipher.dataunit_set |= RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES;
+}
+
+static void sxe2_ipsec_auth_sha_hmac_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH;
+
+	cap->sym.auth.algo = SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC;
+
+	cap->sym.auth.block_size = SXE2_SECURITY_BLOCK_SIZE_64;
+
+	cap->sym.auth.key_size.min = SXE2_IPSEC_SHA_KEY_MIN;
+	cap->sym.auth.key_size.max = SXE2_IPSEC_SHA_KEY_MAX;
+	cap->sym.auth.key_size.increment = SXE2_IPSEC_SHA_KEY_INC;
+
+	cap->sym.auth.iv_size.min = SXE2_IPSEC_SHA_IV_MIN;
+	cap->sym.auth.iv_size.max = SXE2_IPSEC_SHA_IV_MAX;
+	cap->sym.auth.iv_size.increment = SXE2_IPSEC_SHA_IV_INC;
+
+	cap->sym.auth.digest_size.min = SXE2_IPSEC_SHA_DIGEST_MIN;
+	cap->sym.auth.digest_size.max = SXE2_IPSEC_SHA_DIGEST_MAX;
+	cap->sym.auth.digest_size.increment = SXE2_IPSEC_SHA_DIGEST_INC;
+
+	cap->sym.auth.aad_size.min = SXE2_IPSEC_AAD_MIN;
+	cap->sym.auth.aad_size.max = SXE2_IPSEC_AAD_MAX;
+	cap->sym.auth.aad_size.increment = SXE2_IPSEC_AAD_INC;
+}
+
+static void sxe2_ipsec_auth_sm3_hmac_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH;
+
+	cap->sym.auth.algo = SXE2_RTE_CRYPTO_AUTH_SM3_HMAC;
+
+	cap->sym.auth.block_size = SXE2_SECURITY_BLOCK_SIZE_64;
+
+	cap->sym.auth.key_size.min = SXE2_IPSEC_SM3_KEY_MIN;
+	cap->sym.auth.key_size.max = SXE2_IPSEC_SM3_KEY_MAX;
+	cap->sym.auth.key_size.increment = SXE2_IPSEC_SM3_KEY_INC;
+
+	cap->sym.auth.iv_size.min = SXE2_IPSEC_SM3_IV_MIN;
+	cap->sym.auth.iv_size.max = SXE2_IPSEC_SM3_IV_MAX;
+	cap->sym.auth.iv_size.increment = SXE2_IPSEC_SM3_IV_INC;
+
+	cap->sym.auth.digest_size.min = SXE2_IPSEC_SM3_DIGEST_MIN;
+	cap->sym.auth.digest_size.max = SXE2_IPSEC_SM3_DIGEST_MAX;
+	cap->sym.auth.digest_size.increment = SXE2_IPSEC_SM3_DIGEST_INC;
+
+	cap->sym.auth.aad_size.min = SXE2_IPSEC_AAD_MIN;
+	cap->sym.auth.aad_size.max = SXE2_IPSEC_AAD_MAX;
+	cap->sym.auth.aad_size.increment = SXE2_IPSEC_AAD_INC;
+}
+
+static int32_t
+sxe2_ipsec_capabilities_init(struct sxe2_security_ctx *sxe2_sctx)
+{
+	struct rte_cryptodev_capabilities *capabilities = NULL;
+	struct sxe2_security_capabilities *sxe2_cap   =
+			&sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC];
+	int32_t ret                                         = -1;
+	uint8_t index                                        = 0;
+
+	sxe2_cap->action = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO;
+	sxe2_cap->ipsec.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP;
+	sxe2_cap->ipsec.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL;
+	sxe2_cap->ipsec.options.stats = 1;
+
+	capabilities = rte_zmalloc("security_caps",
+				sizeof(struct rte_cryptodev_capabilities) * SXE2_IPSEC_CAP_MAX, 0);
+	if (capabilities == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	for (index = 0; index < SXE2_IPSEC_CAP_MAX; index++) {
+		capabilities[index].op = RTE_CRYPTO_OP_TYPE_SYMMETRIC;
+		switch (index) {
+		case SXE2_IPSEC_CAP_ENC_AES_CBC:
+			sxe2_ipsec_enc_aes_cbc_fill(&capabilities[index]);
+			break;
+		case SXE2_IPSEC_CAP_ENC_SM4_CBC:
+			sxe2_ipsec_enc_sm4_cbc_fill(&capabilities[index]);
+			break;
+		case SXE2_IPSEC_CAP_AUTH_SHA256_HMAC:
+			sxe2_ipsec_auth_sha_hmac_fill(&capabilities[index]);
+			break;
+		case SXE2_IPSEC_CAP_AUTH_SM3_HMAC:
+			sxe2_ipsec_auth_sm3_hmac_fill(&capabilities[index]);
+			break;
+		default:
+			break;
+		}
+	}
+
+	sxe2_cap->crypto_capabilities = capabilities;
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static void
+sxe2_ipsec_tx_sa_init(struct sxe2_ipsec_tx_sa *tx_sa, uint16_t len)
+{
+	struct sxe2_ipsec_tx_sa *per = NULL;
+	uint16_t i;
+
+	memset(tx_sa, 0, sizeof(struct sxe2_ipsec_tx_sa) * len);
+	for (i = 0; i < len; i++) {
+		per = &tx_sa[i];
+		per->id = i;
+	}
+}
+
+static void
+sxe2_ipsec_rx_sa_init(struct sxe2_ipsec_rx_sa *rx_sa, uint16_t len)
+{
+	struct sxe2_ipsec_rx_sa *per = NULL;
+	uint16_t i;
+
+	memset(rx_sa, 0, sizeof(struct sxe2_ipsec_rx_sa) * len);
+	for (i = 0; i < len; i++) {
+		per = &rx_sa[i];
+		per->id = i;
+	}
+}
+
+static void
+sxe2_ipsec_rx_tcam_init(struct sxe2_ipsec_rx_tcam *rx_tcam, uint16_t len)
+{
+	struct sxe2_ipsec_rx_tcam *per = NULL;
+	uint16_t i;
+
+	memset(rx_tcam, 0, sizeof(struct sxe2_ipsec_rx_tcam) * len);
+	for (i = 0; i < len; i++) {
+		per = &rx_tcam[i];
+		per->id = i;
+	}
+}
+
+static void
+sxe2_ipsec_rx_udp_group_init(struct sxe2_ipsec_rx_udp_group *rx_udp_group, uint16_t len)
+{
+	struct sxe2_ipsec_rx_udp_group *per = NULL;
+	uint16_t i;
+
+	memset(rx_udp_group, 0, sizeof(struct sxe2_ipsec_rx_udp_group) * len);
+	for (i = 0; i < len; i++) {
+		per = &rx_udp_group[i];
+		per->id = i;
+	}
+}
+
+static int32_t
+sxe2_ipsec_hw_table_init(struct sxe2_security_ctx *sxe2_sctx)
+{
+	struct sxe2_ipsec_tx_sa *tx_sa = NULL;
+	struct sxe2_ipsec_rx_sa *rx_sa = NULL;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = NULL;
+	uint16_t max_tx_sa = sxe2_sctx->ipsec_ctx.max_tx_sa;
+	uint16_t max_rx_sa = sxe2_sctx->ipsec_ctx.max_rx_sa;
+	uint16_t max_tcam  = sxe2_sctx->ipsec_ctx.max_tcam;
+	uint16_t max_udp_group  = sxe2_sctx->ipsec_ctx.max_udp_group;
+	int32_t ret       = -1;
+
+	tx_sa = rte_zmalloc("sxe2_ipsec_tx_sa", sizeof(struct sxe2_ipsec_tx_sa) * max_tx_sa, 0);
+	if (tx_sa == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_tx_sa_init(tx_sa, max_tx_sa);
+	sxe2_sctx->ipsec_ctx.tx_sa = tx_sa;
+
+	rx_sa = rte_zmalloc("sxe2_ipsec_rx_sa", sizeof(struct sxe2_ipsec_rx_sa) * max_rx_sa, 0);
+	if (rx_sa == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_rx_sa_init(rx_sa, max_rx_sa);
+	sxe2_sctx->ipsec_ctx.rx_sa = rx_sa;
+
+	rx_tcam = rte_zmalloc("sxe2_ipsec_rx_tcam",
+				sizeof(struct sxe2_ipsec_rx_tcam) * max_tcam, 0);
+	if (rx_tcam == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_rx_tcam_init(rx_tcam, max_tcam);
+	sxe2_sctx->ipsec_ctx.rx_tcam = rx_tcam;
+
+	rx_udp_group = rte_zmalloc("sxe2_ipsec_rx_udp_group",
+				sizeof(struct sxe2_ipsec_rx_udp_group) * max_udp_group, 0);
+	if (rx_udp_group == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_rx_udp_group_init(rx_udp_group, max_udp_group);
+	sxe2_sctx->ipsec_ctx.rx_udp_group = rx_udp_group;
+
+	ret = 0;
+
+l_end:
+	if (ret) {
+		if (tx_sa != NULL) {
+			rte_free(tx_sa);
+			sxe2_sctx->ipsec_ctx.tx_sa = NULL;
+		}
+		if (rx_sa != NULL) {
+			rte_free(rx_sa);
+			sxe2_sctx->ipsec_ctx.rx_sa = NULL;
+		}
+		if (rx_tcam != NULL) {
+			rte_free(rx_tcam);
+			sxe2_sctx->ipsec_ctx.rx_tcam = NULL;
+		}
+		if (rx_udp_group != NULL) {
+			rte_free(rx_udp_group);
+			sxe2_sctx->ipsec_ctx.rx_udp_group = NULL;
+		}
+	}
+	return ret;
+}
+
+int32_t sxe2_ipsec_init(struct sxe2_adapter *adapter)
+{
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_capabilities *sxe2_cap = NULL;
+	int32_t ret                               = -1;
+	struct rte_mbuf_dynfield pkt_md_dynfield = {
+	.name = "sxe2_ipsec_pkt_metadata",
+		.size = sizeof(struct sxe2_ipsec_pkt_metadata),
+		.align = alignof(struct sxe2_ipsec_pkt_metadata)
+	};
+
+	PMD_LOG_INFO(INIT, "Init ipsec.");
+
+	sxe2_sctx->ipsec_ctx.md_offset = rte_mbuf_dynfield_register(&pkt_md_dynfield);
+	if (sxe2_sctx->ipsec_ctx.md_offset < 0) {
+		PMD_LOG_ERR(INIT, "Failed to register ipsec mbuf dynamic field.");
+		ret = -EIO;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_capabilities_init(sxe2_sctx);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init ipsec capabilities.");
+		goto l_end;
+	}
+
+	ret = sxe2_drv_ipsec_get_capa(adapter);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to get ipsec capabilities.");
+		goto l_caps_free;
+	}
+
+	ret = sxe2_ipsec_bitmap_init(sxe2_sctx);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init ipsec bitmap.");
+		goto l_caps_free;
+	}
+
+	ret = sxe2_ipsec_hw_table_init(sxe2_sctx);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init ipsec hw table.");
+		goto l_bitmap_free;
+	}
+
+	goto l_end;
+
+l_bitmap_free:
+
+	if (sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem = NULL;
+	}
+l_caps_free:
+	sxe2_cap = &sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC];
+	if (sxe2_cap->crypto_capabilities != NULL) {
+		rte_free(sxe2_cap->crypto_capabilities);
+		sxe2_cap->crypto_capabilities = NULL;
+	}
+l_end:
+	return ret;
+}
+
+void sxe2_ipsec_uinit(struct sxe2_adapter *adapter)
+{
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_capabilities *sxe2_cap   =
+			&sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC];
+	struct sxe2_ipsec_tx_sa *tx_sa = sxe2_sctx->ipsec_ctx.tx_sa;
+	struct sxe2_ipsec_rx_sa *rx_sa = sxe2_sctx->ipsec_ctx.rx_sa;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = sxe2_sctx->ipsec_ctx.rx_tcam;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = sxe2_sctx->ipsec_ctx.rx_udp_group;
+
+	PMD_LOG_INFO(INIT, "Uinit ipsec.");
+
+	(void)sxe2_drv_ipsec_resource_clear(adapter);
+
+	if (sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem = NULL;
+	}
+
+	if (tx_sa != NULL) {
+		rte_free(tx_sa);
+		sxe2_sctx->ipsec_ctx.tx_sa = NULL;
+	}
+	if (rx_sa != NULL) {
+		rte_free(rx_sa);
+		sxe2_sctx->ipsec_ctx.rx_sa = NULL;
+	}
+	if (rx_tcam != NULL) {
+		rte_free(rx_tcam);
+		sxe2_sctx->ipsec_ctx.rx_tcam = NULL;
+	}
+	if (rx_udp_group != NULL) {
+		rte_free(rx_udp_group);
+		sxe2_sctx->ipsec_ctx.rx_udp_group = NULL;
+	}
+
+	if (sxe2_cap->crypto_capabilities != NULL) {
+		rte_free(sxe2_cap->crypto_capabilities);
+		sxe2_cap->crypto_capabilities = NULL;
+	}
+}
diff --git a/drivers/net/sxe2/sxe2_ipsec.h b/drivers/net/sxe2/sxe2_ipsec.h
new file mode 100644
index 0000000000..02930ddb4f
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_ipsec.h
@@ -0,0 +1,254 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+#ifndef __SXE2_IPSEC_H__
+#define __SXE2_IPSEC_H__
+
+#include <rte_security.h>
+#include <rte_security_driver.h>
+
+struct sxe2_adapter;
+struct sxe2_security_session;
+
+#define        SXE2_IPSEC_AES_KEY_MIN    (32)
+#define        SXE2_IPSEC_AES_KEY_MAX    (32)
+#define        SXE2_IPSEC_AES_KEY_INC    (0)
+
+#define        SXE2_IPSEC_SM4_KEY_MIN    (16)
+#define        SXE2_IPSEC_SM4_KEY_MAX    (16)
+#define        SXE2_IPSEC_SM4_KEY_INC    (0)
+
+#define        SXE2_IPSEC_SHA_KEY_MIN    (32)
+#define        SXE2_IPSEC_SHA_KEY_MAX    (32)
+#define        SXE2_IPSEC_SHA_KEY_INC    (0)
+
+#define        SXE2_IPSEC_SM3_KEY_MIN    (32)
+#define        SXE2_IPSEC_SM3_KEY_MAX    (32)
+#define        SXE2_IPSEC_SM3_KEY_INC    (0)
+
+#define        SXE2_IPSEC_AES_IV_MIN    (16)
+#define        SXE2_IPSEC_AES_IV_MAX    (16)
+#define        SXE2_IPSEC_AES_IV_INC    (0)
+
+#define        SXE2_IPSEC_SM4_IV_MIN    (16)
+#define        SXE2_IPSEC_SM4_IV_MAX    (16)
+#define        SXE2_IPSEC_SM4_IV_INC    (0)
+
+#define        SXE2_IPSEC_SHA_IV_MIN    (0)
+#define        SXE2_IPSEC_SHA_IV_MAX    (32)
+#define        SXE2_IPSEC_SHA_IV_INC    (16)
+
+#define        SXE2_IPSEC_SM3_IV_MIN    (0)
+#define        SXE2_IPSEC_SM3_IV_MAX    (32)
+#define        SXE2_IPSEC_SM3_IV_INC    (16)
+
+#define        SXE2_IPSEC_SHA_DIGEST_MIN    (32)
+#define        SXE2_IPSEC_SHA_DIGEST_MAX    (32)
+#define        SXE2_IPSEC_SHA_DIGEST_INC    (0)
+
+#define        SXE2_IPSEC_SM3_DIGEST_MIN    (32)
+#define        SXE2_IPSEC_SM3_DIGEST_MAX    (32)
+#define        SXE2_IPSEC_SM3_DIGEST_INC    (0)
+
+#define        SXE2_IPSEC_AAD_MIN           (0)
+#define        SXE2_IPSEC_AAD_MAX           (0)
+#define        SXE2_IPSEC_AAD_INC           (0)
+
+#define        SXE2_IPSEC_MAX_KEY_LEN		 (32)
+#define        SXE2_IPSEC_MIN_KEY_LEN       (0)
+
+#define SXE2_IPSEC_OL_FLAGS_IS_TUN    (0x1 << 0)
+#define SXE2_IPSEC_OL_FLAGS_IS_ESP    (0x1 << 1)
+
+#define SXE2_IPSEC_DEFAULT_SA_OFFSET  (0)
+#define SXE2_IPSEC_DEFAULT_SA_LEN     (1024)
+
+#define IPSEC_TX_ENCRYPT    (RTE_BIT32(0))
+#define IPSEC_TX_ENGINE_SM4 (RTE_BIT32(1))
+
+#define IPSEC_RX_VALID      (RTE_BIT32(0))
+#define IPSEC_RX_IPV6       (RTE_BIT32(2))
+#define IPSEC_RX_DECRYPT    (RTE_BIT32(3))
+#define IPSEC_RX_ENGINE_SM4 (RTE_BIT32(4))
+
+#define IPSEC_IPV6_LEN                 (4)
+#define IPSEC_ESP_OFFSET_MIN           (16)
+#define IPSEC_ESP_OFFSET_MAX           (256)
+
+enum sxe2_ipsec_cap {
+	SXE2_IPSEC_CAP_ENC_AES_CBC      = 0,
+	SXE2_IPSEC_CAP_ENC_SM4_CBC      = 1,
+	SXE2_IPSEC_CAP_AUTH_SHA256_HMAC = 2,
+	SXE2_IPSEC_CAP_AUTH_SM3_HMAC    = 3,
+	SXE2_IPSEC_CAP_MAX              = 4,
+};
+
+enum sxe2_ipsec_icv_len {
+	SXE2_IPSEC_ICV_0_BYTES = 0,
+	SXE2_IPSEC_ICV_12_BYTES,
+	SXE2_IPSEC_ICV_16_BYTES,
+	SXE2_IPSEC_ICV_INVALID,
+};
+
+enum sxe2_ipsec_bypass_dir {
+	SXE2_IPSEC_BYPASS_DIR_RX = 0,
+	SXE2_IPSEC_BYPASS_DIR_TX,
+	SXE2_IPSEC_BYPASS_DIR_INVALID,
+};
+
+enum sxe2_ipsec_bypass_status {
+	SXE2_IPSEC_BYPASS_STATUS_DISABLE = 0,
+	SXE2_IPSEC_BYPASS_STATUS_ENABLE,
+	SXE2_IPSEC_BYPASS_STATUS_INVALID,
+};
+
+enum sxe2_ipsec_status {
+	SXE2_IPSEC_ENC_BYPASS = 0,
+	SXE2_IPSEC_ENC_ENABLE,
+	SXE2_IPSEC_ENC_INVALID,
+};
+
+enum sxe2_ipsec_mode {
+	SXE2_IPSEC_MODE_ENC_AND_AUTH = 0,
+	SXE2_IPSEC_MODE_ONLY_ENCRYPT,
+	SXE2_IPSEC_MODE_INVALID,
+};
+
+struct sxe2_ipsec_ip_param {
+	enum rte_security_ipsec_tunnel_type type;
+	union {
+		uint32_t dst_ipv4;
+		uint32_t dst_ipv6[IPSEC_IPV6_LEN];
+	};
+};
+
+enum sxe2_ipsec_algorithm {
+	SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC = 0,
+	SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC,
+	SXE2_IPSEC_ALGO_INVALID,
+};
+
+struct sxe2_ipsec_pkt_metadata {
+	uint16_t                  sa_idx;
+	uint16_t                  esp_head_offset;
+	uint8_t                   ol_flags;
+	uint8_t                   mode;
+	uint8_t                   algo;
+};
+
+struct sxe2_ipsec_bitmap {
+	struct rte_bitmap *tx_sa_bmp;
+	struct rte_bitmap *rx_sa_bmp;
+	struct rte_bitmap *rx_tcam_bmp;
+	struct rte_bitmap *rx_udp_bmp;
+	void *tx_sa_mem;
+	void *rx_sa_mem;
+	void *rx_tcam_mem;
+	void *rx_udp_mem;
+};
+
+struct sxe2_ipsec_security_sa {
+	uint32_t spi;
+	uint16_t hw_idx;
+	uint16_t sw_idx;
+};
+
+struct sxe2_ipsec_esn {
+	union {
+		uint64_t value;
+		struct {
+			uint32_t hi;
+			uint32_t low;
+		};
+	};
+	uint8_t enabled;
+};
+
+struct sxe2_ipsec_udp {
+	struct rte_security_ipsec_udp_param value;
+	uint8_t enabled;
+};
+
+struct sxe2_ipsec_tx_sa {
+	struct rte_security_ipsec_xform xform;
+	uint16_t id;
+	uint16_t hw_sa_id;
+	enum sxe2_ipsec_mode mode;
+	enum sxe2_ipsec_algorithm algo;
+	uint8_t enc_key[SXE2_IPSEC_MAX_KEY_LEN];
+	uint8_t auth_key[SXE2_IPSEC_MAX_KEY_LEN];
+};
+
+struct sxe2_ipsec_rx_sa {
+	struct rte_security_ipsec_xform xform;
+	uint32_t spi;
+	uint16_t id;
+	uint16_t hw_sa_id;
+	uint8_t hw_ip_id;
+	uint8_t hw_udp_group_id;
+	uint8_t tcam_id;
+	uint8_t udp_group_id;
+	uint8_t sdn_group_id;
+	enum sxe2_ipsec_mode mode;
+	enum sxe2_ipsec_algorithm algo;
+	uint8_t enc_key[SXE2_IPSEC_MAX_KEY_LEN];
+	uint8_t auth_key[SXE2_IPSEC_MAX_KEY_LEN];
+};
+
+struct sxe2_ipsec_rx_tcam {
+	struct sxe2_ipsec_ip_param ip_addr;
+	uint16_t id;
+	uint8_t hw_ip_id;
+	uint8_t ref_cnt;
+};
+
+struct sxe2_ipsec_rx_udp_group {
+	uint16_t udp_port;
+	uint8_t sport_en;
+	uint8_t dport_en;
+	uint8_t id;
+	uint8_t hw_group_id;
+	uint8_t ref_cnt;
+};
+
+struct sxe2_ipsec_ctx {
+	struct sxe2_ipsec_tx_sa             *tx_sa;
+	struct sxe2_ipsec_rx_sa             *rx_sa;
+	struct sxe2_ipsec_rx_tcam           *rx_tcam;
+	struct sxe2_ipsec_rx_udp_group      *rx_udp_group;
+	struct sxe2_ipsec_bitmap            bmp;
+	int                                  md_offset;
+	uint16_t                                  max_tx_sa;
+	uint16_t                                  max_rx_sa;
+	uint16_t                                  max_tcam;
+	uint8_t                                   max_udp_group;
+};
+
+struct sxe2_ipsec_metadata_params {
+	uint16_t esp_header_offset;
+	uint16_t reserved;
+};
+
+bool sxe2_ipsec_supported(struct sxe2_adapter *adapter);
+
+bool sxe2_ipsec_valid_tx_offloads(uint64_t offloads);
+
+bool sxe2_ipsec_valid_rx_offloads(uint64_t offloads);
+
+int sxe2_ipsec_pkt_md_offset_get(struct sxe2_adapter *adapter);
+
+int sxe2_ipsec_session_create(void *device,
+			      struct rte_security_session_conf *conf,
+			      struct sxe2_security_session *sxe2_sess);
+
+int sxe2_ipsec_session_destroy(void *device,
+		struct rte_security_session *session);
+
+int sxe2_ipsec_pkt_metadata_set(void *device, struct rte_security_session *session,
+				struct rte_mbuf *m, void *params);
+
+int32_t sxe2_ipsec_init(struct sxe2_adapter *adapter);
+
+void sxe2_ipsec_uinit(struct sxe2_adapter *adapter);
+
+#endif /* __SXE2_IPSEC_H__ */
diff --git a/drivers/net/sxe2/sxe2_rx.c b/drivers/net/sxe2/sxe2_rx.c
index 28832d5f71..007192c7d8 100644
--- a/drivers/net/sxe2/sxe2_rx.c
+++ b/drivers/net/sxe2/sxe2_rx.c
@@ -294,6 +294,11 @@ int32_t __rte_cold sxe2_rx_queue_setup(struct rte_eth_dev *dev,
 		goto l_end;
 	}
 
+	if (!sxe2_ipsec_valid_rx_offloads(offloads)) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
 	rxq = sxe2_rx_queue_alloc(dev, queue_idx, nb_desc, socket_id);
 	if (rxq == NULL) {
 		PMD_LOG_ERR(RX, "rx queue[%d] resource alloc failed", queue_idx);
diff --git a/drivers/net/sxe2/sxe2_security.c b/drivers/net/sxe2/sxe2_security.c
new file mode 100644
index 0000000000..bc59d1b880
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_security.c
@@ -0,0 +1,335 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_malloc.h>
+
+#include "sxe2_ethdev.h"
+#include "sxe2_security.h"
+#include "sxe2_ipsec.h"
+#include "sxe2_common_log.h"
+
+static unsigned int
+sxe2_security_session_size_get(void *device __rte_unused)
+{
+	return sizeof(struct sxe2_security_session);
+}
+
+static int
+sxe2_security_session_create(void *device,
+			     struct rte_security_session_conf *conf,
+			     struct rte_security_session *session)
+{
+	int32_t ret = -1;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+
+	switch (conf->protocol) {
+	case RTE_SECURITY_PROTOCOL_IPSEC:
+		ret = sxe2_ipsec_session_create(device, conf, sxe2_sess);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid security protocol.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int
+sxe2_security_session_destroy(void *device, struct rte_security_session *session)
+{
+	int32_t ret = -1;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+
+	switch (sxe2_sess->protocol) {
+	case RTE_SECURITY_PROTOCOL_IPSEC:
+		ret = sxe2_ipsec_session_destroy(device, session);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid security protocol.");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int
+sxe2_security_pkt_metadata_set(void *device,
+			       struct rte_security_session *session,
+			       struct rte_mbuf *m, void *params)
+{
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+	int32_t ret = -1;
+
+	switch (sxe2_sess->protocol) {
+	case RTE_SECURITY_PROTOCOL_IPSEC:
+		ret = sxe2_ipsec_pkt_metadata_set(device, session, m, params);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid security protocol.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct rte_security_capability *
+sxe2_security_capabilities_get(void *device __rte_unused)
+{
+	static const struct rte_cryptodev_capabilities
+	ipsec_crypto_capabilities[] = {
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER,
+				{.cipher = {
+					.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_16,
+					.key_size = {
+						.min = SXE2_IPSEC_AES_KEY_MIN,
+						.max = SXE2_IPSEC_AES_KEY_MAX,
+						.increment = SXE2_IPSEC_AES_KEY_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_AES_IV_MIN,
+						.max = SXE2_IPSEC_AES_IV_MAX,
+						.increment = SXE2_IPSEC_AES_IV_INC
+					},
+					.dataunit_set = RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES,
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER,
+				{.cipher = {
+					.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_16,
+					.key_size = {
+						.min = SXE2_IPSEC_SM4_KEY_MIN,
+						.max = SXE2_IPSEC_SM4_KEY_MAX,
+						.increment = SXE2_IPSEC_SM4_KEY_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_SM4_IV_MIN,
+						.max = SXE2_IPSEC_SM4_IV_MAX,
+						.increment = SXE2_IPSEC_SM4_IV_INC
+					},
+					.dataunit_set = RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES,
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH,
+				{.auth = {
+					.algo = SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_64,
+					.key_size = {
+						.min = SXE2_IPSEC_SHA_KEY_MIN,
+						.max = SXE2_IPSEC_SHA_KEY_MAX,
+						.increment = SXE2_IPSEC_SHA_KEY_INC
+					},
+					.digest_size = {
+						.min = SXE2_IPSEC_SHA_DIGEST_MIN,
+						.max = SXE2_IPSEC_SHA_DIGEST_MAX,
+						.increment = SXE2_IPSEC_SHA_DIGEST_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_SHA_IV_MIN,
+						.max = SXE2_IPSEC_SHA_IV_MAX,
+						.increment = SXE2_IPSEC_SHA_IV_INC
+					},
+					.aad_size = {
+						.min = SXE2_IPSEC_AAD_MIN,
+						.max = SXE2_IPSEC_AAD_MAX,
+						.increment = SXE2_IPSEC_AAD_INC
+					}
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH,
+				{.auth = {
+					.algo = SXE2_RTE_CRYPTO_AUTH_SM3_HMAC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_64,
+					.key_size = {
+						.min = SXE2_IPSEC_SM3_KEY_MIN,
+						.max = SXE2_IPSEC_SM3_KEY_MAX,
+						.increment = SXE2_IPSEC_SM3_KEY_INC
+					},
+					.digest_size = {
+						.min = SXE2_IPSEC_SM3_DIGEST_MIN,
+						.max = SXE2_IPSEC_SM3_DIGEST_MAX,
+						.increment = SXE2_IPSEC_SM3_DIGEST_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_SM3_IV_MIN,
+						.max = SXE2_IPSEC_SM3_IV_MAX,
+						.increment = SXE2_IPSEC_SM3_IV_INC
+					},
+					.aad_size = {
+						.min = SXE2_IPSEC_AAD_MIN,
+						.max = SXE2_IPSEC_AAD_MAX,
+						.increment = SXE2_IPSEC_AAD_INC
+					}
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_UNDEFINED,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_NOT_SPECIFIED
+			}, }
+		}
+	};
+
+	static const struct rte_security_capability
+	sxe2_security_capabilities[] = {
+		{
+			.action = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO,
+			.protocol = RTE_SECURITY_PROTOCOL_IPSEC,
+			{.ipsec = {
+				.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP,
+				.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL,
+				.direction = RTE_SECURITY_IPSEC_SA_DIR_EGRESS,
+				.options = {
+					.esn = 0,
+					.udp_encap = 1,
+					.copy_dscp = 0,
+					.copy_flabel = 0,
+					.copy_df = 0,
+					.dec_ttl = 0,
+					.ecn = 0,
+					.stats = 1,
+					.iv_gen_disable = 0,
+					.tunnel_hdr_verify = 1,
+					.udp_ports_verify = 1,
+					.ip_csum_enable = 0,
+					.l4_csum_enable = 0,
+					.ip_reassembly_en = 0,
+					.ingress_oop = 0
+			} } },
+			.crypto_capabilities = ipsec_crypto_capabilities,
+			.ol_flags = RTE_SECURITY_TX_OLOAD_NEED_MDATA
+		},
+		{
+			.action = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO,
+			.protocol = RTE_SECURITY_PROTOCOL_IPSEC,
+			{.ipsec = {
+				.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP,
+				.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL,
+				.direction = RTE_SECURITY_IPSEC_SA_DIR_INGRESS,
+				.options = {
+					.esn = 0,
+					.udp_encap = 1,
+					.copy_dscp = 0,
+					.copy_flabel = 0,
+					.copy_df = 0,
+					.dec_ttl = 0,
+					.ecn = 0,
+					.stats = 1,
+					.iv_gen_disable = 0,
+					.tunnel_hdr_verify = 1,
+					.udp_ports_verify = 1,
+					.ip_csum_enable = 0,
+					.l4_csum_enable = 0,
+					.ip_reassembly_en = 0,
+					.ingress_oop = 0
+			} } },
+			.crypto_capabilities = ipsec_crypto_capabilities,
+			.ol_flags = 0
+		},
+		{
+			.action = RTE_SECURITY_ACTION_TYPE_NONE
+		}
+	};
+
+	return sxe2_security_capabilities;
+}
+
+static struct rte_security_ops sxe2_security_ops = {
+	.session_get_size		= sxe2_security_session_size_get,
+	.session_create			= sxe2_security_session_create,
+	.session_destroy		= sxe2_security_session_destroy,
+	.set_pkt_metadata		= sxe2_security_pkt_metadata_set,
+	.capabilities_get		= sxe2_security_capabilities_get,
+};
+
+int32_t sxe2_security_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_security_ctx *sctx = NULL;
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	int32_t ret = -1;
+
+	if (!sxe2_ipsec_supported(adapter)) {
+		ret = 0;
+		PMD_LOG_INFO(INIT, "Not support security feature.");
+		goto l_end;
+	}
+
+	PMD_LOG_INFO(INIT, "Init security feature.");
+
+	sctx = rte_zmalloc("security_ctx", sizeof(struct rte_security_ctx), 0);
+	if (sctx == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	sctx->device = dev;
+	sctx->ops = &sxe2_security_ops;
+	sctx->sess_cnt = 0;
+	sctx->flags = 0;
+	dev->security_ctx = (void *)sctx;
+
+	rte_spinlock_init(&sxe2_sctx->security_lock);
+	sxe2_sctx->adapter = adapter;
+
+	if (sxe2_ipsec_supported(adapter)) {
+		ret = sxe2_ipsec_init(adapter);
+		if (ret) {
+			rte_free(sctx);
+			sctx = NULL;
+			dev->security_ctx = NULL;
+			goto l_end;
+		}
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+void sxe2_security_uinit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_security_ctx *sctx = dev->security_ctx;
+
+	if (!sxe2_ipsec_supported(adapter)) {
+		PMD_LOG_INFO(INIT, "Not support security feature.");
+		goto l_end;
+	}
+
+	PMD_LOG_INFO(INIT, "Uinit security feature.");
+
+	if (sctx != NULL) {
+		rte_free(sctx);
+		sctx = NULL;
+	}
+
+	sxe2_ipsec_uinit(adapter);
+
+l_end:
+	return;
+}
diff --git a/drivers/net/sxe2/sxe2_security.h b/drivers/net/sxe2/sxe2_security.h
new file mode 100644
index 0000000000..366c0614bd
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_security.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_SECURITY_H__
+#define __SXE2_SECURITY_H__
+
+#include <rte_security.h>
+#include <rte_cryptodev.h>
+#include <rte_security_driver.h>
+
+#include "sxe2_ipsec.h"
+
+#define SXE2_DEV_TO_SECURITY(eth) \
+	((struct rte_security_ctx *)(((struct rte_eth_dev *)eth)->security_ctx))
+
+#define SXE2_RTE_CRYPTO_CIPHER_AES_CBC   (RTE_CRYPTO_CIPHER_AES_CBC)
+
+#define SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC   (RTE_CRYPTO_CIPHER_SM4_CBC)
+
+#define SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC  (RTE_CRYPTO_AUTH_SHA256_HMAC)
+
+#define SXE2_RTE_CRYPTO_AUTH_SM3_HMAC   (RTE_CRYPTO_AUTH_SM3_HMAC)
+
+enum sxe2_security_protocol {
+	SXE2_SECURITY_PROTOCOL_IPSEC       = 0,
+	SXE2_SECURITY_PROTOCOL_MAX         = 1,
+};
+
+enum sxe2_security_xform {
+	SXE2_SECURITY_IPSEC_EN       = 0,
+	SXE2_SECURITY_IPSEC_DE       = 1,
+	SXE2_SECURITY_NUM_MAX        = 2,
+};
+
+enum sxe2_security_block_size {
+	SXE2_SECURITY_BLOCK_SIZE_16        = 16,
+	SXE2_SECURITY_BLOCK_SIZE_64        = 64,
+};
+
+struct sxe2_security_ipsec_caps {
+	enum rte_security_ipsec_sa_protocol   proto;
+	enum rte_security_ipsec_sa_mode       mode;
+	struct rte_security_ipsec_sa_options  options;
+};
+
+struct sxe2_security_capabilities {
+	struct rte_cryptodev_capabilities     *crypto_capabilities;
+	enum rte_security_session_action_type action;
+	struct sxe2_security_ipsec_caps      ipsec;
+};
+
+struct sxe2_security_session {
+	struct sxe2_adapter                   *adapter;
+	struct sxe2_ipsec_pkt_metadata        pkt_metadata_template;
+	struct sxe2_ipsec_security_sa         sa;
+	struct sxe2_ipsec_esn                 esn;
+	struct sxe2_ipsec_udp                 udp_cap;
+	enum rte_security_session_protocol     protocol;
+	enum rte_security_ipsec_sa_direction   direction;
+	enum rte_security_ipsec_sa_mode        mode;
+	enum rte_security_ipsec_sa_protocol    sa_proto;
+	enum rte_security_ipsec_tunnel_type    type;
+};
+
+struct sxe2_security_ctx {
+	struct sxe2_adapter                 *adapter;
+	struct sxe2_security_capabilities   sxe2_capabilities[SXE2_SECURITY_PROTOCOL_MAX];
+	struct sxe2_ipsec_ctx               ipsec_ctx;
+	rte_spinlock_t                       security_lock;
+};
+
+int32_t sxe2_security_init(struct rte_eth_dev *dev);
+
+void sxe2_security_uinit(struct rte_eth_dev *dev);
+
+#endif /* __SXE2_SECURITY_H__ */
diff --git a/drivers/net/sxe2/sxe2_tx.c b/drivers/net/sxe2/sxe2_tx.c
index a280edc9c5..f49238ceef 100644
--- a/drivers/net/sxe2/sxe2_tx.c
+++ b/drivers/net/sxe2/sxe2_tx.c
@@ -304,6 +304,11 @@ int32_t __rte_cold sxe2_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	offloads = tx_conf->offloads | dev->data->dev_conf.txmode.offloads;
+	if (!sxe2_ipsec_valid_tx_offloads(offloads)) {
+		ret = -EINVAL;
+		goto end;
+	}
+
 	txq = sxe2_tx_queue_alloc(dev, queue_idx, nb_desc, socket_id);
 	if (txq == NULL) {
 		PMD_LOG_ERR(TX, "failed to alloc sxe2vf tx queue:%u resource", queue_idx);
@@ -327,6 +332,9 @@ int32_t __rte_cold sxe2_tx_queue_setup(struct rte_eth_dev *dev,
 	txq->ops               = sxe2_tx_default_ops_get();
 	txq->ops.queue_reset(txq);
 
+	if (sxe2_ipsec_supported(adapter) && txq->offloads & RTE_ETH_TX_OFFLOAD_SECURITY)
+		txq->ipsec_pkt_md_offset = sxe2_ipsec_pkt_md_offset_get(adapter);
+
 	dev->data->tx_queues[queue_idx] = txq;
 	ret = 0;
 
diff --git a/drivers/net/sxe2/sxe2_txrx_poll.c b/drivers/net/sxe2/sxe2_txrx_poll.c
index 3c6fe37404..8b6e585c36 100644
--- a/drivers/net/sxe2/sxe2_txrx_poll.c
+++ b/drivers/net/sxe2/sxe2_txrx_poll.c
@@ -307,6 +307,25 @@ static __rte_always_inline void sxe2_desc_tso_fill(struct rte_mbuf *tx_pkt,
 	return;
 }
 
+static __rte_always_inline void sxe2_desc_ipsec_fill(struct rte_mbuf *tx_pkt,
+			struct sxe2_tx_queue *txq, uint16_t *ipsec_offset,
+			uint64_t *desc_type_cmd_tso_mss)
+{
+	struct sxe2_ipsec_pkt_metadata *md = NULL;
+	uint16_t ipsec_pkt_md_offset = txq->ipsec_pkt_md_offset;
+
+	md = RTE_MBUF_DYNFIELD(tx_pkt, ipsec_pkt_md_offset, struct sxe2_ipsec_pkt_metadata *);
+	*ipsec_offset = md->esp_head_offset;
+	*desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_IPSEC_EN;
+	if (md->mode == SXE2_IPSEC_MODE_ONLY_ENCRYPT)
+		*desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_IPSEC_MODE;
+
+	if (md->algo == SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC)
+		*desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_IPSEC_ENGINE;
+
+	*desc_type_cmd_tso_mss |= (uint64_t)(md->sa_idx) << SXE2_TX_CTXT_DESC_IPSEC_SA_SHIFT;
+}
+
 static __rte_always_inline uint64_t
 sxe2_tx_data_desc_build_cobt(uint32_t cmd, uint32_t offset, uint16_t buf_size, uint16_t l2tag)
 {
@@ -426,6 +445,11 @@ uint16_t sxe2_tx_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 			else if (offloads & RTE_MBUF_F_TX_IEEE1588_TMST)
 				desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_CMD_TSYN_MASK;
 
+			if (offloads & RTE_MBUF_F_TX_SEC_OFFLOAD) {
+				sxe2_desc_ipsec_fill(tx_pkt, txq, &ipsec_offset,
+						     &desc_type_cmd_tso_mss);
+			}
+
 			if (offloads & RTE_MBUF_F_TX_QINQ) {
 				desc_l2tag2 = tx_pkt->vlan_tci_outer;
 				desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_CMD_IL2TAG2_MASK;
@@ -786,6 +810,36 @@ static inline void sxe2_rx_desc_ptp_para_fill(struct sxe2_rx_queue *rxq,
 			     rxq->ts_low);
 	}
 }
+
+static inline void sxe2_rx_desc_ipsec_para_fill(struct sxe2_rx_queue *rxq __rte_unused,
+		struct rte_mbuf *mbuf, union sxe2_rx_desc *desc)
+{
+	uint32_t status_lrocnt_fdpf_id = rte_le_to_cpu_32(desc->wb.status_lrocnt_fdpf_id);
+	enum sxe2_rx_desc_ipsec_status ipsec_status;
+
+	if (status_lrocnt_fdpf_id & SXE2_RX_DESC_IPSEC_PKT_MASK) {
+		mbuf->ol_flags |= RTE_MBUF_F_RX_SEC_OFFLOAD;
+		ipsec_status = SXE2_RX_DESC_IPSEC_STATUS_VAL_GET(status_lrocnt_fdpf_id);
+		switch (ipsec_status) {
+		case SXE2_RX_DESC_IPSEC_STATUS_SUCCESS:
+			break;
+		case SXE2_RX_DESC_IPSEC_STATUS_PKG_OVER_2K:
+		case SXE2_RX_DESC_IPSEC_STATUS_SPI_IP_INVALID:
+		case SXE2_RX_DESC_IPSEC_STATUS_SA_INVALID:
+		case SXE2_RX_DESC_IPSEC_STATUS_NOT_ALIGN:
+		case SXE2_RX_DESC_IPSEC_STATUS_ICV_ERROR:
+		case SXE2_RX_DESC_IPSEC_STATUS_BY_PASSH:
+		case SXE2_RX_DESC_IPSEC_STATUS_MAC_BY_PASSH:
+			PMD_LOG_INFO(RX, "IPsec status error:%d", ipsec_status);
+			mbuf->ol_flags |= RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED;
+			break;
+		default:
+			PMD_LOG_INFO(RX, "Invalid ipsec status:%d", ipsec_status);
+			mbuf->ol_flags |= RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED;
+			break;
+		}
+	}
+}
 #endif
 
 static __rte_always_inline void
@@ -803,6 +857,7 @@ sxe2_rx_mbuf_common_fields_fill(struct sxe2_rx_queue *rxq, struct rte_mbuf *mbuf
 	sxe2_rx_desc_vlan_para_fill(mbuf, rxd);
 	sxe2_rx_desc_filter_para_fill(rxq, mbuf, rxd);
 #ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+	sxe2_rx_desc_ipsec_para_fill(rxq, mbuf, rxd);
 	sxe2_rx_desc_ptp_para_fill(rxq, mbuf, rxd);
 #endif
 
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 16/23] net/sxe2: support SFP module info and EEPROM access
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch implements 'get_module_info' and 'get_module_eeprom'
ops for the sxe2 PMD. These interfaces allow applications to retrieve
the type of the plugged-in optical module and read its internal
EEPROM data.

The implementation utilizes the shared SFP header definitions to
parse the module ID, connector type, and encoding. It supports
reading the standard 256-byte EEPROM maps (SFF-8472 for SFP and
SFF-8636 for QSFP) via hardware-specific access commands.

Key features:
- Identify module types (SFP/SFP+/QSFP/QSFP28).
- Support standard EEPROM data retrieval for diagnostic tools.
- Add boundary checks to ensure safe I2C memory access.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/common/sxe2/sxe2_msg.h   |  21 ++-
 drivers/net/sxe2/sxe2_cmd_chnl.c |  46 +++++
 drivers/net/sxe2/sxe2_cmd_chnl.h |   3 +
 drivers/net/sxe2/sxe2_drv_cmd.h  |  18 ++
 drivers/net/sxe2/sxe2_ethdev.c   | 298 +++++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_ethdev.h   |   9 +
 6 files changed, 384 insertions(+), 11 deletions(-)

diff --git a/drivers/common/sxe2/sxe2_msg.h b/drivers/common/sxe2/sxe2_msg.h
index a5270b2c13..57814ff8a4 100644
--- a/drivers/common/sxe2/sxe2_msg.h
+++ b/drivers/common/sxe2/sxe2_msg.h
@@ -7,7 +7,7 @@
 #define __SXE2_MSG_H__
 
 enum sfp_type_identifier {
-	SXE2_SFP_TYPE_UNKNOW       = 0x00,
+	SXE2_SFP_TYPE_UNKNOWN      = 0x00,
 	SXE2_SFP_TYPE_SFP          = 0x03,
 
 	SXE2_SFP_TYPE_QSFP_PLUS    = 0x0D,
@@ -29,21 +29,20 @@ enum sfp_bus_addr {
 };
 
 struct sxe2_sfp_req {
-	u8 is_wr;
-	u8 is_qsfp;
+	uint8_t is_wr;
+	uint8_t is_qsfp;
 	uint16_t bus_addr;
 	uint16_t page_cnt;
 	uint16_t offset;
 	uint16_t data_len;
 	uint16_t rvd;
-	u8 data[];
+	uint8_t data[];
 };
-
 struct sxe2_sfp_resp {
-	u8 is_wr;
-	u8 is_qsfp;
+	uint8_t is_wr;
+	uint8_t is_qsfp;
 	uint16_t data_len;
-	u8 data[];
+	uint8_t data[];
 };
 
 enum sfp_page_cnt {
@@ -106,12 +105,12 @@ enum sxe2_led_mode {
 
 
 typedef struct sxe2_led_ctrl {
-	u32 mode;
-	u32 duration;
+	uint32_t mode;
+	uint32_t duration;
 } sxe2_led_ctrl_s;
 
 typedef struct sxe2_led_ctrl_resp {
-	u32 ack;
+	uint32_t ack;
 } sxe2_led_ctrl_resp_s;
 #endif
 
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index 926eaee062..43e8c59487 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -1833,3 +1833,49 @@ int32_t sxe2_drv_srcvsi_prune_config(struct sxe2_adapter *adapter,
 
 	return ret;
 }
+
+int32_t sxe2_drv_sfp_eeprom_read(struct sxe2_adapter *adapter, struct sxe2_sfp_read_info *sfp_info)
+{
+	int32_t ret = -1;
+	struct sxe2_drv_sfp_req req = {0};
+	struct sxe2_drv_sfp_resp *resp = NULL;
+	struct sxe2_drv_cmd_params cmd = {0};
+
+	resp = rte_zmalloc("read sfp data", sizeof(*resp) + sfp_info->len, 0);
+	if (!resp) {
+		PMD_LOG_ERR(DRV, "Alloc memory failed");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	req.is_wr = false;
+	req.is_qsfp = sfp_info->is_qsfp;
+	req.page_cnt = rte_cpu_to_le_16(sfp_info->page_cnt);
+	req.offset = rte_cpu_to_le_16(sfp_info->offset);
+	req.data_len = rte_cpu_to_le_16(sfp_info->len);
+	req.bus_addr = rte_cpu_to_le_16(sfp_info->bus_addr);
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "is_qsfp=%u, page_cnt=%u, offset=%u, datalen=%u, "
+			 "bus_addr=%u", sfp_info->is_qsfp, sfp_info->page_cnt, sfp_info->offset,
+			 sfp_info->len, sfp_info->bus_addr);
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_OPT_EEP_GET,
+				 &req, sizeof(req),
+				 resp, sizeof(*resp) + sfp_info->len);
+	ret = sxe2_drv_cmd_exec(adapter->cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to read sfp, ret=%d", ret);
+		goto l_end;
+	}
+
+	ret = 0;
+	rte_memcpy(sfp_info->data, resp->data, sfp_info->len);
+
+l_end:
+	if (resp) {
+		rte_free(resp);
+		resp = NULL;
+	}
+
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index 43f28c8304..8ac485b331 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -167,4 +167,7 @@ int32_t sxe2_drv_flow_fnav_query_stat(struct sxe2_adapter *adapter,
 int32_t sxe2_drv_srcvsi_prune_config(struct sxe2_adapter *adapter,
 		uint16_t *vsi_list, uint16_t vsi_cnt, bool set);
 
+int32_t sxe2_drv_sfp_eeprom_read(struct sxe2_adapter *adapter,
+		struct sxe2_sfp_read_info *sfp_info);
+
 #endif /* __SXE2_CMD_CHNL_H__ */
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index 5b5ddf9960..b7c70b0ea7 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -630,6 +630,24 @@ struct sxe2_drv_tx_map_req {
 	uint8_t pool_idx;
 };
 
+struct sxe2_drv_sfp_req {
+	uint8_t is_wr;
+	uint8_t is_qsfp;
+	__le16 bus_addr;
+	__le16 page_cnt;
+	__le16 offset;
+	__le16 data_len;
+	__le16 rvd;
+	uint8_t data[];
+};
+
+struct sxe2_drv_sfp_resp {
+	uint8_t is_wr;
+	uint8_t is_qsfp;
+	__le16 data_len;
+	uint8_t data[];
+};
+
 enum sxe2_drv_cmd_module {
 	SXE2_DRV_CMD_MODULE_HANDSHAKE = 0,
 	SXE2_DRV_CMD_MODULE_DEV = 1,
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 45e245740b..edcedbab45 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -41,6 +41,7 @@
 #include "sxe2_ethdev_repr.h"
 #include "sxe2vf_regs.h"
 #include "sxe2_switchdev.h"
+#include "sxe2_msg.h"
 
 #define SXE2_PCI_VENDOR_ID_1    0x1ff2
 #define SXE2_PCI_DEVICE_ID_PF_1 0x10b1
@@ -122,6 +123,10 @@ static int32_t sxe2_udp_tunnel_port_del(struct rte_eth_dev *dev,
 					struct rte_eth_udp_tunnel *tunnel_udp);
 static int32_t sxe2_fw_version_string_get(struct rte_eth_dev *dev,
 				      char *fw_version, size_t fw_size);
+static int32_t sxe2_get_module_info(struct rte_eth_dev *dev,
+				struct rte_eth_dev_module_info *info);
+static int32_t sxe2_get_module_eeprom(struct rte_eth_dev *dev,
+				  struct rte_dev_eeprom_info *info);
 
 static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.dev_configure              = sxe2_dev_configure,
@@ -186,6 +191,9 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.fw_version_get             = sxe2_fw_version_string_get,
 
 	.get_monitor_addr           = sxe2_get_monitor_addr,
+
+	.get_module_info            = sxe2_get_module_info,
+	.get_module_eeprom          = sxe2_get_module_eeprom,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
@@ -291,6 +299,296 @@ static int32_t sxe2_dev_start(struct rte_eth_dev *dev)
 	return ret;
 }
 
+static int32_t sxe2_sfp_type_get(struct sxe2_adapter *adapter, uint8_t *type)
+{
+	int32_t ret = -1;
+	struct sxe2_sfp_read_info sfp_info;
+
+	memset(&sfp_info, 0, sizeof(sfp_info));
+	sfp_info.bus_addr = SXE2_SFP_E2P_I2C_7BIT_ADDR0;
+	sfp_info.len = 1;
+	sfp_info.data = type;
+	sfp_info.offset = 0;
+	sfp_info.page_cnt = 0;
+	sfp_info.is_qsfp = false;
+
+	ret = sxe2_drv_sfp_eeprom_read(adapter, &sfp_info);
+	if (ret)
+		goto l_end;
+
+	ret = 0;
+	PMD_LOG_INFO(DRV, "Get sfp type success, type=%u", *type);
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_sfp_module_info_get(struct sxe2_adapter *adapter,
+						  struct rte_eth_dev_module_info *info)
+{
+	int32_t ret = -1;
+	bool page_swap = false;
+	uint8_t sff8472_rev = 0;
+	uint8_t addr_mode = 0;
+	struct sxe2_sfp_read_info sfp_info;
+
+	memset(&sfp_info, 0, sizeof(sfp_info));
+	sfp_info.bus_addr = SXE2_SFP_E2P_I2C_7BIT_ADDR0;
+	sfp_info.is_qsfp = false;
+	sfp_info.len = 1;
+	sfp_info.data = &sff8472_rev;
+	sfp_info.offset = SXE2_MODULE_SFF_8472_COMP;
+	sfp_info.page_cnt = 0;
+
+	ret = sxe2_drv_sfp_eeprom_read(adapter, &sfp_info);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to read 8472 protocol, ret=%d", ret);
+		goto l_end;
+	}
+
+	sfp_info.data = &addr_mode;
+	sfp_info.offset = SXE2_MODULE_SFF_8472_SWAP;
+
+	ret = sxe2_drv_sfp_eeprom_read(adapter, &sfp_info);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to read A2 page, ret=%d", ret);
+		goto l_end;
+	}
+
+	if (addr_mode & SXE2_MODULE_SFF_ADDR_MODE) {
+		PMD_LOG_ERR(DRV, "address change required to access page 0xA2, "
+			    "but not supported. please report the module "
+			    "type to the driver maintainers.");
+		page_swap = true;
+	}
+
+	PMD_LOG_INFO(DRV, "Read sfp module info, sff_8472=%u, a2_page=%u, swap_page=%d",
+		     sff8472_rev, addr_mode, page_swap);
+
+	if (sff8472_rev == SXE2_MODULE_SFF_8472_UNSUP ||
+	    page_swap ||
+	    !(addr_mode & SXE2_MODULE_SFF_DDM_IMPLEMENTED)) {
+		info->type = SXE2_MODULE_SFF_8079;
+		info->eeprom_len = SXE2_MODULE_SFF_8079_LEN;
+	} else {
+		info->type = SXE2_MODULE_SFF_8472;
+		info->eeprom_len = SXE2_MODULE_SFF_8472_LEN;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_qsfp_module_info_get(struct sxe2_adapter *adapter, struct rte_eth_dev_module_info *info)
+{
+	int32_t ret = -1;
+	uint8_t sff8636_rev = 0;
+	struct sxe2_sfp_read_info sfp_info;
+
+	memset(&sfp_info, 0, sizeof(sfp_info));
+	sfp_info.bus_addr = SXE2_SFP_E2P_I2C_7BIT_ADDR0;
+	sfp_info.is_qsfp = true;
+	sfp_info.len = 1;
+	sfp_info.data = &sff8636_rev;
+	sfp_info.offset = SXE2_MODULE_REVISION_ADDR;
+	sfp_info.page_cnt = 0;
+
+	ret = sxe2_drv_sfp_eeprom_read(adapter, &sfp_info);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to read 8636 protocol, ret=%d", ret);
+		goto l_end;
+	}
+
+	if (sff8636_rev > 0x02) {
+		info->type = SXE2_MODULE_SFF_8636;
+		info->eeprom_len = SXE2_MODULE_SFF_8636_MAX_LEN;
+	} else {
+		info->type = SXE2_MODULE_SFF_8436;
+		info->eeprom_len = SXE2_MODULE_SFF_8436_MAX_LEN;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_get_module_info(struct rte_eth_dev *dev, struct rte_eth_dev_module_info *info)
+{
+	int32_t ret = -1;
+	uint8_t type = 0;
+	struct sxe2_adapter *adapter = dev->data->dev_private;
+
+	ret = sxe2_sfp_type_get(adapter, &type);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to read sfp type, ret=%d", ret);
+		goto l_end;
+	}
+
+	switch (type) {
+	case SXE2_MODULE_SFF_SFP_TYPE:
+		ret = sxe2_sfp_module_info_get(adapter, info);
+		if (ret)
+			goto l_end;
+		break;
+	case SXE2_MODULE_TYPE_QSFP_PLUS:
+	case SXE2_MODULE_TYPE_QSFP28:
+		ret = sxe2_qsfp_module_info_get(adapter, info);
+		if (ret)
+			goto l_end;
+		break;
+	default:
+		ret = -ENXIO;
+		PMD_LOG_ERR(DRV, "Invalid sfp type, type=%d.", type);
+		goto l_end;
+	}
+
+	PMD_LOG_INFO(DRV, "sfp eeprom type=%x, eeprom len=%d.", info->type, info->eeprom_len);
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_get_sfp_eeprom(struct sxe2_adapter *adapter, struct sxe2_sfp_read_info *sfp_info)
+{
+	int32_t ret = -1;
+	uint16_t ori_len = sfp_info->len;
+	uint16_t ori_offset = sfp_info->offset;
+
+	if ((ori_len + ori_offset) > SXE2_SFP_EEP_LEN_MAX) {
+		sfp_info->len = (uint16_t)(SXE2_SFP_EEP_LEN_MAX - ori_offset);
+		ret = sxe2_drv_sfp_eeprom_read(adapter, sfp_info);
+		if (ret)
+			goto l_end;
+		sfp_info->bus_addr = SXE2_SFP_E2P_I2C_7BIT_ADDR1;
+		sfp_info->len = (uint16_t)(ori_len - (SXE2_SFP_EEP_LEN_MAX - ori_offset));
+		sfp_info->data = (uint8_t *)(sfp_info->data) + (SXE2_SFP_EEP_LEN_MAX - ori_offset);
+		sfp_info->offset = 0;
+		sfp_info->page_cnt = 0;
+		ret = sxe2_drv_sfp_eeprom_read(adapter, sfp_info);
+	} else {
+		ret = sxe2_drv_sfp_eeprom_read(adapter, sfp_info);
+	}
+
+l_end:
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to read sfp.");
+	return ret;
+}
+
+static int32_t
+sxe2_get_qsfp_eeprom(struct sxe2_adapter *adapter,
+					  struct sxe2_sfp_read_info *sfp_info)
+{
+	int32_t ret = -1;
+	uint16_t ori_len = sfp_info->len;
+	uint16_t ori_offset = sfp_info->offset;
+	uint16_t read_len = 0;
+	uint16_t remain_len = 0;
+
+	if ((ori_len + ori_offset) > SXE2_SFP_EEP_LEN_MAX) {
+		sfp_info->len = (uint16_t)(SXE2_SFP_EEP_LEN_MAX - ori_offset);
+		ret = sxe2_drv_sfp_eeprom_read(adapter, sfp_info);
+		if (ret)
+			goto l_end;
+
+		do {
+			read_len = read_len + sfp_info->len;
+			sfp_info->data = (uint8_t *)(sfp_info->data) + sfp_info->len;
+			sfp_info->offset = SXE2_QSFP_PAGE_OFST_START;
+			sfp_info->page_cnt++;
+			remain_len = (uint16_t)(ori_len - read_len);
+			sfp_info->len = (remain_len > SXE2_QSFP_PAGE_OFST_START) ?
+					SXE2_QSFP_PAGE_OFST_START : remain_len;
+			ret = sxe2_drv_sfp_eeprom_read(adapter, sfp_info);
+			if (ret)
+				goto l_end;
+		} while (remain_len > SXE2_QSFP_PAGE_OFST_START);
+	} else {
+		ret = sxe2_drv_sfp_eeprom_read(adapter, sfp_info);
+	}
+
+l_end:
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to read sfp.");
+	return ret;
+}
+
+static int32_t
+sxe2_get_module_eeprom(struct rte_eth_dev *dev, struct rte_dev_eeprom_info *info)
+{
+	int32_t ret = -1;
+	uint8_t type = 0;
+	struct sxe2_adapter *adapter = dev->data->dev_private;
+	struct sxe2_sfp_read_info sfp_info;
+
+	memset(&sfp_info, 0, sizeof(sfp_info));
+
+	if (!info || !info->length || !info->data ||
+			info->offset >= SXE2_SFP_EEP_LEN_MAX) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	PMD_LOG_INFO(DRV, "Dump sfp eeprom info offset=0x%x, len=0x%x.",
+		     info->offset, info->length);
+
+	ret = sxe2_sfp_type_get(adapter, &type);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to read sfp type, ret=%d", ret);
+		goto l_end;
+	}
+
+	sfp_info.bus_addr = SXE2_SFP_E2P_I2C_7BIT_ADDR0;
+	sfp_info.len = info->length;
+	sfp_info.data = info->data;
+	sfp_info.offset = info->offset;
+	sfp_info.page_cnt = 0;
+
+	switch (type) {
+	case SXE2_MODULE_SFF_SFP_TYPE:
+		if (info->length > SXE2_SFP_EEP_LEN_MAX * 2) {
+			ret = -EINVAL;
+			PMD_LOG_ERR(DRV, "sfp read size[%u] > eeprom max size[%d], ret=%d",
+				    info->length, SXE2_SFP_EEP_LEN_MAX * 2, ret);
+			goto l_end;
+		}
+		sfp_info.is_qsfp = false;
+		ret = sxe2_get_sfp_eeprom(adapter, &sfp_info);
+		if (ret)
+			goto l_end;
+		break;
+	case SXE2_MODULE_TYPE_QSFP_PLUS:
+	case SXE2_MODULE_TYPE_QSFP28:
+		if (info->length > SXE2_MODULE_SFF_8636_MAX_LEN) {
+			ret = -EINVAL;
+			PMD_LOG_ERR(DRV, "sfp read size[%u] > eeprom max size[%d], ret=%d",
+				    info->length, SXE2_SFP_EEP_LEN_MAX * 2, ret);
+			goto l_end;
+		}
+		sfp_info.is_qsfp = true;
+		ret = sxe2_get_qsfp_eeprom(adapter, &sfp_info);
+		if (ret)
+			goto l_end;
+		break;
+	default:
+		ret = -ENXIO;
+		PMD_LOG_ERR(DRV, "Invalid sfp type, type=%d.", type);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
 static enum sxe2_udp_tunnel_protocol
 sxe2_udp_tunnel_type_rte_to_sxe2(enum rte_eth_tunnel_type rte_type)
 {
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index d6c6a152e7..e7a8ee0dd5 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -274,6 +274,15 @@ struct sxe2_sched_hw_cap {
 	uint8_t adj_lvl;
 };
 
+struct sxe2_sfp_read_info {
+	uint8_t *data;
+	uint16_t offset;
+	uint16_t len;
+	uint16_t bus_addr;
+	uint16_t page_cnt;
+	bool is_qsfp;
+};
+
 struct sxe2_link_context {
 	rte_spinlock_t link_lock;
 	bool link_up;
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 19/23] net/sxe2: add testpmd commands for private features
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

Introduce private testpmd commands and implementation files to enable
debugging and testing of sxe2-specific hardware features (such as
packet scheduling reset, UDP tunnel configuration, and IPsec ingress/
egress offloads) directly within the testpmd application.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build        |   5 +-
 drivers/net/sxe2/sxe2_ethdev.c      |  26 +
 drivers/net/sxe2/sxe2_ethdev.h      |   2 +
 drivers/net/sxe2/sxe2_testpmd.c     | 733 +++++++++++++++++++++
 drivers/net/sxe2/sxe2_testpmd_lib.c | 969 ++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_testpmd_lib.h | 142 ++++
 drivers/net/sxe2/sxe2_tm.c          |  18 +
 drivers/net/sxe2/sxe2_tm.h          |   2 +
 8 files changed, 1895 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_testpmd.c
 create mode 100644 drivers/net/sxe2/sxe2_testpmd_lib.c
 create mode 100644 drivers/net/sxe2/sxe2_testpmd_lib.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index 5a02b1c3d3..00a331c208 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -9,9 +9,10 @@ endif
 
 cflags += ['-g']
 
-deps += ['common_sxe2', 'hash','cryptodev','security']
+deps += ['common_sxe2', 'hash', 'cryptodev', 'security', 'cmdline']
 
 includes += include_directories('../../common/sxe2')
+testpmd_sources = files('sxe2_testpmd.c')
 
 if arch_subdir == 'x86'
         sources += files('sxe2_txrx_vec_sse.c')
@@ -79,7 +80,7 @@ sources += files(
         'sxe2_flow_parse_engine.c',
         'sxe2_dump.c',
         'sxe2_txrx_check_mbuf.c',
-
+        'sxe2_testpmd_lib.c',
 )
 
 allow_internal_get_api = true
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 73a92d99f8..0b19e17c2e 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -1661,6 +1661,32 @@ static int32_t sxe2_sched_uinit(struct rte_eth_dev *dev)
 	return ret;
 }
 
+int32_t sxe2_sched_reset(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+
+	if (dev->data->dev_started) {
+		PMD_LOG_ERR(DRV, "Device failed to Stop.");
+		ret = -EPERM;
+		goto l_end;
+	}
+
+	ret = sxe2_tm_conf_reset(dev);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_sched_uinit(dev);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_sched_init(dev);
+	if (ret)
+		goto l_end;
+
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 			     struct sxe2_dev_kvargs_info *kvargs __rte_unused)
 {
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index e7a8ee0dd5..56b3b3cfe4 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -361,6 +361,8 @@ bool sxe2_ethdev_check(struct rte_eth_dev *dev);
 
 uint32_t sxe2_sched_mode_get(struct sxe2_adapter *adapter);
 
+int32_t sxe2_sched_reset(struct rte_eth_dev *dev);
+
 struct sxe2_pci_map_bar_info *sxe2_dev_get_bar_info(struct sxe2_adapter *adapter,
 						    enum sxe2_pci_map_resource res_type);
 
diff --git a/drivers/net/sxe2/sxe2_testpmd.c b/drivers/net/sxe2/sxe2_testpmd.c
new file mode 100644
index 0000000000..5792058212
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_testpmd.c
@@ -0,0 +1,733 @@
+
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef SXE2_TEST
+#include <cmdline_parse_num.h>
+#include <cmdline_parse_string.h>
+#include <stdlib.h>
+#include <testpmd.h>
+
+#include "sxe2_common_log.h"
+#include "sxe2_testpmd_lib.h"
+
+#define SXE2_SWITCH_BUFF_SIZE (4 * 1024 * 1024)
+
+struct cmd_stats_info_show_result {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t show;
+	cmdline_fixed_string_t stats;
+	portid_t port_id;
+};
+cmdline_parse_token_string_t cmd_stats_info_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_stats_info_show_result, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_stats_info_show =
+	TOKEN_STRING_INITIALIZER(struct cmd_stats_info_show_result, show, "show");
+cmdline_parse_token_string_t cmd_stats_info_stats =
+	TOKEN_STRING_INITIALIZER(struct cmd_stats_info_show_result, stats, "stats");
+cmdline_parse_token_num_t cmd_stats_info_port_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_stats_info_show_result, port_id, RTE_UINT16);
+
+struct cmd_flow_rule_result {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t flow;
+	cmdline_fixed_string_t rule;
+	cmdline_fixed_string_t dump;
+	portid_t port_id;
+};
+cmdline_parse_token_string_t cmd_flow_rule_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_flow_rule_result, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_flow_rule_flow =
+	TOKEN_STRING_INITIALIZER(struct cmd_flow_rule_result, flow, "flow");
+cmdline_parse_token_string_t cmd_flow_rule_rule =
+	TOKEN_STRING_INITIALIZER(struct cmd_flow_rule_result, rule, "rule");
+cmdline_parse_token_string_t cmd_flow_rule_dmp =
+	TOKEN_STRING_INITIALIZER(struct cmd_flow_rule_result, dump, "dump");
+cmdline_parse_token_num_t cmd_flow_rule_port_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_flow_rule_result, port_id, RTE_UINT16);
+
+struct cmd_udp_tunnel {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t tunnel_type;
+	cmdline_fixed_string_t action;
+	cmdline_fixed_string_t udp_tunnel_port;
+	uint16_t               udp_port;
+	portid_t               port_id;
+};
+
+cmdline_parse_token_string_t cmd_udp_tunnel_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_udp_tunnel, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_udp_tunnel_action =
+	TOKEN_STRING_INITIALIZER(struct cmd_udp_tunnel, action, "add#rm#show");
+cmdline_parse_token_string_t cmd_udp_tunnel_udp_tunnel_port =
+	TOKEN_STRING_INITIALIZER(struct cmd_udp_tunnel, udp_tunnel_port, "udp_tunnel_port");
+cmdline_parse_token_string_t cmd_udp_tunnel_tunnel_type =
+	TOKEN_STRING_INITIALIZER(struct cmd_udp_tunnel,
+	tunnel_type, "vxlan#vxlan-gpe#geneve#gtp-c#gtp-u#pfcp#ecpri#mpls#nvgre#l2tp#teredo");
+cmdline_parse_token_num_t cmd_udp_tunnel_udp_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_udp_tunnel, udp_port, RTE_UINT16);
+cmdline_parse_token_num_t cmd_udp_tunnel_port_id  =
+	TOKEN_NUM_INITIALIZER(struct cmd_udp_tunnel, port_id, RTE_UINT16);
+
+struct cmd_sched_result {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t sched;
+	cmdline_fixed_string_t reset;
+	portid_t port_id;
+};
+
+cmdline_parse_token_string_t cmd_sched_sxe2 =
+	 TOKEN_STRING_INITIALIZER(struct cmd_sched_result, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_sched_sched =
+	 TOKEN_STRING_INITIALIZER(struct cmd_sched_result, sched, "sched");
+cmdline_parse_token_string_t cmd_sched_reset =
+	 TOKEN_STRING_INITIALIZER(struct cmd_sched_result, reset, "reset");
+cmdline_parse_token_num_t cmd_sched_port_id =
+	 TOKEN_NUM_INITIALIZER(struct cmd_sched_result, port_id, RTE_UINT16);
+
+struct cmd_ipsec_result {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t engin;
+	cmdline_fixed_string_t dir;
+	cmdline_fixed_string_t op;
+	portid_t port_id;
+	uint16_t session_id;
+	cmdline_fixed_string_t encrypt_algo;
+	cmdline_fixed_string_t encrypt_key;
+	cmdline_fixed_string_t auth_algo;
+	cmdline_fixed_string_t auth_key;
+	cmdline_fixed_string_t dst_ip;
+	uint16_t sport;
+	uint16_t dport;
+	uint32_t spi;
+};
+cmdline_parse_token_string_t cmd_ipsec_mgt_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_ipsec_mgt_module =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, engin, "ipsec");
+cmdline_parse_token_string_t cmd_ipsec_mgt_dir =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, dir, "egress#ingress");
+cmdline_parse_token_string_t cmd_ipsec_mgt_op =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, op, "add#rm#show");
+cmdline_parse_token_num_t cmd_ipsec_mgt_port_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_result, port_id, RTE_UINT16);
+cmdline_parse_token_num_t cmd_ipsec_mgt_session_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_result, session_id, RTE_UINT16);
+cmdline_parse_token_string_t cmd_ipsec_mgt_encrypt_algo =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, encrypt_algo, "aes-cbc#sm4-cbc#null");
+cmdline_parse_token_string_t cmd_ipsec_mgt_encrypt_key =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, encrypt_key, NULL);
+cmdline_parse_token_string_t cmd_ipsec_mgt_auth_algo =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, auth_algo, "sha-hmac#sm3-hmac#null");
+cmdline_parse_token_string_t cmd_ipsec_mgt_auth_key =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, auth_key, NULL);
+cmdline_parse_token_string_t cmd_ipsec_mgt_dst_ip =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_result, dst_ip, NULL);
+cmdline_parse_token_num_t cmd_ipsec_mgt_sport =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_result, sport, RTE_UINT16);
+cmdline_parse_token_num_t cmd_ipsec_mgt_dport =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_result, dport, RTE_UINT16);
+cmdline_parse_token_num_t cmd_ipsec_mgt_spi =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_result, spi, RTE_UINT32);
+
+struct cmd_ipsec_set_result {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t engin;
+	cmdline_fixed_string_t op;
+	cmdline_fixed_string_t type;
+	portid_t port_id;
+	uint16_t conf_value;
+};
+cmdline_parse_token_string_t cmd_ipsec_set_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_set_result, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_ipsec_set_module =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_set_result, engin, "ipsec");
+cmdline_parse_token_string_t cmd_ipsec_set_op =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_set_result, op, "set#get");
+cmdline_parse_token_string_t cmd_ipsec_set_type =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_set_result, type, "session-id#esp-hdr-offset");
+cmdline_parse_token_num_t cmd_ipsec_set_port_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_set_result, port_id, RTE_UINT16);
+cmdline_parse_token_num_t cmd_ipsec_set_value =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_set_result, conf_value, RTE_UINT16);
+
+struct cmd_ipsec_flush_result {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t engin;
+	cmdline_fixed_string_t op;
+	portid_t port_id;
+};
+cmdline_parse_token_string_t cmd_ipsec_flush_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_flush_result, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_ipsec_flush_module =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_flush_result, engin, "ipsec");
+cmdline_parse_token_string_t cmd_ipsec_flush_op =
+	TOKEN_STRING_INITIALIZER(struct cmd_ipsec_flush_result, op, "flush");
+cmdline_parse_token_num_t cmd_ipsec_flush_port_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_ipsec_flush_result, port_id, RTE_UINT16);
+
+struct cmd_inject_irq {
+	cmdline_fixed_string_t sxe2;
+	cmdline_fixed_string_t inject;
+	cmdline_fixed_string_t irq;
+	portid_t port_id;
+	cmdline_fixed_string_t type;
+};
+cmdline_parse_token_string_t cmd_inject_irq_sxe2 =
+	TOKEN_STRING_INITIALIZER(struct cmd_inject_irq, sxe2, "sxe2");
+cmdline_parse_token_string_t cmd_inject_irq_inject =
+	TOKEN_STRING_INITIALIZER(struct cmd_inject_irq, inject, "inject");
+cmdline_parse_token_string_t cmd_inject_irq_irq =
+	TOKEN_STRING_INITIALIZER(struct cmd_inject_irq, irq, "irq");
+cmdline_parse_token_num_t cmd_inject_irq_port_id =
+	TOKEN_NUM_INITIALIZER(struct cmd_inject_irq, port_id, RTE_UINT16);
+cmdline_parse_token_string_t cmd_inject_irq_type =
+	TOKEN_STRING_INITIALIZER(struct cmd_inject_irq, type, "reset#lsc");
+
+static void cmd_dump_flow_rule_parsed(void *parsed_result,
+				      struct cmdline *cl,
+				      __rte_unused void *data)
+{
+	struct cmd_flow_rule_result *res = parsed_result;
+	int                          ret = -1;
+
+	ret = sxe2_flow_rule_dump(res->port_id, cl);
+	switch (ret) {
+	case 0:
+		break;
+	case -EINVAL:
+		cmdline_printf(cl, "Invalid parameters.\n");
+		break;
+	case -ENODEV:
+		cmdline_printf(cl, "Device doesn't support\n");
+		break;
+	default:
+		cmdline_printf(cl,
+			"Failed to switch rule dump,"
+			" error: (%s)\n",
+			strerror(-ret));
+	}
+}
+
+static void cmd_udp_tunnel_set_parsed(void *parsed_result,
+				      struct cmdline *cl,
+				      __rte_unused void *data)
+{
+	struct cmd_udp_tunnel *res = parsed_result;
+	int32_t ret = -1;
+	uint8_t action;
+	const char *action_str[SXE2_TESTPMD_CMD_UDP_TUNNEL_MAX] = {
+		[SXE2_TESTPMD_CMD_UDP_TUNNEL_ADD] = "add",
+		[SXE2_TESTPMD_CMD_UDP_TUNNEL_DEL] = "rm",
+		[SXE2_TESTPMD_CMD_UDP_TUNNEL_GET] = "show"};
+
+	for (action = 0; action < SXE2_TESTPMD_CMD_UDP_TUNNEL_MAX; action++)
+		if (!strcmp(res->action, action_str[action]))
+			break;
+
+	if (action >= SXE2_TESTPMD_CMD_UDP_TUNNEL_MAX) {
+		cmdline_printf(cl, "Invalid action!\n");
+		return;
+	}
+
+	ret = sxe2_udp_tunnel_operations(res->port_id, cl, action,
+					 res->udp_port,
+					 res->tunnel_type);
+	if (ret)
+		cmdline_printf(cl, "%s udp tunnel port failed, ret = %d\n",
+				action_str[action], ret);
+}
+
+static void cmd_dump_stats_info_parsed(void *parsed_result,
+				       struct cmdline *cl,
+				       __rte_unused void *data)
+{
+	struct cmd_stats_info_show_result *res = parsed_result;
+	int ret = -1;
+
+	ret = sxe2_stats_info_show(res->port_id);
+	switch (ret) {
+	case 0:
+		break;
+	case -EINVAL:
+		cmdline_printf(cl, "Invalid parameters.\n");
+		break;
+	case -ENODEV:
+		cmdline_printf(cl, "Device doesn't support\n");
+		break;
+	default:
+		cmdline_printf(cl,
+			"Failed to show stats info,"
+			" error: (%s)\n", strerror(-ret));
+	}
+}
+
+static uint8_t cmd_ipsec_op_get(char *op)
+{
+	uint8_t i;
+	const char *op_type[SXE2_TESTPMD_CMD_IPSEC_OP_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_OP_ADD] = "add",
+		[SXE2_TESTPMD_CMD_IPSEC_OP_RM] = "rm",
+		[SXE2_TESTPMD_CMD_IPSEC_OP_SHOW] = "show",
+	};
+
+	for (i = 0; i < SXE2_TESTPMD_CMD_IPSEC_OP_MAX; i++) {
+		if (!strcmp(op, op_type[i]))
+			break;
+	}
+
+	return i;
+}
+
+static uint8_t cmd_ipsec_dir_get(char *dir)
+{
+	uint8_t i;
+	const char *dir_type[SXE2_TESTPMD_CMD_IPSEC_DIR_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_DIR_EGRESS] = "egress",
+		[SXE2_TESTPMD_CMD_IPSEC_DIR_INGRESS] = "ingress"
+	};
+
+	for (i = 0; i < SXE2_TESTPMD_CMD_IPSEC_DIR_MAX; i++) {
+		if (!strcmp(dir, dir_type[i]))
+			break;
+	}
+
+	return i;
+}
+
+static int sxe2_hex_to_val(char c)
+{
+	int val = 0;
+
+	if (c >= '0' && c <= '9')
+		val = c - '0';
+	if (c >= 'A' && c <= 'F')
+		val = 10 + c - 'A';
+	if (c >= 'a' && c <= 'f')
+		val = 10 + c - 'a';
+	return val;
+}
+
+static void sxe2_hex_to_bytes(uint8_t *enc_key, char *hex_str, uint8_t len)
+{
+	uint8_t i;
+	int high = 0;
+	int low = 0;
+
+	for (i = 0; i < len; i++) {
+		high = sxe2_hex_to_val(hex_str[2 * i]);
+		low = sxe2_hex_to_val(hex_str[2 * i + 1]);
+		enc_key[i] = (high << 4) | low;
+	}
+}
+
+static int32_t cmd_ipsec_add_param_fill(struct sxe2_ipsec_conf_param *param,
+					struct cmdline *cl,
+					struct cmd_ipsec_result *res)
+{
+	uint8_t i;
+	uint8_t j;
+	int32_t ret = -1;
+	const char *encrypt_algo[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC] = "aes-cbc",
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_SM4_CBC] = "sm4-cbc",
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_NULL] = "null"
+	};
+
+	const char *auth_algo[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SHA_HMAC] = "sha-hmac",
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SM3_HMAC] = "sm3-hmac",
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL] = "null"
+	};
+
+	for (i = 0; i < SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_MAX; i++)
+		if (!strcmp(res->encrypt_algo, encrypt_algo[i]))
+			break;
+
+	if (i >= SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_MAX) {
+		cmdline_printf(cl, "Invalid ipsec encrypt algo: %s!\n", res->encrypt_algo);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	for (j = 0; j < SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_MAX; j++) {
+		if (!strcmp(res->auth_algo, auth_algo[j]))
+			break;
+	}
+
+
+	if (j >= SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_MAX) {
+		cmdline_printf(cl, "Invalid ipsec auth algo: %s!\n", res->auth_algo);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	param->encrypt_algo = i;
+	param->auth_algo = j;
+	if (param->encrypt_algo == SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_SM4_CBC)
+		param->enc_len = 16;
+	else
+		param->enc_len = 32;
+
+	sxe2_hex_to_bytes(param->enc_key, res->encrypt_key, param->enc_len);
+	if (param->auth_algo != SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL) {
+		param->auth_len = 32;
+		sxe2_hex_to_bytes(param->auth_key, res->auth_key, param->auth_len);
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t cmd_ipsec_egress_op_parsed(struct sxe2_ipsec_conf_param *param,
+					  struct cmdline *cl,
+					  struct cmd_ipsec_result *res)
+{
+	int32_t ret = -1;
+
+	switch (param->op) {
+	case SXE2_TESTPMD_CMD_IPSEC_OP_ADD:
+		ret = cmd_ipsec_add_param_fill(param, cl, res);
+		if (ret)
+			goto l_end;
+		ret = sxe2_ipsec_egress_create(param, cl);
+		break;
+	case SXE2_TESTPMD_CMD_IPSEC_OP_RM:
+		param->session_id = res->session_id;
+		ret = sxe2_ipsec_egress_destroy(param, cl);
+		break;
+	case SXE2_TESTPMD_CMD_IPSEC_OP_SHOW:
+		ret = sxe2_ipsec_egress_show(param, cl);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t cmd_ipsec_ip_addr_parsed(struct sxe2_ipsec_conf_param *param,
+					struct cmdline *cl,
+					struct cmd_ipsec_result *res)
+{
+	int32_t ret = -1;
+	struct in_addr addr4;
+	struct in6_addr addr6;
+
+	if (inet_pton(AF_INET, res->dst_ip, &addr4) == 1) {
+		param->ip_addr.type = RTE_SECURITY_IPSEC_TUNNEL_IPV4;
+		param->ip_addr.dst_ipv4 = addr4.s_addr;
+		ret = 0;
+	} else if (inet_pton(AF_INET6, res->dst_ip, &addr6) == 1) {
+		param->ip_addr.type = RTE_SECURITY_IPSEC_TUNNEL_IPV6;
+		memcpy(&param->ip_addr.dst_ipv6, &addr6, sizeof(param->ip_addr.dst_ipv6));
+		ret = 0;
+	} else {
+		cmdline_printf(cl, "Invalid ip address: %s!\n", res->dst_ip);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t cmd_ipsec_ingress_op_parsed(struct sxe2_ipsec_conf_param *param,
+					   struct cmdline *cl,
+					   struct cmd_ipsec_result *res)
+{
+	int32_t ret = -1;
+
+	switch (param->op) {
+	case SXE2_TESTPMD_CMD_IPSEC_OP_ADD:
+		ret = cmd_ipsec_add_param_fill(param, cl, res);
+		if (ret)
+			goto l_end;
+		param->sport = htons(res->sport);
+		param->dport = htons(res->dport);
+		param->spi = htonl(res->spi);
+		ret = cmd_ipsec_ip_addr_parsed(param, cl, res);
+		if (ret)
+			goto l_end;
+		ret = sxe2_ipsec_ingress_create(param, cl);
+		break;
+	case SXE2_TESTPMD_CMD_IPSEC_OP_RM:
+		param->session_id = res->session_id;
+		ret = sxe2_ipsec_ingress_destroy(param, cl);
+		break;
+	case SXE2_TESTPMD_CMD_IPSEC_OP_SHOW:
+		ret = sxe2_ipsec_ingress_show(param, cl);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t cmd_ipsec_dir_parsed(struct sxe2_ipsec_conf_param *param,
+				    struct cmdline *cl,
+				    struct cmd_ipsec_result *res)
+{
+	int32_t ret = -1;
+
+	switch (param->dir) {
+	case SXE2_TESTPMD_CMD_IPSEC_DIR_EGRESS:
+		ret = cmd_ipsec_egress_op_parsed(param, cl, res);
+		break;
+	case SXE2_TESTPMD_CMD_IPSEC_DIR_INGRESS:
+		ret = cmd_ipsec_ingress_op_parsed(param, cl, res);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static void cmd_ipsec_mgt_parsed(void *parsed_result,
+				 struct cmdline *cl,
+				 __rte_unused void *data)
+{
+	struct cmd_ipsec_result *res = parsed_result;
+	struct sxe2_ipsec_conf_param param;
+	int32_t ret = -1;
+	uint8_t dir = 0;
+	uint8_t op = 0;
+
+	dir = cmd_ipsec_dir_get(res->dir);
+	if (dir >= SXE2_TESTPMD_CMD_IPSEC_DIR_MAX) {
+		cmdline_printf(cl, "Invalid ipsec direction: %s!\n", res->dir);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	op = cmd_ipsec_op_get(res->op);
+	if (op >= SXE2_TESTPMD_CMD_IPSEC_OP_MAX) {
+		cmdline_printf(cl, "Invalid ipsec operation: %s!\n", res->op);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	memset(&param, 0, sizeof(struct sxe2_ipsec_conf_param));
+	param.dir = dir;
+	param.op = op;
+	param.port_id = res->port_id;
+	ret = cmd_ipsec_dir_parsed(&param, cl, res);
+
+	if (ret)
+		cmdline_printf(cl, "Command execute failed, ret = %d\n", ret);
+
+l_end:
+	return;
+}
+
+static void cmd_ipsec_set_parsed(void *parsed_result,
+				 struct cmdline *cl,
+				 __rte_unused void *data)
+{
+	struct cmd_ipsec_set_result *res = parsed_result;
+	int32_t ret = -1;
+
+	if (!strcmp(res->op, "set"))
+		ret = sxe2_ipsec_conf_set(res->port_id, cl, res->type, res->conf_value);
+	else if (!strcmp(res->op, "get"))
+		ret = sxe2_ipsec_conf_get(res->port_id, cl, res->type);
+	else
+		cmdline_printf(cl, "Invalid op: %s\n", res->op);
+
+	if (ret)
+		cmdline_printf(cl, "Command execute failed, ret = %d\n", ret);
+}
+
+static void cmd_ipsec_flush_parsed(void *parsed_result,
+				   struct cmdline *cl,
+				   __rte_unused void *data)
+{
+	struct cmd_ipsec_flush_result *res = parsed_result;
+	int32_t ret = -1;
+
+	ret = sxe2_ipsec_flush(res->port_id, cl);
+
+	if (ret)
+		cmdline_printf(cl, "Command execute failed, ret = %d\n", ret);
+}
+
+cmdline_parse_inst_t cmd_flow_rule_dump = {
+	.f        = cmd_dump_flow_rule_parsed,
+	.data     = NULL,
+	.help_str = "sxe2 flow rule dump <port_id>",
+	.tokens = {
+		(void *)&cmd_flow_rule_sxe2,
+		(void *)&cmd_flow_rule_flow,
+		(void *)&cmd_flow_rule_rule,
+		(void *)&cmd_flow_rule_dmp,
+		(void *)&cmd_flow_rule_port_id,
+		NULL,
+	},
+};
+
+cmdline_parse_inst_t cmd_udp_tunnel_set = {
+	.f        = cmd_udp_tunnel_set_parsed,
+	.data     = NULL,
+	.help_str = "sxe2 <port_id> udp_tunnel_port add|rm|show "
+			"vxlan|vxlan-gpe|geneve|gtp-c|gtp-u|pfcp|ecpri|mpls|nvgre|l2tp|teredo <udp_port>",
+	.tokens = {
+		(void *)&cmd_udp_tunnel_sxe2,
+		(void *)&cmd_udp_tunnel_port_id,
+		(void *)&cmd_udp_tunnel_udp_tunnel_port,
+		(void *)&cmd_udp_tunnel_action,
+		(void *)&cmd_udp_tunnel_tunnel_type,
+		(void *)&cmd_udp_tunnel_udp_port,
+		NULL,
+	},
+};
+
+cmdline_parse_inst_t cmd_stats_mgt = {
+	.f        = cmd_dump_stats_info_parsed,
+	.data     = NULL,
+	.help_str = "sxe2 show stats <port_id>",
+	.tokens = {
+		(void *)&cmd_stats_info_sxe2,
+		(void *)&cmd_stats_info_show,
+		(void *)&cmd_stats_info_stats,
+		(void *)&cmd_stats_info_port_id,
+		NULL,
+	},
+};
+
+static void cmd_sched_reset_cfg(void *parsed_result,
+				struct cmdline *cl,
+				__rte_unused void *data)
+{
+	struct cmd_sched_result *res = parsed_result;
+	int32_t ret = -1;
+
+	ret = sxe2_testpmd_sched_reset(res->port_id);
+	switch (ret) {
+	case 0:
+		break;
+	case -EINVAL:
+		cmdline_printf(cl, "invalid sched ops\n");
+		break;
+	case -ENOTSUP:
+		cmdline_printf(cl, "function not implemented\n");
+		break;
+	default:
+		cmdline_printf(cl, "programming error: (%s)\n",
+			strerror(-ret));
+	}
+}
+
+cmdline_parse_inst_t cmd_sched_reset_cmd = {
+	.f        = cmd_sched_reset_cfg,
+	.data     = NULL,
+	.help_str = "sxe2 sched reset <port_id>",
+	.tokens = {
+		(void *)&cmd_sched_sxe2,
+		(void *)&cmd_sched_sched,
+		(void *)&cmd_sched_reset,
+		(void *)&cmd_sched_port_id,
+		NULL,
+	},
+};
+
+cmdline_parse_inst_t cmd_ipsec_mgt = {
+	.f = cmd_ipsec_mgt_parsed,
+	.data = NULL,
+	.help_str = "sxe2 ipsec egress|ingress add|rm|show "
+	"<port_id> <session_id> aes-cbc|sm4-cbc|null <encrypt_key> sha-hmac|sm3-hmac|null "
+	"<auth_key> <dst_ip> <sport> <dport> <spi>",
+	.tokens = {
+		(void *)&cmd_ipsec_mgt_sxe2,
+		(void *)&cmd_ipsec_mgt_module,
+		(void *)&cmd_ipsec_mgt_dir,
+		(void *)&cmd_ipsec_mgt_op,
+		(void *)&cmd_ipsec_mgt_port_id,
+		(void *)&cmd_ipsec_mgt_session_id,
+		(void *)&cmd_ipsec_mgt_encrypt_algo,
+		(void *)&cmd_ipsec_mgt_encrypt_key,
+		(void *)&cmd_ipsec_mgt_auth_algo,
+		(void *)&cmd_ipsec_mgt_auth_key,
+		(void *)&cmd_ipsec_mgt_dst_ip,
+		(void *)&cmd_ipsec_mgt_sport,
+		(void *)&cmd_ipsec_mgt_dport,
+		(void *)&cmd_ipsec_mgt_spi,
+		NULL,
+	},
+};
+
+cmdline_parse_inst_t cmd_ipsec_set = {
+	.f = cmd_ipsec_set_parsed,
+	.data = NULL,
+	.help_str = "sxe2 ipsec set|get esp-hdr-offset|session-id <port_id> <value>",
+	.tokens = {
+		(void *)&cmd_ipsec_set_sxe2,
+		(void *)&cmd_ipsec_set_module,
+		(void *)&cmd_ipsec_set_op,
+		(void *)&cmd_ipsec_set_type,
+		(void *)&cmd_ipsec_set_port_id,
+		(void *)&cmd_ipsec_set_value,
+		NULL,
+	},
+};
+
+cmdline_parse_inst_t cmd_ipsec_flush = {
+	.f = cmd_ipsec_flush_parsed,
+	.data = NULL,
+	.help_str = "sxe2 ipsec flush <port_id>.\n",
+	.tokens = {
+		(void *)&cmd_ipsec_flush_sxe2,
+		(void *)&cmd_ipsec_flush_module,
+		(void *)&cmd_ipsec_flush_op,
+		(void *)&cmd_ipsec_flush_port_id,
+		NULL,
+	},
+};
+
+static struct testpmd_driver_commands sxe2_cmds = {
+	.commands = {
+		{
+			&cmd_udp_tunnel_set,
+			"sxe2 udp tunnel port set.\n"
+			"Add or remove a customed udp port for specific tunnel protocol\n\n",
+		},
+			{
+			&cmd_sched_reset_cmd,
+			"sxe2 sched reset <port_id>.\n"
+			"Reset sched node on the port\n\n",
+		},
+		{
+			&cmd_stats_mgt,
+			"sxe2 show stats.\n"
+			"Dump a runtime sxe2 dev stats on a port\n\n",
+		},
+		{
+			&cmd_ipsec_mgt,
+			"sxe2 ipsec <dir> <op> <port_id> <session_id>  <encrypt_algo> <encrypt_key>"
+			"<encrypt_len> <auth_algo> <auth_key> <auth_len> <dst_ip> <sport> <dport> <spi>.\n"
+			"Create/query/remove ipsec security session\n\n",
+		},
+		{
+			&cmd_ipsec_set,
+			"sxe2 ipsec set <port_id> <session_id> <esp_hdr_offset>.\n"
+			"Set enabled tx session id or esp offset.\n\n",
+		},
+		{
+			&cmd_ipsec_flush,
+			"sxe2 ipsec flush <port_id>.\n"
+			"Flush ipsec all configurations\n\n",
+		},
+		{	NULL, NULL},
+	},
+};
+TESTPMD_ADD_DRIVER_COMMANDS(sxe2_cmds)
+#endif
diff --git a/drivers/net/sxe2/sxe2_testpmd_lib.c b/drivers/net/sxe2/sxe2_testpmd_lib.c
new file mode 100644
index 0000000000..ab2530ffe6
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_testpmd_lib.c
@@ -0,0 +1,969 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_bus.h>
+#include <eal_export.h>
+
+#include "sxe2_common_log.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_stats.h"
+#include "sxe2_testpmd_lib.h"
+
+struct rte_mempool *g_sess_pool;
+
+bool g_sxe2_ipsec_mgt_init;
+struct sxe2_ipsec_session_mgt g_tx_session[SXE2_IPSEC_PORT_MAX][SXE2_IPSEC_SESSION_MAX];
+struct sxe2_ipsec_session_mgt g_rx_session[SXE2_IPSEC_PORT_MAX][SXE2_IPSEC_SESSION_MAX];
+uint16_t g_tx_sess_id[SXE2_IPSEC_PORT_MAX] = {0};
+uint16_t g_esp_header_offset[SXE2_IPSEC_PORT_MAX] = {0};
+
+static bool sxe2_is_supported(struct rte_eth_dev *dev)
+{
+	return sxe2_ethdev_check(dev);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_testpmd_sched_reset, 26.07)
+int32_t
+sxe2_testpmd_sched_reset(uint16_t port_id)
+{
+	struct rte_eth_dev   *dev     = NULL;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		return -ENODEV;
+	}
+
+	return sxe2_sched_reset(dev);
+}
+
+extern const char *sxe2_flow_type_name[SXE2_FLOW_TYPE_MAX];
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_flow_rule_dump, 26.07)
+int32_t
+sxe2_flow_rule_dump(uint16_t port_id, struct cmdline *cl)
+{
+	struct rte_eth_dev            *dev           = NULL;
+	struct sxe2_adapter           *adapter       = NULL;
+	int32_t                            ret       = -1;
+	struct rte_flow_list_t        *flow_list     = NULL;
+	struct rte_flow               *flow          = NULL;
+	uint32_t                            index         = 0;
+	struct sxe2_flow              *hw_flow       = NULL;
+	uint8_t i = 0;
+
+	const char *sxe2_flow_engine_name[SXE2_FLOW_ENGINE_MAX] = {
+		[SXE2_FLOW_ENGINE_ACL] = "acl",
+		[SXE2_FLOW_ENGINE_RSS] = "rss",
+		[SXE2_FLOW_ENGINE_SWITCH] = "switch",
+		[SXE2_FLOW_ENGINE_FNAV] = "fnav",
+	};
+	const char *sxe2_flow_action_name[SXE2_FLOW_ACTION_MAX] = {
+		[SXE2_FLOW_ACTION_DROP] = "drop",
+		[SXE2_FLOW_ACTION_TC_REDIRECT] = "tc_redirect",
+		[SXE2_FLOW_ACTION_TO_VSI] = "to_vsi",
+		[SXE2_FLOW_ACTION_TO_VSI_LIST] = "to_vsi_list",
+		[SXE2_FLOW_ACTION_PASSTHRU] = "passthru",
+		[SXE2_FLOW_ACTION_QUEUE] = "queue",
+		[SXE2_FLOW_ACTION_Q_REGION] = "q_region",
+		[SXE2_FLOW_ACTION_MARK] = "mark",
+		[SXE2_FLOW_ACTION_COUNT] = "count",
+		[SXE2_FLOW_ACTION_RSS] = "rss",
+	};
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev");
+		ret = -ENODEV;
+		goto l_end;
+	}
+	adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	flow_list = &adapter->flow_ctxt.rte_flow_list;
+	cmdline_printf(cl, "Dump sxe2 flow rule:\n");
+	TAILQ_FOREACH(flow, flow_list, next) {
+		cmdline_printf(cl, "rule index: %d\n", index++);
+		TAILQ_FOREACH(hw_flow, &flow->sxe2_flow_list, next) {
+			cmdline_printf(cl, "\thw flow id: %d\n", hw_flow->flow_id);
+			cmdline_printf(cl, "\t\ttype: %s\n",
+					sxe2_flow_type_name[hw_flow->meta.flow_type]);
+			cmdline_printf(cl, "\t\tprio: %d\n", hw_flow->meta.flow_prio);
+			cmdline_printf(cl, "\t\tsrc vsi: %d,rule vsi: %d\n",
+				hw_flow->meta.flow_src_vsi, hw_flow->meta.flow_rule_vsi);
+			cmdline_printf(cl, "\t\tengine type: %s\n",
+				sxe2_flow_engine_name[hw_flow->engine_type]);
+			cmdline_printf(cl, "\t\taction:");
+			for (i = 0; i < SXE2_FLOW_ACTION_MAX; i++) {
+				if (sxe2_test_bit(i, hw_flow->action.act_types))
+					cmdline_printf(cl, "%s ", sxe2_flow_action_name[i]);
+			}
+			cmdline_printf(cl, "\n");
+		}
+	}
+	cmdline_printf(cl, "Dump sxe2 flow rule end.\n");
+	ret = 0;
+l_end:
+	return ret;
+}
+
+static const char *tunnel_type_list[SXE2_UDP_TUNNEL_MAX] = {
+	[SXE2_UDP_TUNNEL_PROTOCOL_VXLAN] = "vxlan",
+	[SXE2_UDP_TUNNEL_PROTOCOL_VXLAN_GPE] = "vxlan-gpe",
+	[SXE2_UDP_TUNNEL_PROTOCOL_GENEVE] = "geneve",
+	[SXE2_UDP_TUNNEL_PROTOCOL_GTP_C] = "gtp-c",
+	[SXE2_UDP_TUNNEL_PROTOCOL_GTP_U] = "gtp-u",
+	[SXE2_UDP_TUNNEL_PROTOCOL_PFCP] = "pfcp",
+	[SXE2_UDP_TUNNEL_PROTOCOL_ECPRI] = "ecpri",
+	[SXE2_UDP_TUNNEL_PROTOCOL_MPLS] = "mpls",
+	[SXE2_UDP_TUNNEL_PROTOCOL_NVGRE] = "nvgre",
+	[SXE2_UDP_TUNNEL_PROTOCOL_L2TP] = "l2tp",
+	[SXE2_UDP_TUNNEL_PROTOCOL_TEREDO] = "teredo"
+};
+
+static enum sxe2_udp_tunnel_protocol sxe2_udp_tunnel_type_str2proto(const char *tunnel_type)
+{
+	enum sxe2_udp_tunnel_protocol proto;
+
+	for (proto = 0; proto < SXE2_UDP_TUNNEL_MAX; proto++) {
+		if (tunnel_type_list[proto] != NULL &&
+		    strcmp(tunnel_type_list[proto], tunnel_type) == 0) {
+			break;
+		}
+	}
+
+	return proto;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_udp_tunnel_operations, 26.07)
+int32_t
+sxe2_udp_tunnel_operations(uint16_t port_id, struct cmdline *cl, uint8_t action,
+			   uint16_t udp_port, const char *tunnel_type)
+{
+	enum sxe2_udp_tunnel_protocol proto = sxe2_udp_tunnel_type_str2proto(tunnel_type);
+	struct rte_eth_dev            *dev = NULL;
+	struct sxe2_adapter           *adapter = NULL;
+	struct sxe2_udp_tunnel_cfg    tunnel_config = { 0 };
+	int32_t ret   = -1;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	if (proto >= SXE2_UDP_TUNNEL_MAX) {
+		cmdline_printf(cl, "Invalid tunnel type!\n");
+		goto l_end;
+	}
+	adapter = dev->data->dev_private;
+	switch (action) {
+	case SXE2_TESTPMD_CMD_UDP_TUNNEL_ADD:
+		ret = sxe2_udp_tunnel_port_add_common(adapter, proto, udp_port);
+		break;
+	case SXE2_TESTPMD_CMD_UDP_TUNNEL_DEL:
+		ret = sxe2_udp_tunnel_port_del_common(adapter, proto, udp_port);
+		break;
+	case SXE2_TESTPMD_CMD_UDP_TUNNEL_GET:
+		tunnel_config.protocol = proto;
+		ret = sxe2_udp_tunnel_port_get_common(adapter, &tunnel_config);
+		if (!ret) {
+			cmdline_printf(cl, "Dump firmware udp tunnel config: [proto:%s, port:%d,"
+				"enable:%d, src/dst:%d/%d, used:%d]\n",
+				 tunnel_type_list[proto], tunnel_config.fw_port,
+				 tunnel_config.fw_status, tunnel_config.fw_src_en,
+				 tunnel_config.fw_dst_en, tunnel_config.fw_used);
+		}
+		break;
+	default:
+	break;
+	}
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_stats_info_show, 26.07)
+int32_t
+sxe2_stats_info_show(uint16_t port_id)
+{
+	struct rte_eth_dev *dev = NULL;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int32_t sxe2_ipsec_init_mempools(void *sec_ctx)
+{
+	uint16_t nb_sess = 8192;
+	uint32_t sess_sz;
+	char s[64];
+	int32_t ret = -1;
+
+	sess_sz = rte_security_session_get_size(sec_ctx);
+	if (g_sess_pool == NULL) {
+		snprintf(s, sizeof(s), "sess_pool");
+		g_sess_pool = rte_mempool_create(s, nb_sess, sess_sz,
+				MEMPOOL_CACHE_SIZE, 0,
+				NULL, NULL, NULL, NULL,
+				SOCKET_ID_ANY, 0);
+		if (g_sess_pool == NULL) {
+			ret = -ENOMEM;
+			PMD_LOG_ERR(DRV, "Failed to malloc session pool memory.");
+			goto l_end;
+		}
+	}
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static void sxe2_ipsec_init_session_mgt(void)
+{
+	uint16_t i;
+	uint8_t port_id;
+
+	if (g_sxe2_ipsec_mgt_init)
+		return;
+
+	for (port_id = 0; port_id < SXE2_IPSEC_PORT_MAX; port_id++) {
+		for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+			g_tx_session[port_id][i].session = NULL;
+			g_tx_session[port_id][i].encrypt_algo = SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_NULL;
+			g_tx_session[port_id][i].auth_algo = SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL;
+			g_tx_session[port_id][i].session_id = i;
+			g_tx_session[port_id][i].status = 0;
+		}
+	}
+
+	for (port_id = 0; port_id < SXE2_IPSEC_PORT_MAX; port_id++) {
+		for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+			g_rx_session[port_id][i].session = NULL;
+			g_rx_session[port_id][i].encrypt_algo = SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_NULL;
+			g_rx_session[port_id][i].auth_algo = SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL;
+			g_rx_session[port_id][i].session_id = i;
+			g_rx_session[port_id][i].status = 0;
+		}
+	}
+
+	g_sxe2_ipsec_mgt_init = true;
+}
+
+static uint16_t sxe2_ipsec_session_mgt_alloc(enum sxe2_testpmd_ipsec_dir dir, uint16_t port_id)
+{
+	uint16_t i;
+	uint16_t index = 0XFFFF;
+	struct sxe2_ipsec_session_mgt *mgt = NULL;
+
+	if (dir == SXE2_TESTPMD_CMD_IPSEC_DIR_EGRESS)
+		mgt = g_tx_session[port_id];
+	else
+		mgt = g_rx_session[port_id];
+
+	for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+		if (mgt[i].status == 0) {
+			index = i;
+			mgt[i].status = 1;
+			break;
+		}
+	}
+
+	return index;
+}
+
+static void sxe2_ipsec_session_mgt_free(enum sxe2_testpmd_ipsec_dir dir,
+					uint16_t index, uint16_t port_id)
+{
+	struct sxe2_ipsec_session_mgt *mgt = NULL;
+
+	if (dir == SXE2_TESTPMD_CMD_IPSEC_DIR_EGRESS)
+		mgt = g_tx_session[port_id];
+	else
+		mgt = g_rx_session[port_id];
+
+	mgt[index].session = NULL;
+	mgt[index].status = 0;
+}
+
+static int32_t sxe2_ipsec_egress_construct(struct cmdline *cl,
+					   struct rte_crypto_sym_xform **xform,
+					   struct sxe2_ipsec_conf_param *param)
+{
+	struct rte_crypto_sym_xform *cur_xform = NULL;
+	struct rte_crypto_sym_xform *next_xform = NULL;
+	int32_t ret = -1;
+
+	cur_xform = rte_zmalloc("current xform",
+				sizeof(struct rte_crypto_sym_xform), 0);
+	if (cur_xform == NULL) {
+		ret = -ENOMEM;
+		cmdline_printf(cl, "Failed to malloc memory!\n");
+		goto l_end;
+	}
+	cur_xform->type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+	cur_xform->cipher.op = RTE_CRYPTO_CIPHER_OP_ENCRYPT;
+	if (param->encrypt_algo == SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC)
+		cur_xform->cipher.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC;
+	else
+		cur_xform->cipher.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC;
+	cur_xform->cipher.key.length = param->enc_len;
+	cur_xform->cipher.key.data = param->enc_key;
+
+	if (param->auth_algo == SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL) {
+		ret = 0;
+		goto l_end;
+	}
+
+	next_xform = rte_zmalloc("next xform",
+				sizeof(struct rte_crypto_sym_xform), 0);
+	if (next_xform == NULL) {
+		rte_free(cur_xform);
+		ret = -ENOMEM;
+		cmdline_printf(cl, "Failed to malloc memory!\n");
+		goto l_end;
+	}
+	next_xform->type = RTE_CRYPTO_SYM_XFORM_AUTH;
+	next_xform->auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+	if (param->auth_algo == SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SHA_HMAC)
+		next_xform->auth.algo = SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC;
+	else
+		next_xform->auth.algo = SXE2_RTE_CRYPTO_AUTH_SM3_HMAC;
+	next_xform->auth.key.length = param->auth_len;
+	next_xform->auth.key.data = param->auth_key;
+	cur_xform->next = next_xform;
+	ret = 0;
+
+l_end:
+	*xform = cur_xform;
+	return ret;
+}
+
+static int32_t sxe2_ipsec_ingress_construct(struct cmdline *cl,
+					    struct rte_crypto_sym_xform **xform,
+					    struct sxe2_ipsec_conf_param *param)
+{
+	struct rte_crypto_sym_xform *cur_xform = NULL;
+	struct rte_crypto_sym_xform *next_xform = NULL;
+	int32_t ret = -1;
+
+	cur_xform = rte_zmalloc("current xform",
+				sizeof(struct rte_crypto_sym_xform), 0);
+	if (cur_xform == NULL) {
+		ret = -ENOMEM;
+		cmdline_printf(cl, "Failed to malloc memory!\n");
+		goto l_end;
+	}
+
+	if (param->auth_algo == SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL) {
+		cur_xform->type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+		cur_xform->cipher.op = RTE_CRYPTO_CIPHER_OP_DECRYPT;
+		if (param->encrypt_algo == SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC)
+			cur_xform->cipher.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC;
+		else
+			cur_xform->cipher.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC;
+		cur_xform->cipher.key.length = param->enc_len;
+		cur_xform->cipher.key.data = param->enc_key;
+		ret = 0;
+		goto l_end;
+	}
+
+	cur_xform->type = RTE_CRYPTO_SYM_XFORM_AUTH;
+	cur_xform->auth.op = RTE_CRYPTO_AUTH_OP_VERIFY;
+	if (param->auth_algo == SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SHA_HMAC)
+		cur_xform->auth.algo = SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC;
+	else
+		cur_xform->auth.algo = SXE2_RTE_CRYPTO_AUTH_SM3_HMAC;
+
+	cur_xform->auth.key.length = param->auth_len;
+	cur_xform->auth.key.data = param->auth_key;
+
+	next_xform = rte_zmalloc("next xform",
+				 sizeof(struct rte_crypto_sym_xform), 0);
+	if (next_xform == NULL) {
+		rte_free(cur_xform);
+		ret = -ENOMEM;
+		cmdline_printf(cl, "Failed to malloc memory!\n");
+		goto l_end;
+	}
+
+	next_xform->type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+	next_xform->cipher.op = RTE_CRYPTO_CIPHER_OP_DECRYPT;
+	if (param->encrypt_algo == SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC)
+		next_xform->cipher.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC;
+	else
+		next_xform->cipher.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC;
+	next_xform->cipher.key.length = param->enc_len;
+	next_xform->cipher.key.data = param->enc_key;
+	cur_xform->next = next_xform;
+	ret = 0;
+
+l_end:
+	*xform = cur_xform;
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_ingress_create, 26.07)
+int32_t
+sxe2_ipsec_ingress_create(struct sxe2_ipsec_conf_param *param, struct cmdline *cl)
+{
+	struct rte_eth_dev *dev       = NULL;
+	struct rte_security_session_conf conf;
+	struct rte_crypto_sym_xform *encrypt_xform = NULL;
+	void *session = NULL;
+	struct rte_security_ctx *p_ctx = NULL;
+	int32_t ret = -1;
+	uint16_t index;
+	uint8_t i;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(param->port_id, -ENODEV);
+
+	dev = &rte_eth_devices[param->port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	if (dev->data->dev_started != 0) {
+		cmdline_printf(cl, "port %d must be stopped.\n", dev->data->port_id);
+		ret = 0;
+		goto l_end;
+	}
+
+	p_ctx = rte_eth_dev_get_sec_ctx(param->port_id);
+
+	if (g_sess_pool == NULL) {
+		ret = sxe2_ipsec_init_mempools(p_ctx);
+		if (ret)
+			goto l_end;
+	}
+
+	sxe2_ipsec_init_session_mgt();
+
+	memset(&conf, 0, sizeof(conf));
+	conf.protocol = RTE_SECURITY_PROTOCOL_IPSEC;
+	conf.action_type = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO;
+	conf.ipsec.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL;
+	conf.ipsec.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP;
+	conf.ipsec.direction = RTE_SECURITY_IPSEC_SA_DIR_INGRESS;
+	conf.ipsec.spi = param->spi;
+	conf.ipsec.udp.sport = param->sport;
+	conf.ipsec.udp.dport = param->dport;
+	conf.ipsec.tunnel.type = param->ip_addr.type;
+	if (param->sport || param->dport)
+		conf.ipsec.options.udp_encap = true;
+	if (param->ip_addr.type == RTE_SECURITY_IPSEC_TUNNEL_IPV4)
+		conf.ipsec.tunnel.ipv4.dst_ip.s_addr = param->ip_addr.dst_ipv4;
+	else
+		memcpy(&conf.ipsec.tunnel.ipv6.dst_addr,
+		       &param->ip_addr.dst_ipv6,
+		       sizeof(param->ip_addr.dst_ipv6));
+
+	ret = sxe2_ipsec_ingress_construct(cl, &encrypt_xform, param);
+	if (ret)
+		goto l_end;
+	conf.crypto_xform = encrypt_xform;
+
+	session = rte_security_session_create(p_ctx, &conf, g_sess_pool);
+	if (session == NULL) {
+		ret = -1;
+		goto l_free;
+	}
+
+	index = sxe2_ipsec_session_mgt_alloc(param->dir, param->port_id);
+	if (index == 0XFFFF) {
+		ret = -1;
+		goto l_free;
+	}
+
+	g_rx_session[param->port_id][index].session = session;
+	g_rx_session[param->port_id][index].encrypt_algo = param->encrypt_algo;
+	g_rx_session[param->port_id][index].auth_algo = param->auth_algo;
+	for (i = 0; i < 32; i++) {
+		g_rx_session[param->port_id][index].enc_key[i] = param->enc_key[i];
+		g_rx_session[param->port_id][index].auth_key[i] = param->auth_key[i];
+	}
+	g_rx_session[param->port_id][index].sport = ntohs(param->sport);
+	g_rx_session[param->port_id][index].dport = ntohs(param->dport);
+	g_rx_session[param->port_id][index].spi = ntohl(param->spi);
+	memcpy(&g_rx_session[param->port_id][index].ip_addr,
+	       &param->ip_addr,
+	       sizeof(struct sxe2_ipsec_ip_param));
+
+	ret = 0;
+
+l_free:
+	if (encrypt_xform->next)
+		rte_free(encrypt_xform->next);
+	if (encrypt_xform)
+		rte_free(encrypt_xform);
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_ingress_destroy, 26.07)
+int32_t
+sxe2_ipsec_ingress_destroy(struct sxe2_ipsec_conf_param *param, struct cmdline *cl)
+{
+	struct rte_eth_dev *dev       = NULL;
+	struct rte_security_ctx *p_ctx = NULL;
+	struct rte_security_session *session = NULL;
+	int32_t ret = -1;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(param->port_id, -ENODEV);
+
+	dev = &rte_eth_devices[param->port_id];
+	if (!sxe2_is_supported(dev)) {
+		cmdline_printf(cl, "Invalid dev.\n");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	if (dev->data->dev_started != 0) {
+		cmdline_printf(cl, "port %d must be stopped.\n", dev->data->port_id);
+		ret = 0;
+		goto l_end;
+	}
+
+	if (param->session_id >= SXE2_IPSEC_SESSION_MAX) {
+		PMD_LOG_ERR(DRV, "Invalid session id.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (!g_rx_session[param->port_id][param->session_id].status) {
+		PMD_LOG_ERR(DRV, "Invalid session status.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (g_rx_session[param->port_id][param->session_id].session == NULL) {
+		PMD_LOG_ERR(DRV, "Invalid session data.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	p_ctx = rte_eth_dev_get_sec_ctx(param->port_id);
+
+	session = g_rx_session[param->port_id][param->session_id].session;
+	ret = rte_security_session_destroy(p_ctx, session);
+	if (ret)
+		goto l_end;
+	sxe2_ipsec_session_mgt_free(param->dir, param->session_id, param->port_id);
+
+	ret = 0;
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_ingress_show, 26.07)
+int32_t
+sxe2_ipsec_ingress_show(struct sxe2_ipsec_conf_param *param, struct cmdline *cl)
+{
+	struct rte_eth_dev *dev       = NULL;
+	int32_t ret = -1;
+	uint16_t i;
+	uint8_t j;
+	char encrypt_key[65];
+	char auth_key[65];
+	const char *encrypt_algo[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC] = "aes-cbc",
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_SM4_CBC] = "sm4-cbc",
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_NULL] = "null"
+	};
+
+	const char *auth_algo[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SHA_HMAC] = "sha-hmac",
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SM3_HMAC] = "sm3-hmac",
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL] = "null"
+	};
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(param->port_id, -ENODEV);
+
+	dev = &rte_eth_devices[param->port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+		if (g_rx_session[param->port_id][i].status &&
+		    g_rx_session[param->port_id][i].session) {
+			memset(encrypt_key, '\0', sizeof(encrypt_key));
+			memset(auth_key, '\0', sizeof(auth_key));
+			for (j = 0; j < 32; j++) {
+				sprintf(encrypt_key + 2 * j, "%02x",
+					g_rx_session[param->port_id][i].enc_key[j]);
+			}
+
+			if (g_rx_session[param->port_id][i].auth_algo !=
+			    SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL) {
+				for (j = 0; j < 32; j++) {
+					sprintf(auth_key + 2 * j, "%02x",
+						g_rx_session[param->port_id][i].auth_key[j]);
+				}
+			}
+
+			cmdline_printf(cl, "session_id:%u, direction:rx ,"
+				"encrypt_algo:%s, encrypt_key:0x%s,"
+				"auth_algo:%s, auth_key:0x%s, sport:%u, dport:%u, spi:%u\n",
+				i,
+				encrypt_algo[g_rx_session[param->port_id][i].encrypt_algo],
+				encrypt_key,
+				auth_algo[g_rx_session[param->port_id][i].auth_algo],
+				auth_key,
+				g_rx_session[param->port_id][i].sport,
+				g_rx_session[param->port_id][i].dport,
+				g_rx_session[param->port_id][i].spi);
+		}
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_egress_create, 26.07)
+int32_t
+sxe2_ipsec_egress_create(struct sxe2_ipsec_conf_param *param, struct cmdline *cl)
+{
+	struct rte_eth_dev *dev       = NULL;
+	struct rte_security_session_conf conf;
+	struct rte_crypto_sym_xform *encrypt_xform = NULL;
+	void *session = NULL;
+	struct rte_security_ctx *p_ctx = NULL;
+	int32_t ret = -1;
+	uint16_t index;
+	uint8_t i;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(param->port_id, -ENODEV);
+
+	dev = &rte_eth_devices[param->port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	if (dev->data->dev_started != 0) {
+		cmdline_printf(cl, "port %d must be stopped.\n", dev->data->port_id);
+		ret = 0;
+		goto l_end;
+	}
+
+	p_ctx = rte_eth_dev_get_sec_ctx(param->port_id);
+
+	if (g_sess_pool == NULL) {
+		ret = sxe2_ipsec_init_mempools(p_ctx);
+		if (ret)
+			goto l_end;
+	}
+
+	sxe2_ipsec_init_session_mgt();
+
+	memset(&conf, 0, sizeof(conf));
+	conf.protocol = RTE_SECURITY_PROTOCOL_IPSEC;
+	conf.action_type = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO;
+	conf.ipsec.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL;
+	conf.ipsec.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP;
+	conf.ipsec.direction = RTE_SECURITY_IPSEC_SA_DIR_EGRESS;
+
+	ret = sxe2_ipsec_egress_construct(cl, &encrypt_xform, param);
+	if (ret)
+		goto l_end;
+	conf.crypto_xform = encrypt_xform;
+
+	session = rte_security_session_create(p_ctx, &conf, g_sess_pool);
+	if (session == NULL) {
+		ret = -1;
+		goto l_free;
+	}
+
+	index = sxe2_ipsec_session_mgt_alloc(param->dir, param->port_id);
+	if (index == 0XFFFF) {
+		ret = -1;
+		goto l_free;
+	}
+
+	g_tx_session[param->port_id][index].session = session;
+	g_tx_session[param->port_id][index].encrypt_algo = param->encrypt_algo;
+	g_tx_session[param->port_id][index].auth_algo = param->auth_algo;
+	for (i = 0; i < 32; i++) {
+		g_tx_session[param->port_id][index].enc_key[i] = param->enc_key[i];
+		g_tx_session[param->port_id][index].auth_key[i] = param->auth_key[i];
+	}
+	ret = 0;
+
+l_free:
+	if (encrypt_xform->next)
+		rte_free(encrypt_xform->next);
+	if (encrypt_xform)
+		rte_free(encrypt_xform);
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_egress_destroy, 26.07)
+int32_t
+sxe2_ipsec_egress_destroy(struct sxe2_ipsec_conf_param *param, struct cmdline *cl)
+{
+	struct rte_eth_dev *dev       = NULL;
+	struct rte_security_ctx *p_ctx = NULL;
+	struct rte_security_session *session = NULL;
+	int32_t ret = -1;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(param->port_id, -ENODEV);
+
+	dev = &rte_eth_devices[param->port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	if (dev->data->dev_started != 0) {
+		cmdline_printf(cl, "port %d must be stopped.\n", dev->data->port_id);
+		ret = 0;
+		goto l_end;
+	}
+
+	if (param->session_id >= SXE2_IPSEC_SESSION_MAX) {
+		PMD_LOG_ERR(DRV, "Invalid session id.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (!g_tx_session[param->port_id][param->session_id].status) {
+		PMD_LOG_ERR(DRV, "Invalid session status.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (g_tx_session[param->port_id][param->session_id].session == NULL) {
+		PMD_LOG_ERR(DRV, "Invalid session data.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	p_ctx = rte_eth_dev_get_sec_ctx(param->port_id);
+
+	session = g_tx_session[param->port_id][param->session_id].session;
+	ret = rte_security_session_destroy(p_ctx, session);
+	if (ret)
+		goto l_end;
+	sxe2_ipsec_session_mgt_free(param->dir, param->session_id, param->port_id);
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_egress_show, 26.07)
+int32_t
+sxe2_ipsec_egress_show(struct sxe2_ipsec_conf_param *param, struct cmdline *cl)
+{
+	struct rte_eth_dev *dev       = NULL;
+	int32_t ret = -1;
+	uint16_t i;
+	uint8_t j;
+	char encrypt_key[65];
+	char auth_key[65];
+	const char *encrypt_algo[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC] = "aes-cbc",
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_SM4_CBC] = "sm4-cbc",
+		[SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_NULL] = "null"
+	};
+
+	const char *auth_algo[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_MAX] = {
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SHA_HMAC] = "sha-hmac",
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SM3_HMAC] = "sm3-hmac",
+		[SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL] = "null"
+	};
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(param->port_id, -ENODEV);
+
+	dev = &rte_eth_devices[param->port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+		if (g_tx_session[param->port_id][i].status &&
+		    g_tx_session[param->port_id][i].session) {
+			memset(encrypt_key, '\0', sizeof(encrypt_key));
+			memset(auth_key, '\0', sizeof(auth_key));
+			for (j = 0; j < 32; j++)
+				sprintf(encrypt_key + 2 * j, "%02x",
+					g_tx_session[param->port_id][i].enc_key[j]);
+			if (g_tx_session[param->port_id][i].auth_algo !=
+			    SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL)
+				for (j = 0; j < 32; j++)
+					sprintf(auth_key + 2 * j, "%02x",
+						g_tx_session[param->port_id][i].auth_key[j]);
+
+			cmdline_printf(cl, "id:%u, tx , encrypt_algo:%s,"
+				"encrypt_key:0x%s, auth_algo:%s, auth_key:0x%s.\n",
+				i,
+				encrypt_algo[g_tx_session[param->port_id][i].encrypt_algo],
+				encrypt_key,
+				auth_algo[g_tx_session[param->port_id][i].auth_algo],
+				auth_key);
+		}
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_conf_get, 26.07)
+int32_t
+sxe2_ipsec_conf_get(uint16_t port_id, struct cmdline *cl, char type[])
+{
+	struct rte_eth_dev *dev = NULL;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		return -ENODEV;
+	}
+	if (!strcmp(type, "session-id"))
+		cmdline_printf(cl, "session-id: %u\n",
+			g_tx_sess_id[port_id]);
+	else if (!strcmp(type, "esp-hdr-offset"))
+		cmdline_printf(cl, "esp-hdr-offset: %u\n",
+			g_esp_header_offset[port_id]);
+	else
+		cmdline_printf(cl, "Invalid type: %s\n", type);
+
+	return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_conf_set, 26.07)
+int32_t
+sxe2_ipsec_conf_set(uint16_t port_id, struct cmdline *cl, char type[], uint16_t value)
+{
+	struct rte_eth_dev *dev = NULL;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		PMD_LOG_ERR(DRV, "Invalid dev.");
+		return -ENODEV;
+	}
+	if (!strcmp(type, "session-id")) {
+		if (value >= 4096 || !g_tx_session[port_id][value].status) {
+			cmdline_printf(cl, "Invalid session-id: %u,"
+				"0 <= value <= 4095 or the session is inactive.\n", value);
+			return -EINVAL;
+		}
+		g_tx_sess_id[port_id] = value;
+		cmdline_printf(cl, "session-id: %u\n", g_tx_sess_id[port_id]);
+	} else if (!strcmp(type, "esp-hdr-offset")) {
+		if (value < 34 || value > 512) {
+			cmdline_printf(cl, "Invalid esp-hdr-offset: %u,"
+				       "34 <= value <= 512.\n", value);
+			return -EINVAL;
+		}
+		g_esp_header_offset[port_id] = value;
+		cmdline_printf(cl, "esp-hdr-offset: %u\n",
+			g_esp_header_offset[port_id]);
+	} else {
+		cmdline_printf(cl, "Invalid type: %s\n", type);
+	}
+
+	return 0;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_stats_show, 26.07)
+int32_t
+sxe2_ipsec_stats_show(uint16_t port_id)
+{
+	(void)port_id;
+	return 0;
+}
+
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(sxe2_ipsec_flush, 26.07)
+int32_t
+sxe2_ipsec_flush(uint16_t port_id, struct cmdline *cl)
+{
+	struct rte_eth_dev   *dev     = NULL;
+	struct rte_security_ctx *p_ctx = NULL;
+	struct rte_security_session *session = NULL;
+	int32_t ret = -1;
+	uint16_t i;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+
+	dev = &rte_eth_devices[port_id];
+	if (!sxe2_is_supported(dev)) {
+		cmdline_printf(cl, "Invalid dev.\n");
+		ret = -ENODEV;
+		goto l_end;
+	}
+
+	if (dev->data->dev_started != 0) {
+		cmdline_printf(cl, "port %d must be stopped.\n", dev->data->port_id);
+		ret = 0;
+		goto l_end;
+	}
+
+	p_ctx = rte_eth_dev_get_sec_ctx(port_id);
+
+	g_esp_header_offset[port_id] = 0;
+	g_tx_sess_id[port_id] = 0;
+
+	for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+		session = g_tx_session[port_id][i].session;
+		if (g_tx_session[port_id][i].status && session) {
+			ret = rte_security_session_destroy(p_ctx, session);
+			if (ret)
+				cmdline_printf(cl, "failed to destroy tx session: %d.\n", i);
+			else
+				sxe2_ipsec_session_mgt_free(SXE2_TESTPMD_CMD_IPSEC_DIR_EGRESS,
+							    i, port_id);
+		}
+	}
+
+	for (i = 0; i < SXE2_IPSEC_SESSION_MAX; i++) {
+		session = g_rx_session[port_id][i].session;
+		if (g_rx_session[port_id][i].status && session) {
+			ret = rte_security_session_destroy(p_ctx, session);
+			if (ret)
+				cmdline_printf(cl, "failed to destroy rx session: %d.\n", i);
+			else
+				sxe2_ipsec_session_mgt_free(SXE2_TESTPMD_CMD_IPSEC_DIR_INGRESS,
+							    i, port_id);
+		}
+	}
+
+	g_sxe2_ipsec_mgt_init = false;
+	ret = 0;
+
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_testpmd_lib.h b/drivers/net/sxe2/sxe2_testpmd_lib.h
new file mode 100644
index 0000000000..3d2659ef00
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_testpmd_lib.h
@@ -0,0 +1,142 @@
+
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_TESTPMD_LIB_H__
+#define __SXE2_TESTPMD_LIB_H__
+#include <cmdline.h>
+#include "sxe2_ipsec.h"
+
+#define SXE2_IPSEC_SESSION_MAX (4096)
+#define SXE2_IPSEC_PORT_MAX  RTE_MAX_ETHPORTS
+#define MEMPOOL_CACHE_SIZE (512 / 2)
+
+enum {
+	SXE2_TESTPMD_CMD_UDP_TUNNEL_ADD = 0,
+	SXE2_TESTPMD_CMD_UDP_TUNNEL_DEL = 1,
+	SXE2_TESTPMD_CMD_UDP_TUNNEL_GET = 2,
+	SXE2_TESTPMD_CMD_UDP_TUNNEL_MAX,
+};
+
+enum sxe2_testpmd_ipsec_op {
+	SXE2_TESTPMD_CMD_IPSEC_OP_ADD = 0,
+	SXE2_TESTPMD_CMD_IPSEC_OP_RM = 1,
+	SXE2_TESTPMD_CMD_IPSEC_OP_SHOW = 2,
+	SXE2_TESTPMD_CMD_IPSEC_OP_MAX,
+};
+
+enum sxe2_testpmd_ipsec_dir {
+	SXE2_TESTPMD_CMD_IPSEC_DIR_EGRESS = 0,
+	SXE2_TESTPMD_CMD_IPSEC_DIR_INGRESS = 1,
+	SXE2_TESTPMD_CMD_IPSEC_DIR_MAX,
+};
+
+enum sxe2_testpmd_ipsec_encrypt_algo {
+	SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_AES_CBC = 0,
+	SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_SM4_CBC = 1,
+	SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_NULL = 2,
+	SXE2_TESTPMD_CMD_IPSEC_EN_ALGO_MAX,
+};
+
+enum sxe2_testpmd_ipsec_auth_algo {
+	SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SHA_HMAC = 0,
+	SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_SM3_HMAC = 1,
+	SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_NULL = 2,
+	SXE2_TESTPMD_CMD_IPSEC_AUTH_ALGO_MAX,
+};
+
+struct sxe2_ipsec_conf_param {
+	enum sxe2_testpmd_ipsec_dir dir;
+	enum sxe2_testpmd_ipsec_op op;
+	enum sxe2_testpmd_ipsec_encrypt_algo encrypt_algo;
+	enum sxe2_testpmd_ipsec_auth_algo auth_algo;
+	struct sxe2_ipsec_ip_param ip_addr;
+	uint32_t spi;
+	uint16_t port_id;
+	uint16_t session_id;
+	uint16_t sport;
+	uint16_t dport;
+	uint8_t enc_key[32];
+	uint8_t enc_len;
+	uint8_t auth_key[32];
+	uint8_t auth_len;
+};
+
+struct sxe2_ipsec_session_mgt {
+	void *session;
+	enum sxe2_testpmd_ipsec_encrypt_algo encrypt_algo;
+	enum sxe2_testpmd_ipsec_auth_algo auth_algo;
+	struct sxe2_ipsec_ip_param ip_addr;
+	uint32_t spi;
+	uint16_t session_id;
+	uint16_t sport;
+	uint16_t dport;
+	uint8_t enc_key[32];
+	uint8_t auth_key[32];
+	uint8_t status;
+};
+
+__rte_experimental
+int32_t
+sxe2_testpmd_sched_reset(uint16_t port_id);
+
+__rte_experimental
+int32_t
+sxe2_flow_rule_dump(uint16_t port_id, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_udp_tunnel_operations(uint16_t port_id, struct cmdline *cl, uint8_t action,
+			   uint16_t udp_port, const char *tunnel_type);
+
+__rte_experimental
+int32_t
+sxe2_stats_info_show(uint16_t port_id);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_ingress_create(struct sxe2_ipsec_conf_param *param, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_ingress_destroy(struct sxe2_ipsec_conf_param *param, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_ingress_show(struct sxe2_ipsec_conf_param *param, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_egress_create(struct sxe2_ipsec_conf_param *param, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_egress_destroy(struct sxe2_ipsec_conf_param *param, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_egress_show(struct sxe2_ipsec_conf_param *param, struct cmdline *cl);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_conf_get(uint16_t port_id, struct cmdline *cl, char type[]);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_conf_set(uint16_t port_id, struct cmdline *cl, char type[], uint16_t value);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_stats_show(uint16_t port_id);
+
+__rte_experimental
+int32_t
+sxe2_ipsec_flush(uint16_t port_id, struct cmdline *cl);
+
+extern struct sxe2_ipsec_session_mgt g_tx_session[SXE2_IPSEC_PORT_MAX][SXE2_IPSEC_SESSION_MAX];
+extern uint16_t g_tx_sess_id[SXE2_IPSEC_PORT_MAX];
+extern uint16_t g_esp_header_offset[SXE2_IPSEC_PORT_MAX];
+extern struct rte_mempool *g_sess_pool;
+
+#endif /* __SXE2_TESTPMD_LIB_H__ */
diff --git a/drivers/net/sxe2/sxe2_tm.c b/drivers/net/sxe2/sxe2_tm.c
index 4c4f793cd5..5de9b5d3b7 100644
--- a/drivers/net/sxe2/sxe2_tm.c
+++ b/drivers/net/sxe2/sxe2_tm.c
@@ -982,6 +982,24 @@ int32_t sxe2_tm_init(struct rte_eth_dev *dev)
 	return ret;
 }
 
+int32_t sxe2_tm_conf_reset(struct rte_eth_dev *dev)
+{
+	int32_t ret;
+
+	ret = sxe2_tm_uninit(dev);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_tm_init(dev);
+	if (ret)
+		goto l_end;
+
+	PMD_LOG_DEBUG(DRV, "Tm config reset succeed.");
+
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_tm_chk_all_leaf(struct rte_eth_dev *dev)
 {
 	int32_t ret = 0;
diff --git a/drivers/net/sxe2/sxe2_tm.h b/drivers/net/sxe2/sxe2_tm.h
index c4f8da6a8e..b0bfc2091d 100644
--- a/drivers/net/sxe2/sxe2_tm.h
+++ b/drivers/net/sxe2/sxe2_tm.h
@@ -73,4 +73,6 @@ int32_t sxe2_tm_init(struct rte_eth_dev *dev);
 
 int32_t sxe2_tm_uninit(struct rte_eth_dev *dev);
 
+int32_t sxe2_tm_conf_reset(struct rte_eth_dev *dev);
+
 #endif /* __SXE2_TM_H__ */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 20/23] net/sxe2: add private devargs parsing
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

Introduce runtime private device arguments (devargs) support for the
sxe2 PMD. This allows users to customize driver behavior at startup
without recompiling the source code.

The following private parameters are added:
- high-performance-mode': Enable high-performance burst mode (0: disable,
  1: enable, default 0).

The parameters are parsed using the standard 'rte_kvargs' library during
the PCI/vdev probing phase. Documentation for these parameters is also
updated.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/sxe2_ethdev.c | 216 ++++++++++++++++++++++++++++++++-
 1 file changed, 215 insertions(+), 1 deletion(-)

diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 0b19e17c2e..4b9258edd8 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -2,6 +2,7 @@
  * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
  */
 
+#include <asm-generic/errno-base.h>
 #include <rte_string_fns.h>
 #include <ethdev_pci.h>
 #include <ctype.h>
@@ -68,7 +69,14 @@ static const struct rte_pci_id pci_id_sxe2_tbl[] = {
 	{ RTE_PCI_DEVICE(SXE2_PCI_VENDOR_ID_206F, SXE2_PCI_DEVICE_ID_VF_1)},
 	{ .vendor_id = 0, },
 };
-
+#define SXE2_TXSCH_NODE_ADJ_LVL_MAX  3
+#define SXE2_DEVARG_FLOW_DULP_PATTERN_MODE "flow-duplicate-pattern"
+#define SXE2_DEVARG_FUNC_FLOW_DIRCT "function-flow-direct"
+#define SXE2_DEVARG_FNAV_STAT_TYPE "fnav-stat-type"
+#define SXE2_DEVARG_SW_STATS "drv-sw-stats"
+#define SXE2_DEVARG_HIGH_PERFORMANCE_MODE "high-performance-mode"
+#define SXE2_DEVARG_SCHED_LAYER_MODE "sched-layer-mode"
+#define SXE2_DEVARG_RX_LOW_LATENCY "rx-low-latency"
 static struct sxe2_pci_map_addr_info sxe2_net_map_addr_info_pf[SXE2_PCI_MAP_RES_MAX_COUNT] = {
 	[SXE2_PCI_MAP_RES_INVALID] = {.addr_base = 0,
 				      .bar_idx = 0,
@@ -980,6 +988,143 @@ static inline void sxe2_init_ptype_tbl(struct rte_eth_dev *dev)
 	sxe2_init_ptype_list(ptype);
 }
 
+static int32_t sxe2_parse_fnav_stat_type(const char *key, const char *value, void *args)
+{
+	int32_t ret = -EINVAL;
+	uint8_t *num = (uint8_t *)args;
+	uint8_t fnav_stat_type = 0;
+	char *endptr = NULL;
+
+	if (value == NULL || args == NULL) {
+		ret = 0;
+		goto l_end;
+	}
+	errno = 0;
+	fnav_stat_type = (uint8_t)strtoul(value, &endptr, 10);
+	if (errno != 0 || *endptr != '\0') {
+		PMD_LOG_WARN(INIT, "%s: \"%s\" is not a valid int value.",
+			key, value);
+		goto l_end;
+	}
+	if (fnav_stat_type > SXE2_FNAV_STAT_ENA_ALL ||
+		fnav_stat_type == SXE2_FNAV_STAT_ENA_NONE) {
+		PMD_LOG_ERR(INIT, "%s: \"%s\" out of range [1-3].",
+			key, value);
+		goto l_end;
+	}
+	*num = fnav_stat_type;
+	ret = 0;
+l_end:
+	return ret;
+}
+static int32_t sxe2_parse_sched_layer_mode(const char *key, const char *value, void *args)
+{
+	int32_t ret = -EINVAL;
+	uint8_t *num = (uint8_t *)args;
+	uint8_t sched_layer_mode;
+	char *endptr = NULL;
+
+	if (value == NULL || args == NULL) {
+		ret = 0;
+		goto l_end;
+	}
+	errno = 0;
+	sched_layer_mode = (uint8_t)strtoul(value, &endptr, 10);
+	if (errno != 0 || *endptr != '\0') {
+		PMD_LOG_WARN(INIT, "%s: \"%s\" is not a valid int value.",
+			key, value);
+		goto l_end;
+	}
+	if (sched_layer_mode > SXE2_TXSCH_NODE_ADJ_LVL_MAX) {
+		PMD_LOG_ERR(INIT, "%s: \"%s\" > 3.",
+			key, value);
+		goto l_end;
+	}
+	*num = sched_layer_mode;
+	ret = 0;
+l_end:
+	return ret;
+}
+static int32_t sxe2_parse_high_performance_mode(const char *key, const char *value, void *args)
+{
+	int32_t ret = -EINVAL;
+	uint8_t *num = (uint8_t *)args;
+	uint8_t high_performance_mode;
+	char *endptr = NULL;
+
+	if (value == NULL || args == NULL) {
+		ret = 0;
+		goto l_end;
+	}
+	errno = 0;
+	high_performance_mode = (uint8_t)strtoul(value, &endptr, 10);
+	if (errno != 0 || *endptr != '\0') {
+		PMD_LOG_WARN(INIT, "%s: \"%s\" is not a valid int value.",
+			key, value);
+		goto l_end;
+	}
+	if (high_performance_mode != 1) {
+		PMD_LOG_ERR(INIT, "%s: \"%s\" != 1.",
+			key, value);
+		goto l_end;
+	}
+	*num = high_performance_mode;
+	ret = 0;
+l_end:
+	return ret;
+}
+static int32_t sxe2_parse_u8(const char *key, const char *value, void *args)
+{
+	uint8_t *num = (uint8_t *)args;
+	char *end;
+	uint8_t u8_val;
+	int32_t ret = -EINVAL;
+
+	if (value == NULL || args == NULL) {
+		ret = 0;
+		goto l_end;
+	}
+	errno = 0;
+	u8_val = strtoul(value, &end, 10);
+	if (errno) {
+		PMD_LOG_WARN(INIT, "%s: \"%s\" is not a valid uint8_t",
+			key, value);
+		goto l_end;
+	}
+	*num = u8_val;
+	ret = 0;
+l_end:
+	return ret;
+}
+static int32_t sxe2_parse_bool(const char *key, const char *value, void *args)
+{
+	int32_t ret = -EINVAL;
+	uint8_t *num = (uint8_t *)args;
+	uint8_t bool_val = 0;
+	char *endptr = NULL;
+
+	if (value == NULL || args == NULL) {
+		ret = 0;
+		goto l_end;
+	}
+	errno = 0;
+	bool_val = (uint8_t)strtoul(value, &endptr, 10);
+	if (errno != 0 || *endptr != '\0') {
+		PMD_LOG_WARN(INIT, "%s: \"%s\" is not a valid int value.",
+			key, value);
+		goto l_end;
+	}
+	if (bool_val != 0 && bool_val != 1) {
+		PMD_LOG_ERR(INIT, "%s: \"%s\" out of range [0|1].",
+			key, value);
+		goto l_end;
+	}
+	*num = bool_val;
+	ret = 0;
+l_end:
+	return ret;
+}
+
 struct sxe2_pci_map_bar_info *sxe2_dev_get_bar_info(struct sxe2_adapter *adapter,
 						    enum sxe2_pci_map_resource res_type)
 {
@@ -1047,6 +1192,69 @@ void *sxe2_pci_map_addr_get(struct sxe2_adapter *adapter,
 	return addr;
 }
 
+static int32_t sxe2_args_parse(struct rte_eth_dev *dev, struct sxe2_dev_kvargs_info *kvargs)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	PMD_INIT_FUNC_TRACE();
+
+	if (kvargs == NULL)
+		goto l_end;
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_FNAV_STAT_TYPE,
+				 &sxe2_parse_fnav_stat_type,
+				 &adapter->devargs.fnav_stat_type);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse fnav stat type, ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_SW_STATS,
+				 &sxe2_parse_bool,
+				 &adapter->devargs.sw_stats_en);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse sw stats enable, ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_HIGH_PERFORMANCE_MODE,
+				 &sxe2_parse_high_performance_mode,
+				 &adapter->devargs.high_performance_mode);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse high performance, ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_SCHED_LAYER_MODE,
+				 &sxe2_parse_sched_layer_mode,
+				 &adapter->devargs.sched_layer_mode);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse sched layer mode, ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_FLOW_DULP_PATTERN_MODE,
+				 &sxe2_parse_u8,
+				 &adapter->devargs.flow_dup_pattern_mode);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse switch dulpliate flow pattern mode,"
+				"ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_FUNC_FLOW_DIRCT,
+				 &sxe2_parse_bool,
+				 &adapter->devargs.func_flow_direct_en);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse function flow rule enable,"
+				"ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_kvargs_process(kvargs, SXE2_DEVARG_RX_LOW_LATENCY,
+				 &sxe2_parse_bool,
+				 &adapter->devargs.rx_low_latency);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to parse rx low latency, ret:%d", ret);
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_eth_init(struct rte_eth_dev *dev)
 {
 	int32_t ret = 0;
@@ -1709,6 +1917,12 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 
 	sxe2_init_ptype_tbl(dev);
 
+	ret = sxe2_args_parse(dev, kvargs);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to parse devargs, ret=%d", ret);
+		goto l_end;
+	}
+
 	ret = sxe2_hw_init(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to initialize hw, ret=[%d]", ret);
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 11/23] net/sxe2: add support for VF representors
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

Add support for VF representors in sxe2 PMD. This allows the host
application (e.g., OVS-DPDK) to control and monitor virtual functions
through a dedicated ethdev on the PF (Physical Function) side.

Key changes include:
- Added representor enumeration and identification logic.
- Implemented representor-specific dev_ops (link update, stats, etc.).
- Configured back-channel communication between PF and VF for control
  messages.
- Supported the "-a <DBDF>,representor=[0-N]" EAL parameter to
  instantiate representor ports.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/common/sxe2/sxe2_common.c          |   46 +
 drivers/common/sxe2/sxe2_common.h          |    2 +
 drivers/net/sxe2/meson.build               |    8 +-
 drivers/net/sxe2/sxe2_cmd_chnl.c           |  311 +++-
 drivers/net/sxe2/sxe2_cmd_chnl.h           |   28 +
 drivers/net/sxe2/sxe2_drv_cmd.h            |  280 +--
 drivers/net/sxe2/sxe2_ethdev.c             |  201 ++-
 drivers/net/sxe2/sxe2_ethdev.h             |   11 +
 drivers/net/sxe2/sxe2_ethdev_repr.c        |  607 +++++++
 drivers/net/sxe2/sxe2_ethdev_repr.h        |   32 +
 drivers/net/sxe2/sxe2_filter.c             |  121 +-
 drivers/net/sxe2/sxe2_filter.h             |    2 +
 drivers/net/sxe2/sxe2_flow.c               | 1337 ++++++++++++++
 drivers/net/sxe2/sxe2_flow.h               |   29 +
 drivers/net/sxe2/sxe2_flow_parse_action.c  | 1182 +++++++++++++
 drivers/net/sxe2/sxe2_flow_parse_action.h  |   23 +
 drivers/net/sxe2/sxe2_flow_parse_engine.c  |  106 ++
 drivers/net/sxe2/sxe2_flow_parse_engine.h  |   13 +
 drivers/net/sxe2/sxe2_flow_parse_pattern.c | 1822 ++++++++++++++++++++
 drivers/net/sxe2/sxe2_flow_parse_pattern.h |   40 +
 drivers/net/sxe2/sxe2_irq.c                |   54 +
 drivers/net/sxe2/sxe2_irq.h                |    4 +
 drivers/net/sxe2/sxe2_queue.c              |    6 +-
 drivers/net/sxe2/sxe2_stats.c              |   17 +-
 drivers/net/sxe2/sxe2_switchdev.c          |  332 ++++
 drivers/net/sxe2/sxe2_switchdev.h          |   33 +
 drivers/net/sxe2/sxe2_txrx.c               |    7 +
 drivers/net/sxe2/sxe2_txrx_poll.c          |    8 +
 drivers/net/sxe2/sxe2_vsi.c                |  146 ++
 drivers/net/sxe2/sxe2_vsi.h                |   12 +-
 30 files changed, 6684 insertions(+), 136 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_ethdev_repr.c
 create mode 100644 drivers/net/sxe2/sxe2_ethdev_repr.h
 create mode 100644 drivers/net/sxe2/sxe2_flow.c
 create mode 100644 drivers/net/sxe2/sxe2_flow.h
 create mode 100644 drivers/net/sxe2/sxe2_flow_parse_action.c
 create mode 100644 drivers/net/sxe2/sxe2_flow_parse_action.h
 create mode 100644 drivers/net/sxe2/sxe2_flow_parse_engine.c
 create mode 100644 drivers/net/sxe2/sxe2_flow_parse_engine.h
 create mode 100644 drivers/net/sxe2/sxe2_flow_parse_pattern.c
 create mode 100644 drivers/net/sxe2/sxe2_flow_parse_pattern.h
 create mode 100644 drivers/net/sxe2/sxe2_switchdev.c
 create mode 100644 drivers/net/sxe2/sxe2_switchdev.h

diff --git a/drivers/common/sxe2/sxe2_common.c b/drivers/common/sxe2/sxe2_common.c
index f34427c569..a5d36998e1 100644
--- a/drivers/common/sxe2/sxe2_common.c
+++ b/drivers/common/sxe2/sxe2_common.c
@@ -169,6 +169,37 @@ static int32_t sxe2_parse_class_type(const char *key, const char *value, void *a
 	return ret;
 }
 
+static int32_t sxe2_parse_driver(const char *key, const char *value, void *args)
+{
+	int32_t ret = -EINVAL;
+
+	if (value == NULL || args == NULL) {
+		ret = 0;
+		goto l_end;
+	}
+
+	if (strcmp(value, "sxe2") != 0) {
+		PMD_LOG_ERR(COM, "%s: \"%s\" is not a valid driver.",
+			key, value);
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_parse_representor(const char *key, const char *value, void *args)
+{
+	int32_t ret = 0;
+
+	if (value == NULL || args == NULL)
+		goto l_end;
+
+	PMD_LOG_INFO(COM, "representor arg %s: \"%s\".", key, value);
+
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_common_device_setup(struct sxe2_common_device *cdev)
 {
 	struct rte_pci_device *pci_dev = RTE_DEV_TO_PCI(cdev->dev);
@@ -394,6 +425,21 @@ static int32_t sxe2_common_pci_probe(struct rte_pci_driver *pci_drv __rte_unused
 			goto l_free_args;
 		}
 
+		ret = sxe2_kvargs_process(kv_info_p, SXE2_DEVARGS_KEY_DRIVER,
+				sxe2_parse_driver, NULL);
+		if (ret < 0) {
+			PMD_LOG_ERR(COM, "Unsupported sxe2 driver name: %s",
+				rte_dev->devargs->args);
+			goto l_free_args;
+		}
+
+		ret = sxe2_kvargs_process(kv_info_p, SXE2_DEVARGS_KEY_REPR,
+				sxe2_parse_representor, NULL);
+		if (ret < 0) {
+			PMD_LOG_ERR(COM, "Unsupported sxe2 driver representor: %s",
+				rte_dev->devargs->args);
+			goto l_free_args;
+		}
 	}
 
 	cdev = sxe2_common_device_alloc(rte_dev, class_type);
diff --git a/drivers/common/sxe2/sxe2_common.h b/drivers/common/sxe2/sxe2_common.h
index 5fe218db99..23cffac581 100644
--- a/drivers/common/sxe2/sxe2_common.h
+++ b/drivers/common/sxe2/sxe2_common.h
@@ -18,6 +18,8 @@
 	((cdev)->config.cmd_fd)
 
 #define SXE2_DEVARGS_KEY_CLASS "class"
+#define SXE2_DEVARGS_KEY_DRIVER "driver"
+#define SXE2_DEVARGS_KEY_REPR "representor"
 
 struct sxe2_class_driver;
 
diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index 0658b2ee3a..61925ee80f 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -71,6 +71,12 @@ sources += files(
         'sxe2_mp.c',
         'sxe2_stats.c',
         'sxe2_irq.c',
+        'sxe2_switchdev.c',
+        'sxe2_ethdev_repr.c',
+        'sxe2_flow.c',
+        'sxe2_flow_parse_action.c',
+        'sxe2_flow_parse_pattern.c',
+        'sxe2_flow_parse_engine.c',
 )
 
-allow_internal_get_api = true
\ No newline at end of file
+allow_internal_get_api = true
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index d1f15084ed..6e2dd139a5 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -64,6 +64,23 @@ int32_t sxe2_drv_dev_caps_get(struct sxe2_adapter *adapter, struct sxe2_drv_dev_
 	return ret;
 }
 
+int32_t sxe2_drv_switchdev_info_get(struct sxe2_adapter *adapter,
+				    struct sxe2_switchdev_info *switchdev_info)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_DEV_GET_SWITCHDEV_INFO,
+				 NULL, 0, switchdev_info,
+				 sizeof(struct sxe2_switchdev_info));
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "get switchdev info failed, ret=%d", ret);
+
+	return ret;
+}
+
 int32_t sxe2_drv_dev_info_get(struct sxe2_adapter *adapter,
 				struct sxe2_drv_dev_info_resp *dev_info_resp)
 {
@@ -167,7 +184,11 @@ static int32_t sxe2_rxq_ctxt_cfg_fill(struct sxe2_rx_queue *rxq,
 	req->q_cnt = rxq_cnt;
 	req->max_frame_size = dev_data->mtu + SXE2_ETH_OVERHEAD;
 
-	ctxt->queue_id = rxq->queue_id;
+	if (adapter->is_dev_repr)
+		ctxt->queue_id = adapter->repr_priv_data->repr_q_id;
+	else
+		ctxt->queue_id = rxq->queue_id;
+
 	ctxt->depth = rxq->ring_depth;
 	ctxt->buf_len = RTE_ALIGN(rxq->rx_buf_len, SXE2_RXQ_CTXT_CFG_BUF_LEN_ALIGN);
 	ctxt->dma_addr = rxq->base_addr;
@@ -241,7 +262,10 @@ static void sxe2_txq_ctxt_cfg_fill(struct sxe2_tx_queue *txq,
 		ctxt = &req->cfg[q_idx];
 		ctxt->depth = txq[q_idx].ring_depth;
 		ctxt->dma_addr = txq[q_idx].base_addr;
-		ctxt->queue_id = txq[q_idx].queue_id;
+		if (adapter->is_dev_repr)
+			ctxt->queue_id = adapter->repr_priv_data->repr_q_id;
+		else
+			ctxt->queue_id = txq[q_idx].queue_id;
 
 		ctxt->sched_mode = sxe2_sched_mode_get(adapter);
 	}
@@ -288,7 +312,10 @@ int32_t sxe2_drv_rxq_switch(struct sxe2_adapter *adapter, struct sxe2_rx_queue *
 	struct sxe2_drv_q_switch_req req;
 
 	req.vsi_id = rte_cpu_to_le_16(rxq->vsi->vsi_id);
-	req.q_idx = rxq->queue_id;
+	if (adapter->is_dev_repr)
+		req.q_idx = adapter->repr_priv_data->repr_q_id;
+	else
+		req.q_idx = rxq->queue_id;
 
 	req.is_enable  = (uint8_t)enable;
 	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RXQ_DISABLE,
@@ -310,7 +337,10 @@ int32_t sxe2_drv_txq_switch(struct sxe2_adapter *adapter, struct sxe2_tx_queue *
 	struct sxe2_drv_q_switch_req req;
 
 	req.vsi_id = rte_cpu_to_le_16(txq->vsi->vsi_id);
-	req.q_idx = txq->queue_id;
+	if (adapter->is_dev_repr)
+		req.q_idx = adapter->repr_priv_data->repr_q_id;
+	else
+		req.q_idx = txq->queue_id;
 
 	req.is_enable  = (uint8_t)enable;
 	req.sched_mode = sxe2_sched_mode_get(adapter);
@@ -326,6 +356,37 @@ int32_t sxe2_drv_txq_switch(struct sxe2_adapter *adapter, struct sxe2_tx_queue *
 	return ret;
 }
 
+int32_t sxe2_drv_vsi_info_get(struct sxe2_adapter *adapter, struct sxe2_vsi *vsi)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_drv_vsi_info_get_req vsi_info_get_req = {0};
+	struct sxe2_drv_vsi_info_get_resp vsi_info_get_resp = {0};
+
+	vsi_info_get_req.vsi_id = vsi->vsi_id;
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_VSI_INFO_GET,
+			&vsi_info_get_req, sizeof(vsi_info_get_req),
+			&vsi_info_get_resp, sizeof(vsi_info_get_resp));
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "switchdev cpvsi info get failed, ret=%d", ret);
+		goto l_end;
+	}
+
+	vsi->txqs.q_cnt = vsi_info_get_resp.used_queues.queues_cnt;
+	vsi->txqs.base_idx_in_func = vsi_info_get_resp.used_queues.base_idx_in_pf;
+
+	vsi->rxqs.q_cnt = vsi_info_get_resp.used_queues.queues_cnt;
+	vsi->rxqs.base_idx_in_func = vsi_info_get_resp.used_queues.base_idx_in_pf;
+
+	vsi->irqs.avail_cnt = vsi_info_get_resp.used_msix.msix_vectors_cnt;
+	vsi->irqs.base_idx_in_pf = vsi_info_get_resp.used_msix.base_idx_in_func;
+
+l_end:
+	return ret;
+}
+
 int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter)
 {
 	int32_t ret = 0;
@@ -614,6 +675,101 @@ int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set)
 	return ret;
 }
 
+int32_t sxe2_drv_switchdev_uplink_config(struct sxe2_adapter *adapter,  bool set)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_switchdev_uplink_info switchdev_uplink_info_req = {0};
+
+	switchdev_uplink_info_req.pf_id = adapter->pf_idx;
+	switchdev_uplink_info_req.is_set = set;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_SWITCH_UPLINK,
+				 &switchdev_uplink_info_req,
+				 sizeof(switchdev_uplink_info_req),
+				 NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "switchdev uplink info config failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_switchdev_repr_vf_config(struct sxe2_adapter *adapter,
+				      struct sxe2_switchdev_repr_info *repr_vf,
+				      bool set)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_switchdev_repr_info switchdev_repr_info_req = {0};
+
+	switchdev_repr_info_req.pf_id = adapter->pf_idx;
+	switchdev_repr_info_req.is_set = set;
+	switchdev_repr_info_req.cp_vsi_id = repr_vf->cp_vsi_id;
+	switchdev_repr_info_req.repr_pf_id = repr_vf->repr_pf_id;
+	switchdev_repr_info_req.repr_vf_id = repr_vf->repr_vf_id;
+	switchdev_repr_info_req.repr_q_id = repr_vf->repr_q_id;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_SWITCH_REPR,
+			&switchdev_repr_info_req,
+			sizeof(switchdev_repr_info_req),
+			NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "switchdev repr info config failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_switchdev_mode_get(struct sxe2_adapter *adapter, bool *is_switchdev)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_switchdev_mode_info switchdev_mode_info_req = {0};
+	struct sxe2_switchdev_mode_info switchdev_mode_info_resp = {0};
+
+	switchdev_mode_info_req.pf_id = adapter->pf_idx;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_SWITCH_MODE,
+				 &switchdev_mode_info_req,
+				 sizeof(switchdev_mode_info_req),
+				 &switchdev_mode_info_resp,
+				 sizeof(switchdev_mode_info_resp));
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "switchdev mode info get failed, ret=%d", ret);
+	else
+		*is_switchdev = (bool)switchdev_mode_info_resp.is_switchdev;
+
+	return ret;
+}
+
+int32_t sxe2_drv_switchdev_cpvsi_get(struct sxe2_adapter *adapter, uint16_t *cp_vsi_id)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_switchdev_cpvsi_info switchdev_cpvsi_resp = {0};
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_SWITCH_CPVSI,
+				 NULL, 0,
+				 &switchdev_cpvsi_resp,
+				 sizeof(switchdev_cpvsi_resp));
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "switchdev cpvsi info get failed, ret=%d", ret);
+	else
+		*cp_vsi_id = switchdev_cpvsi_resp.cp_vsi_id;
+
+	return ret;
+}
+
 int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add)
 {
 	int32_t ret = 0;
@@ -1434,3 +1590,150 @@ int32_t sxe2_drv_mapping_stats_info_clear(struct rte_eth_dev *eth_dev)
 
 	return ret;
 }
+
+int32_t sxe2_drv_flow_filter_add(struct sxe2_adapter *adapter, struct sxe2_flow *flow)
+{
+	struct sxe2_drv_flow_filter_req req = { 0 };
+	struct sxe2_drv_flow_filter_resp resp = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	memcpy(&req.pattern_inner, &flow->pattern_inner, sizeof(req.pattern_inner));
+	memcpy(&req.pattern_outer, &flow->pattern_outer, sizeof(req.pattern_outer));
+	memcpy(&req.action, &flow->action, sizeof(req.action));
+	memcpy(&req.meta, &flow->meta, sizeof(req.meta));
+	req.engine_type = flow->engine_type;
+	req.flow_id = 0;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_FLOW_FILTER_ADD, &req,
+			   sizeof(req), &resp, sizeof(resp));
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add flow filter, ret: %d.", ret);
+	flow->flow_id = resp.flow_id;
+	flow->create_err = ret;
+	return ret;
+}
+
+int32_t sxe2_drv_flow_filter_del(struct sxe2_adapter *adapter, struct sxe2_flow *flow)
+{
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_drv_flow_filter_req req = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+	memcpy(&req.pattern_inner, &flow->pattern_inner, sizeof(req.pattern_inner));
+	memcpy(&req.pattern_outer, &flow->pattern_outer, sizeof(req.pattern_outer));
+	memcpy(&req.action, &flow->action, sizeof(req.action));
+	memcpy(&req.meta, &flow->meta, sizeof(req.meta));
+	req.engine_type = flow->engine_type;
+	req.flow_id = flow->flow_id;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_FLOW_FILTER_DEL, &req,
+			   sizeof(req), NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV,
+			"Failed to delete flow filter, flow id: %u, ret: %d.",
+			flow->flow_id, ret);
+	return ret;
+}
+
+int32_t sxe2_drv_flow_fnav_get_stat_id(struct sxe2_adapter *adapter, uint32_t *stat_id)
+{
+	struct sxe2_drv_flow_fnav_get_stat_id_req req = { 0 };
+	struct sxe2_drv_flow_fnav_get_stat_id_resp resp = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_FLOW_FNAV_STAT_ALLOC,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to get fnav stat id, ret: %d.", ret);
+		goto l_end;
+	}
+	*stat_id = resp.stat_id;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_flow_fnav_free_stat(struct sxe2_adapter *adapter, uint32_t stat_id)
+{
+	struct sxe2_drv_flow_fnav_free_stat_id_req req = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	req.stat_id = stat_id;
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_FLOW_FNAV_STAT_FREE,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to free fnav stat id, ret: %d.", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_flow_fnav_query_stat(struct sxe2_adapter *adapter,
+		struct sxe2_fnav_cid_mgr *mgr)
+{
+	struct sxe2_drv_flow_fnav_query_stat_req req = { 0 };
+	struct sxe2_drv_flow_fnav_query_stat_resp resp = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	req.stat_id = mgr->stat_index;
+	req.stat_ctrl = mgr->count_type;
+	req.is_clear = 1;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_FLOW_FNAV_STAT_QUERY,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV,
+			"Failed to query fnav stat, stat id: %u, ret: %d.",
+			req.stat_id, ret);
+		goto l_end;
+	}
+	mgr->hits += resp.stat_hits;
+	mgr->bytes += resp.stat_bytes;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_srcvsi_prune_config(struct sxe2_adapter *adapter,
+			uint16_t *vsi_list, uint16_t vsi_cnt, bool set)
+{
+	int32_t ret = 0;
+	uint16_t idx;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_srcvsi_ext_cfg_req srcvsi_list_prune_cfg_req = {0};
+
+	srcvsi_list_prune_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	srcvsi_list_prune_cfg_req.is_add = set;
+	srcvsi_list_prune_cfg_req.srcvsi_cnt = vsi_cnt;
+	for (idx = 0; idx < vsi_cnt; idx++)
+		srcvsi_list_prune_cfg_req.srcvsi_list[idx] = vsi_list[idx];
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_VSI_SRCVSI_PRUNE,
+				 &srcvsi_list_prune_cfg_req,
+				 sizeof(srcvsi_list_prune_cfg_req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "srcvsi prune config failed, ret=%d", ret);
+
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index c13653e8af..d505f93dc1 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -12,6 +12,9 @@
 int32_t sxe2_drv_dev_caps_get(struct sxe2_adapter *adapter,
 		struct sxe2_drv_dev_caps_resp *dev_caps);
 
+int32_t sxe2_drv_switchdev_info_get(struct sxe2_adapter *adapter,
+		struct sxe2_switchdev_info *switchdev_info);
+
 int32_t sxe2_drv_dev_info_get(struct sxe2_adapter *adapter,
 		struct sxe2_drv_dev_info_resp *dev_info_resp);
 
@@ -64,6 +67,8 @@ int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
 
 int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set);
 
+int32_t sxe2_drv_vsi_info_get(struct sxe2_adapter *adapter, struct sxe2_vsi *vsi);
+
 int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter);
 
 int32_t sxe2_drv_get_mac_stats(struct sxe2_adapter *adapter);
@@ -91,6 +96,15 @@ int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set);
 
 int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
 
+int32_t sxe2_drv_switchdev_uplink_config(struct sxe2_adapter *adapter,  bool set);
+
+int32_t sxe2_drv_switchdev_repr_vf_config(struct sxe2_adapter *adapter,
+				struct sxe2_switchdev_repr_info *repr_vf, bool set);
+
+int32_t sxe2_drv_switchdev_cpvsi_get(struct sxe2_adapter *adapter, uint16_t *cp_vsi_id);
+
+int32_t sxe2_drv_switchdev_mode_get(struct sxe2_adapter *adapter, bool *is_switchdev);
+
 int32_t sxe2_drv_mc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
 
 int32_t sxe2_drv_vlan_config_query(struct sxe2_adapter *adapter);
@@ -122,4 +136,18 @@ int32_t sxe2_drv_rxq_bind_irq(struct sxe2_adapter *adapter, uint16_t rxq_idx, ui
 
 int32_t sxe2_drv_rxq_unbind_irq(struct sxe2_adapter *adapter, uint16_t rxq_idx);
 
+int32_t sxe2_drv_flow_filter_add(struct sxe2_adapter *adapter, struct sxe2_flow *flow);
+
+int32_t sxe2_drv_flow_filter_del(struct sxe2_adapter *adapter, struct sxe2_flow *flow);
+
+int32_t sxe2_drv_flow_fnav_get_stat_id(struct sxe2_adapter *adapter, uint32_t *stat_id);
+
+int32_t sxe2_drv_flow_fnav_free_stat(struct sxe2_adapter *adapter, uint32_t stat_id);
+
+int32_t sxe2_drv_flow_fnav_query_stat(struct sxe2_adapter *adapter,
+		struct sxe2_fnav_cid_mgr *mgr);
+
+int32_t sxe2_drv_srcvsi_prune_config(struct sxe2_adapter *adapter,
+		uint16_t *vsi_list, uint16_t vsi_cnt, bool set);
+
 #endif /* __SXE2_CMD_CHNL_H__ */
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index 0b2a715000..48c012367c 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -108,7 +108,6 @@ enum sxe2_phys_port_name_type {
 	SXE2_PHYS_PORT_NAME_TYPE_LEGACY,
 	SXE2_PHYS_PORT_NAME_TYPE_UPLINK,
 	SXE2_PHYS_PORT_NAME_TYPE_PFVF,
-
 	SXE2_PHYS_PORT_NAME_TYPE_UNKNOWN,
 };
 
@@ -123,12 +122,27 @@ struct sxe2_switchdev_info {
 	uint32_t mpesw_owner;
 };
 
+struct sxe2_switchdev_uplink_info {
+	uint8_t pf_id;
+	uint8_t is_set;
+	uint8_t rsv[2];
+};
+
+struct sxe2_switchdev_repr_info {
+	uint8_t pf_id;
+	uint8_t is_set;
+	uint8_t rsv[2];
+	uint16_t cp_vsi_id;
+	uint16_t repr_pf_id;
+	uint16_t repr_vf_id;
+	uint16_t repr_q_id;
+};
+
 struct sxe2_switchdev_mode_info {
 	uint8_t pf_id;
 	uint8_t is_switchdev;
 	uint8_t rsv[2];
 };
-
 struct sxe2_switchdev_cpvsi_info {
 	uint16_t cp_vsi_id;
 	uint8_t rsv[2];
@@ -216,8 +230,8 @@ struct sxe2_drv_q_switch_req {
 };
 
 struct sxe2_drv_queue_irq_bind_req {
-	__le16 q_idx;
-	__le16 msix_idx;
+	uint16_t q_idx;
+	uint16_t msix_idx;
 	uint8_t itr_idx;
 	uint8_t bind;
 	uint8_t rsv[2];
@@ -253,6 +267,44 @@ struct sxe2_drv_link_info_resp {
 	uint8_t rsv[3];
 };
 
+struct sxe2_drv_flow_filter_req {
+	uint32_t flow_id;
+	struct sxe2_flow_meta meta;
+	enum sxe2_flow_engine_type engine_type;
+	struct sxe2_flow_pattern pattern_outer;
+	struct sxe2_flow_pattern pattern_inner;
+	struct sxe2_flow_action action;
+};
+
+struct sxe2_drv_flow_filter_resp {
+	enum sxe2_flow_engine_type engine_type;
+	uint32_t flow_id;
+};
+
+struct sxe2_drv_flow_fnav_get_stat_id_req {
+	uint8_t need_update;
+};
+
+struct sxe2_drv_flow_fnav_get_stat_id_resp {
+	uint32_t stat_id;
+};
+
+struct sxe2_drv_flow_fnav_free_stat_id_req {
+	uint32_t stat_id;
+};
+
+struct sxe2_drv_flow_fnav_query_stat_req {
+	uint32_t stat_id;
+	uint32_t stat_ctrl;
+	uint32_t is_clear;
+};
+
+struct sxe2_drv_flow_fnav_query_stat_resp {
+	uint32_t stat_index;
+	uint64_t stat_hits;
+	uint64_t stat_bytes;
+};
+
 struct sxe2_drv_vlan_cfg_query_resp {
 	uint16_t vsi_id;
 	uint8_t port_vlan_exist;
@@ -330,25 +382,25 @@ struct sxe2_vlan_filter_switch_req {
 };
 
 struct sxe2_rss_key_req {
-	__le16 vsi_id;
-	__le16 key_size;
+	uint16_t vsi_id;
+	uint16_t key_size;
 	uint8_t key[];
 };
 
 struct sxe2_rss_lut_req {
-	__le16 vsi_id;
-	__le16 lut_size;
+	uint16_t vsi_id;
+	uint16_t lut_size;
 	uint8_t lut[];
 };
 
 struct sxe2_rss_func_req {
-	__le16 vsi_id;
+	uint16_t vsi_id;
 	uint8_t func;
 	uint8_t rsv[1];
 };
 
 struct sxe2_rss_hf_req {
-	__le16 vsi_id;
+	uint16_t vsi_id;
 	uint8_t rsv[2];
 	uint32_t headers[BITS_TO_U32(SXE2_FLOW_HDR_MAX)];
 	uint32_t hash_flds[BITS_TO_U32(SXE2_FLOW_FLD_ID_MAX)];
@@ -358,7 +410,7 @@ struct sxe2_rss_hf_req {
 };
 
 struct sxe2_tm_res {
-	__le16 teid;
+	uint16_t teid;
 };
 
 struct sxe2_tm_info {
@@ -366,32 +418,32 @@ struct sxe2_tm_info {
 	uint32_t peak;
 	uint8_t priority;
 	uint8_t reserve;
-	__le16 weight;
+	uint16_t weight;
 };
 
 struct sxe2_tm_add_mid_msg {
-	__le16 parent_teid;
+	uint16_t parent_teid;
 	uint8_t adj_lvl;
 	struct sxe2_tm_info info;
 };
 struct sxe2_tm_add_queue_msg {
-	__le16 parent_teid;
-	__le16 queue_id;
+	uint16_t parent_teid;
+	uint16_t queue_id;
 	uint8_t adj_lvl;
 	struct sxe2_tm_info info;
 };
 
 struct sxe2_drv_ipsec_capa_resq {
-	__le16 tx_sa_cnt;
-	__le16 rx_sa_cnt;
-	__le16 ip_id_cnt;
-	__le16 udp_group_cnt;
+	uint16_t tx_sa_cnt;
+	uint16_t rx_sa_cnt;
+	uint16_t ip_id_cnt;
+	uint16_t udp_group_cnt;
 };
 
 #define SXE2_IPSEC_KEY_LEN (32)
 #define SXE2_IPV6_ADDR_LEN (4)
 struct sxe2_drv_ipsec_txsa_add_req {
-	__le32 mode;
+	uint32_t mode;
 	uint8_t encrypt_keys[SXE2_IPSEC_KEY_LEN];
 	uint8_t auth_keys[SXE2_IPSEC_KEY_LEN];
 	bool func_type;
@@ -400,14 +452,14 @@ struct sxe2_drv_ipsec_txsa_add_req {
 };
 
 struct sxe2_drv_ipsec_txsa_add_resp {
-	__le16 index;
+	uint16_t index;
 };
 
 struct sxe2_drv_ipsec_rxsa_add_req {
-	__le32 mode;
-	__le32 spi;
-	__le32 ipaddr[SXE2_IPV6_ADDR_LEN];
-	__le32 udp_port;
+	uint32_t mode;
+	uint32_t spi;
+	uint32_t ipaddr[SXE2_IPV6_ADDR_LEN];
+	uint32_t udp_port;
 	uint8_t sport_en;
 	uint8_t dport_en;
 	uint8_t is_over_sdn;
@@ -422,11 +474,11 @@ struct sxe2_drv_ipsec_rxsa_add_req {
 struct sxe2_drv_ipsec_rxsa_add_resp {
 	uint8_t ip_id;
 	uint8_t udp_group_id;
-	__le16 sa_idx;
+	uint16_t sa_idx;
 };
 
 struct sxe2_drv_ipsec_txsa_del_req {
-	__le16 sa_idx;
+	uint16_t sa_idx;
 	bool func_type;
 	uint8_t func_id;
 	uint8_t drv_id;
@@ -435,116 +487,116 @@ struct sxe2_drv_ipsec_txsa_del_req {
 struct sxe2_drv_ipsec_rxsa_del_req {
 	uint8_t ip_id;
 	uint8_t group_id;
-	__le16 sa_idx;
-	__le32 spi;
+	uint16_t sa_idx;
+	uint32_t spi;
 	bool func_type;
 	uint8_t func_id;
 	uint8_t drv_id;
 };
 
 struct sxe2_drv_vsi_sw_stats {
-	__le64 rx_packets;
-	__le64 rx_bytes;
-	__le64 tx_packets;
-	__le64 tx_bytes;
+	uint64_t rx_packets;
+	uint64_t rx_bytes;
+	uint64_t tx_packets;
+	uint64_t tx_bytes;
 };
 
 struct sxe2_drv_vsi_stats_req {
-	__le16 vsi_id;
+	uint16_t vsi_id;
 	uint8_t  rsv[2];
 	struct sxe2_drv_vsi_sw_stats sw_stats;
 };
 
 struct sxe2_drv_vsi_stats_resp {
-	__le64 rx_vsi_unicast_packets;
-	__le64 rx_vsi_bytes;
-	__le64 tx_vsi_unicast_packets;
-	__le64 tx_vsi_bytes;
-	__le64 rx_vsi_multicast_packets;
-	__le64 tx_vsi_multicast_packets;
-	__le64 rx_vsi_broadcast_packets;
-	__le64 tx_vsi_broadcast_packets;
+	uint64_t rx_vsi_unicast_packets;
+	uint64_t rx_vsi_bytes;
+	uint64_t tx_vsi_unicast_packets;
+	uint64_t tx_vsi_bytes;
+	uint64_t rx_vsi_multicast_packets;
+	uint64_t tx_vsi_multicast_packets;
+	uint64_t rx_vsi_broadcast_packets;
+	uint64_t tx_vsi_broadcast_packets;
 };
 
 #define SXE2_MAX_USER_PRIORITY         (8)
 
 struct sxe2_drv_mac_stats_resp {
-	__le64 rx_out_of_buffer;
-	__le64 rx_qblock_drop;
-	__le64 tx_frame_good;
-	__le64 rx_frame_good;
-	__le64 rx_crc_errors;
-	__le64 tx_bytes_good;
-	__le64 rx_bytes_good;
-	__le64 tx_multicast_good;
-	__le64 tx_broadcast_good;
-	__le64 rx_multicast_good;
-	__le64 rx_broadcast_good;
-	__le64 rx_len_errors;
-	__le64 rx_out_of_range_errors;
-	__le64 rx_oversize_pkts_phy;
-	__le64 rx_symbol_err;
-	__le64 rx_pause_frame;
-	__le64 tx_pause_frame;
-	__le64 rx_discards_phy;
-	__le64 rx_discards_ips_phy;
-	__le64 tx_dropped_link_down;
-	__le64 rx_undersize_good;
-	__le64 rx_runt_error;
-	__le64 tx_bytes_good_bad;
-	__le64 tx_frame_good_bad;
-	__le64 rx_jabbers;
-	__le64 rx_size_64;
-	__le64 rx_size_65_127;
-	__le64 rx_size_128_255;
-	__le64 rx_size_256_511;
-	__le64 rx_size_512_1023;
-	__le64 rx_size_1024_1522;
-	__le64 rx_size_1523_max;
-	__le64 rx_pcs_symbol_err_phy;
-	__le64 rx_corrected_bits_phy;
-	__le64 rx_err_lane_0_phy;
-	__le64 rx_err_lane_1_phy;
-	__le64 rx_err_lane_2_phy;
-	__le64 rx_err_lane_3_phy;
-	__le64 rx_prio_buf_discard[SXE2_MAX_USER_PRIORITY];
-	__le64 rx_illegal_bytes;
-	__le64 rx_oversize_good;
-	__le64 tx_unicast;
-	__le64 tx_broadcast;
-	__le64 tx_multicast;
-	__le64 tx_vlan_packet_good;
-	__le64 tx_size_64;
-	__le64 tx_size_65_127;
-	__le64 tx_size_128_255;
-	__le64 tx_size_256_511;
-	__le64 tx_size_512_1023;
-	__le64 tx_size_1024_1522;
-	__le64 tx_size_1523_max;
-	__le64 tx_underflow_error;
-	__le64 rx_byte_good_bad;
-	__le64 rx_frame_good_bad;
-	__le64 rx_unicast_good;
-	__le64 rx_vlan_packets;
-	__le64 prio_xoff_rx[SXE2_MAX_USER_PRIORITY];
-	__le64 prio_xon_rx[SXE2_MAX_USER_PRIORITY];
-	__le64 prio_xon_tx[SXE2_MAX_USER_PRIORITY];
-	__le64 prio_xoff_tx[SXE2_MAX_USER_PRIORITY];
-	__le64 prio_xon_2_xoff[SXE2_MAX_USER_PRIORITY];
+	uint64_t rx_out_of_buffer;
+	uint64_t rx_qblock_drop;
+	uint64_t tx_frame_good;
+	uint64_t rx_frame_good;
+	uint64_t rx_crc_errors;
+	uint64_t tx_bytes_good;
+	uint64_t rx_bytes_good;
+	uint64_t tx_multicast_good;
+	uint64_t tx_broadcast_good;
+	uint64_t rx_multicast_good;
+	uint64_t rx_broadcast_good;
+	uint64_t rx_len_errors;
+	uint64_t rx_out_of_range_errors;
+	uint64_t rx_oversize_pkts_phy;
+	uint64_t rx_symbol_err;
+	uint64_t rx_pause_frame;
+	uint64_t tx_pause_frame;
+	uint64_t rx_discards_phy;
+	uint64_t rx_discards_ips_phy;
+	uint64_t tx_dropped_link_down;
+	uint64_t rx_undersize_good;
+	uint64_t rx_runt_error;
+	uint64_t tx_bytes_good_bad;
+	uint64_t tx_frame_good_bad;
+	uint64_t rx_jabbers;
+	uint64_t rx_size_64;
+	uint64_t rx_size_65_127;
+	uint64_t rx_size_128_255;
+	uint64_t rx_size_256_511;
+	uint64_t rx_size_512_1023;
+	uint64_t rx_size_1024_1522;
+	uint64_t rx_size_1523_max;
+	uint64_t rx_pcs_symbol_err_phy;
+	uint64_t rx_corrected_bits_phy;
+	uint64_t rx_err_lane_0_phy;
+	uint64_t rx_err_lane_1_phy;
+	uint64_t rx_err_lane_2_phy;
+	uint64_t rx_err_lane_3_phy;
+	uint64_t rx_prio_buf_discard[SXE2_MAX_USER_PRIORITY];
+	uint64_t rx_illegal_bytes;
+	uint64_t rx_oversize_good;
+	uint64_t tx_unicast;
+	uint64_t tx_broadcast;
+	uint64_t tx_multicast;
+	uint64_t tx_vlan_packet_good;
+	uint64_t tx_size_64;
+	uint64_t tx_size_65_127;
+	uint64_t tx_size_128_255;
+	uint64_t tx_size_256_511;
+	uint64_t tx_size_512_1023;
+	uint64_t tx_size_1024_1522;
+	uint64_t tx_size_1523_max;
+	uint64_t tx_underflow_error;
+	uint64_t rx_byte_good_bad;
+	uint64_t rx_frame_good_bad;
+	uint64_t rx_unicast_good;
+	uint64_t rx_vlan_packets;
+	uint64_t prio_xoff_rx[SXE2_MAX_USER_PRIORITY];
+	uint64_t prio_xon_rx[SXE2_MAX_USER_PRIORITY];
+	uint64_t prio_xon_tx[SXE2_MAX_USER_PRIORITY];
+	uint64_t prio_xoff_tx[SXE2_MAX_USER_PRIORITY];
+	uint64_t prio_xon_2_xoff[SXE2_MAX_USER_PRIORITY];
 };
 
 struct sxe2_txq_map_info {
-	__le32 txq_lan_pkt_cnt;
-	__le32 txq_lan_byte_cnt;
+	uint32_t txq_lan_pkt_cnt;
+	uint32_t txq_lan_byte_cnt;
 };
 
 struct sxe2_rxq_map_info {
-	__le64 rxq_lan_in_pkt_cnt;
-	__le64 rxq_lan_in_byte_cnt;
-	__le64 rxq_fd_in_pkt_cnt;
-	__le64 rxq_mng_in_pkt_cnt;
-	__le64 rxq_mng_in_byte_cnt;
-	__le64 rxq_mng_out_pkt_cnt;
+	uint64_t rxq_lan_in_pkt_cnt;
+	uint64_t rxq_lan_in_byte_cnt;
+	uint64_t rxq_fd_in_pkt_cnt;
+	uint64_t rxq_mng_in_pkt_cnt;
+	uint64_t rxq_mng_in_byte_cnt;
+	uint64_t rxq_mng_out_pkt_cnt;
 };
 
 struct sxe2_queue_map_info {
@@ -553,12 +605,12 @@ struct sxe2_queue_map_info {
 };
 
 struct sxe2_drv_rx_map_req {
-	__le16 queue_id;
+	uint16_t queue_id;
 	uint8_t pool_idx;
 };
 
 struct sxe2_drv_tx_map_req {
-	__le16 queue_id;
+	uint16_t queue_id;
 	uint8_t pool_idx;
 };
 
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index f3fee74ddf..317101fb60 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -33,9 +33,13 @@
 #include "sxe2_ptype.h"
 #include "sxe2_common_log.h"
 #include "sxe2_mp.h"
+#include "sxe2_flow.h"
 #include "sxe2_stats.h"
 #include "sxe2_host_regs.h"
+#include "sxe2_switchdev.h"
 #include "sxe2_ioctl_chnl_func.h"
+#include "sxe2_ethdev_repr.h"
+#include "sxe2vf_regs.h"
 
 #define SXE2_PCI_VENDOR_ID_1    0x1ff2
 #define SXE2_PCI_DEVICE_ID_PF_1 0x10b1
@@ -83,6 +87,27 @@ static struct sxe2_pci_map_addr_info sxe2_net_map_addr_info_pf[SXE2_PCI_MAP_RES_
 				      .reg_width = 10},
 };
 
+static struct sxe2_pci_map_addr_info sxe2_net_map_addr_info_vf[SXE2_PCI_MAP_RES_MAX_COUNT] = {
+	[SXE2_PCI_MAP_RES_INVALID]  = {.addr_base = 0,
+				      .bar_idx = 0,
+				      .reg_width = 0},
+	[SXE2_PCI_MAP_RES_DOORBELL_TX] = {.addr_base = SXE2VF_TXQ_TAIL(0),
+				      .bar_idx = 0,
+				      .reg_width = 4},
+	[SXE2_PCI_MAP_RES_DOORBELL_RX_TAIL] = {.addr_base = SXE2VF_RXQ_TAIL(0),
+				      .bar_idx = 0,
+				      .reg_width = 4},
+	[SXE2_PCI_MAP_RES_IRQ_DYN] = {.addr_base = SXE2VF_VF_DYN_CTL(0),
+				      .bar_idx = 0,
+				      .reg_width = 4},
+	[SXE2_PCI_MAP_RES_IRQ_ITR] = {.addr_base = SXE2VF_VF_INT_ITR(0, 0),
+				      .bar_idx = 0,
+				      .reg_width = 4},
+	[SXE2_PCI_MAP_RES_IRQ_MSIX] = {.addr_base = SXE2VF_BAR4_MSIX_CTL(0),
+				      .bar_idx = 4,
+				      .reg_width = 0x10},
+};
+
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev);
 static int32_t sxe2_dev_start(struct rte_eth_dev *dev);
 static int32_t sxe2_dev_stop(struct rte_eth_dev *dev);
@@ -137,6 +162,7 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.rss_hash_update            = sxe2_dev_rss_hash_update,
 	.rss_hash_conf_get          = sxe2_dev_rss_hash_conf_get,
 
+	.flow_ops_get               = sxe2_flow_ops_get,
 	.tm_ops_get                 = sxe2_tm_ops_get,
 
 	.stats_get                  = sxe2_stats_info_get,
@@ -566,7 +592,7 @@ static int32_t sxe2_eth_init(struct rte_eth_dev *dev)
 	return ret;
 }
 
-static void sxe2_eth_uinit(struct rte_eth_dev *dev __rte_unused)
+void sxe2_eth_uinit(struct rte_eth_dev *dev __rte_unused)
 {
 	sxe2_mac_addr_uinit(dev);
 	(void)sxe2_filter_uinit(dev);
@@ -607,6 +633,16 @@ static void sxe2_drv_dev_caps_set(struct sxe2_adapter *adapter,
 		adapter->cap_flags |= SXE2_DEV_CAPS_OFFLOAD_FC_STATE;
 }
 
+static void sxe2_sw_representor_ctx_hw_cap_set(struct sxe2_adapter *adapter,
+			struct sxe2_drv_representor_caps *repr_caps)
+{
+	adapter->repr_ctxt.nb_vf = repr_caps->cnt_repr_vf;
+	if (adapter->repr_ctxt.nb_vf > 0) {
+		memcpy(adapter->repr_ctxt.repr_vf_id, repr_caps->repr_vf_id,
+			adapter->repr_ctxt.nb_vf * sizeof(struct sxe2_drv_vsi_caps));
+	}
+}
+
 static void sxe2_sw_sched_hw_cap_set(struct sxe2_adapter *adapter,
 				     struct sxe2_txsch_caps *txsch_caps)
 {
@@ -636,20 +672,47 @@ static int32_t sxe2_func_caps_get(struct sxe2_adapter *adapter)
 
 	sxe2_sw_vsi_ctx_hw_cap_set(adapter, &dev_caps.vsi_caps);
 
+	sxe2_sw_representor_ctx_hw_cap_set(adapter, &dev_caps.repr_caps);
+
 	sxe2_sw_sched_hw_cap_set(adapter, &dev_caps.txsch_caps);
 
 l_end:
 	return ret;
 }
 
+static int32_t sxe2_switchdev_info_get(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	struct sxe2_switchdev_info switchdev_info = {0};
+
+	ret = sxe2_drv_switchdev_info_get(adapter, &switchdev_info);
+	if (ret)
+		goto l_end;
+	if (switchdev_info.master && switchdev_info.representor) {
+		PMD_LOG_ERR(INIT, "device could not be both master and representor");
+		ret = -ENODEV;
+		goto l_end;
+	}
+	adapter->switchdev_info = switchdev_info;
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_dev_caps_get(struct sxe2_adapter *adapter)
 {
 	int32_t ret = -1;
 
 	ret = sxe2_func_caps_get(adapter);
-	if (ret)
+	if (ret) {
 		PMD_LOG_ERR(INIT, "get function caps failed, ret=%d", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_switchdev_info_get(adapter);
+	if (ret)
+		PMD_LOG_ERR(INIT, "get switchdev info failed, ret=%d", ret);
 
+l_end:
 	return ret;
 }
 
@@ -935,7 +998,10 @@ int32_t sxe2_dev_pci_map_init(struct rte_eth_dev *dev)
 	bar_info[1].seg_info = seg_info;
 	map_ctxt->bar_info = bar_info;
 
-	map_ctxt->addr_info = sxe2_net_map_addr_info_pf;
+	if (adapter->dev_type == SXE2_DEV_T_VF)
+		map_ctxt->addr_info = sxe2_net_map_addr_info_vf;
+	else
+		map_ctxt->addr_info = sxe2_net_map_addr_info_pf;
 
 	ret = sxe2_dev_pci_res_seg_map(adapter, SXE2_PCI_MAP_RES_DOORBELL_TX,
 				       txq_cnt, txq_base);
@@ -1138,6 +1204,12 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_dev_info_err;
 	}
 
+	ret = sxe2_switchdev_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize switchdev mode, ret=[%d]", ret);
+		goto init_switchdev_err;
+	}
+
 	ret = sxe2_sw_init(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to initialize sw parameters, ret=[%d]", ret);
@@ -1168,6 +1240,12 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_rss_err;
 	}
 
+	ret = sxe2_flow_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init flow, ret=%d", ret);
+		goto init_flow_err;
+	}
+
 	ret = sxe2_sched_init(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to init sched, ret=%d", ret);
@@ -1191,15 +1269,19 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 init_xstats_err:
 	(void)sxe2_sched_uinit(dev);
 init_sched_err:
+	(void)sxe2_flow_uninit(dev);
+init_flow_err:
 init_rss_err:
 	sxe2_security_uinit(dev);
 init_security_err:
+	sxe2_eth_uinit(dev);
+init_eth_err:
 	sxe2_intr_uninit(dev);
 init_irq_err:
 	sxe2_sw_uninit(dev);
 init_sw_err:
-	sxe2_eth_uinit(dev);
-init_eth_err:
+	(void)sxe2_switchdev_uninit(dev);
+init_switchdev_err:
 init_dev_info_err:
 	sxe2_vsi_uninit(dev);
 init_vsi_err:
@@ -1214,6 +1296,7 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 		sxe2_mp_uninit(dev);
 		goto l_end;
 	}
+	sxe2_repr_all_close(dev);
 	(void)sxe2_dev_stop(dev);
 	(void)sxe2_queues_release(dev);
 	sxe2_mp_uninit(dev);
@@ -1222,6 +1305,7 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 	sxe2_vsi_uninit(dev);
 	sxe2_security_uinit(dev);
 	sxe2_intr_uninit(dev);
+	(void)sxe2_switchdev_uninit(dev);
 	sxe2_sw_uninit(dev);
 	sxe2_eth_uinit(dev);
 	sxe2_dev_pci_map_uinit(dev);
@@ -1233,10 +1317,29 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 static int32_t sxe2_dev_uninit(struct rte_eth_dev *dev)
 {
 	int32_t ret = 0;
+	int32_t i = 0;
+	struct sxe2_adapter *adapter = NULL;
+	struct rte_eth_dev *rep_dev = NULL;
 
 	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
 		goto l_end;
 
+	adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	for (i = 0; i < adapter->repr_ctxt.nb_repr_vf; i++) {
+		rep_dev = adapter->repr_ctxt.vf_rep_eth_dev[i];
+		if (rep_dev) {
+			ret = rep_dev->dev_ops->dev_close(rep_dev);
+			if (ret)
+				goto l_end;
+			if (rep_dev->intr_handle)
+				rte_intr_instance_free(rep_dev->intr_handle);
+			ret = rte_eth_dev_release_port(rep_dev);
+			if (ret)
+				goto l_end;
+			adapter->repr_ctxt.vf_rep_eth_dev[i] = NULL;
+		}
+	}
+
 	ret = sxe2_dev_close(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Sxe2 dev close failed, ret=%d", ret);
@@ -1270,6 +1373,65 @@ static int32_t sxe2_eth_pmd_remove(struct sxe2_common_device *cdev)
 	return ret;
 }
 
+static uint16_t sxe2_switchdev_repr_id_encode_get(struct sxe2_switchdev_info *switchdev_info)
+{
+	enum rte_eth_representor_type type;
+	uint16_t repr = switchdev_info->vf_num;
+	uint32_t pf = switchdev_info->pf_num;
+
+	switch (switchdev_info->port_name_type) {
+	case SXE2_PHYS_PORT_NAME_TYPE_UPLINK:
+		if (!switchdev_info->representor)
+			return UINT16_MAX;
+		type = RTE_ETH_REPRESENTOR_PF;
+		pf = switchdev_info->mpesw_owner;
+		break;
+	case SXE2_PHYS_PORT_NAME_TYPE_PFVF:
+	default:
+		type = RTE_ETH_REPRESENTOR_VF;
+		break;
+	}
+
+	return SXE2_REPRESENTOR_ID(pf, type, repr);
+}
+
+static bool sxe2_switchdev_repr_match(struct sxe2_adapter *adapter,
+				   struct rte_eth_devargs *req_eth_da)
+{
+	uint32_t port_idx = 0;
+	uint32_t repr_idx;
+	uint16_t kernel_repr_id = sxe2_switchdev_repr_id_encode_get(&adapter->switchdev_info);
+	uint16_t repr_id;
+
+	switch (req_eth_da->type) {
+	case RTE_ETH_REPRESENTOR_PF:
+		break;
+	case RTE_ETH_REPRESENTOR_VF:
+		if (adapter->switchdev_info.port_name_type !=
+		SXE2_PHYS_PORT_NAME_TYPE_PFVF) {
+			rte_errno = EBUSY;
+			return false;
+		}
+		break;
+	case RTE_ETH_REPRESENTOR_NONE:
+		rte_errno = EBUSY;
+		return false;
+	default:
+		rte_errno = ENOTSUP;
+		return false;
+	}
+
+	for (repr_idx = 0; repr_idx < req_eth_da->nb_representor_ports; ++repr_idx) {
+		repr_id = SXE2_REPRESENTOR_ID(req_eth_da->ports[port_idx],
+					      req_eth_da->type,
+					      req_eth_da->representor_ports[repr_idx]);
+		if (repr_id == kernel_repr_id)
+			return true;
+	}
+	rte_errno = EBUSY;
+	return false;
+}
+
 static int32_t sxe2_eth_pmd_probe_pf(struct sxe2_common_device *cdev,
 		struct rte_eth_devargs *req_eth_da __rte_unused,
 		uint16_t owner_id __rte_unused,
@@ -1311,10 +1473,34 @@ static int32_t sxe2_eth_pmd_probe_pf(struct sxe2_common_device *cdev,
 		goto l_release_port;
 	}
 
+	if (req_eth_da->nb_representor_ports > 0) {
+		if (!adapter->switchdev_info.is_switchdev) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Representor requested but Switchdev not enabled");
+			ret = -ENOTSUP;
+			goto l_dev_uinit;
+		}
+
+		if (!sxe2_switchdev_repr_match(adapter, req_eth_da)) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Representor parameters mismatch");
+			ret = -ENOTSUP;
+			goto l_dev_uinit;
+		}
+
+		ret = sxe2_switchdev_repr_devs_init(adapter, req_eth_da);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Failed to init representor, ret=%d", ret);
+			goto l_dev_uinit;
+		}
+	} else {
+		PMD_DEV_LOG_DEBUG(adapter, INIT, "No representors requested, skipping.");
+	}
+
 	rte_eth_dev_probing_finish(eth_dev);
 	PMD_DEV_LOG_DEBUG(adapter, INIT, "Sxe2 eth pmd probe successful!");
 	goto l_end;
 
+l_dev_uinit:
+	(void)sxe2_dev_uninit(eth_dev);
 l_release_port:
 	(void)rte_eth_dev_release_port(eth_dev);
 l_end:
@@ -1384,6 +1570,11 @@ static struct sxe2_class_driver sxe2_eth_pmd = {
 	.intr_rmv = 1,
 };
 
+bool sxe2_ethdev_check(struct rte_eth_dev *dev)
+{
+	return !strcmp(dev->device->driver->name, "sxe2_pci");
+}
+
 RTE_INIT(rte_sxe2_pmd_init)
 {
 	sxe2_common_init();
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 65ada44c12..6397a2e5c6 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -64,6 +64,9 @@ enum sxe2_fnav_tunnel_flag_type {
 #define upper_32_bits(n) ((uint32_t)(((n) >> 16) >> 16))
 #define lower_32_bits(n) ((uint32_t)((n) & 0xffffffff))
 
+#define SXE2_REPRESENTOR_ID(pf, type, repr) \
+		(((pf) << 14) + ((type) << 12) + ((repr) & 0xfff))
+
 #define SXE2_I2C_EEPROM_DEV_ADDR	0xA0
 #define SXE2_I2C_EEPROM_DEV_ADDR2	0xA2
 #define SXE2_MODULE_TYPE_SFP		0x03
@@ -309,16 +312,20 @@ struct sxe2_adapter {
 	struct sxe2_vsi_context       vsi_ctxt;
 	struct sxe2_filter_context    filter_ctxt;
 	struct sxe2_rss_context       rss_ctxt;
+	struct sxe2_flow_context      flow_ctxt;
 	struct sxe2_link_context      link_ctxt;
 	struct sxe2_ptp_context       ptp_ctxt;
 	struct sxe2_sched_hw_cap      sched_ctxt;
 	struct sxe2_tm_context        tm_ctxt;
 	struct sxe2_devargs           devargs;
 	struct sxe2_security_ctx      security_ctx;
+	struct sxe2_repr_context      repr_ctxt;
 	struct sxe2_switchdev_info    switchdev_info;
 	bool                          rule_started;
 	bool                          flow_isolated;
+	bool                          flow_isolate_cfg;
 	uint16_t                           dev_port_id;
+	bool                          is_dev_repr;
 	uint64_t                           cap_flags;
 	enum sxe2_dev_type            dev_type;
 	uint32_t    ptype_tbl[SXE2_MAX_PTYPE_NUM];
@@ -340,6 +347,8 @@ void *sxe2_pci_map_addr_get(struct sxe2_adapter *adapter,
 			    enum sxe2_pci_map_resource res_type,
 			    uint16_t idx_in_func);
 
+bool sxe2_ethdev_check(struct rte_eth_dev *dev);
+
 uint32_t sxe2_sched_mode_get(struct sxe2_adapter *adapter);
 
 struct sxe2_pci_map_bar_info *sxe2_dev_get_bar_info(struct sxe2_adapter *adapter,
@@ -366,6 +375,8 @@ int32_t sxe2_dev_pci_map_init(struct rte_eth_dev *dev);
 
 void sxe2_dev_pci_map_uinit(struct rte_eth_dev *dev);
 
+void sxe2_eth_uinit(struct rte_eth_dev *dev);
+
 static inline bool
 sxe2_dev_port_vlan_check(struct rte_eth_dev *dev)
 {
diff --git a/drivers/net/sxe2/sxe2_ethdev_repr.c b/drivers/net/sxe2/sxe2_ethdev_repr.c
new file mode 100644
index 0000000000..a43991c379
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_ethdev_repr.c
@@ -0,0 +1,607 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include "sxe2_ethdev_repr.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_common.h"
+#include "sxe2_common_log.h"
+#include "sxe2_tx.h"
+#include "sxe2_rx.h"
+#include "sxe2_txrx.h"
+#include "sxe2_switchdev.h"
+#include "sxe2_ptype.h"
+#include "sxe2_mp.h"
+#include "sxe2_stats.h"
+#include "sxe2_flow.h"
+
+static struct sxe2_pci_map_addr_info sxe2_net_map_addr_info_repr[SXE2_PCI_MAP_RES_MAX_COUNT] = {
+	{0, 0, 0},
+	{ SXE2_TXQ_LEGACY_DBLL(0), 0, 4},
+	{ SXE2_RXQ_TAIL(0), 0, 4},
+	{ SXE2_VF_DYN_CTL(0), 0, 4},
+	{ SXE2_VF_INT_ITR(0, 0), 0, 4},
+	{ SXE2_BAR4_MSIX_CTL(0), 4, 0x10},
+};
+
+static void sxe2_repr_dev_uinit(struct rte_eth_dev *dev);
+
+static int32_t sxe2_repr_promisc_enable(struct rte_eth_dev *dev __rte_unused)
+{
+	return 0;
+}
+static int32_t sxe2_repr_promisc_disable(struct rte_eth_dev *dev __rte_unused)
+{
+	return 0;
+}
+static int32_t sxe2_repr_allmulti_enable(struct rte_eth_dev *dev __rte_unused)
+{
+	return 0;
+}
+static int32_t sxe2_repr_allmulti_disable(struct rte_eth_dev *dev __rte_unused)
+{
+	return 0;
+}
+
+static int32_t sxe2_repr_dev_configure(struct rte_eth_dev *dev)
+{
+	dev->data->mtu = SXE2_FRAME_SIZE_MAX - SXE2_ETH_OVERHEAD;
+	return 0;
+}
+
+static int32_t sxe2_repr_dev_start(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	PMD_INIT_FUNC_TRACE();
+
+	ret = sxe2_queues_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init queues.");
+		goto l_end;
+	}
+
+	sxe2_rx_mode_func_set(dev);
+	sxe2_tx_mode_func_set(dev);
+
+	ret = sxe2_link_update_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize link update, ret:%d", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_repr_rxq_intr_enable(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to enable rx queue intr");
+		goto l_end;
+	}
+
+	ret = sxe2_queues_start(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "enable queues failed");
+		goto l_start_queues_err;
+	}
+
+	dev->data->dev_started = 1;
+	adapter->started = 1;
+	goto l_end;
+l_start_queues_err:
+	(void)sxe2_rxq_intr_disable(dev);
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_repr_dev_stop(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	PMD_INIT_FUNC_TRACE();
+
+	if (adapter->started == 0)
+		goto l_end;
+
+	sxe2_repr_rxq_intr_disable(dev);
+
+	sxe2_txqs_all_stop(dev);
+	sxe2_rxqs_all_stop(dev);
+
+	dev->data->dev_started = 0;
+	adapter->started = 0;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_repr_dev_close(struct rte_eth_dev *dev)
+{
+	PMD_DEV_LOG_INFO(SXE2_DEV_PRIVATE_TO_ADAPTER(dev),
+			 INIT, "repr close");
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY) {
+		sxe2_mp_uninit(dev);
+		goto l_end;
+	}
+	(void)sxe2_repr_dev_stop(dev);
+	(void)sxe2_queues_release(dev);
+	sxe2_mp_uninit(dev);
+	sxe2_repr_dev_uinit(dev);
+l_end:
+	return 0;
+}
+
+static int32_t sxe2_repr_dev_infos_get(struct rte_eth_dev *dev,
+			struct rte_eth_dev_info *dev_info)
+{
+	dev_info->max_rx_queues = 1;
+	dev_info->max_tx_queues = 1;
+	dev_info->min_rx_bufsize = SXE2_MIN_BUF_SIZE;
+	dev_info->max_rx_pktlen = SXE2_FRAME_SIZE_MAX;
+	dev_info->max_lro_pkt_size = SXE2_FRAME_SIZE_MAX * SXE2_RX_LRO_DESC_MAX_NUM;
+	dev_info->max_mtu = dev_info->max_rx_pktlen - SXE2_ETH_OVERHEAD;
+	dev_info->min_mtu = RTE_ETHER_MIN_MTU;
+	dev_info->max_mac_addrs = SXE2_NUM_MACADDR_MAX;
+
+	dev_info->rx_offload_capa =
+		RTE_ETH_RX_OFFLOAD_KEEP_CRC |
+		RTE_ETH_RX_OFFLOAD_IPV4_CKSUM |
+		RTE_ETH_RX_OFFLOAD_UDP_CKSUM |
+		RTE_ETH_RX_OFFLOAD_TCP_CKSUM |
+		RTE_ETH_RX_OFFLOAD_SCTP_CKSUM |
+		RTE_ETH_RX_OFFLOAD_OUTER_IPV4_CKSUM;
+	dev_info->tx_offload_capa =
+		RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+		RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+		RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+		RTE_ETH_TX_OFFLOAD_SCTP_CKSUM |
+		RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM |
+		RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM;
+
+	dev_info->tx_queue_offload_capa = RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;
+
+	dev_info->default_rxconf = (struct rte_eth_rxconf) {
+		.rx_thresh = {
+			.pthresh = SXE2_DEFAULT_RX_PTHRESH,
+			.hthresh = SXE2_DEFAULT_RX_HTHRESH,
+			.wthresh = SXE2_DEFAULT_RX_WTHRESH,
+		},
+		.rx_free_thresh = SXE2_DEFAULT_RX_FREE_THRESH,
+		.rx_drop_en = 0,
+		.offloads = 0,
+	};
+
+	dev_info->default_txconf = (struct rte_eth_txconf) {
+		.tx_thresh = {
+			.pthresh = SXE2_DEFAULT_TX_PTHRESH,
+			.hthresh = SXE2_DEFAULT_TX_HTHRESH,
+			.wthresh = SXE2_DEFAULT_TX_WTHRESH,
+		},
+		.tx_free_thresh = SXE2_DEFAULT_TX_FREE_THRESH,
+		.tx_rs_thresh = SXE2_DEFAULT_TX_RSBIT_THRESH,
+		.offloads = 0,
+	};
+
+	dev_info->rx_desc_lim = (struct rte_eth_desc_lim) {
+		.nb_max = SXE2_MAX_RING_DESC,
+		.nb_min = SXE2_MIN_RING_DESC,
+		.nb_align = SXE2_ALIGN,
+	};
+
+	dev_info->tx_desc_lim = (struct rte_eth_desc_lim) {
+		.nb_max = SXE2_MAX_RING_DESC,
+		.nb_min = SXE2_MIN_RING_DESC,
+		.nb_align = SXE2_ALIGN,
+		.nb_mtu_seg_max = SXE2_TX_MTU_SEG_MAX,
+		.nb_seg_max = SXE2_MAX_RING_DESC,
+	};
+
+	dev_info->speed_capa = RTE_ETH_LINK_SPEED_10G | RTE_ETH_LINK_SPEED_25G |
+			 RTE_ETH_LINK_SPEED_50G | RTE_ETH_LINK_SPEED_100G;
+
+	dev_info->nb_rx_queues = dev->data->nb_rx_queues;
+	dev_info->nb_tx_queues = dev->data->nb_tx_queues;
+
+	dev_info->default_rxportconf.burst_size = SXE2_RX_MAX_BURST;
+	dev_info->default_txportconf.burst_size = SXE2_TX_MAX_BURST;
+	dev_info->default_rxportconf.nb_queues = 1;
+	dev_info->default_txportconf.nb_queues = 1;
+	dev_info->default_rxportconf.ring_size = SXE2_RING_SIZE_MIN;
+	dev_info->default_txportconf.ring_size = SXE2_RING_SIZE_MIN;
+
+	dev_info->rx_seg_capa.offset_allowed = false;
+
+	dev_info->rx_seg_capa.offset_align_log2 = false;
+
+	return 0;
+}
+
+static const struct eth_dev_ops sxe2_switchdev_repr_dev_ops = {
+	.dev_configure              = sxe2_repr_dev_configure,
+
+	.dev_start                  = sxe2_repr_dev_start,
+	.dev_stop                   = sxe2_repr_dev_stop,
+
+	.rx_queue_start             = sxe2_rx_queue_start,
+	.rx_queue_stop              = sxe2_rx_queue_stop,
+	.tx_queue_start             = sxe2_tx_queue_start,
+	.tx_queue_stop              = sxe2_tx_queue_stop,
+	.rx_queue_setup             = sxe2_rx_queue_setup,
+	.rx_queue_release           = sxe2_rx_queue_release,
+	.tx_queue_setup             = sxe2_tx_queue_setup,
+	.tx_queue_release           = sxe2_tx_queue_release,
+
+	.dev_close                  = sxe2_repr_dev_close,
+	.dev_infos_get              = sxe2_repr_dev_infos_get,
+	.dev_supported_ptypes_get   = sxe2_dev_supported_ptypes_get,
+	.link_update                = sxe2_link_update,
+
+	.promiscuous_enable         = sxe2_repr_promisc_enable,
+	.promiscuous_disable        = sxe2_repr_promisc_disable,
+	.allmulticast_enable        = sxe2_repr_allmulti_enable,
+	.allmulticast_disable       = sxe2_repr_allmulti_disable,
+
+	.stats_get                  = sxe2_stats_info_get,
+	.stats_reset                = sxe2_stats_info_reset,
+	.xstats_get                 = sxe2_xstats_info_get,
+	.xstats_get_names           = sxe2_xstats_names_get,
+	.xstats_reset               = sxe2_stats_info_reset,
+};
+
+void sxe2_repr_all_close(struct rte_eth_dev *dev)
+{
+	uint16_t vf_id;
+	struct rte_eth_dev *repr_eth_dev = NULL;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (adapter->repr_ctxt.nb_repr_vf) {
+		for (vf_id = 0; vf_id < adapter->repr_ctxt.nb_repr_vf; vf_id++) {
+			repr_eth_dev = adapter->repr_ctxt.vf_rep_eth_dev[vf_id];
+			if (!repr_eth_dev || repr_eth_dev->data->dev_started == 0)
+				continue;
+
+			(void)rte_eth_dev_stop(repr_eth_dev->data->port_id);
+			(void)rte_eth_dev_close(repr_eth_dev->data->port_id);
+		}
+	}
+}
+
+static void sxe2_repr_adapter_init(struct rte_eth_dev *dev_repr,
+				   struct sxe2_adapter *parent_adapter,
+				   uint16_t repr_id)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev_repr);
+
+	dev_repr->data->backer_port_id = parent_adapter->dev_port_id;
+	dev_repr->data->representor_id = repr_id;
+	dev_repr->data->dev_flags |= RTE_ETH_DEV_REPRESENTOR;
+
+	adapter->is_dev_repr = true;
+	adapter->dev_port_id = dev_repr->data->port_id;
+	adapter->dev_type = parent_adapter->dev_type;
+	adapter->switchdev_info.is_switchdev = parent_adapter->switchdev_info.is_switchdev;
+	adapter->port_idx = parent_adapter->port_idx;
+	adapter->pf_idx = parent_adapter->pf_idx;
+	adapter->dev_info.pci = parent_adapter->dev_info.pci;
+	adapter->dev_info.fw = parent_adapter->dev_info.fw;
+}
+
+static int32_t sxe2_repr_eth_init(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+
+	ret = sxe2_filter_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize l2 filter, ret:%d", ret);
+		goto l_end;
+	}
+	ret = sxe2_link_update_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize link update, ret:%d", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_mac_addr_init(dev);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to initialize mac address, ret:%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_repr_dev_pci_map_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *rep_adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_pci_map_context *map_ctxt = &rep_adapter->map_ctxt;
+	struct sxe2_pci_map_bar_info *bar_info = NULL;
+	struct sxe2_pci_map_segment_info *seg_info = NULL;
+	uint16_t txq_cnt = rep_adapter->q_ctxt.qp_cnt_assign;
+	uint16_t txq_base = rep_adapter->q_ctxt.base_idx_in_pf;
+	uint16_t rxq_cnt = rep_adapter->q_ctxt.qp_cnt_assign;
+	uint16_t rxq_base = rep_adapter->q_ctxt.base_idx_in_pf;
+	uint16_t irq_cnt = rep_adapter->irq_ctxt.max_cnt_hw;
+	uint16_t irq_base = rep_adapter->irq_ctxt.base_idx_in_func;
+	int32_t ret = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	rep_adapter->dev_info.dev_data = dev->data;
+
+	map_ctxt->bar_cnt = 2;
+
+	bar_info = rte_zmalloc("repr_bar_info",
+			sizeof(struct sxe2_pci_map_bar_info) * map_ctxt->bar_cnt, 0);
+	if (bar_info == NULL) {
+		PMD_LOG_ERR(INIT, "Failed to alloc bar_info");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	bar_info[0].bar_idx = 0;
+	bar_info[0].map_cnt = SXE2_PCI_MAP_RES_MAX_COUNT;
+	seg_info = rte_zmalloc("repr_seg_info_bar0",
+			sizeof(struct sxe2_pci_map_segment_info) * bar_info[0].map_cnt, 0);
+	if (seg_info == NULL) {
+		PMD_LOG_ERR(INIT, "Failed to alloc seg_info");
+		ret = -ENOMEM;
+		goto l_free_bar;
+	}
+
+	bar_info[0].seg_info = seg_info;
+
+	bar_info[1].bar_idx = 4;
+	bar_info[1].map_cnt = SXE2_PCI_MAP_RES_MAX_COUNT;
+	seg_info = rte_zmalloc("repr_seg_info_bar4",
+			sizeof(struct sxe2_pci_map_segment_info) * bar_info[1].map_cnt,
+			0);
+	if (!seg_info) {
+		PMD_LOG_ERR(INIT, "Failed to alloc seg_info");
+		ret = -ENOMEM;
+		goto l_free_seg0;
+	}
+
+	bar_info[1].seg_info = seg_info;
+	map_ctxt->bar_info = bar_info;
+
+	map_ctxt->addr_info = sxe2_net_map_addr_info_repr;
+
+	ret = sxe2_dev_pci_res_seg_map(rep_adapter, SXE2_PCI_MAP_RES_DOORBELL_TX,
+			txq_cnt, txq_base);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to map txq doorbell addr, ret=%d", ret);
+		goto l_free_seg1;
+	}
+
+	ret = sxe2_dev_pci_res_seg_map(rep_adapter, SXE2_PCI_MAP_RES_DOORBELL_RX_TAIL,
+			rxq_cnt, rxq_base);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to map rxq tail doorbell addr, ret=%d", ret);
+		goto l_free_txq;
+	}
+
+	ret = sxe2_dev_pci_res_seg_map(rep_adapter, SXE2_PCI_MAP_RES_IRQ_DYN,
+			irq_cnt, irq_base);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to map irq dyn addr, ret=%d", ret);
+		goto l_free_rxq_tail;
+	}
+
+	ret = sxe2_dev_pci_res_seg_map(rep_adapter, SXE2_PCI_MAP_RES_IRQ_ITR,
+			irq_cnt, irq_base);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to map irq itr addr, ret=%d", ret);
+		goto l_free_irq_dyn;
+	}
+
+	ret = sxe2_dev_pci_res_seg_map(rep_adapter, SXE2_PCI_MAP_RES_IRQ_MSIX,
+			irq_cnt, irq_base);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to map irq msix addr, ret=%d", ret);
+		goto l_free_irq_itr;
+	}
+	goto l_end;
+
+l_free_irq_itr:
+	(void)sxe2_dev_pci_seg_unmap(rep_adapter, SXE2_PCI_MAP_RES_IRQ_ITR);
+l_free_irq_dyn:
+	(void)sxe2_dev_pci_seg_unmap(rep_adapter, SXE2_PCI_MAP_RES_IRQ_DYN);
+l_free_rxq_tail:
+	(void)sxe2_dev_pci_seg_unmap(rep_adapter, SXE2_PCI_MAP_RES_DOORBELL_RX_TAIL);
+l_free_txq:
+	(void)sxe2_dev_pci_seg_unmap(rep_adapter, SXE2_PCI_MAP_RES_DOORBELL_TX);
+l_free_seg1:
+	if (bar_info[1].seg_info) {
+		rte_free(bar_info[1].seg_info);
+		bar_info[1].seg_info = NULL;
+	}
+l_free_seg0:
+	if (bar_info[0].seg_info) {
+		rte_free(bar_info[0].seg_info);
+		bar_info[0].seg_info = NULL;
+	}
+l_free_bar:
+	if (bar_info) {
+		rte_free(bar_info);
+		bar_info = NULL;
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_repr_dev_init(struct rte_eth_dev *dev,
+			   struct sxe2_adapter *parent_adapter,
+			   uint16_t repr_id)
+{
+	int32_t ret = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	sxe2_set_common_function(dev);
+
+	sxe2_repr_adapter_init(dev, parent_adapter, repr_id);
+
+	dev->dev_ops = &sxe2_switchdev_repr_dev_ops;
+
+	ret = sxe2_vsi_repr_main_vsi_create(dev, parent_adapter, repr_id);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to create representor main vsi, ret=[%d]", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_switchdev_repr_private_data_init(dev, parent_adapter, repr_id);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to fill representor private data, ret=[%d]", ret);
+		goto l_init_priv_data_err;
+	}
+
+	ret = sxe2_repr_dev_pci_map_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to pci addr map, ret=[%d]", ret);
+		goto l_init_pci_error;
+	}
+
+	ret = sxe2_switchdev_dev_info_init(dev, parent_adapter);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to get device info, ret=[%d]", ret);
+		goto l_init_dev_info_err;
+	}
+
+	ret = sxe2_flow_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init flow, ret=%d", ret);
+		goto l_init_flow_err;
+	}
+
+	ret = sxe2_repr_eth_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init device, status = %d", ret);
+		goto l_init_eth_err;
+	}
+
+	ret = sxe2_sw_irq_ctxt_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize sw parameters, ret=[%d]", ret);
+		goto l_init_sw_err;
+	}
+
+	goto l_end;
+
+l_init_sw_err:
+	sxe2_eth_uinit(dev);
+l_init_eth_err:
+	(void)sxe2_flow_uninit(dev);
+l_init_flow_err:
+l_init_dev_info_err:
+	sxe2_dev_pci_map_uinit(dev);
+l_init_pci_error:
+	(void)sxe2_switchdev_uninit(dev);
+l_init_priv_data_err:
+	sxe2_vsi_repr_main_vsi_destroy(dev);
+l_end:
+	return ret;
+}
+
+static void sxe2_repr_dev_uinit(struct rte_eth_dev *dev)
+{
+	sxe2_eth_uinit(dev);
+	(void)sxe2_flow_uninit(dev);
+	sxe2_dev_pci_map_uinit(dev);
+	(void)sxe2_switchdev_uninit(dev);
+	sxe2_vsi_repr_main_vsi_destroy(dev);
+}
+
+int32_t sxe2_switchdev_repr_devs_init(struct sxe2_adapter *adapter,
+				  struct rte_eth_devargs *req_eth_da)
+{
+	struct rte_eth_dev *eth_dev = NULL;
+	int32_t ret;
+	uint16_t repr_idx = 0, tmp_repr_idx = 0;
+	char name[RTE_ETH_NAME_MAX_LEN];
+
+	if (req_eth_da->nb_representor_ports == 0) {
+		ret = 0;
+		goto l_end;
+	}
+
+	if (req_eth_da->nb_representor_ports > adapter->repr_ctxt.nb_vf) {
+		PMD_LOG_ERR(INIT, "Failed to create repr vsi, nb_representor_ports=%d, nb_vf=%d",
+			    req_eth_da->nb_representor_ports, adapter->repr_ctxt.nb_vf);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_other_vsi_create(adapter, req_eth_da->nb_representor_ports);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to create representor vsi, ret=%d", ret);
+		goto l_release_port;
+	}
+
+	adapter->repr_ctxt.vf_rep_eth_dev = rte_zmalloc("sxe2_repr_ethdev",
+			req_eth_da->nb_representor_ports * sizeof(struct rte_eth_dev *), 0);
+	if (adapter->repr_ctxt.vf_rep_eth_dev == NULL) {
+		PMD_LOG_ERR(INIT, "Failed to malloc representor eth dev.");
+		ret = -ENOMEM;
+		goto l_release_port;
+	}
+
+	for (repr_idx = 0; repr_idx < req_eth_da->nb_representor_ports; ++repr_idx) {
+		snprintf(name, sizeof(name), "sxe2_representor_c%dpf%d%s%u",
+			 adapter->pf_idx, adapter->pf_idx,
+			 "vf",
+			 req_eth_da->representor_ports[repr_idx]);
+
+		eth_dev = rte_eth_dev_allocate(name);
+		if (!eth_dev) {
+			ret = -ENOMEM;
+			goto l_release_port;
+		}
+		eth_dev->data->dev_private = rte_zmalloc_socket(name,
+			sizeof(struct sxe2_adapter),
+			RTE_CACHE_LINE_SIZE,
+			rte_socket_id());
+
+		if (!eth_dev->data->dev_private) {
+			rte_eth_dev_release_port(eth_dev);
+			ret = -ENOMEM;
+			goto l_release_port;
+		}
+
+		eth_dev->device = rte_eth_devices[adapter->dev_info.dev_data->port_id].device;
+
+		ret = sxe2_repr_dev_init(eth_dev, adapter,
+					 req_eth_da->representor_ports[repr_idx]);
+		if (ret) {
+			PMD_LOG_ERR(INIT, "Sxe2 dev init failed, ret=%d", ret);
+			rte_eth_dev_release_port(eth_dev);
+			goto l_release_port;
+		}
+
+		eth_dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_SHARED);
+		if (eth_dev->intr_handle == NULL) {
+			PMD_LOG_ERR(INIT, "Sxe2 dev init representor intr_handle failed");
+			ret = -ENOMEM;
+			sxe2_repr_dev_uinit(eth_dev);
+			rte_eth_dev_release_port(eth_dev);
+			goto l_release_port;
+		}
+		adapter->repr_ctxt.vf_rep_eth_dev[repr_idx] = eth_dev;
+		rte_eth_dev_probing_finish(eth_dev);
+	}
+	adapter->repr_ctxt.nb_repr_vf = req_eth_da->nb_representor_ports;
+	goto l_end;
+
+l_release_port:
+	for (tmp_repr_idx = 0; tmp_repr_idx < repr_idx; ++tmp_repr_idx) {
+		struct rte_eth_dev *rep_dev = adapter->repr_ctxt.vf_rep_eth_dev[tmp_repr_idx];
+		if (rep_dev) {
+			sxe2_repr_dev_uinit(rep_dev);
+			if (rep_dev->intr_handle)
+				rte_intr_instance_free(rep_dev->intr_handle);
+			rte_eth_dev_release_port(rep_dev);
+			adapter->repr_ctxt.vf_rep_eth_dev[tmp_repr_idx] = NULL;
+		}
+	}
+
+	rte_free(adapter->repr_ctxt.vf_rep_eth_dev);
+	adapter->repr_ctxt.vf_rep_eth_dev = NULL;
+
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_ethdev_repr.h b/drivers/net/sxe2/sxe2_ethdev_repr.h
new file mode 100644
index 0000000000..71a666337f
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_ethdev_repr.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SX2_ETHDEV_REPR_H__
+#define __SX2_ETHDEV_REPR_H__
+#include <rte_compat.h>
+#include <rte_kvargs.h>
+#include <rte_time.h>
+#include <ethdev_driver.h>
+#include <ethdev_pci.h>
+#include <rte_tm_driver.h>
+#include <rte_io.h>
+#include <rte_ethdev.h>
+#include <rte_alarm.h>
+#include <rte_dev_info.h>
+
+#include "sxe2_vsi.h"
+#include "sxe2_irq.h"
+#include "sxe2_queue.h"
+struct sxe2_adapter;
+
+void sxe2_repr_all_close(struct rte_eth_dev *dev);
+
+int32_t sxe2_repr_dev_init(struct rte_eth_dev *dev,
+		       struct sxe2_adapter *parent_adapter,
+		       uint16_t repr_id);
+
+int32_t sxe2_switchdev_repr_devs_init(struct sxe2_adapter *adapter,
+				  struct rte_eth_devargs *req_eth_da);
+
+#endif /* __SX2_ETHDEV_REPR_H__ */
diff --git a/drivers/net/sxe2/sxe2_filter.c b/drivers/net/sxe2/sxe2_filter.c
index cfeeb7a6c3..696242ed0c 100644
--- a/drivers/net/sxe2/sxe2_filter.c
+++ b/drivers/net/sxe2/sxe2_filter.c
@@ -9,6 +9,7 @@
 #include "sxe2_common_log.h"
 #include "sxe2_ethdev.h"
 #include "sxe2_cmd_chnl.h"
+#include "sxe2_switchdev.h"
 
 static struct sxe2_mac_filter *sxe2_uc_filter_find(struct sxe2_adapter *adapter,
 			struct rte_ether_addr *macaddr)
@@ -700,16 +701,96 @@ static int32_t sxe2_all_filter_hw_set(struct sxe2_adapter *adapter)
 	return ret;
 }
 
+static int32_t sxe2_uplink_hw_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (adapter->filter_ctxt.hw_uplink_config) {
+		if (adapter->dev_type == SXE2_DEV_T_PF) {
+			ret = sxe2_uplink_clear(adapter);
+			if (ret) {
+				PMD_DEV_LOG_ERR(adapter, DRV,
+						"Failed to clear uplink, ret:%d", ret);
+				goto l_end;
+			}
+			adapter->filter_ctxt.hw_uplink_config = false;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_uplink_hw_set(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->filter_ctxt.hw_uplink_config) {
+		if (adapter->dev_type == SXE2_DEV_T_PF) {
+			ret = sxe2_uplink_set(adapter);
+			if (ret && ret != -EEXIST) {
+				PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set uplink, ret:%d", ret);
+				goto l_end;
+			}
+			adapter->filter_ctxt.hw_uplink_config = true;
+			ret = 0;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_repr_hw_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (adapter->filter_ctxt.hw_repr_config) {
+		if (adapter->dev_type == SXE2_DEV_T_PF ||
+			adapter->dev_type == SXE2_DEV_T_PF_BOND) {
+			ret = sxe2_repr_clear(adapter);
+			if (ret) {
+				PMD_DEV_LOG_ERR(adapter, DRV, "Failed to clear repr, ret:%d", ret);
+				goto l_end;
+			}
+			adapter->filter_ctxt.hw_repr_config = false;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_repr_hw_set(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->filter_ctxt.hw_repr_config) {
+		if (adapter->dev_type == SXE2_DEV_T_PF ||
+			adapter->dev_type == SXE2_DEV_T_PF_BOND) {
+			ret = sxe2_repr_set(adapter);
+			if (ret && ret != -EEXIST) {
+				PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set repr, ret:%d", ret);
+				goto l_end;
+			}
+			adapter->filter_ctxt.hw_repr_config = true;
+			ret = 0;
+		}
+	}
+l_end:
+	return ret;
+}
+
 int32_t sxe2_l2_rule_update(struct sxe2_adapter *adapter)
 {
 	int32_t ret = 0;
 
-	if (!adapter->flow_isolated && !adapter->switchdev_info.is_switchdev &&
-	    adapter->rule_started) {
+	if (!adapter->flow_isolated &&
+	    !adapter->switchdev_info.is_switchdev &&
+	    adapter->rule_started)
 		adapter->filter_ctxt.cur_l2_config = true;
-	} else {
+	else
 		adapter->filter_ctxt.cur_l2_config = false;
-	}
 
 	if (adapter->filter_ctxt.cur_l2_config !=
 	    adapter->filter_ctxt.hw_l2_config) {
@@ -726,6 +807,38 @@ int32_t sxe2_l2_rule_update(struct sxe2_adapter *adapter)
 	return ret;
 }
 
+int32_t sxe2_switchdev_rule_update(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->flow_isolated &&
+	    adapter->switchdev_info.is_switchdev) {
+		adapter->filter_ctxt.cur_uplink_config = true;
+		adapter->filter_ctxt.cur_repr_config = true;
+	} else {
+		adapter->filter_ctxt.cur_uplink_config = false;
+		adapter->filter_ctxt.cur_repr_config = false;
+	}
+
+	if (adapter->filter_ctxt.cur_uplink_config !=
+	    adapter->filter_ctxt.hw_uplink_config) {
+		if (adapter->filter_ctxt.cur_uplink_config)
+			ret = sxe2_uplink_hw_set(adapter);
+		else
+			ret = sxe2_uplink_hw_clear(adapter);
+	}
+
+	if (adapter->filter_ctxt.cur_repr_config !=
+	    adapter->filter_ctxt.hw_repr_config) {
+		if (adapter->filter_ctxt.cur_repr_config)
+			ret = sxe2_repr_hw_set(adapter);
+		else
+			ret = sxe2_repr_hw_clear(adapter);
+	}
+
+	return ret;
+}
+
 int32_t sxe2_filter_rule_stop(struct rte_eth_dev *dev)
 {
 	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
diff --git a/drivers/net/sxe2/sxe2_filter.h b/drivers/net/sxe2/sxe2_filter.h
index 6262e8c845..b2538ed22f 100644
--- a/drivers/net/sxe2/sxe2_filter.h
+++ b/drivers/net/sxe2/sxe2_filter.h
@@ -89,6 +89,8 @@ int32_t sxe2_l2_rule_update(struct sxe2_adapter *adapter);
 
 int32_t sxe2_filter_rule_stop(struct rte_eth_dev *dev);
 
+int32_t sxe2_switchdev_rule_update(struct sxe2_adapter *adapter);
+
 int32_t sxe2_filter_rule_start(struct rte_eth_dev *dev);
 
 int32_t sxe2_filter_init(struct rte_eth_dev *dev);
diff --git a/drivers/net/sxe2/sxe2_flow.c b/drivers/net/sxe2/sxe2_flow.c
new file mode 100644
index 0000000000..6999cb0725
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow.c
@@ -0,0 +1,1337 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <sys/queue.h>
+#include <unistd.h>
+#include "sxe2_ethdev.h"
+#include "sxe2_flow.h"
+#include "sxe2_flow_parse_pattern.h"
+#include "sxe2_flow_parse_action.h"
+#include "sxe2_flow_parse_engine.h"
+#include "sxe2_cmd_chnl.h"
+#include "sxe2_flow_public.h"
+#include "sxe2_common_log.h"
+
+static int32_t sxe2_check_para(const struct rte_flow_attr *attr,
+			   const struct rte_flow_item pattern[],
+			   const struct rte_flow_action actions[],
+			   struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	if (!pattern) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ITEM_NUM,
+				NULL, "NULL pattern.");
+		ret = -rte_errno;
+		goto l_end;
+	}
+
+	if (!actions) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION_NUM,
+				NULL, "NULL action.");
+		ret = -rte_errno;
+		goto l_end;
+	}
+
+	if (!attr) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ATTR,
+				NULL, "NULL attribute.");
+		ret = -rte_errno;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_valid_attr(const struct rte_flow_attr *attr, struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+
+	if (!attr->ingress) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
+				attr, "Only support ingress.");
+		ret = -rte_errno;
+		goto l_end;
+	}
+
+	if (attr->egress) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ATTR_EGRESS,
+				attr, "Not support egress.");
+		ret = -rte_errno;
+		goto l_end;
+	}
+
+	if (attr->group >= 4) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
+				attr, "Not support group >= 4.");
+		ret = -rte_errno;
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_check_hdr_duplicate(struct sxe2_flow_item *item_new,
+					 struct sxe2_flow_item *item_exist)
+{
+	int32_t ret = 0;
+	uint16_t i = 0;
+	uint16_t size = sizeof(struct sxe2_flow_item);
+	union sxe2_flow_item_raw item_raw_new;
+	union sxe2_flow_item_raw item_raw_exist;
+	rte_memcpy(&item_raw_new.item, item_new, size);
+	rte_memcpy(&item_raw_exist.item, item_exist, size);
+
+	for (i = 0; i < size; i++) {
+		if (item_raw_new.raw[i] != item_raw_exist.raw[i])
+			goto l_end;
+	}
+	ret = -EEXIST;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_check_flow_duplicate(struct sxe2_flow *flow_new,
+					      struct sxe2_flow *flow_exist)
+{
+	int32_t ret = 0;
+	int32_t ret_mask1 = 0;
+	int32_t ret_mask2 = 0;
+	int32_t ret_spec1 = 0;
+	int32_t ret_spec2 = 0;
+
+	if (flow_new->engine_type != flow_exist->engine_type)
+		goto l_end;
+	if (flow_new->meta.flow_type != flow_exist->meta.flow_type)
+		goto l_end;
+	if (!sxe2_bitmap_equal(flow_new->flow_type, flow_exist->flow_type,
+			SXE2_EXPANSION_MAX))
+		goto l_end;
+	if (flow_new->meta.flow_prio != flow_exist->meta.flow_prio)
+		goto l_end;
+
+	ret_mask1 = sxe2_flow_check_hdr_duplicate(&flow_new->pattern_outer.item_mask,
+						  &flow_exist->pattern_outer.item_mask);
+	ret_mask2 = sxe2_flow_check_hdr_duplicate(&flow_new->pattern_inner.item_mask,
+						  &flow_exist->pattern_inner.item_mask);
+
+	ret_spec1 = sxe2_flow_check_hdr_duplicate(&flow_new->pattern_outer.item_spec,
+						  &flow_exist->pattern_outer.item_spec);
+	ret_spec2 = sxe2_flow_check_hdr_duplicate(&flow_new->pattern_inner.item_spec,
+						  &flow_exist->pattern_inner.item_spec);
+
+	if (flow_new->engine_type == SXE2_FLOW_ENGINE_FNAV) {
+		if (ret_mask1 == 0 || ret_mask2 == 0) {
+			ret = -EEXIST;
+			goto l_end;
+		}
+
+		if (ret_spec1 == -EEXIST && ret_spec2 == -EEXIST) {
+			ret = -EEXIST;
+			goto l_end;
+		}
+	} else {
+		if (ret_mask1 == -EEXIST && ret_mask2 == -EEXIST &&
+			ret_spec1 == -EEXIST && ret_spec2 == -EEXIST) {
+			ret = -EEXIST;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_check_flow_list_duplicate(struct rte_eth_dev *dev,
+						   struct rte_flow *flow_list)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_flow *sxe2_flow_new = NULL;
+	struct rte_flow *rte_flow_exist = NULL;
+	struct sxe2_flow *sxe2_flow_exist = NULL;
+	TAILQ_FOREACH(sxe2_flow_new, &flow_list->sxe2_flow_list, next) {
+		TAILQ_FOREACH(rte_flow_exist, &adapter->flow_ctxt.rte_flow_list, next) {
+			TAILQ_FOREACH(sxe2_flow_exist, &rte_flow_exist->sxe2_flow_list, next) {
+				ret = sxe2_flow_check_flow_duplicate(sxe2_flow_new,
+								     sxe2_flow_exist);
+				if (ret != 0)
+					goto l_end;
+			}
+		}
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_check_function(struct rte_eth_dev *dev,
+				    struct rte_flow *flow_list,
+				    struct rte_flow_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow *flow = TAILQ_FIRST(sxe2_flow_list);
+	int32_t ret = 0;
+
+	uint16_t flow_dst_vsi = UINT16_MAX;
+
+	if (adapter->dev_type == SXE2_DEV_T_VF) {
+		if (sxe2_test_bit(SXE2_FLOW_ACTION_TO_VSI, flow->action.act_types)) {
+			flow_dst_vsi = flow->action.vsi.vsi_index;
+
+			if (adapter->vsi_ctxt.dpdk_vsi_id != flow_dst_vsi &&
+				adapter->vsi_ctxt.kernel_vsi_id != flow_dst_vsi) {
+				PMD_LOG_ERR(DRV, "Failed to redirect other function");
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to redirect other function");
+				ret = -ENOTSUP;
+				goto l_end;
+			}
+		}
+
+		if (sxe2_test_bit(SXE2_FLOW_ACTION_TO_VSI_LIST, flow->action.act_types)) {
+			PMD_LOG_ERR(DRV,
+				"Failed to redirect multiple driver or function");
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to redirect multiple driver or function");
+				ret = -ENOTSUP;
+				goto l_end;
+		}
+
+		if (!adapter->flow_isolated &&
+			flow->engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+			PMD_LOG_ERR(DRV,
+				"Failed to switch engine rules in a non-flow-isolated state");
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to switch engine rules in a non-flow-isolated state");
+				ret = -ENOTSUP;
+				goto l_end;
+		}
+
+		if (adapter->switchdev_info.is_switchdev &&
+			flow->engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+			PMD_LOG_ERR(DRV,
+				"Failed to switch engine rules in a switchdev mode state");
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to switch engine rules in a switchdev mode state");
+				ret = -ENOTSUP;
+				goto l_end;
+		}
+	}
+
+	if (adapter->is_dev_repr) {
+		if (flow->engine_type != SXE2_FLOW_ENGINE_SWITCH) {
+			PMD_LOG_ERR(DRV,
+				"Failed to config non switch engine rules in representor dev");
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to config non switch engine rules in representor dev");
+				ret = -ENOTSUP;
+				goto l_end;
+		}
+
+		if (sxe2_test_bit(SXE2_FLOW_ACTION_QUEUE, flow->action.act_types) ||
+			sxe2_test_bit(SXE2_FLOW_ACTION_Q_REGION, flow->action.act_types)) {
+			PMD_LOG_ERR(DRV,
+				"Failed to config queue rules in representor dev");
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"Failed to config queue rules in representor dev");
+				ret = -ENOTSUP;
+				goto l_end;
+		}
+	}
+
+	if (adapter->switchdev_info.is_switchdev &&
+		adapter->dev_type == SXE2_DEV_T_PF &&
+		!adapter->is_dev_repr &&
+		!adapter->flow_isolated) {
+		if (sxe2_test_bit(SXE2_FLOW_ACTION_TO_VSI, flow->action.act_types)) {
+			if (flow->action.vsi.vsi_index == adapter->vsi_ctxt.dpdk_vsi_id) {
+				PMD_LOG_ERR(DRV,
+					"Failed to config rx fwd rule to current uplink dev");
+					rte_flow_error_set(error, ENOTSUP,
+						RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+						"Failed to config rx fwd rule to current uplink dev");
+					ret = -ENOTSUP;
+					goto l_end;
+			}
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_meta_proc(struct rte_eth_dev *dev,
+			       const struct rte_flow_attr *attr,
+			       struct rte_flow *flow_list,
+			       struct rte_flow_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow *flow = TAILQ_FIRST(sxe2_flow_list);
+	int32_t ret = 0;
+
+	if (attr->priority >= 1) {
+		if (flow->engine_type != SXE2_FLOW_ENGINE_SWITCH) {
+			PMD_LOG_ERR(DRV, "Only support priority 0.");
+			rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+					attr, "Only support priority 0.");
+			ret = -rte_errno;
+			goto l_end;
+		} else if (!adapter->switchdev_info.is_switchdev) {
+			PMD_LOG_ERR(DRV, "Legacy mode only support priority 0.");
+			rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+					attr, "Legacy mode only priority 0.");
+			ret = -rte_errno;
+			goto l_end;
+		} else {
+			flow->meta.flow_prio = attr->priority;
+		}
+	}
+
+	flow->meta.flow_src_vsi = adapter->vsi_ctxt.dpdk_vsi_id;
+
+	if (adapter->is_dev_repr && adapter->repr_priv_data &&
+		adapter->repr_priv_data->parent_adapter) {
+		flow->meta.flow_rule_vsi =
+			adapter->repr_priv_data->parent_adapter->vsi_ctxt.dpdk_vsi_id;
+	} else {
+		flow->meta.flow_rule_vsi = adapter->vsi_ctxt.dpdk_vsi_id;
+	}
+
+	if (flow->engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+		flow->meta.switch_pattern_dup_allow =
+			adapter->devargs.flow_dup_pattern_mode;
+
+		flow->meta.switch_src_direct = SXE2_FLOW_SW_DIRECT_RX;
+
+		if (adapter->switchdev_info.is_switchdev && adapter->is_dev_repr) {
+			flow->meta.switch_src_direct = SXE2_FLOW_SW_DIRECT_TX;
+			flow->meta.flow_src_vsi = adapter->repr_priv_data->repr_vf_vsi_id;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_src_split_proc(struct rte_eth_dev *dev,
+				    struct rte_flow *flow_list,
+				    struct rte_flow_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow *flow = TAILQ_FIRST(sxe2_flow_list);
+	int32_t ret = 0;
+
+	int32_t idx = 0;
+	uint8_t flow_cnt = 0;
+	uint8_t flow_create_cnt = 0;
+	uint8_t flow_bond_num = 1;
+	uint16_t flow_src_vsi[SXE2_MAX_DRV_TYPE_CNT][SXE2_MAX_BOND_MEMBER_CNT];
+	uint16_t flow_dst_vsi = UINT16_MAX;
+	struct sxe2_flow *flow_new = NULL;
+
+	if (sxe2_test_bit(SXE2_FLOW_ACTION_TO_VSI, flow->action.act_types))
+		flow_dst_vsi = flow->action.vsi.vsi_index;
+
+	for (idx = 0; idx < SXE2_MAX_BOND_MEMBER_CNT; idx++) {
+		flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx] = UINT16_MAX;
+		flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] = UINT16_MAX;
+	}
+
+	flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][0] = adapter->vsi_ctxt.dpdk_vsi_id;
+	flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][0] = adapter->vsi_ctxt.kernel_vsi_id;
+	if (flow->engine_type == SXE2_FLOW_ENGINE_FNAV ||
+		flow->engine_type == SXE2_FLOW_ENGINE_ACL) {
+		if (!adapter->devargs.func_flow_direct_en &&
+			adapter->dev_type != SXE2_DEV_T_PF_BOND) {
+			if (adapter->flow_isolated) {
+				for (idx = 0; idx < flow_bond_num; idx++) {
+					if (flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] !=
+								UINT16_MAX)
+						flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx] =
+										UINT16_MAX;
+				}
+			} else {
+				for (idx = 0; idx < flow_bond_num; idx++)
+					flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] = UINT16_MAX;
+			}
+		}
+
+		for (idx = 0; idx < flow_bond_num; idx++) {
+			if (flow_dst_vsi == flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx])
+				flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] = UINT16_MAX;
+			if (flow_dst_vsi == flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx])
+				flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx] = UINT16_MAX;
+		}
+	} else {
+		for (idx = 0; idx < flow_bond_num; idx++)
+			flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] = UINT16_MAX;
+	}
+
+	if (adapter->switchdev_info.is_switchdev && adapter->is_dev_repr) {
+		flow_bond_num = 1;
+		flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][0] =
+			adapter->repr_priv_data->repr_vf_u_vsi_id;
+		flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][0] =
+			adapter->repr_priv_data->repr_vf_k_vsi_id;
+	}
+
+	for (idx = 0; idx < flow_bond_num; idx++) {
+		if (flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] != UINT16_MAX)
+			flow_cnt++;
+		if (flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx] != UINT16_MAX)
+			flow_cnt++;
+	}
+
+	if (flow_cnt == 0) {
+		PMD_LOG_ERR(DRV, "Failed to redirect same device.");
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"Failed to redirect same device");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	for (idx = 0; idx < flow_bond_num; idx++) {
+		if (flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx] != UINT16_MAX) {
+			if (flow_create_cnt == 0) {
+				flow->meta.flow_src_vsi =
+					flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx];
+				flow_create_cnt++;
+			} else {
+				flow_new = rte_zmalloc("sxe2_flow", sizeof(*flow), 0);
+				if (!flow_new) {
+					rte_flow_error_set(error, ENOMEM,
+						   RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+						   "Failed to alloc memory for flow rule");
+					ret = -ENOMEM;
+					goto l_end;
+				}
+				rte_memcpy(flow_new, flow, sizeof(struct sxe2_flow));
+				TAILQ_INSERT_TAIL(sxe2_flow_list, flow_new, next);
+				flow_new->meta.flow_src_vsi =
+						flow_src_vsi[SXE2_MAX_DRV_TYPE_DPDK][idx];
+				flow_create_cnt++;
+			}
+		}
+		if (flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx] != UINT16_MAX) {
+			if (flow_create_cnt == 0) {
+				flow->meta.flow_src_vsi =
+					flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx];
+				flow_create_cnt++;
+			} else {
+				flow_new = rte_zmalloc("sxe2_flow", sizeof(*flow), 0);
+				if (!flow_new) {
+					rte_flow_error_set(error, ENOMEM,
+						RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+						"Failed to alloc memory for flow rule");
+					ret = -ENOMEM;
+					goto l_end;
+				}
+				rte_memcpy(flow_new, flow, sizeof(struct sxe2_flow));
+				TAILQ_INSERT_TAIL(sxe2_flow_list, flow_new, next);
+				flow_new->meta.flow_src_vsi =
+					flow_src_vsi[SXE2_MAX_DRV_TYPE_KERNEL][idx];
+				flow_create_cnt++;
+			}
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_adjust_action(struct rte_eth_dev *dev __rte_unused,
+				   struct rte_flow *flow_list,
+				   struct rte_flow_error *error __rte_unused)
+{
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow *flow = NULL;
+	int32_t ret = 0;
+	int32_t dest_num = 0;
+	int32_t pass_num = 0;
+	int32_t mark_num = 0;
+	int32_t count_num = 0;
+	int32_t drop_num = 0;
+
+	TAILQ_FOREACH(flow, sxe2_flow_list, next) {
+		if (flow->engine_type == SXE2_FLOW_ENGINE_FNAV) {
+			dest_num = sxe2_test_bit(SXE2_FLOW_ACTION_Q_REGION,
+					    flow->action.act_types) +
+				   sxe2_test_bit(SXE2_FLOW_ACTION_QUEUE,
+					    flow->action.act_types);
+			pass_num = sxe2_test_bit(SXE2_FLOW_ACTION_PASSTHRU,
+					    flow->action.act_types);
+			mark_num = sxe2_test_bit(SXE2_FLOW_ACTION_MARK,
+					    flow->action.act_types);
+			count_num = sxe2_test_bit(SXE2_FLOW_ACTION_COUNT,
+					     flow->action.act_types);
+			drop_num = sxe2_test_bit(SXE2_FLOW_ACTION_DROP,
+					    flow->action.act_types);
+
+			if (dest_num) {
+				sxe2_clear_bit(SXE2_FLOW_ACTION_PASSTHRU,
+					  flow->action.act_types);
+				pass_num = 0;
+				sxe2_clear_bit(SXE2_FLOW_ACTION_TO_VSI,
+					  flow->action.act_types);
+			}
+
+			if (pass_num)
+				flow->action.passthru.vsi_index = flow->meta.flow_src_vsi;
+
+			if (mark_num) {
+				if (dest_num == 0) {
+					flow->action.q_region.q_index = 0;
+					flow->action.q_region.region = 7;
+					flow->action.q_region.vsi_index = flow->meta.flow_src_vsi;
+					sxe2_set_bit(SXE2_FLOW_ACTION_Q_REGION,
+						flow->action.act_types);
+					dest_num++;
+				}
+				sxe2_clear_bit(SXE2_FLOW_ACTION_PASSTHRU,
+					flow->action.act_types);
+				pass_num = 0;
+			}
+			if (count_num) {
+				if (dest_num == 0 && drop_num == 0) {
+					if (pass_num == 0) {
+						sxe2_set_bit(SXE2_FLOW_ACTION_PASSTHRU,
+							flow->action.act_types);
+						flow->action.passthru.vsi_index =
+									flow->meta.flow_src_vsi;
+						pass_num++;
+					}
+				}
+			}
+			PMD_LOG_DEBUG(DRV, "dest_num: %d, pass_num: %d, mark_num: %d, count_num: "
+				"%d, drop_num: %d", dest_num, pass_num, mark_num, count_num,
+				drop_num);
+			PMD_LOG_DEBUG(DRV, "src_vsi: %d", flow->meta.flow_src_vsi);
+		}
+	}
+
+	return ret;
+}
+
+static int32_t sxe2_flow_check_item_empty(uint8_t *item, uint16_t size)
+{
+	uint16_t i = 0;
+
+	for (i = 0; i < size; i++) {
+		if (item[i] != 0)
+			return -1;
+	}
+	return 0;
+}
+
+static int32_t sxe2_flowlist_add_proto_type(struct rte_eth_dev *dev __rte_unused,
+					struct rte_flow *flow_list,
+					struct rte_flow_error *error)
+{
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow *flow = TAILQ_FIRST(sxe2_flow_list);
+	struct sxe2_flow_pattern *pattern = &flow->pattern_outer;
+	int32_t ret = 0;
+
+	if (flow->engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+		if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV4, flow->flow_type) &&
+		    sxe2_flow_check_item_empty((uint8_t *)&pattern->item_mask.ipv4,
+			sizeof(pattern->item_mask.ipv4)) == 0) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_TYPE, pattern->map_spec);
+			pattern->item_spec.eth.ether_type =
+				rte_cpu_to_be_16(SXE2_FLOW_ETH_TYPE_IPV4);
+			pattern->item_mask.eth.ether_type = 0xffff;
+		}
+		if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV6, flow->flow_type) &&
+		    sxe2_flow_check_item_empty((uint8_t *)&pattern->item_mask.ipv6,
+			sizeof(pattern->item_mask.ipv6)) == 0) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_TYPE, pattern->map_spec);
+			pattern->item_spec.eth.ether_type =
+				rte_cpu_to_be_16(SXE2_FLOW_ETH_TYPE_IPV6);
+			pattern->item_mask.eth.ether_type = 0xffff;
+		}
+
+		if (flow->meta.tunnel_type == SXE2_FLOW_TUNNEL_TYPE_NONE) {
+			if (sxe2_test_bit(SXE2_EXPANSION_OUTER_UDP, flow->flow_type) &&
+			    sxe2_flow_check_item_empty((uint8_t *)&pattern->item_mask.udp,
+				sizeof(pattern->item_mask.udp)) == 0) {
+				if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV4,
+					     flow->flow_type)) {
+					sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_PROT, pattern->map_spec);
+					pattern->item_spec.ipv4.protocol =
+						SXE2_FLOW_IP_PROTOCOL_UDP;
+					pattern->item_mask.ipv4.protocol = 0xff;
+				}
+				if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV6,
+					     flow->flow_type)) {
+					ret = -EINVAL;
+					rte_flow_error_set(error, ENOENT,
+						RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+						"UDP after IPv6 must has pattern item.");
+					PMD_LOG_ERR(DRV,
+						"UDP after IPv6 must has pattern item.");
+					goto l_end;
+				}
+			}
+			if (sxe2_test_bit(SXE2_EXPANSION_OUTER_TCP, flow->flow_type) &&
+			    sxe2_flow_check_item_empty((uint8_t *)&pattern->item_mask.tcp,
+				sizeof(pattern->item_mask.tcp)) == 0) {
+				if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV4,
+					     flow->flow_type)) {
+					sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_PROT,
+						pattern->map_spec);
+					pattern->item_spec.ipv4.protocol =
+						SXE2_FLOW_IP_PROTOCOL_TCP;
+					pattern->item_mask.ipv4.protocol = 0xff;
+				}
+				if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV6,
+					     flow->flow_type)) {
+					ret = -EINVAL;
+					rte_flow_error_set(error, ENOENT,
+						RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+						"TCP after IPv6 must has pattern item.");
+					PMD_LOG_ERR(DRV,
+						"TCP after IPv6 must has pattern item.");
+					goto l_end;
+				}
+			}
+			if (sxe2_test_bit(SXE2_EXPANSION_OUTER_SCTP,
+				     flow->flow_type)) {
+				ret = -EINVAL;
+				rte_flow_error_set(error, ENOENT,
+					RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+					"SWITCH not support SCTP.");
+				PMD_LOG_ERR(DRV, "SWITCH not support SCTP.");
+				goto l_end;
+			}
+		}
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_tunnel_split_proc(struct rte_eth_dev *dev __rte_unused,
+				       struct rte_flow *flow_list,
+				       struct rte_flow_error *error)
+{
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow_list_t tunnel_flow_list;
+	struct sxe2_flow *sxe2_flow_exist = NULL;
+	struct sxe2_flow *sxe2_flow_new = NULL;
+	struct sxe2_flow_pattern *pattern = NULL;
+	int32_t ret = 0;
+
+	TAILQ_INIT(&tunnel_flow_list);
+
+	TAILQ_FOREACH(sxe2_flow_exist, sxe2_flow_list, next) {
+		if (sxe2_flow_exist->engine_type != SXE2_FLOW_ENGINE_SWITCH)
+			continue;
+		if (sxe2_test_bit(SXE2_FLOW_FLD_ID_IPV4_PROT,
+				sxe2_flow_exist->pattern_outer.map_spec)) {
+			pattern = &sxe2_flow_exist->pattern_outer;
+			if ((pattern->item_spec.ipv4.protocol &
+				pattern->item_mask.ipv4.protocol) ==
+				(SXE2_FLOW_IP_PROTOCOL_GRE &
+				pattern->item_mask.ipv4.protocol)) {
+				sxe2_flow_new = rte_zmalloc("sxe2_flow",
+					sizeof(struct sxe2_flow), 0);
+				if (!sxe2_flow_new) {
+					rte_flow_error_set(error, ENOMEM,
+						RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+						"Failed to alloc memory for flow rule");
+					ret = -ENOMEM;
+					goto l_end;
+				}
+				rte_memcpy(sxe2_flow_new, sxe2_flow_exist,
+					sizeof(struct sxe2_flow));
+				pattern = &sxe2_flow_new->pattern_outer;
+				sxe2_flow_new->meta.tunnel_type =
+					SXE2_FLOW_TUNNEL_TYPE_GRE;
+				sxe2_set_bit(SXE2_FLOW_HDR_GRE, pattern->hdrs);
+				pattern->item_spec.ipv4.protocol =
+					SXE2_FLOW_IP_PROTOCOL_GRE;
+				pattern->item_mask.ipv4.protocol = 0xff;
+				TAILQ_INSERT_TAIL(&tunnel_flow_list, sxe2_flow_new, next);
+			}
+		}
+	}
+	TAILQ_FOREACH(sxe2_flow_exist, &tunnel_flow_list, next)
+		TAILQ_INSERT_TAIL(sxe2_flow_list, sxe2_flow_exist, next);
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_post_proc(struct rte_eth_dev *dev,
+			       const struct rte_flow_attr *attr,
+			       struct rte_flow *flow_list,
+			       struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+
+	ret = sxe2_flowlist_add_proto_type(dev, flow_list, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_check_function(dev, flow_list, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_meta_proc(dev, attr, flow_list, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_src_split_proc(dev, flow_list, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_adjust_action(dev, flow_list, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_tunnel_split_proc(dev, flow_list, error);
+	if (ret)
+		goto l_end;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_validate_with_flow(struct rte_eth_dev *dev,
+					struct rte_flow *flow_list,
+					const struct rte_flow_attr *attr,
+					const struct rte_flow_item pattern[],
+					const struct rte_flow_action actions[],
+					struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_flow *flow = NULL;
+
+	ret = sxe2_check_para(attr, pattern, actions, error);
+	if (ret != 0)
+		goto l_end;
+
+	ret = sxe2_flow_valid_attr(attr, error);
+	if (ret != 0)
+		goto l_end;
+
+	flow = rte_zmalloc("sxe2_flow", sizeof(*flow), 0);
+	if (!flow) {
+		rte_flow_error_set(error, ENOMEM,
+				RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				"Failed to alloc memory for flow rule");
+		PMD_LOG_ERR(DRV, "Failed to alloc memory for flow rule.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	TAILQ_INSERT_TAIL(&flow_list->sxe2_flow_list, flow, next);
+	flow->create_err = -1;
+
+	ret = sxe2_flow_parse_pattern(dev, pattern, error, flow);
+	if (ret != 0)
+		goto l_end;
+
+	ret = sxe2_flow_parse_engine(dev, attr, actions, error, flow);
+	if (ret != 0)
+		goto l_end;
+
+	ret = sxe2_flow_parse_action(dev, actions, error, flow);
+	if (ret != 0)
+		goto l_end;
+
+	ret = sxe2_flow_post_proc(dev, attr, flow_list, error);
+	if (ret != 0)
+		goto l_end;
+
+	ret = sxe2_flow_check_flow_list_duplicate(dev, flow_list);
+	if (ret != 0) {
+		rte_flow_error_set(error, EEXIST, RTE_FLOW_ERROR_TYPE_ITEM,
+				NULL, "Duplicate flow.");
+		PMD_LOG_ERR(DRV, "Duplicate flow.");
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+static const char *sxe2_flow_convert_ret_to_flow_msg(int32_t ret)
+{
+	const char *msg = NULL;
+	if (ret > 0)
+		ret = -ret;
+	switch (ret) {
+	case -ENOMEM:
+		msg = "no memory";
+		break;
+	case -ENOTSUP:
+		msg = "not support";
+		break;
+	case -EEXIST:
+		msg = "rule already exist";
+		break;
+	case -ETIMEDOUT:
+		msg = "timeout";
+		break;
+	case -EINVAL:
+		msg = "invalid parameter";
+		break;
+	case -ENOSPC:
+		msg = "no space";
+		break;
+	case -ENOENT:
+		msg = "no such rule";
+		break;
+	default:
+		msg = "unknown error";
+		break;
+	}
+	return msg;
+}
+
+static int32_t sxe2_flow_rte_list_free(struct sxe2_adapter *adapter,
+				   struct rte_flow **flow_ptr,
+				   struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	int32_t ret1 = 0;
+	struct rte_flow *flow = *flow_ptr;
+	struct rte_flow *flow_temp = NULL;
+	struct sxe2_flow *hw_flow = NULL;
+	struct sxe2_flow *hw_flow_temp = NULL;
+	struct sxe2_fnav_cid_mgr *mgr = NULL;
+	rte_spinlock_lock(&adapter->flow_ctxt.flow_list_lock);
+	TAILQ_FOREACH(flow_temp, &adapter->flow_ctxt.rte_flow_list, next) {
+		if (flow_temp == flow)
+			TAILQ_REMOVE(&adapter->flow_ctxt.rte_flow_list, flow, next);
+	}
+
+	TAILQ_FOREACH_SAFE(hw_flow, &flow->sxe2_flow_list, next, hw_flow_temp) {
+		if (hw_flow->create_err == 0) {
+			if (sxe2_test_bit(SXE2_FLOW_ACTION_COUNT, hw_flow->action.act_types)) {
+				ret = sxe2_flow_query_mgr(adapter, hw_flow, &mgr, error);
+				if (ret) {
+					PMD_LOG_ERR(DRV,
+						"Failed to query flow count, flow id: %u, ret: %d.",
+						hw_flow->flow_id, ret);
+					rte_flow_error_set(error, EIO,
+						RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+						"Failed to query flow count");
+					ret1 = ret;
+				}
+			}
+
+			ret = sxe2_drv_flow_filter_del(adapter, hw_flow);
+			if (ret) {
+				PMD_LOG_ERR(DRV,
+					"Failed to delete flow filter, ret: %d:%s",
+					ret, sxe2_flow_convert_ret_to_flow_msg(ret));
+				rte_flow_error_set(error, EIO,
+					RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+					"Failed to delete flow filter");
+				ret1 = ret;
+			}
+
+			if (sxe2_test_bit(SXE2_FLOW_ACTION_COUNT, hw_flow->action.act_types)) {
+				ret = sxe2_flow_free_mgr(adapter, hw_flow,
+							 &mgr, error);
+				if (ret)
+					ret1 = ret;
+			}
+		}
+
+		TAILQ_REMOVE(&flow->sxe2_flow_list, hw_flow, next);
+		rte_free(hw_flow);
+	}
+	rte_free(flow);
+	*flow_ptr = NULL;
+	rte_spinlock_unlock(&adapter->flow_ctxt.flow_list_lock);
+
+	return ret1;
+}
+
+static int32_t sxe2_flow_validate(struct rte_eth_dev *dev,
+			      const struct rte_flow_attr *attr,
+			      const struct rte_flow_item pattern[],
+			      const struct rte_flow_action actions[],
+			      struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct rte_flow *flow_list = NULL;
+	struct sxe2_flow *hw_flow = NULL;
+	struct sxe2_flow *hw_flow_temp = NULL;
+	flow_list = rte_zmalloc("rte_flow_va", sizeof(*flow_list), 0);
+	if (!flow_list) {
+		rte_flow_error_set(error, ENOMEM,
+				   RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				   "Failed to alloc memory for flow rule");
+		PMD_LOG_ERR(DRV, "Failed to alloc memory for flow rule.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	TAILQ_INIT(&flow_list->sxe2_flow_list);
+
+	ret = sxe2_flow_validate_with_flow(dev, flow_list, attr, pattern, actions, error);
+	if (ret != 0)
+		goto l_free;
+l_free:
+
+	TAILQ_FOREACH_SAFE(hw_flow, &flow_list->sxe2_flow_list, next, hw_flow_temp) {
+		TAILQ_REMOVE(&flow_list->sxe2_flow_list, hw_flow, next);
+		rte_free(hw_flow);
+	}
+	rte_free(flow_list);
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_isolate(struct rte_eth_dev *dev,
+				 int32_t enable,
+				 struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (dev->data->dev_started) {
+		rte_flow_error_set(error, EBUSY,
+				   RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+				   NULL,
+				   "port must be stopped first");
+		ret = -EBUSY;
+		goto l_end;
+	}
+
+	if (adapter->is_dev_repr) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"representor dev cannot change isolated mode ");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (enable == adapter->flow_isolated)
+		goto l_end;
+
+	if (adapter->dev_type == SXE2_DEV_T_VF &&
+		adapter->switchdev_info.is_switchdev) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			"isolated mode cannot be change when port in switch dev mode");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	rte_spinlock_lock(&adapter->flow_ctxt.flow_list_lock);
+	if (!TAILQ_EMPTY(&adapter->flow_ctxt.rte_flow_list))
+		PMD_DEV_LOG_WARN(adapter, DRV,
+			"The configured flow item may not take effect.");
+	rte_spinlock_unlock(&adapter->flow_ctxt.flow_list_lock);
+
+	adapter->flow_isolated = !!enable;
+
+	ret = sxe2_l2_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update l2 rule");
+
+	ret = sxe2_switchdev_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update switchdev rule");
+
+l_end:
+	if (ret == 0)
+		adapter->flow_isolate_cfg = !!enable;
+	return ret;
+}
+
+static struct rte_flow *sxe2_flow_create(struct rte_eth_dev *dev,
+					 const struct rte_flow_attr *attr,
+					 const struct rte_flow_item pattern[],
+					 const struct rte_flow_action action[],
+					 struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_flow *flow_list = NULL;
+	struct sxe2_flow *flow = NULL;
+
+	flow_list = rte_zmalloc("sxe2_flow_create", sizeof(*flow_list), 0);
+	if (!flow_list) {
+		rte_flow_error_set(error, ENOMEM,
+				   RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				   "Failed to alloc memory for flow rule");
+		PMD_LOG_ERR(DRV, "Failed to alloc memory for flow rule.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	TAILQ_INIT(&flow_list->sxe2_flow_list);
+
+	ret = sxe2_flow_validate_with_flow(dev, flow_list, attr, pattern, action, error);
+	if (ret != 0)
+		goto l_free_flow;
+
+	TAILQ_FOREACH(flow, &flow_list->sxe2_flow_list, next) {
+		ret = sxe2_fnav_get_filter_cid(adapter, flow);
+		if (ret != 0) {
+			PMD_LOG_ERR(DRV, "fnav get stats id failed, ret:%d", ret);
+			rte_flow_error_set(error, EIO,
+				   RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+				   "Failed to add fnav rule:alloc cid failed.");
+			goto l_free_flow;
+		}
+		ret = sxe2_drv_flow_filter_add(adapter, flow);
+		if (ret) {
+			rte_flow_error_set(error, EINVAL,
+				   RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+				   "Failed to add flow filter to hw.");
+			PMD_LOG_ERR(DRV, "Failed to add flow filter to hw.ret:%d:%s",
+				ret, sxe2_flow_convert_ret_to_flow_msg(ret));
+			goto l_free_flow;
+		}
+	}
+
+	TAILQ_INSERT_TAIL(&adapter->flow_ctxt.rte_flow_list, flow_list, next);
+	goto l_end;
+l_free_flow:
+	(void)sxe2_flow_rte_list_free(adapter, &flow_list, error);
+l_end:
+	return flow_list;
+}
+
+static int32_t sxe2_flow_destroy(struct rte_eth_dev *dev,
+			     struct rte_flow *flow,
+			     struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	ret = sxe2_flow_rte_list_free(adapter, &flow, error);
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to destroy flow.ret:%d.", ret);
+	return ret;
+}
+
+static int32_t sxe2_flow_flush(struct rte_eth_dev *dev, struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_flow *flow = NULL;
+	struct rte_flow *tmp_flow = NULL;
+	struct rte_flow_list_t *flow_list = &adapter->flow_ctxt.rte_flow_list;
+	TAILQ_FOREACH_SAFE(flow, flow_list, next, tmp_flow) {
+		ret = sxe2_flow_destroy(dev, flow, error);
+		if (ret) {
+			PMD_LOG_ERR(DRV, "Failed to flush flows.ret:%d.", ret);
+
+			if (ret != -EAGAIN)
+				ret = -EINVAL;
+			goto l_end;
+		}
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_fnav_get_filter_cid(struct sxe2_adapter *adapter, struct sxe2_flow *flow)
+{
+	int32_t ret = 0;
+	struct sxe2_fnav_cid_mgr_list_t *cid_mgr_list =
+				&adapter->flow_ctxt.hw_res.fnav_cid_mgr_list;
+	uint32_t stat_index;
+	uint32_t user_id;
+	uint32_t driver_id;
+	struct sxe2_fnav_cid_mgr *temp = NULL;
+	struct sxe2_fnav_cid_mgr *mgr = NULL;
+
+	if (sxe2_test_bit(SXE2_FLOW_ACTION_COUNT, flow->action.act_types)) {
+		user_id = flow->action.count.user_id;
+		driver_id = flow->action.count.driver_id;
+
+		TAILQ_FOREACH(temp, cid_mgr_list, next) {
+			if (temp->user_id == user_id &&
+				temp->driver_id == driver_id) {
+				mgr = temp;
+				break;
+			}
+		}
+		if (mgr == NULL) {
+			mgr = rte_zmalloc("sxe2_fnav_cid_mgr",
+				sizeof(struct sxe2_fnav_cid_mgr), 0);
+			if (!mgr) {
+				PMD_LOG_ERR(DRV,
+					"Failed to alloc sxe2vf_fnav_cid_mgr memory.");
+				ret = -ENOMEM;
+				goto l_end;
+			}
+
+			ret = sxe2_drv_flow_fnav_get_stat_id(adapter, &stat_index);
+			if (ret) {
+				PMD_LOG_ERR(DRV, "Failed to alloc fw count id.");
+				rte_free(mgr);
+				goto l_end;
+			}
+
+			TAILQ_INSERT_TAIL(cid_mgr_list, mgr, next);
+			mgr->user_id = user_id;
+			mgr->driver_id = driver_id;
+			mgr->stat_index = stat_index;
+			mgr->count_type = adapter->flow_ctxt.hw_res.count_type;
+		}
+		flow->action.count.stat_index = mgr->stat_index;
+		flow->action.count.stat_ctrl = mgr->count_type;
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_flow_free_mgr(struct sxe2_adapter *adapter,
+		       struct sxe2_flow *flow,
+		       struct sxe2_fnav_cid_mgr **mgr_ptr,
+		       struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_fnav_cid_mgr_list_t *cid_mgr_list =
+				&adapter->flow_ctxt.hw_res.fnav_cid_mgr_list;
+	struct sxe2_fnav_cid_mgr *mgr = *mgr_ptr;
+	uint32_t user_id = flow->action.count.user_id;
+	if (user_id == 0) {
+		TAILQ_REMOVE(cid_mgr_list, mgr, next);
+		ret = sxe2_drv_flow_fnav_free_stat(adapter, mgr->stat_index);
+		if (ret) {
+			rte_flow_error_set(error, EIO,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Failed to free flow count.");
+			PMD_LOG_ERR(DRV,
+				"Failed to free flow count, flow id: %u, ret: %d.",
+				flow->flow_id, ret);
+		}
+		rte_free(mgr);
+		*mgr_ptr = NULL;
+	}
+	return ret;
+}
+
+int32_t sxe2_flow_query_mgr(struct sxe2_adapter *adapter,
+			struct sxe2_flow *flow,
+			struct sxe2_fnav_cid_mgr **mgr_ptr,
+			struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_fnav_cid_mgr_list_t *cid_mgr_list =
+				&adapter->flow_ctxt.hw_res.fnav_cid_mgr_list;
+	struct sxe2_fnav_cid_mgr *temp = NULL;
+	struct sxe2_fnav_cid_mgr *mgr = NULL;
+	uint32_t user_id = flow->action.count.user_id;
+	uint32_t driver_id = flow->action.count.driver_id;
+
+	TAILQ_FOREACH(temp, cid_mgr_list, next) {
+		if (temp->user_id == user_id &&
+			temp->driver_id == driver_id) {
+			mgr = temp;
+			break;
+		}
+	}
+	if (!mgr) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"fnav flow query count invalid user_id or driver_id.");
+		PMD_LOG_ERR(DRV,
+			"fnav flow query count invalid user_id or driver_id.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	ret = sxe2_drv_flow_fnav_query_stat(adapter, mgr);
+	if (ret) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+			"Failed to query flow count.");
+		PMD_LOG_ERR(DRV,
+			"Failed to query flow count, flow id: %u, ret: %d.",
+			flow->flow_id, ret);
+		goto l_end;
+	}
+	*mgr_ptr = mgr;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_query_count(struct sxe2_adapter *adapter,
+				 struct sxe2_flow *flow,
+				 struct rte_flow_query_count *count,
+				 struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_fnav_cid_mgr *mgr = NULL;
+	switch (flow->action.count.stat_ctrl) {
+	case SXE2_FNAV_STAT_ENA_NONE:
+		count->hits_set = 0;
+		count->bytes_set = 0;
+		break;
+	case SXE2_FNAV_STAT_ENA_PKTS:
+		count->hits_set = 1;
+		count->bytes_set = 0;
+		break;
+	case SXE2_FNAV_STAT_ENA_BYTES:
+		count->hits_set = 0;
+		count->bytes_set = 1;
+		break;
+	case SXE2_FNAV_STAT_ENA_ALL:
+		count->hits_set = 1;
+		count->bytes_set = 1;
+		break;
+	default:
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (!sxe2_test_bit(SXE2_FLOW_ACTION_COUNT, flow->action.act_types)) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"this flow don't have count action.");
+		PMD_LOG_ERR(DRV, "this flow don't have count action.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	ret = sxe2_flow_query_mgr(adapter, flow, &mgr, error);
+	if (ret) {
+		PMD_LOG_ERR(DRV,
+			"Failed to query flow count, flow id: %u, ret: %d.",
+			flow->flow_id, ret);
+		goto l_end;
+	}
+	count->hits = mgr->hits;
+	count->bytes = mgr->bytes;
+	if (count->reset) {
+		mgr->hits = 0;
+		mgr->bytes = 0;
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_query(struct rte_eth_dev *dev,
+			   struct rte_flow *flow_list,
+			   const struct rte_flow_action *actions,
+			   void *data,
+			   struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct rte_flow_query_count *count = data;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_flow *flow = NULL;
+
+	if (!flow_list) {
+		ret = -EINVAL;
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_HANDLE,
+				NULL, "Invalid flow");
+		PMD_LOG_ERR(DRV, "Invalid flow to query flow.");
+		goto l_end;
+	}
+
+	rte_spinlock_lock(&adapter->flow_ctxt.flow_list_lock);
+	for (; actions->type != RTE_FLOW_ACTION_TYPE_END; actions++) {
+		switch (actions->type) {
+		case RTE_FLOW_ACTION_TYPE_VOID:
+			break;
+		case RTE_FLOW_ACTION_TYPE_COUNT:
+			flow = TAILQ_FIRST(&flow_list->sxe2_flow_list);
+			ret = sxe2_flow_query_count(adapter, flow, count, error);
+			if (ret) {
+				PMD_LOG_ERR(DRV,
+					"Failed to query flow count, flow id: %u, ret: %d.",
+					flow->flow_id, ret);
+				goto l_end_unlock;
+			}
+			break;
+		default:
+			rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION,
+					actions,
+					"action not supported");
+			PMD_LOG_ERR(DRV,
+					"Failed to query flow action type:%d.",
+					actions->type);
+			ret = -ENOTSUP;
+			goto l_end_unlock;
+		}
+	}
+
+l_end_unlock:
+	rte_spinlock_unlock(&adapter->flow_ctxt.flow_list_lock);
+
+l_end:
+	return ret;
+}
+
+const struct rte_flow_ops sxe2_flow_ops = {
+	.validate = sxe2_flow_validate,
+	.create = sxe2_flow_create,
+	.destroy = sxe2_flow_destroy,
+	.flush = sxe2_flow_flush,
+	.query = sxe2_flow_query,
+	.isolate = sxe2_flow_isolate,
+};
+
+int32_t sxe2_flow_ops_get(struct rte_eth_dev *dev, const struct rte_flow_ops **ops)
+{
+	int32_t ret = 0;
+
+	if (dev == NULL) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	*ops = &sxe2_flow_ops;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_flow_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	TAILQ_INIT(&adapter->flow_ctxt.rte_flow_list);
+	TAILQ_INIT(&adapter->flow_ctxt.hw_res.fnav_cid_mgr_list);
+	if (adapter->devargs.fnav_stat_type)
+		adapter->flow_ctxt.hw_res.count_type =
+			adapter->devargs.fnav_stat_type;
+	else
+		adapter->flow_ctxt.hw_res.count_type = SXE2_FNAV_STAT_ENA_ALL;
+
+	adapter->flow_ctxt.fnav_inited = 1;
+	rte_spinlock_init(&adapter->flow_ctxt.flow_list_lock);
+	return ret;
+}
+
+int32_t sxe2_flow_uninit(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_flow_error error;
+	struct sxe2_fnav_cid_mgr *mgr = NULL;
+	struct sxe2_fnav_cid_mgr *temp = NULL;
+	struct sxe2_fnav_cid_mgr_list_t *cid_mgr_list =
+						&adapter->flow_ctxt.hw_res.fnav_cid_mgr_list;
+
+	ret = sxe2_flow_flush(dev, &error);
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to flush flow, ret: %d.", ret);
+
+	TAILQ_FOREACH_SAFE(mgr, cid_mgr_list, next, temp) {
+		TAILQ_REMOVE(cid_mgr_list, mgr, next);
+		ret = sxe2_drv_flow_fnav_free_stat(adapter, mgr->stat_index);
+		if (ret)
+			PMD_LOG_ERR(DRV,
+				"Failed to free fnav stat id, ret: %d.", ret);
+		rte_free(mgr);
+	}
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_flow.h b/drivers/net/sxe2/sxe2_flow.h
new file mode 100644
index 0000000000..9970fddcf0
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_FLOW_H__
+#define __SXE2_FLOW_H__
+#include <rte_flow_driver.h>
+#include "sxe2_osal.h"
+#include "sxe2_common.h"
+
+
+int32_t sxe2_flow_ops_get(struct rte_eth_dev *dev, const struct rte_flow_ops **ops);
+
+int32_t sxe2_flow_init(struct rte_eth_dev *dev);
+
+int32_t sxe2_flow_uninit(struct rte_eth_dev *dev);
+
+int32_t sxe2_fnav_get_filter_cid(struct sxe2_adapter *adapter, struct sxe2_flow *flow);
+
+int32_t sxe2_flow_free_mgr(struct sxe2_adapter *adapter,
+		       struct sxe2_flow *flow,
+		       struct sxe2_fnav_cid_mgr **mgr_ptr,
+		       struct rte_flow_error *error);
+
+int32_t sxe2_flow_query_mgr(struct sxe2_adapter *adapter,
+			struct sxe2_flow *flow,
+			struct sxe2_fnav_cid_mgr **mgr_ptr,
+			struct rte_flow_error *error);
+#endif /* __SXE2_FLOW_H__ */
diff --git a/drivers/net/sxe2/sxe2_flow_parse_action.c b/drivers/net/sxe2/sxe2_flow_parse_action.c
new file mode 100644
index 0000000000..a9559e2d7e
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_parse_action.c
@@ -0,0 +1,1182 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include "sxe2_flow_parse_action.h"
+#include "sxe2_common_log.h"
+#include "sxe2_flow_public.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_vsi.h"
+
+
+static int32_t sxe2_flow_check_rss_action_attr(const struct rte_flow_action_rss *rss,
+					   struct rte_flow_error *error)
+{
+	int32_t ret = ENOTSUP;
+	switch (rss->func) {
+	case RTE_ETH_HASH_FUNCTION_DEFAULT:
+	case RTE_ETH_HASH_FUNCTION_TOEPLITZ:
+	case RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ:
+	case RTE_ETH_HASH_FUNCTION_SIMPLE_XOR:
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "RSS hash function[%d] not support.", rss->func);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (rss->level > 2)
+		rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"RSS  level is could not be greater than 2");
+	if (rss->key_len)
+		rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"a nonzero RSS key_len is not supported");
+	if (rss->queue_num)
+		rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"a non-NULL RSS queue is not supported");
+	ret = 0;
+l_end:
+	return ret;
+}
+
+
+static int32_t sxe2_flow_set_rss_action_func(enum rte_eth_hash_function rss_func,
+					 uint64_t rss_type,
+					 struct sxe2_flow *flow,
+					 struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	if (rss_func == RTE_ETH_HASH_FUNCTION_SIMPLE_XOR) {
+		if (flow->has_hdr) {
+			ret = -EINVAL;
+			rte_flow_error_set(error, -EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Failed to cfg Simple XOR hash with not empty pattern");
+			PMD_LOG_ERR(DRV, "Failed to cfg Simple XOR hash with not empty pattern.");
+			goto l_end;
+		}
+	} else {
+		if (!flow->has_hdr) {
+			ret = -EINVAL;
+			rte_flow_error_set(error, -EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Failed to cfg Simple hash with empty pattern");
+			PMD_LOG_ERR(DRV, "Failed to cfg Simple hash with empty pattern.");
+			goto l_end;
+		}
+	}
+
+	if (rss_func == RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ) {
+		if (rss_type & (RTE_ETH_RSS_L3_SRC_ONLY |
+					RTE_ETH_RSS_L3_DST_ONLY |
+					RTE_ETH_RSS_L4_SRC_ONLY |
+					RTE_ETH_RSS_L4_DST_ONLY)) {
+			ret = -EINVAL;
+			rte_flow_error_set(error, -EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Failed to cfg symm func rss_type l3/l4 only.");
+			PMD_LOG_ERR(DRV, "Failed to cfg symm func rss_type l3/l4 only.");
+			goto l_end;
+		}
+
+		if (!(rss_type & (RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_IPV6 |
+					RTE_ETH_RSS_FRAG_IPV4 | RTE_ETH_RSS_FRAG_IPV6 |
+					RTE_ETH_RSS_NONFRAG_IPV4_UDP |
+					RTE_ETH_RSS_NONFRAG_IPV6_UDP |
+					RTE_ETH_RSS_NONFRAG_IPV4_TCP |
+					RTE_ETH_RSS_NONFRAG_IPV6_TCP |
+					RTE_ETH_RSS_NONFRAG_IPV4_SCTP |
+					RTE_ETH_RSS_NONFRAG_IPV6_SCTP))) {
+			ret = -EINVAL;
+			rte_flow_error_set(error, -EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Failed to cfg symm func unsupported rss_type.");
+			PMD_LOG_ERR(DRV, "Failed to cfg symm func unsupported rss_type.");
+			goto l_end;
+		}
+		flow->action.rss.func = SXE2_RSS_HASH_FUNC_SYM_TOEPLITZ;
+	}
+	if (rss_func == RTE_ETH_HASH_FUNCTION_SIMPLE_XOR)
+		flow->action.rss.func = SXE2_RSS_HASH_FUNC_XOR;
+	if (rss_func == RTE_ETH_HASH_FUNCTION_DEFAULT)
+		flow->action.rss.func = SXE2_RSS_HASH_FUNC_TOEPLITZ;
+	if (rss_func == RTE_ETH_HASH_FUNCTION_TOEPLITZ)
+		flow->action.rss.func = SXE2_RSS_HASH_FUNC_TOEPLITZ;
+l_end:
+	return ret;
+}
+
+
+static uint64_t sxe2_hash_invalid_comb[] = {
+	RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_UDP,
+	RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_TCP,
+	RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_SCTP,
+	RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_NONFRAG_IPV6_UDP,
+	RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_NONFRAG_IPV6_TCP,
+	RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_NONFRAG_IPV6_SCTP,
+	RTE_ETH_RSS_FRAG_IPV4 | RTE_ETH_RSS_NONFRAG_IPV4_OTHER,
+	RTE_ETH_RSS_FRAG_IPV6 | RTE_ETH_RSS_NONFRAG_IPV6_OTHER,
+	RTE_ETH_RSS_L3_PRE32 | RTE_ETH_RSS_L3_PRE48 | RTE_ETH_RSS_L3_PRE64,
+};
+
+struct sxe2_rss_attr_type {
+	uint64_t attr;
+	uint64_t type;
+};
+
+static struct sxe2_rss_attr_type sxe2_rss_attr_valid_type[] = {
+	{RTE_ETH_RSS_L2_SRC_ONLY | RTE_ETH_RSS_L2_DST_ONLY, RTE_ETH_RSS_ETH},
+	{RTE_ETH_RSS_L3_SRC_ONLY | RTE_ETH_RSS_L3_DST_ONLY, SXE2_VALID_RSS_L3},
+	{RTE_ETH_RSS_L4_SRC_ONLY | RTE_ETH_RSS_L4_DST_ONLY, SXE2_VALID_RSS_L4},
+
+	{RTE_ETH_RSS_L3_PRE32, SXE2_VALID_RSS_IPV6},
+	{RTE_ETH_RSS_L3_PRE48, SXE2_VALID_RSS_IPV6},
+	{RTE_ETH_RSS_L3_PRE64, SXE2_VALID_RSS_IPV6},
+	{SXE2_INVALID_RSS_ATTR, 0}
+};
+
+
+static void sxe2_flow_action_pre(struct sxe2_flow *flow)
+{
+	flow->action.vsi.vsi_index = UINT16_MAX;
+	flow->action.vsi_list.vsi_cnt = 0;
+	sxe2_bitmap_zero(flow->action.vsi_list.vsi_list_map, SXE2_VSI_MAX);
+}
+
+static void sxe2_flow_action_post(struct sxe2_flow *flow, uint8_t action_num[])
+{
+	if (sxe2_test_bit(SXE2_FLOW_ACTION_TO_VSI, flow->action.act_types))
+		action_num[SXE2_FLOW_ACTION_TO_VSI] = 1;
+
+	if (sxe2_test_bit(SXE2_FLOW_ACTION_TO_VSI_LIST, flow->action.act_types))
+		action_num[SXE2_FLOW_ACTION_TO_VSI_LIST] = 1;
+}
+
+
+static void sxe2_flow_action_vsi_merge(struct sxe2_flow *flow, uint16_t add_vsi_id)
+{
+	if (flow->action.vsi_list.vsi_cnt == 0) {
+		if (flow->action.vsi.vsi_index == UINT16_MAX) {
+			flow->action.vsi.vsi_index = add_vsi_id;
+			sxe2_set_bit(SXE2_FLOW_ACTION_TO_VSI, flow->action.act_types);
+			goto l_end;
+		}
+
+		if (flow->action.vsi.vsi_index == add_vsi_id)
+			goto l_end;
+
+		sxe2_set_bit(flow->action.vsi.vsi_index, flow->action.vsi_list.vsi_list_map);
+		sxe2_set_bit(add_vsi_id, flow->action.vsi_list.vsi_list_map);
+		flow->action.vsi_list.vsi_cnt = 2;
+		flow->action.vsi.vsi_index = UINT16_MAX;
+		sxe2_clear_bit(SXE2_FLOW_ACTION_TO_VSI, flow->action.act_types);
+		sxe2_set_bit(SXE2_FLOW_ACTION_TO_VSI_LIST, flow->action.act_types);
+	}
+
+	if (sxe2_test_bit(add_vsi_id, flow->action.vsi_list.vsi_list_map))
+		goto l_end;
+
+	sxe2_set_bit(add_vsi_id, flow->action.vsi_list.vsi_list_map);
+	flow->action.vsi_list.vsi_cnt++;
+
+l_end:
+	return;
+}
+
+
+static int32_t sxe2_flow_vsi_get_ethdev(struct rte_eth_dev *dev,
+				uint16_t dev_port_id, uint16_t *vsi_index)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_eth_dev *dst_dev;
+	struct sxe2_adapter *dst_adapter;
+	int32_t ret = 0;
+
+	dst_dev = &rte_eth_devices[dev_port_id];
+	if (!dst_dev->data) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (!sxe2_ethdev_check(dst_dev)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "dst dev is not sxe2 ethdev.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	dst_adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dst_dev);
+	if (!dst_adapter) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "dst dev adapter is null.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (adapter->dev_info.pci.serial_number != dst_adapter->dev_info.pci.serial_number) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "dst dev sn is miss match current dev.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (adapter->dev_type != SXE2_DEV_T_PF_BOND) {
+		if (adapter->pf_idx != dst_adapter->pf_idx) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "dst dev pf id is miss match current dev.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+
+	if (dst_adapter->is_dev_repr) {
+		if (dst_adapter->repr_priv_data == NULL) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "dst dev repr data is null.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		*vsi_index = dst_adapter->repr_priv_data->repr_vf_vsi_id;
+	} else {
+		*vsi_index = dst_adapter->vsi_ctxt.dpdk_vsi_id;
+	}
+
+l_end:
+	return ret;
+}
+static int32_t sxe2_flow_check_rss_action_type_with_pattern(struct sxe2_flow_pattern *pattern,
+							uint64_t rss_type)
+{
+	uint64_t rss_type_allow = pattern->rss_type_allow;
+	int32_t ret = -EINVAL;
+
+	if ((rss_type & rss_type_allow) != rss_type)
+		goto l_end;
+
+	if (sxe2_test_bit(SXE2_FLOW_HDR_VLAN, pattern->hdrs) &&
+		!sxe2_test_bit(SXE2_FLOW_HDR_QINQ, pattern->hdrs)) {
+		if ((rss_type & RTE_ETH_RSS_C_VLAN) != 0 &&
+			(rss_type & RTE_ETH_RSS_S_VLAN) == 0)
+			goto l_end;
+	}
+	ret = 0;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_check_rss_action_type_valid(uint64_t rss_type)
+{
+	struct sxe2_rss_attr_type *attr_type;
+	uint32_t i;
+	int32_t ret = -EINVAL;
+
+	for (i = 0; i < RTE_DIM(sxe2_hash_invalid_comb); i++) {
+		if (rte_popcount64(rss_type & sxe2_hash_invalid_comb[i]) > 1) {
+			PMD_LOG_ERR(DRV, "Error rss_type invalid comb[%d].", i);
+			goto l_end;
+		}
+	}
+
+	for (i = 0; i < RTE_DIM(sxe2_rss_attr_valid_type); i++) {
+		attr_type = &sxe2_rss_attr_valid_type[i];
+		if ((attr_type->attr & rss_type) &&
+				!(attr_type->type & rss_type)) {
+			PMD_LOG_ERR(DRV, "Rss_type valid_comb[%d] check error.", i);
+			goto l_end;
+		}
+	}
+
+	ret = 0;
+l_end:
+	return ret;
+}
+
+
+static void sxe2_flow_set_rss_action_type_l234(BITMAP_TYPE *hdr,
+					       BITMAP_TYPE *fld,
+					       uint64_t rss_type)
+{
+	if (rss_type & RTE_ETH_RSS_ETH) {
+		if (rss_type & RTE_ETH_RSS_L2_SRC_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_SA, fld);
+		} else if (rss_type & RTE_ETH_RSS_L2_DST_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_DA, fld);
+		} else {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_SA, fld);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_DA, fld);
+		}
+	}
+	if (rss_type & RTE_ETH_RSS_L2_PAYLOAD) {
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_TYPE, fld);
+		sxe2_set_bit(SXE2_FLOW_HDR_ETH_NON_IP, hdr);
+	}
+
+	if (sxe2_test_bit(SXE2_FLOW_HDR_VLAN, hdr)) {
+		if (rss_type & RTE_ETH_RSS_S_VLAN)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_S_TCI, fld);
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_QINQ, hdr)) {
+		if (rss_type & RTE_ETH_RSS_C_VLAN)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_C_TCI, fld);
+	}
+
+	if (rss_type & (RTE_ETH_RSS_IPV4 |
+				RTE_ETH_RSS_NONFRAG_IPV4_OTHER |
+				RTE_ETH_RSS_NONFRAG_IPV4_UDP |
+				RTE_ETH_RSS_NONFRAG_IPV4_TCP |
+				RTE_ETH_RSS_NONFRAG_IPV4_SCTP |
+				RTE_ETH_RSS_FRAG_IPV4 |
+				RTE_ETH_RSS_IPV4_CHKSUM)) {
+		if (rss_type & RTE_ETH_RSS_L3_SRC_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_SA, fld);
+		} else if (rss_type & RTE_ETH_RSS_L3_DST_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_DA, fld);
+		} else {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_SA, fld);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_DA, fld);
+		}
+
+		if (rss_type & RTE_ETH_RSS_NONFRAG_IPV4_OTHER)
+			sxe2_set_bit(SXE2_FLOW_HDR_IPV_OTHER, hdr);
+		if (rss_type & RTE_ETH_RSS_FRAG_IPV4) {
+			sxe2_set_bit(SXE2_FLOW_HDR_IPV_FRAG, hdr);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_ID, fld);
+		}
+
+		if (rss_type & RTE_ETH_RSS_IPV4_CHKSUM)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_CHKSUM, fld);
+	}
+
+	if (rss_type & (RTE_ETH_RSS_IPV6 |
+				RTE_ETH_RSS_FRAG_IPV6 |
+				RTE_ETH_RSS_NONFRAG_IPV6_OTHER |
+				RTE_ETH_RSS_NONFRAG_IPV6_UDP |
+				RTE_ETH_RSS_NONFRAG_IPV6_TCP |
+				RTE_ETH_RSS_NONFRAG_IPV6_SCTP)) {
+		if (rss_type & RTE_ETH_RSS_L3_PRE32) {
+			if (rss_type & RTE_ETH_RSS_L3_SRC_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE32_SA, fld);
+			} else if (rss_type & RTE_ETH_RSS_L3_DST_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE32_DA, fld);
+			} else {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE32_SA, fld);
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE32_DA, fld);
+			}
+		} else if (rss_type & RTE_ETH_RSS_L3_PRE48) {
+			if (rss_type & RTE_ETH_RSS_L3_SRC_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE48_SA, fld);
+			} else if (rss_type & RTE_ETH_RSS_L3_DST_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE48_DA, fld);
+			} else {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE48_SA, fld);
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE48_DA, fld);
+			}
+		} else if (rss_type & RTE_ETH_RSS_L3_PRE64) {
+			if (rss_type & RTE_ETH_RSS_L3_SRC_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE64_SA, fld);
+			} else if (rss_type & RTE_ETH_RSS_L3_DST_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE64_DA, fld);
+			} else {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE64_SA, fld);
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PRE64_DA, fld);
+			}
+		} else {
+			if (rss_type & RTE_ETH_RSS_L3_SRC_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_SA, fld);
+			} else if (rss_type & RTE_ETH_RSS_L3_DST_ONLY) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_DA, fld);
+			} else {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_SA, fld);
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_DA, fld);
+			}
+		}
+		if (rss_type & RTE_ETH_RSS_FRAG_IPV6) {
+			sxe2_set_bit(SXE2_FLOW_HDR_IPV_FRAG, hdr);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_ID, fld);
+		}
+		if (rss_type & RTE_ETH_RSS_NONFRAG_IPV6_OTHER)
+			sxe2_set_bit(SXE2_FLOW_HDR_IPV_OTHER, hdr);
+	}
+
+	if (rss_type & (RTE_ETH_RSS_NONFRAG_IPV4_TCP |
+				RTE_ETH_RSS_NONFRAG_IPV6_TCP)) {
+		if (rss_type & RTE_ETH_RSS_L4_SRC_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_SRC_PORT, fld);
+		} else if (rss_type & RTE_ETH_RSS_L4_DST_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_DST_PORT, fld);
+		} else {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_SRC_PORT, fld);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_DST_PORT, fld);
+		}
+		if (rss_type & RTE_ETH_RSS_L4_CHKSUM)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_CHKSUM, fld);
+	}
+
+	if (rss_type & (RTE_ETH_RSS_NONFRAG_IPV4_UDP |
+				RTE_ETH_RSS_NONFRAG_IPV6_UDP)) {
+		if (rss_type & RTE_ETH_RSS_L4_SRC_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_SRC_PORT, fld);
+		} else if (rss_type & RTE_ETH_RSS_L4_DST_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_DST_PORT, fld);
+		} else {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_SRC_PORT, fld);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_DST_PORT, fld);
+		}
+		if (rss_type & RTE_ETH_RSS_L4_CHKSUM)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_CHKSUM, fld);
+	}
+
+	if (rss_type & (RTE_ETH_RSS_NONFRAG_IPV4_SCTP |
+				RTE_ETH_RSS_NONFRAG_IPV6_SCTP)) {
+		if (rss_type & RTE_ETH_RSS_L4_SRC_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_SRC_PORT, fld);
+		} else if (rss_type & RTE_ETH_RSS_L4_DST_ONLY) {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_DST_PORT, fld);
+		} else {
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_SRC_PORT, fld);
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_DST_PORT, fld);
+		}
+		if (rss_type & RTE_ETH_RSS_L4_CHKSUM)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_CHKSUM, fld);
+	}
+}
+
+
+static int32_t sxe2_flow_set_rss_action_hdr_type(struct sxe2_flow *flow,
+		bool is_inner)
+{
+	struct sxe2_flow_action_rss *rss = &flow->action.rss;
+	BITMAP_TYPE *hdr = rss->hdr_out;
+	int32_t ret = 0;
+
+	rss->hdr_type = SXE2_RSS_ANY_HEADERS;
+	if (!is_inner) {
+		rss->hdr_type = SXE2_RSS_OUTER_HEADERS;
+		goto l_end;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV4, hdr)) {
+		if (sxe2_test_bit(SXE2_FLOW_HDR_UDP, hdr)) {
+			if (sxe2_test_bit(SXE2_FLOW_HDR_GRE, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_GRE;
+			} else if (sxe2_test_bit(SXE2_FLOW_HDR_GENEVE, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_GENEVE;
+			} else if (sxe2_test_bit(SXE2_FLOW_HDR_VXLAN, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_VXLAN;
+			} else if (sxe2_test_bit(SXE2_FLOW_HDR_GTPU, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_GTPU;
+			}
+		} else {
+			if (sxe2_test_bit(SXE2_FLOW_HDR_GRE, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_GRE;
+			} else {
+				rss->hdr_type = SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4;
+			}
+		}
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_IPV6, hdr)) {
+		if (sxe2_test_bit(SXE2_FLOW_HDR_UDP, hdr)) {
+			if (sxe2_test_bit(SXE2_FLOW_HDR_GRE, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_GRE;
+			} else if (sxe2_test_bit(SXE2_FLOW_HDR_GENEVE, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_GENEVE;
+			} else if (sxe2_test_bit(SXE2_FLOW_HDR_VXLAN, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_VXLAN;
+			} else if (sxe2_test_bit(SXE2_FLOW_HDR_GTPU, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_GTPU;
+			}
+		} else {
+			if (sxe2_test_bit(SXE2_FLOW_HDR_GRE, hdr)) {
+				rss->hdr_type =
+					SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_GRE;
+			} else {
+				rss->hdr_type = SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6;
+			}
+		}
+	}
+
+l_end:
+	if (rss->hdr_type == SXE2_RSS_ANY_HEADERS) {
+		ret = -EINVAL;
+		PMD_LOG_ERR(DRV, "Unsupported rss hdr type.");
+	}
+	return ret;
+}
+
+static int32_t sxe2_flow_set_rss_action_level(uint32_t level,
+		struct sxe2_flow *flow, struct rte_flow_error *error)
+{
+	bool is_inner = false;
+	struct sxe2_flow_action_rss *rss = &flow->action.rss;
+	int32_t ret = 0;
+
+	if (flow->meta.tunnel_type != SXE2_FLOW_TUNNEL_TYPE_NONE) {
+		if (level == 0 || level == 2)
+			is_inner = true;
+		else if (level == 1)
+			is_inner = false;
+	} else {
+		if (level == 2) {
+			ret = -EINVAL;
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"RSS hash level 2 is not allowed no tunnel flow.");
+			PMD_LOG_ERR(DRV, "RSS hash level 2 is not allowed no tunnel flow.");
+			goto l_end;
+		}
+		is_inner = false;
+	}
+	rss->is_inner = is_inner;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_set_rss_action_type(uint64_t rss_type,
+		struct sxe2_flow *flow, struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_flow_pattern *pattern = NULL;
+	struct sxe2_flow_action_rss *rss = &flow->action.rss;
+	BITMAP_TYPE *hdr;
+	BITMAP_TYPE *fld;
+	bool is_inner = rss->is_inner;
+
+	ret = sxe2_flow_check_rss_action_type_valid(rss_type);
+	if (ret) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"RSS hash type has invalid combination.");
+		PMD_LOG_ERR(DRV, "RSS hash type has invalid combination.");
+		goto l_end;
+	}
+
+	pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	ret = sxe2_flow_check_rss_action_type_with_pattern(pattern,
+			rss_type);
+	if (ret) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"RSS hash type is not allowed by pattern.");
+		PMD_LOG_ERR(DRV, "RSS hash type is not allowed by pattern.");
+		goto l_end;
+	}
+
+	sxe2_bitmap_copy(rss->hdr_out, flow->pattern_outer.hdrs,
+			SXE2_FLOW_HDR_MAX);
+	sxe2_bitmap_copy(rss->hdr_in, flow->pattern_inner.hdrs,
+			SXE2_FLOW_HDR_MAX);
+	hdr = is_inner ? rss->hdr_in : rss->hdr_out;
+	fld = rss->fld;
+	sxe2_flow_set_rss_action_type_l234(hdr, fld, rss_type);
+
+	ret = sxe2_flow_set_rss_action_hdr_type(flow, is_inner);
+	if (ret) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"Unsupported rss hdr type.");
+		PMD_LOG_ERR(DRV, "Unsupported rss hdr type.");
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_action_rss(const struct rte_flow_action *action,
+				      struct rte_flow_error *error,
+				      struct sxe2_flow *flow)
+{
+	const struct rte_flow_action_rss *rss = action->conf;
+	int32_t ret = 0;
+	uint64_t rss_type = rss->types;
+	enum rte_eth_hash_function rss_func = rss->func;
+	uint32_t level = rss->level;
+
+	rss_type = rte_eth_rss_hf_refine(rss_type);
+
+	ret = sxe2_flow_check_rss_action_attr(rss, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_set_rss_action_func(rss_func, rss_type, flow, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_set_rss_action_level(level, flow, error);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_flow_set_rss_action_type(rss_type, flow, error);
+	if (ret)
+		goto l_end;
+	sxe2_set_bit(SXE2_FLOW_ACTION_RSS, flow->action.act_types);
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_action_qregion(struct rte_eth_dev *dev,
+		const struct rte_flow_action *action,
+		struct rte_flow_error *error, struct sxe2_flow *flow)
+{
+	int32_t ret = 0;
+	uint8_t i = 0;
+	const struct rte_flow_action_rss *rss = action->conf;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (rss->types != 0 || rss->key_len != 0) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Queue region not support rss types or key.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (rss->queue_num <= 1) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Queue region size can't be 0 or 1.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	for (i = 0; i < rss->queue_num - 1; i++) {
+		if (rss->queue[i + 1] != rss->queue[i] + 1) {
+			rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+					action, "Queue index for queue region is not continuous.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+	if (rss->queue[rss->queue_num - 1] >= adapter->dev_info.dev_data->nb_rx_queues) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Queue index for queue region is out of range.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (!(rte_is_power_of_2(rss->queue_num))) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Queue region size must be power of 2.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	flow->action.q_region.vsi_index = adapter->vsi_ctxt.dpdk_vsi_id;
+	flow->action.q_region.q_index = rss->queue[0];
+	flow->action.q_region.region = (uint8_t)rte_log2_u32(rss->queue_num);
+	sxe2_set_bit(SXE2_FLOW_ACTION_Q_REGION, flow->action.act_types);
+l_end:
+	return ret;
+}
+
+
+static int32_t sxe2_flow_parse_action_queue(struct rte_eth_dev *dev,
+					const struct rte_flow_action *action,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow)
+{
+	int32_t ret = 0;
+	const struct rte_flow_action_queue *queue = action->conf;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (queue->index >= adapter->dev_info.dev_data->nb_rx_queues) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+			action, "Invalid queue index.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	flow->action.queue.vsi_index = adapter->vsi_ctxt.dpdk_vsi_id;
+	flow->action.queue.q_index = queue->index;
+	sxe2_set_bit(SXE2_FLOW_ACTION_QUEUE, flow->action.act_types);
+l_end:
+	return ret;
+}
+
+
+static int32_t sxe2_flow_parse_action_represented_port(struct rte_eth_dev *dev,
+						   const struct rte_flow_action *action,
+						   struct rte_flow_error *error,
+						   struct sxe2_flow *flow)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	const struct rte_flow_action_ethdev *action_ethdev_conf;
+	const struct rte_eth_dev *dst_repr_dev;
+	uint16_t dst_repr_vsi_id;
+	uint16_t dst_backer_port_id;
+	uint16_t src_backer_port_id;
+	int32_t ret = 0;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		rte_flow_error_set(error, ENOTSUP,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Represented port action only support in switchdev mode.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (adapter->dev_type == SXE2_DEV_T_VF) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"Failed to cfg vf dev type.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	action_ethdev_conf = action->conf;
+	if (!rte_eth_dev_is_valid_port(action_ethdev_conf->port_id)) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Invalid port for represented port action.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	dst_repr_dev = &rte_eth_devices[action_ethdev_conf->port_id];
+	if (!dst_repr_dev->data) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Invalid port for represented port action.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	dst_backer_port_id = dst_repr_dev->data->backer_port_id;
+	if (adapter->is_dev_repr)
+		src_backer_port_id = dev->data->backer_port_id;
+	else
+		src_backer_port_id = adapter->dev_port_id;
+
+	if (src_backer_port_id != dst_backer_port_id) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Represented port action only support to cfg port in same device.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_flow_vsi_get_ethdev(dev, action_ethdev_conf->port_id, &dst_repr_vsi_id);
+	if (ret != 0) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION,
+			action, "Port representor action port dev invalid.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	sxe2_flow_action_vsi_merge(flow, dst_repr_vsi_id);
+l_end:
+	return ret;
+}
+
+
+static int32_t sxe2_flow_parse_action_port_representor(struct rte_eth_dev *dev,
+						   const struct rte_flow_action *action,
+						   struct rte_flow_error *error,
+						   struct sxe2_flow *flow)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	const struct rte_flow_action_ethdev *action_ethdev_conf;
+	uint16_t dst_vsi_id;
+	int32_t ret = 0;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"Port representor action only support in switchdev mode.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (adapter->dev_type == SXE2_DEV_T_VF) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"Cfg rule dev type is vf.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (!adapter->is_dev_repr) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"Cfg rule dev type is not repr.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	action_ethdev_conf = action->conf;
+	if (!action_ethdev_conf || !rte_eth_dev_is_valid_port(action_ethdev_conf->port_id)) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Invalid port for port representor action.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (dev->data->backer_port_id != action_ethdev_conf->port_id) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Invalid port for port representor.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_flow_vsi_get_ethdev(dev, action_ethdev_conf->port_id, &dst_vsi_id);
+	if (ret != 0) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION,
+			action, "Port representor action port dev invalid.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	sxe2_flow_action_vsi_merge(flow, dst_vsi_id);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_flow_parse_action_port_id(struct rte_eth_dev *dev,
+				   const struct rte_flow_action *action,
+				   struct rte_flow_error *error,
+				   struct sxe2_flow *flow)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	const struct rte_flow_action_port_id *action_port_id_conf;
+	uint16_t dst_port_id;
+	uint16_t dst_vsi_id;
+	int32_t ret = 0;
+
+	action_port_id_conf = (const struct rte_flow_action_port_id *)action->conf;
+	dst_port_id = action_port_id_conf->original ?
+			adapter->dev_port_id : action_port_id_conf->id;
+
+	if (!rte_eth_dev_is_valid_port(dst_port_id)) {
+		rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Invalid port id.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_flow_vsi_get_ethdev(dev, dst_port_id, &dst_vsi_id);
+	if (ret != 0) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, action,
+			"Failed to cfg port dev invalid.");
+		goto l_end;
+	}
+
+	sxe2_flow_action_vsi_merge(flow, dst_vsi_id);
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_action_send_to_kernel(struct rte_eth_dev *dev,
+			const struct rte_flow_action *action, struct rte_flow_error *error,
+			struct sxe2_flow *flow)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	if (adapter->vsi_ctxt.kernel_vsi_id == UINT16_MAX) {
+		rte_flow_error_set(error, ENOTSUP,
+			RTE_FLOW_ERROR_TYPE_ACTION, action,
+			"Failed to cfg send to kernel action without kernel vsi.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	sxe2_flow_action_vsi_merge(flow, adapter->vsi_ctxt.kernel_vsi_id);
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flow_check_actions(struct rte_eth_dev *dev __rte_unused, struct sxe2_flow *flow,
+				   uint8_t action_num[], struct rte_flow_error *error)
+{
+	enum sxe2_flow_engine_type engine_type = flow->engine_type;
+	int32_t ret = 0;
+	int32_t dest_num = action_num[SXE2_FLOW_ACTION_Q_REGION] +
+			   action_num[SXE2_FLOW_ACTION_QUEUE];
+	int32_t vsi_num = action_num[SXE2_FLOW_ACTION_TO_VSI];
+	int32_t vsi_list_num = action_num[SXE2_FLOW_ACTION_TO_VSI_LIST];
+	int32_t pass_num = action_num[SXE2_FLOW_ACTION_PASSTHRU];
+	int32_t drop_num = action_num[SXE2_FLOW_ACTION_DROP];
+	int32_t mark_num = action_num[SXE2_FLOW_ACTION_MARK];
+	int32_t count_num = action_num[SXE2_FLOW_ACTION_COUNT];
+	int32_t rss_num = action_num[SXE2_FLOW_ACTION_RSS];
+	int32_t fwd_num = dest_num + vsi_num + vsi_list_num;
+	int32_t total_num = dest_num + vsi_num + vsi_list_num + pass_num +
+			drop_num + mark_num + count_num + rss_num;
+
+	if (pass_num > 1 || drop_num > 1 || mark_num > 1 ||
+			count_num > 1 || rss_num > 1 || dest_num > 1 ||
+			vsi_num > 1 || vsi_list_num > 1) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"ecah action can only be used once.");
+		PMD_LOG_ERR(DRV, "ecah action can only be used once.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (vsi_list_num && engine_type != SXE2_FLOW_ENGINE_SWITCH) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"VSI_LIST action is only supported for switch engine.");
+		PMD_LOG_ERR(DRV, "VSI_LIST action is only supported for switch engine.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (drop_num) {
+		if (total_num > drop_num + count_num) {
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"Drop action can't be used with other actions unless count.");
+			PMD_LOG_ERR(DRV,
+				"Drop action can't be used with other actions unless count.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+
+	if (fwd_num > 1) {
+		rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+			"Only supports one type of forwarding action.");
+		PMD_LOG_ERR(DRV, "Only supports one type of forwarding action.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (vsi_list_num) {
+		if (total_num > vsi_list_num) {
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+				"VSI_LIST action can't be used with other actions.");
+			PMD_LOG_ERR(DRV,
+				"VSI_LIST action can't be used with other actions.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+
+	if (engine_type == SXE2_FLOW_ENGINE_FNAV) {
+		if (vsi_num) {
+			flow->action.q_region.q_index = 0;
+			flow->action.q_region.region = 7;
+			flow->action.q_region.vsi_index = flow->action.vsi.vsi_index;
+			sxe2_set_bit(SXE2_FLOW_ACTION_Q_REGION, flow->action.act_types);
+			dest_num++;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+
+int32_t sxe2_flow_parse_action(struct rte_eth_dev *dev,
+			const struct rte_flow_action actions[],
+			struct rte_flow_error *error,
+			struct sxe2_flow *flow)
+{
+	int32_t ret = 0;
+	const struct rte_flow_action *action;
+	const struct rte_flow_action_count *act_count;
+	const struct rte_flow_action_mark *act_mark;
+	uint8_t action_num[SXE2_FLOW_ACTION_MAX] = {0};
+	enum sxe2_flow_engine_type engine_type = flow->engine_type;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	sxe2_flow_action_pre(flow);
+
+	for (action = actions; action->type != RTE_FLOW_ACTION_TYPE_END; action++) {
+		switch (action->type) {
+		case RTE_FLOW_ACTION_TYPE_VOID:
+			break;
+		case RTE_FLOW_ACTION_TYPE_PASSTHRU:
+			if (engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				sxe2_set_bit(SXE2_FLOW_ACTION_PASSTHRU, flow->action.act_types);
+				action_num[SXE2_FLOW_ACTION_PASSTHRU]++;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"Passthru action is not supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"Passthru action is not supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_DROP:
+			if (engine_type == SXE2_FLOW_ENGINE_ACL ||
+				engine_type == SXE2_FLOW_ENGINE_SWITCH ||
+				engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				sxe2_set_bit(SXE2_FLOW_ACTION_DROP, flow->action.act_types);
+				action_num[SXE2_FLOW_ACTION_DROP]++;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"Drop action is not supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"Drop action is not supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_MARK:
+			if (engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				sxe2_set_bit(SXE2_FLOW_ACTION_MARK, flow->action.act_types);
+				act_mark = action->conf;
+				flow->action.mark.mark_id = act_mark->id;
+				action_num[SXE2_FLOW_ACTION_MARK]++;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"Mark action is not supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"Mark action is not supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_COUNT:
+			if (engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				sxe2_set_bit(SXE2_FLOW_ACTION_COUNT, flow->action.act_types);
+				act_count = action->conf;
+				flow->action.count.user_id = act_count->id;
+				flow->action.count.driver_id = 0;
+				if (flow->action.count.user_id == 0)
+					flow->action.count.driver_id =
+						++adapter->flow_ctxt.hw_res.global_index;
+				action_num[SXE2_FLOW_ACTION_COUNT]++;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"Count action is not supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"Count action is not supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_RSS:
+			if (engine_type == SXE2_FLOW_ENGINE_RSS) {
+				ret = sxe2_flow_parse_action_rss(action, error, flow);
+				if (ret != 0)
+					goto l_end;
+				action_num[SXE2_FLOW_ACTION_RSS]++;
+			} else if (engine_type == SXE2_FLOW_ENGINE_ACL ||
+				engine_type == SXE2_FLOW_ENGINE_SWITCH ||
+				engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				ret = sxe2_flow_parse_action_qregion(dev, action, error, flow);
+				if (ret != 0)
+					goto l_end;
+				action_num[SXE2_FLOW_ACTION_Q_REGION]++;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"RSS action is only supported for RSS flow.");
+				PMD_LOG_ERR(DRV,
+					"RSS action is only supported for RSS flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_QUEUE:
+			if (engine_type == SXE2_FLOW_ENGINE_ACL ||
+				engine_type == SXE2_FLOW_ENGINE_SWITCH ||
+				engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				ret = sxe2_flow_parse_action_queue(dev, action, error, flow);
+				if (ret != 0)
+					goto l_end;
+				action_num[SXE2_FLOW_ACTION_QUEUE]++;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"Queue action is not supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"Queue action is not supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
+			if (engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+				ret = sxe2_flow_parse_action_represented_port(dev,
+					action, error, flow);
+				if (ret != 0)
+					goto l_end;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"REPRESENTED PORT action is only supported for SWITCH flow.");
+				PMD_LOG_ERR(DRV,
+					"REPRESENTED PORT action is only supported for SWITCH flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
+			if (engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+				ret = sxe2_flow_parse_action_port_representor(dev,
+					action, error, flow);
+				if (ret != 0)
+					goto l_end;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"PORT REPRESENTOR action is only supported for SWITCH flow.");
+				PMD_LOG_ERR(DRV,
+					"PORT REPRESENTOR action is only supported for SWITCH flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_PORT_ID:
+			if (engine_type == SXE2_FLOW_ENGINE_SWITCH ||
+				engine_type == SXE2_FLOW_ENGINE_ACL ||
+				engine_type == SXE2_FLOW_ENGINE_FNAV) {
+				ret = sxe2_flow_parse_action_port_id(dev, action,
+						error, flow);
+				if (ret != 0)
+					goto l_end;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"PORT ID action is only supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"PORT ID action is only supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		case RTE_FLOW_ACTION_TYPE_SEND_TO_KERNEL:
+			if (engine_type == SXE2_FLOW_ENGINE_ACL ||
+				engine_type == SXE2_FLOW_ENGINE_FNAV ||
+				engine_type == SXE2_FLOW_ENGINE_SWITCH) {
+				ret = sxe2_flow_parse_action_send_to_kernel(dev,
+						action, error, flow);
+				if (ret != 0)
+					goto l_end;
+			} else {
+				rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION, action,
+					"SEND TO KERNEL action is only supported for this flow.");
+				PMD_LOG_ERR(DRV,
+					"SEND TO KERNEL action is only supported for this flow.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			break;
+		default:
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION, actions,
+				"Invalid action.");
+			PMD_LOG_ERR(DRV, "Invalid action type:%d", actions->type);
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+
+	sxe2_flow_action_post(flow, action_num);
+
+	ret = sxe2_flow_check_actions(dev, flow, action_num, error);
+	if (ret != 0)
+		goto l_end;
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_flow_parse_action.h b/drivers/net/sxe2/sxe2_flow_parse_action.h
new file mode 100644
index 0000000000..479d10a522
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_parse_action.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef SXE2_FLOW_PARSE_ACTION_H_
+#define SXE2_FLOW_PARSE_ACTION_H_
+#include <rte_flow_driver.h>
+
+#include "sxe2_osal.h"
+#include "sxe2_flow_define.h"
+
+
+int32_t sxe2_flow_parse_action(struct rte_eth_dev *dev,
+			const struct rte_flow_action actions[],
+			struct rte_flow_error *error,
+			struct sxe2_flow *flow);
+
+int32_t sxe2_flow_parse_action_port_id(struct rte_eth_dev *dev,
+			const struct rte_flow_action *action,
+			struct rte_flow_error *error,
+			struct sxe2_flow *flow);
+
+#endif /* SXE2_FLOW_PARSE_ACTION_H_ */
diff --git a/drivers/net/sxe2/sxe2_flow_parse_engine.c b/drivers/net/sxe2/sxe2_flow_parse_engine.c
new file mode 100644
index 0000000000..09de1b94c4
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_parse_engine.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include "sxe2_flow_parse_engine.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_flow_public.h"
+#include "sxe2_flow_parse_action.h"
+#include "sxe2_common_log.h"
+
+static int32_t sxe2_flow_parse_engine_chk(struct sxe2_flow *flow,
+		struct rte_flow_error *error)
+{
+	int32_t ret = 0;
+
+	if (flow->engine_type == SXE2_FLOW_ENGINE_FNAV) {
+		if (flow->has_mask) {
+			ret = -EINVAL;
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM_MASK, NULL,
+				"FNAV flow doesn't support mask");
+			PMD_LOG_ERR(DRV, "FNAV flow doesn't support mask");
+			goto l_end;
+		}
+		if (sxe2_test_bit(SXE2_FLOW_FLD_ID_S_VID,
+				flow->pattern_outer.map_spec) &&
+			sxe2_test_bit(SXE2_FLOW_FLD_ID_C_VID,
+				flow->pattern_outer.map_spec)) {
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+				"Can't set double vid,please use tci.");
+			PMD_LOG_ERR(DRV,
+				"Can't set double vid,please use tci.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_flow_parse_engine(struct rte_eth_dev *dev,
+		const struct rte_flow_attr *attr,
+		const struct rte_flow_action actions[],
+		struct rte_flow_error *error,
+		struct sxe2_flow *flow)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	const struct rte_flow_action *action;
+
+	if (flow->has_mask == 0 && flow->has_spec == 0) {
+		flow->engine_type = SXE2_FLOW_ENGINE_RSS;
+		goto l_end;
+	}
+
+	if (attr->group == 1) {
+		flow->engine_type = SXE2_FLOW_ENGINE_SWITCH;
+		goto l_end;
+	}
+	if (attr->group == 2) {
+		flow->engine_type = SXE2_FLOW_ENGINE_ACL;
+		goto l_end;
+	}
+	if (attr->group == 3) {
+		flow->engine_type = SXE2_FLOW_ENGINE_FNAV;
+		goto l_end;
+	}
+
+	if (adapter->is_dev_repr) {
+		flow->engine_type = SXE2_FLOW_ENGINE_SWITCH;
+		goto l_end;
+	}
+
+	if (adapter->switchdev_info.is_switchdev &&
+	    adapter->dev_type == SXE2_DEV_T_VF) {
+		flow->engine_type = SXE2_FLOW_ENGINE_FNAV;
+		goto l_end;
+	}
+
+	for (action = actions; action->type != RTE_FLOW_ACTION_TYPE_END; action++) {
+		switch (action->type) {
+		case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
+		case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
+		case RTE_FLOW_ACTION_TYPE_PORT_ID:
+			flow->engine_type = SXE2_FLOW_ENGINE_SWITCH;
+			goto l_end;
+		default:
+			break;
+		}
+	}
+
+	if (adapter->switchdev_info.is_switchdev) {
+		flow->engine_type = SXE2_FLOW_ENGINE_FNAV;
+		goto l_end;
+	}
+
+	if (adapter->flow_isolated)
+		flow->engine_type = SXE2_FLOW_ENGINE_SWITCH;
+	else
+		flow->engine_type = SXE2_FLOW_ENGINE_FNAV;
+
+l_end:
+	ret = sxe2_flow_parse_engine_chk(flow, error);
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_flow_parse_engine.h b/drivers/net/sxe2/sxe2_flow_parse_engine.h
new file mode 100644
index 0000000000..1485beecb4
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_parse_engine.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef SXE2_FLOW_PARSE_ENGINE_H_
+#define SXE2_FLOW_PARSE_ENGINE_H_
+#include "sxe2_osal.h"
+#include "sxe2_flow_define.h"
+
+int32_t sxe2_flow_parse_engine(struct rte_eth_dev *dev, const struct rte_flow_attr *attr,
+			const struct rte_flow_action actions[], struct rte_flow_error *error,
+			struct sxe2_flow *flow);
+#endif /* SXE2_FLOW_PARSE_ENGINE_H_ */
diff --git a/drivers/net/sxe2/sxe2_flow_parse_pattern.c b/drivers/net/sxe2/sxe2_flow_parse_pattern.c
new file mode 100644
index 0000000000..189abb1a33
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_parse_pattern.c
@@ -0,0 +1,1822 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include "sxe2_flow_parse_pattern.h"
+#include "rte_common.h"
+#include "rte_flow.h"
+#include "sxe2_common_log.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_cmd_chnl.h"
+#include "sxe2_flow_define.h"
+
+const struct sxe2_flow_expand_node sxe2_support_expansion[SXE2_EXPANSION_MAX] = {
+	[SXE2_EXPANSION_OUTER_ETH] = {
+		.type = RTE_FLOW_ITEM_TYPE_ETH,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "eth",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_VLAN,
+					      SXE2_EXPANSION_OUTER_IPV4,
+					      SXE2_EXPANSION_OUTER_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_OUTER_VLAN] = {
+		.type = RTE_FLOW_ITEM_TYPE_VLAN,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "vlan",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_QINQ,
+					      SXE2_EXPANSION_OUTER_IPV4,
+					      SXE2_EXPANSION_OUTER_IPV6,
+					      SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_QINQ] = {
+		.type = RTE_FLOW_ITEM_TYPE_VLAN,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "vlan",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_IPV4,
+					      SXE2_EXPANSION_OUTER_IPV6,
+					      SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_IPV4] = {
+		.type = RTE_FLOW_ITEM_TYPE_IPV4,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_IPIP,
+		.is_tunnel = false,
+		.name = "ipv4",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_UDP,
+					      SXE2_EXPANSION_OUTER_TCP,
+					      SXE2_EXPANSION_OUTER_SCTP,
+					      SXE2_EXPANSION_GRE,
+					      SXE2_EXPANSION_NVGRE,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_IPV6] = {
+		.type = RTE_FLOW_ITEM_TYPE_IPV6,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_IPIP,
+		.is_tunnel = false,
+		.name = "ipv6",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_IPV6_FRAG_EXT,
+					      SXE2_EXPANSION_OUTER_UDP,
+					      SXE2_EXPANSION_OUTER_TCP,
+					      SXE2_EXPANSION_OUTER_SCTP,
+					      SXE2_EXPANSION_GRE,
+					      SXE2_EXPANSION_NVGRE,
+					      SXE2_EXPANSION_ETH,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_IPV6_FRAG_EXT] = {
+		.type = RTE_FLOW_ITEM_TYPE_IPV6_FRAG_EXT,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "ipv6_frag_ext",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_UDP] = {
+		.type = RTE_FLOW_ITEM_TYPE_UDP,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "udp",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_VXLAN,
+					      SXE2_EXPANSION_VXLAN_GPE,
+					      SXE2_EXPANSION_GENEVE,
+					      SXE2_EXPANSION_GTPU,
+					      SXE2_EXPANSION_GRE,
+					      SXE2_EXPANSION_NVGRE,
+					      SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_TCP] = {
+		.type = RTE_FLOW_ITEM_TYPE_TCP,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "tcp",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_SCTP] = {
+		.type = RTE_FLOW_ITEM_TYPE_SCTP,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "sctp",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_OUTER_END),
+	},
+	[SXE2_EXPANSION_OUTER_END] = {
+		.type = RTE_FLOW_ITEM_TYPE_END,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE,
+		.is_tunnel = false,
+		.name = "end",
+		.next = SXE2_FLOW_EXPAND_NEXT(0),
+	},
+	[SXE2_EXPANSION_VXLAN] = {
+		.type = RTE_FLOW_ITEM_TYPE_VXLAN,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_VXLAN,
+		.is_tunnel = true,
+		.name = "vxlan",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_ETH,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_VXLAN_GPE] = {
+		.type = RTE_FLOW_ITEM_TYPE_VXLAN_GPE,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_VXLAN,
+		.is_tunnel = true,
+		.name = "vxlan_gpe",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_ETH,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_GRE] = {
+		.type = RTE_FLOW_ITEM_TYPE_GRE,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_GRE,
+		.is_tunnel = true,
+		.name = "gre",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_ETH,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_NVGRE] = {
+		.type = RTE_FLOW_ITEM_TYPE_NVGRE,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_GRE,
+		.is_tunnel = true,
+		.name = "nvgre",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_ETH,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_GENEVE] = {
+		.type = RTE_FLOW_ITEM_TYPE_GENEVE,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_GENEVE,
+		.is_tunnel = true,
+		.name = "geneve",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_ETH,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_GTPU] = {
+		.type = RTE_FLOW_ITEM_TYPE_GTPU,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_GTPU,
+		.is_tunnel = true,
+		.name = "gtpu",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_ETH] = {
+		.type = RTE_FLOW_ITEM_TYPE_ETH,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "eth",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_VLAN,
+					      SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_VLAN] = {
+		.type = RTE_FLOW_ITEM_TYPE_VLAN,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "vlan",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_IPV4,
+					      SXE2_EXPANSION_IPV6,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_IPV4] = {
+		.type = RTE_FLOW_ITEM_TYPE_IPV4,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "ipv4",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_UDP,
+					      SXE2_EXPANSION_TCP,
+					      SXE2_EXPANSION_SCTP,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_IPV6] = {
+		.type = RTE_FLOW_ITEM_TYPE_IPV6,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "ipv6",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_UDP,
+					      SXE2_EXPANSION_TCP,
+					      SXE2_EXPANSION_SCTP,
+					      SXE2_EXPANSION_IPV6_FRAG_EXT,
+					      SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_UDP] = {
+		.type = RTE_FLOW_ITEM_TYPE_UDP,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "udp",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_TCP] = {
+		.type = RTE_FLOW_ITEM_TYPE_TCP,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "tcp",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_SCTP] = {
+		.type = RTE_FLOW_ITEM_TYPE_SCTP,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "sctp",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_IPV6_FRAG_EXT] = {
+		.type = RTE_FLOW_ITEM_TYPE_IPV6_FRAG_EXT,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "ipv6_frag_ext",
+		.next = SXE2_FLOW_EXPAND_NEXT(SXE2_EXPANSION_END),
+	},
+	[SXE2_EXPANSION_END] = {
+		.type = RTE_FLOW_ITEM_TYPE_END,
+		.tunnel_type = SXE2_FLOW_TUNNEL_TYPE_PARENT,
+		.is_tunnel = true,
+		.name = "end",
+		.next = SXE2_FLOW_EXPAND_NEXT(0),
+	}
+};
+
+const char *sxe2_flow_type_name[SXE2_FLOW_TYPE_MAX] = {
+	[SXE2_FLOW_MAC_PAY] = "SXE2_FLOW_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_FRAG_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_UDP_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_TCP_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_SCTP_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_FRAG_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_UDP_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_TCP_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_SCTP_PAY] =
+	"SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_FRAG_PAY] =
+	"SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_SCTP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_FRAG_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_FRAG_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_UDP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_UDP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_TCP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_TCP_PAY",
+	[SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_SCTP_PAY] = "SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_SCTP_PAY",
+};
+#define SXE2_FLOW_TYPE_NAME_MAX_LEN 128
+
+static int32_t sxe2_flow_get_flow_type(struct sxe2_flow *flow)
+{
+	int32_t ret = -EINVAL;
+	uint16_t i = 0;
+	uint16_t len = 0;
+	char flow_type_name[SXE2_FLOW_TYPE_NAME_MAX_LEN] = {0};
+	len = snprintf(flow_type_name, sizeof(flow_type_name), "SXE2_FLOW_");
+	i += len;
+	if (sxe2_test_bit(SXE2_FLOW_HDR_ETH, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"MAC_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV4, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"IPV4_");
+		i += len;
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_IPV6, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"IPV6_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV_FRAG, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"FRAG_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_UDP, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"UDP_");
+		i += len;
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_TCP, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"TCP_");
+		i += len;
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_SCTP, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"SCTP_");
+		i += len;
+	}
+
+	if (sxe2_test_bit(SXE2_FLOW_HDR_VXLAN, flow->pattern_outer.hdrs) ||
+	    sxe2_test_bit(SXE2_FLOW_HDR_GENEVE, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"VXGEN_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_GRE, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"GRE_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_GTPU, flow->pattern_outer.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"GTPU_");
+		i += len;
+	}
+
+	if (sxe2_test_bit(SXE2_FLOW_HDR_ETH, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"MAC_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_VLAN, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"VLAN_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV4, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"IPV4_");
+		i += len;
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_IPV6, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"IPV6_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV_FRAG, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"FRAG_");
+		i += len;
+	}
+	if (sxe2_test_bit(SXE2_FLOW_HDR_UDP, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"UDP_");
+		i += len;
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_TCP, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"TCP_");
+		i += len;
+	} else if (sxe2_test_bit(SXE2_FLOW_HDR_SCTP, flow->pattern_inner.hdrs)) {
+		len = snprintf(flow_type_name + i, sizeof(flow_type_name) - i,
+			"SCTP_");
+		i += len;
+	}
+
+	len = snprintf(flow_type_name + i, sizeof(flow_type_name), "PAY");
+	i += len;
+
+	for (i = 0; i < SXE2_FLOW_TYPE_MAX; i++) {
+		if (sxe2_flow_type_name[i] == NULL)
+			continue;
+		if (strcmp(flow_type_name, sxe2_flow_type_name[i]) == 0) {
+			flow->meta.flow_type = i;
+			ret = 0;
+			break;
+		}
+	}
+	if (ret != 0)
+		PMD_LOG_ERR(DRV,
+			"Unsupported flow type. %s is not supported.", flow_type_name);
+	return ret;
+}
+
+static int32_t sxe2_flow_is_expandable_item(const struct rte_flow_item *item)
+{
+	int32_t ret = -EINVAL;
+	switch (item->type) {
+	case RTE_FLOW_ITEM_TYPE_ETH:
+	case RTE_FLOW_ITEM_TYPE_VLAN:
+	case RTE_FLOW_ITEM_TYPE_IPV4:
+	case RTE_FLOW_ITEM_TYPE_IPV6:
+	case RTE_FLOW_ITEM_TYPE_IPV6_FRAG_EXT:
+	case RTE_FLOW_ITEM_TYPE_UDP:
+	case RTE_FLOW_ITEM_TYPE_TCP:
+	case RTE_FLOW_ITEM_TYPE_SCTP:
+	case RTE_FLOW_ITEM_TYPE_VXLAN:
+	case RTE_FLOW_ITEM_TYPE_VXLAN_GPE:
+	case RTE_FLOW_ITEM_TYPE_GRE:
+	case RTE_FLOW_ITEM_TYPE_NVGRE:
+	case RTE_FLOW_ITEM_TYPE_GENEVE:
+	case RTE_FLOW_ITEM_TYPE_GTPU:
+	case RTE_FLOW_ITEM_TYPE_VOID:
+	case RTE_FLOW_ITEM_TYPE_END:
+		ret = 0;
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static int32_t sxe2_flow_valid_next_expansion(enum sxe2_expansion *current,
+	const struct rte_flow_item *item,
+	enum sxe2_flow_tunnel_type *tunnel_type, BITMAP_TYPE *flow_type)
+{
+	int32_t ret = -EINVAL;
+	const struct rte_flow_item *next;
+	const enum sxe2_expansion *next_expansion = current;
+	const struct sxe2_flow_expand_node *node;
+	uint8_t len = 0;
+	char typelist[512] = {0};
+	char error[1024] = {0};
+	enum sxe2_flow_tunnel_type tunnel_type_now = SXE2_FLOW_TUNNEL_TYPE_NONE;
+	enum sxe2_flow_tunnel_type tunnel_type_next = SXE2_FLOW_TUNNEL_TYPE_NONE;
+	uint8_t is_tunnel_now = 0;
+	uint8_t is_tunnel_next = 0;
+
+	if (item->type != sxe2_support_expansion[*current].type) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	next = item;
+	do {
+		next++;
+		if (next->type == RTE_FLOW_ITEM_TYPE_VOID)
+			continue;
+		break;
+	} while (1);
+
+	node = &sxe2_support_expansion[*current];
+	next_expansion = node->next;
+	while (*next_expansion != 0) {
+		len = strlen(typelist);
+		snprintf(typelist + len, sizeof(typelist) - len,
+			"%s|", sxe2_support_expansion[*next_expansion].name);
+		if (sxe2_support_expansion[*next_expansion].type == next->type) {
+			ret = 0;
+			break;
+		}
+		next_expansion++;
+	}
+	if (ret != 0) {
+		snprintf(error, sizeof(error),
+			"The next item of %s only can be one of [%s].",
+			sxe2_support_expansion[*current].name, typelist);
+		PMD_LOG_ERR(INIT, "Invalid pattern sequence. %s", error);
+		goto l_end;
+	}
+	tunnel_type_now = sxe2_support_expansion[*current].tunnel_type;
+	tunnel_type_next = sxe2_support_expansion[*next_expansion].tunnel_type;
+	is_tunnel_now = sxe2_support_expansion[*current].is_tunnel;
+	is_tunnel_next = sxe2_support_expansion[*next_expansion].is_tunnel;
+
+
+	if (!is_tunnel_now && is_tunnel_next) {
+		if (tunnel_type_next == SXE2_FLOW_TUNNEL_TYPE_PARENT)
+			*tunnel_type = tunnel_type_now;
+		else
+			*tunnel_type = tunnel_type_next;
+	}
+
+l_end:
+	sxe2_set_bit(*current, flow_type);
+	*current = *next_expansion;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_eth(const struct rte_flow_item *item,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow,
+					enum sxe2_expansion next,
+					bool is_inner)
+{
+	const struct rte_flow_item_eth *eth_spec;
+	const struct rte_flow_item_eth *eth_mask;
+	const struct rte_ether_addr *dst_addr_mask;
+	const struct rte_ether_addr *src_addr_mask;
+	const struct rte_ether_addr *dst_addr_spec;
+	const struct rte_ether_addr *src_addr_spec;
+	rte_be16_t type_mask;
+	rte_be16_t type_spec;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	uint16_t ether_type;
+	eth_spec = item->spec;
+	eth_mask = item->mask;
+
+	if (eth_spec == NULL && eth_mask == NULL)
+		goto l_end;
+
+	dst_addr_mask = &eth_mask->hdr.dst_addr;
+	src_addr_mask = &eth_mask->hdr.src_addr;
+	dst_addr_spec = &eth_spec->hdr.dst_addr;
+	src_addr_spec = &eth_spec->hdr.src_addr;
+	type_mask = eth_mask->hdr.ether_type;
+	type_spec = eth_spec->hdr.ether_type;
+
+	if (eth_mask->has_vlan) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported eth mask has_vlan.");
+		PMD_LOG_ERR(DRV, "Unsupported eth mask has_vlan");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (!rte_is_zero_ether_addr(dst_addr_mask)) {
+		if (!rte_is_broadcast_ether_addr(dst_addr_mask))
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_DA, pattern->map_mask);
+
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_DA, pattern->map_spec);
+		rte_memcpy(pattern->item_spec.eth.dst_addr, dst_addr_spec,
+			RTE_ETHER_ADDR_LEN);
+		rte_memcpy(pattern->item_mask.eth.dst_addr, dst_addr_mask,
+			RTE_ETHER_ADDR_LEN);
+	}
+	if (!rte_is_zero_ether_addr(src_addr_mask)) {
+		if (!rte_is_broadcast_ether_addr(src_addr_mask))
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_SA, pattern->map_mask);
+
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_SA, pattern->map_spec);
+		rte_memcpy(pattern->item_spec.eth.src_addr, src_addr_spec,
+			RTE_ETHER_ADDR_LEN);
+		rte_memcpy(pattern->item_mask.eth.src_addr, src_addr_mask,
+			RTE_ETHER_ADDR_LEN);
+	}
+	if (type_mask != 0) {
+		if (type_mask != UINT16_MAX) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Unsupported eth ether_type mask");
+			PMD_LOG_ERR(DRV, "unsupported eth ether_type mask[0x%x].",
+				    type_mask);
+			ret = -EINVAL;
+			goto l_end;
+		}
+		if (next != SXE2_EXPANSION_OUTER_END && next != SXE2_EXPANSION_END) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Unsupported eth ether_type match with next item.");
+			PMD_LOG_ERR(DRV, "unsupported eth ether_type match with next item.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		ether_type = rte_be_to_cpu_16(type_spec);
+		if (ether_type == RTE_ETHER_TYPE_IPV4 ||
+				ether_type == RTE_ETHER_TYPE_IPV6) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Ether_type unsupported ipv4/ipv6(0x0800/0x86DD).");
+			PMD_LOG_ERR(DRV, "Ether_type unsupported ipv4/ipv6(0x0800/0x86DD).");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		if (ether_type == RTE_ETHER_TYPE_VLAN || ether_type == RTE_ETHER_TYPE_QINQ ||
+				ether_type == RTE_ETHER_TYPE_QINQ1) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Ether_type unsupported vlan(0x8100/0x88a8/0x9100).");
+			PMD_LOG_ERR(DRV, "Ether_type unsupported vlan(0x8100/0x88a8/0x9100).");
+			ret = -EINVAL;
+			goto l_end;
+		}
+
+		if (ether_type <= SXE2_FLOW_ETH_TYPE_MIN) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Ether_type need max 1500.");
+			PMD_LOG_ERR(DRV, "Ether_type need max 1500.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_ETH_TYPE, pattern->map_spec);
+		pattern->item_spec.eth.ether_type = type_spec;
+		pattern->item_mask.eth.ether_type = type_mask;
+	}
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_ETH, pattern->hdrs);
+	pattern->rss_type_allow |= RTE_ETH_RSS_ETH;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L2_DST_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L2_SRC_ONLY;
+	if (next == SXE2_EXPANSION_OUTER_END || next == SXE2_EXPANSION_END)
+		pattern->rss_type_allow |= RTE_ETH_RSS_L2_PAYLOAD;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_vlan(const struct rte_flow_item *item,
+				struct rte_flow_error *error,
+				struct sxe2_flow *flow,
+				enum sxe2_expansion next __rte_unused,
+				bool is_inner)
+{
+	const struct rte_flow_item_vlan *vlan_spec;
+	const struct rte_flow_item_vlan *vlan_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	rte_be16_t vlan_tci_mask;
+	rte_be16_t vlan_tci_spec;
+	rte_be16_t eth_proto_mask;
+	rte_be16_t eth_proto_spec;
+	int32_t ret = 0;
+	vlan_spec = item->spec;
+	vlan_mask = item->mask;
+	bool is_qinq = false;
+
+	if (sxe2_test_bit(SXE2_FLOW_HDR_VLAN, pattern->hdrs))
+		is_qinq = true;
+
+	if (vlan_spec == NULL && vlan_mask == NULL)
+		goto l_end;
+
+	vlan_tci_mask = vlan_mask->hdr.vlan_tci;
+	vlan_tci_spec = vlan_spec->hdr.vlan_tci;
+	eth_proto_mask = vlan_mask->hdr.eth_proto;
+	eth_proto_spec = vlan_spec->hdr.eth_proto;
+
+	if (vlan_mask->has_more_vlan || vlan_mask->reserved) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported vlan mask has_qinq.");
+		PMD_LOG_ERR(DRV, "Unsupported vlan mask has_qinq");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (vlan_tci_mask) {
+		if (vlan_tci_spec == 0) {
+			rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "vlan id can't be 0.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+
+	if (eth_proto_mask) {
+		if (eth_proto_mask != UINT16_MAX) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Unsupported vlan ether_type mask");
+			PMD_LOG_ERR(DRV, "unsupported vlan ether_type mask.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		if (eth_proto_spec != RTE_BE16(0x8100) &&
+			eth_proto_spec != RTE_BE16(0x88a8) &&
+			eth_proto_spec != RTE_BE16(0x9100)) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "Unsupported vlan ether_type, only support 0x8100, 0x88a8 and 0x9100.");
+			PMD_LOG_ERR(DRV, "Unsupported vlan ether_type, only support 0x8100, 0x88a8 and 0x9100.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+	if (!is_qinq) {
+		if (vlan_tci_mask) {
+			if (vlan_tci_mask == RTE_BE16(0x0fff)) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_S_VID, pattern->map_spec);
+			} else if (vlan_tci_mask == UINT16_MAX) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_S_TCI, pattern->map_spec);
+			} else {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_S_TCI, pattern->map_mask);
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_S_TCI, pattern->map_spec);
+			}
+		}
+		if (eth_proto_mask)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_S_TPID, pattern->map_spec);
+	} else {
+		if (vlan_tci_mask) {
+			if (vlan_tci_mask == RTE_BE16(0x0fff)) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_C_VID, pattern->map_spec);
+			} else if (vlan_tci_mask == UINT16_MAX) {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_C_TCI, pattern->map_spec);
+			} else {
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_C_TCI, pattern->map_mask);
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_C_TCI, pattern->map_spec);
+			}
+		}
+		if (eth_proto_mask)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_C_TPID, pattern->map_spec);
+	}
+	if (is_qinq) {
+		pattern->item_spec.qinq.type =  eth_proto_spec;
+		pattern->item_mask.qinq.type =  eth_proto_mask;
+		pattern->item_spec.qinq.vlan = vlan_tci_spec;
+		pattern->item_mask.qinq.vlan = vlan_tci_mask;
+	} else {
+		pattern->item_spec.vlan.type = eth_proto_spec;
+		pattern->item_mask.vlan.type = eth_proto_mask;
+		pattern->item_spec.vlan.vlan = vlan_tci_spec;
+		pattern->item_mask.vlan.vlan = vlan_tci_mask;
+	}
+l_end:
+	pattern->rss_type_allow |= RTE_ETH_RSS_S_VLAN;
+	pattern->rss_type_allow |= RTE_ETH_RSS_C_VLAN;
+	if (!is_qinq)
+		sxe2_set_bit(SXE2_FLOW_HDR_VLAN, pattern->hdrs);
+	else
+		sxe2_set_bit(SXE2_FLOW_HDR_QINQ, pattern->hdrs);
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_ipv4(const struct rte_flow_item *item,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow,
+					enum sxe2_expansion next,
+					bool is_inner)
+{
+	const struct rte_flow_item_ipv4 *ipv4_spec;
+	const struct rte_flow_item_ipv4 *ipv4_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	ipv4_spec = item->spec;
+	ipv4_mask = item->mask;
+
+	if (ipv4_mask == NULL && ipv4_spec == NULL)
+		goto l_end;
+
+	if (ipv4_mask->hdr.version_ihl || ipv4_mask->hdr.total_length ||
+			ipv4_mask->hdr.hdr_checksum || ipv4_mask->hdr.packet_id) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some IPv4 mask.");
+		PMD_LOG_ERR(DRV, "Unsupported some IPv4 mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (ipv4_mask->hdr.src_addr) {
+		if (ipv4_mask->hdr.src_addr != UINT32_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_SA, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_SA, pattern->map_spec);
+		pattern->item_spec.ipv4.saddr = ipv4_spec->hdr.src_addr;
+		pattern->item_mask.ipv4.saddr = ipv4_mask->hdr.src_addr;
+	}
+	if (ipv4_mask->hdr.dst_addr) {
+		if (ipv4_mask->hdr.dst_addr != UINT32_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_DA, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_DA, pattern->map_spec);
+		pattern->item_spec.ipv4.daddr = ipv4_spec->hdr.dst_addr;
+		pattern->item_mask.ipv4.daddr = ipv4_mask->hdr.dst_addr;
+	}
+
+	if (ipv4_mask->hdr.next_proto_id) {
+		if (next != SXE2_EXPANSION_OUTER_END && next != SXE2_EXPANSION_END) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "IPv4 proto id must be the last partten.");
+			PMD_LOG_ERR(DRV, "IPv4 proto id must be the last partten.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		if (ipv4_mask->hdr.next_proto_id != UINT8_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_PROT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_PROT, pattern->map_spec);
+		pattern->item_spec.ipv4.protocol = ipv4_spec->hdr.next_proto_id;
+		pattern->item_mask.ipv4.protocol = ipv4_mask->hdr.next_proto_id;
+	}
+	if (ipv4_mask->hdr.time_to_live) {
+		if (ipv4_mask->hdr.time_to_live != UINT8_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_TTL, pattern->map_mask);
+		if (ipv4_spec->hdr.time_to_live == 0) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "ipv4 ttl must be not 0.");
+			PMD_LOG_ERR(DRV, "ipv4 ttl must be not 0.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_TTL, pattern->map_spec);
+		pattern->item_spec.ipv4.ttl = ipv4_spec->hdr.time_to_live;
+		pattern->item_mask.ipv4.ttl = ipv4_mask->hdr.time_to_live;
+	}
+	if (ipv4_mask->hdr.type_of_service) {
+		if (ipv4_mask->hdr.type_of_service != UINT8_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_TOS, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_TOS, pattern->map_spec);
+		pattern->item_spec.ipv4.tos = ipv4_spec->hdr.type_of_service;
+		pattern->item_mask.ipv4.tos = ipv4_mask->hdr.type_of_service;
+	}
+	if (ipv4_mask->hdr.fragment_offset) {
+		if (ipv4_spec->hdr.fragment_offset == rte_cpu_to_be_16(RTE_IPV4_HDR_MF_FLAG) &&
+			ipv4_mask->hdr.fragment_offset == rte_cpu_to_be_16(RTE_IPV4_HDR_MF_FLAG)) {
+			if (next != SXE2_EXPANSION_OUTER_END && next != SXE2_EXPANSION_END) {
+				rte_flow_error_set(error, EINVAL,
+						RTE_FLOW_ERROR_TYPE_ITEM,
+						item, "IPv4 frag offset must be the last partten.");
+				PMD_LOG_ERR(DRV, "IPv4 frag offset must be the last partten.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			sxe2_set_bit(SXE2_FLOW_HDR_IPV_FRAG, pattern->hdrs);
+		} else {
+			rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported ipv4 fragment_offset cfg.");
+			PMD_LOG_ERR(DRV, "Unsupported ipv4 fragment_offset cfg.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_IPV4, pattern->hdrs);
+	pattern->rss_type_allow |= RTE_ETH_RSS_IPV4;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_SRC_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_DST_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_IPV4_CHKSUM;
+	if (next == SXE2_EXPANSION_OUTER_END || next == SXE2_EXPANSION_END) {
+		pattern->rss_type_allow |= RTE_ETH_RSS_FRAG_IPV4;
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV4_OTHER;
+	}
+
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_ipv6(const struct rte_flow_item *item,
+				struct rte_flow_error *error,
+				struct sxe2_flow *flow,
+				enum sxe2_expansion next __rte_unused,
+				bool is_inner)
+{
+	const struct rte_flow_item_ipv6 *ipv6_spec;
+	const struct rte_flow_item_ipv6 *ipv6_mask;
+	uint32_t vtc_flow_mask;
+	uint32_t tc_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	ipv6_spec = item->spec;
+	ipv6_mask = item->mask;
+	uint8_t ipv6_addr_mask[16] = {
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+	};
+	uint8_t ipv6_addr_empty[16] = { 0 };
+
+	if (ipv6_mask == NULL && ipv6_spec == NULL)
+		goto l_end;
+
+	if (ipv6_mask->hdr.payload_len || ipv6_mask->has_hop_ext ||
+		ipv6_mask->has_route_ext || ipv6_mask->has_frag_ext ||
+		ipv6_mask->has_auth_ext || ipv6_mask->has_esp_ext ||
+		ipv6_mask->has_dest_ext || ipv6_mask->has_mobil_ext ||
+		ipv6_mask->has_hip_ext || ipv6_mask->has_shim6_ext) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some IPv6 mask");
+		PMD_LOG_ERR(DRV, "Unsupported some IPv6 mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (memcmp(&ipv6_mask->hdr.src_addr, ipv6_addr_empty,
+		   sizeof(ipv6_addr_empty)) != 0) {
+		if (memcmp(&ipv6_mask->hdr.src_addr, ipv6_addr_mask,
+			   sizeof(ipv6_addr_mask)) != 0)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_SA, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_SA, pattern->map_spec);
+		rte_memcpy(&pattern->item_spec.ipv6.saddr, &ipv6_spec->hdr.src_addr,
+			   sizeof(ipv6_spec->hdr.src_addr));
+		rte_memcpy(&pattern->item_mask.ipv6.saddr, &ipv6_mask->hdr.src_addr,
+			   sizeof(ipv6_mask->hdr.src_addr));
+	}
+	if (memcmp(&ipv6_mask->hdr.dst_addr, ipv6_addr_empty,
+		    sizeof(ipv6_addr_empty)) != 0) {
+		if (memcmp(&ipv6_mask->hdr.dst_addr, ipv6_addr_mask,
+			   sizeof(ipv6_addr_mask)) != 0)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_DA, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_DA, pattern->map_spec);
+		rte_memcpy(&pattern->item_spec.ipv6.daddr, &ipv6_spec->hdr.dst_addr,
+			   sizeof(ipv6_spec->hdr.dst_addr));
+		rte_memcpy(&pattern->item_mask.ipv6.daddr, &ipv6_mask->hdr.dst_addr,
+			   sizeof(ipv6_mask->hdr.dst_addr));
+	}
+	if (ipv6_mask->hdr.vtc_flow) {
+		vtc_flow_mask = rte_be_to_cpu_32(ipv6_mask->hdr.vtc_flow);
+		tc_mask = vtc_flow_mask & (SXE2_IPV6_TC_MASK << SXE2_IPV6_TC_SHIFT);
+		if (tc_mask != vtc_flow_mask) {
+			rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "ipv6 vtc_flow only support TC mask.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		if (tc_mask != (SXE2_IPV6_TC_MASK << SXE2_IPV6_TC_SHIFT))
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_DSCP, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_DSCP, pattern->map_spec);
+		pattern->item_spec.ipv6.pri_ver_flow = ipv6_spec->hdr.vtc_flow;
+		pattern->item_mask.ipv6.pri_ver_flow = ipv6_mask->hdr.vtc_flow;
+	}
+	if (ipv6_mask->hdr.proto) {
+		if (next != SXE2_EXPANSION_OUTER_END && next != SXE2_EXPANSION_END) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "IPv6 proto id must be the last partten.");
+			PMD_LOG_ERR(DRV, "IPv6 proto id must be the last partten.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		if (ipv6_mask->hdr.proto != UINT8_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PROT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PROT, pattern->map_spec);
+		pattern->item_spec.ipv6.nexthdr = ipv6_spec->hdr.proto;
+		pattern->item_mask.ipv6.nexthdr = ipv6_mask->hdr.proto;
+	}
+	if (ipv6_mask->hdr.hop_limits) {
+		if (ipv6_mask->hdr.hop_limits != UINT8_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_TTL, pattern->map_mask);
+
+		if (ipv6_spec->hdr.hop_limits == 0) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					item, "ipv6 hop must be not 0.");
+			PMD_LOG_ERR(DRV, "ipv6 hop must be not 0.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_TTL, pattern->map_spec);
+		pattern->item_spec.ipv6.hop_limit = ipv6_spec->hdr.hop_limits;
+		pattern->item_mask.ipv6.hop_limit = ipv6_mask->hdr.hop_limits;
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_IPV6, pattern->hdrs);
+	pattern->rss_type_allow |= RTE_ETH_RSS_IPV6;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_SRC_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_DST_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_PRE32;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_PRE48;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L3_PRE64;
+	if (next == SXE2_EXPANSION_OUTER_END || next == SXE2_EXPANSION_END)
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV6_OTHER;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_ipv6_frag_ext(const struct rte_flow_item *item,
+					 struct rte_flow_error *error,
+					 struct sxe2_flow *flow,
+					 enum sxe2_expansion next __rte_unused,
+					 bool is_inner)
+{
+	const struct rte_flow_item_ipv6_frag_ext *ipv6_frag_spec;
+	const struct rte_flow_item_ipv6_frag_ext *ipv6_frag_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	ipv6_frag_spec = item->spec;
+	ipv6_frag_mask = item->mask;
+
+	if (ipv6_frag_mask == NULL && ipv6_frag_spec == NULL)
+		goto l_end;
+
+	if (ipv6_frag_mask->hdr.reserved || ipv6_frag_mask->hdr.frag_data ||
+	    ipv6_frag_mask->hdr.id || ipv6_frag_mask->hdr.next_header) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some IPv6 frag ext mask");
+		PMD_LOG_ERR(DRV, "Unsupported some IPv6 frag ext mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_IPV_FRAG, pattern->hdrs);
+	pattern->rss_type_allow |= RTE_ETH_RSS_FRAG_IPV6;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_tcp(const struct rte_flow_item *item,
+				struct rte_flow_error *error,
+				struct sxe2_flow *flow,
+				enum sxe2_expansion next __rte_unused,
+				bool is_inner)
+{
+	const struct rte_flow_item_tcp *tcp_spec;
+	const struct rte_flow_item_tcp *tcp_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	tcp_spec = item->spec;
+	tcp_mask = item->mask;
+
+	if (tcp_mask == NULL && tcp_spec == NULL)
+		goto l_end;
+
+	if (tcp_mask->hdr.sent_seq || tcp_mask->hdr.recv_ack ||
+	    tcp_mask->hdr.data_off || tcp_mask->hdr.tcp_flags ||
+	    tcp_mask->hdr.rx_win || tcp_mask->hdr.cksum ||
+	    tcp_mask->hdr.tcp_urp) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some TCP mask");
+		PMD_LOG_ERR(DRV, "Unsupported some TCP mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (tcp_mask->hdr.src_port) {
+		if (tcp_mask->hdr.src_port != UINT16_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_SRC_PORT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_SRC_PORT, pattern->map_spec);
+		pattern->item_spec.tcp.source = tcp_spec->hdr.src_port;
+		pattern->item_mask.tcp.source = tcp_mask->hdr.src_port;
+	}
+	if (tcp_mask->hdr.dst_port) {
+		if (tcp_mask->hdr.dst_port != UINT16_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_DST_PORT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_TCP_DST_PORT, pattern->map_spec);
+		pattern->item_spec.tcp.dest = tcp_spec->hdr.dst_port;
+		pattern->item_mask.tcp.dest = tcp_mask->hdr.dst_port;
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_TCP, pattern->hdrs);
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV4, pattern->hdrs))
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV4_TCP;
+	else if (sxe2_test_bit(SXE2_FLOW_HDR_IPV6, pattern->hdrs))
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV6_TCP;
+
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_SRC_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_DST_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_CHKSUM;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_udp(const struct rte_flow_item *item,
+				struct rte_flow_error *error,
+				struct sxe2_flow *flow,
+				enum sxe2_expansion next __rte_unused,
+				bool is_inner)
+{
+	const struct rte_flow_item_udp *udp_spec;
+	const struct rte_flow_item_udp *udp_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	udp_spec = item->spec;
+	udp_mask = item->mask;
+
+	if (udp_mask == NULL && udp_spec == NULL)
+		goto l_end;
+
+	if (udp_mask->hdr.dgram_len || udp_mask->hdr.dgram_cksum) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some UDP mask");
+		PMD_LOG_ERR(DRV, "Unsupported some UDP mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (udp_mask->hdr.src_port) {
+		if (udp_mask->hdr.src_port != UINT16_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_SRC_PORT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_SRC_PORT, pattern->map_spec);
+		pattern->item_spec.udp.source = udp_spec->hdr.src_port;
+		pattern->item_mask.udp.source = udp_mask->hdr.src_port;
+	}
+	if (udp_mask->hdr.dst_port) {
+		if (udp_mask->hdr.dst_port != UINT16_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_DST_PORT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_DST_PORT, pattern->map_spec);
+		pattern->item_spec.udp.dest = udp_spec->hdr.dst_port;
+		pattern->item_mask.udp.dest = udp_mask->hdr.dst_port;
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_UDP, pattern->hdrs);
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV4, pattern->hdrs))
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV4_UDP;
+	else if (sxe2_test_bit(SXE2_FLOW_HDR_IPV6, pattern->hdrs))
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV6_UDP;
+
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_SRC_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_DST_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_CHKSUM;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_sctp(const struct rte_flow_item *item,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow,
+					enum sxe2_expansion next __rte_unused,
+					bool is_inner)
+{
+	const struct rte_flow_item_sctp *sctp_spec;
+	const struct rte_flow_item_sctp *sctp_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	sctp_spec = item->spec;
+	sctp_mask = item->mask;
+
+	if (sctp_mask == NULL && sctp_spec == NULL)
+		goto l_end;
+
+	if (sctp_mask->hdr.cksum || sctp_mask->hdr.tag) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some SCTP mask");
+		PMD_LOG_ERR(DRV, "Unsupported some SCTP mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (sctp_mask->hdr.src_port) {
+		if (sctp_mask->hdr.src_port != UINT16_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_SRC_PORT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_SRC_PORT, pattern->map_spec);
+		pattern->item_spec.sctp.src_port = sctp_spec->hdr.src_port;
+		pattern->item_mask.sctp.src_port = sctp_mask->hdr.src_port;
+	}
+	if (sctp_mask->hdr.dst_port) {
+		if (sctp_mask->hdr.dst_port != UINT16_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_DST_PORT, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_SCTP_DST_PORT, pattern->map_spec);
+		pattern->item_spec.sctp.dst_port = sctp_spec->hdr.dst_port;
+		pattern->item_mask.sctp.dst_port = sctp_mask->hdr.dst_port;
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_SCTP, pattern->hdrs);
+	if (sxe2_test_bit(SXE2_FLOW_HDR_IPV4, pattern->hdrs))
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV4_SCTP;
+	else if (sxe2_test_bit(SXE2_FLOW_HDR_IPV6, pattern->hdrs))
+		pattern->rss_type_allow |= RTE_ETH_RSS_NONFRAG_IPV6_SCTP;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_SRC_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_DST_ONLY;
+	pattern->rss_type_allow |= RTE_ETH_RSS_L4_CHKSUM;
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_geneve(const struct rte_flow_item *item,
+							struct rte_flow_error *error,
+							struct sxe2_flow *flow,
+							enum sxe2_expansion next __rte_unused,
+							bool is_inner)
+{
+	const struct rte_flow_item_geneve *geneve_spec;
+	const struct rte_flow_item_geneve *geneve_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	geneve_spec = item->spec;
+	geneve_mask = item->mask;
+
+	if (!(geneve_spec && geneve_mask))
+		goto l_end;
+
+	if (geneve_mask->protocol || geneve_mask->ver_opt_len_o_c_rsvd0 || geneve_mask->rsvd1) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some Geneve mask");
+		PMD_LOG_ERR(DRV, "Unsupported some Geneve mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (geneve_mask->vni[0] || geneve_mask->vni[1] || geneve_mask->vni[2]) {
+		if (strcmp((const char *)geneve_mask->vni, "\xFF\xFF\xFF") != 0)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_GENEVE_VNI, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_GENEVE_VNI, pattern->map_spec);
+		pattern->item_spec.geneve.vni = (geneve_spec->vni[2] << 16) |
+			(geneve_spec->vni[1] << 8) | geneve_spec->vni[0];
+		pattern->item_mask.geneve.vni = (geneve_mask->vni[2] << 16) |
+			(geneve_mask->vni[1] << 8) | geneve_mask->vni[0];
+	}
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_GENEVE, pattern->hdrs);
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_gtpu(const struct rte_flow_item *item,
+						struct rte_flow_error *error,
+						struct sxe2_flow *flow,
+						enum sxe2_expansion next __rte_unused,
+						bool is_inner)
+{
+	const struct rte_flow_item_gtp *gtpu_spec;
+	const struct rte_flow_item_gtp *gtpu_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	gtpu_spec = item->spec;
+	gtpu_mask = item->mask;
+
+	if (gtpu_mask == NULL && gtpu_spec == NULL)
+		goto l_end;
+
+	if (gtpu_mask->v_pt_rsv_flags || gtpu_mask->msg_type || gtpu_mask->msg_len) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some GTPU mask");
+		PMD_LOG_ERR(DRV, "Unsupported some GTPU mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (gtpu_mask->teid) {
+		if (gtpu_mask->teid != UINT32_MAX)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_GTPU_TEID, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_GTPU_TEID, pattern->map_spec);
+		pattern->item_spec.gtpu.teid = gtpu_spec->teid;
+		pattern->item_mask.gtpu.teid = gtpu_mask->teid;
+	}
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_GTPU, pattern->hdrs);
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_gre(const struct rte_flow_item *item,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow,
+					enum sxe2_expansion next __rte_unused,
+					bool is_inner)
+{
+	const struct rte_flow_item_gre *gre_spec;
+	const struct rte_flow_item_gre *gre_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	gre_spec = item->spec;
+	gre_mask = item->mask;
+
+	if (gre_mask == NULL && gre_spec == NULL)
+		goto l_end;
+
+	if (gre_mask->c_rsvd0_ver || gre_mask->protocol) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some GRE mask");
+		PMD_LOG_ERR(DRV, "Unsupported some GRE mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_GRE, pattern->hdrs);
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_nvgre(const struct rte_flow_item *item,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow,
+					enum sxe2_expansion next __rte_unused,
+					bool is_inner)
+{
+	const struct rte_flow_item_nvgre *nvgre_spec;
+	const struct rte_flow_item_nvgre *nvgre_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	nvgre_spec = item->spec;
+	nvgre_mask = item->mask;
+
+	if (nvgre_mask == NULL && nvgre_spec == NULL)
+		goto l_end;
+
+	if (nvgre_mask->c_k_s_rsvd0_ver || nvgre_mask->protocol || nvgre_mask->flow_id) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some NVGRE mask");
+		PMD_LOG_ERR(DRV, "Unsupported some NVGRE mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (nvgre_mask->tni[0] || nvgre_mask->tni[1] || nvgre_mask->tni[2]) {
+		if (strcmp((const char *)nvgre_mask->tni, "\xFF\xFF\xFF") != 0)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_NVGRE_TNI, pattern->map_mask);
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_NVGRE_TNI, pattern->map_spec);
+		pattern->item_spec.nvgre.tni =
+			(nvgre_spec->tni[2] << 16) |
+			(nvgre_spec->tni[1] << 8) |
+			(nvgre_spec->tni[0]);
+		pattern->item_mask.nvgre.tni =
+			(nvgre_mask->tni[2] << 16) |
+			(nvgre_mask->tni[1] << 8) |
+			(nvgre_mask->tni[0]);
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_GRE, pattern->hdrs);
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_vxlan(const struct rte_flow_item *item,
+					struct rte_flow_error *error,
+					struct sxe2_flow *flow,
+					enum sxe2_expansion next __rte_unused,
+					bool is_inner)
+{
+	const struct rte_flow_item_vxlan *vxlan_spec;
+	const struct rte_flow_item_vxlan *vxlan_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	vxlan_spec = item->spec;
+	vxlan_mask = item->mask;
+
+	if (vxlan_mask == NULL && vxlan_spec == NULL)
+		goto l_end;
+
+	if (vxlan_mask->flags ||
+	    vxlan_mask->rsvd1 ||
+	    vxlan_mask->rsvd0[0] ||
+	    vxlan_mask->rsvd0[1] ||
+	    vxlan_mask->rsvd0[2]) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some VXLAN mask");
+		PMD_LOG_ERR(DRV, "Unsupported some VXLAN mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (vxlan_mask->vni[0] || vxlan_mask->vni[1] || vxlan_mask->vni[2]) {
+		if (strcmp((const char *)vxlan_mask->vni, "\xFF\xFF\xFF") != 0)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_VXLAN_VNI, pattern->map_mask);
+
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_VXLAN_VNI, pattern->map_spec);
+		pattern->item_spec.vxlan.vni =
+			(vxlan_spec->vni[2] << 16) |
+			(vxlan_spec->vni[1] << 8) |
+			(vxlan_spec->vni[0]);
+		pattern->item_mask.vxlan.vni =
+			(vxlan_mask->vni[2] << 16) |
+			(vxlan_mask->vni[1] << 8) |
+			(vxlan_mask->vni[0]);
+	}
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_VXLAN, pattern->hdrs);
+	return ret;
+}
+
+static int32_t sxe2_flow_parse_pattern_vxlan_gpe(const struct rte_flow_item *item,
+					     struct rte_flow_error *error,
+					     struct sxe2_flow *flow,
+					     enum sxe2_expansion next __rte_unused,
+					     bool is_inner)
+{
+	const struct rte_flow_item_vxlan_gpe *vxlan_gpe_spec;
+	const struct rte_flow_item_vxlan_gpe *vxlan_gpe_mask;
+	struct sxe2_flow_pattern *pattern = is_inner ? &flow->pattern_inner : &flow->pattern_outer;
+	int32_t ret = 0;
+	vxlan_gpe_spec = item->spec;
+	vxlan_gpe_mask = item->mask;
+
+	if (vxlan_gpe_mask == NULL && vxlan_gpe_spec == NULL)
+		goto l_end;
+
+	if (vxlan_gpe_mask->flags ||
+	    vxlan_gpe_mask->protocol ||
+	    vxlan_gpe_mask->rsvd1 ||
+	    vxlan_gpe_mask->rsvd0[0] ||
+	    vxlan_gpe_mask->rsvd0[1]) {
+		rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ITEM,
+				item, "Unsupported some VXLAN-GPE mask");
+		PMD_LOG_ERR(DRV, "Unsupported some VXLAN-GPE mask");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if (vxlan_gpe_mask->vni[0] || vxlan_gpe_mask->vni[1] || vxlan_gpe_mask->vni[2]) {
+		if (strcmp((const char *)vxlan_gpe_mask->vni, "\xFF\xFF\xFF") != 0)
+			sxe2_set_bit(SXE2_FLOW_FLD_ID_VXLAN_VNI, pattern->map_mask);
+
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_VXLAN_VNI, pattern->map_spec);
+		pattern->item_spec.vxlan.vni =
+			(vxlan_gpe_spec->vni[2] << 16) |
+			(vxlan_gpe_spec->vni[1] << 8) |
+			(vxlan_gpe_spec->vni[0]);
+		pattern->item_mask.vxlan.vni =
+			(vxlan_gpe_mask->vni[2] << 16) |
+			(vxlan_gpe_mask->vni[1] << 8) |
+			(vxlan_gpe_mask->vni[0]);
+	}
+
+l_end:
+	sxe2_set_bit(SXE2_FLOW_HDR_VXLAN, pattern->hdrs);
+	return ret;
+}
+
+struct sxe2_flow_parse_pattern_ops sxe2_flow_parse_pattern_list[] = {
+	[SXE2_EXPANSION_OUTER_ETH] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_eth,
+	},
+	[SXE2_EXPANSION_OUTER_VLAN] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_vlan,
+	},
+	[SXE2_EXPANSION_OUTER_QINQ] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_vlan,
+	},
+	[SXE2_EXPANSION_OUTER_IPV4] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_ipv4,
+	},
+	[SXE2_EXPANSION_OUTER_IPV6] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_ipv6,
+	},
+	[SXE2_EXPANSION_OUTER_IPV6_FRAG_EXT] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_ipv6_frag_ext,
+	},
+	[SXE2_EXPANSION_OUTER_TCP] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_tcp,
+	},
+	[SXE2_EXPANSION_OUTER_UDP] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_udp,
+	},
+	[SXE2_EXPANSION_OUTER_SCTP] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_sctp,
+	},
+	[SXE2_EXPANSION_GENEVE] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_geneve,
+	},
+	[SXE2_EXPANSION_GTPU] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_gtpu,
+	},
+	[SXE2_EXPANSION_GRE] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_gre,
+	},
+	[SXE2_EXPANSION_NVGRE] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_nvgre,
+	},
+	[SXE2_EXPANSION_VXLAN] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_vxlan,
+	},
+	[SXE2_EXPANSION_VXLAN_GPE] = {
+		.is_inner = false,
+		.func = sxe2_flow_parse_pattern_vxlan_gpe,
+	},
+	[SXE2_EXPANSION_ETH] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_eth,
+	},
+	[SXE2_EXPANSION_VLAN] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_vlan,
+	},
+	[SXE2_EXPANSION_IPV4] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_ipv4,
+	},
+	[SXE2_EXPANSION_IPV6] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_ipv6,
+	},
+	[SXE2_EXPANSION_IPV6_FRAG_EXT] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_ipv6_frag_ext,
+	},
+	[SXE2_EXPANSION_TCP] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_tcp,
+	},
+	[SXE2_EXPANSION_UDP] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_udp,
+	},
+	[SXE2_EXPANSION_SCTP] = {
+		.is_inner = true,
+		.func = sxe2_flow_parse_pattern_sctp,
+	},
+
+};
+
+int32_t sxe2_flow_parse_pattern(struct rte_eth_dev *dev __rte_unused,
+			    const struct rte_flow_item patterns[],
+			    struct rte_flow_error *error,
+			    struct sxe2_flow *flow)
+{
+	int32_t ret = 0;
+	const struct rte_flow_item *item = patterns;
+	enum sxe2_expansion now = SXE2_EXPANSION_OUTER_ETH;
+	enum sxe2_expansion next = SXE2_EXPANSION_OUTER_ETH;
+	enum sxe2_flow_tunnel_type tunnel_type = SXE2_FLOW_TUNNEL_TYPE_NONE;
+	DECLARE_BITMAP(flow_type, SXE2_EXPANSION_MAX);
+	sxe2_bitmap_zero(flow_type, SXE2_EXPANSION_MAX);
+	sxe2_flow_parse_pattern_func_t func;
+	bool is_inner = false;
+
+	for (item = patterns; item->type != RTE_FLOW_ITEM_TYPE_END; item++) {
+		if (item->type == RTE_FLOW_ITEM_TYPE_VOID)
+			continue;
+
+		if (item->last) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, item,
+					"FANV not support range pattern.");
+			PMD_LOG_ERR(DRV, "flow not supported range pattern.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		ret = sxe2_flow_is_expandable_item(item);
+		if (ret != 0) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, item,
+					"Unsupported item type.");
+			PMD_LOG_ERR(DRV, "Unsupported item type: %d", item->type);
+			goto l_end;
+		}
+		next = now;
+		ret = sxe2_flow_valid_next_expansion(&next, item,
+				&tunnel_type, flow_type);
+		if (ret != 0) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, item,
+					"Invalid pattern sequence for rule.");
+			PMD_LOG_ERR(DRV, "Invalid pattern sequence for rule.");
+			goto l_end;
+		}
+
+		if ((item->spec != NULL && item->mask == NULL) ||
+		    (item->spec == NULL && item->mask != NULL)) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM, item,
+					"Invalid pattern spec miss macth mask for rule.");
+			PMD_LOG_ERR(DRV, "Invalid pattern spec miss macth mask for rule.");
+			goto l_end;
+		}
+
+		func = sxe2_flow_parse_pattern_list[now].func;
+		is_inner = sxe2_flow_parse_pattern_list[now].is_inner;
+		ret = func(item, error, flow, next, is_inner);
+		if (ret != 0)
+			goto l_end;
+		now = next;
+	}
+	if (sxe2_bitmap_weight(flow->pattern_outer.hdrs, SXE2_FLOW_HDR_MAX) > 0)
+		flow->has_hdr = 1;
+
+	if (sxe2_bitmap_weight(flow->pattern_inner.map_mask, SXE2_FLOW_FLD_ID_MAX) > 0 ||
+	   sxe2_bitmap_weight(flow->pattern_outer.map_mask, SXE2_FLOW_FLD_ID_MAX) > 0)
+		flow->has_mask = 1;
+
+	if (sxe2_bitmap_weight(flow->pattern_inner.map_spec, SXE2_FLOW_FLD_ID_MAX) > 0 ||
+	   sxe2_bitmap_weight(flow->pattern_outer.map_spec, SXE2_FLOW_FLD_ID_MAX) > 0)
+		flow->has_spec = 1;
+
+	flow->meta.tunnel_type = tunnel_type;
+	if (flow->has_hdr) {
+		ret = sxe2_flow_get_flow_type(flow);
+		if (ret != 0) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					NULL, "Unsupported flow type.");
+			goto l_end;
+		}
+	}
+	sxe2_bitmap_copy(flow->flow_type, flow_type, SXE2_EXPANSION_MAX);
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_flow_parse_pattern.h b/drivers/net/sxe2/sxe2_flow_parse_pattern.h
new file mode 100644
index 0000000000..69d83a6ea6
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_parse_pattern.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef SXE2_FLOW_PARSE_PATTERN_H_
+#define SXE2_FLOW_PARSE_PATTERN_H_
+#include <rte_flow_driver.h>
+#include "sxe2_osal.h"
+#include "sxe2_flow_define.h"
+
+#define SXE2_FLOW_EXPAND_NEXT(...) \
+	((const enum sxe2_expansion []){ \
+		__VA_ARGS__, 0, \
+	})
+
+struct sxe2_flow_expand_node {
+	const enum rte_flow_item_type type;
+	const enum sxe2_flow_tunnel_type tunnel_type;
+	const uint8_t is_tunnel;
+	const enum sxe2_expansion *const next;
+	const char *const name;
+};
+
+typedef int32_t (*sxe2_flow_parse_pattern_func_t)(const struct rte_flow_item *item,
+					      struct rte_flow_error *error,
+					      struct sxe2_flow *flow,
+					      enum sxe2_expansion next,
+					      bool is_inner);
+
+struct sxe2_flow_parse_pattern_ops {
+	bool is_inner;
+	sxe2_flow_parse_pattern_func_t func;
+};
+
+int32_t sxe2_flow_parse_pattern(struct rte_eth_dev *dev,
+			    const struct rte_flow_item patterns[],
+			    struct rte_flow_error *error,
+			    struct sxe2_flow *flow);
+
+#endif /* SXE2_FLOW_PARSE_PATTERN_H_ */
diff --git a/drivers/net/sxe2/sxe2_irq.c b/drivers/net/sxe2/sxe2_irq.c
index 13619500ea..3634eb99b6 100644
--- a/drivers/net/sxe2/sxe2_irq.c
+++ b/drivers/net/sxe2/sxe2_irq.c
@@ -20,6 +20,7 @@
 #include "sxe2vf_regs.h"
 #include "sxe2_host_regs.h"
 #include "sxe2_cmd_chnl.h"
+#include "sxe2_switchdev.h"
 
 #define SXE2_INT_EVENT_OICR_ALL (SXE2_PF_INT_OICR_SWINT | \
 					SXE2_PF_INT_OICR_LAN_TX_ERR | \
@@ -58,6 +59,14 @@ static void sxe2_event_irq_common_handler(struct sxe2_adapter *adapter, uint64_t
 						     RTE_ETH_EVENT_INTR_LSC,
 						     NULL);
 	}
+	if (oicr & RTE_BIT32(SXE2_COM_SW_MODE_SWITCHDEV)) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "event notify switchdev");
+		(void)sxe2_switchdev_notify_callback(adapter, true);
+	}
+	if (oicr & RTE_BIT32(SXE2_COM_SW_MODE_LEGACY)) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "event notify legacy");
+		(void)sxe2_switchdev_notify_callback(adapter, false);
+	}
 }
 
 static uint32_t sxe2_event_intr_handle(void *param __rte_unused)
@@ -881,6 +890,42 @@ int32_t sxe2_rxq_intr_enable(struct rte_eth_dev *dev)
 	return ret;
 }
 
+int32_t sxe2_repr_rxq_intr_enable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	uint16_t rxq_cnt = dev->data->nb_rx_queues;
+	int32_t ret = 0;
+	uint16_t qid = adapter->repr_priv_data->repr_q_id;
+	uint32_t val;
+
+	if (!rxq_cnt)
+		goto l_end;
+
+	sxe2_pci_hw_irq_disable(adapter, qid);
+	sxe2_pci_hw_int_itr_set(adapter, qid, SXE2_ITR_INTERVAL_NORMAL);
+	ret = sxe2_drv_rxq_bind_irq(adapter, qid, qid);
+	if (ret != 0) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "RXQ[%u] bind IRQ[%u] failed.",
+				qid, qid);
+		goto l_end;
+	}
+	sxe2_pci_hw_irq_enable(adapter, qid);
+
+	val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, qid);
+	if ((val & SXE2VF_DYN_CTL_INTENABLE) == 0)
+		goto l_end;
+
+	sxe2_pci_hw_msix_disable(adapter, qid);
+	sxe2_pci_hw_irq_trigger(adapter, qid);
+	val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, qid);
+	sxe2_pci_hw_irq_clear_pba(adapter, qid);
+	val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, qid);
+	sxe2_pci_hw_msix_enable(adapter, qid);
+
+l_end:
+	return ret;
+}
+
 void sxe2_rxq_intr_disable(struct rte_eth_dev *dev)
 {
 	struct sxe2_adapter *adapter =
@@ -901,6 +946,15 @@ void sxe2_rxq_intr_disable(struct rte_eth_dev *dev)
 	return;
 }
 
+void sxe2_repr_rxq_intr_disable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	uint16_t qid = adapter->repr_priv_data->repr_q_id;
+
+	sxe2_pci_hw_irq_disable(adapter, qid);
+	(void)sxe2_drv_rxq_unbind_irq(adapter, qid);
+}
+
 int32_t sxe2_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
 {
 	struct sxe2_adapter *adapter =
diff --git a/drivers/net/sxe2/sxe2_irq.h b/drivers/net/sxe2/sxe2_irq.h
index c898c16f84..e35278610b 100644
--- a/drivers/net/sxe2/sxe2_irq.h
+++ b/drivers/net/sxe2/sxe2_irq.h
@@ -60,8 +60,12 @@ void sxe2_sw_irq_ctx_hw_cap_set(struct sxe2_adapter *adapter,
 
 int32_t sxe2_rxq_intr_enable(struct rte_eth_dev *dev);
 
+int32_t sxe2_repr_rxq_intr_enable(struct rte_eth_dev *dev);
+
 void sxe2_rxq_intr_disable(struct rte_eth_dev *dev);
 
+void sxe2_repr_rxq_intr_disable(struct rte_eth_dev *dev);
+
 int32_t sxe2_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id);
 
 int32_t sxe2_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id);
diff --git a/drivers/net/sxe2/sxe2_queue.c b/drivers/net/sxe2/sxe2_queue.c
index 220cab6fce..afb2681b72 100644
--- a/drivers/net/sxe2/sxe2_queue.c
+++ b/drivers/net/sxe2/sxe2_queue.c
@@ -24,7 +24,11 @@ int32_t sxe2_queues_init(struct rte_eth_dev *dev)
 	struct sxe2_rx_queue *rxq;
 	uint16_t nb_rxq;
 
-	frame_size = dev->data->mtu + SXE2_ETH_OVERHEAD;
+	if (adapter->is_dev_repr)
+		frame_size = SXE2_FRAME_SIZE_MAX - SXE2_ETH_OVERHEAD;
+	else
+		frame_size = dev->data->mtu + SXE2_ETH_OVERHEAD;
+
 	for (nb_rxq = 0; nb_rxq < dev->data->nb_rx_queues; nb_rxq++) {
 		rxq = dev->data->rx_queues[nb_rxq];
 		if (!rxq)
diff --git a/drivers/net/sxe2/sxe2_stats.c b/drivers/net/sxe2/sxe2_stats.c
index 7ea2815fa3..2f8bb432c4 100644
--- a/drivers/net/sxe2/sxe2_stats.c
+++ b/drivers/net/sxe2/sxe2_stats.c
@@ -154,7 +154,12 @@ static int32_t sxe2_vsi_hw_stats_get_update(struct sxe2_adapter *adapter)
 
 	ret = sxe2_drv_get_vsi_stats(adapter);
 	if (ret) {
-		PMD_LOG_ERR(DRV, "get vsi stats failed, ret:%d.", ret);
+		if (adapter->is_dev_repr) {
+			PMD_LOG_WARN(DRV, "get repr vsi stats failed, ret:%d.", ret);
+			ret = 0;
+		} else {
+			PMD_LOG_ERR(DRV, "get vsi stats failed, ret:%d.", ret);
+		}
 		goto l_end;
 	}
 
@@ -231,7 +236,7 @@ static void sxe2_stats_update(struct sxe2_adapter *adapter)
 	stats->rx_sw_drop_bytes = sw_stats->rx_sw_drop_bytes +
 			sw_stats_prev->rx_sw_drop_bytes;
 
-	if (adapter->dev_type != SXE2_DEV_T_VF) {
+	if (adapter->dev_type != SXE2_DEV_T_VF  && !adapter->is_dev_repr) {
 		stats->rx_out_of_buffer = hw_stats->rx_out_of_buffer;
 		stats->rx_qblock_drop = hw_stats->rx_qblock_drop;
 		stats->tx_frame_good = hw_stats->tx_frame_good;
@@ -364,7 +369,7 @@ int32_t sxe2_xstats_info_get(struct rte_eth_dev *dev,
 	if (rte_eal_process_type() == RTE_PROC_SECONDARY)
 		return sxe2_mp_req_get_xstats(dev, xstats, usr_cnt);
 
-	if (adapter->dev_type == SXE2_DEV_T_VF)
+	if (adapter->dev_type == SXE2_DEV_T_VF || adapter->is_dev_repr)
 		xstats_cnt = SXE2_XSTAT_CNT_VF;
 	else
 		xstats_cnt = SXE2_XSTAT_CNT_PF;
@@ -387,7 +392,7 @@ int32_t sxe2_xstats_info_get(struct rte_eth_dev *dev,
 		goto end;
 	}
 
-	if (adapter->dev_type == SXE2_DEV_T_VF) {
+	if (adapter->dev_type == SXE2_DEV_T_VF || adapter->is_dev_repr) {
 		sxe2_stats_update(adapter);
 		for (i = 0; i < xstats_cnt; i++) {
 			(void)sxe2_xstat_vf_offset_get(i, &offset);
@@ -431,7 +436,7 @@ int32_t sxe2_xstats_names_get(__rte_unused struct rte_eth_dev *dev,
 	int32_t ret = -1;
 	uint32_t xstats_cnt = 0;
 
-	if (adapter->dev_type == SXE2_DEV_T_VF) {
+	if (adapter->dev_type == SXE2_DEV_T_VF || adapter->is_dev_repr) {
 		field = sxe2_xstats_field_vf;
 		xstats_cnt = SXE2_XSTAT_CNT_VF;
 	} else {
@@ -476,7 +481,7 @@ int32_t sxe2_stats_hw_reset(struct rte_eth_dev *dev)
 		PMD_LOG_ERR(DRV, "reset vsi stats failed, ret:%d.", ret);
 		goto l_end;
 	}
-	if (adapter->dev_type != SXE2_DEV_T_VF) {
+	if (adapter->dev_type != SXE2_DEV_T_VF && !adapter->is_dev_repr) {
 		ret = sxe2_drv_mac_stats_reset(adapter);
 		if (ret) {
 			PMD_LOG_ERR(DRV, "reset mac stats failed, ret:%d.", ret);
diff --git a/drivers/net/sxe2/sxe2_switchdev.c b/drivers/net/sxe2/sxe2_switchdev.c
new file mode 100644
index 0000000000..44703cfb5c
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_switchdev.c
@@ -0,0 +1,332 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_os.h>
+#include <rte_tailq.h>
+#include "sxe2_osal.h"
+#include "sxe2_mac.h"
+#include "sxe2_common_log.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_switchdev.h"
+#include "sxe2_cmd_chnl.h"
+#include "sxe2_host_regs.h"
+
+int32_t sxe2_uplink_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "Current mode is not switchdev");
+		goto l_end;
+	}
+
+	ret = sxe2_drv_switchdev_uplink_config(adapter, false);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_uplink_set(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "Current mode is not switchdev");
+		goto l_end;
+	}
+
+	ret = sxe2_drv_switchdev_uplink_config(adapter, true);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_repr_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	uint16_t repr_id = 0;
+	struct rte_eth_dev *repr_dev;
+	struct sxe2_adapter *repr_adapter;
+	struct sxe2_switchdev_repr_info repr_vf;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "Current mode is not switchdev");
+		goto l_end;
+	}
+
+	for (repr_id = 0; repr_id < adapter->repr_ctxt.nb_repr_vf; repr_id++) {
+		repr_dev = adapter->repr_ctxt.vf_rep_eth_dev[repr_id];
+		if (!repr_dev)
+			continue;
+		repr_adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(repr_dev);
+		if (repr_adapter &&
+		    repr_adapter->repr_priv_data &&
+		    repr_adapter->repr_priv_data->cp_vsi) {
+			memset(&repr_vf, 0, sizeof(struct sxe2_switchdev_repr_info));
+
+			repr_vf.repr_pf_id = repr_adapter->repr_priv_data->repr_pf_id;
+			repr_vf.repr_vf_id = repr_adapter->repr_priv_data->repr_vf_id;
+			repr_vf.cp_vsi_id = repr_adapter->repr_priv_data->cp_vsi->vsi_id;
+			repr_vf.repr_q_id = repr_adapter->repr_priv_data->repr_q_id;
+			ret = sxe2_drv_switchdev_repr_vf_config(adapter, &repr_vf,
+								false);
+		}
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_repr_set(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	uint16_t repr_id = 0;
+	struct rte_eth_dev *repr_dev;
+	struct sxe2_adapter *repr_adapter;
+	struct sxe2_switchdev_repr_info repr_vf;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "Current mode is not switchdev");
+		goto l_end;
+	}
+
+	for (repr_id = 0; repr_id < adapter->repr_ctxt.nb_repr_vf; repr_id++) {
+		repr_dev = adapter->repr_ctxt.vf_rep_eth_dev[repr_id];
+		if (!repr_dev)
+			continue;
+		repr_adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(repr_dev);
+		if (repr_adapter &&
+		    repr_adapter->repr_priv_data &&
+		    repr_adapter->repr_priv_data->cp_vsi) {
+			memset(&repr_vf, 0, sizeof(struct sxe2_switchdev_repr_info));
+
+			repr_vf.repr_pf_id = repr_adapter->repr_priv_data->repr_pf_id;
+			repr_vf.repr_vf_id = repr_adapter->repr_priv_data->repr_vf_id;
+			repr_vf.cp_vsi_id = repr_adapter->repr_priv_data->cp_vsi->vsi_id;
+			repr_vf.repr_q_id = repr_adapter->repr_priv_data->repr_q_id;
+			ret = sxe2_drv_switchdev_repr_vf_config(adapter, &repr_vf, true);
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+void sxe2_free_repr_info(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (adapter->repr_ctxt.vf_rep_eth_dev) {
+		rte_free(adapter->repr_ctxt.vf_rep_eth_dev);
+		adapter->repr_ctxt.vf_rep_eth_dev = NULL;
+	}
+
+	adapter->repr_ctxt.nb_repr_vf = 0;
+}
+
+static int32_t sxe2_switchdev_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "Current mode is not switchdev");
+		goto l_end;
+	}
+
+	adapter->switchdev_info.is_switchdev = false;
+
+	if (!adapter->flow_isolate_cfg && adapter->flow_isolated)
+		adapter->flow_isolated = false;
+
+	ret = sxe2_l2_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update l2 rule");
+
+	ret = sxe2_switchdev_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update switchdev rule");
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_switchdev_set(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "Current mode switch dev");
+		goto l_end;
+	}
+
+	adapter->switchdev_info.is_switchdev = true;
+
+	if (adapter->flow_isolate_cfg && !adapter->flow_isolated)
+		adapter->flow_isolated = true;
+
+	ret = sxe2_l2_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update l2 rule");
+
+	ret = sxe2_switchdev_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update switchdev rule");
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_switchdev_notify_callback(struct sxe2_adapter *adapter, bool set)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[adapter->dev_info.dev_data->port_id];
+	int32_t ret = 0;
+	bool cur_switchdev_set = false;
+
+	if (adapter->repr_ctxt.nb_repr_vf) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "switch dev notify remove dev");
+		rte_eth_dev_callback_process(dev, RTE_ETH_EVENT_INTR_RMV, NULL);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_switchdev_mode_get(adapter, &cur_switchdev_set);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to get switchdev mode");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (set != cur_switchdev_set) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "current switchdev mode miss macth");
+		goto l_end;
+	}
+
+	if (set) {
+		ret = sxe2_switchdev_set(adapter);
+		if (ret != 0) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set switchdev");
+			goto l_end;
+		}
+	} else {
+		ret = sxe2_switchdev_clear(adapter);
+		if (ret != 0) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to clear switchdev");
+			goto l_end;
+		}
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_switchdev_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	PMD_DEV_LOG_INFO(adapter, INIT, "switchdev init");
+
+	if (adapter->switchdev_info.is_switchdev)
+		adapter->flow_isolated = true;
+
+	adapter->repr_priv_data = NULL;
+	adapter->repr_ctxt.nb_repr_vf = 0;
+
+	return 0;
+}
+
+int32_t sxe2_switchdev_uninit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	PMD_DEV_LOG_INFO(adapter, INIT, "switchdev uinit");
+
+	if (adapter->repr_priv_data) {
+		rte_free(adapter->repr_priv_data);
+		adapter->repr_priv_data = NULL;
+	}
+
+	return 0;
+}
+
+int32_t sxe2_switchdev_dev_info_init(struct rte_eth_dev *dev,
+			struct sxe2_adapter *parent_adapter)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_dev_info *dev_info = &adapter->dev_info;
+	struct sxe2_dev_info *parent_dev_info = &parent_adapter->dev_info;
+	struct sxe2_drv_dev_info_resp dev_info_resp = {0};
+	struct sxe2_drv_dev_fw_info_resp dev_fw_info_resp = {0};
+	int32_t ret = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	dev_info->pci = parent_dev_info->pci;
+	dev_info->pci.max_vfs = 0;
+
+	ret = sxe2_drv_dev_info_get(adapter, &dev_info_resp);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to get device info, ret=[%d]", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_dev_fw_info_get(adapter, &dev_fw_info_resp);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to get device fw info, ret=[%d]", ret);
+		goto l_end;
+	}
+	dev_info->fw.build_id = dev_fw_info_resp.build_id;
+	dev_info->fw.fix_version_id = dev_fw_info_resp.fix_version_id;
+	dev_info->fw.sub_version_id = dev_fw_info_resp.sub_version_id;
+	dev_info->fw.main_version_id = dev_fw_info_resp.main_version_id;
+
+	rte_ether_addr_copy((struct rte_ether_addr *)dev_info_resp.mac_addr,
+						(struct rte_ether_addr *)dev_info->mac.perm_addr);
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_switchdev_repr_private_data_init(struct rte_eth_dev *dev,
+		struct sxe2_adapter *parent_adapter, uint16_t repr_id)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_repr_private_data *repr_priv_data = adapter->repr_priv_data;
+	int32_t ret = 0;
+
+	if (repr_priv_data != NULL)
+		goto l_end;
+
+	repr_priv_data = rte_zmalloc("sxe2_repr_priv_data",
+			sizeof(struct sxe2_repr_private_data), 0);
+	if (repr_priv_data == NULL) {
+		PMD_LOG_ERR(INIT, "Failed to malloc representor private data.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	repr_priv_data->parent_adapter = parent_adapter;
+	repr_priv_data->repr_id = repr_id;
+	repr_priv_data->cp_vsi =
+		TAILQ_FIRST(&parent_adapter->vsi_ctxt.other_vsi_list);
+	if (repr_priv_data->cp_vsi == NULL) {
+		PMD_LOG_ERR(INIT, "Failed to get cp vsi.");
+		ret = -EINVAL;
+		goto l_free;
+	}
+	repr_priv_data->repr_q_id = repr_id;
+	repr_priv_data->repr_pf_id = parent_adapter->pf_idx;
+	repr_priv_data->repr_vf_id = repr_id;
+	repr_priv_data->repr_vf_k_vsi_id =
+		parent_adapter->repr_ctxt.repr_vf_id[repr_id].kernel_vsi_id;
+	repr_priv_data->repr_vf_u_vsi_id =
+		parent_adapter->repr_ctxt.repr_vf_id[repr_id].dpdk_vsi_id;
+
+	repr_priv_data->repr_vf_vsi_id =
+		parent_adapter->repr_ctxt.repr_vf_id[repr_id].kernel_vsi_id !=
+		SXE2_INVALID_VSI_ID ?
+		parent_adapter->repr_ctxt.repr_vf_id[repr_id].kernel_vsi_id :
+		parent_adapter->repr_ctxt.repr_vf_id[repr_id].dpdk_vsi_id;
+
+	adapter->repr_priv_data = repr_priv_data;
+	goto l_end;
+l_free:
+	rte_free(repr_priv_data);
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_switchdev.h b/drivers/net/sxe2/sxe2_switchdev.h
new file mode 100644
index 0000000000..0a74454424
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_switchdev.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_SWITCHDEV_H__
+#define __SXE2_SWITCHDEV_H__
+#include <ethdev_driver.h>
+
+struct sxe2_adapter;
+
+int32_t sxe2_uplink_clear(struct sxe2_adapter *adapter);
+
+int32_t sxe2_uplink_set(struct sxe2_adapter *adapter);
+
+int32_t sxe2_repr_clear(struct sxe2_adapter *adapter);
+
+int32_t sxe2_repr_set(struct sxe2_adapter *adapter);
+
+int32_t sxe2_switchdev_notify_callback(struct sxe2_adapter *adapter, bool set);
+
+int32_t sxe2_switchdev_init(struct rte_eth_dev *dev);
+
+int32_t sxe2_switchdev_uninit(struct rte_eth_dev *dev);
+
+void sxe2_free_repr_info(struct rte_eth_dev *dev);
+
+int32_t sxe2_switchdev_dev_info_init(struct rte_eth_dev *dev,
+			struct sxe2_adapter *parent_adapter);
+
+int32_t sxe2_switchdev_repr_private_data_init(struct rte_eth_dev *dev,
+		struct sxe2_adapter *parent_adapter, uint16_t repr_id);
+
+#endif /* __SXE2_SWITCHDEV_H__ */
diff --git a/drivers/net/sxe2/sxe2_txrx.c b/drivers/net/sxe2/sxe2_txrx.c
index 2eb8365457..a2cea954d5 100644
--- a/drivers/net/sxe2/sxe2_txrx.c
+++ b/drivers/net/sxe2/sxe2_txrx.c
@@ -152,6 +152,13 @@ void sxe2_tx_mode_func_set(struct rte_eth_dev *dev)
 	uint32_t batch_flags = 0;
 
 	PMD_INIT_FUNC_TRACE();
+
+	if (adapter->is_dev_repr) {
+		dev->tx_pkt_prepare = sxe2_tx_pkts_prepare;
+		dev->tx_pkt_burst = sxe2_tx_pkts;
+		tx_mode_flags = 0;
+		return;
+	}
 	if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
 		tx_mode_flags = 0;
 		ret = sxe2_tx_vec_support_check(dev, &vec_flags);
diff --git a/drivers/net/sxe2/sxe2_txrx_poll.c b/drivers/net/sxe2/sxe2_txrx_poll.c
index 8b6e585c36..f3c4fa0d91 100644
--- a/drivers/net/sxe2/sxe2_txrx_poll.c
+++ b/drivers/net/sxe2/sxe2_txrx_poll.c
@@ -454,6 +454,14 @@ uint16_t sxe2_tx_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 				desc_l2tag2 = tx_pkt->vlan_tci_outer;
 				desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_CMD_IL2TAG2_MASK;
 			}
+			if (unlikely(vsi->vsi_type == SXE2_VSI_T_DPDK_ESW)) {
+				desc_type_cmd_tso_mss |=
+					(SXE2_TX_CTXT_DESC_CMD_SWTCH_VSI <<
+					SXE2_TX_CTXT_DESC_CMD_SHIFT);
+				desc_type_cmd_tso_mss |=
+					((vsi->adapter->repr_priv_data->repr_vf_vsi_id & 0x3FFULL)
+					<< SXE2_TX_CTXT_DESC_VSI_SHIFT);
+			}
 
 			ctxt_desc->tunneling_params =
 				rte_cpu_to_le_32(desc_tunneling_params);
diff --git a/drivers/net/sxe2/sxe2_vsi.c b/drivers/net/sxe2/sxe2_vsi.c
index baaa20c02e..d29480b931 100644
--- a/drivers/net/sxe2/sxe2_vsi.c
+++ b/drivers/net/sxe2/sxe2_vsi.c
@@ -98,9 +98,15 @@ static struct sxe2_vsi *sxe2_vsi_node_create(struct sxe2_adapter *adapter,
 
 static void sxe2_vsi_node_free(struct sxe2_vsi *vsi)
 {
+	struct sxe2_adapter *adapter;
+
 	if (!vsi)
 		return;
 
+	adapter = vsi->adapter;
+	if (vsi->vsi_type == SXE2_VSI_T_ESW)
+		TAILQ_REMOVE(&adapter->vsi_ctxt.other_vsi_list, vsi, next);
+
 	rte_free(vsi);
 	vsi = NULL;
 }
@@ -174,10 +180,54 @@ static int32_t sxe2_main_vsi_create(struct sxe2_adapter *adapter)
 	return ret;
 }
 
+int32_t sxe2_other_vsi_create(struct sxe2_adapter *adapter, uint16_t cnt_vf)
+{
+	int32_t ret = 0;
+	struct sxe2_vsi *other_vsi = NULL;
+	uint16_t vsi_id = SXE2_INVALID_VSI_ID;
+
+	PMD_INIT_FUNC_TRACE();
+
+	other_vsi = sxe2_vsi_node_create(adapter, SXE2_INVALID_VSI_ID, SXE2_VSI_T_DPDK_ESW);
+	if (other_vsi == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	ret = sxe2_drv_switchdev_cpvsi_get(adapter, &vsi_id);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to query vsi from fw, ret=%d", ret);
+		goto l_free_vsi;
+	}
+
+	other_vsi->vsi_id = vsi_id;
+	other_vsi->vsi_type = SXE2_VSI_T_DPDK_ESW;
+
+	ret = sxe2_drv_vsi_info_get(adapter, other_vsi);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to query vsi info from fw, ret=%d", ret);
+		goto l_free_vsi;
+	}
+
+	other_vsi->txqs.q_cnt = RTE_MIN(cnt_vf, other_vsi->txqs.q_cnt);
+	other_vsi->rxqs.q_cnt = RTE_MIN(cnt_vf, other_vsi->rxqs.q_cnt);
+	other_vsi->irqs.avail_cnt = RTE_MIN(cnt_vf, other_vsi->irqs.avail_cnt);
+
+	TAILQ_INSERT_TAIL(&adapter->vsi_ctxt.other_vsi_list, other_vsi, next);
+	goto l_end;
+
+l_free_vsi:
+	sxe2_vsi_node_free(other_vsi);
+l_end:
+	return ret;
+}
+
 int32_t sxe2_vsi_init(struct rte_eth_dev *dev)
 {
 	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
 	int32_t ret = 0;
+	uint16_t srcvsi_list[SXE2_SRCVSI_PRUNE_MAX_NUM];
+	uint16_t srcvsi_cnt;
 
 	PMD_INIT_FUNC_TRACE();
 
@@ -187,6 +237,18 @@ int32_t sxe2_vsi_init(struct rte_eth_dev *dev)
 		goto l_end;
 	}
 
+	if (adapter->vsi_ctxt.kernel_vsi_id != SXE2_INVALID_VSI_ID) {
+		srcvsi_list[0] = adapter->vsi_ctxt.kernel_vsi_id;
+		srcvsi_list[1] = adapter->vsi_ctxt.dpdk_vsi_id;
+		srcvsi_cnt = 2;
+		ret = sxe2_drv_srcvsi_prune_config(adapter, srcvsi_list, srcvsi_cnt, true);
+		if (ret) {
+			PMD_LOG_ERR(DRV, "Failed to set src vsi to fw, ret=%d", ret);
+			goto l_end;
+		}
+		PMD_LOG_DEBUG(DRV, "Successfully set src vsi");
+	}
+
 l_end:
 	return ret;
 }
@@ -194,21 +256,105 @@ int32_t sxe2_vsi_init(struct rte_eth_dev *dev)
 void sxe2_vsi_uninit(struct rte_eth_dev *dev)
 {
 	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_vsi *var, *tvar;
 	int32_t ret;
+	uint16_t srcvsi_list[SXE2_SRCVSI_PRUNE_MAX_NUM];
+	uint16_t srcvsi_cnt;
 
 	if (adapter->vsi_ctxt.main_vsi == NULL) {
 		PMD_LOG_INFO(DRV, "vsi is not created, no need to destroy.");
 		goto l_end;
 	}
 
+	if (adapter->vsi_ctxt.kernel_vsi_id != SXE2_INVALID_VSI_ID) {
+		srcvsi_list[0] = adapter->vsi_ctxt.kernel_vsi_id;
+		srcvsi_list[1] = adapter->vsi_ctxt.dpdk_vsi_id;
+		srcvsi_cnt = 2;
+		ret = sxe2_drv_srcvsi_prune_config(adapter, srcvsi_list,
+						   srcvsi_cnt, false);
+		if (ret) {
+			PMD_LOG_ERR(DRV, "Failed to clear src vsi to fw, ret=%d", ret);
+			if (ret == -EPERM)
+				goto l_free;
+			goto l_end;
+		}
+		PMD_LOG_DEBUG(DRV, "Successfully clear src vsi");
+	}
+
+l_free:
 	ret = sxe2_vsi_destroy(adapter, adapter->vsi_ctxt.main_vsi);
 	if (ret) {
 		PMD_LOG_ERR(DRV, "Failed to del vsi from fw, ret=%d", ret);
 		goto l_end;
 	}
+	RTE_TAILQ_FOREACH_SAFE(var, &adapter->vsi_ctxt.other_vsi_list, next, tvar) {
+		ret = sxe2_vsi_destroy(adapter, var);
+		if (ret) {
+			PMD_LOG_ERR(DRV, "Failed to del vsi from fw, ret=%d", ret);
+			break;
+		}
+	}
 
 	PMD_LOG_DEBUG(DRV, "vsi destroyed.");
 
 l_end:
 	return;
 }
+
+int32_t sxe2_vsi_repr_main_vsi_create(struct rte_eth_dev *dev,
+				  struct sxe2_adapter *parent_adapter,
+				  uint16_t repr_id)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_vsi *vsi = NULL;
+	struct sxe2_vsi *pos;
+	int32_t ret = 0;
+
+	TAILQ_FOREACH(pos, &parent_adapter->vsi_ctxt.other_vsi_list, next) {
+		if (pos->vsi_type == SXE2_VSI_T_DPDK_ESW) {
+			vsi = pos;
+			break;
+		}
+	}
+
+	if (vsi == NULL) {
+		PMD_LOG_ERR(INIT, "Failed to get dpdk vsi.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	TAILQ_INIT(&adapter->vsi_ctxt.other_vsi_list);
+
+	adapter->vsi_ctxt.vsi_type = SXE2_VSI_T_DPDK_ESW;
+	adapter->vsi_ctxt.kernel_vsi_id = SXE2_INVALID_VSI_ID;
+
+	adapter->cdev = parent_adapter->cdev;
+
+	adapter->q_ctxt.base_idx_in_pf = vsi->txqs.base_idx_in_func +
+		RTE_MIN(vsi->txqs.q_cnt, repr_id);
+	adapter->irq_ctxt.base_idx_in_func = vsi->irqs.base_idx_in_pf +
+		RTE_MIN(vsi->irqs.avail_cnt, repr_id);
+	adapter->q_ctxt.qp_cnt_assign = RTE_MIN(vsi->txqs.q_cnt, 1);
+	adapter->irq_ctxt.max_cnt_hw = RTE_MIN(vsi->irqs.avail_cnt, 1);
+
+	adapter->vsi_ctxt.main_vsi =
+		sxe2_vsi_node_create(adapter, vsi->vsi_id, SXE2_VSI_T_DPDK_ESW);
+	if (adapter->vsi_ctxt.main_vsi == NULL) {
+		ret = -ENOMEM;
+		PMD_LOG_ERR(DRV, "Failed to create vsi struct, ret=%d", ret);
+		goto l_end;
+	}
+	adapter->vsi_ctxt.dpdk_vsi_id = adapter->vsi_ctxt.main_vsi->vsi_id;
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+void sxe2_vsi_repr_main_vsi_destroy(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	sxe2_vsi_node_free(adapter->vsi_ctxt.main_vsi);
+}
diff --git a/drivers/net/sxe2/sxe2_vsi.h b/drivers/net/sxe2/sxe2_vsi.h
index e712f738f1..176f34b4f8 100644
--- a/drivers/net/sxe2/sxe2_vsi.h
+++ b/drivers/net/sxe2/sxe2_vsi.h
@@ -192,13 +192,23 @@ struct sxe2_vsi_context {
 	uint16_t bond_member_dpdk_vsi_id[SXE2_MAX_BOND_MEMBER_CNT];
 
 	struct sxe2_vsi *main_vsi;
+
+	struct sxe2_vsi_list_head other_vsi_list;
 };
 
 void sxe2_sw_vsi_ctx_hw_cap_set(struct sxe2_adapter *adapter,
-		struct sxe2_drv_vsi_caps *vsi_caps);
+				struct sxe2_drv_vsi_caps *vsi_caps);
+
+int32_t sxe2_other_vsi_create(struct sxe2_adapter *adapter,  uint16_t cnt_vf);
 
 int32_t sxe2_vsi_init(struct rte_eth_dev *dev);
 
 void sxe2_vsi_uninit(struct rte_eth_dev *dev);
 
+int32_t sxe2_vsi_repr_main_vsi_create(struct rte_eth_dev *dev,
+				  struct sxe2_adapter *parent_adapter,
+				  uint16_t repr_id);
+
+void sxe2_vsi_repr_main_vsi_destroy(struct rte_eth_dev *dev);
+
 #endif /* __SXE2_VSI_H__ */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 17/23] net/sxe2: implement private dump info
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch implements the 'eth_dev_priv_dump' ops for the sxe2 PMD.
This interface allows applications to dump driver-specific internal
state and configuration information to a file stream.

The output includes:
- capabilities.
- device base info.
- device args info.
- device filter info.
- reprenstor info.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build        |   1 +
 drivers/net/sxe2/sxe2_dump.c        | 289 ++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_dump.h        |  12 ++
 drivers/net/sxe2/sxe2_ethdev.c      |   3 +
 drivers/net/sxe2/sxe2_ethdev_repr.c |   3 +
 5 files changed, 308 insertions(+)
 create mode 100644 drivers/net/sxe2/sxe2_dump.c
 create mode 100644 drivers/net/sxe2/sxe2_dump.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index 61925ee80f..631730bf3e 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -77,6 +77,7 @@ sources += files(
         'sxe2_flow_parse_action.c',
         'sxe2_flow_parse_pattern.c',
         'sxe2_flow_parse_engine.c',
+        'sxe2_dump.c',
 )
 
 allow_internal_get_api = true
diff --git a/drivers/net/sxe2/sxe2_dump.c b/drivers/net/sxe2/sxe2_dump.c
new file mode 100644
index 0000000000..9898456aea
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_dump.c
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_malloc.h>
+#include <arpa/inet.h>
+
+#include "sxe2_common_log.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_dump.h"
+#include "sxe2_stats.h"
+
+static void
+sxe2_dump_dev_feature_capability(FILE *file, struct rte_eth_dev *dev)
+{
+	uint32_t i;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	const struct {
+		uint32_t cap_bit;
+		const char *name;
+	} caps_name[] = {
+		{SXE2_DEV_CAPS_OFFLOAD_L2, "L2"},
+		{SXE2_DEV_CAPS_OFFLOAD_VLAN, "VLAN"},
+		{SXE2_DEV_CAPS_OFFLOAD_IPSEC, "IPSEC"},
+		{SXE2_DEV_CAPS_OFFLOAD_RSS, "RSS"},
+		{SXE2_DEV_CAPS_OFFLOAD_FNAV, "FNAV"},
+		{SXE2_DEV_CAPS_OFFLOAD_TM, "TM"},
+		{SXE2_DEV_CAPS_OFFLOAD_PTP, "PTP"},
+	};
+	if (adapter->is_dev_repr)
+		goto l_end;
+
+	fprintf(file, "  - Dev Capability:\n");
+	for (i = 0; i < RTE_DIM(caps_name); i++) {
+		fprintf(file, "\t  -- support %s: %s\n", caps_name[i].name,
+			(adapter->cap_flags & caps_name[i].cap_bit) ? "Yes" :
+									 "No");
+	}
+l_end:
+	return;
+}
+
+static void
+sxe2_dump_device_basic_info(FILE *file, struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	fprintf(file,
+		"  - Device Base Info:\n"
+		"\t  -- name: %s\n"
+		"\t  -- pf_idx: %u port_idx: %u\n"
+		"\t  -- tx_mode_flags: 0x%x rx_mode_flags: 0x%x\n"
+		"\t  -- flow_isolate_cfg: 0x%x flow_isolated: 0x%x\n"
+		"\t  -- dev_type: 0x%x is_switchdev: 0x%x\n"
+		"\t  -- is_dev_repr: 0x%x dev_port_id: 0x%x\n"
+		"\t  -- dev_flags: 0x%x\n"
+		"\t  -- intr_conf lsc: %u rxq: %u rmv: %u\n",
+		dev->data->name,
+		adapter->pf_idx, adapter->port_idx,
+		adapter->tx_mode_flags, adapter->rx_mode_flags,
+		adapter->flow_isolate_cfg, adapter->flow_isolated,
+		adapter->dev_type, adapter->switchdev_info.is_switchdev,
+		adapter->is_dev_repr, adapter->dev_port_id,
+		dev->data->dev_flags,
+		dev->data->dev_conf.intr_conf.lsc,
+		dev->data->dev_conf.intr_conf.rxq,
+		dev->data->dev_conf.intr_conf.rmv);
+}
+
+static void
+sxe2_dump_dev_args_info(FILE *file, struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (adapter->is_dev_repr)
+		goto l_end;
+
+	fprintf(file,
+		"  - Device Args Info:\n"
+		"\t  -- sw-stats-en: %s\n"
+		"\t  -- high-performance-mode: %s\n"
+		"\t  -- flow-duplicate-pattern: %u\n"
+		"\t  -- fnav-stat-type: %u\n"
+		"\t  -- sched_layer_mode: %u\n"
+		"\t  -- rx_low_latency: %s\n"
+		"\t  -- function-flow-direct: %s\n",
+		adapter->devargs.sw_stats_en ? "On" : "Off",
+		adapter->devargs.high_performance_mode ? "On" : "Off",
+		adapter->devargs.flow_dup_pattern_mode,
+		adapter->devargs.fnav_stat_type,
+		adapter->devargs.sched_layer_mode,
+		adapter->devargs.rx_low_latency ? "On" : "Off",
+		adapter->devargs.func_flow_direct_en ? "On" : "Off");
+l_end:
+	return;
+}
+
+static void sxe2_dump_filter_info(FILE *file, struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_mac_filter *mac_entry;
+	struct sxe2_mac_filter *next_mac_entry;
+	struct sxe2_vlan_filter *vlan_entry;
+	struct sxe2_vlan_filter *next_vlan_entry;
+
+	if (adapter->is_dev_repr)
+		goto l_end;
+
+	fprintf(file,
+		"  - Device Filter Info:\n"
+		"\t  -- cur_promisc:0x%x hw_promisc:0x%x\n"
+		"\t  -- unicast_num: %u multicast_num: %u\n"
+		"\t  -- vlan_num: %u filter_on: %u hw_filter_on: %u\n"
+		"\t  -- vlan max_cnt: %u cnt: %u\n"
+		"\t  -- tpid: 0x%x vid: 0x%x\n"
+		"\t  -- vlan_outer_insert: 0x%x vlan_outer_strip: 0x%x\n"
+		"\t  -- vlan_inner_insert: 0x%x vlan_inner_strip: 0x%x\n",
+		adapter->filter_ctxt.cur_promisc_flags,
+		adapter->filter_ctxt.hw_promisc_flags,
+		adapter->filter_ctxt.uc_num,
+		adapter->filter_ctxt.mc_num,
+		adapter->filter_ctxt.vlan_num,
+		adapter->filter_ctxt.vlan_info.filter_on,
+		adapter->filter_ctxt.vlan_info.hw_filter_on,
+		adapter->filter_ctxt.vlan_info.max_cnt,
+		adapter->filter_ctxt.vlan_info.cnt,
+		adapter->filter_ctxt.vlan_info.tpid,
+		adapter->filter_ctxt.vlan_info.vid,
+		adapter->filter_ctxt.vlan_info.outer_insert,
+		adapter->filter_ctxt.vlan_info.outer_strip,
+		adapter->filter_ctxt.vlan_info.inner_insert,
+		adapter->filter_ctxt.vlan_info.inner_strip);
+
+	if (adapter->filter_ctxt.uc_num > 0) {
+		fprintf(file,
+			"\t  -- Unicast entry:\n");
+		RTE_TAILQ_FOREACH_SAFE(mac_entry, &adapter->filter_ctxt.uc_list, next,
+				       next_mac_entry) {
+			fprintf(file,
+				"\t  -- addr: %02x:%02x:%02x:%02x:%02x:%02x hw status:%u "
+				"default:%u\n",
+				mac_entry->mac_addr.addr_bytes[0],
+				mac_entry->mac_addr.addr_bytes[1],
+				mac_entry->mac_addr.addr_bytes[2],
+				mac_entry->mac_addr.addr_bytes[3],
+				mac_entry->mac_addr.addr_bytes[4],
+				mac_entry->mac_addr.addr_bytes[5],
+				mac_entry->hw_config,
+				mac_entry->default_config);
+		}
+	}
+
+	if (adapter->filter_ctxt.mc_num > 0) {
+		fprintf(file,
+			"\t  -- Multicast entry:\n");
+		RTE_TAILQ_FOREACH_SAFE(mac_entry, &adapter->filter_ctxt.mc_list,
+				       next, next_mac_entry) {
+			fprintf(file,
+				"\t  -- addr: %02x:%02x:%02x:%02x:%02x:%02x "
+				"hw status:%u default:%u\n",
+				mac_entry->mac_addr.addr_bytes[0],
+				mac_entry->mac_addr.addr_bytes[1],
+				mac_entry->mac_addr.addr_bytes[2],
+				mac_entry->mac_addr.addr_bytes[3],
+				mac_entry->mac_addr.addr_bytes[4],
+				mac_entry->mac_addr.addr_bytes[5],
+				mac_entry->hw_config,
+				mac_entry->default_config);
+		}
+	}
+
+	if (adapter->filter_ctxt.vlan_num > 0) {
+		fprintf(file,
+			"\t  -- Vlan entry:\n");
+		RTE_TAILQ_FOREACH_SAFE(vlan_entry, &adapter->filter_ctxt.vlan_list,
+			next, next_vlan_entry) {
+			fprintf(file,
+				"\t  -- vlan tpid:0x%04x vid:0x%04x prio:%d "
+				"hw status:%u default:%u\n",
+				vlan_entry->vlan_info.tpid,
+				vlan_entry->vlan_info.vid,
+				vlan_entry->vlan_info.prio,
+				vlan_entry->hw_config,
+				vlan_entry->default_config);
+		}
+	}
+l_end:
+	return;
+}
+
+static const char *sxe2_vsi_id_str(uint16_t vsi_id, char *buf, size_t len)
+{
+	if (vsi_id == SXE2_INVALID_VSI_ID)
+		return "NA";
+
+	snprintf(buf, len, "%u", vsi_id);
+	return buf;
+}
+
+static void
+sxe2_dump_switchdev_info(FILE *file, struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	uint32_t idx;
+	char k_vsi_buf[16];
+	char u_vsi_buf[16];
+
+	if (adapter->is_dev_repr && adapter->repr_priv_data) {
+		fprintf(file,
+			"  - Reprenstor Info:\n"
+			"\t  -- repr_id: %u\n"
+			"\t  -- repr_q_id: %u\n"
+			"\t  -- repr_pf_id: %u\n"
+			"\t  -- repr_vf_id: %u\n"
+			"\t  -- repr_vf_vsi_id: %u\n"
+			"\t  -- repr_vf_k_vsi_id: %s\n"
+			"\t  -- repr_vf_u_vsi_id: %s\n",
+			adapter->repr_priv_data->repr_id,
+			adapter->repr_priv_data->repr_q_id,
+			adapter->repr_priv_data->repr_pf_id,
+			adapter->repr_priv_data->repr_vf_id,
+			adapter->repr_priv_data->repr_vf_vsi_id,
+			sxe2_vsi_id_str(adapter->repr_priv_data->repr_vf_k_vsi_id,
+					k_vsi_buf, sizeof(k_vsi_buf)),
+			sxe2_vsi_id_str(adapter->repr_priv_data->repr_vf_u_vsi_id,
+					u_vsi_buf, sizeof(u_vsi_buf)));
+		goto l_end;
+	}
+	if (adapter->switchdev_info.is_switchdev) {
+		fprintf(file,
+			"  - Switchdev Info:\n"
+			"\t  -- master:0x%x\n"
+			"\t  -- representor: 0x%x\n"
+			"\t  -- port_name_type: 0x%x\n"
+			"\t  -- nb_vf: %u nb_repr_vf: %u\n",
+			adapter->switchdev_info.master,
+			adapter->switchdev_info.representor,
+			adapter->switchdev_info.port_name_type,
+			adapter->repr_ctxt.nb_vf,
+			adapter->repr_ctxt.nb_repr_vf);
+		if (adapter->repr_ctxt.nb_vf > 0) {
+			fprintf(file,
+				"\t  -- vf entry:\n");
+			for (idx = 0; idx < adapter->repr_ctxt.nb_vf; idx++) {
+				fprintf(file,
+					"\t  -- func_id:%u vsi_type:%u kernel_vsi_id:%u dpdk_vsi_id:%u\n",
+					adapter->repr_ctxt.repr_vf_id[idx].func_id,
+					adapter->repr_ctxt.repr_vf_id[idx].vsi_type,
+					adapter->repr_ctxt.repr_vf_id[idx].kernel_vsi_id,
+					adapter->repr_ctxt.repr_vf_id[idx].dpdk_vsi_id);
+			}
+		}
+	}
+
+l_end:
+	return;
+}
+
+int32_t sxe2_eth_dev_priv_dump(struct rte_eth_dev *dev, FILE *file)
+{
+	char *buf = NULL;
+	size_t size = 0;
+	FILE *str;
+	int32_t ret = -1;
+
+	str = open_memstream(&buf, &size);
+	if (!str) {
+		PMD_LOG_ERR(DRV, "fopen fail.");
+		goto l_end;
+	}
+
+	sxe2_dump_dev_feature_capability(str, dev);
+	sxe2_dump_device_basic_info(str, dev);
+	sxe2_dump_dev_args_info(str, dev);
+	sxe2_dump_filter_info(str, dev);
+	sxe2_dump_switchdev_info(str, dev);
+
+	(void)fflush(str);
+
+	(void)fwrite(buf, 1, size, file);
+	(void)fflush(file);
+
+	ret = 0;
+
+	(void)fclose(str);
+	free(buf);
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_dump.h b/drivers/net/sxe2/sxe2_dump.h
new file mode 100644
index 0000000000..05d6db9b3d
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_dump.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_DUMP_H__
+#define __SXE2_DUMP_H__
+
+#include <ethdev_driver.h>
+
+int32_t sxe2_eth_dev_priv_dump(struct rte_eth_dev *dev, FILE *file);
+
+#endif /* __SXE2_DUMP_H__ */
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index edcedbab45..73a92d99f8 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -38,6 +38,7 @@
 #include "sxe2_host_regs.h"
 #include "sxe2_switchdev.h"
 #include "sxe2_ioctl_chnl_func.h"
+#include "sxe2_dump.h"
 #include "sxe2_ethdev_repr.h"
 #include "sxe2vf_regs.h"
 #include "sxe2_switchdev.h"
@@ -194,6 +195,8 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 
 	.get_module_info            = sxe2_get_module_info,
 	.get_module_eeprom          = sxe2_get_module_eeprom,
+
+	.eth_dev_priv_dump          = sxe2_eth_dev_priv_dump,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
diff --git a/drivers/net/sxe2/sxe2_ethdev_repr.c b/drivers/net/sxe2/sxe2_ethdev_repr.c
index a43991c379..faac1b2701 100644
--- a/drivers/net/sxe2/sxe2_ethdev_repr.c
+++ b/drivers/net/sxe2/sxe2_ethdev_repr.c
@@ -12,6 +12,7 @@
 #include "sxe2_switchdev.h"
 #include "sxe2_ptype.h"
 #include "sxe2_mp.h"
+#include "sxe2_dump.h"
 #include "sxe2_stats.h"
 #include "sxe2_flow.h"
 
@@ -237,6 +238,8 @@ static const struct eth_dev_ops sxe2_switchdev_repr_dev_ops = {
 	.allmulticast_enable        = sxe2_repr_allmulti_enable,
 	.allmulticast_disable       = sxe2_repr_allmulti_disable,
 
+	.eth_dev_priv_dump          = sxe2_eth_dev_priv_dump,
+
 	.stats_get                  = sxe2_stats_info_get,
 	.stats_reset                = sxe2_stats_info_reset,
 	.xstats_get                 = sxe2_xstats_info_get,
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 18/23] net/sxe2: add mbuf validation in Tx debug mode
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

Introduce the `sxe2_txrx_check_mbuf` helper function to validate outgoing
mbufs when `RTE_ETHDEV_DEBUG_TX` is enabled. This helps developers catch
malformed mbufs (e.g., invalid segment lengths, bad offload flags, or
unaligned buffers) before passing them to the hardware rings, avoiding
potential hardware hangs or silent packet drops.

The validation is fully wrapped inside `RTE_ETHDEV_DEBUG_TX` conditional
compilation blocks to ensure zero performance overhead in standard
production builds.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build            |   2 +
 drivers/net/sxe2/sxe2_txrx.c            |   8 +-
 drivers/net/sxe2/sxe2_txrx_check_mbuf.c | 595 ++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_txrx_check_mbuf.h |  38 ++
 4 files changed, 641 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_txrx_check_mbuf.c
 create mode 100644 drivers/net/sxe2/sxe2_txrx_check_mbuf.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index 631730bf3e..5a02b1c3d3 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -78,6 +78,8 @@ sources += files(
         'sxe2_flow_parse_pattern.c',
         'sxe2_flow_parse_engine.c',
         'sxe2_dump.c',
+        'sxe2_txrx_check_mbuf.c',
+
 )
 
 allow_internal_get_api = true
diff --git a/drivers/net/sxe2/sxe2_txrx.c b/drivers/net/sxe2/sxe2_txrx.c
index a2cea954d5..55e477db1d 100644
--- a/drivers/net/sxe2/sxe2_txrx.c
+++ b/drivers/net/sxe2/sxe2_txrx.c
@@ -13,6 +13,7 @@
 #include "sxe2_txrx_common.h"
 #include "sxe2_txrx_vec.h"
 #include "sxe2_txrx_poll.h"
+#include "sxe2_txrx_check_mbuf.h"
 #include "sxe2_ethdev.h"
 #include "sxe2_common_log.h"
 #include "sxe2_osal.h"
@@ -121,13 +122,11 @@ uint16_t sxe2_tx_pkts_prepare(void *tx_queue,
 			rte_errno = -EINVAL;
 			goto l_end;
 		}
-#ifdef RTE_ETHDEV_DEBUG_TX
 		ret = rte_validate_tx_offload(mbuf);
 		if (ret != 0) {
 			rte_errno = -ret;
 			goto l_end;
 		}
-#endif
 		ret = rte_net_intel_cksum_prepare(mbuf);
 		if (ret != 0) {
 			rte_errno = -ret;
@@ -138,6 +137,11 @@ uint16_t sxe2_tx_pkts_prepare(void *tx_queue,
 			rte_errno = -ret;
 			goto l_end;
 		}
+		ret = sxe2_txrx_check_mbuf(mbuf);
+		if (ret != 0) {
+			rte_errno = -ret;
+			goto l_end;
+		}
 	}
 l_end:
 	return i;
diff --git a/drivers/net/sxe2/sxe2_txrx_check_mbuf.c b/drivers/net/sxe2/sxe2_txrx_check_mbuf.c
new file mode 100644
index 0000000000..5a12b42120
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_txrx_check_mbuf.c
@@ -0,0 +1,595 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_common.h>
+#include <rte_net.h>
+#include <rte_vect.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <ethdev_driver.h>
+#include <rte_geneve.h>
+
+#include "sxe2_txrx_check_mbuf.h"
+#include "sxe2_common_log.h"
+
+#define TX_IPPROTO_IPIP 4
+#define TX_IPPROTO_GRE  47
+#define GRE_CHECKSUM_PRESENT 0x8000
+#define GRE_KEY_PRESENT 0x2000
+#define GRE_SEQUENCE_PRESENT 0x1000
+#define GRE_EXT_LEN 4
+#define GRE_SUPPORTED_FIELDS (GRE_CHECKSUM_PRESENT | GRE_KEY_PRESENT | GRE_SEQUENCE_PRESENT)
+
+
+static uint16_t vxlan_gpe_udp_port = RTE_VXLAN_GPE_DEFAULT_PORT;
+static uint16_t geneve_udp_port = RTE_GENEVE_DEFAULT_PORT;
+
+static inline int32_t check_mbuf_len(struct offload_info *info, struct rte_mbuf *m)
+{
+	int32_t ret = 0;
+	if (m->ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK) {
+		if (info->outer_l2_len != m->outer_l2_len) {
+			PMD_LOG_ERR(TX, "outer_l2_len error in mbuf. Original "
+				    "length:%hu calculated length:%u", m->outer_l2_len,
+				    info->outer_l2_len);
+			ret = -1;
+			goto end;
+		}
+		if (info->outer_l3_len != m->outer_l3_len) {
+			PMD_LOG_ERR(TX, "outer_l3_len error in mbuf. Original "
+				    "length:%hu calculated length:%u", m->outer_l3_len,
+				    info->outer_l3_len);
+			ret = -1;
+			goto end;
+		}
+	}
+
+	if (info->l2_len != m->l2_len) {
+		PMD_LOG_ERR(TX, "l2_len error in mbuf. Original "
+			"length:%hu calculated length:%u", m->l2_len, info->l2_len);
+		ret = -1;
+		goto end;
+	}
+	if (info->l3_len != m->l3_len) {
+		PMD_LOG_ERR(TX, "l3_len error in mbuf. Original "
+			"length:%hu calculated length:%u", m->l3_len, info->l3_len);
+		ret = -1;
+		goto end;
+	}
+	if (info->l4_len != m->l4_len) {
+		PMD_LOG_ERR(TX, "l4_len error in mbuf. Original "
+			"length:%hu calculated length:%u", m->l4_len, info->l4_len);
+		ret = -1;
+		goto end;
+	}
+	ret = 0;
+
+end:
+	return ret;
+}
+
+static inline int32_t check_ether_type(struct offload_info *info, struct rte_mbuf *m)
+{
+	int32_t ret = 0;
+
+	if (m->ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK) {
+		if (info->outer_ethertype == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
+			if (!(m->ol_flags & RTE_MBUF_F_TX_OUTER_IPV4)) {
+				PMD_LOG_ERR(TX, "Outer ethernet type is ipv4, "
+					"tx offload missing `RTE_MBUF_F_TX_OUTER_IPV4` flag");
+				ret = -1;
+				goto end;
+			}
+			if (m->ol_flags & RTE_MBUF_F_TX_OUTER_IPV6) {
+				PMD_LOG_ERR(TX, "Outer ethernet type is ipv4, tx "
+					"offload contains wrong `RTE_MBUF_F_TX_OUTER_IPV6` flag");
+				ret = -1;
+				goto end;
+			}
+		} else if (info->outer_ethertype == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6)) {
+			if (!(m->ol_flags & RTE_MBUF_F_TX_OUTER_IPV6)) {
+				PMD_LOG_ERR(TX, "Outer ethernet type is ipv6, "
+					"tx offload missing `RTE_MBUF_F_TX_OUTER_IPV6` flag");
+				ret = -1;
+				goto end;
+			}
+			if (m->ol_flags & RTE_MBUF_F_TX_OUTER_IPV4) {
+				PMD_LOG_ERR(TX, "Outer ethernet type is ipv6, tx "
+					"offload contains wrong `RTE_MBUF_F_TX_OUTER_IPV4` flag");
+				ret = -1;
+				goto end;
+			}
+		}
+	}
+
+	if (info->ethertype == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
+		if (!(m->ol_flags & RTE_MBUF_F_TX_IPV4)) {
+			PMD_LOG_ERR(TX, "Ethernet type is ipv4, tx offload "
+				"missing `RTE_MBUF_F_TX_IPV4` flag.");
+			ret = -1;
+			goto end;
+		}
+		if (m->ol_flags & RTE_MBUF_F_TX_IPV6) {
+			PMD_LOG_ERR(TX, "Ethernet type is ipv4, tx "
+				"offload contains wrong `RTE_MBUF_F_TX_IPV6` flag");
+			ret = -1;
+			goto end;
+		}
+	} else if (info->ethertype == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6)) {
+		if (!(m->ol_flags & RTE_MBUF_F_TX_IPV6)) {
+			PMD_LOG_ERR(TX, "Ethernet type is ipv6, tx offload "
+				"missing `RTE_MBUF_F_TX_IPV6` flag.");
+			ret = -1;
+			goto end;
+		}
+		if (m->ol_flags & RTE_MBUF_F_TX_IPV4) {
+			PMD_LOG_ERR(TX, "Ethernet type is ipv6, tx offload "
+				"contains wrong `RTE_MBUF_F_TX_IPV4` flag");
+			ret = -1;
+			goto end;
+		}
+	}
+	ret = 0;
+
+end:
+	return ret;
+}
+
+static inline void parse_ipv4(struct rte_ipv4_hdr *ipv4_hdr, struct offload_info *info)
+{
+	struct rte_tcp_hdr *tcp_hdr;
+
+	info->l3_len   = rte_ipv4_hdr_len(ipv4_hdr);
+	info->l4_proto = ipv4_hdr->next_proto_id;
+
+	if (info->l4_proto == IPPROTO_TCP) {
+		tcp_hdr = (struct rte_tcp_hdr *)((char *)ipv4_hdr + info->l3_len);
+		info->l4_len = (tcp_hdr->data_off & 0xf0) >> 2;
+	} else if (info->l4_proto == IPPROTO_UDP) {
+		info->l4_len = sizeof(struct rte_udp_hdr);
+	} else {
+		info->l4_len = 0;
+	}
+}
+
+static inline void parse_ipv6(struct rte_ipv6_hdr *ipv6_hdr, struct offload_info *info)
+{
+	struct rte_tcp_hdr *tcp_hdr;
+
+	info->l3_len   = sizeof(struct rte_ipv6_hdr);
+	info->l4_proto = ipv6_hdr->proto;
+
+	if (info->l4_proto == IPPROTO_TCP) {
+		tcp_hdr = (struct rte_tcp_hdr *)((char *)ipv6_hdr + info->l3_len);
+		info->l4_len = (tcp_hdr->data_off & 0xf0) >> 2;
+	} else if (info->l4_proto == IPPROTO_UDP) {
+		info->l4_len = sizeof(struct rte_udp_hdr);
+	} else {
+		info->l4_len = 0;
+	}
+}
+
+static inline void parse_ethernet(struct rte_ether_hdr *eth_hdr, struct offload_info *info)
+{
+	struct rte_ipv4_hdr *ipv4_hdr;
+	struct rte_ipv6_hdr *ipv6_hdr;
+	struct rte_vlan_hdr *vlan_hdr;
+
+	info->l2_len = sizeof(struct rte_ether_hdr);
+	info->ethertype = eth_hdr->ether_type;
+
+	while (info->ethertype == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
+		   info->ethertype == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
+		vlan_hdr = (struct rte_vlan_hdr *)
+			((char *)eth_hdr + info->l2_len);
+		info->l2_len   += sizeof(struct rte_vlan_hdr);
+		info->ethertype = vlan_hdr->eth_proto;
+	}
+
+	switch (info->ethertype) {
+	case RTE_STATIC_BSWAP16(RTE_ETHER_TYPE_IPV4):
+		ipv4_hdr = (struct rte_ipv4_hdr *)((char *)eth_hdr + info->l2_len);
+		parse_ipv4(ipv4_hdr, info);
+		break;
+	case RTE_STATIC_BSWAP16(RTE_ETHER_TYPE_IPV6):
+		ipv6_hdr = (struct rte_ipv6_hdr *)((char *)eth_hdr + info->l2_len);
+		parse_ipv6(ipv6_hdr, info);
+		break;
+	default:
+		info->l4_len = 0;
+		info->l3_len = 0;
+		info->l4_proto = 0;
+		break;
+	}
+}
+
+static inline void update_tunnel_outer(struct offload_info *info)
+{
+	info->is_tunnel       = 1;
+	info->outer_ethertype = info->ethertype;
+	info->outer_l2_len    = info->l2_len;
+	info->outer_l3_len    = info->l3_len;
+	info->outer_l4_proto  = info->l4_proto;
+}
+
+static inline void parse_gtp(struct rte_udp_hdr *udp_hdr, struct offload_info *info)
+{
+	struct rte_ipv4_hdr *ipv4_hdr;
+	struct rte_ipv6_hdr *ipv6_hdr;
+	struct rte_gtp_hdr *gtp_hdr;
+	uint8_t gtp_len = sizeof(*gtp_hdr);
+	uint8_t ip_ver;
+
+	if (udp_hdr->dst_port != rte_cpu_to_be_16(RTE_GTPC_UDP_PORT) &&
+		udp_hdr->src_port != rte_cpu_to_be_16(RTE_GTPC_UDP_PORT) &&
+		udp_hdr->dst_port != rte_cpu_to_be_16(RTE_GTPU_UDP_PORT))
+		goto end;
+
+	update_tunnel_outer(info);
+	info->l2_len = 0;
+
+	gtp_hdr = (struct rte_gtp_hdr *)((char *)udp_hdr + sizeof(*udp_hdr));
+
+	if (gtp_hdr->msg_type == 0xff) {
+		ip_ver = *(uint8_t *)((char *)udp_hdr + sizeof(*udp_hdr) + sizeof(*gtp_hdr));
+		ip_ver = (ip_ver) & 0xf0;
+
+		if (ip_ver == RTE_GTP_TYPE_IPV4) {
+			ipv4_hdr = (struct rte_ipv4_hdr *)((char *)gtp_hdr + gtp_len);
+			info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+			parse_ipv4(ipv4_hdr, info);
+		} else if (ip_ver == RTE_GTP_TYPE_IPV6) {
+			ipv6_hdr = (struct rte_ipv6_hdr *)((char *)gtp_hdr + gtp_len);
+			info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6);
+			parse_ipv6(ipv6_hdr, info);
+		}
+	} else {
+		info->ethertype = 0;
+		info->l4_len    = 0;
+		info->l3_len    = 0;
+		info->l4_proto  = 0;
+	}
+
+	info->l2_len += RTE_ETHER_GTP_HLEN;
+
+end:
+	return;
+}
+
+static inline void parse_vxlan(struct rte_udp_hdr *udp_hdr, struct offload_info *info)
+{
+	struct rte_ether_hdr *eth_hdr;
+
+	if (udp_hdr->dst_port != rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT))
+		goto end;
+
+	update_tunnel_outer(info);
+
+	eth_hdr = (struct rte_ether_hdr *)((char *)udp_hdr +
+		sizeof(struct rte_udp_hdr) + sizeof(struct rte_vxlan_hdr));
+
+	parse_ethernet(eth_hdr, info);
+	info->l2_len += RTE_ETHER_VXLAN_HLEN;
+
+end:
+	return;
+}
+
+static inline void parse_vxlan_gpe(struct rte_udp_hdr *udp_hdr, struct offload_info *info)
+{
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ipv4_hdr;
+	struct rte_ipv6_hdr *ipv6_hdr;
+	struct rte_vxlan_gpe_hdr *vxlan_gpe_hdr;
+	uint8_t vxlan_gpe_len = sizeof(*vxlan_gpe_hdr);
+
+	if (udp_hdr->dst_port != rte_cpu_to_be_16(vxlan_gpe_udp_port))
+		goto end;
+
+	vxlan_gpe_hdr = (struct rte_vxlan_gpe_hdr *)((char *)udp_hdr + sizeof(struct rte_udp_hdr));
+
+	if (!vxlan_gpe_hdr->proto || vxlan_gpe_hdr->proto == RTE_VXLAN_GPE_TYPE_IPV4) {
+		update_tunnel_outer(info);
+
+		ipv4_hdr = (struct rte_ipv4_hdr *)((char *)vxlan_gpe_hdr + vxlan_gpe_len);
+
+		parse_ipv4(ipv4_hdr, info);
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+		info->l2_len = 0;
+
+	} else if (vxlan_gpe_hdr->proto == RTE_VXLAN_GPE_TYPE_IPV6) {
+		update_tunnel_outer(info);
+
+		ipv6_hdr = (struct rte_ipv6_hdr *)((char *)vxlan_gpe_hdr + vxlan_gpe_len);
+
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6);
+		parse_ipv6(ipv6_hdr, info);
+		info->l2_len = 0;
+
+	} else if (vxlan_gpe_hdr->proto == RTE_VXLAN_GPE_TYPE_ETH) {
+		update_tunnel_outer(info);
+
+		eth_hdr = (struct rte_ether_hdr *)((char *)vxlan_gpe_hdr + vxlan_gpe_len);
+
+		parse_ethernet(eth_hdr, info);
+	} else {
+		goto end;
+	}
+
+	info->l2_len += RTE_ETHER_VXLAN_GPE_HLEN;
+
+end:
+	return;
+}
+
+static inline void parse_geneve(struct rte_udp_hdr *udp_hdr, struct offload_info *info)
+{
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ipv4_hdr;
+	struct rte_ipv6_hdr *ipv6_hdr;
+	struct rte_geneve_hdr *geneve_hdr;
+	uint16_t geneve_len;
+
+	if (udp_hdr->dst_port != rte_cpu_to_be_16(geneve_udp_port))
+		goto end;
+
+	geneve_hdr = (struct rte_geneve_hdr *)((char *)udp_hdr + sizeof(struct rte_udp_hdr));
+	geneve_len = sizeof(struct rte_geneve_hdr) + geneve_hdr->opt_len * 4;
+	if (!geneve_hdr->proto || geneve_hdr->proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
+		update_tunnel_outer(info);
+		ipv4_hdr = (struct rte_ipv4_hdr *)((char *)geneve_hdr + geneve_len);
+		parse_ipv4(ipv4_hdr, info);
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+		info->l2_len = 0;
+	} else if (geneve_hdr->proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6)) {
+		update_tunnel_outer(info);
+		ipv6_hdr = (struct rte_ipv6_hdr *)((char *)geneve_hdr + geneve_len);
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6);
+		parse_ipv6(ipv6_hdr, info);
+		info->l2_len = 0;
+
+	} else if (geneve_hdr->proto == rte_cpu_to_be_16(RTE_GENEVE_TYPE_ETH)) {
+		update_tunnel_outer(info);
+		eth_hdr = (struct rte_ether_hdr *)((char *)geneve_hdr + geneve_len);
+		parse_ethernet(eth_hdr, info);
+	} else {
+		goto end;
+	}
+
+	info->l2_len += (sizeof(struct rte_udp_hdr) + sizeof(struct rte_geneve_hdr) +
+		((struct rte_geneve_hdr *)geneve_hdr)->opt_len * 4);
+
+end:
+	return;
+}
+
+static inline void parse_gre(struct simple_gre_hdr *gre_hdr, struct offload_info *info)
+{
+	struct rte_ether_hdr *eth_hdr;
+	struct rte_ipv4_hdr *ipv4_hdr;
+	struct rte_ipv6_hdr *ipv6_hdr;
+	uint8_t gre_len = 0;
+
+	gre_len += sizeof(struct simple_gre_hdr);
+
+	if (gre_hdr->flags & rte_cpu_to_be_16(GRE_KEY_PRESENT))
+		gre_len += GRE_EXT_LEN;
+	if (gre_hdr->flags & rte_cpu_to_be_16(GRE_SEQUENCE_PRESENT))
+		gre_len += GRE_EXT_LEN;
+	if (gre_hdr->flags & rte_cpu_to_be_16(GRE_CHECKSUM_PRESENT))
+		gre_len += GRE_EXT_LEN;
+
+	if (gre_hdr->proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
+		update_tunnel_outer(info);
+
+		ipv4_hdr = (struct rte_ipv4_hdr *)((char *)gre_hdr + gre_len);
+
+		parse_ipv4(ipv4_hdr, info);
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+		info->l2_len = 0;
+
+	} else if (gre_hdr->proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6)) {
+		update_tunnel_outer(info);
+
+		ipv6_hdr = (struct rte_ipv6_hdr *)((char *)gre_hdr + gre_len);
+
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6);
+		parse_ipv6(ipv6_hdr, info);
+		info->l2_len = 0;
+
+	} else if (gre_hdr->proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_TEB)) {
+		update_tunnel_outer(info);
+
+		eth_hdr = (struct rte_ether_hdr *)((char *)gre_hdr + gre_len);
+
+		parse_ethernet(eth_hdr, info);
+	} else {
+		goto end;
+	}
+
+	info->l2_len += gre_len;
+
+end:
+	return;
+}
+
+static inline void parse_encap_ip(void *encap_ip, struct offload_info *info)
+{
+	struct rte_ipv4_hdr *ipv4_hdr = encap_ip;
+	struct rte_ipv6_hdr *ipv6_hdr = encap_ip;
+	uint8_t ip_version;
+
+	ip_version = ((ipv4_hdr->version_ihl & 0xf0) >> 4);
+
+	if (ip_version != 4 && ip_version != 6)
+		goto end;
+
+	info->is_tunnel = 1;
+	info->outer_ethertype = info->ethertype;
+	info->outer_l2_len = info->l2_len;
+	info->outer_l3_len = info->l3_len;
+
+	if (ip_version == 4) {
+		parse_ipv4(ipv4_hdr, info);
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+	} else {
+		parse_ipv6(ipv6_hdr, info);
+		info->ethertype = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV6);
+	}
+	info->l2_len = 0;
+
+end:
+	return;
+}
+
+__rte_unused int32_t sxe2_txrx_check_mbuf(struct rte_mbuf *m)
+{
+	int32_t ret = 0;
+	struct rte_ether_hdr *eth_hdr;
+	void *l3_hdr = NULL;
+	struct offload_info info = {0};
+	uint64_t ol_flags = m->ol_flags;
+	uint64_t tunnel_type = ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK;
+
+	eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);
+	parse_ethernet(eth_hdr, &info);
+	l3_hdr = (char *)eth_hdr + info.l2_len;
+	if (info.l4_proto == IPPROTO_UDP) {
+		struct rte_udp_hdr *udp_hdr;
+
+		udp_hdr = (struct rte_udp_hdr *)((char *)l3_hdr + info.l3_len);
+		if ((info.l2_len + info.l3_len + sizeof(struct rte_udp_hdr)) > m->data_len) {
+			PMD_LOG_ERR(TX, "UDP header exceeds mbuf data length");
+			ret = -1;
+			goto end;
+		}
+		parse_gtp(udp_hdr, &info);
+		if (info.is_tunnel) {
+			if (!tunnel_type) {
+				PMD_LOG_ERR(TX, "gtp tunnel packet missing tx "
+					"offload missing `RTE_MBUF_F_TX_TUNNEL_GTP` flag");
+				ret = -1;
+				goto end;
+			}
+			if (tunnel_type != RTE_MBUF_F_TX_TUNNEL_GTP) {
+				PMD_LOG_ERR(TX, "gtp tunnel packet, tx offload has wrong "
+					"`%s` flag correct is `RTE_MBUF_F_TX_TUNNEL_GTP` flag",
+				rte_get_tx_ol_flag_name(tunnel_type));
+				ret = -1;
+				goto end;
+			}
+			goto check_len;
+		}
+		parse_vxlan_gpe(udp_hdr, &info);
+		if (info.is_tunnel) {
+			if (!tunnel_type) {
+				PMD_LOG_ERR(TX, "vxlan gpe tunnel packet missing tx "
+					"offload missing `RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE` flag");
+				ret = -1;
+				goto end;
+			}
+			if (tunnel_type != RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE) {
+				PMD_LOG_ERR(TX, "vxlan gpe tunnel packet, tx offload has "
+					"wrong `%s` flag correct is `RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE` flag",
+				rte_get_tx_ol_flag_name(tunnel_type));
+				ret = -1;
+				goto end;
+			}
+			goto check_len;
+		}
+		parse_vxlan(udp_hdr, &info);
+		if (info.is_tunnel) {
+			if (!tunnel_type) {
+				PMD_LOG_ERR(TX, "vxlan tunnel packet missing tx "
+					"offload missing `RTE_MBUF_F_TX_TUNNEL_VXLAN` flag");
+				ret = -1;
+				goto end;
+			}
+			if (tunnel_type != RTE_MBUF_F_TX_TUNNEL_VXLAN) {
+				PMD_LOG_ERR(TX, "vxlan tunnel packet, tx offload has "
+					"wrong `%s` flag correct is `RTE_MBUF_F_TX_TUNNEL_VXLAN` flag",
+				rte_get_tx_ol_flag_name(tunnel_type));
+				ret = -1;
+				goto end;
+			}
+			goto check_len;
+		}
+		parse_geneve(udp_hdr, &info);
+		if (info.is_tunnel) {
+			if (!tunnel_type) {
+				PMD_LOG_ERR(TX, "geneve tunnel packet missing tx "
+					"offload missing `RTE_MBUF_F_TX_TUNNEL_GENEVE` flag");
+				ret = -1;
+				goto end;
+			}
+			if (tunnel_type != RTE_MBUF_F_TX_TUNNEL_GENEVE) {
+				PMD_LOG_ERR(TX, "geneve tunnel packet, tx offload has "
+					"wrong `%s` flag correct is `RTE_MBUF_F_TX_TUNNEL_GENEVE` flag",
+				rte_get_tx_ol_flag_name(tunnel_type));
+				ret = -1;
+				goto end;
+			}
+			goto check_len;
+		}
+
+		if (unlikely(RTE_ETH_IS_TUNNEL_PKT(m->packet_type) != 0)) {
+			PMD_LOG_ERR(TX, "Unknown tunnel packet UDP dst port:%hu",
+				    udp_hdr->dst_port);
+			ret = -1;
+			goto end;
+		}
+	} else if (info.l4_proto == TX_IPPROTO_GRE) {
+		struct simple_gre_hdr *gre_hdr;
+
+		gre_hdr = (struct simple_gre_hdr *)((char *)l3_hdr + info.l3_len);
+		parse_gre(gre_hdr, &info);
+		if (info.is_tunnel) {
+			if (!tunnel_type) {
+				PMD_LOG_ERR(TX, "gre tunnel packet missing tx "
+					"offload missing `RTE_MBUF_F_TX_TUNNEL_GRE` flag.");
+				ret = -1;
+				goto end;
+			}
+			if (tunnel_type != RTE_MBUF_F_TX_TUNNEL_GRE) {
+				PMD_LOG_ERR(TX, "gre tunnel packet, tx offload has "
+					"wrong `%s` flag, correct is `RTE_MBUF_F_TX_TUNNEL_GRE` flag",
+				rte_get_tx_ol_flag_name(tunnel_type));
+				ret = -1;
+				goto end;
+			}
+			goto check_len;
+		}
+	} else if (info.l4_proto == TX_IPPROTO_IPIP) {
+		void *encap_ip_hdr;
+
+		encap_ip_hdr = (char *)l3_hdr + info.l3_len;
+		parse_encap_ip(encap_ip_hdr, &info);
+		if (info.is_tunnel) {
+			if (!tunnel_type) {
+				PMD_LOG_ERR(TX, "Ipip tunnel packet missing tx "
+					"offload missing `RTE_MBUF_F_TX_TUNNEL_IPIP` flag");
+				ret = -1;
+				goto end;
+			}
+			if (tunnel_type != RTE_MBUF_F_TX_TUNNEL_IPIP) {
+				PMD_LOG_ERR(TX, "Ipip tunnel packet, tx offload has "
+					"wrong `%s` flag, correct is `RTE_MBUF_F_TX_TUNNEL_IPIP` flag",
+				rte_get_tx_ol_flag_name(tunnel_type));
+				ret = -1;
+				goto end;
+			}
+			goto check_len;
+		}
+	}
+
+check_len:
+	if (check_mbuf_len(&info, m) != 0) {
+		ret = -1;
+		goto end;
+	}
+	ret = check_ether_type(&info, m);
+
+end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_txrx_check_mbuf.h b/drivers/net/sxe2/sxe2_txrx_check_mbuf.h
new file mode 100644
index 0000000000..98197f85d9
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_txrx_check_mbuf.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_TXRX_CHECK_MBUF_H__
+#define __SXE2_TXRX_CHECK_MBUF_H__
+
+#include <rte_common.h>
+#include <rte_net.h>
+#include <rte_vect.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <ethdev_driver.h>
+
+struct offload_info {
+	uint16_t ethertype;
+	uint8_t  gso_enable;
+	uint16_t l2_len;
+	uint16_t l3_len;
+	uint16_t l4_len;
+	uint8_t  l4_proto;
+	uint8_t  is_tunnel;
+	uint16_t outer_ethertype;
+	uint16_t outer_l2_len;
+	uint16_t outer_l3_len;
+	uint8_t  outer_l4_proto;
+	uint16_t tso_segsz;
+	uint16_t tunnel_tso_segsz;
+	uint32_t pkt_len;
+};
+
+struct simple_gre_hdr {
+	uint16_t flags;
+	uint16_t proto;
+};
+
+__rte_unused int32_t sxe2_txrx_check_mbuf(struct rte_mbuf *m);
+#endif /* __SXE2_TXRX_CHECK_MBUF_H__ */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 15/23] common/sxe2: add shared SFP module definitions
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch adds a new shared header file 'sxe2_msg.h' which
contains definitions for SFP/SFP+ modules. This file is shared across
Firmware, Kernel driver, and DPDK PMD to ensure consistent protocol
handling.

The header includes:
- SFP EEPROM memory map offsets.
- Module type encoding definitions.

By using this shared header, the PMD can correctly identify module
capabilities and report diagnostic information in a way that is
consistent with the underlying firmware logic.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/common/sxe2/sxe2_msg.h | 118 +++++++++++++++++++++++++++++++++
 1 file changed, 118 insertions(+)
 create mode 100644 drivers/common/sxe2/sxe2_msg.h

diff --git a/drivers/common/sxe2/sxe2_msg.h b/drivers/common/sxe2/sxe2_msg.h
new file mode 100644
index 0000000000..a5270b2c13
--- /dev/null
+++ b/drivers/common/sxe2/sxe2_msg.h
@@ -0,0 +1,118 @@
+
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_MSG_H__
+#define __SXE2_MSG_H__
+
+enum sfp_type_identifier {
+	SXE2_SFP_TYPE_UNKNOW       = 0x00,
+	SXE2_SFP_TYPE_SFP          = 0x03,
+
+	SXE2_SFP_TYPE_QSFP_PLUS    = 0x0D,
+	SXE2_SFP_TYPE_QSFP28       = 0x11,
+
+	SXE2_SFP_TYPE_MAX          = 0xFF,
+};
+
+#ifndef SFP_DEFINE
+#define SFP_DEFINE
+
+#define SXE2_SFP_EEP_WR            0x1
+#define SXE2_SFP_EEP_QSFP          0x1
+
+enum sfp_bus_addr {
+	SXE2_SFP_EEP_I2C_ADDR0 = 0xA0,
+	SXE2_SFP_EEP_I2C_ADDR1 = 0xA2,
+	SXE2_SFP_EEP_I2C_ADDR_NR = 0xFFFF,
+};
+
+struct sxe2_sfp_req {
+	u8 is_wr;
+	u8 is_qsfp;
+	uint16_t bus_addr;
+	uint16_t page_cnt;
+	uint16_t offset;
+	uint16_t data_len;
+	uint16_t rvd;
+	u8 data[];
+};
+
+struct sxe2_sfp_resp {
+	u8 is_wr;
+	u8 is_qsfp;
+	uint16_t data_len;
+	u8 data[];
+};
+
+enum sfp_page_cnt {
+	SXE2_SFP_EEP_PAGE_CNT0     = 0,
+	SXE2_SFP_EEP_PAGE_CNT1,
+	SXE2_SFP_EEP_PAGE_CNT2,
+	SXE2_SFP_EEP_PAGE_CNT3,
+	SXE2_SFP_EEP_PAGE_CNT20    = 20,
+	SXE2_SFP_EEP_PAGE_CNT21    = 21,
+
+	SXE2_SFP_EEP_PAGE_CNT_NR   = 0xFFFF,
+};
+
+#define SXE2_SFP_E2P_I2C_7BIT_ADDR0             (SXE2_SFP_EEP_I2C_ADDR0 >> 1)
+#define SXE2_SFP_E2P_I2C_7BIT_ADDR1             (SXE2_SFP_EEP_I2C_ADDR1 >> 1)
+
+#define SXE2_QSFP_PAGE_OFST_START  128
+#define SXE2_SFP_EEP_OFST_MAX      255
+#define SXE2_SFP_EEP_LEN_MAX       256
+#endif
+
+#ifndef FW_STATE_DEFINE
+#define FW_STATE_DEFINE
+
+#define SXE2_FW_STATUS_MAIN_SHIF       (16)
+#define SXE2_FW_STATUS_MAIN_MASK       (0xFF0000)
+#define SXE2_FW_STATUS_SUB_MASK        (0xFFFF)
+
+enum Sxe2FwStateMain {
+	SXE2_FW_STATE_MAIN_UNDEFINED       = 0x00,
+	SXE2_FW_STATE_MAIN_INIT            = 0x10000,
+	SXE2_FW_STATE_MAIN_RUN             = 0x20000,
+	SXE2_FW_STATE_MAIN_ABNOMAL         = 0x30000,
+};
+
+enum Sxe2FwState {
+	SXE2_FW_START_STATE_UNDEFINED = SXE2_FW_STATE_MAIN_UNDEFINED,
+	SXE2_FW_START_STATE_INIT_BASE = (SXE2_FW_STATE_MAIN_INIT + 0x1),
+	SXE2_FW_START_STATE_SCAN_DEVICE = (SXE2_FW_STATE_MAIN_INIT + 0x20),
+	SXE2_FW_START_STATE_FINISHED = (SXE2_FW_STATE_MAIN_RUN + 0x0),
+	SXE2_FW_START_STATE_UPGRADE = (SXE2_FW_STATE_MAIN_RUN + 0x1),
+	SXE2_FW_START_STATE_SYNC = (SXE2_FW_STATE_MAIN_RUN + 0x2),
+	SXE2_FW_RUNNING_STATE_ABNOMAL = (SXE2_FW_STATE_MAIN_ABNOMAL + 0x1),
+	SXE2_FW_RUNNING_STATE_ABNOMAL_CORE1 = (SXE2_FW_STATE_MAIN_ABNOMAL + 0x2),
+	SXE2_FW_RUNNING_STATE_ABNOMAL_HEART = (SXE2_FW_STATE_MAIN_ABNOMAL + 0x3),
+	SXE2_FW_START_STATE_MASK = (SXE2_FW_STATUS_MAIN_MASK | SXE2_FW_STATUS_SUB_MASK),
+};
+#endif
+
+#ifndef LED_DEFINE
+#define LED_DEFINE
+
+enum sxe2_led_mode {
+	SXE2_IDENTIFY_LED_BLINK_ON   = 0,
+	SXE2_IDENTIFY_LED_BLINK_OFF,
+	SXE2_IDENTIFY_LED_ON,
+	SXE2_IDENTIFY_LED_OFF,
+	SXE2_IDENTIFY_LED_RESET,
+};
+
+
+typedef struct sxe2_led_ctrl {
+	u32 mode;
+	u32 duration;
+} sxe2_led_ctrl_s;
+
+typedef struct sxe2_led_ctrl_resp {
+	u32 ack;
+} sxe2_led_ctrl_resp_s;
+#endif
+
+#endif /* __SXE2_MSG_H__ */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 12/23] net/sxe2: add support for custom UDP tunnel ports
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch enables the configuration of custom UDP port numbers for
tunneling protocols in the SXE2 PMD.

The change includes:
- Adding a new entry in the tunnel port lookup table.
- Updating the hardware profile to recognize
  the custom UDP port as a tunnel type.
- Enabling inner header parsing for packets arriving on these ports.

This allows the Switch module to correctly apply recipes based on
inner packet fields (e.g., inner MAC or IP).

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/sxe2_cmd_chnl.c           |  96 ++++++++++
 drivers/net/sxe2/sxe2_cmd_chnl.h           |  17 ++
 drivers/net/sxe2/sxe2_drv_cmd.h            |  16 ++
 drivers/net/sxe2/sxe2_ethdev.c             | 206 ++++++++++++++++++++-
 drivers/net/sxe2/sxe2_ethdev.h             |  12 ++
 drivers/net/sxe2/sxe2_flow.c               |  54 ++++++
 drivers/net/sxe2/sxe2_flow.h               |   3 +-
 drivers/net/sxe2/sxe2_flow_define.h        |   1 +
 drivers/net/sxe2/sxe2_flow_parse_pattern.c | 113 +++++++++++
 drivers/net/sxe2/sxe2_flow_parse_pattern.h |   6 +
 drivers/net/sxe2/sxe2_txrx_poll.c          |  46 ++++-
 11 files changed, 566 insertions(+), 4 deletions(-)

diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index 6e2dd139a5..926eaee062 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -1455,6 +1455,102 @@ int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
 	return ret;
 }
 
+int32_t sxe2_drv_udp_tunnel_add(struct sxe2_adapter *adapter,
+			    enum sxe2_udp_tunnel_protocol tunnel_proto,
+			    uint16_t udp_port)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_udp_tunnel_req req = {};
+	struct sxe2_drv_cmd_params cmd = {};
+	int32_t ret = -1;
+
+	req.type = tunnel_proto;
+	req.port = udp_port;
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_UDPTUNNEL_ADD,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to add udp proto %d port %d, ret=%d",
+				tunnel_proto, udp_port, ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_udp_tunnel_del(struct sxe2_adapter *adapter,
+			    enum sxe2_udp_tunnel_protocol tunnel_proto,
+			    uint16_t udp_port)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_udp_tunnel_req req = {};
+	struct sxe2_drv_cmd_params cmd = {};
+	int32_t ret = -1;
+
+	req.type = tunnel_proto;
+	req.port = udp_port;
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_UDPTUNNEL_DEL,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to del udp proto %d port %d, ret=%d",
+				tunnel_proto, udp_port, ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_get_udp_tunnel_port(struct sxe2_adapter *adapter,
+				 enum sxe2_flow_udp_tunnel_protocol proto,
+				 uint16_t *port)
+{
+	int32_t ret = 0;
+	static const uint16_t flow_proto_to_udp_tunnel_proto[SXE2_FLOW_UDP_TUNNEL_MAX] = {
+		[SXE2_FLOW_UDP_TUNNEL_PROTOCOL_VXLAN] = SXE2_UDP_TUNNEL_PROTOCOL_VXLAN,
+		[SXE2_FLOW_UDP_TUNNEL_PROTOCOL_VXLAN_GPE] = SXE2_UDP_TUNNEL_PROTOCOL_VXLAN_GPE,
+		[SXE2_FLOW_UDP_TUNNEL_PROTOCOL_GENEVE] = SXE2_UDP_TUNNEL_PROTOCOL_GENEVE,
+		[SXE2_FLOW_UDP_TUNNEL_PROTOCOL_GTP_U] = SXE2_UDP_TUNNEL_PROTOCOL_GTP_U,
+		[SXE2_FLOW_UDP_TUNNEL_PROTOCOL_NVGRE] = SXE2_UDP_TUNNEL_PROTOCOL_NVGRE,
+	};
+	struct sxe2_udp_tunnel_cfg tunnel_config = {};
+
+	tunnel_config.protocol = flow_proto_to_udp_tunnel_proto[proto];
+	ret = sxe2_drv_udp_tunnel_get(adapter, &tunnel_config);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to get udp tunnel port, ret=%d", ret);
+		goto l_end;
+	}
+
+	*port = tunnel_config.fw_port;
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_udp_tunnel_get(struct sxe2_adapter *adapter,
+			    struct sxe2_udp_tunnel_cfg *tunnel_config)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_udp_tunnel_req req = {};
+	struct sxe2_drv_udp_tunnel_resp resp = {};
+	struct sxe2_drv_cmd_params cmd = {};
+	int32_t ret = -1;
+
+	req.type = tunnel_config->protocol;
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_UDPTUNNEL_GET,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to get udp proto %d port, ret=%d", req.type, ret);
+
+	tunnel_config->fw_port   = resp.port;
+	tunnel_config->fw_status = resp.enable;
+	tunnel_config->fw_dst_en = resp.dst;
+	tunnel_config->fw_src_en = resp.src;
+	tunnel_config->fw_used   = resp.fw_used;
+
+	return ret;
+}
+
 int32_t sxe2_drv_queue_info_get_update(struct sxe2_adapter *adapter, struct eth_queue_stats *qstats)
 {
 	struct sxe2_drv_cmd_params param = {0};
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index d505f93dc1..43f28c8304 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -67,6 +67,23 @@ int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
 
 int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set);
 
+int32_t sxe2_drv_udp_tunnel_add(struct sxe2_adapter *adapter,
+			    enum sxe2_udp_tunnel_protocol tunnel_proto,
+			    uint16_t udp_port);
+
+int32_t sxe2_drv_udp_tunnel_del(struct sxe2_adapter *adapter,
+			    enum sxe2_udp_tunnel_protocol tunnel_proto,
+			    uint16_t udp_port);
+
+int32_t sxe2_drv_udp_tunnel_get(struct sxe2_adapter *adapter,
+			    struct sxe2_udp_tunnel_cfg *tunnel_config);
+
+int32_t sxe2_drv_get_udp_tunnel_port(struct sxe2_adapter *adapter,
+				 enum sxe2_flow_udp_tunnel_protocol proto,
+				 uint16_t *port);
+
+int32_t sxe2_drv_vsi_info_get(struct sxe2_adapter *adapter, struct sxe2_vsi *vsi);
+
 int32_t sxe2_drv_vsi_info_get(struct sxe2_adapter *adapter, struct sxe2_vsi *vsi);
 
 int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter);
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index 48c012367c..5b5ddf9960 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -261,6 +261,22 @@ struct sxe2_drv_vsi_info_get_resp {
 	struct sxe2_drv_msix_caps used_msix;
 };
 
+struct sxe2_drv_udp_tunnel_req {
+	uint8_t type;
+	uint8_t rsv;
+	uint16_t port;
+};
+
+struct sxe2_drv_udp_tunnel_resp {
+	uint8_t type;
+	uint8_t enable;
+	uint8_t dst;
+	uint8_t src;
+	uint16_t port;
+	uint8_t fw_used;
+	uint8_t rsv;
+};
+
 struct sxe2_drv_link_info_resp {
 	uint32_t speed;
 	uint8_t status;
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 317101fb60..14c8f6c16d 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -40,10 +40,11 @@
 #include "sxe2_ioctl_chnl_func.h"
 #include "sxe2_ethdev_repr.h"
 #include "sxe2vf_regs.h"
+#include "sxe2_switchdev.h"
 
 #define SXE2_PCI_VENDOR_ID_1    0x1ff2
 #define SXE2_PCI_DEVICE_ID_PF_1 0x10b1
-#define SXE2_PCI_DEVICE_ID_VF_1 0x10b2
+#define SXE2_PCI_DEVICE_ID_VF_1 0x10b
 
 #define SXE2_PCI_VENDOR_ID_2    0x1d94
 #define SXE2_PCI_DEVICE_ID_PF_2 0x1260
@@ -115,6 +116,11 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev);
 static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info);
 static const uint32_t *sxe2_buffer_split_supported_hdr_ptypes_get(struct rte_eth_dev *dev
 				__rte_unused, size_t *no_of_elements __rte_unused);
+static int32_t sxe2_udp_tunnel_port_add(struct rte_eth_dev *dev,
+					struct rte_eth_udp_tunnel *tunnel_udp);
+static int32_t sxe2_udp_tunnel_port_del(struct rte_eth_dev *dev,
+					struct rte_eth_udp_tunnel *tunnel_udp);
+
 
 static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.dev_configure              = sxe2_dev_configure,
@@ -162,6 +168,9 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.rss_hash_update            = sxe2_dev_rss_hash_update,
 	.rss_hash_conf_get          = sxe2_dev_rss_hash_conf_get,
 
+	.udp_tunnel_port_add        = sxe2_udp_tunnel_port_add,
+	.udp_tunnel_port_del        = sxe2_udp_tunnel_port_del,
+
 	.flow_ops_get               = sxe2_flow_ops_get,
 	.tm_ops_get                 = sxe2_tm_ops_get,
 
@@ -226,6 +235,12 @@ static int32_t sxe2_dev_start(struct rte_eth_dev *dev)
 	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
 	PMD_INIT_FUNC_TRACE();
 
+	ret = sxe2_flow_init_udp_tunnel_port(dev);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to init udp tunnel port, ret: %d.", ret);
+		goto l_end;
+	}
+
 	ret = sxe2_queues_init(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to init queues.");
@@ -271,6 +286,188 @@ static int32_t sxe2_dev_start(struct rte_eth_dev *dev)
 	return ret;
 }
 
+static enum sxe2_udp_tunnel_protocol
+sxe2_udp_tunnel_type_rte_to_sxe2(enum rte_eth_tunnel_type rte_type)
+{
+	static enum sxe2_udp_tunnel_protocol sxe2_udp_proto_map[RTE_ETH_TUNNEL_TYPE_MAX] = {
+		[RTE_ETH_TUNNEL_TYPE_NONE] = SXE2_UDP_TUNNEL_MAX,
+		[RTE_ETH_TUNNEL_TYPE_VXLAN] = SXE2_UDP_TUNNEL_PROTOCOL_VXLAN,
+		[RTE_ETH_TUNNEL_TYPE_GENEVE] = SXE2_UDP_TUNNEL_PROTOCOL_GENEVE,
+		[RTE_ETH_TUNNEL_TYPE_TEREDO] = SXE2_UDP_TUNNEL_PROTOCOL_TEREDO,
+		[RTE_ETH_TUNNEL_TYPE_NVGRE] = SXE2_UDP_TUNNEL_PROTOCOL_NVGRE,
+		[RTE_ETH_TUNNEL_TYPE_IP_IN_GRE] = SXE2_UDP_TUNNEL_MAX,
+		[RTE_ETH_L2_TUNNEL_TYPE_E_TAG] = SXE2_UDP_TUNNEL_MAX,
+		[RTE_ETH_TUNNEL_TYPE_VXLAN_GPE] = SXE2_UDP_TUNNEL_PROTOCOL_VXLAN_GPE,
+		[RTE_ETH_TUNNEL_TYPE_ECPRI]  = SXE2_UDP_TUNNEL_PROTOCOL_ECPRI
+	};
+
+	if (rte_type >= RTE_ETH_TUNNEL_TYPE_MAX) {
+		PMD_LOG_ERR(DRV, "Invalid rte_eth_tunnel_type %d!", rte_type);
+		rte_type = RTE_ETH_TUNNEL_TYPE_NONE;
+	}
+
+	return sxe2_udp_proto_map[rte_type];
+}
+
+int32_t sxe2_udp_tunnel_port_add_common(struct sxe2_adapter *ad,
+				    enum sxe2_udp_tunnel_protocol tunnel_proto,
+				    uint16_t udp_port)
+{
+	struct sxe2_udp_tunnel_cfg *tunnel_config;
+	int32_t ret = -1;
+
+	rte_spinlock_lock(&ad->udp_tunnel_ctx.lock);
+
+	tunnel_config = &ad->udp_tunnel_ctx.tunnel_conf[tunnel_proto];
+
+	if (tunnel_config->dev_status == SXE2_UDP_TUNNEL_ENABLE) {
+		if (udp_port == tunnel_config->dev_port &&
+			tunnel_config->dev_ref_cnt < 0xFFFFU) {
+			tunnel_config->dev_ref_cnt++;
+			ret = 0;
+			goto l_unlock_end;
+		} else {
+			PMD_LOG_ERR(DRV, "Adding multiple ports to the same protocol "
+				    "is not supported!");
+			ret = -EINVAL;
+			goto l_unlock_end;
+		}
+	} else {
+		ret = sxe2_drv_udp_tunnel_add(ad, tunnel_proto, udp_port);
+		if (ret != 0)
+			goto l_unlock_end;
+
+		tunnel_config->protocol = tunnel_proto;
+		tunnel_config->dev_port = udp_port;
+		tunnel_config->dev_status  = SXE2_UDP_TUNNEL_ENABLE;
+		tunnel_config->dev_ref_cnt++;
+	}
+
+l_unlock_end:
+	rte_spinlock_unlock(&ad->udp_tunnel_ctx.lock);
+	return ret;
+}
+
+int32_t sxe2_udp_tunnel_port_del_common(struct sxe2_adapter *ad,
+				    enum sxe2_udp_tunnel_protocol tunnel_proto,
+				    uint16_t udp_port)
+{
+	struct sxe2_udp_tunnel_cfg *tunnel_config;
+	int32_t ret = -1;
+
+	rte_spinlock_lock(&ad->udp_tunnel_ctx.lock);
+	tunnel_config = &ad->udp_tunnel_ctx.tunnel_conf[tunnel_proto];
+
+	if (tunnel_config->dev_status == SXE2_UDP_TUNNEL_ENABLE &&
+		udp_port == tunnel_config->dev_port) {
+		if (tunnel_config->dev_ref_cnt > 1) {
+			tunnel_config->dev_ref_cnt--;
+			ret = 0;
+			goto l_unlock_end;
+		} else {
+			ret = sxe2_drv_udp_tunnel_del(ad, tunnel_proto, udp_port);
+			if (ret != 0)
+				goto l_unlock_end;
+
+			tunnel_config->dev_status  = SXE2_UDP_TUNNEL_DISABLE;
+			tunnel_config->dev_ref_cnt = 0;
+		}
+		goto l_unlock_end;
+	}
+
+	ret = -EINVAL;
+
+l_unlock_end:
+	rte_spinlock_unlock(&ad->udp_tunnel_ctx.lock);
+	return ret;
+}
+
+int32_t sxe2_udp_tunnel_port_get_common(struct sxe2_adapter *ad,
+				    struct sxe2_udp_tunnel_cfg *tunnel_config)
+{
+	return sxe2_drv_udp_tunnel_get(ad, tunnel_config);
+}
+
+static int32_t sxe2_udp_tunnel_port_clear(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *ad = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_udp_tunnel_cfg *tunnel_config;
+	int32_t ret = 0;
+	uint16_t tunnel_proto = 0;
+
+	rte_spinlock_lock(&ad->udp_tunnel_ctx.lock);
+
+	for (tunnel_proto = 0; tunnel_proto < SXE2_UDP_TUNNEL_MAX; tunnel_proto++) {
+		tunnel_config = &ad->udp_tunnel_ctx.tunnel_conf[tunnel_proto];
+		if (tunnel_config->dev_status == SXE2_UDP_TUNNEL_ENABLE) {
+			ret = sxe2_drv_udp_tunnel_del(ad, tunnel_config->protocol,
+					tunnel_config->dev_port);
+			if (ret) {
+				PMD_LOG_ERR(DRV, "Failed to delete udp tunnel port %d, proto %d",
+					    tunnel_config->dev_port, tunnel_config->protocol);
+				goto l_unlock_end;
+			}
+
+			tunnel_config->dev_status  = SXE2_UDP_TUNNEL_DISABLE;
+			tunnel_config->dev_ref_cnt = 0;
+		}
+	}
+l_unlock_end:
+	rte_spinlock_unlock(&ad->udp_tunnel_ctx.lock);
+	return ret;
+}
+
+static int32_t sxe2_udp_tunnel_port_add(struct rte_eth_dev *dev,
+			struct rte_eth_udp_tunnel *tunnel_udp)
+{
+	int32_t ret = 0;
+	enum sxe2_udp_tunnel_protocol tunnel_proto;
+	struct sxe2_adapter *ad = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (tunnel_udp->udp_port == 0) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	tunnel_proto = sxe2_udp_tunnel_type_rte_to_sxe2(tunnel_udp->prot_type);
+	if (tunnel_proto >= SXE2_UDP_TUNNEL_MAX) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_udp_tunnel_port_add_common(ad, tunnel_proto, tunnel_udp->udp_port);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Add tunnel port failed, ret = %d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_udp_tunnel_port_del(struct rte_eth_dev *dev,
+			struct rte_eth_udp_tunnel *tunnel_udp)
+{
+	int32_t ret = 0;
+	enum sxe2_udp_tunnel_protocol tunnel_proto;
+	struct sxe2_adapter *ad = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	tunnel_proto = sxe2_udp_tunnel_type_rte_to_sxe2(tunnel_udp->prot_type);
+	if (tunnel_proto >= SXE2_UDP_TUNNEL_MAX) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_udp_tunnel_port_del_common(ad, tunnel_proto, tunnel_udp->udp_port);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Delete tunnel port failed, ret = %d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev,
 			struct rte_eth_dev_info *dev_info)
 {
@@ -1300,15 +1497,20 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 	(void)sxe2_dev_stop(dev);
 	(void)sxe2_queues_release(dev);
 	sxe2_mp_uninit(dev);
-	(void)sxe2_rss_disable(dev);
 	(void)sxe2_sched_uinit(dev);
+	(void)sxe2_rss_disable(dev);
+	(void)sxe2_flow_uninit(dev);
+	(void)sxe2_udp_tunnel_port_clear(dev);
 	sxe2_vsi_uninit(dev);
 	sxe2_security_uinit(dev);
 	sxe2_intr_uninit(dev);
 	(void)sxe2_switchdev_uninit(dev);
 	sxe2_sw_uninit(dev);
+	(void)sxe2_switchdev_uninit(dev);
+	sxe2_dev_pci_map_uinit(dev);
 	sxe2_eth_uinit(dev);
 	sxe2_dev_pci_map_uinit(dev);
+	sxe2_free_repr_info(dev);
 
 l_end:
 	return 0;
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 6397a2e5c6..d6c6a152e7 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -318,6 +318,7 @@ struct sxe2_adapter {
 	struct sxe2_sched_hw_cap      sched_ctxt;
 	struct sxe2_tm_context        tm_ctxt;
 	struct sxe2_devargs           devargs;
+	struct sxe2_udp_tunnel_ctx    udp_tunnel_ctx;
 	struct sxe2_security_ctx      security_ctx;
 	struct sxe2_repr_context      repr_ctxt;
 	struct sxe2_switchdev_info    switchdev_info;
@@ -373,6 +374,17 @@ void sxe2_dev_pci_seg_unmap(struct sxe2_adapter *adapter, uint32_t res_type);
 
 int32_t sxe2_dev_pci_map_init(struct rte_eth_dev *dev);
 
+void sxe2_dev_pci_seg_unmap(struct sxe2_adapter *adapter, uint32_t res_type);
+
+int32_t sxe2_udp_tunnel_port_get_common(struct sxe2_adapter *ad,
+		struct sxe2_udp_tunnel_cfg *tunnel_config);
+
+int32_t sxe2_udp_tunnel_port_del_common(struct sxe2_adapter *ad,
+		enum sxe2_udp_tunnel_protocol tunnel_proto, uint16_t udp_port);
+
+int32_t sxe2_udp_tunnel_port_add_common(struct sxe2_adapter *ad,
+		enum sxe2_udp_tunnel_protocol tunnel_proto, uint16_t udp_port);
+
 void sxe2_dev_pci_map_uinit(struct rte_eth_dev *dev);
 
 void sxe2_eth_uinit(struct rte_eth_dev *dev);
diff --git a/drivers/net/sxe2/sxe2_flow.c b/drivers/net/sxe2/sxe2_flow.c
index 6999cb0725..63cfc36968 100644
--- a/drivers/net/sxe2/sxe2_flow.c
+++ b/drivers/net/sxe2/sxe2_flow.c
@@ -523,6 +523,51 @@ static int32_t sxe2_flow_adjust_action(struct rte_eth_dev *dev __rte_unused,
 	return ret;
 }
 
+int32_t sxe2_flow_init_udp_tunnel_port(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	uint16_t i = 0;
+	uint16_t *flow_udp_tunnel_port = NULL;
+
+	memset(adapter->flow_ctxt.tunnel_port_list, 0,
+	       sizeof(adapter->flow_ctxt.tunnel_port_list));
+
+	flow_udp_tunnel_port = adapter->flow_ctxt.tunnel_port_list;
+	for (i = 0; i < SXE2_FLOW_UDP_TUNNEL_MAX; i++) {
+		if (flow_udp_tunnel_port[i] == 0) {
+			ret = sxe2_drv_get_udp_tunnel_port(adapter, i,
+							   &flow_udp_tunnel_port[i]);
+			if (ret != 0) {
+				PMD_LOG_ERR(DRV, "Failed to get udp tunnel port, proto: %d,"
+					    "ret: %d", i, ret);
+				goto l_end;
+			}
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_flowlist_add_tunnel_port(struct rte_eth_dev *dev,
+			struct rte_flow *flow_list,
+			struct rte_flow_error *error)
+{
+	struct sxe2_flow_list_t *sxe2_flow_list = &flow_list->sxe2_flow_list;
+	struct sxe2_flow *flow = TAILQ_FIRST(sxe2_flow_list);
+	enum sxe2_flow_tunnel_type tunnel_type = flow->meta.tunnel_type;
+	DECLARE_BITMAP(flow_type, SXE2_EXPANSION_MAX);
+	sxe2_bitmap_zero(flow_type, SXE2_EXPANSION_MAX);
+	sxe2_bitmap_copy(flow_type, flow->flow_type, SXE2_EXPANSION_MAX);
+	int32_t ret = 0;
+
+	if (flow->engine_type == SXE2_FLOW_ENGINE_FNAV)
+		return sxe2_flow_add_tunnel_port(dev, error, flow, flow_type, tunnel_type);
+
+	return ret;
+}
+
 static int32_t sxe2_flow_check_item_empty(uint8_t *item, uint16_t size)
 {
 	uint16_t i = 0;
@@ -679,6 +724,10 @@ static int32_t sxe2_flow_post_proc(struct rte_eth_dev *dev,
 {
 	int32_t ret = 0;
 
+	ret = sxe2_flowlist_add_tunnel_port(dev, flow_list, error);
+	if (ret)
+		goto l_end;
+
 	ret = sxe2_flowlist_add_proto_type(dev, flow_list, error);
 	if (ret)
 		goto l_end;
@@ -1308,6 +1357,11 @@ int32_t sxe2_flow_init(struct rte_eth_dev *dev)
 
 	adapter->flow_ctxt.fnav_inited = 1;
 	rte_spinlock_init(&adapter->flow_ctxt.flow_list_lock);
+
+	ret = sxe2_flow_init_udp_tunnel_port(dev);
+	if (ret)
+		PMD_LOG_ERR(DRV, "Failed to init udp tunnel port, ret: %d.", ret);
+
 	return ret;
 }
 
diff --git a/drivers/net/sxe2/sxe2_flow.h b/drivers/net/sxe2/sxe2_flow.h
index 9970fddcf0..daaeedd4dc 100644
--- a/drivers/net/sxe2/sxe2_flow.h
+++ b/drivers/net/sxe2/sxe2_flow.h
@@ -8,7 +8,6 @@
 #include "sxe2_osal.h"
 #include "sxe2_common.h"
 
-
 int32_t sxe2_flow_ops_get(struct rte_eth_dev *dev, const struct rte_flow_ops **ops);
 
 int32_t sxe2_flow_init(struct rte_eth_dev *dev);
@@ -26,4 +25,6 @@ int32_t sxe2_flow_query_mgr(struct sxe2_adapter *adapter,
 			struct sxe2_flow *flow,
 			struct sxe2_fnav_cid_mgr **mgr_ptr,
 			struct rte_flow_error *error);
+
+int32_t sxe2_flow_init_udp_tunnel_port(struct rte_eth_dev *dev);
 #endif /* __SXE2_FLOW_H__ */
diff --git a/drivers/net/sxe2/sxe2_flow_define.h b/drivers/net/sxe2/sxe2_flow_define.h
index d2f6000efa..263a573f04 100644
--- a/drivers/net/sxe2/sxe2_flow_define.h
+++ b/drivers/net/sxe2/sxe2_flow_define.h
@@ -119,6 +119,7 @@ struct sxe2_flow_context {
 	struct rte_flow_list_t rte_flow_list;
 	rte_spinlock_t flow_list_lock;
 	struct sxe2_fnav_count_resource hw_res;
+	uint16_t tunnel_port_list[SXE2_FLOW_UDP_TUNNEL_MAX];
 	uint32_t fnav_inited;
 };
 #define SXE2_INVALID_RSS_ATTR	\
diff --git a/drivers/net/sxe2/sxe2_flow_parse_pattern.c b/drivers/net/sxe2/sxe2_flow_parse_pattern.c
index 189abb1a33..f5bf8922c6 100644
--- a/drivers/net/sxe2/sxe2_flow_parse_pattern.c
+++ b/drivers/net/sxe2/sxe2_flow_parse_pattern.c
@@ -1637,6 +1637,119 @@ static int32_t sxe2_flow_parse_pattern_vxlan_gpe(const struct rte_flow_item *ite
 	return ret;
 }
 
+static int32_t sxe2_flow_parse_pattern_ipip(struct sxe2_flow *flow, BITMAP_TYPE *flow_type)
+{
+	sxe2_set_bit(SXE2_EXPANSION_IPIP, flow_type);
+	if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV4, flow_type)) {
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_PROT, flow->pattern_outer.map_spec);
+		if (sxe2_test_bit(SXE2_EXPANSION_IPV4, flow_type))
+			flow->pattern_outer.item_spec.ipv4.protocol = SXE2_FLOW_IP_PROTOCOL_IPV4;
+		if (sxe2_test_bit(SXE2_EXPANSION_IPV6, flow_type))
+			flow->pattern_outer.item_spec.ipv4.protocol = SXE2_FLOW_IP_PROTOCOL_IPV6;
+	}
+	if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV6, flow_type)) {
+		sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PROT, flow->pattern_outer.map_spec);
+		if (sxe2_test_bit(SXE2_EXPANSION_ETH, flow_type)) {
+			flow->pattern_outer.item_spec.ipv6.nexthdr = SXE2_FLOW_IP_PROTOCOL_ETH;
+		} else {
+			if (sxe2_test_bit(SXE2_EXPANSION_IPV4, flow_type))
+				flow->pattern_outer.item_spec.ipv6.nexthdr =
+					SXE2_FLOW_IP_PROTOCOL_IPV4;
+			if (sxe2_test_bit(SXE2_EXPANSION_IPV6, flow_type))
+				flow->pattern_outer.item_spec.ipv6.nexthdr =
+					SXE2_FLOW_IP_PROTOCOL_IPV6;
+		}
+	}
+	return 0;
+}
+
+static int32_t sxe2_flow_add_udp_tunnel_port(struct sxe2_adapter *adapter,
+					 enum sxe2_flow_udp_tunnel_protocol proto,
+					 struct sxe2_flow *flow,
+					 BITMAP_TYPE *flow_type)
+{
+	int32_t ret = 0;
+	uint16_t tun_port;
+
+	tun_port = adapter->flow_ctxt.tunnel_port_list[proto];
+	if (tun_port == 0xffff || tun_port == 0) {
+		ret = -EINVAL;
+		PMD_LOG_ERR(DRV, "UDP tunnel port not initialized, proto: %d", proto);
+		goto l_end;
+	}
+	if (!sxe2_test_bit(SXE2_EXPANSION_OUTER_UDP, flow_type)) {
+		ret = -EINVAL;
+		PMD_LOG_ERR(DRV, "UDP must be over tunnel");
+		goto l_end;
+	}
+	sxe2_set_bit(SXE2_FLOW_FLD_ID_UDP_DST_PORT, flow->pattern_outer.map_spec);
+	flow->pattern_outer.item_spec.udp.dest = rte_cpu_to_be_16(tun_port);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_flow_add_tunnel_port(struct rte_eth_dev *dev,
+			struct rte_flow_error *error,
+			struct sxe2_flow *flow, BITMAP_TYPE *flow_type,
+			enum sxe2_flow_tunnel_type tunnel_type)
+{
+	int32_t ret = 0;
+	enum sxe2_flow_udp_tunnel_protocol proto = SXE2_FLOW_UDP_TUNNEL_MAX;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_flow_pattern *pattern = &flow->pattern_outer;
+	switch (tunnel_type) {
+	case SXE2_FLOW_TUNNEL_TYPE_VXLAN:
+		if (sxe2_test_bit(SXE2_EXPANSION_ETH, flow_type)) {
+			proto = SXE2_FLOW_UDP_TUNNEL_PROTOCOL_VXLAN;
+		} else if (sxe2_test_bit(SXE2_EXPANSION_IPV4, flow_type) ||
+			sxe2_test_bit(SXE2_EXPANSION_IPV6, flow_type)) {
+			proto = SXE2_FLOW_UDP_TUNNEL_PROTOCOL_VXLAN_GPE;
+		}
+		break;
+	case SXE2_FLOW_TUNNEL_TYPE_GTPU:
+		proto = SXE2_FLOW_UDP_TUNNEL_PROTOCOL_GTP_U;
+		break;
+	case SXE2_FLOW_TUNNEL_TYPE_GENEVE:
+		proto = SXE2_FLOW_UDP_TUNNEL_PROTOCOL_GENEVE;
+		break;
+	case SXE2_FLOW_TUNNEL_TYPE_GRE:
+		if (sxe2_test_bit(SXE2_EXPANSION_OUTER_UDP, flow_type)) {
+			proto = SXE2_FLOW_UDP_TUNNEL_PROTOCOL_NVGRE;
+		} else {
+			if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV4, flow_type)) {
+				pattern->item_spec.ipv4.protocol = SXE2_FLOW_IP_PROTOCOL_GRE;
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV4_PROT, pattern->map_spec);
+			}
+			if (sxe2_test_bit(SXE2_EXPANSION_OUTER_IPV6, flow_type)) {
+				pattern->item_spec.ipv6.nexthdr = SXE2_FLOW_IP_PROTOCOL_GRE;
+				sxe2_set_bit(SXE2_FLOW_FLD_ID_IPV6_PROT, pattern->map_spec);
+			}
+		}
+		break;
+	case SXE2_FLOW_TUNNEL_TYPE_IPIP:
+		ret = sxe2_flow_parse_pattern_ipip(flow, flow_type);
+		break;
+	default:
+		break;
+	}
+	if (proto != SXE2_FLOW_UDP_TUNNEL_MAX) {
+		ret = sxe2_flow_add_udp_tunnel_port(adapter, proto, flow, flow_type);
+		if (ret != 0) {
+			rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ITEM,
+					NULL, "Failed to add udp port for tunnel.");
+			PMD_LOG_ERR(DRV, "Failed to add udp port for tunnel, ret %d.", ret);
+			goto l_end;
+		}
+	}
+	if (tunnel_type != SXE2_FLOW_TUNNEL_TYPE_NONE) {
+		if (!sxe2_test_bit(SXE2_EXPANSION_OUTER_UDP, flow_type))
+			sxe2_set_bit(SXE2_FLOW_HDR_IPV_OTHER, pattern->hdrs);
+	}
+l_end:
+	return ret;
+}
+
 struct sxe2_flow_parse_pattern_ops sxe2_flow_parse_pattern_list[] = {
 	[SXE2_EXPANSION_OUTER_ETH] = {
 		.is_inner = false,
diff --git a/drivers/net/sxe2/sxe2_flow_parse_pattern.h b/drivers/net/sxe2/sxe2_flow_parse_pattern.h
index 69d83a6ea6..8442c35cae 100644
--- a/drivers/net/sxe2/sxe2_flow_parse_pattern.h
+++ b/drivers/net/sxe2/sxe2_flow_parse_pattern.h
@@ -37,4 +37,10 @@ int32_t sxe2_flow_parse_pattern(struct rte_eth_dev *dev,
 			    struct rte_flow_error *error,
 			    struct sxe2_flow *flow);
 
+int32_t sxe2_flow_add_tunnel_port(struct rte_eth_dev *dev,
+			      struct rte_flow_error *error,
+			      struct sxe2_flow *flow,
+			      BITMAP_TYPE *flow_type,
+			      enum sxe2_flow_tunnel_type tunnel_type);
+
 #endif /* SXE2_FLOW_PARSE_PATTERN_H_ */
diff --git a/drivers/net/sxe2/sxe2_txrx_poll.c b/drivers/net/sxe2/sxe2_txrx_poll.c
index f3c4fa0d91..746f9cc2d5 100644
--- a/drivers/net/sxe2/sxe2_txrx_poll.c
+++ b/drivers/net/sxe2/sxe2_txrx_poll.c
@@ -234,6 +234,44 @@ sxe2_tx_pkt_data_desc_count(struct rte_mbuf *tx_pkt)
 	return count;
 }
 
+static __rte_always_inline void sxe2_tx_desc_tunneling_params_fill(uint64_t offloads,
+					union sxe2_tx_offload_info ol_info,
+					uint32_t *desc_tunneling_params)
+{
+	if (offloads & RTE_MBUF_F_TX_OUTER_IP_CKSUM)
+		*desc_tunneling_params |= SXE2_TX_CTXT_DESC_EIPT_IPV4;
+	else if (offloads & RTE_MBUF_F_TX_OUTER_IPV4)
+		*desc_tunneling_params |= SXE2_TX_CTXT_DESC_EIPT_IPV4_NO_CSUM;
+	else if (offloads & RTE_MBUF_F_TX_OUTER_IPV6)
+		*desc_tunneling_params |= SXE2_TX_CTXT_DESC_EIPT_IPV6;
+
+	*desc_tunneling_params |=
+			SXE2_TX_CTXT_DESC_EIPLEN_VAL(ol_info.outer_l3_len);
+	switch (offloads & RTE_MBUF_F_TX_TUNNEL_MASK) {
+	case RTE_MBUF_F_TX_TUNNEL_IPIP:
+		break;
+	case RTE_MBUF_F_TX_TUNNEL_VXLAN:
+	case RTE_MBUF_F_TX_TUNNEL_VXLAN_GPE:
+	case RTE_MBUF_F_TX_TUNNEL_GTP:
+	case RTE_MBUF_F_TX_TUNNEL_GENEVE:
+		*desc_tunneling_params |= SXE2_TX_CTXT_DESC_UDP_TUNNE;
+		break;
+	case RTE_MBUF_F_TX_TUNNEL_GRE:
+		*desc_tunneling_params |= SXE2_TX_CTXT_DESC_GRE_TUNNE;
+		break;
+	default:
+		PMD_LOG_ERR(TX, "Tunnel type [0x%" PRIx64 "] is not supported.",
+			    (uint64_t)(offloads & RTE_MBUF_F_TX_TUNNEL_MASK));
+		return;
+	}
+	*desc_tunneling_params |= SXE2_TX_CTXT_DESC_NATLEN_VAL(ol_info.l2_len);
+	if (!(*desc_tunneling_params & SXE2_TX_CTXT_DESC_EIPT_NONE) &&
+			(*desc_tunneling_params & SXE2_TX_CTXT_DESC_UDP_TUNNE) &&
+			(offloads & RTE_MBUF_F_TX_OUTER_UDP_CKSUM)) {
+		*desc_tunneling_params |= SXE2_TX_CTXT_DESC_L4T_CS_MASK;
+	}
+}
+
 static __rte_always_inline void
 sxe2_tx_desc_checksum_fill(uint64_t offloads, uint32_t *desc_cmd, uint32_t *desc_offset,
 		union sxe2_tx_offload_info ol_info)
@@ -414,7 +452,13 @@ uint16_t sxe2_tx_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 			}
 		}
 
-		desc_offset |= SXE2_TX_DATA_DESC_MACLEN_VAL(ol_info.l2_len);
+		if ((offloads & RTE_MBUF_F_TX_TUNNEL_MASK) && ctxt_desc_num) {
+			desc_offset |= SXE2_TX_DATA_DESC_MACLEN_VAL(ol_info.outer_l2_len);
+			sxe2_tx_desc_tunneling_params_fill(offloads, ol_info,
+						&desc_tunneling_params);
+		} else {
+			desc_offset |= SXE2_TX_DATA_DESC_MACLEN_VAL(ol_info.l2_len);
+		}
 
 		if (offloads & SXE2_TX_OFFLOAD_CKSUM_MASK) {
 			sxe2_tx_desc_checksum_fill(offloads, &desc_cmd,
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 14/23] net/sxe2: implement get monitor address
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch implements the 'get_monitor_addr' ethdev ops in the sxe2
PMD. This interface allows the Ethernet device to provide the
address of the next expected Rx descriptor to the power management
library.

The implementation calculates the address of the next Rx descriptor
based on the receive queue's current hardware ring position and
descriptor size. Applications can then use this address with
rte_power_monitor() to put the CPU into a low-power state until
new packets are written to the descriptor by the hardware.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/sxe2_ethdev.c |  2 ++
 drivers/net/sxe2/sxe2_rx.c     | 21 +++++++++++++++++++++
 drivers/net/sxe2/sxe2_rx.h     |  2 ++
 3 files changed, 25 insertions(+)

diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 215ae772b5..45e245740b 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -184,6 +184,8 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.queue_stats_mapping_set    = sxe2_queue_stats_mapping_set,
 
 	.fw_version_get             = sxe2_fw_version_string_get,
+
+	.get_monitor_addr           = sxe2_get_monitor_addr,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
diff --git a/drivers/net/sxe2/sxe2_rx.c b/drivers/net/sxe2/sxe2_rx.c
index 007192c7d8..9055363ee3 100644
--- a/drivers/net/sxe2/sxe2_rx.c
+++ b/drivers/net/sxe2/sxe2_rx.c
@@ -557,3 +557,24 @@ void __rte_cold sxe2_rxqs_all_stop(struct rte_eth_dev *dev)
 		}
 	}
 }
+
+static int32_t sxe2_monitor_callback(const uint64_t value,
+				 const uint64_t arg[RTE_POWER_MONITOR_OPAQUE_SZ] __rte_unused)
+{
+	const uint64_t dd_state = rte_cpu_to_le_64(1 << SXE2_RX_DESC_STATUS_DD_SHIFT);
+	return (value & dd_state) == dd_state ? -1 : 0;
+}
+
+int32_t sxe2_get_monitor_addr(void *rx_queue, struct rte_power_monitor_cond *pmc)
+{
+	volatile union sxe2_rx_desc *rxdp;
+	struct sxe2_rx_queue *rxq = (struct sxe2_rx_queue *)rx_queue;
+
+	rxdp = &rxq->desc_ring[rxq->processing_idx];
+
+	pmc->addr = &rxdp->wb.status_err_ptype_len;
+	pmc->fn   = sxe2_monitor_callback;
+	pmc->size = sizeof(uint16_t);
+
+	return 0;
+}
diff --git a/drivers/net/sxe2/sxe2_rx.h b/drivers/net/sxe2/sxe2_rx.h
index 1c53f7f559..05b991e233 100644
--- a/drivers/net/sxe2/sxe2_rx.h
+++ b/drivers/net/sxe2/sxe2_rx.h
@@ -29,4 +29,6 @@ int32_t __rte_cold sxe2_rxqs_all_start(struct rte_eth_dev *dev);
 
 void __rte_cold sxe2_rxqs_all_stop(struct rte_eth_dev *dev);
 
+int32_t sxe2_get_monitor_addr(void *rx_queue, struct rte_power_monitor_cond *pmc);
+
 #endif /* __SXE2_RX_H__ */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 13/23] net/sxe2: support firmware version reading
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch implements the logic to retrieve the firmware version and
Build ID from the hardware during device initialization.

The version is exposed to applications through the dev_info_get API.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/sxe2_ethdev.c | 35 +++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 14c8f6c16d..215ae772b5 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -120,7 +120,8 @@ static int32_t sxe2_udp_tunnel_port_add(struct rte_eth_dev *dev,
 					struct rte_eth_udp_tunnel *tunnel_udp);
 static int32_t sxe2_udp_tunnel_port_del(struct rte_eth_dev *dev,
 					struct rte_eth_udp_tunnel *tunnel_udp);
-
+static int32_t sxe2_fw_version_string_get(struct rte_eth_dev *dev,
+				      char *fw_version, size_t fw_size);
 
 static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.dev_configure              = sxe2_dev_configure,
@@ -181,6 +182,8 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.xstats_reset               = sxe2_stats_info_reset,
 
 	.queue_stats_mapping_set    = sxe2_queue_stats_mapping_set,
+
+	.fw_version_get             = sxe2_fw_version_string_get,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
@@ -1575,6 +1578,36 @@ static int32_t sxe2_eth_pmd_remove(struct sxe2_common_device *cdev)
 	return ret;
 }
 
+static int32_t sxe2_fw_version_string_get(struct rte_eth_dev *dev, char *fw_version, size_t fw_size)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_fw_info *fw_info = &adapter->dev_info.fw;
+	int32_t ret_len;
+	int32_t ret;
+
+	ret_len = snprintf(fw_version, fw_size,
+			   "%u.%u.%u.%u",
+			   fw_info->main_version_id,
+			   fw_info->sub_version_id,
+			   fw_info->fix_version_id,
+			   fw_info->build_id);
+
+	if (ret_len < 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret_len += 1;
+	if (fw_size < (size_t)ret_len)
+		ret = -EINVAL;
+	else
+		ret = 0;
+
+out:
+	return ret;
+}
+
 static uint16_t sxe2_switchdev_repr_id_encode_get(struct sxe2_switchdev_info *switchdev_info)
 {
 	enum rte_eth_representor_type type;
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 10/23] net/sxe2: add NEON vec Rx/Tx burst functions
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

- Implement sxe2_recv_pkts_vec_neon for bulk packet receiving.
- Implement sxe2_xmit_pkts_vec_neon for bulk packet transmission.
- Added logic to select the vectorized path based on runtime config
  and CPU flags (RTE_ARCH_ARM64).

Vectorized path improves throughput for small packets by processing
multiple descriptors simultaneously using SIMD instructions.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build            |   2 +
 drivers/net/sxe2/sxe2_txrx.c            |  36 ++
 drivers/net/sxe2/sxe2_txrx_vec.h        |  16 +-
 drivers/net/sxe2/sxe2_txrx_vec_common.h |   1 -
 drivers/net/sxe2/sxe2_txrx_vec_neon.c   | 707 ++++++++++++++++++++++++
 5 files changed, 759 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_txrx_vec_neon.c

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index c73e13bbad..0658b2ee3a 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -48,6 +48,8 @@ if arch_subdir == 'x86'
                 include_directories: includes,
                 c_args: [cflags, '-mavx2'])
         objs += sxe2_avx2_lib.extract_objects('sxe2_txrx_vec_avx2.c')
+elif arch_subdir == 'arm'
+        sources += files('sxe2_txrx_vec_neon.c')
 endif
 
 sources += files(
diff --git a/drivers/net/sxe2/sxe2_txrx.c b/drivers/net/sxe2/sxe2_txrx.c
index dcfaf7278d..2eb8365457 100644
--- a/drivers/net/sxe2/sxe2_txrx.c
+++ b/drivers/net/sxe2/sxe2_txrx.c
@@ -176,6 +176,10 @@ void sxe2_tx_mode_func_set(struct rte_eth_dev *dev)
 
 			if ((0 == (tx_mode_flags & SXE2_TX_MODE_VEC_SET_MASK)))
 				tx_mode_flags |=  SXE2_TX_MODE_VEC_SSE;
+#elif defined(RTE_ARCH_ARM64)
+			if (rte_cpu_get_flag_enabled(RTE_CPUFLAG_NEON) == 1) {
+				tx_mode_flags |= (vec_flags | SXE2_TX_MODE_VEC_NEON);
+			}
 #endif
 			if (tx_mode_flags & SXE2_TX_MODE_VEC_SET_MASK) {
 				ret = sxe2_tx_queues_vec_prepare(dev);
@@ -228,6 +232,13 @@ void sxe2_tx_mode_func_set(struct rte_eth_dev *dev)
 				dev->tx_pkt_burst = sxe2_tx_pkts_vec_sse_simple;
 			}
 		}
+#elif defined(RTE_ARCH_ARM64)
+		if (adapter->tx_mode_flags & SXE2_TX_MODE_VEC_NEON) {
+			dev->tx_pkt_prepare = sxe2_tx_pkts_prepare;
+			dev->tx_pkt_burst = sxe2_tx_pkts_vec_neon;
+		} else {
+			dev->tx_pkt_burst = sxe2_tx_pkts_vec_neon_simple;
+		}
 	} else {
 #endif
 		if (tx_mode_flags & SXE2_TX_MODE_SIMPLE_BATCH) {
@@ -263,6 +274,12 @@ static const struct {
 	{ sxe2_tx_pkts_vec_sse_simple,
 	      "Vector SSE Simple" },
 #endif
+#ifdef RTE_ARCH_ARM64
+	{ sxe2_tx_pkts_vec_neon,
+	  "Vector NEON" },
+	{ sxe2_tx_pkts_vec_neon_simple,
+	  "Vector NEON Simple" },
+#endif
 };
 
 int32_t sxe2_tx_burst_mode_get(struct rte_eth_dev *dev,
@@ -366,6 +383,11 @@ void sxe2_rx_mode_func_set(struct rte_eth_dev *dev)
 			if (((rx_mode_flags & SXE2_RX_MODE_VEC_SET_MASK) == 0) &&
 				rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_128)
 				rx_mode_flags |= SXE2_RX_MODE_VEC_SSE;
+
+#elif defined(RTE_ARCH_ARM64)
+			if (rte_cpu_get_flag_enabled(RTE_CPUFLAG_NEON) == 1) {
+				rx_mode_flags |= (vec_flags | SXE2_RX_MODE_VEC_NEON);
+			}
 #endif
 			if ((rx_mode_flags & SXE2_RX_MODE_VEC_SET_MASK) != 0) {
 				ret = sxe2_rx_queues_vec_prepare(dev);
@@ -397,6 +419,14 @@ void sxe2_rx_mode_func_set(struct rte_eth_dev *dev)
 		}
 		return;
 	}
+#elif defined(RTE_ARCH_ARM64)
+	if (rx_mode_flags & SXE2_RX_MODE_VEC_SET_MASK) {
+		if (rx_mode_flags & SXE2_RX_MODE_VEC_OFFLOAD)
+			dev->rx_pkt_burst = sxe2_rx_pkts_scattered_vec_neon_offload;
+		else
+			dev->rx_pkt_burst = sxe2_rx_pkts_scattered_vec_neon;
+		goto l_end;
+	}
 #endif
 	if (sxe2_rx_offload_en_check(dev, RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT))
 		dev->rx_pkt_burst = sxe2_rx_pkts_scattered_split;
@@ -426,6 +456,12 @@ static const struct {
 	{ sxe2_rx_pkts_scattered_vec_sse_offload,
 	      "Vector SSE Scattered" },
 #endif
+#ifdef RTE_ARCH_ARM64
+	{ sxe2_rx_pkts_scattered_vec_neon,
+	  "Vector NEON Scattered" },
+	{ sxe2_rx_pkts_scattered_vec_neon_offload,
+	  "Offload Vector NEON Scattered" },
+#endif
 };
 
 int32_t sxe2_rx_burst_mode_get(struct rte_eth_dev *dev,
diff --git a/drivers/net/sxe2/sxe2_txrx_vec.h b/drivers/net/sxe2/sxe2_txrx_vec.h
index d7a0ce6ca5..02b1743e3e 100644
--- a/drivers/net/sxe2/sxe2_txrx_vec.h
+++ b/drivers/net/sxe2/sxe2_txrx_vec.h
@@ -12,19 +12,23 @@
 #define SXE2_RX_MODE_VEC_SSE       RTE_BIT32(2)
 #define SXE2_RX_MODE_VEC_AVX2      RTE_BIT32(3)
 #define SXE2_RX_MODE_VEC_AVX512    RTE_BIT32(4)
+#define SXE2_RX_MODE_VEC_NEON      RTE_BIT32(5)
 #define SXE2_RX_MODE_BATCH_ALLOC   RTE_BIT32(10)
 #define SXE2_RX_MODE_VEC_SET_MASK	(SXE2_RX_MODE_VEC_SIMPLE | \
 			SXE2_RX_MODE_VEC_OFFLOAD | SXE2_RX_MODE_VEC_SSE | \
-			SXE2_RX_MODE_VEC_AVX2 | SXE2_RX_MODE_VEC_AVX512)
+			SXE2_RX_MODE_VEC_AVX2 | SXE2_RX_MODE_VEC_AVX512 | \
+			SXE2_RX_MODE_VEC_NEON)
 #define SXE2_TX_MODE_VEC_SIMPLE   RTE_BIT32(0)
 #define SXE2_TX_MODE_VEC_OFFLOAD  RTE_BIT32(1)
 #define SXE2_TX_MODE_VEC_SSE      RTE_BIT32(2)
 #define SXE2_TX_MODE_VEC_AVX2     RTE_BIT32(3)
 #define SXE2_TX_MODE_VEC_AVX512   RTE_BIT32(4)
+#define SXE2_TX_MODE_VEC_NEON     RTE_BIT32(5)
 #define SXE2_TX_MODE_SIMPLE_BATCH RTE_BIT32(10)
 #define SXE2_TX_MODE_VEC_SET_MASK	(SXE2_TX_MODE_VEC_SIMPLE | \
 			SXE2_TX_MODE_VEC_OFFLOAD | SXE2_TX_MODE_VEC_SSE | \
-			SXE2_TX_MODE_VEC_AVX2 | SXE2_TX_MODE_VEC_AVX512)
+			SXE2_TX_MODE_VEC_AVX2 | SXE2_TX_MODE_VEC_AVX512 | \
+			SXE2_TX_MODE_VEC_NEON)
 #define SXE2_TX_VEC_NO_SUPPORT_OFFLOAD (		  \
 			RTE_ETH_TX_OFFLOAD_MULTI_SEGS |		  \
 			RTE_ETH_TX_OFFLOAD_QINQ_INSERT |	  \
@@ -75,6 +79,14 @@ uint16_t sxe2_rx_pkts_scattered_vec_avx2(void *rx_queue,
 		struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
 uint16_t sxe2_rx_pkts_scattered_vec_avx2_offload(void *rx_queue,
 		struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
+
+#elif defined(RTE_ARCH_ARM64)
+uint16_t sxe2_rx_pkts_scattered_vec_neon(void *rx_queue, struct rte_mbuf **rx_pkts,
+					 uint16_t nb_pkts);
+uint16_t sxe2_rx_pkts_scattered_vec_neon_offload(void *rx_queue, struct rte_mbuf **rx_pkts,
+						 uint16_t nb_pkts);
+uint16_t sxe2_tx_pkts_vec_neon_simple(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
+uint16_t sxe2_tx_pkts_vec_neon(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 #endif
 int32_t __rte_cold sxe2_tx_vec_support_check(struct rte_eth_dev *dev, uint32_t *vec_flags);
 int32_t __rte_cold sxe2_tx_queues_vec_prepare(struct rte_eth_dev *dev);
diff --git a/drivers/net/sxe2/sxe2_txrx_vec_common.h b/drivers/net/sxe2/sxe2_txrx_vec_common.h
index 138b748f4a..8fce2bb7cc 100644
--- a/drivers/net/sxe2/sxe2_txrx_vec_common.h
+++ b/drivers/net/sxe2/sxe2_txrx_vec_common.h
@@ -4,7 +4,6 @@
 
 #ifndef __SXE2_TXRX_VEC_COMMON_H__
 #define __SXE2_TXRX_VEC_COMMON_H__
-#include <rte_atomic.h>
 #ifdef PCLINT
 #include "avx_stub.h"
 #endif
diff --git a/drivers/net/sxe2/sxe2_txrx_vec_neon.c b/drivers/net/sxe2/sxe2_txrx_vec_neon.c
new file mode 100644
index 0000000000..e50a0b21bf
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_txrx_vec_neon.c
@@ -0,0 +1,707 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifdef RTE_ARCH_ARM64
+#include <arm_neon.h>
+#include <rte_vect.h>
+
+#include "sxe2_txrx_vec_common.h"
+#include "sxe2_txrx_vec.h"
+#include "sxe2_common_log.h"
+
+#define PKTLEN_SHIFT     10
+#define SXE2_UINT16_BIT (CHAR_BIT * sizeof(uint16_t))
+
+static __rte_always_inline void
+sxe2_tx_desc_fill_one_neon(volatile union sxe2_tx_data_desc *desc,
+			struct rte_mbuf *pkt, uint64_t desc_cmd, bool with_offloads)
+{
+	uint64_t desc_qw1;
+	uint32_t desc_offset;
+
+	desc_qw1 = (SXE2_TX_DESC_DTYPE_DATA |
+				((uint64_t)desc_cmd) << SXE2_TX_DATA_DESC_CMD_SHIFT |
+				((uint64_t)pkt->data_len) << SXE2_TX_DATA_DESC_BUF_SZ_SHIFT);
+
+	desc_offset = SXE2_TX_DATA_DESC_MACLEN_VAL(pkt->l2_len);
+	desc_qw1 |= ((uint64_t)desc_offset) << SXE2_TX_DATA_DESC_OFFSET_SHIFT;
+	if (with_offloads)
+		sxe2_tx_desc_fill_offloads(pkt, &desc_qw1);
+
+	uint64x2_t data_desc = { rte_pktmbuf_iova(pkt), desc_qw1 };
+
+	vst1q_u64((uint64_t *)desc, data_desc);
+}
+
+static __rte_always_inline uint16_t
+sxe2_tx_pkts_vec_neon_batch(struct sxe2_tx_queue *txq, struct rte_mbuf **tx_pkts,
+			uint16_t nb_pkts, bool with_offloads)
+{
+	volatile union sxe2_tx_data_desc *desc;
+	struct sxe2_tx_buffer *buffer;
+	uint16_t next_use;
+	uint16_t res_num;
+	uint16_t tx_num;
+	uint16_t i;
+
+	if (txq->desc_free_num < txq->free_thresh)
+		(void)sxe2_tx_bufs_free_vec(txq);
+
+	nb_pkts = RTE_MIN(txq->desc_free_num, nb_pkts);
+	if (unlikely(nb_pkts == 0)) {
+		PMD_LOG_TX_DEBUG("Tx pkts neon batch: may not enough free desc, "
+				"free_desc=%u, need_tx_pkts=%u",
+				txq->desc_free_num, nb_pkts);
+		goto l_end;
+	}
+	tx_num = nb_pkts;
+
+	next_use = txq->next_use;
+	desc     = &txq->desc_ring[next_use];
+	buffer   = &txq->buffer_ring[next_use];
+
+	txq->desc_free_num -= nb_pkts;
+
+	res_num = txq->ring_depth - txq->next_use;
+
+	if (tx_num >= res_num) {
+		sxe2_tx_pkts_mbuf_fill(buffer, tx_pkts, res_num);
+
+		for (i = 0; i < res_num - 1; ++i, ++tx_pkts, ++desc) {
+			sxe2_tx_desc_fill_one_neon(desc, *tx_pkts,
+					SXE2_TX_DATA_DESC_CMD_EOP, with_offloads);
+		}
+
+		sxe2_tx_desc_fill_one_neon(desc, *tx_pkts++,
+					(SXE2_TX_DATA_DESC_CMD_EOP | SXE2_TX_DATA_DESC_CMD_RS),
+					with_offloads);
+
+		tx_num -= res_num;
+
+		next_use     = 0;
+		txq->next_rs = txq->rs_thresh - 1;
+		desc         = &txq->desc_ring[next_use];
+		buffer       = &txq->buffer_ring[next_use];
+	}
+
+	sxe2_tx_pkts_mbuf_fill(buffer, tx_pkts, tx_num);
+
+	for (i = 0; i < tx_num; ++i, ++tx_pkts, ++desc) {
+		sxe2_tx_desc_fill_one_neon(desc, *tx_pkts,
+				SXE2_TX_DATA_DESC_CMD_EOP, with_offloads);
+	}
+
+	next_use += tx_num;
+	if (next_use > txq->next_rs) {
+		txq->desc_ring[txq->next_rs].read.type_cmd_off_bsz_l2t |=
+			rte_cpu_to_le_64(SXE2_TX_DATA_DESC_CMD_RS_MASK);
+
+		txq->next_rs += txq->rs_thresh;
+	}
+	txq->next_use = next_use;
+
+	SXE2_PCI_REG_WRITE_WC(txq->tdt_reg_addr, txq->next_use);
+
+l_end:
+	return nb_pkts;
+}
+
+static __rte_always_inline uint16_t
+sxe2_tx_pkts_vec_neon_common(struct sxe2_tx_queue *txq, struct rte_mbuf **tx_pkts,
+			uint16_t nb_pkts, bool with_offloads)
+{
+	uint16_t tx_done_num = 0;
+	uint16_t tx_once_num;
+	uint16_t tx_need_num;
+
+	while (nb_pkts) {
+		tx_need_num = RTE_MIN(nb_pkts, txq->rs_thresh);
+		tx_once_num = sxe2_tx_pkts_vec_neon_batch(txq,
+					tx_pkts + tx_done_num,
+					tx_need_num, with_offloads);
+
+		nb_pkts     -= tx_once_num;
+		tx_done_num += tx_once_num;
+
+		if (tx_once_num < tx_need_num)
+			break;
+	}
+
+	PMD_LOG_TX_DEBUG("Tx pkts neon: port_id=%u, queue_id=%u, "
+			"nb_pkts=%u, tx_done_num=%u with_offloads=%u",
+			txq->port_id, txq->idx_in_pf, nb_pkts, tx_done_num, with_offloads);
+
+	SXE2_TX_STATS_CNT(txq, tx_pkts_num, tx_done_num);
+	return tx_done_num;
+}
+
+uint16_t sxe2_tx_pkts_vec_neon_simple(void *tx_queue,
+			struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	return sxe2_tx_pkts_vec_neon_common((struct sxe2_tx_queue *)tx_queue,
+				tx_pkts, nb_pkts, false);
+}
+
+uint16_t sxe2_tx_pkts_vec_neon(void *tx_queue,
+			struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	return sxe2_tx_pkts_vec_neon_common((struct sxe2_tx_queue *)tx_queue,
+				tx_pkts, nb_pkts, true);
+}
+
+static __rte_always_inline void
+sxe2_rx_desc_ptype_fill_neon(uint16x8_t staterr, struct rte_mbuf **__rte_restrict rx_pkts,
+		const uint32_t *__rte_restrict ptype_tbl)
+{
+	uint16x8_t ptype_mask = {
+		0, 0x3FFULL,
+		0, 0x3FFULL,
+		0, 0x3FFULL,
+		0, 0x3FFULL,
+	};
+	uint16x8_t ptype_all;
+
+	ptype_all = vandq_u16(staterr, ptype_mask);
+
+	rx_pkts[3]->packet_type = ptype_tbl[vgetq_lane_u16(ptype_all, 3)];
+	rx_pkts[2]->packet_type = ptype_tbl[vgetq_lane_u16(ptype_all, 7)];
+	rx_pkts[1]->packet_type = ptype_tbl[vgetq_lane_u16(ptype_all, 1)];
+	rx_pkts[0]->packet_type = ptype_tbl[vgetq_lane_u16(ptype_all, 5)];
+}
+
+static __rte_always_inline uint32x4_t
+sxe2_rx_desc_fnav_flags_neon(uint64x2_t descs_arr[4])
+{
+	uint32x4_t descs_tmp1, descs_tmp2;
+	uint32x4_t descs_fnav_vld;
+	uint32x4_t v_zeros, v_ffff, v_u32_one;
+	uint32x4_t m_flags;
+
+	const uint32x4_t fdir_flags = vdupq_n_u32(RTE_MBUF_F_RX_FDIR |
+						RTE_MBUF_F_RX_FDIR_ID);
+
+	{
+		uint32x4_t d0 = vreinterpretq_u32_u64(descs_arr[0]);
+		uint32x4_t d1 = vreinterpretq_u32_u64(descs_arr[1]);
+		uint32x4_t d2 = vreinterpretq_u32_u64(descs_arr[2]);
+		uint32x4_t d3 = vreinterpretq_u32_u64(descs_arr[3]);
+
+		descs_tmp1 = vzip1q_u32(d1, d
+
+static __rte_always_inline void
+sxe2_rx_desc_offloads_para_fill_neon(struct sxe2_rx_queue *rxq,
+			volatile union sxe2_rx_desc *desc,
+			uint64x2_t descs[4], struct rte_mbuf **rx_pkts)
+{
+	uint32x4_t desc_lo, desc_hi, flags, tmp_flags;
+	const uint64x2_t mbuf_init = {rxq->mbuf_init_value, 0};
+	uint64x2_t rearm0, rearm1, rearm2, rearm3;
+
+	const uint32x4_t desc_msk = {
+		0x00001C04, 0x00001C04, 0x00001C04, 0x00001C04};
+
+	const uint32x4_t rss_msk = {
+		0x20000000, 0x20000000, 0x20000000, 0x20000000};
+
+	const uint32x4_t vlan_msk = {
+		RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_RX_VLAN_STRIPPED,
+		RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_RX_VLAN_STRIPPED,
+		RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_RX_VLAN_STRIPPED,
+		RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_RX_VLAN_STRIPPED
+	};
+	const uint8x16_t vlan_flags = {
+		0, 0, 0, 0,
+		RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_RX_VLAN_STRIPPED, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0
+	};
+
+	const uint8x16_t rss_flags = {
+		0, 0, 0, 0,
+		RTE_MBUF_F_RX_RSS_HASH, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0
+	};
+
+	const uint32x4_t cksum_mask = {
+		RTE_MBUF_F_RX_IP_CKSUM_MASK | RTE_MBUF_F_RX_L4_CKSUM_MASK |
+		RTE_MBUF_F_RX_OUTER_L4_CKSUM_MASK | RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD,
+		RTE_MBUF_F_RX_IP_CKSUM_MASK | RTE_MBUF_F_RX_L4_CKSUM_MASK |
+		RTE_MBUF_F_RX_OUTER_L4_CKSUM_MASK | RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD,
+		RTE_MBUF_F_RX_IP_CKSUM_MASK | RTE_MBUF_F_RX_L4_CKSUM_MASK |
+		RTE_MBUF_F_RX_OUTER_L4_CKSUM_MASK | RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD,
+		RTE_MBUF_F_RX_IP_CKSUM_MASK | RTE_MBUF_F_RX_L4_CKSUM_MASK |
+		RTE_MBUF_F_RX_OUTER_L4_CKSUM_MASK | RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD,
+	};
+
+	const uint8x16_t cksum_flags = {
+		((RTE_MBUF_F_RX_L4_CKSUM_GOOD | RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+		((RTE_MBUF_F_RX_L4_CKSUM_GOOD | RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+		((RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+		((RTE_MBUF_F_RX_L4_CKSUM_BAD | RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+		((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD |
+			RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+		((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_GOOD |
+			RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+		((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD |
+			RTE_MBUF_F_RX_IP_CKSUM_GOOD) >> 1),
+		((RTE_MBUF_F_RX_OUTER_IP_CKSUM_BAD | RTE_MBUF_F_RX_L4_CKSUM_BAD |
+			RTE_MBUF_F_RX_IP_CKSUM_BAD) >> 1),
+		0, 0, 0, 0, 0, 0, 0, 0
+	};
+
+	{
+		uint32x4_t d0 = vreinterpretq_u32_u64(descs[0]);
+		uint32x4_t d1 = vreinterpretq_u32_u64(descs[1]);
+		uint32x4_t d2 = vreinterpretq_u32_u64(descs[2]);
+		uint32x4_t d3 = vreinterpretq_u32_u64(descs[3]);
+		uint64x2_t f64, t64;
+
+		flags = vzip2q_u32(d1, d0);
+		tmp_flags = vzip2q_u32(d3, d2);
+		f64 = vreinterpretq_u64_u32(flags);
+		t64 = vreinterpretq_u64_u32(tmp_flags);
+		desc_lo = vreinterpretq_u32_u64(vcombine_u64(vget_low_u64(f64),
+							     vget_low_u64(t64)));
+		desc_hi = vreinterpretq_u32_u64(vcombine_u64(vget_high_u64(f64),
+							     vget_high_u64(t64)));
+	}
+
+	desc_lo = vandq_u32(desc_lo, desc_msk);
+	desc_hi = vandq_u32(desc_hi, rss_msk);
+
+	tmp_flags = vreinterpretq_u32_u8(vqtbl1q_u8(vlan_flags,
+						vreinterpretq_u8_u32(desc_lo)));
+	flags = vandq_u32(tmp_flags, vlan_msk);
+
+	desc_lo = vshrq_n_u32(desc_lo, 10);
+	tmp_flags = vreinterpretq_u32_u8(vqtbl1q_u8(cksum_flags,
+					 vreinterpretq_u8_u32(desc_lo)));
+	tmp_flags = vshlq_n_u32(tmp_flags, 1);
+	tmp_flags = vandq_u32(tmp_flags, cksum_mask);
+	flags = vorrq_u32(flags, tmp_flags);
+
+	desc_hi = vshrq_n_u32(desc_hi, 27);
+	tmp_flags = vreinterpretq_u32_u8(vqtbl1q_u8(rss_flags,
+					 vreinterpretq_u8_u32(desc_hi)));
+	flags = vorrq_u32(flags, tmp_flags);
+
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+	if (rxq->fnav_enable) {
+		uint32x4_t tmp_fnav_flags = sxe2_rx_desc_fnav_flags_neon(descs);
+		flags = vorrq_u32(flags, tmp_fnav_flags);
+
+		rx_pkts[0]->hash.fdir.hi = desc[0].wb.fd_filter_id;
+		rx_pkts[1]->hash.fdir.hi = desc[1].wb.fd_filter_id;
+		rx_pkts[2]->hash.fdir.hi = desc[2].wb.fd_filter_id;
+		rx_pkts[3]->hash.fdir.hi = desc[3].wb.fd_filter_id;
+	}
+#endif
+
+	rearm0 = vsetq_lane_u64(vgetq_lane_u32(flags, 0), mbuf_init, 1);
+	rearm1 = vsetq_lane_u64(vgetq_lane_u32(flags, 1), mbuf_init, 1);
+	rearm2 = vsetq_lane_u64(vgetq_lane_u32(flags, 2), mbuf_init, 1);
+	rearm3 = vsetq_lane_u64(vgetq_lane_u32(flags, 3), mbuf_init, 1);
+
+	vst1q_u64((uint64_t *)&rx_pkts[0]->rearm_data, rearm0);
+	vst1q_u64((uint64_t *)&rx_pkts[1]->rearm_data, rearm1);
+	vst1q_u64((uint64_t *)&rx_pkts[2]->rearm_data, rearm2);
+	vst1q_u64((uint64_t *)&rx_pkts[3]->rearm_data, rearm3);
+}
+
+static inline void sxe2_rx_queue_rearm_neon(struct sxe2_rx_queue *rxq)
+{
+	volatile union sxe2_rx_desc *desc;
+	struct rte_mbuf **buffer;
+	struct rte_mbuf *mbuf0, *mbuf1;
+	uint64x2_t dma_addr0, dma_addr1;
+	uint64x2_t zero = vdupq_n_u64(0);
+	uint64x2_t virt_addr0, virt_addr1;
+	uint64x2_t hdr_room = vdupq_n_u64(RTE_PKTMBUF_HEADROOM);
+	int32_t ret;
+	uint16_t i;
+	uint16_t new_tail;
+
+	buffer = &rxq->buffer_ring[rxq->realloc_start];
+	desc = &rxq->desc_ring[rxq->realloc_start];
+
+	ret = rte_mempool_get_bulk(rxq->mb_pool, (void *)buffer,
+			SXE2_RX_REARM_THRESH_VEC);
+	if (ret != 0) {
+		PMD_LOG_RX_INFO("Rx mbuf vec alloc failed port_id=%u "
+				"queue_id=%u", rxq->port_id,
+				rxq->idx_in_pf);
+
+		if ((rxq->realloc_num + SXE2_RX_REARM_THRESH_VEC) >= rxq->ring_depth) {
+			for (i = 0; i < SXE2_RX_NUM_PER_LOOP_NEON; ++i) {
+				buffer[i] = &rxq->fake_mbuf;
+				vst1q_u64((uint64_t *)&desc[i].read, zero);
+			}
+		}
+
+		rxq->vsi->adapter->dev_info.dev_data->rx_mbuf_alloc_failed +=
+				SXE2_RX_REARM_THRESH_VEC;
+		goto l_end;
+	}
+
+	for (i = 0; i < SXE2_RX_REARM_THRESH_VEC; i += 2, buffer += 2) {
+		mbuf0 = buffer[0];
+		mbuf1 = buffer[1];
+#if RTE_IOVA_IN_MBUF
+		RTE_BUILD_BUG_ON(offsetof(struct rte_mbuf, buf_iova) !=
+				 offsetof(struct rte_mbuf, buf_addr) + 8);
+#endif
+		virt_addr0 = vld1q_u64((uint64_t *)&mbuf0->buf_addr);
+		virt_addr1 = vld1q_u64((uint64_t *)&mbuf1->buf_addr);
+
+#if RTE_IOVA_IN_MBUF
+		dma_addr0 = vdupq_n_u64((uint64_t)vget_high_u64(virt_addr0));
+		dma_addr1 = vdupq_n_u64((uint64_t)vget_high_u64(virt_addr1));
+#else
+		dma_addr0 = vdupq_n_u64((uint64_t)vget_low_u64(virt_addr0));
+		dma_addr1 = vdupq_n_u64((uint64_t)vget_low_u64(virt_addr1));
+#endif
+		dma_addr0 = vaddq_u64(dma_addr0, hdr_room);
+		dma_addr1 = vaddq_u64(dma_addr1, hdr_room);
+
+		vst1q_u64((uint64_t *)&desc++->read, dma_addr0);
+		vst1q_u64((uint64_t *)&desc++->read, dma_addr1);
+	}
+
+	rxq->realloc_start += SXE2_RX_REARM_THRESH_VEC;
+	if (rxq->realloc_start >= rxq->ring_depth)
+		rxq->realloc_start = 0;
+	rxq->realloc_num -= SXE2_RX_REARM_THRESH_VEC;
+
+	new_tail = (rxq->realloc_start == 0) ?
+		(rxq->ring_depth - 1) : (rxq->realloc_start - 1);
+
+	SXE2_PCI_REG_WRITE_WC(rxq->rdt_reg_addr, new_tail);
+
+l_end:
+	return;
+}
+
+static __rte_always_inline uint16_t
+sxe2_rx_pkts_common_vec_neon(struct sxe2_rx_queue *rxq, struct rte_mbuf **rx_pkts,
+		uint16_t nb_pkts, uint8_t *split_rxe_flags, uint8_t *umbcast_flags,
+		bool do_offload)
+{
+	volatile union sxe2_rx_desc *desc;
+	struct rte_mbuf **buffer;
+	uint32_t i;
+	uint16_t done_num = 0;
+	const uint32_t *ptype_tbl = rxq->vsi->adapter->ptype_tbl;
+
+	uint8x16_t rvp_shuf_mask = {
+		0xFF, 0xFF, 0xFF, 0xFF,
+		12, 13, 0xFF, 0xFF,
+		12, 13,
+		2, 3,
+		4, 5, 6, 7
+	};
+
+	uint16x8_t crc_adjust = {
+		0, 0,
+		rxq->crc_len,
+		0, rxq->crc_len,
+		0, 0, 0
+	};
+
+	desc = &rxq->desc_ring[rxq->processing_idx];
+	rte_prefetch0(desc);
+
+	nb_pkts = RTE_ALIGN_FLOOR(nb_pkts, SXE2_RX_NUM_PER_LOOP_NEON);
+
+	if (rxq->realloc_num > SXE2_RX_REARM_THRESH_VEC)
+		sxe2_rx_queue_rearm_neon(rxq);
+
+	if ((rte_le_to_cpu_64(desc->wb.status_err_ptype_len) &
+			SXE2_RX_DESC_STATUS_DD_MASK) == 0) {
+		goto l_end;
+	}
+
+	buffer = &rxq->buffer_ring[rxq->processing_idx];
+	for (i = 0; i < nb_pkts; i += SXE2_RX_NUM_PER_LOOP_NEON,
+				desc += SXE2_RX_NUM_PER_LOOP_NEON) {
+		uint64x2_t descs[SXE2_RX_NUM_PER_LOOP_NEON];
+		uint8x16_t pkt_mb1, pkt_mb2, pkt_mb3, pkt_mb4;
+		uint64x2_t mbp1, mbp2;
+		uint16x8_t staterr;
+		uint16x8_t tmp;
+		uint16_t bit_num;
+
+		descs[3] = vld1q_u64((uint64_t *)(desc + 3));
+		rte_atomic_thread_fence(rte_memory_order_acquire);
+		descs[2] = vld1q_u64((uint64_t *)(desc + 2));
+		rte_atomic_thread_fence(rte_memory_order_acquire);
+		descs[1] = vld1q_u64((uint64_t *)(desc + 1));
+		rte_atomic_thread_fence(rte_memory_order_acquire);
+		descs[0] = vld1q_u64((uint64_t *)(desc));
+
+		rte_atomic_thread_fence(rte_memory_order_acquire);
+
+		descs[3] = vld1q_lane_u64((uint64_t *)(desc + 3), descs[3], 0);
+		descs[2] = vld1q_lane_u64((uint64_t *)(desc + 2), descs[2], 0);
+		descs[1] = vld1q_lane_u64((uint64_t *)(desc + 1), descs[1], 0);
+		descs[0] = vld1q_lane_u64((uint64_t *)(desc), descs[0], 0);
+
+		mbp1 = vld1q_u64((uint64_t *)&buffer[i]);
+		mbp2 = vld1q_u64((uint64_t *)&buffer[i + 2]);
+
+		vst1q_u64((uint64_t *)&rx_pkts[i], mbp1);
+		vst1q_u64((uint64_t *)&rx_pkts[i + 2], mbp2);
+
+		if (split_rxe_flags) {
+			rte_mbuf_prefetch_part2(rx_pkts[i]);
+			rte_mbuf_prefetch_part2(rx_pkts[i + 1]);
+			rte_mbuf_prefetch_part2(rx_pkts[i + 2]);
+			rte_mbuf_prefetch_part2(rx_pkts[i + 3]);
+		}
+
+		pkt_mb4 = vqtbl1q_u8(vreinterpretq_u8_u64(descs[3]), rvp_shuf_mask);
+		pkt_mb3 = vqtbl1q_u8(vreinterpretq_u8_u64(descs[2]), rvp_shuf_mask);
+		pkt_mb2 = vqtbl1q_u8(vreinterpretq_u8_u64(descs[1]), rvp_shuf_mask);
+		pkt_mb1 = vqtbl1q_u8(vreinterpretq_u8_u64(descs[0]), rvp_shuf_mask);
+
+		if (do_offload) {
+			sxe2_rx_desc_offloads_para_fill_neon(rxq, desc, descs, &rx_pkts[i]);
+		} else {
+			const uint64x2_t mbuf_init = {
+				rxq->mbuf_init_value,
+				0,
+			};
+
+			vst1q_u64((uint64_t *)&rx_pkts[i]->rearm_data, mbuf_init);
+			vst1q_u64((uint64_t *)&rx_pkts[i + 1]->rearm_data, mbuf_init);
+			vst1q_u64((uint64_t *)&rx_pkts[i + 2]->rearm_data, mbuf_init);
+			vst1q_u64((uint64_t *)&rx_pkts[i + 3]->rearm_data, mbuf_init);
+		}
+
+		tmp = vsubq_u16(vreinterpretq_u16_u8(pkt_mb4), crc_adjust);
+		pkt_mb4 = vreinterpretq_u8_u16(tmp);
+		tmp = vsubq_u16(vreinterpretq_u16_u8(pkt_mb3), crc_adjust);
+		pkt_mb3 = vreinterpretq_u8_u16(tmp);
+		tmp = vsubq_u16(vreinterpretq_u16_u8(pkt_mb2), crc_adjust);
+		pkt_mb2 = vreinterpretq_u8_u16(tmp);
+		tmp = vsubq_u16(vreinterpretq_u16_u8(pkt_mb1), crc_adjust);
+		pkt_mb1 = vreinterpretq_u8_u16(tmp);
+
+		vst1q_u8((void *)&rx_pkts[i + 3]->rx_descriptor_fields1,
+				pkt_mb4);
+		vst1q_u8((void *)&rx_pkts[i + 2]->rx_descriptor_fields1,
+				pkt_mb3);
+		vst1q_u8((void *)&rx_pkts[i + 1]->rx_descriptor_fields1,
+				pkt_mb2);
+		vst1q_u8((void *)&rx_pkts[i]->rx_descriptor_fields1,
+				pkt_mb1);
+
+		if (likely(i + SXE2_RX_NUM_PER_LOOP_NEON < nb_pkts))
+			rte_prefetch_non_temporal(desc + SXE2_RX_NUM_PER_LOOP_NEON);
+
+		{
+			uint32x4_t d0 = vreinterpretq_u32_u64(descs[0]);
+			uint32x4_t d1 = vreinterpretq_u32_u64(descs[1]);
+			uint32x4_t d2 = vreinterpretq_u32_u64(descs[2]);
+			uint32x4_t d3 = vreinterpretq_u32_u64(descs[3]);
+			uint32x4_t sterr_tmp1 = vzip2q_u32(d1, d0);
+			uint32x4_t sterr_tmp2 = vzip2q_u32(d3, d2);
+			uint32x4_t sterr_u32 = vzip1q_u32(sterr_tmp1, sterr_tmp2);
+
+			staterr = vreinterpretq_u16_u32(sterr_u32);
+		}
+
+		sxe2_rx_desc_ptype_fill_neon(staterr, &rx_pkts[i], ptype_tbl);
+
+		if (umbcast_flags != NULL) {
+			uint32x4_t umbcast_mask = {
+				SXE2_RX_DESC_STATUS_UMBCAST_MASK, SXE2_RX_DESC_STATUS_UMBCAST_MASK,
+				SXE2_RX_DESC_STATUS_UMBCAST_MASK, SXE2_RX_DESC_STATUS_UMBCAST_MASK,
+			};
+
+			uint8x16_t umbcast_shuf_mask = {
+				0x0B, 0x03, 0x0F, 0x07,
+				0xFF, 0xFF, 0xFF, 0xFF,
+				0xFF, 0xFF, 0xFF, 0xFF,
+				0xFF, 0xFF, 0xFF, 0xFF,
+			};
+			uint8x16_t umbcast_bits =
+				vreinterpretq_u8_u32(vandq_u32(vreinterpretq_u32_u16(staterr),
+							       umbcast_mask));
+
+			umbcast_bits = vqtbl1q_u8(umbcast_bits, umbcast_shuf_mask);
+			vst1q_lane_u32((uint32_t *)umbcast_flags,
+					vreinterpretq_u32_u8(umbcast_bits), 0);
+			umbcast_flags += SXE2_RX_NUM_PER_LOOP_NEON;
+		}
+
+		if (split_rxe_flags) {
+			uint8x16_t eop_shuf_mask = {
+					0x08, 0x00, 0x0C, 0x04,
+					0xFF, 0xFF, 0xFF, 0xFF,
+					0xFF, 0xFF, 0xFF, 0xFF,
+					0xFF, 0xFF, 0xFF, 0xFF};
+			uint8x16_t eop_bits;
+			uint32x4_t rxe_mask = {
+				0x2080, 0x2080, 0x2080, 0x2080
+			};
+			uint32x4_t rxe_bits;
+			uint32x4_t eop_mask;
+
+			eop_mask = vshlq_n_u32(vdupq_n_u32(1), SXE2_RX_DESC_STATUS_EOP_SHIFT);
+			eop_bits = vandq_u8(vmvnq_u8(vreinterpretq_u8_u16(staterr)),
+					vreinterpretq_u8_u32(eop_mask));
+
+			rxe_bits = vandq_u32(vreinterpretq_u32_u16(staterr), rxe_mask);
+			rxe_bits = vshrq_n_u32(rxe_bits, 7);
+
+			eop_bits = vorrq_u8(eop_bits, vreinterpretq_u8_u32(rxe_bits));
+
+			eop_bits = vqtbl1q_u8(eop_bits, eop_shuf_mask);
+
+			vst1q_lane_u32((uint32_t *)split_rxe_flags,
+				       vreinterpretq_u32_u8(eop_bits), 0);
+			split_rxe_flags += SXE2_RX_NUM_PER_LOOP_NEON;
+
+#ifdef RTE_IOVA_IN_MBUF
+			rx_pkts[i]->next = NULL;
+			rx_pkts[i + 1]->next = NULL;
+			rx_pkts[i + 2]->next = NULL;
+			rx_pkts[i + 3]->next = NULL;
+#endif
+		}
+
+		{
+			uint32x4_t dd_mask = vdupq_n_u32(1);
+			uint32x4_t sterr_dd = vandq_u32(vreinterpretq_u32_u16(staterr), dd_mask);
+			uint16x4_t packed_lo = vmovn_u32(sterr_dd);
+			uint64_t dd64 = vget_lane_u64(vreinterpret_u64_u16(packed_lo), 0);
+
+			bit_num = (uint16_t)rte_popcount64(dd64);
+		}
+		done_num += bit_num;
+		if (likely(bit_num != SXE2_RX_NUM_PER_LOOP_NEON))
+			break;
+	}
+
+	rxq->processing_idx += done_num;
+	rxq->processing_idx &= (rxq->ring_depth - 1);
+	rxq->realloc_num    += done_num;
+
+l_end:
+	return done_num;
+}
+
+static __rte_always_inline uint16_t
+sxe2_rx_pkts_scattered_batch_vec_neon(struct sxe2_rx_queue *rxq,
+		struct rte_mbuf **rx_pkts, uint16_t nb_pkts, bool do_offload)
+{
+	const uint64_t *split_flags64;
+	uint8_t split_rxe_flags[SXE2_RX_PKTS_BURST_BATCH_NUM_VEC] = {0};
+	uint8_t umbcast_flags[SXE2_RX_PKTS_BURST_BATCH_NUM_VEC] = {0};
+	uint16_t rx_done_num;
+	uint16_t rx_pkt_done_num;
+
+	rx_pkt_done_num = 0;
+
+	if (rxq->vsi->adapter->devargs.sw_stats_en) {
+		rx_done_num = sxe2_rx_pkts_common_vec_neon((struct sxe2_rx_queue *)rxq,
+					rx_pkts, nb_pkts, split_rxe_flags, umbcast_flags,
+					do_offload);
+	} else {
+		rx_done_num = sxe2_rx_pkts_common_vec_neon((struct sxe2_rx_queue *)rxq,
+					rx_pkts, nb_pkts, split_rxe_flags, NULL,
+					do_offload);
+	}
+
+	if (rx_done_num == 0)
+		goto l_end;
+
+	if (!rxq->vsi->adapter->devargs.sw_stats_en) {
+		split_flags64 = (uint64_t *)split_rxe_flags;
+
+		if (rxq->pkt_first_seg == NULL &&
+				split_flags64[0] == 0 && split_flags64[1] == 0 &&
+				split_flags64[2] == 0 && split_flags64[3] == 0) {
+			rx_pkt_done_num = rx_done_num;
+			goto l_end;
+		}
+
+		if (rxq->pkt_first_seg == NULL) {
+			while (rx_pkt_done_num < rx_done_num &&
+					split_rxe_flags[rx_pkt_done_num] == 0) {
+				rx_pkt_done_num++;
+			}
+
+			if (rx_pkt_done_num == rx_done_num)
+				goto l_end;
+
+			rxq->pkt_first_seg = rx_pkts[rx_pkt_done_num];
+		}
+	}
+
+	rx_pkt_done_num += sxe2_rx_pkts_refactor(rxq, &rx_pkts[rx_pkt_done_num],
+			rx_done_num - rx_pkt_done_num, &split_rxe_flags[rx_pkt_done_num],
+			&umbcast_flags[rx_pkt_done_num]);
+
+l_end:
+	return rx_pkt_done_num;
+}
+
+uint16_t sxe2_rx_pkts_scattered_vec_neon_offload(void *rx_queue,
+			struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
+{
+	uint16_t done_num = 0;
+	uint16_t once_num;
+
+	while (nb_pkts > SXE2_RX_PKTS_BURST_BATCH_NUM_VEC) {
+		once_num = sxe2_rx_pkts_scattered_batch_vec_neon((struct sxe2_rx_queue *)rx_queue,
+								 rx_pkts + done_num,
+								 SXE2_RX_PKTS_BURST_BATCH_NUM_VEC,
+								 true);
+
+		done_num += once_num;
+		nb_pkts  -= once_num;
+
+		if (once_num < SXE2_RX_PKTS_BURST_BATCH_NUM_VEC)
+			goto l_end;
+	}
+
+	done_num += sxe2_rx_pkts_scattered_batch_vec_neon((struct sxe2_rx_queue *)rx_queue,
+							  rx_pkts + done_num,
+							  nb_pkts,
+							  true);
+l_end:
+	SXE2_RX_STATS_CNT(rx_queue, rx_pkts_num, done_num);
+	return done_num;
+}
+
+uint16_t sxe2_rx_pkts_scattered_vec_neon(void *rx_queue,
+			struct rte_mbuf **rx_pkts, uint16_t nb_pkts)
+{
+	uint16_t done_num = 0;
+	uint16_t once_num;
+
+	while (nb_pkts > SXE2_RX_PKTS_BURST_BATCH_NUM_VEC) {
+		once_num = sxe2_rx_pkts_scattered_batch_vec_neon((struct sxe2_rx_queue *)rx_queue,
+								 rx_pkts + done_num,
+								 SXE2_RX_PKTS_BURST_BATCH_NUM_VEC,
+								 false);
+
+		done_num += once_num;
+		nb_pkts  -= once_num;
+
+		if (once_num < SXE2_RX_PKTS_BURST_BATCH_NUM_VEC)
+			goto l_end;
+	}
+
+	done_num += sxe2_rx_pkts_scattered_batch_vec_neon((struct sxe2_rx_queue *)rx_queue,
+							  rx_pkts + done_num,
+							  nb_pkts,
+							  false);
+l_end:
+	SXE2_RX_STATS_CNT(rx_queue, rx_pkts_num, done_num);
+	return done_num;
+}
+#endif
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 05/23] drivers: support RSS feature
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

Add support for Receive Side Scaling (RSS) to distribute incoming
traffic across multiple receive queues.

- Implement rss_hash_update and rss_hash_conf_get.
- Implement reta_update and reta_query.
- Support RSS hash configuration for IPv4, IPv6, TCP and UDP.
- Default hash key is initialized during port start.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/common/sxe2/sxe2_flow_public.h | 633 +++++++++++++++++++++++++
 drivers/net/sxe2/meson.build           |   1 +
 drivers/net/sxe2/sxe2_cmd_chnl.c       | 173 +++++++
 drivers/net/sxe2/sxe2_cmd_chnl.h       |  16 +
 drivers/net/sxe2/sxe2_drv_cmd.h        |  29 ++
 drivers/net/sxe2/sxe2_ethdev.c         |  37 ++
 drivers/net/sxe2/sxe2_ethdev.h         |   8 +
 drivers/net/sxe2/sxe2_flow_define.h    | 143 ++++++
 drivers/net/sxe2/sxe2_queue.c          |  11 +
 drivers/net/sxe2/sxe2_rss.c            | 584 +++++++++++++++++++++++
 drivers/net/sxe2/sxe2_rss.h            |  81 ++++
 drivers/net/sxe2/sxe2_txrx.h           |   4 +
 drivers/net/sxe2/sxe2_txrx_poll.c      |  85 +++-
 13 files changed, 1804 insertions(+), 1 deletion(-)
 create mode 100644 drivers/common/sxe2/sxe2_flow_public.h
 create mode 100644 drivers/net/sxe2/sxe2_flow_define.h
 create mode 100644 drivers/net/sxe2/sxe2_rss.c
 create mode 100644 drivers/net/sxe2/sxe2_rss.h

diff --git a/drivers/common/sxe2/sxe2_flow_public.h b/drivers/common/sxe2/sxe2_flow_public.h
new file mode 100644
index 0000000000..32ab2a9713
--- /dev/null
+++ b/drivers/common/sxe2/sxe2_flow_public.h
@@ -0,0 +1,633 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_FLOW_PUBLIC_H__
+#define __SXE2_FLOW_PUBLIC_H__
+#include "sxe2_osal.h"
+
+enum sxe2_flow_type {
+	SXE2_FLOW_TYPE_NONE                               = 0,
+	SXE2_FLOW_MAC_PAY                                 = 1,
+	SXE2_FLOW_MAC_IPV4_FRAG_PAY                       = 22,
+	SXE2_FLOW_MAC_IPV4_PAY                            = 23,
+	SXE2_FLOW_MAC_IPV4_UDP_PAY                        = 24,
+	SXE2_FLOW_MAC_IPV4_TCP_PAY                        = 26,
+	SXE2_FLOW_MAC_IPV4_SCTP_PAY                       = 27,
+	SXE2_FLOW_MAC_IPV4_IPV4_FRAG_PAY                  = 29,
+	SXE2_FLOW_MAC_IPV4_IPV4_PAY                       = 30,
+	SXE2_FLOW_MAC_IPV4_IPV4_UDP_PAY                   = 31,
+	SXE2_FLOW_MAC_IPV4_IPV4_TCP_PAY                   = 33,
+	SXE2_FLOW_MAC_IPV4_IPV4_SCTP_PAY                  = 34,
+	SXE2_FLOW_MAC_IPV4_IPV6_FRAG_PAY                  = 36,
+	SXE2_FLOW_MAC_IPV4_IPV6_PAY                       = 37,
+	SXE2_FLOW_MAC_IPV4_IPV6_UDP_PAY                   = 38,
+	SXE2_FLOW_MAC_IPV4_IPV6_TCP_PAY                   = 40,
+	SXE2_FLOW_MAC_IPV4_IPV6_SCTP_PAY                  = 41,
+	SXE2_FLOW_MAC_IPV4_GRE_PAY                        = 43,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV4_FRAG_PAY              = 44,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV4_PAY                   = 45,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV4_UDP_PAY               = 46,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV4_TCP_PAY               = 48,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV4_SCTP_PAY              = 49,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV6_FRAG_PAY              = 51,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV6_PAY                   = 52,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV6_UDP_PAY               = 53,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV6_TCP_PAY               = 55,
+	SXE2_FLOW_MAC_IPV4_GRE_IPV6_SCTP_PAY              = 56,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_PAY                    = 58,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_FRAG_PAY          = 59,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_PAY               = 60,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_UDP_PAY           = 61,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_TCP_PAY           = 63,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV4_SCTP_PAY          = 64,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_FRAG_PAY          = 66,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_PAY               = 67,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_UDP_PAY           = 68,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_TCP_PAY           = 70,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_IPV6_SCTP_PAY          = 71,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_PAY               = 73,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_FRAG_PAY     = 74,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_PAY          = 75,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_UDP_PAY      = 76,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_TCP_PAY      = 78,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV4_SCTP_PAY     = 79,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_FRAG_PAY     = 81,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_PAY          = 82,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_UDP_PAY      = 83,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_TCP_PAY      = 85,
+	SXE2_FLOW_MAC_IPV4_GRE_MAC_VLAN_IPV6_SCTP_PAY     = 86,
+	SXE2_FLOW_MAC_IPV6_FRAG_PAY                       = 88,
+	SXE2_FLOW_MAC_IPV6_PAY                            = 89,
+	SXE2_FLOW_MAC_IPV6_UDP_PAY                        = 90,
+	SXE2_FLOW_MAC_IPV6_TCP_PAY                        = 92,
+	SXE2_FLOW_MAC_IPV6_SCTP_PAY                       = 93,
+	SXE2_FLOW_MAC_IPV6_IPV4_FRAG_PAY                  = 95,
+	SXE2_FLOW_MAC_IPV6_IPV4_PAY                       = 96,
+	SXE2_FLOW_MAC_IPV6_IPV4_UDP_PAY                   = 97,
+	SXE2_FLOW_MAC_IPV6_IPV4_TCP_PAY                   = 99,
+	SXE2_FLOW_MAC_IPV6_IPV4_SCTP_PAY                  = 100,
+	SXE2_FLOW_MAC_IPV6_IPV6_FRAG_PAY                  = 102,
+	SXE2_FLOW_MAC_IPV6_IPV6_PAY                       = 103,
+	SXE2_FLOW_MAC_IPV6_IPV6_UDP_PAY                   = 104,
+	SXE2_FLOW_MAC_IPV6_IPV6_TCP_PAY                   = 106,
+	SXE2_FLOW_MAC_IPV6_IPV6_SCTP_PAY                  = 107,
+	SXE2_FLOW_MAC_IPV6_GRE_PAY                        = 109,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV4_FRAG_PAY              = 110,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV4_PAY                   = 111,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV4_UDP_PAY               = 112,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV4_TCP_PAY               = 114,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV4_SCTP_PAY              = 115,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV6_FRAG_PAY              = 117,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV6_PAY                   = 118,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV6_UDP_PAY               = 119,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV6_TCP_PAY               = 121,
+	SXE2_FLOW_MAC_IPV6_GRE_IPV6_SCTP_PAY              = 122,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_PAY                    = 124,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_FRAG_PAY          = 125,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_PAY               = 126,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_UDP_PAY           = 127,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_TCP_PAY           = 129,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV4_SCTP_PAY          = 130,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_FRAG_PAY          = 132,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_PAY               = 133,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_UDP_PAY           = 134,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_TCP_PAY           = 136,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_IPV6_SCTP_PAY          = 137,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_PAY               = 139,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_FRAG_PAY     = 140,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_PAY          = 141,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_UDP_PAY      = 142,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_TCP_PAY      = 144,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV4_SCTP_PAY     = 145,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_FRAG_PAY     = 147,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_PAY          = 148,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_UDP_PAY      = 149,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_TCP_PAY      = 151,
+	SXE2_FLOW_MAC_IPV6_GRE_MAC_VLAN_IPV6_SCTP_PAY     = 152,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_PAY                   = 329,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_PAY                   = 330,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_FRAG_PAY         = 331,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_PAY              = 332,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_UDP_PAY          = 333,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_TCP_PAY          = 334,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV4_SCTP_PAY         = 335,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_FRAG_PAY         = 336,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_PAY              = 337,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_UDP_PAY          = 338,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_TCP_PAY          = 339,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV4_SCTP_PAY         = 340,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_FRAG_PAY         = 341,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_PAY              = 342,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_UDP_PAY          = 343,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_TCP_PAY          = 344,
+	SXE2_FLOW_MAC_IPV4_UDP_GTPU_IPV6_SCTP_PAY         = 345,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_FRAG_PAY         = 346,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_PAY              = 347,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_UDP_PAY          = 348,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_TCP_PAY          = 349,
+	SXE2_FLOW_MAC_IPV6_UDP_GTPU_IPV6_SCTP_PAY         = 350,
+	SXE2_FLOW_MAC_IPV6_MAC_PAY                        = 820,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV4_FRAG_PAY              = 821,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV4_PAY                   = 822,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV4_UDP_PAY               = 823,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV4_TCP_PAY               = 824,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV4_SCTP_PAY              = 825,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV6_FRAG_PAY              = 827,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV6_PAY                   = 828,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV6_UDP_PAY               = 829,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV6_TCP_PAY               = 830,
+	SXE2_FLOW_MAC_IPV6_MAC_IPV6_SCTP_PAY              = 831,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_PAY                   = 835,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_FRAG_PAY         = 836,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_PAY              = 837,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_UDP_PAY          = 838,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_TCP_PAY          = 839,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV4_SCTP_PAY         = 840,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_FRAG_PAY         = 842,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_PAY              = 843,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_UDP_PAY          = 844,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_TCP_PAY          = 845,
+	SXE2_FLOW_MAC_IPV6_MAC_VLAN_IPV6_SCTP_PAY         = 846,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_PAY                  = 878,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_FRAG_PAY        = 877,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_PAY             = 876,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_UDP_PAY         = 879,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_TCP_PAY         = 880,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV4_SCTP_PAY        = 875,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_FRAG_PAY        = 871,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_PAY             = 870,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_UDP_PAY         = 872,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_TCP_PAY         = 873,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_IPV6_SCTP_PAY        = 869,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_PAY                  = 891,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_FRAG_PAY        = 890,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_PAY             = 889,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_UDP_PAY         = 892,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_TCP_PAY         = 893,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV4_SCTP_PAY        = 888,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_FRAG_PAY        = 884,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_PAY             = 883,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_UDP_PAY         = 885,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_TCP_PAY         = 886,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_IPV6_SCTP_PAY        = 882,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_PAY                    = 904,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_FRAG_PAY          = 903,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_PAY               = 902,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_UDP_PAY           = 905,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_TCP_PAY           = 906,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV4_SCTP_PAY          = 901,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_FRAG_PAY          = 897,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_PAY               = 896,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_UDP_PAY           = 898,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_TCP_PAY           = 899,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_IPV6_SCTP_PAY          = 895,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_PAY                    = 917,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_FRAG_PAY          = 916,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_PAY               = 915,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_UDP_PAY           = 918,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_TCP_PAY           = 919,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV4_SCTP_PAY          = 914,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_FRAG_PAY          = 910,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_PAY               = 909,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_UDP_PAY           = 911,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_TCP_PAY           = 912,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_IPV6_SCTP_PAY          = 908,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_PAY         = 930,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_FRAG_PAY = 929,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_PAY    = 928,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_UDP_PAY = 931,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_TCP_PAY = 932,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV4_SCTP_PAY = 927,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_FRAG_PAY = 923,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_PAY    = 922,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_UDP_PAY = 924,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_TCP_PAY = 925,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_VLAN_IPV6_SCTP_PAY = 921,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_PAY         = 943,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_FRAG_PAY = 942,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_PAY    = 941,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_UDP_PAY = 944,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_TCP_PAY = 945,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV4_SCTP_PAY = 940,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_FRAG_PAY = 936,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_PAY    = 935,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_UDP_PAY = 937,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_TCP_PAY = 938,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_VLAN_IPV6_SCTP_PAY = 934,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_PAY           = 956,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_FRAG_PAY = 955,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_PAY      = 954,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_UDP_PAY  = 957,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_TCP_PAY  = 958,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV4_SCTP_PAY = 953,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_FRAG_PAY = 949,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_PAY      = 948,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_UDP_PAY  = 950,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_TCP_PAY  = 951,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_VLAN_IPV6_SCTP_PAY = 947,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_PAY           = 969,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_FRAG_PAY = 968,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_PAY      = 967,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_UDP_PAY  = 970,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_TCP_PAY  = 971,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV4_SCTP_PAY = 966,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_FRAG_PAY = 962,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_PAY      = 961,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_UDP_PAY  = 963,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_TCP_PAY  = 964,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_VLAN_IPV6_SCTP_PAY = 960,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_PAY              = 982,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_FRAG_PAY    = 981,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_PAY         = 980,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_UDP_PAY     = 983,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_TCP_PAY     = 984,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV4_SCTP_PAY    = 979,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_FRAG_PAY    = 975,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_PAY         = 974,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_UDP_PAY     = 976,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_TCP_PAY     = 977,
+	SXE2_FLOW_MAC_IPV6_UDP_VXGEN_MAC_IPV6_SCTP_PAY    = 973,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_PAY              = 995,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_FRAG_PAY    = 994,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_PAY         = 993,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_UDP_PAY     = 996,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_TCP_PAY     = 997,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV4_SCTP_PAY    = 992,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_FRAG_PAY    = 988,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_PAY         = 987,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_UDP_PAY     = 989,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_TCP_PAY     = 990,
+	SXE2_FLOW_MAC_IPV4_UDP_VXGEN_MAC_IPV6_SCTP_PAY    = 986,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_PAY                = 1008,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_FRAG_PAY      = 1007,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_PAY           = 1006,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_UDP_PAY       = 1009,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_TCP_PAY       = 1010,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV4_SCTP_PAY      = 1005,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_FRAG_PAY      = 1001,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_PAY           = 1000,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_UDP_PAY       = 1002,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_TCP_PAY       = 1003,
+	SXE2_FLOW_MAC_IPV6_UDP_GRE_MAC_IPV6_SCTP_PAY      = 999,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_PAY                = 1021,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_FRAG_PAY      = 1020,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_PAY           = 1019,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_UDP_PAY       = 1022,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_TCP_PAY       = 1023,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV4_SCTP_PAY      = 1018,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_FRAG_PAY      = 1014,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_PAY           = 1013,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_UDP_PAY       = 1015,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_TCP_PAY       = 1016,
+	SXE2_FLOW_MAC_IPV4_UDP_GRE_MAC_IPV6_SCTP_PAY      = 1012,
+	SXE2_FLOW_TYPE_MAX                                = 2048,
+};
+
+enum sxe2_rss_cfg_hdr_type {
+	SXE2_RSS_OUTER_HEADERS,
+	SXE2_RSS_INNER_HEADERS,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_GRE,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_GRE,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_GRE,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_GRE,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_VXLAN,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_VXLAN,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_GENEVE,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_GENEVE,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV4_UDP_GTPU,
+
+	SXE2_RSS_INNER_HEADERS_WITH_OUTER_IPV6_UDP_GTPU,
+	SXE2_RSS_ANY_HEADERS
+};
+
+enum sxe2_flow_hdr {
+	SXE2_FLOW_HDR_ETH = 0,
+	SXE2_FLOW_HDR_VLAN,
+	SXE2_FLOW_HDR_QINQ,
+	SXE2_FLOW_HDR_IPV4,
+	SXE2_FLOW_HDR_IPV6,
+	SXE2_FLOW_HDR_ICMP = 5,
+	SXE2_FLOW_HDR_TCP,
+	SXE2_FLOW_HDR_UDP,
+	SXE2_FLOW_HDR_SCTP,
+	SXE2_FLOW_HDR_GRE,
+	SXE2_FLOW_HDR_VXLAN = 10,
+	SXE2_FLOW_HDR_GENEVE,
+	SXE2_FLOW_HDR_GTPU,
+
+	SXE2_FLOW_HDR_IPV_FRAG,
+
+	SXE2_FLOW_HDR_IPV_OTHER,
+
+	SXE2_FLOW_HDR_ETH_NON_IP = 15,
+	SXE2_FLOW_HDR_MAX = 128,
+};
+
+enum sxe2_flow_fld_id {
+	SXE2_FLOW_FLD_ID_ETH_DA = 0,
+	SXE2_FLOW_FLD_ID_ETH_SA,
+	SXE2_FLOW_FLD_ID_S_TCI,
+	SXE2_FLOW_FLD_ID_C_TCI,
+	SXE2_FLOW_FLD_ID_S_TPID,
+	SXE2_FLOW_FLD_ID_C_TPID = 5,
+	SXE2_FLOW_FLD_ID_S_VID,
+	SXE2_FLOW_FLD_ID_C_VID,
+	SXE2_FLOW_FLD_ID_ETH_TYPE,
+
+	SXE2_FLOW_FLD_ID_IPV4_TOS,
+	SXE2_FLOW_FLD_ID_IPV6_DSCP = 10,
+	SXE2_FLOW_FLD_ID_IPV4_TTL,
+	SXE2_FLOW_FLD_ID_IPV4_PROT,
+	SXE2_FLOW_FLD_ID_IPV6_TTL,
+	SXE2_FLOW_FLD_ID_IPV6_PROT,
+	SXE2_FLOW_FLD_ID_IPV4_SA = 15,
+	SXE2_FLOW_FLD_ID_IPV4_DA,
+	SXE2_FLOW_FLD_ID_IPV6_SA,
+	SXE2_FLOW_FLD_ID_IPV6_DA,
+	SXE2_FLOW_FLD_ID_IPV4_CHKSUM,
+	SXE2_FLOW_FLD_ID_IPV4_ID = 20,
+	SXE2_FLOW_FLD_ID_IPV6_ID,
+	SXE2_FLOW_FLD_ID_IPV6_PRE32_SA,
+	SXE2_FLOW_FLD_ID_IPV6_PRE32_DA,
+	SXE2_FLOW_FLD_ID_IPV6_PRE48_SA,
+	SXE2_FLOW_FLD_ID_IPV6_PRE48_DA = 25,
+	SXE2_FLOW_FLD_ID_IPV6_PRE64_SA,
+	SXE2_FLOW_FLD_ID_IPV6_PRE64_DA,
+
+	SXE2_FLOW_FLD_ID_TCP_SRC_PORT,
+	SXE2_FLOW_FLD_ID_TCP_DST_PORT,
+	SXE2_FLOW_FLD_ID_UDP_SRC_PORT = 30,
+	SXE2_FLOW_FLD_ID_UDP_DST_PORT,
+	SXE2_FLOW_FLD_ID_SCTP_SRC_PORT,
+	SXE2_FLOW_FLD_ID_SCTP_DST_PORT,
+	SXE2_FLOW_FLD_ID_TCP_FLAGS,
+	SXE2_FLOW_FLD_ID_TCP_CHKSUM = 35,
+	SXE2_FLOW_FLD_ID_UDP_CHKSUM,
+	SXE2_FLOW_FLD_ID_SCTP_CHKSUM,
+
+	SXE2_FLOW_FLD_ID_VXLAN_VNI,
+
+	SXE2_FLOW_FLD_ID_GENEVE_VNI,
+
+	SXE2_FLOW_FLD_ID_GTPU_TEID = 40,
+
+	SXE2_FLOW_FLD_ID_NVGRE_TNI,
+
+	SXE2_FLOW_FLD_ID_MAX = 128,
+};
+
+struct sxe2_ether_hdr {
+	uint8_t dst_addr[SXE2_ETH_ALEN];
+	uint8_t src_addr[SXE2_ETH_ALEN];
+	uint16_t  ether_type;
+};
+
+struct sxe2_vlan_hdr {
+	uint16_t  type;
+	uint16_t  vlan;
+};
+
+struct sxe2_ipv4_hdr {
+	uint8_t ver_ihl;
+	uint8_t tos;
+	uint16_t  tot_len;
+	uint16_t  id;
+	uint16_t  frag_off;
+	uint8_t ttl;
+	uint8_t protocol;
+	uint16_t  check;
+	uint32_t saddr;
+	uint32_t daddr;
+};
+#define SXE2_IPV6_ADDR_LENGTH        (16)
+#define SXE2_IPV6_TC_SHIFT (20)
+#define SXE2_IPV6_TC_MASK  (0xFF)
+
+struct sxe2_ipv6_hdr {
+	uint32_t pri_ver_flow;
+	uint16_t  payload_len;
+	uint8_t nexthdr;
+	uint8_t hop_limit;
+	union {
+		uint8_t saddr[16];
+		uint16_t	saddr16[8];
+		uint32_t	saddr32[4];
+	};
+	union {
+		uint8_t daddr[16];
+		uint16_t	daddr16[8];
+		uint32_t	daddr32[4];
+	};
+};
+
+struct sxe2_tcp_hdr {
+	uint16_t  source;
+	uint16_t  dest;
+	uint32_t seq;
+	uint32_t ack_seq;
+	uint16_t  flag;
+	uint16_t  window;
+	uint16_t  check;
+	uint16_t  urg_ptr;
+};
+
+struct sxe2_udp_hdr {
+	uint16_t  source;
+	uint16_t  dest;
+	uint16_t  len;
+	uint16_t  check;
+};
+
+struct sxe2_sctp_hdr {
+	uint16_t src_port;
+	uint16_t dst_port;
+};
+
+struct sxe2_nvgre_hdr {
+	uint16_t flags;
+	uint16_t protocol;
+	uint32_t tni;
+};
+struct sxe2_geneve_hdr {
+	uint16_t flags;
+	uint16_t protocol;
+	uint32_t vni;
+};
+struct sxe2_gtpu_hdr {
+	uint8_t flag;
+	uint8_t msg_type;
+	uint16_t msg_len;
+	uint32_t teid;
+};
+struct sxe2_vxlan_hdr {
+	uint8_t flag;
+	uint8_t resvd0;
+	uint8_t resvd1;
+	uint8_t protocol;
+	uint32_t vni;
+};
+
+enum sxe2_flow_act_type {
+	SXE2_FLOW_ACTION_DROP = 0,
+	SXE2_FLOW_ACTION_TC_REDIRECT,
+	SXE2_FLOW_ACTION_TO_VSI,
+	SXE2_FLOW_ACTION_TO_VSI_LIST,
+	SXE2_FLOW_ACTION_PASSTHRU,
+	SXE2_FLOW_ACTION_QUEUE,
+	SXE2_FLOW_ACTION_Q_REGION,
+	SXE2_FLOW_ACTION_MARK,
+	SXE2_FLOW_ACTION_COUNT,
+	SXE2_FLOW_ACTION_RSS,
+	SXE2_FLOW_ACTION_MAX = 32,
+};
+
+enum sxe2_rss_hash_key_func {
+	SXE2_RSS_HASH_FUNC_TOEPLITZ     = 0,
+	SXE2_RSS_HASH_FUNC_SYM_TOEPLITZ = 1,
+	SXE2_RSS_HASH_FUNC_XOR          = 2,
+	SXE2_RSS_HASH_FUNC_JEKINS       = 3
+};
+
+struct sxe2_flow_action_rss {
+	DECLARE_BITMAP(hdr_out, SXE2_FLOW_HDR_MAX);
+	DECLARE_BITMAP(hdr_in, SXE2_FLOW_HDR_MAX);
+	DECLARE_BITMAP(fld, SXE2_FLOW_FLD_ID_MAX);
+	uint8_t is_inner;
+	uint8_t func;
+	uint8_t hdr_type;
+};
+
+struct sxe2_flow_action_queue {
+	uint16_t vsi_index;
+	uint16_t q_index;
+};
+
+struct sxe2_flow_action_queue_region {
+	uint16_t vsi_index;
+	uint16_t q_index;
+	uint8_t region;
+};
+
+struct sxe2_flow_action_passthru {
+	uint16_t vsi_index;
+};
+
+struct sxe2_flow_action_mark {
+	uint32_t mark_id;
+};
+
+#define SXE2_VSI_MAX (2048)
+struct sxe2_flow_action_vsi {
+	uint16_t vsi_index;
+};
+
+struct sxe2_flow_action_vsi_list {
+	DECLARE_BITMAP(vsi_list_map, SXE2_VSI_MAX);
+	uint16_t vsi_cnt;
+};
+
+enum sxe2_fnav_stat_ctrl_type {
+	SXE2_FNAV_STAT_ENA_NONE = 0,
+	SXE2_FNAV_STAT_ENA_PKTS,
+	SXE2_FNAV_STAT_ENA_BYTES,
+	SXE2_FNAV_STAT_ENA_ALL,
+};
+
+struct sxe2_flow_action_count {
+	uint32_t user_id;
+	uint32_t driver_id;
+	uint32_t stat_index;
+	uint32_t stat_ctrl;
+};
+
+enum sxe2_flow_engine_type {
+	SXE2_FLOW_ENGINE_ACL,
+	SXE2_FLOW_ENGINE_SWITCH,
+	SXE2_FLOW_ENGINE_FNAV,
+	SXE2_FLOW_ENGINE_RSS,
+	SXE2_FLOW_ENGINE_MAX,
+};
+
+struct sxe2_flow_item {
+	struct sxe2_ether_hdr eth;
+	struct sxe2_vlan_hdr vlan;
+	struct sxe2_vlan_hdr qinq;
+	struct sxe2_ipv4_hdr ipv4;
+	struct sxe2_ipv6_hdr ipv6;
+	struct sxe2_udp_hdr udp;
+	struct sxe2_tcp_hdr tcp;
+	struct sxe2_sctp_hdr sctp;
+	struct sxe2_gtpu_hdr gtpu;
+	struct sxe2_vxlan_hdr vxlan;
+	struct sxe2_nvgre_hdr nvgre;
+	struct sxe2_geneve_hdr geneve;
+};
+
+enum sxe2_flow_sw_direct_type {
+	SXE2_FLOW_SW_DIRECT_TX,
+	SXE2_FLOW_SW_DIRECT_RX,
+	SXE2_FLOW_SW_DIRECT_MAX,
+};
+enum sxe2_flow_sw_pattern_type {
+	SXE2_FLOW_SW_PATTERN_ONLY,
+	SXE2_FLOW_SW_PATTERN_LAST,
+	SXE2_FLOW_SW_PATTERN_FIRST,
+	SXE2_FLOW_SW_PATTERN_MAX,
+};
+
+enum sxe2_flow_tunnel_type {
+	SXE2_FLOW_TUNNEL_TYPE_NONE,
+	SXE2_FLOW_TUNNEL_TYPE_PARENT,
+	SXE2_FLOW_TUNNEL_TYPE_VXLAN,
+	SXE2_FLOW_TUNNEL_TYPE_GTPU,
+	SXE2_FLOW_TUNNEL_TYPE_GENEVE,
+	SXE2_FLOW_TUNNEL_TYPE_GRE,
+	SXE2_FLOW_TUNNEL_TYPE_IPIP,
+};
+
+struct sxe2_flow_meta {
+	uint8_t switch_pattern_dup_allow;
+	uint8_t switch_src_direct;
+	uint16_t flow_src_vsi;
+	uint16_t flow_rule_vsi;
+	uint32_t flow_prio;
+	uint16_t flow_type;
+	uint8_t tunnel_type;
+	uint8_t rsv;
+};
+
+struct sxe2_flow_pattern {
+	DECLARE_BITMAP(hdrs, SXE2_FLOW_HDR_MAX);
+	DECLARE_BITMAP(map_spec, SXE2_FLOW_FLD_ID_MAX);
+	DECLARE_BITMAP(map_mask, SXE2_FLOW_FLD_ID_MAX);
+	struct sxe2_flow_item item_spec;
+	struct sxe2_flow_item item_mask;
+	uint64_t rss_type_allow;
+};
+
+struct sxe2_flow_action {
+	DECLARE_BITMAP(act_types, SXE2_FLOW_ACTION_MAX);
+	struct sxe2_flow_action_rss rss;
+	struct sxe2_flow_action_queue queue;
+	struct sxe2_flow_action_queue_region q_region;
+	struct sxe2_flow_action_passthru passthru;
+	struct sxe2_flow_action_vsi vsi;
+	struct sxe2_flow_action_vsi_list vsi_list;
+	struct sxe2_flow_action_mark mark;
+	struct sxe2_flow_action_count count;
+};
+#endif /* __SXE2_FLOW_PUBLIC_H__ */
diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index 8ff74e5233..dfd31bfc97 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -62,6 +62,7 @@ sources += files(
         'sxe2_txrx_vec.c',
         'sxe2_mac.c',
         'sxe2_filter.c',
+        'sxe2_rss.c',
 )
 
 allow_internal_get_api = true
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index 1fa9ad718e..b997e7b044 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -541,3 +541,176 @@ int32_t sxe2_drv_vlan_filter_switch(struct sxe2_adapter *adapter, bool on)
 
 	return ret;
 }
+
+int32_t sxe2_drv_rss_key_set(struct sxe2_adapter *adapter, uint8_t *key, uint16_t key_size)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_rss_key_req *req = NULL;
+	int32_t ret = 0;
+	uint16_t buf_size = sizeof(*req) + key_size;
+
+	req = rte_zmalloc("drv_cmd_rss_key", buf_size, 0);
+	if (!req) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to alloc rss key");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	req->vsi_id = rte_cpu_to_le_16(adapter->vsi_ctxt.dpdk_vsi_id);
+	req->key_size = rte_cpu_to_le_16(key_size);
+	rte_memcpy(req->key, key, key_size);
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RSS_KEY_SET,
+		req, buf_size, NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cmd set rss key, ret=%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	if (req) {
+		rte_free(req);
+		req = NULL;
+	}
+	return ret;
+}
+
+int32_t sxe2_drv_rss_lut_set(struct sxe2_adapter *adapter, uint8_t *lut, uint16_t lut_size)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_rss_lut_req *req = NULL;
+	int32_t ret = 0;
+	uint16_t buf_size = sizeof(struct sxe2_rss_lut_req) + lut_size;
+
+	req = rte_zmalloc("drv_cmd_rss_lut", buf_size, 0);
+	if (!req) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to alloc rss lut");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	req->vsi_id = rte_cpu_to_le_16(adapter->vsi_ctxt.dpdk_vsi_id);
+	req->lut_size = rte_cpu_to_le_16(lut_size);
+	rte_memcpy(req->lut, lut, lut_size);
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RSS_LUT_SET,
+		req, buf_size, NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cmd set rss lut, ret=%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	if (req) {
+		rte_free(req);
+		req = NULL;
+	}
+	return ret;
+}
+
+int32_t sxe2_drv_rss_hash_ctrl_func(struct sxe2_adapter *adapter, enum sxe2_rss_hash_key_func func)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_rss_func_req req = {0};
+	int32_t ret = 0;
+
+	req.vsi_id = rte_cpu_to_le_16(adapter->vsi_ctxt.dpdk_vsi_id);
+	req.func = func;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RSS_FUNC_SET,
+		&req, sizeof(req), NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cmd set rss func, ret=%d", ret);
+	return ret;
+}
+
+static void sxe2_drv_flow_bitmap_fill(uint32_t *bitmap, uint16_t *list)
+{
+	uint16_t index = 0;
+	uint16_t i = 0;
+	uint16_t map_size = sizeof(*bitmap) * SXE2_BITS_PER_BYTE;
+
+	while (list[i] != SXE2_FLOW_END) {
+		index = list[i] / map_size;
+		bitmap[index] |= (1UL << (list[i] % map_size));
+		i++;
+	}
+}
+
+int32_t sxe2_drv_rss_hf_add(struct sxe2_adapter *adapter,
+			struct sxe2_rss_hf_config *rss_conf)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_rss_hf_req req = {0};
+	int32_t ret = 0;
+
+	req.vsi_id = rte_cpu_to_le_16(adapter->vsi_ctxt.dpdk_vsi_id);
+	req.symm = rss_conf->symm;
+	req.hdr_type = rte_cpu_to_le_32(SXE2_RSS_OUTER_HEADERS);
+	sxe2_drv_flow_bitmap_fill(req.headers, rss_conf->hdrs);
+	sxe2_drv_flow_bitmap_fill(req.hash_flds, rss_conf->flds);
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RSS_HF_ADD,
+		&req, sizeof(req), NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cmd add rss hf, ret=%d", ret);
+	return ret;
+}
+
+int32_t sxe2_drv_rss_hf_del(struct sxe2_adapter *adapter,
+				struct sxe2_rss_hf_config *rss_conf)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_rss_hf_req req = {0};
+	int32_t ret = 0;
+
+	req.vsi_id = rte_cpu_to_le_16(adapter->vsi_ctxt.dpdk_vsi_id);
+	req.symm = rss_conf->symm;
+	req.hdr_type = rte_cpu_to_le_32(SXE2_RSS_OUTER_HEADERS);
+	sxe2_drv_flow_bitmap_fill(req.headers, rss_conf->hdrs);
+	sxe2_drv_flow_bitmap_fill(req.hash_flds, rss_conf->flds);
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RSS_HF_DEL,
+		&req, sizeof(req), NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cmd del rss hf, ret=%d", ret);
+	return ret;
+}
+
+int32_t sxe2_drv_rss_hf_clear(struct sxe2_adapter *adapter)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	int32_t ret = 0;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_RSS_HF_CLEAR,
+		NULL, 0, NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cmd clear rss hf, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_ptp_gettime(struct sxe2_adapter *adapter, struct sxe2_rx_queue *rxq)
+{
+	(void)adapter;
+	(void)rxq;
+	return 0;
+}
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index fb01c41aad..3274db6551 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -53,4 +53,20 @@ int32_t sxe2_drv_vlan_insert_strip_cfg(struct sxe2_adapter *adapter);
 
 int32_t sxe2_drv_vlan_filter_switch(struct sxe2_adapter *adapter, bool on);
 
+int32_t sxe2_drv_rss_key_set(struct sxe2_adapter *adapter, uint8_t *key, uint16_t key_size);
+
+int32_t sxe2_drv_rss_lut_set(struct sxe2_adapter *adapter, uint8_t *lut, uint16_t lut_size);
+
+int32_t sxe2_drv_rss_hash_ctrl_func(struct sxe2_adapter *adapter, enum sxe2_rss_hash_key_func func);
+
+int32_t sxe2_drv_rss_hf_add(struct sxe2_adapter *adapter,
+			struct sxe2_rss_hf_config *rss_conf);
+
+int32_t sxe2_drv_rss_hf_del(struct sxe2_adapter *adapter,
+				struct sxe2_rss_hf_config *rss_conf);
+
+int32_t sxe2_drv_rss_hf_clear(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_ptp_gettime(struct sxe2_adapter *adapter, struct sxe2_rx_queue *rxq);
+
 #endif /* __SXE2_CMD_CHNL_H__ */
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index c9d7cc719b..ced5a4b1a0 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -6,6 +6,7 @@
 #define __SXE2_DRV_CMD_H__
 
 #include "sxe2_osal.h"
+#include "sxe2_flow_public.h"
 
 #define SXE2_DRV_CMD_MODULE_S        (16)
 #define SXE2_MK_DRV_CMD(module, cmd) (((module) << SXE2_DRV_CMD_MODULE_S) | ((cmd) & 0xFFFF))
@@ -320,6 +321,34 @@ struct sxe2_vlan_filter_switch_req {
 	uint8_t rsv;
 };
 
+struct sxe2_rss_key_req {
+	__le16 vsi_id;
+	__le16 key_size;
+	uint8_t key[];
+};
+
+struct sxe2_rss_lut_req {
+	__le16 vsi_id;
+	__le16 lut_size;
+	uint8_t lut[];
+};
+
+struct sxe2_rss_func_req {
+	__le16 vsi_id;
+	uint8_t func;
+	uint8_t rsv[1];
+};
+
+struct sxe2_rss_hf_req {
+	__le16 vsi_id;
+	uint8_t rsv[2];
+	uint32_t headers[BITS_TO_U32(SXE2_FLOW_HDR_MAX)];
+	uint32_t hash_flds[BITS_TO_U32(SXE2_FLOW_FLD_ID_MAX)];
+	uint32_t hdr_type;
+	uint8_t symm;
+	uint8_t rsv1[3];
+};
+
 enum sxe2_drv_cmd_module {
 	SXE2_DRV_CMD_MODULE_HANDSHAKE = 0,
 	SXE2_DRV_CMD_MODULE_DEV = 1,
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 9b117f097e..d48841b8e4 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -125,6 +125,11 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 
 	.vlan_filter_set            = sxe2_dev_vlan_filter_set,
 	.vlan_offload_set           = sxe2_dev_vlan_offload_set,
+
+	.reta_update                = sxe2_dev_rss_reta_update,
+	.reta_query                 = sxe2_dev_rss_reta_query,
+	.rss_hash_update            = sxe2_dev_rss_hash_update,
+	.rss_hash_conf_get          = sxe2_dev_rss_hash_conf_get,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
@@ -141,6 +146,12 @@ static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
 		goto end;
 	}
 
+	ret = sxe2_rss_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init rss, ret=%d", ret);
+		goto end;
+	}
+
 end:
 	return ret;
 }
@@ -281,6 +292,22 @@ static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev,
 		RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO |
 		RTE_ETH_TX_OFFLOAD_GENEVE_TNL_TSO;
 
+
+	if (adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_PTP)
+		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_TIMESTAMP;
+
+	if (adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_RSS) {
+		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_RSS_HASH;
+		dev_info->flow_type_rss_offloads  |= SXE2_RSS_HF_SUPPORT_ALL;
+		dev_info->reta_size = adapter->rss_ctxt.rss_lut_size;
+		dev_info->hash_key_size = adapter->rss_ctxt.rss_key_size;
+		dev_info->rss_algo_capa =
+			RTE_ETH_HASH_ALGO_TO_CAPA(RTE_ETH_HASH_FUNCTION_DEFAULT) |
+			RTE_ETH_HASH_ALGO_TO_CAPA(RTE_ETH_HASH_FUNCTION_TOEPLITZ) |
+			RTE_ETH_HASH_ALGO_TO_CAPA(RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ) |
+			RTE_ETH_HASH_ALGO_TO_CAPA(RTE_ETH_HASH_FUNCTION_SIMPLE_XOR);
+	}
+
 	dev_info->default_rxconf = (struct rte_eth_rxconf) {
 		.rx_thresh = {
 			.pthresh = SXE2_DEFAULT_RX_PTHRESH,
@@ -563,6 +590,8 @@ static int32_t sxe2_func_caps_get(struct sxe2_adapter *adapter)
 
 	sxe2_sw_queue_ctx_hw_cap_set(adapter, &dev_caps.queue_caps);
 
+	sxe2_sw_rss_ctx_hw_cap_set(adapter, &dev_caps.rss_hash_caps);
+
 	sxe2_sw_vsi_ctx_hw_cap_set(adapter, &dev_caps.vsi_caps);
 
 l_end:
@@ -950,8 +979,15 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_eth_err;
 	}
 
+	ret = sxe2_rss_disable(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to disable rss, ret=%d", ret);
+		goto init_rss_err;
+	}
+
 	goto l_end;
 
+init_rss_err:
 init_eth_err:
 init_dev_info_err:
 	sxe2_vsi_uninit(dev);
@@ -965,6 +1001,7 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 {
 	(void)sxe2_dev_stop(dev);
 	(void)sxe2_queues_release(dev);
+	(void)sxe2_rss_disable(dev);
 	sxe2_vsi_uninit(dev);
 	sxe2_dev_pci_map_uinit(dev);
 	sxe2_eth_uinit(dev);
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index cc8a84c0a0..556a11cc77 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -13,6 +13,7 @@
 
 #include "sxe2_common.h"
 #include "sxe2_vsi.h"
+#include "sxe2_rss.h"
 #include "sxe2_irq.h"
 #include "sxe2_queue.h"
 #include "sxe2_mac.h"
@@ -120,6 +121,11 @@ enum {
 	SXE2_FLAGS_NBITS
 };
 
+struct sxe2_ptp_context {
+	uint64_t mbuf_rx_ts_flag;
+	int32_t mbuf_rx_ts_offset;
+};
+
 struct sxe2_devargs {
 	uint8_t flow_dup_pattern_mode;
 	uint8_t func_flow_direct_en;
@@ -298,7 +304,9 @@ struct sxe2_adapter {
 	struct sxe2_queue_context     q_ctxt;
 	struct sxe2_vsi_context       vsi_ctxt;
 	struct sxe2_filter_context    filter_ctxt;
+	struct sxe2_rss_context       rss_ctxt;
 	struct sxe2_link_context      link_ctxt;
+	struct sxe2_ptp_context       ptp_ctxt;
 	struct sxe2_devargs           devargs;
 	struct sxe2_switchdev_info    switchdev_info;
 	bool                          rule_started;
diff --git a/drivers/net/sxe2/sxe2_flow_define.h b/drivers/net/sxe2/sxe2_flow_define.h
new file mode 100644
index 0000000000..d2f6000efa
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_flow_define.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_FLOW_DEFINE_H__
+#define __SXE2_FLOW_DEFINE_H__
+#include <rte_tailq.h>
+#include <rte_eal.h>
+#include <rte_flow_driver.h>
+
+#include "sxe2_osal.h"
+#include "sxe2_flow_public.h"
+
+#define SXE2_FLOW_ETH_TYPE_MIN        (1500)
+
+enum sxe2_expansion {
+	SXE2_EXPANSION_ERROR = 0,
+	SXE2_EXPANSION_OUTER_ETH,
+	SXE2_EXPANSION_OUTER_VLAN,
+	SXE2_EXPANSION_OUTER_QINQ,
+	SXE2_EXPANSION_OUTER_IPV4,
+	SXE2_EXPANSION_OUTER_IPV4_FRAG_EXT,
+	SXE2_EXPANSION_OUTER_IPV6,
+	SXE2_EXPANSION_OUTER_IPV6_FRAG_EXT,
+	SXE2_EXPANSION_OUTER_UDP,
+	SXE2_EXPANSION_OUTER_TCP,
+	SXE2_EXPANSION_OUTER_SCTP,
+
+	SXE2_EXPANSION_VXLAN,
+	SXE2_EXPANSION_VXLAN_GPE,
+	SXE2_EXPANSION_GRE,
+	SXE2_EXPANSION_NVGRE,
+	SXE2_EXPANSION_GENEVE,
+	SXE2_EXPANSION_GTPU,
+	SXE2_EXPANSION_IPIP,
+	SXE2_EXPANSION_OUTER_END,
+
+	SXE2_EXPANSION_ETH,
+	SXE2_EXPANSION_VLAN,
+	SXE2_EXPANSION_IPV4,
+	SXE2_EXPANSION_IPV4_FRAG_EXT,
+	SXE2_EXPANSION_IPV6,
+	SXE2_EXPANSION_IPV6_FRAG_EXT,
+	SXE2_EXPANSION_UDP,
+	SXE2_EXPANSION_TCP,
+	SXE2_EXPANSION_SCTP,
+
+	SXE2_EXPANSION_END,
+	SXE2_EXPANSION_MAX,
+};
+
+enum sxe2_flow_udp_tunnel_protocol {
+	SXE2_FLOW_UDP_TUNNEL_PROTOCOL_VXLAN,
+	SXE2_FLOW_UDP_TUNNEL_PROTOCOL_VXLAN_GPE,
+	SXE2_FLOW_UDP_TUNNEL_PROTOCOL_GENEVE,
+	SXE2_FLOW_UDP_TUNNEL_PROTOCOL_GTP_U,
+	SXE2_FLOW_UDP_TUNNEL_PROTOCOL_NVGRE,
+	SXE2_FLOW_UDP_TUNNEL_MAX,
+};
+
+enum {
+	SXE2_FLOW_ETH_TYPE_IPV4 = 0x0800,
+	SXE2_FLOW_ETH_TYPE_IPV6 = 0x86DD,
+	SXE2_FLOW_IP_PROTOCOL_GRE = 0x2F,
+	SXE2_FLOW_IP_PROTOCOL_IPV4 = 0x04,
+	SXE2_FLOW_IP_PROTOCOL_IPV6 = 0x29,
+	SXE2_FLOW_IP_PROTOCOL_ETH = 0x3B,
+	SXE2_FLOW_IP_PROTOCOL_UDP = 0x11,
+	SXE2_FLOW_IP_PROTOCOL_TCP = 0x06,
+	SXE2_FLOW_IP_PROTOCOL_SCTP = 0x84,
+};
+
+union sxe2_flow_item_raw {
+	struct sxe2_flow_item item;
+	uint8_t raw[sizeof(struct sxe2_flow_item)];
+};
+
+struct sxe2_flow {
+	TAILQ_ENTRY(sxe2_flow) next;
+	enum sxe2_flow_engine_type engine_type;
+	struct sxe2_flow_pattern pattern_outer;
+	struct sxe2_flow_pattern pattern_inner;
+	uint8_t has_mask;
+	uint8_t has_spec;
+	uint8_t has_hdr;
+	struct sxe2_flow_meta meta;
+	struct sxe2_flow_action action;
+	uint32_t flow_id;
+	int32_t create_err;
+	DECLARE_BITMAP(flow_type, SXE2_EXPANSION_MAX);
+};
+
+TAILQ_HEAD(sxe2_flow_list_t, sxe2_flow);
+
+struct rte_flow {
+	TAILQ_ENTRY(rte_flow) next;
+	struct sxe2_flow_list_t sxe2_flow_list;
+};
+TAILQ_HEAD(rte_flow_list_t, rte_flow);
+
+struct sxe2_fnav_cid_mgr {
+	TAILQ_ENTRY(sxe2_fnav_cid_mgr) next;
+	uint16_t stat_index;
+	uint32_t user_id;
+	uint32_t driver_id;
+	uint32_t count_type;
+	uint64_t hits;
+	uint64_t bytes;
+};
+TAILQ_HEAD(sxe2_fnav_cid_mgr_list_t, sxe2_fnav_cid_mgr);
+
+struct sxe2_fnav_count_resource {
+	uint32_t count_type;
+	uint32_t global_index;
+	struct sxe2_fnav_cid_mgr_list_t fnav_cid_mgr_list;
+};
+
+struct sxe2_flow_context {
+	struct rte_flow_list_t rte_flow_list;
+	rte_spinlock_t flow_list_lock;
+	struct sxe2_fnav_count_resource hw_res;
+	uint32_t fnav_inited;
+};
+#define SXE2_INVALID_RSS_ATTR	\
+			(RTE_ETH_RSS_L3_PRE40 | RTE_ETH_RSS_L3_PRE56 | RTE_ETH_RSS_L3_PRE96)
+#define SXE2_VALID_RSS_IPV4_L4	 \
+			(RTE_ETH_RSS_NONFRAG_IPV4_UDP | RTE_ETH_RSS_NONFRAG_IPV4_TCP | \
+			 RTE_ETH_RSS_NONFRAG_IPV4_SCTP)
+
+#define SXE2_VALID_RSS_IPV6_L4	\
+			(RTE_ETH_RSS_NONFRAG_IPV6_UDP | RTE_ETH_RSS_NONFRAG_IPV6_TCP | \
+			 RTE_ETH_RSS_NONFRAG_IPV6_SCTP)
+#define SXE2_VALID_RSS_IPV4	\
+			(RTE_ETH_RSS_IPV4 | RTE_ETH_RSS_FRAG_IPV4  | \
+			 RTE_ETH_RSS_NONFRAG_IPV4_OTHER | SXE2_VALID_RSS_IPV4_L4)
+#define SXE2_VALID_RSS_IPV6	\
+			(RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_FRAG_IPV6 | \
+			 RTE_ETH_RSS_NONFRAG_IPV6_OTHER | SXE2_VALID_RSS_IPV6_L4)
+
+#define SXE2_VALID_RSS_L3	(SXE2_VALID_RSS_IPV4 | SXE2_VALID_RSS_IPV6)
+#define SXE2_VALID_RSS_L4	(SXE2_VALID_RSS_IPV4_L4 | SXE2_VALID_RSS_IPV6_L4)
+
+#endif /* __SXE2_FLOW_DEFINE_H__ */
diff --git a/drivers/net/sxe2/sxe2_queue.c b/drivers/net/sxe2/sxe2_queue.c
index 1786d6ea4f..220cab6fce 100644
--- a/drivers/net/sxe2/sxe2_queue.c
+++ b/drivers/net/sxe2/sxe2_queue.c
@@ -17,6 +17,7 @@ void sxe2_sw_queue_ctx_hw_cap_set(struct sxe2_adapter *adapter,
 
 int32_t sxe2_queues_init(struct rte_eth_dev *dev)
 {
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
 	int32_t ret = 0;
 	uint16_t buf_size;
 	uint16_t frame_size;
@@ -36,6 +37,16 @@ int32_t sxe2_queues_init(struct rte_eth_dev *dev)
 			dev->data->scattered_rx = 1;
 	}
 
+	adapter->ptp_ctxt.mbuf_rx_ts_offset = -1;
+	adapter->ptp_ctxt.mbuf_rx_ts_flag = 0;
+	if (dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_TIMESTAMP) {
+		ret = rte_mbuf_dyn_rx_timestamp_register
+			(&adapter->ptp_ctxt.mbuf_rx_ts_offset,
+			 (uint64_t *)&adapter->ptp_ctxt.mbuf_rx_ts_flag);
+		if (ret)
+			PMD_LOG_ERR(INIT, "Failed to enable timestamp offloads, ret=%d", ret);
+	}
+
 	return ret;
 }
 
diff --git a/drivers/net/sxe2/sxe2_rss.c b/drivers/net/sxe2/sxe2_rss.c
new file mode 100644
index 0000000000..1d56613043
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_rss.c
@@ -0,0 +1,584 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include "sxe2_rss.h"
+#include "sxe2_common_log.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_cmd_chnl.h"
+
+void sxe2_sw_rss_ctx_hw_cap_set(struct sxe2_adapter *adapter,
+		struct sxe2_drv_rss_hash_caps *rss_caps)
+{
+	adapter->rss_ctxt.rss_key_size = rss_caps->hash_key_size;
+	adapter->rss_ctxt.rss_lut_size = rss_caps->lut_key_size;
+}
+
+int32_t sxe2_rss_hash_key_init(struct rte_eth_dev *dev)
+{
+	struct rte_eth_rss_conf *rss_conf = &dev->data->dev_conf.rx_adv_conf.rss_conf;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = 0;
+	uint16_t i = 0;
+
+	if (rss_ctxt->rss_key == NULL) {
+		rss_ctxt->rss_key = (uint8_t *)rte_zmalloc("rss_key", rss_ctxt->rss_key_size, 0);
+		if (rss_ctxt->rss_key == NULL) {
+			PMD_LOG_ERR(INIT, "Failed to allocate rss key");
+			ret = -ENOMEM;
+			goto l_end;
+		}
+	}
+
+	if (!rss_conf->rss_key) {
+		for (i = 0; i < rss_ctxt->rss_key_size; i++)
+			rss_ctxt->rss_key[i] = (uint8_t)rte_rand();
+	} else {
+		rte_memcpy(rss_ctxt->rss_key, rss_conf->rss_key,
+			   RTE_MIN(rss_conf->rss_key_len, rss_ctxt->rss_key_size));
+	}
+
+	ret = sxe2_drv_rss_key_set(adapter, rss_ctxt->rss_key,
+				   rss_ctxt->rss_key_size);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set rss key, ret:%d", ret);
+		rte_free(rss_ctxt->rss_key);
+		rss_ctxt->rss_key = NULL;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+void sxe2_rss_hash_key_uninit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+
+	if (rss_ctxt->rss_key) {
+		rte_free(rss_ctxt->rss_key);
+		rss_ctxt->rss_key = NULL;
+	}
+}
+
+int32_t sxe2_rss_lut_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = 0;
+	uint16_t i;
+
+	if (rss_ctxt->rss_lut == NULL) {
+		rss_ctxt->rss_lut = (uint8_t *)rte_zmalloc("rss_lut", rss_ctxt->rss_lut_size, 0);
+		if (rss_ctxt->rss_lut == NULL) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Failed to allocate rss lut");
+			ret = -ENOMEM;
+			goto l_end;
+		}
+	}
+
+	for (i = 0; i < rss_ctxt->rss_lut_size; i++)
+		rss_ctxt->rss_lut[i] = (uint8_t)(i % dev->data->nb_rx_queues);
+
+	ret = sxe2_drv_rss_lut_set(adapter, rss_ctxt->rss_lut, rss_ctxt->rss_lut_size);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set rss lut, ret:%d", ret);
+		rte_free(rss_ctxt->rss_lut);
+		rss_ctxt->rss_lut = NULL;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+void sxe2_rss_lut_uninit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+
+	if (rss_ctxt->rss_lut) {
+		rte_free(rss_ctxt->rss_lut);
+		rss_ctxt->rss_lut = NULL;
+	}
+}
+
+static struct sxe2_rss_hf_config sxe2_rss_default_hf_config[] = {
+	{
+		.rss_hf = RTE_ETH_RSS_L2_PAYLOAD,
+		.hdrs = {SXE2_FLOW_HDR_ETH,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_ETH_TYPE,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_IPV4,
+		.hdrs = {SXE2_FLOW_HDR_IPV4,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV4_SA,
+				 SXE2_FLOW_FLD_ID_IPV4_DA,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_IPV6,
+		.hdrs = {SXE2_FLOW_HDR_IPV6,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV6_SA,
+				 SXE2_FLOW_FLD_ID_IPV6_DA,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_FRAG_IPV4,
+		.hdrs = {SXE2_FLOW_HDR_IPV4,
+				 SXE2_FLOW_HDR_IPV_FRAG,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV4_SA,
+				 SXE2_FLOW_FLD_ID_IPV4_DA,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_FRAG_IPV6,
+		.hdrs = {SXE2_FLOW_HDR_IPV6,
+				 SXE2_FLOW_HDR_IPV_FRAG,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV6_SA,
+				 SXE2_FLOW_FLD_ID_IPV6_DA,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV4_OTHER,
+		.hdrs = {SXE2_FLOW_HDR_IPV4,
+				 SXE2_FLOW_HDR_IPV_OTHER,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV4_SA,
+				 SXE2_FLOW_FLD_ID_IPV4_DA,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV6_OTHER,
+		.hdrs = {SXE2_FLOW_HDR_IPV6,
+				 SXE2_FLOW_HDR_IPV_OTHER,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV6_SA,
+				 SXE2_FLOW_FLD_ID_IPV6_DA,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV4_UDP,
+		.hdrs = {SXE2_FLOW_HDR_IPV4,
+				 SXE2_FLOW_HDR_UDP,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV4_SA,
+				 SXE2_FLOW_FLD_ID_IPV4_DA,
+				 SXE2_FLOW_FLD_ID_UDP_SRC_PORT,
+				 SXE2_FLOW_FLD_ID_UDP_DST_PORT,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV6_UDP,
+		.hdrs = {SXE2_FLOW_HDR_IPV6,
+				 SXE2_FLOW_HDR_UDP,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV6_SA,
+				 SXE2_FLOW_FLD_ID_IPV6_DA,
+				 SXE2_FLOW_FLD_ID_UDP_SRC_PORT,
+				 SXE2_FLOW_FLD_ID_UDP_DST_PORT,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV4_TCP,
+		.hdrs = {SXE2_FLOW_HDR_IPV4,
+				 SXE2_FLOW_HDR_TCP,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV4_SA,
+				 SXE2_FLOW_FLD_ID_IPV4_DA,
+				 SXE2_FLOW_FLD_ID_TCP_SRC_PORT,
+				 SXE2_FLOW_FLD_ID_TCP_DST_PORT,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV6_TCP,
+		.hdrs = {SXE2_FLOW_HDR_IPV6,
+				 SXE2_FLOW_HDR_TCP,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV6_SA,
+				 SXE2_FLOW_FLD_ID_IPV6_DA,
+				 SXE2_FLOW_FLD_ID_TCP_SRC_PORT,
+				 SXE2_FLOW_FLD_ID_TCP_DST_PORT,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV4_SCTP,
+		.hdrs = {SXE2_FLOW_HDR_IPV4,
+				 SXE2_FLOW_HDR_SCTP,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV4_SA,
+				 SXE2_FLOW_FLD_ID_IPV4_DA,
+				 SXE2_FLOW_FLD_ID_SCTP_SRC_PORT,
+				 SXE2_FLOW_FLD_ID_SCTP_DST_PORT,
+				 SXE2_FLOW_END},
+	},
+	{
+		.rss_hf = RTE_ETH_RSS_NONFRAG_IPV6_SCTP,
+		.hdrs = {SXE2_FLOW_HDR_IPV6,
+				 SXE2_FLOW_HDR_SCTP,
+				 SXE2_FLOW_END},
+		.flds = {SXE2_FLOW_FLD_ID_IPV6_SA,
+				 SXE2_FLOW_FLD_ID_IPV6_DA,
+				 SXE2_FLOW_FLD_ID_SCTP_SRC_PORT,
+				 SXE2_FLOW_FLD_ID_SCTP_DST_PORT,
+				 SXE2_FLOW_END},
+	},
+};
+
+int32_t sxe2_rss_hf_type_set(struct rte_eth_dev *dev, uint64_t rss_hf)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = 0;
+	uint32_t i;
+	uint8_t symm = 0;
+
+	if (0 == (rss_hf & SXE2_RSS_HF_SUPPORT_ALL) && rss_hf != 0) {
+		PMD_DEV_LOG_ERR(adapter, DRV,
+			"Failed to set unsupported rss_hf:0x%016" PRIx64,
+			rss_hf);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	for (i = 0; i < RTE_DIM(sxe2_rss_default_hf_config); i++) {
+		if (rss_ctxt->rss_hf & sxe2_rss_default_hf_config[i].rss_hf) {
+			sxe2_rss_default_hf_config[i].symm = rss_ctxt->symm;
+			ret = sxe2_drv_rss_hf_del(adapter, &sxe2_rss_default_hf_config[i]);
+			if (ret) {
+				PMD_DEV_LOG_ERR(adapter, INIT,
+					"Failed to del rss hf cfg[%d], ret:%d", i, ret);
+				goto l_end;
+			}
+		}
+	}
+
+	if (rss_ctxt->hash_func == RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ)
+		symm = 1;
+
+	for (i = 0; i < RTE_DIM(sxe2_rss_default_hf_config); i++) {
+		if (rss_hf & sxe2_rss_default_hf_config[i].rss_hf) {
+			sxe2_rss_default_hf_config[i].symm = symm;
+			ret = sxe2_drv_rss_hf_add(adapter, &sxe2_rss_default_hf_config[i]);
+			if (ret) {
+				PMD_DEV_LOG_ERR(adapter, INIT,
+					"Failed to add rss hf cfg[%d], ret:%d", i, ret);
+				goto l_end;
+			}
+		}
+	}
+
+	rss_ctxt->rss_hf = rss_hf & SXE2_RSS_HF_SUPPORT_ALL;
+	rss_ctxt->symm = symm;
+l_end:
+	return ret;
+}
+
+int32_t sxe2_rss_hash_function_set(struct rte_eth_dev *dev, enum rte_eth_hash_function func)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	enum sxe2_rss_hash_key_func hash_func = SXE2_RSS_HASH_FUNC_SYM_TOEPLITZ;
+	int32_t ret = 0;
+
+	switch (func) {
+	case RTE_ETH_HASH_FUNCTION_DEFAULT:
+	case RTE_ETH_HASH_FUNCTION_TOEPLITZ:
+	case RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ:
+		hash_func = SXE2_RSS_HASH_FUNC_SYM_TOEPLITZ;
+		break;
+	case RTE_ETH_HASH_FUNCTION_SIMPLE_XOR:
+		hash_func = SXE2_RSS_HASH_FUNC_XOR;
+		break;
+	default:
+		PMD_DEV_LOG_ERR(adapter, DRV, "RSS hash function[%d] not support.", func);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_drv_rss_hash_ctrl_func(adapter, hash_func);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set rss hash function, ret=[%d]", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_rss_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_eth_rss_conf *rss_conf = &dev->data->dev_conf.rx_adv_conf.rss_conf;
+	enum rte_eth_hash_function rss_func = RTE_ETH_HASH_FUNCTION_SYMMETRIC_TOEPLITZ;
+	int32_t ret = 0;
+
+	adapter->rss_ctxt.inited = false;
+
+	if (dev->data->nb_rx_queues <= 1) {
+		PMD_DEV_LOG_DEBUG(adapter, INIT, "No need to init rss, rx queues %d.",
+				dev->data->nb_rx_queues);
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_RSS) == 0) {
+		PMD_DEV_LOG_WARN(adapter, INIT, "RSS not supported");
+		goto l_end;
+	}
+
+	ret = sxe2_rss_hash_key_init(dev);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to init rss key");
+		goto l_end;
+	}
+
+	ret = sxe2_rss_lut_init(dev);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to init rss lut");
+		goto l_err_key;
+	}
+
+	rss_func = rss_conf->algorithm;
+	ret = sxe2_rss_hash_function_set(dev, rss_func);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to init rss hash function");
+		goto l_err_lut;
+	}
+	ret = sxe2_rss_hf_type_set(dev, rss_conf->rss_hf);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set rss hf type");
+		goto l_err_lut;
+	}
+	adapter->rss_ctxt.inited = true;
+	goto l_end;
+
+l_err_lut:
+	sxe2_rss_lut_uninit(dev);
+l_err_key:
+	sxe2_rss_hash_key_uninit(dev);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_rss_disable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_RSS) == 0)
+		goto l_end;
+
+	ret = sxe2_drv_rss_hf_clear(adapter);
+	if (ret)
+		PMD_LOG_ERR(INIT, "Failed to clear rss hf");
+
+	sxe2_rss_hash_key_uninit(dev);
+
+	sxe2_rss_lut_uninit(dev);
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_dev_rss_reta_update(struct rte_eth_dev *dev,
+		struct rte_eth_rss_reta_entry64 *reta_conf,
+		uint16_t reta_size)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	uint8_t *lut_tmp = NULL;
+	int32_t ret = 0;
+	uint16_t i;
+	uint16_t shift;
+	uint16_t idx;
+
+	if (!adapter->rss_ctxt.inited) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "RSS not inited.");
+		ret = -ENOTSUP;
+		goto l_end;
+	}
+
+	if (reta_size != rss_ctxt->rss_lut_size) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "The size of hash lookup table configured "
+				"(%d) doesn't match the number of hardware can "
+			"support (%d)", reta_size, rss_ctxt->rss_lut_size);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	lut_tmp = rte_zmalloc("rss_lut_temp", reta_size, 0);
+	if (!lut_tmp) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "No memory can be allocated");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	rte_memcpy(lut_tmp, rss_ctxt->rss_lut, reta_size);
+
+	for (i = 0; i < reta_size; i++) {
+		idx = i / RTE_ETH_RETA_GROUP_SIZE;
+		shift = i % RTE_ETH_RETA_GROUP_SIZE;
+		if (reta_conf[idx].mask & (1ULL << shift))
+			lut_tmp[i] = reta_conf[idx].reta[shift];
+	}
+
+	ret = sxe2_drv_rss_lut_set(adapter, lut_tmp, reta_size);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set rss lut");
+		goto l_end;
+	}
+
+	rte_memcpy(rss_ctxt->rss_lut, lut_tmp, reta_size);
+
+l_end:
+	if (lut_tmp)
+		rte_free(lut_tmp);
+	return ret;
+}
+
+int32_t sxe2_dev_rss_reta_query(struct rte_eth_dev *dev,
+		struct rte_eth_rss_reta_entry64 *reta_conf,
+		uint16_t reta_size)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = 0;
+	uint16_t i;
+	uint16_t shift;
+	uint16_t idx;
+
+	if (!adapter->rss_ctxt.inited) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "RSS not inited.");
+		ret = -ENOTSUP;
+		goto l_end;
+	}
+
+	if (reta_size != rss_ctxt->rss_lut_size) {
+		PMD_LOG_ERR(INIT, "The size of hash lookup table configured "
+			"(%d) doesn't match the number of hardware can "
+			"support (%d)", reta_size, rss_ctxt->rss_lut_size);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	for (i = 0; i < reta_size; i++) {
+		idx = i / RTE_ETH_RETA_GROUP_SIZE;
+		shift = i % RTE_ETH_RETA_GROUP_SIZE;
+		if (reta_conf[idx].mask & (1ULL << shift))
+			reta_conf[idx].reta[shift] = rss_ctxt->rss_lut[i];
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_rss_hash_key_update(struct rte_eth_dev *dev,
+		struct rte_eth_rss_conf *rss_conf)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = 0;
+	uint16_t i;
+
+	if (rss_conf->rss_key_len == 0 || rss_conf->rss_key == NULL)
+		goto l_end;
+
+	if (rss_conf->rss_key_len != rss_ctxt->rss_key_size) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "The size of hash key configured "
+			"(%d) doesn't match the size of hardware can "
+			"support (%d)", rss_conf->rss_key_len,
+			rss_ctxt->rss_key_size);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	for (i = 0; i < rss_conf->rss_key_len; i++) {
+		if (rss_conf->rss_key[i] != rss_ctxt->rss_key[i])
+			break;
+	}
+	if (i == rss_conf->rss_key_len)
+		goto l_end;
+
+	ret = sxe2_drv_rss_key_set(adapter, rss_conf->rss_key,
+				   rss_conf->rss_key_len);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set rss key");
+		goto l_end;
+	}
+
+	rte_memcpy(rss_ctxt->rss_key, rss_conf->rss_key, rss_conf->rss_key_len);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_dev_rss_hash_update(struct rte_eth_dev *dev,
+		struct rte_eth_rss_conf *rss_conf)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = -1;
+
+	if (!adapter->rss_ctxt.inited) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "RSS not inited.");
+		ret = -ENOTSUP;
+		goto l_end;
+	}
+
+	ret = sxe2_rss_hash_key_update(dev, rss_conf);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set rss hash key");
+		goto l_end;
+	}
+
+	if (rss_conf->algorithm != rss_ctxt->hash_func) {
+		ret = sxe2_rss_hash_function_set(dev, rss_conf->algorithm);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set rss hash function");
+			goto l_end;
+		}
+		rss_ctxt->hash_func = rss_conf->algorithm;
+	}
+
+	if ((rss_conf->rss_hf & SXE2_RSS_HF_SUPPORT_ALL)
+			!= rss_ctxt->rss_hf) {
+		ret = sxe2_rss_hf_type_set(dev, rss_conf->rss_hf);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set rss hf type");
+			goto l_end;
+		}
+	}
+	ret = 0;
+l_end:
+	return ret;
+}
+
+int32_t sxe2_dev_rss_hash_conf_get(struct rte_eth_dev *dev,
+		struct rte_eth_rss_conf *rss_conf)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_rss_context *rss_ctxt = &adapter->rss_ctxt;
+	int32_t ret = 0;
+
+	if (adapter->rss_ctxt.inited == 0) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "RSS not inited.");
+		ret = -ENOTSUP;
+		goto l_end;
+	}
+
+	if (rss_conf->rss_key) {
+		rss_conf->rss_key_len = rss_ctxt->rss_key_size;
+		rte_memcpy(rss_conf->rss_key, rss_ctxt->rss_key, rss_ctxt->rss_key_size);
+	}
+	rss_conf->rss_hf = rss_ctxt->rss_hf;
+	rss_conf->algorithm = rss_ctxt->hash_func;
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_rss.h b/drivers/net/sxe2/sxe2_rss.h
new file mode 100644
index 0000000000..2a454ac1b3
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_rss.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_RSS_H__
+#define __SXE2_RSS_H__
+#include <ethdev_driver.h>
+#include "sxe2_osal.h"
+#include "sxe2_drv_cmd.h"
+#include "sxe2_flow_define.h"
+
+#define SXE2_FLOW_END (0xFFFF)
+
+struct sxe2_rss_context {
+	enum rte_eth_hash_function hash_func;
+	uint16_t rss_key_size;
+	uint16_t rss_lut_size;
+	uint8_t  *rss_key;
+	uint8_t  *rss_lut;
+	uint64_t rss_hf;
+	uint8_t symm;
+	bool inited;
+};
+
+struct sxe2_rss_hf_config {
+	uint64_t rss_hf;
+	uint16_t hdrs[SXE2_FLOW_HDR_MAX];
+	uint16_t flds[SXE2_FLOW_FLD_ID_MAX];
+	uint8_t symm;
+};
+
+#define SXE2_RSS_HF_SUPPORT_ALL ( \
+	RTE_ETH_RSS_IPV4 | \
+	RTE_ETH_RSS_FRAG_IPV4 | \
+	RTE_ETH_RSS_NONFRAG_IPV4_TCP | \
+	RTE_ETH_RSS_NONFRAG_IPV4_UDP | \
+	RTE_ETH_RSS_NONFRAG_IPV4_SCTP | \
+	RTE_ETH_RSS_NONFRAG_IPV4_OTHER | \
+	RTE_ETH_RSS_IPV6 | \
+	RTE_ETH_RSS_FRAG_IPV6 | \
+	RTE_ETH_RSS_NONFRAG_IPV6_TCP | \
+	RTE_ETH_RSS_NONFRAG_IPV6_UDP | \
+	RTE_ETH_RSS_NONFRAG_IPV6_SCTP | \
+	RTE_ETH_RSS_NONFRAG_IPV6_OTHER | \
+	RTE_ETH_RSS_L2_PAYLOAD)
+
+struct sxe2_adapter;
+
+void sxe2_sw_rss_ctx_hw_cap_set(struct sxe2_adapter *adapter,
+		struct sxe2_drv_rss_hash_caps *rss_caps);
+
+int32_t sxe2_rss_hash_key_init(struct rte_eth_dev *dev);
+
+void sxe2_rss_hash_key_uninit(struct rte_eth_dev *dev);
+
+int32_t sxe2_rss_lut_init(struct rte_eth_dev *dev);
+
+void sxe2_rss_lut_uninit(struct rte_eth_dev *dev);
+
+int32_t sxe2_rss_init(struct rte_eth_dev *dev);
+
+int32_t sxe2_rss_hash_function_set(struct rte_eth_dev *dev, enum rte_eth_hash_function func);
+
+int32_t sxe2_rss_hf_type_set(struct rte_eth_dev *dev, uint64_t rss_hf);
+
+int32_t sxe2_rss_disable(struct rte_eth_dev *dev);
+
+int32_t sxe2_dev_rss_reta_update(struct rte_eth_dev *dev,
+		struct rte_eth_rss_reta_entry64 *reta_conf,
+		uint16_t reta_size);
+
+int32_t sxe2_dev_rss_reta_query(struct rte_eth_dev *dev,
+		struct rte_eth_rss_reta_entry64 *reta_conf,
+		uint16_t reta_size);
+
+int32_t sxe2_dev_rss_hash_update(struct rte_eth_dev *dev,
+		struct rte_eth_rss_conf *rss_conf);
+
+int32_t sxe2_dev_rss_hash_conf_get(struct rte_eth_dev *dev,
+		struct rte_eth_rss_conf *rss_conf);
+#endif /* __SXE2_RSS_H__ */
diff --git a/drivers/net/sxe2/sxe2_txrx.h b/drivers/net/sxe2/sxe2_txrx.h
index 6d3d7455c2..a642a077bf 100644
--- a/drivers/net/sxe2/sxe2_txrx.h
+++ b/drivers/net/sxe2/sxe2_txrx.h
@@ -20,4 +20,8 @@ int32_t sxe2_tx_burst_mode_get(struct rte_eth_dev *dev,
 			__rte_unused uint16_t queue_id, struct rte_eth_burst_mode *mode);
 int32_t sxe2_rx_burst_mode_get(struct rte_eth_dev *dev,
 			__rte_unused uint16_t queue_id, struct rte_eth_burst_mode *mode);
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+int32_t sxe2_rx_update_ptp_time(struct sxe2_rx_queue *rxq);
+#endif
+
 #endif /* __SXE2_TXRX_H__ */
diff --git a/drivers/net/sxe2/sxe2_txrx_poll.c b/drivers/net/sxe2/sxe2_txrx_poll.c
index 21d5c38725..3c6fe37404 100644
--- a/drivers/net/sxe2/sxe2_txrx_poll.c
+++ b/drivers/net/sxe2/sxe2_txrx_poll.c
@@ -17,6 +17,7 @@
 #include "sxe2_queue.h"
 #include "sxe2_ethdev.h"
 #include "sxe2_common_log.h"
+#include "sxe2_cmd_chnl.h"
 
 static __rte_always_inline int32_t
 sxe2_tx_bufs_free(struct sxe2_tx_queue *txq)
@@ -282,6 +283,30 @@ sxe2_tx_desc_checksum_fill(uint64_t offloads, uint32_t *desc_cmd, uint32_t *desc
 	return;
 }
 
+static __rte_always_inline void sxe2_desc_tso_fill(struct rte_mbuf *tx_pkt,
+						   uint64_t *desc_type_cmd_tso_mss,
+						   union sxe2_tx_offload_info ol_info)
+{
+	uint32_t hdr_len;
+	uint32_t tso_len;
+
+	if (!ol_info.l4_len) {
+		PMD_LOG_DEBUG(TX, "TSO ERROR: L4 length is 0");
+		goto l_end;
+	}
+	hdr_len = ol_info.l2_len + ol_info.l3_len + ol_info.l4_len;
+	if (tx_pkt->ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK)
+		hdr_len += ol_info.outer_l2_len + ol_info.outer_l3_len;
+
+	tso_len = tx_pkt->pkt_len - hdr_len;
+	*desc_type_cmd_tso_mss |=
+			((uint64_t)SXE2_TX_CTXT_DESC_CMD_TSO << SXE2_TX_CTXT_DESC_CMD_SHIFT) |
+			((uint64_t)tso_len << SXE2_TX_CTXT_DESC_TSO_LEN_SHIFT) |
+			((uint64_t)tx_pkt->tso_segsz << SXE2_TX_CTXT_DESC_MSS_SHIFT);
+l_end:
+	return;
+}
+
 static __rte_always_inline uint64_t
 sxe2_tx_data_desc_build_cobt(uint32_t cmd, uint32_t offset, uint16_t buf_size, uint16_t l2tag)
 {
@@ -395,6 +420,11 @@ uint16_t sxe2_tx_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 				rte_pktmbuf_free_seg(buffer->mbuf);
 				buffer->mbuf = NULL;
 			}
+			if (offloads & (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG))
+				sxe2_desc_tso_fill(tx_pkt,
+					&desc_type_cmd_tso_mss, ol_info);
+			else if (offloads & RTE_MBUF_F_TX_IEEE1588_TMST)
+				desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_CMD_TSYN_MASK;
 
 			if (offloads & RTE_MBUF_F_TX_QINQ) {
 				desc_l2tag2 = tx_pkt->vlan_tci_outer;
@@ -707,6 +737,57 @@ sxe2_rx_desc_filter_para_fill(struct sxe2_rx_queue *rxq __rte_unused,
 #endif
 }
 
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+int32_t sxe2_rx_update_ptp_time(struct sxe2_rx_queue *rxq)
+{
+	struct sxe2_adapter *adapter;
+	uint64_t cur_time_ms;
+	int32_t ret = 0;
+	cur_time_ms = rte_get_timer_cycles() / (rte_get_timer_hz() / 1000);
+
+	if (likely((cur_time_ms - rxq->update_time) < SXE2_RX_PKTS_TS_TIMEOUT_VAL))
+		goto l_end;
+	rxq->update_time = cur_time_ms;
+	adapter = rxq->vsi->adapter;
+	rxq->ts_need_update = true;
+	ret = sxe2_drv_ptp_gettime(adapter, rxq);
+	if (rxq->desc_ts < rxq->ts_low)
+		rxq->ts_need_update = false;
+
+	PMD_LOG_INFO(RX, "rxq update time ret=%d, cur time=%" PRIu64 ", rxqh=%" PRIu64 ", rxql=%d",
+		     ret, cur_time_ms, rxq->ts_high, rxq->ts_low);
+l_end:
+	return ret;
+}
+
+static inline void sxe2_rx_desc_ptp_para_fill(struct sxe2_rx_queue *rxq,
+					      struct rte_mbuf *mbuf,
+					      union sxe2_rx_desc *desc)
+{
+	struct sxe2_adapter *adapter = rxq->vsi->adapter;
+	uint64_t ts_ns;
+
+	if (adapter->ptp_ctxt.mbuf_rx_ts_flag != 0 &&
+	    (rxq->offloads & RTE_ETH_RX_OFFLOAD_TIMESTAMP) &&
+	    SXE2_RX_DESC_RXDID_VAL_GET(desc->wb.rxdid_src) == SXE2_RX_DESC_RXDID_1588) {
+		rxq->desc_ts = rte_le_to_cpu_32(desc->wb_ts.ts_h);
+		(void)sxe2_rx_update_ptp_time(rxq);
+		if (rxq->ts_need_update && rxq->desc_ts < rxq->ts_low)
+			rxq->ts_high += 1;
+
+		rxq->ts_need_update = true;
+		rxq->ts_low = rxq->desc_ts;
+		rxq->update_time = rte_get_timer_cycles() /
+			(rte_get_timer_hz() / 1000);
+		ts_ns = rxq->ts_high * NSEC_PER_SEC + rxq->ts_low;
+		*RTE_MBUF_DYNFIELD(mbuf, adapter->ptp_ctxt.mbuf_rx_ts_offset, uint64_t *) = ts_ns;
+		mbuf->ol_flags |= adapter->ptp_ctxt.mbuf_rx_ts_flag;
+		PMD_LOG_INFO(RX, "receive ptp pkt,ts_s=%" PRIu64 ", ts_ns=%d", rxq->ts_high,
+			     rxq->ts_low);
+	}
+}
+#endif
+
 static __rte_always_inline void
 sxe2_rx_mbuf_common_fields_fill(struct sxe2_rx_queue *rxq, struct rte_mbuf *mbuf,
 		union sxe2_rx_desc *rxd)
@@ -718,10 +799,12 @@ sxe2_rx_mbuf_common_fields_fill(struct sxe2_rx_queue *rxq, struct rte_mbuf *mbuf
 
 	mbuf->ol_flags = 0;
 	mbuf->packet_type = ptype_tbl[SXE2_RX_DESC_PTYPE_VAL_GET(qword1)];
-
 	pkt_flags = sxe2_rx_desc_error_para(rxq, rxd);
 	sxe2_rx_desc_vlan_para_fill(mbuf, rxd);
 	sxe2_rx_desc_filter_para_fill(rxq, mbuf, rxd);
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+	sxe2_rx_desc_ptp_para_fill(rxq, mbuf, rxd);
+#endif
 
 	mbuf->ol_flags |= pkt_flags;
 }
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 04/23] net/sxe2: support L2 filtering and MAC config
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

- Support primary/secondary MAC address setup.
- Enable L2 broadcast/multicast filter bits.
- Add multicast address update logic.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build      |   1 +
 drivers/net/sxe2/sxe2_cmd_chnl.c  | 198 ++++++++
 drivers/net/sxe2/sxe2_cmd_chnl.h  |  17 +
 drivers/net/sxe2/sxe2_drv_cmd.h   |  89 +++-
 drivers/net/sxe2/sxe2_ethdev.c    |  70 ++-
 drivers/net/sxe2/sxe2_ethdev.h    |  43 +-
 drivers/net/sxe2/sxe2_filter.c    | 784 ++++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_filter.h    |  98 ++++
 drivers/net/sxe2/sxe2_mac.c       | 432 ++++++++++++++++
 drivers/net/sxe2/sxe2_mac.h       |  36 +-
 drivers/net/sxe2/sxe2_txrx_poll.c |  49 ++
 11 files changed, 1809 insertions(+), 8 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_filter.c
 create mode 100644 drivers/net/sxe2/sxe2_filter.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index e22204e850..8ff74e5233 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -61,6 +61,7 @@ sources += files(
         'sxe2_txrx.c',
         'sxe2_txrx_vec.c',
         'sxe2_mac.c',
+        'sxe2_filter.c',
 )
 
 allow_internal_get_api = true
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index 07eeb7f38c..1fa9ad718e 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -343,3 +343,201 @@ int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter)
 l_end:
 	return ret;
 }
+
+int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_promisc_filter_cfg_req promisc_filter_cfg_req = {0};
+
+	promisc_filter_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	promisc_filter_cfg_req.is_add = set;
+	promisc_filter_cfg_req.type = SXE2_PROMISC_FILTER_TYPE_PROMISC;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_PROMISC_CFG,
+				 &promisc_filter_cfg_req,
+				 sizeof(promisc_filter_cfg_req),
+				 NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "promic config failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_promisc_filter_cfg_req promisc_filter_cfg_req = {0};
+
+	promisc_filter_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	promisc_filter_cfg_req.is_add = set;
+	promisc_filter_cfg_req.type = SXE2_PROMISC_FILTER_TYPE_ALLMULTI;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_ALLMULTI_CFG,
+				 &promisc_filter_cfg_req,
+				 sizeof(promisc_filter_cfg_req),
+				 NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "allmulti config failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add)
+{
+	int32_t ret = 0;
+	int32_t i;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_mac_filter_cfg_req mac_filter_cfg_req = {0};
+
+	mac_filter_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	for (i = 0; i < SXE2_ETH_ALEN; i++)
+		mac_filter_cfg_req.addr[i] = addr->addr_bytes[i];
+	mac_filter_cfg_req.is_add = add;
+	mac_filter_cfg_req.type = SXE2_MAC_FILTER_TYPE_UC;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_MAC_ADDR_UC,
+		 &mac_filter_cfg_req, sizeof(mac_filter_cfg_req),
+		 NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "uc config query failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_mc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add)
+{
+	int32_t ret = 0;
+	int32_t i;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_mac_filter_cfg_req mac_filter_cfg_req = {0};
+
+	mac_filter_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	for (i = 0; i < SXE2_ETH_ALEN; i++)
+		mac_filter_cfg_req.addr[i] = addr->addr_bytes[i];
+
+	mac_filter_cfg_req.is_add = add;
+	mac_filter_cfg_req.type = SXE2_MAC_FILTER_TYPE_MC;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_MAC_ADDR_MC,
+		 &mac_filter_cfg_req, sizeof(mac_filter_cfg_req),
+		 NULL, 0);
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "mac config query failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_vlan_config_query(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_drv_vlan_cfg_query_resp vlan_cfg_query_resp = {0};
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_VLAN_CFG_QUERY,
+				 NULL, 0,
+				 &vlan_cfg_query_resp,
+	 sizeof(vlan_cfg_query_resp));
+
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "vlan config query failed, ret=%d", ret);
+
+	adapter->filter_ctxt.vlan_info.port_vlan_exist = vlan_cfg_query_resp.port_vlan_exist;
+	adapter->filter_ctxt.vlan_info.is_switchdev = vlan_cfg_query_resp.is_switchdev;
+
+
+	adapter->filter_ctxt.vlan_info.tpid = vlan_cfg_query_resp.tpid;
+	adapter->filter_ctxt.vlan_info.vid = vlan_cfg_query_resp.vid;
+
+	adapter->filter_ctxt.vlan_info.outer_insert = vlan_cfg_query_resp.outer_insert;
+	adapter->filter_ctxt.vlan_info.outer_strip = vlan_cfg_query_resp.outer_strip;
+	adapter->filter_ctxt.vlan_info.inner_insert = vlan_cfg_query_resp.inner_insert;
+	adapter->filter_ctxt.vlan_info.inner_strip = vlan_cfg_query_resp.inner_strip;
+
+	return ret;
+}
+
+int32_t sxe2_drv_vlan_filter_id_config(struct sxe2_adapter *adapter,
+				       struct sxe2_vlan *vlan, bool on)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_vlan_filter_cfg_req vlan_filter_cfg_req = {0};
+
+	vlan_filter_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	vlan_filter_cfg_req.tpid_id = vlan->tpid;
+	vlan_filter_cfg_req.vlan_id = vlan->vid;
+	vlan_filter_cfg_req.prio = vlan->prio;
+	vlan_filter_cfg_req.is_add = on;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_VLAN_FILTER_ADD_DEL,
+				 &vlan_filter_cfg_req, sizeof(vlan_filter_cfg_req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "vlan config failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_vlan_insert_strip_cfg(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_drv_vlan_offload_cfg_req vlan_offload_cfg_req = {0};
+
+	vlan_offload_cfg_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	vlan_offload_cfg_req.tpid = adapter->filter_ctxt.vlan_info.tpid;
+	vlan_offload_cfg_req.outer_insert = adapter->filter_ctxt.vlan_info.outer_insert;
+	vlan_offload_cfg_req.outer_strip = adapter->filter_ctxt.vlan_info.outer_strip;
+	vlan_offload_cfg_req.inner_insert = adapter->filter_ctxt.vlan_info.inner_insert;
+	vlan_offload_cfg_req.inner_strip = adapter->filter_ctxt.vlan_info.inner_strip;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_VLAN_OFFLOAD_CFG,
+				 &vlan_offload_cfg_req,
+				 sizeof(vlan_offload_cfg_req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "vlan config query failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_vlan_filter_switch(struct sxe2_adapter *adapter, bool on)
+{
+	int32_t ret = 0;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_vlan_filter_switch_req vlan_filter_switch_req = {0};
+
+	vlan_filter_switch_req.vsi_id = adapter->vsi_ctxt.dpdk_vsi_id;
+	vlan_filter_switch_req.is_oper_enable = on;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_VLAN_FILTER_SWITCH,
+				 &vlan_filter_switch_req,
+				 sizeof(vlan_filter_switch_req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_WARN(adapter, DRV, "vlan config filter failed, ret=%d", ret);
+
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index 34004d37e2..fb01c41aad 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -36,4 +36,21 @@ int32_t sxe2_drv_txq_ctxt_cfg(struct sxe2_adapter *adapter,
 
 int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter);
 
+int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set);
+
+int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set);
+
+int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
+
+int32_t sxe2_drv_mc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
+
+int32_t sxe2_drv_vlan_config_query(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_vlan_filter_id_config(struct sxe2_adapter *adapter,
+				       struct sxe2_vlan *vlan, bool on);
+
+int32_t sxe2_drv_vlan_insert_strip_cfg(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_vlan_filter_switch(struct sxe2_adapter *adapter, bool on);
+
 #endif /* __SXE2_CMD_CHNL_H__ */
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index e0ec70638e..c9d7cc719b 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -111,6 +111,17 @@ enum sxe2_phys_port_name_type {
 	SXE2_PHYS_PORT_NAME_TYPE_UNKNOWN,
 };
 
+struct sxe2_switchdev_info {
+	uint8_t is_switchdev;
+	uint8_t master;
+	uint8_t representor;
+	uint8_t port_name_type;
+	uint32_t ctrl_num;
+	uint32_t pf_num;
+	uint32_t vf_num;
+	uint32_t mpesw_owner;
+};
+
 struct sxe2_switchdev_mode_info {
 	uint8_t pf_id;
 	uint8_t is_switchdev;
@@ -228,11 +239,87 @@ struct sxe2_drv_vsi_info_get_resp {
 };
 
 struct sxe2_drv_link_info_resp {
-	__le32 speed;
+	uint32_t speed;
 	uint8_t status;
 	uint8_t rsv[3];
 };
 
+struct sxe2_drv_vlan_cfg_query_resp {
+	uint16_t vsi_id;
+	uint8_t port_vlan_exist;
+	uint8_t is_switchdev;
+	uint16_t tpid;
+	uint16_t vid;
+	uint8_t outer_insert;
+	uint8_t outer_strip;
+	uint8_t inner_insert;
+	uint8_t inner_strip;
+};
+
+struct sxe2_drv_vlan_offload_cfg_req {
+	uint16_t vsi_id;
+	uint16_t tpid;
+	uint8_t outer_insert;
+	uint8_t outer_strip;
+	uint8_t inner_insert;
+	uint8_t inner_strip;
+};
+
+struct sxe2_drv_port_vlan_cfg_req {
+	uint16_t vsi_id;
+	uint16_t tpid;
+	uint16_t vid;
+	uint8_t prio;
+	uint8_t rsv;
+};
+
+enum sxe2_mac_filter_type {
+	SXE2_MAC_FILTER_TYPE_UC = 0,
+	SXE2_MAC_FILTER_TYPE_MC,
+	SXE2_MAC_FILTER_TYPE_MAX,
+};
+
+struct sxe2_mac_filter_cfg_req {
+	uint16_t vsi_id;
+	uint8_t addr[SXE2_ETH_ALEN];
+	uint8_t type;
+	uint8_t is_add;
+	uint8_t rsv[2];
+};
+
+enum sxe2_promisc_filter_type {
+	SXE2_PROMISC_FILTER_TYPE_PROMISC = 0,
+	SXE2_PROMISC_FILTER_TYPE_ALLMULTI,
+	SXE2_PROMISC_FILTER_TYPE_MAX,
+};
+
+struct sxe2_promisc_filter_cfg_req {
+	uint16_t vsi_id;
+	uint8_t type;
+	uint8_t is_add;
+};
+
+struct sxe2_srcvsi_ext_cfg_req {
+	uint16_t vsi_id;
+	uint16_t srcvsi_list[SXE2_SRCVSI_PRUNE_MAX_NUM];
+	uint8_t srcvsi_cnt;
+	uint8_t is_add;
+};
+
+struct sxe2_vlan_filter_cfg_req {
+	uint16_t vsi_id;
+	uint16_t vlan_id;
+	uint16_t tpid_id;
+	uint8_t prio;
+	uint8_t is_add;
+};
+
+struct sxe2_vlan_filter_switch_req {
+	uint16_t vsi_id;
+	uint8_t is_oper_enable;
+	uint8_t rsv;
+};
+
 enum sxe2_drv_cmd_module {
 	SXE2_DRV_CMD_MODULE_HANDSHAKE = 0,
 	SXE2_DRV_CMD_MODULE_DEV = 1,
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index 01552a8202..9b117f097e 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -111,8 +111,20 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.tx_burst_mode_get          = sxe2_tx_burst_mode_get,
 	.tx_done_cleanup            = sxe2_tx_done_cleanup,
 
+	.promiscuous_enable         = sxe2_promisc_enable,
+	.promiscuous_disable        = sxe2_promisc_disable,
+	.allmulticast_enable        = sxe2_allmulti_enable,
+	.allmulticast_disable       = sxe2_allmulti_disable,
+
+	.mac_addr_add               = sxe2_mac_addr_add,
+	.mac_addr_remove            = sxe2_mac_addr_del,
+	.mac_addr_set               = sxe2_mac_addr_set,
+	.set_mc_addr_list           = sxe2_set_mc_addr_list,
 	.mtu_set                    = sxe2_mtu_set,
 	.buffer_split_supported_hdr_ptypes_get = sxe2_buffer_split_supported_hdr_ptypes_get,
+
+	.vlan_filter_set            = sxe2_dev_vlan_filter_set,
+	.vlan_offload_set           = sxe2_dev_vlan_offload_set,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
@@ -123,6 +135,13 @@ static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
 	if (dev->data->dev_conf.rxmode.mq_mode  & RTE_ETH_MQ_RX_RSS_FLAG)
 		dev->data->dev_conf.rxmode.offloads |= RTE_ETH_RX_OFFLOAD_RSS_HASH;
 
+	ret = sxe2_vlan_default_cfg(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init vlan, ret=%d", ret);
+		goto end;
+	}
+
+end:
 	return ret;
 }
 
@@ -138,6 +157,8 @@ static int32_t sxe2_dev_stop(struct rte_eth_dev *dev)
 	sxe2_txqs_all_stop(dev);
 	sxe2_rxqs_all_stop(dev);
 
+	(void)sxe2_filter_rule_stop(dev);
+
 	dev->data->dev_started = 0;
 	adapter->started = 0;
 l_end:
@@ -165,16 +186,23 @@ static int32_t sxe2_dev_start(struct rte_eth_dev *dev)
 		goto l_end;
 	}
 
+	ret = sxe2_filter_rule_start(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to add all mc addr to fw.");
+		goto l_end;
+	}
+
 	ret = sxe2_queues_start(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "enable queues failed");
-		goto l_end;
+		goto l_start_queues_err;
 	}
 
 	dev->data->dev_started = 1;
 	adapter->started = 1;
 	goto l_end;
-
+l_start_queues_err:
+	(void)sxe2_filter_rule_stop(dev);
 l_end:
 	return ret;
 }
@@ -194,6 +222,7 @@ static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev,
 	dev_info->min_mtu = RTE_ETHER_MIN_MTU;
 
 	dev_info->rx_offload_capa =
+		RTE_ETH_RX_OFFLOAD_VLAN_STRIP |
 		RTE_ETH_RX_OFFLOAD_KEEP_CRC |
 		RTE_ETH_RX_OFFLOAD_SCATTER |
 		RTE_ETH_RX_OFFLOAD_IPV4_CKSUM |
@@ -202,9 +231,15 @@ static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev,
 		RTE_ETH_RX_OFFLOAD_SCTP_CKSUM |
 		RTE_ETH_RX_OFFLOAD_OUTER_IPV4_CKSUM |
 		RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT |
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+		RTE_ETH_RX_OFFLOAD_QINQ_STRIP |
+#endif
+		RTE_ETH_RX_OFFLOAD_VLAN_EXTEND |
 		RTE_ETH_RX_OFFLOAD_TCP_LRO;
 
 	dev_info->tx_offload_capa =
+		RTE_ETH_TX_OFFLOAD_VLAN_INSERT |
+		RTE_ETH_TX_OFFLOAD_QINQ_INSERT |
 		RTE_ETH_TX_OFFLOAD_MULTI_SEGS |
 		RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE |
 		RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
@@ -428,6 +463,12 @@ static int32_t sxe2_eth_init(struct rte_eth_dev *dev)
 {
 	int32_t ret = 0;
 
+	ret = sxe2_filter_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize l2 filter, ret:%d", ret);
+		goto l_end;
+	}
+
 	ret = sxe2_link_update_init(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to initialize link update, ret:%d", ret);
@@ -439,12 +480,37 @@ static int32_t sxe2_eth_init(struct rte_eth_dev *dev)
 		PMD_LOG_ERR(INIT, "Failed to set mtu, ret=%d", ret);
 		goto l_end;
 	}
+
+	ret = sxe2_mac_addr_init(dev);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to initialize mac address, ret:%d", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_mac_default_cfg(dev);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to configure default mac address, ret:%d", ret);
+		goto l_err;
+	}
+
+	ret = sxe2_vlan_cfg_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize vlan config, ret:%d", ret);
+		goto l_err;
+	}
+	goto l_end;
+
+l_err:
+	sxe2_mac_addr_uinit(dev);
+	(void)sxe2_filter_uinit(dev);
 l_end:
 	return ret;
 }
 
 static void sxe2_eth_uinit(struct rte_eth_dev *dev __rte_unused)
 {
+	sxe2_mac_addr_uinit(dev);
+	(void)sxe2_filter_uinit(dev);
 }
 
 static void sxe2_drv_dev_caps_set(struct sxe2_adapter *adapter,
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 66f49ac0cc..cc8a84c0a0 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -13,9 +13,11 @@
 
 #include "sxe2_common.h"
 #include "sxe2_vsi.h"
-#include "sxe2_queue.h"
 #include "sxe2_irq.h"
+#include "sxe2_queue.h"
+#include "sxe2_mac.h"
 #include "sxe2_osal.h"
+#include "sxe2_filter.h"
 
 struct sxe2_link_msg {
 	uint32_t speed;
@@ -33,7 +35,7 @@ enum sxe2_fnav_tunnel_flag_type {
 #define SXE2_FRAME_SIZE_MAX    9832
 #define SXE2_VLAN_TAG_SIZE     4
 #define SXE2_ETH_OVERHEAD \
-	(RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN + SXE2_VLAN_TAG_SIZE)
+	(RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN + 2 * SXE2_VLAN_TAG_SIZE)
 #define SXE2_ETH_MAX_LEN (RTE_ETHER_MTU + SXE2_ETH_OVERHEAD)
 
 #ifdef SXE2_TEST
@@ -265,6 +267,27 @@ struct sxe2_link_context {
 	uint32_t  speed;
 };
 
+struct sxe2_filter_context {
+	rte_spinlock_t filter_lock;
+	struct sxe2_vlan_info               vlan_info;
+	struct sxe2_uc_filter_list_head    uc_list;
+	struct sxe2_mc_filter_list_head    mc_list;
+	struct sxe2_vlan_filter_list_head  vlan_list;
+	uint8_t                                 uc_num;
+	uint8_t                                 mc_num;
+	uint8_t                                 vlan_num;
+	uint8_t                                 rsv;
+	uint32_t hw_promisc_flags;
+	uint32_t cur_promisc_flags;
+
+	bool hw_uplink_config;
+	bool cur_uplink_config;
+	bool hw_repr_config;
+	bool cur_repr_config;
+	bool hw_l2_config;
+	bool cur_l2_config;
+};
+
 struct sxe2_adapter {
 	struct sxe2_common_device      *cdev;
 	struct sxe2_dev_info            dev_info;
@@ -274,10 +297,14 @@ struct sxe2_adapter {
 	struct sxe2_irq_context       irq_ctxt;
 	struct sxe2_queue_context     q_ctxt;
 	struct sxe2_vsi_context       vsi_ctxt;
+	struct sxe2_filter_context    filter_ctxt;
 	struct sxe2_link_context      link_ctxt;
 	struct sxe2_devargs           devargs;
-	uint16_t                      dev_port_id;
-	uint64_t                      cap_flags;
+	struct sxe2_switchdev_info    switchdev_info;
+	bool                          rule_started;
+	bool                          flow_isolated;
+	uint16_t                           dev_port_id;
+	uint64_t                           cap_flags;
 	enum sxe2_dev_type            dev_type;
 	uint32_t    ptype_tbl[SXE2_MAX_PTYPE_NUM];
 	struct rte_ether_addr           mac_addr;
@@ -316,4 +343,12 @@ int32_t sxe2_dev_pci_map_init(struct rte_eth_dev *dev);
 
 void sxe2_dev_pci_map_uinit(struct rte_eth_dev *dev);
 
+static inline bool
+sxe2_dev_port_vlan_check(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *ad = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	return ad->filter_ctxt.vlan_info.port_vlan_exist;
+}
+
 #endif /* __SXE2_ETHDEV_H__ */
diff --git a/drivers/net/sxe2/sxe2_filter.c b/drivers/net/sxe2/sxe2_filter.c
new file mode 100644
index 0000000000..cfeeb7a6c3
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_filter.c
@@ -0,0 +1,784 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_os.h>
+#include <rte_tailq.h>
+#include "sxe2_osal.h"
+#include "sxe2_mac.h"
+#include "sxe2_common_log.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_cmd_chnl.h"
+
+static struct sxe2_mac_filter *sxe2_uc_filter_find(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *macaddr)
+{
+	struct sxe2_mac_filter *filter      = NULL;
+	struct sxe2_mac_filter *entry       = NULL;
+	struct sxe2_mac_filter *next_entry  = NULL;
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	RTE_TAILQ_FOREACH_SAFE(entry, &adapter->filter_ctxt.uc_list, next, next_entry) {
+		if (rte_is_same_ether_addr(macaddr, &entry->mac_addr)) {
+			filter = entry;
+			break;
+		}
+	}
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+
+	return filter;
+}
+
+int32_t sxe2_uc_filter_add(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr, bool default_config)
+{
+	struct sxe2_mac_filter *filter = NULL;
+	bool hw_config = false;
+	int32_t ret = 0;
+
+	filter = sxe2_uc_filter_find(adapter, mac_addr);
+	if (filter) {
+		if (default_config && !filter->default_config)
+			filter->default_config = true;
+		PMD_DEV_LOG_INFO(adapter, DRV, "This MAC filter already exists.");
+		goto l_end;
+	}
+
+	if (!adapter->rule_started) {
+		PMD_DEV_LOG_DEBUG(adapter, DRV, "cannot add hw uc addr in port stop status");
+	} else if (adapter->flow_isolated) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add hw uc addr in flow isolation mode");
+	} else if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add hw uc addr in switchdev mode");
+	} else {
+		ret = sxe2_drv_uc_config(adapter, mac_addr, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add uc rule");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		hw_config = true;
+	}
+
+	filter = rte_zmalloc("sxe2_uc_filter",
+			     sizeof(struct sxe2_mac_filter), 0);
+	if (!filter) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to allocate memory");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	filter->hw_config = hw_config;
+	filter->default_config = default_config;
+	rte_ether_addr_copy(mac_addr, &filter->mac_addr);
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_INSERT_TAIL(&adapter->filter_ctxt.uc_list, filter, next);
+	adapter->filter_ctxt.uc_num++;
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "add mac rule, mac num %u.", adapter->filter_ctxt.uc_num);
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_uc_filter_del(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr)
+{
+	struct sxe2_mac_filter *filter = NULL;
+	int32_t ret                         = -1;
+
+	filter = sxe2_uc_filter_find(adapter, mac_addr);
+	if (!filter) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "This MAC filter not exists.");
+		ret = 0;
+		goto l_end;
+	}
+	if (filter->hw_config) {
+		ret = sxe2_drv_uc_config(adapter, mac_addr, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete mac rule");
+			if (ret == -EPERM)
+				goto l_free;
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+	PMD_DEV_LOG_INFO(adapter, DRV, "remove mac rule, uc num %u.", adapter->filter_ctxt.uc_num);
+	ret = 0;
+
+l_free:
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_REMOVE(&adapter->filter_ctxt.uc_list, filter, next);
+	adapter->filter_ctxt.uc_num--;
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+	rte_free(filter);
+	filter = NULL;
+l_end:
+	return ret;
+}
+
+void sxe2_uc_filter_clear(struct sxe2_adapter *adapter, bool default_config)
+{
+	struct sxe2_mac_filter *entry;
+	struct sxe2_mac_filter *next_entry;
+
+	RTE_TAILQ_FOREACH_SAFE(entry, &adapter->filter_ctxt.uc_list, next, next_entry) {
+		if (entry->default_config && !default_config)
+			continue;
+
+		if (sxe2_uc_filter_del(adapter, &entry->mac_addr))
+			PMD_DEV_LOG_ERR(adapter, DRV, "This MAC filter delete fail.");
+	}
+}
+
+static struct sxe2_mac_filter *sxe2_mc_filter_find(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *macaddr)
+{
+	struct sxe2_mac_filter *filter      = NULL;
+	struct sxe2_mac_filter *entry       = NULL;
+	struct sxe2_mac_filter *next_entry  = NULL;
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	RTE_TAILQ_FOREACH_SAFE(entry, &adapter->filter_ctxt.mc_list, next, next_entry) {
+		if (rte_is_same_ether_addr(macaddr, &entry->mac_addr)) {
+			filter = entry;
+			break;
+		}
+	}
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+
+	return filter;
+}
+
+int32_t sxe2_mc_filter_add(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr, bool default_config)
+{
+	struct sxe2_mac_filter *filter = NULL;
+	bool hw_config = false;
+	int32_t ret = 0;
+
+	filter = sxe2_mc_filter_find(adapter, mac_addr);
+	if (filter) {
+		if (default_config && !filter->default_config)
+			filter->default_config = true;
+		PMD_DEV_LOG_INFO(adapter, DRV, "This MAC filter already exists.");
+		goto l_end;
+	}
+
+	if (!adapter->rule_started) {
+		PMD_DEV_LOG_DEBUG(adapter, DRV, "cannot add hw mc addr in port stop status");
+	} else if (adapter->flow_isolated) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add hw mc addr in flow isolation mode");
+	} else if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add hw mc addr in switchdev mode");
+	} else {
+		ret = sxe2_drv_mc_config(adapter, mac_addr, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add mac rule");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		hw_config = true;
+	}
+
+	filter = rte_zmalloc("sxe2_mc_filter",
+			     sizeof(struct sxe2_mac_filter), 0);
+	if (!filter) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to allocate memory");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	filter->hw_config = hw_config;
+	filter->default_config = default_config;
+	rte_ether_addr_copy(mac_addr, &filter->mac_addr);
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_INSERT_TAIL(&adapter->filter_ctxt.mc_list, filter, next);
+	adapter->filter_ctxt.mc_num++;
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "add mc rule, mc num %u.", adapter->filter_ctxt.mc_num);
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_mc_filter_del(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr)
+{
+	struct sxe2_mac_filter *filter = NULL;
+	int32_t ret                         = -1;
+
+	filter = sxe2_mc_filter_find(adapter, mac_addr);
+	if (!filter) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "This MAC filter not exists.");
+		ret = 0;
+		goto l_end;
+	}
+
+	if (filter->hw_config) {
+		ret = sxe2_drv_mc_config(adapter, mac_addr, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete mc rule");
+			if (ret == -EPERM)
+				goto l_free;
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+	PMD_DEV_LOG_INFO(adapter, DRV, "remove mc rule, mc num %u.", adapter->filter_ctxt.mc_num);
+	ret = 0;
+
+l_free:
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_REMOVE(&adapter->filter_ctxt.mc_list, filter, next);
+	adapter->filter_ctxt.mc_num--;
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+	rte_free(filter);
+	filter = NULL;
+l_end:
+	return ret;
+}
+
+void sxe2_mc_filter_clear(struct sxe2_adapter *adapter, bool default_config)
+{
+	struct sxe2_mac_filter *entry;
+	struct sxe2_mac_filter *next_entry;
+
+	RTE_TAILQ_FOREACH_SAFE(entry, &adapter->filter_ctxt.mc_list, next, next_entry) {
+		if (entry->default_config && !default_config)
+			continue;
+		if (sxe2_mc_filter_del(adapter, &entry->mac_addr))
+			PMD_DEV_LOG_ERR(adapter, DRV, "This MAC filter delete fail.");
+	}
+}
+
+static struct sxe2_vlan_filter *sxe2_vlan_filter_find(struct sxe2_adapter *adapter,
+			struct sxe2_vlan *vlan)
+{
+	struct sxe2_vlan_filter *f;
+	struct sxe2_vlan_filter *save_f = NULL;
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_FOREACH(f, &adapter->filter_ctxt.vlan_list, next)
+	{
+		if (vlan->tpid == f->vlan_info.tpid &&
+			vlan->vid == f->vlan_info.vid) {
+			save_f = f;
+			break;
+		}
+	}
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+
+	return save_f;
+}
+
+int32_t sxe2_vlan_filter_add(struct sxe2_adapter *adapter,
+			     struct sxe2_vlan *vlan, bool default_config)
+{
+	struct sxe2_vlan_filter *filter = NULL;
+	bool hw_config                 = false;
+	int32_t ret                    = 0;
+
+	if (!vlan || vlan->vid > RTE_ETHER_MAX_VLAN_ID) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "This vlan filter is invalid.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	filter = sxe2_vlan_filter_find(adapter, vlan);
+	if (filter) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "This vlan filter already exists.");
+		ret = 0;
+		goto l_end;
+	}
+	if (!adapter->rule_started) {
+		PMD_DEV_LOG_DEBUG(adapter, DRV, "cannot add vlan in port stop status");
+	} else if (adapter->flow_isolated) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add vlan in flow isolation mode");
+	} else if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add vlan in switchdev mode");
+	} else {
+		ret = sxe2_drv_vlan_filter_id_config(adapter, vlan, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add vlan rule");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		hw_config = true;
+	}
+
+	filter = rte_zmalloc("sxe2_vlan_filter", sizeof(*filter), 0);
+	if (!filter) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to allocate memory");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	filter->hw_config = hw_config;
+	filter->default_config = default_config;
+
+	filter->vlan_info.tpid = vlan->tpid;
+	filter->vlan_info.vid = vlan->vid;
+	filter->vlan_info.prio = vlan->prio;
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_INSERT_TAIL(&adapter->filter_ctxt.vlan_list, filter, next);
+	adapter->filter_ctxt.vlan_num++;
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_vlan_filter_del(struct sxe2_adapter *adapter, struct sxe2_vlan *vlan)
+{
+	struct sxe2_vlan_filter *filter = NULL;
+	int32_t ret                         = -1;
+
+	if (!vlan || vlan->vid > RTE_ETHER_MAX_VLAN_ID) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "This vlan filter is invalid.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	filter = sxe2_vlan_filter_find(adapter, vlan);
+	if (!filter) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "This vlan filter not exists.");
+		ret = 0;
+		goto l_end;
+	}
+
+	if (filter->hw_config) {
+		ret = sxe2_drv_vlan_filter_id_config(adapter, vlan, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete vlan rule");
+			if (ret == -EPERM)
+				goto l_free;
+			ret = -EINVAL;
+			goto l_end;
+		}
+	}
+	ret = 0;
+
+l_free:
+
+	rte_spinlock_lock(&adapter->filter_ctxt.filter_lock);
+	TAILQ_REMOVE(&adapter->filter_ctxt.vlan_list, filter, next);
+	adapter->filter_ctxt.vlan_num--;
+	rte_spinlock_unlock(&adapter->filter_ctxt.filter_lock);
+	rte_free(filter);
+	filter = NULL;
+l_end:
+	return ret;
+}
+
+void sxe2_vlan_filters_clear(struct sxe2_adapter *adapter, bool default_config)
+{
+	int32_t ret = 0;
+	struct sxe2_vlan_filter *v_f;
+	void *temp;
+
+	if (adapter->filter_ctxt.vlan_num == 0)
+		goto l_end;
+
+	RTE_TAILQ_FOREACH_SAFE(v_f, &adapter->filter_ctxt.vlan_list, next, temp)
+	{
+		if (v_f->default_config && !default_config)
+			continue;
+		ret = sxe2_vlan_filter_del(adapter, &v_f->vlan_info);
+		if (ret)
+			PMD_DEV_LOG_ERR(adapter, DRV, "This vlan filter delete fail.");
+	}
+
+l_end:
+}
+
+int32_t sxe2_vlan_filter_ctrl(struct sxe2_adapter *adapter, bool flag)
+{
+	struct sxe2_vlan_info *vlan_info = &adapter->filter_ctxt.vlan_info;
+	int32_t ret = 0;
+
+	if (vlan_info->filter_on == flag)
+		goto l_end;
+	if (!adapter->rule_started) {
+		PMD_DEV_LOG_DEBUG(adapter, DRV, "cannot add vlan filter ctrl in port stop status");
+	} else if (adapter->flow_isolated) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add vlan filter ctrl in flow isolation mode");
+	} else if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot add vlan filter ctrl in switchdev mode");
+	} else {
+		ret = sxe2_drv_vlan_filter_switch(adapter, flag);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add vlan filter ctrl");
+			goto l_end;
+		}
+		vlan_info->hw_filter_on = flag;
+	}
+	vlan_info->filter_on = flag;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_promisc_add(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->rule_started) {
+		PMD_DEV_LOG_DEBUG(adapter, DRV, "cannot enable promiscuous in port stop status");
+	} else if (adapter->flow_isolated) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot enable promiscuous in flow isolation mode");
+	} else if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot enable promiscuous in switchdev mode");
+	} else if (!(adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC)) {
+		ret = sxe2_drv_promisc_config(adapter, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "failed to cfg promiscuous, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags |= SXE2_PROMISC;
+	}
+	adapter->filter_ctxt.cur_promisc_flags |= SXE2_PROMISC;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_promisc_del(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->flow_isolated &&
+	    (adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC)) {
+		ret = sxe2_drv_promisc_config(adapter, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "failed to cfg promiscuous, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags &= ~SXE2_PROMISC;
+	}
+
+	adapter->filter_ctxt.cur_promisc_flags &= ~SXE2_PROMISC;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_allmulti_add(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->rule_started) {
+		PMD_DEV_LOG_DEBUG(adapter, DRV, "cannot enable allmulticast in port stop status");
+	} else if (adapter->flow_isolated) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot enable allmulticast in flow isolation mode");
+	} else if (adapter->switchdev_info.is_switchdev) {
+		PMD_DEV_LOG_WARN(adapter, DRV, "cannot enable allmulticast in switchdev mode");
+	} else if (!(adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC_MULTICAST)) {
+		ret = sxe2_drv_allmulti_config(adapter, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "failed to cfg allmulticast, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags |= SXE2_PROMISC_MULTICAST;
+	}
+	adapter->filter_ctxt.cur_promisc_flags |= SXE2_PROMISC_MULTICAST;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_allmulti_del(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->flow_isolated &&
+	    (adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC_MULTICAST)) {
+		ret = sxe2_drv_allmulti_config(adapter, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "failed to cfg allmulticast, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags &= ~SXE2_PROMISC_MULTICAST;
+	}
+
+	adapter->filter_ctxt.cur_promisc_flags &= ~SXE2_PROMISC_MULTICAST;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_all_filter_hw_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	struct sxe2_mac_filter *mac_entry;
+	struct sxe2_mac_filter *next_mac_entry;
+	struct sxe2_vlan_filter *vlan_entry;
+	struct sxe2_vlan_filter *next_vlan_entry;
+
+	if (adapter->filter_ctxt.uc_num > 0) {
+		RTE_TAILQ_FOREACH_SAFE(mac_entry, &adapter->filter_ctxt.uc_list, next,
+			    next_mac_entry) {
+			if (mac_entry->hw_config) {
+				ret = sxe2_drv_uc_config(adapter, &mac_entry->mac_addr, false);
+				if (ret) {
+					PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete mac rule");
+					ret = -EINVAL;
+					goto l_end;
+				}
+				mac_entry->hw_config = false;
+			}
+		}
+	}
+
+	if (adapter->filter_ctxt.mc_num > 0) {
+		RTE_TAILQ_FOREACH_SAFE(mac_entry, &adapter->filter_ctxt.mc_list, next,
+			    next_mac_entry) {
+			if (mac_entry->hw_config) {
+				ret = sxe2_drv_mc_config(adapter, &mac_entry->mac_addr, false);
+				if (ret) {
+					PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete mc rule");
+					ret = -EINVAL;
+					goto l_end;
+				}
+				mac_entry->hw_config = false;
+			}
+		}
+	}
+
+	if (adapter->filter_ctxt.vlan_num > 0) {
+		RTE_TAILQ_FOREACH_SAFE(vlan_entry, &adapter->filter_ctxt.vlan_list, next,
+			    next_vlan_entry) {
+			if (vlan_entry->hw_config) {
+				ret = sxe2_drv_vlan_filter_id_config(adapter,
+				    &vlan_entry->vlan_info, false);
+				if (ret) {
+					PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete vlan rule");
+					ret = -EINVAL;
+					goto l_end;
+				}
+				vlan_entry->hw_config = false;
+			}
+		}
+	}
+
+	if (adapter->filter_ctxt.vlan_info.hw_filter_on) {
+		ret = sxe2_drv_vlan_filter_switch(adapter, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to delete vlan rule");
+			ret = -EINVAL;
+			goto l_end;
+		}
+		adapter->filter_ctxt.vlan_info.hw_filter_on = false;
+	}
+
+	if (adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC) {
+		ret = sxe2_drv_promisc_config(adapter, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "failed to cfg promiscuous, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags &= ~SXE2_PROMISC;
+	}
+
+	if (adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC_MULTICAST) {
+		ret = sxe2_drv_allmulti_config(adapter, false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "failed to cfg allmulticast, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags &= ~SXE2_PROMISC_MULTICAST;
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_all_filter_hw_set(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+	struct sxe2_mac_filter *mac_entry;
+	struct sxe2_mac_filter *next_mac_entry;
+	struct sxe2_vlan_filter *vlan_entry;
+	struct sxe2_vlan_filter *next_vlan_entry;
+
+	if (adapter->filter_ctxt.uc_num > 0) {
+		RTE_TAILQ_FOREACH_SAFE(mac_entry, &adapter->filter_ctxt.uc_list, next,
+				       next_mac_entry) {
+			if (!mac_entry->hw_config) {
+				ret = sxe2_drv_uc_config(adapter, &mac_entry->mac_addr,
+							 true);
+				if (ret && ret != -EEXIST) {
+					PMD_DEV_LOG_ERR(adapter, DRV,
+							"Failed to add uc rule, ret:%d", ret);
+					ret = -EINVAL;
+					goto l_end;
+				}
+				mac_entry->hw_config = true;
+				ret = 0;
+			}
+		}
+	}
+
+	if (adapter->filter_ctxt.mc_num > 0) {
+		RTE_TAILQ_FOREACH_SAFE(mac_entry, &adapter->filter_ctxt.mc_list, next,
+				       next_mac_entry) {
+			if (!mac_entry->hw_config) {
+				ret = sxe2_drv_mc_config(adapter, &mac_entry->mac_addr, true);
+				if (ret && ret != -EEXIST) {
+					PMD_DEV_LOG_ERR(adapter, DRV,
+							"Failed to add mc rule, ret:%d", ret);
+					ret = -EINVAL;
+					goto l_end;
+				}
+				mac_entry->hw_config = true;
+				ret = 0;
+			}
+		}
+	}
+
+	if (adapter->filter_ctxt.vlan_num > 0) {
+		RTE_TAILQ_FOREACH_SAFE(vlan_entry, &adapter->filter_ctxt.vlan_list, next,
+				       next_vlan_entry) {
+			if (!vlan_entry->hw_config) {
+				ret = sxe2_drv_vlan_filter_id_config(adapter,
+				    &vlan_entry->vlan_info, true);
+				if (ret && ret != -EEXIST) {
+					PMD_DEV_LOG_ERR(adapter, DRV,
+							"Failed to add vlan rule, ret:%d", ret);
+					ret = -EINVAL;
+					goto l_end;
+				}
+				vlan_entry->hw_config = true;
+				ret = 0;
+			}
+		}
+	}
+
+	if (adapter->filter_ctxt.vlan_info.filter_on) {
+		if (!(adapter->filter_ctxt.vlan_info.hw_filter_on)) {
+			ret = sxe2_drv_vlan_filter_switch(adapter, true);
+			if (ret && ret != -EEXIST) {
+				PMD_DEV_LOG_ERR(adapter, DRV,
+						"Failed to add vlan ctrl, ret:%d", ret);
+				ret = -EINVAL;
+				goto l_end;
+			}
+			adapter->filter_ctxt.vlan_info.hw_filter_on = true;
+			ret = 0;
+		}
+	}
+
+	if ((adapter->filter_ctxt.cur_promisc_flags & SXE2_PROMISC) &&
+	    (!(adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC))) {
+		ret = sxe2_drv_promisc_config(adapter, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV,
+					"Failed to set promisc, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags |= SXE2_PROMISC;
+		ret = 0;
+	}
+
+	if ((adapter->filter_ctxt.cur_promisc_flags & SXE2_PROMISC_MULTICAST) &&
+	    (!(adapter->filter_ctxt.hw_promisc_flags & SXE2_PROMISC_MULTICAST))) {
+		ret = sxe2_drv_allmulti_config(adapter, true);
+		if (ret && ret != -EEXIST) {
+			PMD_DEV_LOG_ERR(adapter, DRV,
+					"Failed to set allmulti, ret:%d", ret);
+			goto l_end;
+		}
+		adapter->filter_ctxt.hw_promisc_flags |= SXE2_PROMISC_MULTICAST;
+		ret = 0;
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_l2_rule_update(struct sxe2_adapter *adapter)
+{
+	int32_t ret = 0;
+
+	if (!adapter->flow_isolated && !adapter->switchdev_info.is_switchdev &&
+	    adapter->rule_started) {
+		adapter->filter_ctxt.cur_l2_config = true;
+	} else {
+		adapter->filter_ctxt.cur_l2_config = false;
+	}
+
+	if (adapter->filter_ctxt.cur_l2_config !=
+	    adapter->filter_ctxt.hw_l2_config) {
+		if (adapter->filter_ctxt.cur_l2_config) {
+			ret = sxe2_all_filter_hw_set(adapter);
+			if (!ret)
+				adapter->filter_ctxt.hw_l2_config = true;
+		} else {
+			ret = sxe2_all_filter_hw_clear(adapter);
+			if (!ret)
+				adapter->filter_ctxt.hw_l2_config = false;
+		}
+	}
+	return ret;
+}
+
+int32_t sxe2_filter_rule_stop(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	adapter->rule_started = 0;
+
+	ret = sxe2_l2_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update l2 rule");
+
+	return ret;
+}
+
+int32_t sxe2_filter_rule_start(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	adapter->rule_started = 1;
+
+	ret = sxe2_l2_rule_update(adapter);
+	if (ret != 0)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to update l2 rule");
+
+	return ret;
+}
+
+int32_t sxe2_filter_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	rte_spinlock_init(&adapter->filter_ctxt.filter_lock);
+
+	TAILQ_INIT(&adapter->filter_ctxt.uc_list);
+	adapter->filter_ctxt.uc_num = 0;
+
+	TAILQ_INIT(&adapter->filter_ctxt.mc_list);
+	adapter->filter_ctxt.mc_num = 0;
+
+	TAILQ_INIT(&adapter->filter_ctxt.vlan_list);
+	adapter->filter_ctxt.vlan_num = 0;
+	return 0;
+}
+
+int32_t sxe2_filter_uinit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	sxe2_uc_filter_clear(adapter, true);
+	adapter->filter_ctxt.uc_num = 0;
+
+	sxe2_mc_filter_clear(adapter, true);
+	adapter->filter_ctxt.mc_num = 0;
+
+	sxe2_vlan_filters_clear(adapter, true);
+	adapter->filter_ctxt.vlan_num = 0;
+	return 0;
+}
diff --git a/drivers/net/sxe2/sxe2_filter.h b/drivers/net/sxe2/sxe2_filter.h
new file mode 100644
index 0000000000..6262e8c845
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_filter.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_FILTER_H__
+#define __SXE2_FILTER_H__
+#include <ethdev_driver.h>
+
+#define SXE2_PROMISC  (1UL << 0UL)
+#define SXE2_PROMISC_MULTICAST  (1UL << 1UL)
+
+struct sxe2_vlan_info {
+	uint8_t port_vlan_exist;
+	uint8_t is_switchdev;
+	uint16_t max_cnt;
+	uint16_t cnt;
+
+	bool filter_on;
+	bool hw_filter_on;
+
+	uint16_t tpid;
+	uint16_t vid;
+
+	uint8_t outer_insert;
+	uint8_t outer_strip;
+	uint8_t inner_insert;
+	uint8_t inner_strip;
+};
+
+struct sxe2_vlan {
+	uint16_t tpid;
+	uint16_t vid;
+	uint8_t prio;
+};
+
+struct sxe2_vlan_filter {
+	TAILQ_ENTRY(sxe2_vlan_filter) next;
+	bool hw_config;
+	bool default_config;
+	struct sxe2_vlan vlan_info;
+};
+
+TAILQ_HEAD(sxe2_vlan_filter_list_head, sxe2_vlan_filter);
+
+struct sxe2_mac_filter {
+	TAILQ_ENTRY(sxe2_mac_filter) next;
+	bool hw_config;
+	bool default_config;
+	struct rte_ether_addr mac_addr;
+};
+
+TAILQ_HEAD(sxe2_uc_filter_list_head, sxe2_mac_filter);
+TAILQ_HEAD(sxe2_mc_filter_list_head, sxe2_mac_filter);
+
+int32_t sxe2_uc_filter_add(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr, bool default_config);
+
+int32_t sxe2_uc_filter_del(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr);
+
+void sxe2_uc_filter_clear(struct sxe2_adapter *adapter, bool default_config);
+
+int32_t sxe2_mc_filter_add(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr, bool default_config);
+
+int32_t sxe2_mc_filter_del(struct sxe2_adapter *adapter,
+			struct rte_ether_addr *mac_addr);
+
+void sxe2_mc_filter_clear(struct sxe2_adapter *adapter, bool default_config);
+
+int32_t sxe2_vlan_filter_add(struct sxe2_adapter *adapter,
+	struct sxe2_vlan *vlan, bool default_config);
+
+int32_t sxe2_vlan_filter_del(struct sxe2_adapter *adapter, struct sxe2_vlan *vlan);
+
+void sxe2_vlan_filters_clear(struct sxe2_adapter *adapter, bool default_config);
+
+int32_t sxe2_vlan_filter_ctrl(struct sxe2_adapter *adapter, bool flag);
+
+int32_t sxe2_promisc_add(struct sxe2_adapter *adapter);
+
+int32_t sxe2_promisc_del(struct sxe2_adapter *adapter);
+
+int32_t sxe2_allmulti_add(struct sxe2_adapter *adapter);
+
+int32_t sxe2_allmulti_del(struct sxe2_adapter *adapter);
+
+int32_t sxe2_l2_rule_update(struct sxe2_adapter *adapter);
+
+int32_t sxe2_filter_rule_stop(struct rte_eth_dev *dev);
+
+int32_t sxe2_filter_rule_start(struct rte_eth_dev *dev);
+
+int32_t sxe2_filter_init(struct rte_eth_dev *dev);
+
+int32_t sxe2_filter_uinit(struct rte_eth_dev *dev);
+
+#endif /* __SXE2_FILTER_H__ */
diff --git a/drivers/net/sxe2/sxe2_mac.c b/drivers/net/sxe2/sxe2_mac.c
index 3c2f909002..d94936a742 100644
--- a/drivers/net/sxe2/sxe2_mac.c
+++ b/drivers/net/sxe2/sxe2_mac.c
@@ -10,6 +10,438 @@
 #include "sxe2_cmd_chnl.h"
 #include "sxe2_host_regs.h"
 
+int32_t sxe2_mac_default_cfg(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t                     ret;
+	struct rte_ether_addr broadcast = {
+		.addr_bytes = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff} };
+	struct rte_ether_addr mac_addr;
+
+	rte_ether_addr_copy((struct rte_ether_addr *)
+		adapter->dev_info.mac.perm_addr, &mac_addr);
+	ret = sxe2_uc_filter_add(adapter, &mac_addr, true);
+	if (ret != 0) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add default MAC filter");
+		goto l_end;
+	}
+
+	rte_ether_addr_copy(&broadcast, &mac_addr);
+	ret = sxe2_mc_filter_add(adapter, &mac_addr, true);
+	if (ret != 0) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add broadcast MAC filter");
+		goto l_end;
+	}
+
+	ret = 0;
+l_end:
+	return ret;
+}
+
+int32_t sxe2_mac_addr_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret                         = -1;
+	PMD_INIT_FUNC_TRACE();
+
+	if (!rte_is_unicast_ether_addr
+		((struct rte_ether_addr *)adapter->dev_info.mac.perm_addr)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Invalid MAC address");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	dev->data->mac_addrs = rte_zmalloc("sxe2_mac_adds",
+					sizeof(struct rte_ether_addr) * SXE2_NUM_MACADDR_MAX, 0);
+	if (!dev->data->mac_addrs) {
+		PMD_LOG_ERR(DRV, "Failed to allocate memory to store mac address");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	rte_ether_addr_copy((struct rte_ether_addr *)adapter->dev_info.mac.perm_addr,
+		&dev->data->mac_addrs[0]);
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+void sxe2_mac_addr_uinit(struct rte_eth_dev *dev)
+{
+	PMD_INIT_FUNC_TRACE();
+	if (dev != NULL && dev->data->mac_addrs != NULL) {
+		rte_free(dev->data->mac_addrs);
+		dev->data->mac_addrs = NULL;
+	}
+}
+
+int32_t sxe2_mac_addr_add(struct rte_eth_dev *dev, struct rte_ether_addr *mac_addr,
+		      __rte_unused uint32_t index, __rte_unused uint32_t pool)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = -1;
+
+	if (rte_is_zero_ether_addr(mac_addr)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Invalid MAC Address");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (rte_is_multicast_ether_addr(mac_addr))
+		ret = sxe2_mc_filter_add(adapter, mac_addr, true);
+	else
+		ret = sxe2_uc_filter_add(adapter, mac_addr, false);
+
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add MAC filter");
+
+l_end:
+	return ret;
+}
+
+void sxe2_mac_addr_del(struct rte_eth_dev *dev,  uint32_t index)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_ether_addr *mac_addr = &dev->data->mac_addrs[index];
+	int32_t ret = -1;
+
+	if (rte_is_multicast_ether_addr(mac_addr))
+		ret = sxe2_mc_filter_del(adapter, mac_addr);
+	else
+		ret = sxe2_uc_filter_del(adapter, mac_addr);
+
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to remove MAC filter");
+}
+
+int32_t sxe2_mac_addr_set(struct rte_eth_dev *dev, struct rte_ether_addr *mac_addr)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	struct rte_ether_addr *old_addr = (struct rte_ether_addr *)&adapter->dev_info.mac.perm_addr;
+	struct rte_ether_addr temp_addr;
+
+	if (rte_is_same_ether_addr(old_addr, mac_addr))
+		goto l_end;
+
+	if (rte_is_multicast_ether_addr(mac_addr)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to set multicast addr");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_uc_filter_del(adapter, old_addr);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to remove MAC filter");
+		goto l_end;
+	}
+
+	rte_ether_addr_copy(old_addr, &temp_addr);
+
+	rte_ether_addr_copy(mac_addr, old_addr);
+
+	ret = sxe2_uc_filter_add(adapter, mac_addr, true);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add MAC filter");
+		rte_ether_addr_copy(&temp_addr, old_addr);
+		(void)sxe2_uc_filter_add(adapter, old_addr, true);
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_set_mc_addr_list(struct rte_eth_dev *dev,
+			struct rte_ether_addr *mc_addrs,
+			uint32_t mc_addrs_num)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	uint32_t i;
+	const uint8_t *mac;
+
+	if (mc_addrs_num > SXE2_NUM_MACADDR_MAX) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Too many multicast MAC addresses, ");
+		ret =  -1;
+		goto l_end;
+	}
+
+	sxe2_mc_filter_clear(adapter, false);
+
+	for (i = 0; i < mc_addrs_num; i++) {
+		if (!rte_is_multicast_ether_addr(&mc_addrs[i])) {
+			mac = mc_addrs[i].addr_bytes;
+			PMD_DEV_LOG_ERR(adapter, DRV,
+					"Invalid mac: %02x:%02x:%02x:%02x:%02x:%02x",
+					mac[0], mac[1], mac[2], mac[3], mac[4],
+					mac[5]);
+			ret = -EINVAL;
+			goto add_err;
+		}
+
+		ret = sxe2_mc_filter_add(adapter, &mc_addrs[i], false);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV,
+			    "Failed to remove old multicast MAC filter list");
+			goto add_err;
+		}
+	}
+	goto l_end;
+add_err:
+	sxe2_mc_filter_clear(adapter, false);
+l_end:
+	return ret;
+}
+
+int32_t sxe2_dev_vlan_filter_set(struct rte_eth_dev *dev, uint16_t vlan_id, int32_t on)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_vlan vlan = {
+		.tpid = RTE_ETHER_TYPE_VLAN,
+		.vid = vlan_id,
+		.prio = 0
+	};
+	int32_t ret = 0;
+
+	if (sxe2_dev_port_vlan_check(dev)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Filter not supported with Port VLAN");
+		ret = -ENOTSUP;
+		goto l_end;
+	}
+
+	if (vlan_id == 0)
+		goto l_end;
+
+	if (on) {
+		ret = sxe2_vlan_filter_add(adapter, &vlan, false);
+		if (ret < 0) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add vlan filter");
+			goto l_end;
+		}
+	} else {
+		ret = sxe2_vlan_filter_del(adapter, &vlan);
+		if (ret < 0) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to remove vlan filter");
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_dev_vlan_offload_set(struct rte_eth_dev *dev, int32_t mask)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	struct rte_eth_rxmode *rxmode = &dev->data->dev_conf.rxmode;
+	struct rte_eth_txmode *txmode = &dev->data->dev_conf.txmode;
+	struct sxe2_vlan_info new_info = adapter->filter_ctxt.vlan_info;
+	bool port_vlan = new_info.port_vlan_exist;
+
+	uint8_t out_strip_mask = SXE2_DPDK_OFFLOAD_OUTER_STRIP_8021Q |
+			    SXE2_DPDK_OFFLOAD_OUTER_STRIP_8021AD |
+			    SXE2_DPDK_OFFLOAD_OUTER_STRIP_QINQ1;
+
+	if (txmode->offloads & RTE_ETH_TX_OFFLOAD_QINQ_INSERT) {
+		if (!(txmode->offloads & RTE_ETH_TX_OFFLOAD_VLAN_INSERT)) {
+			PMD_DEV_LOG_ERR(adapter, DRV,
+			    "VLAN INSERT must be enabled when QinQ INSERT is enabled");
+			return -EINVAL;
+		}
+		if (port_vlan) {
+			PMD_DEV_LOG_ERR(adapter, DRV,
+					"QINQ INSERT not supported with Port VLAN");
+			return -EINVAL;
+		}
+	}
+
+	if (mask & RTE_ETH_QINQ_STRIP_MASK) {
+		if (rxmode->offloads & RTE_ETH_RX_OFFLOAD_QINQ_STRIP) {
+			if (port_vlan) {
+				PMD_DEV_LOG_ERR(adapter, DRV,
+						"QinQ strip not supported with Port VLAN");
+				return -EINVAL;
+			}
+			new_info.inner_strip = SXE2_VSI_TSR_ID_VLAN;
+		} else {
+			new_info.inner_strip = 0;
+		}
+	}
+
+	if (mask & RTE_ETH_VLAN_STRIP_MASK) {
+		if (rxmode->offloads & RTE_ETH_RX_OFFLOAD_VLAN_STRIP) {
+			new_info.outer_strip =
+				port_vlan ? 0 : out_strip_mask;
+			new_info.inner_strip =
+				port_vlan ? new_info.inner_strip : new_info.inner_strip;
+		} else {
+			if (new_info.inner_strip != 0) {
+				PMD_DEV_LOG_ERR(adapter, DRV,
+					"Must disable QinQ strip before disabling VLAN strip");
+				return -EINVAL;
+			}
+			new_info.outer_strip = 0;
+		}
+	}
+
+	if (mask & (RTE_ETH_VLAN_STRIP_MASK | RTE_ETH_QINQ_STRIP_MASK)) {
+		struct sxe2_vlan_info old_info = adapter->filter_ctxt.vlan_info;
+		adapter->filter_ctxt.vlan_info = new_info;
+
+		ret = sxe2_drv_vlan_insert_strip_cfg(adapter);
+		if (ret) {
+			adapter->filter_ctxt.vlan_info = old_info;
+			return ret;
+		}
+	}
+	if (mask & RTE_ETH_VLAN_FILTER_MASK) {
+		if (adapter->filter_ctxt.vlan_info.port_vlan_exist) {
+			ret = 0;
+			PMD_DEV_LOG_INFO(adapter, INIT, "vlan filter is not support when port vlan is enabled");
+			goto l_end;
+		}
+
+		ret = sxe2_vlan_filter_ctrl(adapter,
+			    !!(rxmode->offloads & RTE_ETH_RX_OFFLOAD_VLAN_FILTER));
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV,
+			    "sxe2_drv_vlan_filter_switch failed ret:%d", ret);
+			goto l_end;
+		}
+	}
+
+	PMD_DEV_LOG_DEBUG(adapter, DRV,
+	    "mask:0x%x rx mode offload:0x%" PRIx64 " vlan offload set done",
+	    mask, rxmode->offloads);
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_vlan_filter_zero(struct sxe2_adapter *adapter)
+{
+	struct sxe2_vlan vlan;
+	int32_t ret;
+	uint16_t tpids[] = {RTE_ETHER_TYPE_VLAN, RTE_ETHER_TYPE_QINQ, RTE_ETHER_TYPE_QINQ1};
+	uint8_t i;
+
+	vlan = (struct sxe2_vlan){0, 0, 0};
+	ret = sxe2_vlan_filter_add(adapter, &vlan, true);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add VLAN ID 0");
+		goto l_end;
+	}
+
+	for (i = 0; i < RTE_DIM(tpids); i++) {
+		vlan = (struct sxe2_vlan){tpids[i], 0, 0};
+		ret = sxe2_vlan_filter_add(adapter, &vlan, true);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add VLAN ID 0 when tpid:0x%x",
+					tpids[i]);
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_vlan_cfg_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_drv_vlan_config_query(adapter);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to query vlan config, ret=%d", ret);
+		goto l_end;
+	}
+
+	if (!sxe2_dev_port_vlan_check(dev))
+		adapter->filter_ctxt.vlan_info.outer_insert =
+			SXE2_DPDK_OFFLOAD_OUTER_INSERT_8021Q |
+			SXE2_DPDK_OFFLOAD_INSERT_ENABLE;
+	else
+		adapter->filter_ctxt.vlan_info.outer_insert = 0;
+
+	adapter->filter_ctxt.vlan_info.inner_insert =
+			SXE2_DPDK_OFFLOAD_INNER_INSERT_QINQ1 | SXE2_DPDK_OFFLOAD_INSERT_ENABLE;
+
+	if (!sxe2_dev_port_vlan_check(dev)) {
+		ret = sxe2_vlan_filter_zero(adapter);
+		if (ret != 0)
+			PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add vlan filter switch:0 "
+					"for port:%d", adapter->port_idx);
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_vlan_default_cfg(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	ret = sxe2_dev_vlan_offload_set(dev, RTE_ETH_VLAN_STRIP_MASK |
+					RTE_ETH_QINQ_STRIP_MASK |
+					RTE_ETH_VLAN_FILTER_MASK);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to cfg vlan offload, ret:%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_promisc_enable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_promisc_add(adapter);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to enable promisc, ret:%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_promisc_disable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_promisc_del(adapter);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to disable promisc, ret:%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_allmulti_enable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_allmulti_add(adapter);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to enable allmulti, ret:%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_allmulti_disable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_allmulti_del(adapter);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to disable allmulti, ret:%d", ret);
+
+	return ret;
+}
+
 int32_t sxe2_link_update_init(struct rte_eth_dev *dev)
 {
 	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
diff --git a/drivers/net/sxe2/sxe2_mac.h b/drivers/net/sxe2/sxe2_mac.h
index 28dc05e125..55fd1829a0 100644
--- a/drivers/net/sxe2/sxe2_mac.h
+++ b/drivers/net/sxe2/sxe2_mac.h
@@ -34,7 +34,7 @@
 					SXE2_DPDK_OFFLOAD_OUTER_STRIP_QINQ1)
 #define SXE2_DPDK_OFFLOAD_STRIP_OFFSET SXE2_VSI_TSR_SHOW_TAG_S
 
-#define SXE2_DPDK_OFFLOAD_INSERT_ENABLE (BIT(3))
+#define SXE2_DPDK_OFFLOAD_INSERT_ENABLE (RTE_BIT32(3))
 
 struct sxe2_mac_mc_list {
 	uint32_t count;
@@ -43,6 +43,40 @@ struct sxe2_mac_mc_list {
 
 int32_t sxe2_link_update_init(struct rte_eth_dev *dev);
 
+int32_t sxe2_mac_default_cfg(struct rte_eth_dev *dev);
+
+int32_t sxe2_vlan_cfg_init(struct rte_eth_dev *dev);
+
+int32_t sxe2_mac_addr_init(struct rte_eth_dev *dev);
+
+void sxe2_mac_addr_uinit(struct rte_eth_dev *dev);
+
+int32_t sxe2_mac_addr_add(struct rte_eth_dev *dev,
+			struct rte_ether_addr *mac_addr,
+			__rte_unused uint32_t index, __rte_unused uint32_t pool);
+
+void sxe2_mac_addr_del(struct rte_eth_dev *dev,  uint32_t index);
+
+int32_t sxe2_mac_addr_set(struct rte_eth_dev *dev, struct rte_ether_addr *mac_addr);
+
+int32_t sxe2_set_mc_addr_list(struct rte_eth_dev *dev,
+	struct rte_ether_addr *mc_addrs,
+	uint32_t mc_addrs_num);
+
+int32_t sxe2_promisc_enable(struct rte_eth_dev *dev);
+
+int32_t sxe2_promisc_disable(struct rte_eth_dev *dev);
+
+int32_t sxe2_allmulti_enable(struct rte_eth_dev *dev);
+
+int32_t sxe2_allmulti_disable(struct rte_eth_dev *dev);
+
+int32_t sxe2_dev_vlan_filter_set(struct rte_eth_dev *dev, uint16_t vlan_id, int32_t on);
+
+int32_t sxe2_dev_vlan_offload_set(struct rte_eth_dev *dev, int32_t mask);
+
+int32_t sxe2_vlan_default_cfg(struct rte_eth_dev *dev);
+
 int32_t sxe2_link_update(struct rte_eth_dev *dev, __rte_unused int32_t wait_to_complete);
 
 int32_t sxe2_mtu_set(struct rte_eth_dev *dev, uint16_t mtu);
diff --git a/drivers/net/sxe2/sxe2_txrx_poll.c b/drivers/net/sxe2/sxe2_txrx_poll.c
index b9d34afb31..21d5c38725 100644
--- a/drivers/net/sxe2/sxe2_txrx_poll.c
+++ b/drivers/net/sxe2/sxe2_txrx_poll.c
@@ -660,6 +660,53 @@ sxe2_rx_desc_error_para(__rte_unused struct sxe2_rx_queue *rxq,
 	return flags;
 }
 
+static inline void sxe2_rx_desc_vlan_para_fill(struct rte_mbuf *mbuf,
+			union sxe2_rx_desc *desc)
+{
+	if (0 == (rte_le_to_cpu_64(desc->wb.status_err_ptype_len) &
+		  SXE2_RX_DESC_STATUS_L2TAG1_P_MASK)) {
+		mbuf->vlan_tci = 0;
+	} else {
+		mbuf->ol_flags |= (RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_RX_VLAN_STRIPPED);
+		mbuf->vlan_tci = rte_le_to_cpu_16(desc->wb.l2tag1);
+		PMD_LOG_DEBUG(RX, "Rx desc mbuf vlan, vlan_tci:%u",
+			mbuf->vlan_tci);
+	}
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+	if (0 == (rte_le_to_cpu_32(desc->wb.status_lrocnt_fdpf_id) &
+				SXE2_RX_DESC_EXT_STATUS_L2TAG2P_MASK)) {
+		mbuf->vlan_tci_outer = 0;
+	} else {
+		mbuf->ol_flags |= RTE_MBUF_F_RX_QINQ_STRIPPED | RTE_MBUF_F_RX_QINQ |
+				RTE_MBUF_F_RX_VLAN_STRIPPED | RTE_MBUF_F_RX_VLAN;
+		mbuf->vlan_tci_outer = mbuf->vlan_tci;
+		mbuf->vlan_tci = rte_le_to_cpu_16(desc->wb.l2tag2_2nd);
+		PMD_LOG_DEBUG(RX, "Rx desc out vlan, l2tag2_1st:%u l2tag2_2nd:%u.",
+				rte_le_to_cpu_16(desc->wb.l2tag2_1st),
+				rte_le_to_cpu_16(desc->wb.l2tag2_2nd));
+	}
+#endif
+}
+
+static inline void
+sxe2_rx_desc_filter_para_fill(struct sxe2_rx_queue *rxq __rte_unused,
+		struct rte_mbuf *mbuf, union sxe2_rx_desc *desc)
+{
+	if (SXE2_RX_DESC_STATUS_RSS_VLD_MASK &
+				rte_le_to_cpu_64(desc->wb.status_err_ptype_len)) {
+		mbuf->ol_flags |= RTE_MBUF_F_RX_RSS_HASH;
+		mbuf->hash.rss = rte_le_to_cpu_32(desc->wb.filter_status);
+		PMD_LOG_DEBUG(RX, "rss id:%u", mbuf->hash.rss);
+	}
+#ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+	if (SXE2_RX_DESC_FD_VLD_MASK & desc->wb.rxdid_src) {
+		mbuf->ol_flags |= (RTE_MBUF_F_RX_FDIR | RTE_MBUF_F_RX_FDIR_ID);
+		mbuf->hash.fdir.hi = rte_le_to_cpu_32(desc->wb.fd_filter_id);
+		PMD_LOG_DEBUG(RX, "fdir id:%u", mbuf->hash.fdir.hi);
+	}
+#endif
+}
+
 static __rte_always_inline void
 sxe2_rx_mbuf_common_fields_fill(struct sxe2_rx_queue *rxq, struct rte_mbuf *mbuf,
 		union sxe2_rx_desc *rxd)
@@ -673,6 +720,8 @@ sxe2_rx_mbuf_common_fields_fill(struct sxe2_rx_queue *rxq, struct rte_mbuf *mbuf
 	mbuf->packet_type = ptype_tbl[SXE2_RX_DESC_PTYPE_VAL_GET(qword1)];
 
 	pkt_flags = sxe2_rx_desc_error_para(rxq, rxd);
+	sxe2_rx_desc_vlan_para_fill(mbuf, rxd);
+	sxe2_rx_desc_filter_para_fill(rxq, mbuf, rxd);
 
 	mbuf->ol_flags |= pkt_flags;
 }
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 06/23] net/sxe2: support TM hierarchy and shaping
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch implements the traffic management ops for example PMD.
It supports a 4-level hierarchy: port, vsi, queue group and queue.

- Support node add/delete and hierarchy commit.
- Support private shaper and rate limiting on each node.

The hardware requires all nodes to be configured before the hierarchy
is committed to the global registers.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build     |    3 +-
 drivers/net/sxe2/sxe2_cmd_chnl.c |  163 +++++
 drivers/net/sxe2/sxe2_cmd_chnl.h |    6 +
 drivers/net/sxe2/sxe2_drv_cmd.h  |   24 +
 drivers/net/sxe2/sxe2_ethdev.c   |   82 +++
 drivers/net/sxe2/sxe2_ethdev.h   |    6 +
 drivers/net/sxe2/sxe2_tm.c       | 1151 ++++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_tm.h       |   76 ++
 drivers/net/sxe2/sxe2_tx.c       |    1 -
 9 files changed, 1510 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_tm.c
 create mode 100644 drivers/net/sxe2/sxe2_tm.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index dfd31bfc97..d0aa7fecf0 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -63,6 +63,7 @@ sources += files(
         'sxe2_mac.c',
         'sxe2_filter.c',
         'sxe2_rss.c',
+        'sxe2_tm.c',
 )
 
-allow_internal_get_api = true
+allow_internal_get_api = true
\ No newline at end of file
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index b997e7b044..19323ffcc4 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -230,6 +230,7 @@ static void sxe2_txq_ctxt_cfg_fill(struct sxe2_tx_queue *txq,
 				   struct sxe2_drv_txq_cfg_req *req,
 				   uint16_t txq_cnt)
 {
+	struct sxe2_adapter *adapter = txq->vsi->adapter;
 	struct sxe2_drv_txq_ctxt *ctxt = req->cfg;
 	uint16_t q_idx = 0;
 
@@ -241,6 +242,8 @@ static void sxe2_txq_ctxt_cfg_fill(struct sxe2_tx_queue *txq,
 		ctxt->depth = txq[q_idx].ring_depth;
 		ctxt->dma_addr = txq[q_idx].base_addr;
 		ctxt->queue_id = txq[q_idx].queue_id;
+
+		ctxt->sched_mode = sxe2_sched_mode_get(adapter);
 	}
 }
 
@@ -310,6 +313,7 @@ int32_t sxe2_drv_txq_switch(struct sxe2_adapter *adapter, struct sxe2_tx_queue *
 	req.q_idx = txq->queue_id;
 
 	req.is_enable  = (uint8_t)enable;
+	req.sched_mode = sxe2_sched_mode_get(adapter);
 	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_TXQ_DISABLE,
 			&req, sizeof(req), NULL, 0);
 
@@ -714,3 +718,162 @@ int32_t sxe2_drv_ptp_gettime(struct sxe2_adapter *adapter, struct sxe2_rx_queue
 	(void)rxq;
 	return 0;
 }
+
+int32_t sxe2_drv_root_tree_alloc(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_tm_res tm_resp;
+	int32_t ret;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_SCHED_ROOT_TREE_ALLOC,
+				 NULL, 0,
+				 &tm_resp, sizeof(tm_resp));
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "add sched root failed, ret:%d", ret);
+		goto l_end;
+	}
+
+	tm_ctxt->root_teid = tm_resp.teid;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_root_tree_release(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_tm_res tm_res = {0};
+	int32_t ret;
+
+	tm_res.teid = adapter->tm_ctxt.root_teid;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_SCHED_ROOT_TREE_RELEASE,
+				 &tm_res, sizeof(tm_res),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "release sched root failed, ret:%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static void sxe2_drv_tm_node_to_info(struct sxe2_adapter *adapter,
+		struct sxe2_tm_node *node, struct sxe2_tm_info *info)
+{
+	uint32_t rate = 0;
+
+	if (node->shaper_profile->profile.committed.rate == UINT64_MAX)
+		rate = UINT32_MAX;
+	else
+		rate = (uint32_t)(node->shaper_profile->profile.committed.rate * 8 / 1000);
+
+	info->committed = rte_cpu_to_le_32(rate);
+
+	if (node->shaper_profile->profile.peak.rate == UINT64_MAX)
+		rate = UINT32_MAX;
+	else
+		rate = (uint32_t)(node->shaper_profile->profile.peak.rate * 8 / 1000);
+
+	info->peak = rte_cpu_to_le_32(rate);
+
+	info->priority = (adapter->tm_ctxt.prio_max - 1 - node->priority);
+
+	info->weight = rte_cpu_to_le_16(node->hw_weight);
+}
+
+static int32_t sxe2_drv_tm_commit_node(struct sxe2_adapter *adapter,
+		struct sxe2_tm_node *tm_node)
+{
+	struct sxe2_drv_cmd_params cmd = { 0 };
+	struct sxe2_tm_add_mid_msg msg_mid = {0};
+	struct sxe2_tm_add_queue_msg msg_queue = {0};
+	struct sxe2_tm_res res = {0};
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret;
+	uint32_t i;
+
+	if (tm_node->type == SXE2_TM_NODE_TYPE_VSIG) {
+		goto l_add;
+	} else if (tm_node->type == SXE2_TM_NODE_TYPE_MID) {
+		sxe2_drv_tm_node_to_info(adapter, tm_node, &msg_mid.info);
+		msg_mid.parent_teid = rte_cpu_to_le_16(tm_node->parent->teid);
+		msg_mid.adj_lvl = adapter->sched_ctxt.adj_lvl;
+
+		sxe2_drv_cmd_params_fill(adapter, &cmd,
+					 SXE2_DRV_CMD_SCHED_TM_ADD_MID_NODE,
+					 &msg_mid, sizeof(msg_mid),
+					 &res, sizeof(res));
+		ret = sxe2_drv_cmd_exec(cdev, &cmd);
+		if (ret) {
+			PMD_LOG_ERR(DRV, "add tm mid node failed, ret:%d", ret);
+			goto l_end;
+		}
+	} else if (tm_node->type == SXE2_TM_NODE_TYPE_QUEUE) {
+		sxe2_drv_tm_node_to_info(adapter, tm_node, &msg_queue.info);
+		msg_queue.parent_teid = rte_cpu_to_le_16(tm_node->parent->teid);
+		msg_queue.queue_id = rte_cpu_to_le_16(tm_node->id);
+		msg_queue.adj_lvl = adapter->sched_ctxt.adj_lvl;
+
+		sxe2_drv_cmd_params_fill(adapter, &cmd,
+					 SXE2_DRV_CMD_SCHED_TM_ADD_QUEUE_NODE,
+					 &msg_queue, sizeof(msg_queue),
+					 &res, sizeof(res));
+		ret = sxe2_drv_cmd_exec(cdev, &cmd);
+		if (ret) {
+			PMD_LOG_ERR(DRV, "add tm queue failed, ret:%d", ret);
+			goto l_end;
+		}
+	} else {
+		PMD_LOG_ERR(DRV, "commit tm node failed, type:%d", tm_node->type);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	tm_node->teid = rte_le_to_cpu_16(res.teid);
+
+l_add:
+	for (i = 0; i < tm_node->child_cnt; i++) {
+		ret = sxe2_drv_tm_commit_node(adapter, tm_node->children[i]);
+		if (ret)
+			goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_tm_commit(struct sxe2_adapter *adapter)
+{
+	struct sxe2_drv_cmd_params cmd = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_tm_res tm_res = {0};
+	int32_t ret;
+
+	tm_res.teid = adapter->tm_ctxt.root_teid;
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_SCHED_ROOT_CHILDREN_DELETE,
+				 &tm_res, sizeof(tm_res),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "del tm root failed, ret:%d", ret);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_tm_commit_node(adapter, adapter->tm_ctxt.root);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "commit tm node failed, ret:%d", ret);
+		goto l_end;
+	}
+
+	PMD_LOG_DEBUG(DRV, "commit tm success");
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index 3274db6551..6e209377c7 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -38,6 +38,12 @@ int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter);
 
 int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set);
 
+int32_t sxe2_drv_root_tree_release(struct rte_eth_dev *dev);
+
+int32_t sxe2_drv_root_tree_alloc(struct rte_eth_dev *dev);
+
+int32_t sxe2_drv_tm_commit(struct sxe2_adapter *adapter);
+
 int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set);
 
 int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index ced5a4b1a0..07f083644c 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -349,6 +349,30 @@ struct sxe2_rss_hf_req {
 	uint8_t rsv1[3];
 };
 
+struct sxe2_tm_res {
+	__le16 teid;
+};
+
+struct sxe2_tm_info {
+	uint32_t committed;
+	uint32_t peak;
+	uint8_t priority;
+	uint8_t reserve;
+	__le16 weight;
+};
+
+struct sxe2_tm_add_mid_msg {
+	__le16 parent_teid;
+	uint8_t adj_lvl;
+	struct sxe2_tm_info info;
+};
+struct sxe2_tm_add_queue_msg {
+	__le16 parent_teid;
+	__le16 queue_id;
+	uint8_t adj_lvl;
+	struct sxe2_tm_info info;
+};
+
 enum sxe2_drv_cmd_module {
 	SXE2_DRV_CMD_MODULE_HANDSHAKE = 0,
 	SXE2_DRV_CMD_MODULE_DEV = 1,
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index d48841b8e4..a095888c00 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -130,6 +130,8 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.reta_query                 = sxe2_dev_rss_reta_query,
 	.rss_hash_update            = sxe2_dev_rss_hash_update,
 	.rss_hash_conf_get          = sxe2_dev_rss_hash_conf_get,
+
+	.tm_ops_get                 = sxe2_tm_ops_get,
 };
 
 static int32_t sxe2_dev_configure(struct rte_eth_dev *dev)
@@ -575,6 +577,14 @@ static void sxe2_drv_dev_caps_set(struct sxe2_adapter *adapter,
 		adapter->cap_flags |= SXE2_DEV_CAPS_OFFLOAD_FC_STATE;
 }
 
+static void sxe2_sw_sched_hw_cap_set(struct sxe2_adapter *adapter,
+				     struct sxe2_txsch_caps *txsch_caps)
+{
+	adapter->sched_ctxt.tm_layers = txsch_caps->layer_cap;
+	adapter->sched_ctxt.root_max_children = txsch_caps->tm_mid_node_num;
+	adapter->sched_ctxt.prio_max = txsch_caps->prio_num;
+}
+
 static int32_t sxe2_func_caps_get(struct sxe2_adapter *adapter)
 {
 	int32_t ret = -1;
@@ -594,6 +604,8 @@ static int32_t sxe2_func_caps_get(struct sxe2_adapter *adapter)
 
 	sxe2_sw_vsi_ctx_hw_cap_set(adapter, &dev_caps.vsi_caps);
 
+	sxe2_sw_sched_hw_cap_set(adapter, &dev_caps.txsch_caps);
+
 l_end:
 	return ret;
 }
@@ -930,6 +942,68 @@ void sxe2_dev_pci_map_uinit(struct rte_eth_dev *dev)
 	adapter->dev_info.dev_data = NULL;
 }
 
+uint32_t sxe2_sched_mode_get(struct sxe2_adapter *adapter)
+{
+	uint32_t ret_mode = SXE2_SCHED_MODE_INVALID;
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+
+	if (adapter->devargs.high_performance_mode)
+		ret_mode = SXE2_SCHED_MODE_HIGH_PERFORMANCE;
+	else if (tm_ctxt->committed)
+		ret_mode = SXE2_SCHED_MODE_TM;
+	else
+		ret_mode = SXE2_SCHED_MODE_DEFAULT;
+
+	return ret_mode;
+}
+
+static int32_t sxe2_sched_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	adapter->sched_ctxt.adj_lvl = adapter->devargs.sched_layer_mode;
+
+	if (adapter->devargs.high_performance_mode) {
+		PMD_LOG_DEBUG(DRV, "TM feature will be disabled in high-performance mode.");
+		adapter->cap_flags &= ~(SXE2_DEV_CAPS_OFFLOAD_TM);
+	} else {
+		ret = sxe2_tm_init(dev);
+		if (ret)
+			goto l_end;
+
+		ret = sxe2_drv_root_tree_alloc(dev);
+		if (ret)
+			goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_sched_uinit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	if (adapter->devargs.high_performance_mode == 0) {
+		ret = sxe2_tm_uninit(dev);
+		if (ret) {
+			PMD_LOG_ERR(INIT, "Failed to uninit tm, ret=%d", ret);
+			goto l_end;
+		}
+
+		ret = sxe2_drv_root_tree_release(dev);
+		if (ret) {
+			PMD_LOG_ERR(INIT, "Failed to release root tree, ret=%d", ret);
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
 static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 			     struct sxe2_dev_kvargs_info *kvargs __rte_unused)
 {
@@ -985,8 +1059,15 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_rss_err;
 	}
 
+	ret = sxe2_sched_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init sched, ret=%d", ret);
+		goto init_sched_err;
+	}
+
 	goto l_end;
 
+init_sched_err:
 init_rss_err:
 init_eth_err:
 init_dev_info_err:
@@ -1002,6 +1083,7 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 	(void)sxe2_dev_stop(dev);
 	(void)sxe2_queues_release(dev);
 	(void)sxe2_rss_disable(dev);
+	(void)sxe2_sched_uinit(dev);
 	sxe2_vsi_uninit(dev);
 	sxe2_dev_pci_map_uinit(dev);
 	sxe2_eth_uinit(dev);
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 556a11cc77..609e1e92ba 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
  */
+
 #ifndef __SXE2_ETHDEV_H__
 #define __SXE2_ETHDEV_H__
 #include <rte_compat.h>
@@ -18,6 +19,7 @@
 #include "sxe2_queue.h"
 #include "sxe2_mac.h"
 #include "sxe2_osal.h"
+#include "sxe2_tm.h"
 #include "sxe2_filter.h"
 
 struct sxe2_link_msg {
@@ -307,6 +309,8 @@ struct sxe2_adapter {
 	struct sxe2_rss_context       rss_ctxt;
 	struct sxe2_link_context      link_ctxt;
 	struct sxe2_ptp_context       ptp_ctxt;
+	struct sxe2_sched_hw_cap      sched_ctxt;
+	struct sxe2_tm_context        tm_ctxt;
 	struct sxe2_devargs           devargs;
 	struct sxe2_switchdev_info    switchdev_info;
 	bool                          rule_started;
@@ -333,6 +337,8 @@ void *sxe2_pci_map_addr_get(struct sxe2_adapter *adapter,
 			    enum sxe2_pci_map_resource res_type,
 			    uint16_t idx_in_func);
 
+uint32_t sxe2_sched_mode_get(struct sxe2_adapter *adapter);
+
 struct sxe2_pci_map_bar_info *sxe2_dev_get_bar_info(struct sxe2_adapter *adapter,
 						    enum sxe2_pci_map_resource res_type);
 
diff --git a/drivers/net/sxe2/sxe2_tm.c b/drivers/net/sxe2/sxe2_tm.c
new file mode 100644
index 0000000000..4c4f793cd5
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_tm.c
@@ -0,0 +1,1151 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_ethdev.h>
+#include <rte_tm_driver.h>
+
+#include "sxe2_tm.h"
+#include "sxe2_ethdev.h"
+#include "sxe2_common_log.h"
+#include "sxe2_cmd_chnl.h"
+
+static uint16_t sxe2_tm_level_node_num_get(uint8_t level)
+{
+	uint16_t node_num = 0;
+
+	switch (level) {
+	case 0:
+		node_num = SXE2_TM_1L_NODE_NUM_MAX;
+		break;
+	case 1:
+		node_num = SXE2_TM_2L_NODE_NUM_MAX;
+		break;
+	case 2:
+		node_num = SXE2_TM_3L_NODE_NUM_MAX;
+	break;
+	case 3:
+		node_num = SXE2_TM_4L_NODE_NUM_MAX;
+		break;
+	}
+	return node_num;
+}
+
+static int32_t sxe2_tm_capabilities_get(struct rte_eth_dev *dev,
+			  struct rte_tm_capabilities *cap,
+			  struct rte_tm_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	uint32_t i;
+
+	if (!cap || !error) {
+		PMD_LOG_ERR(DRV, "sxe2 get tm cap failed, cap or error is NULL.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	error->type = RTE_TM_ERROR_TYPE_NONE;
+	memset(cap, 0, sizeof(struct rte_tm_capabilities));
+
+	for (i = 0; i < adapter->tm_ctxt.tm_layers; i++)
+		cap->n_nodes_max += sxe2_tm_level_node_num_get(i);
+
+	cap->n_levels_max = adapter->tm_ctxt.tm_layers;
+
+	cap->non_leaf_nodes_identical = 1;
+
+	cap->leaf_nodes_identical = 1;
+
+	cap->shaper_n_max = cap->n_nodes_max;
+
+	cap->shaper_private_n_max = cap->n_nodes_max;
+
+	cap->shaper_private_dual_rate_n_max = cap->n_nodes_max;
+
+	cap->shaper_private_rate_min = 0;
+
+	cap->shaper_private_rate_max = 12500000000ull;
+
+	cap->shaper_private_packet_mode_supported = 0;
+	cap->shaper_private_byte_mode_supported = 1;
+
+	cap->shaper_pkt_length_adjust_min = RTE_TM_ETH_FRAMING_OVERHEAD;
+
+	cap->shaper_pkt_length_adjust_max = RTE_TM_ETH_FRAMING_OVERHEAD_FCS;
+
+	cap->shaper_shared_n_max = 0;
+	cap->shaper_shared_n_nodes_per_shaper_max = 0;
+	cap->shaper_shared_n_shapers_per_node_max = 0;
+	cap->shaper_shared_dual_rate_n_max = 0;
+	cap->shaper_shared_rate_min = 0;
+	cap->shaper_shared_rate_max = 0;
+	cap->shaper_shared_packet_mode_supported = 0;
+	cap->shaper_shared_byte_mode_supported = 0;
+
+	cap->sched_n_children_max = dev->data->nb_tx_queues;
+
+	cap->sched_sp_n_priorities_max = 7;
+
+	cap->sched_wfq_n_children_per_group_max = 1;
+	cap->sched_wfq_n_groups_max = 0;
+	cap->sched_wfq_weight_max = 0;
+	cap->sched_wfq_packet_mode_supported = 0;
+	cap->sched_wfq_byte_mode_supported = 0;
+
+	cap->cman_wred_packet_mode_supported = 0;
+	cap->cman_wred_byte_mode_supported = 0;
+	cap->cman_head_drop_supported = 0;
+	cap->cman_wred_context_n_max = 0;
+	cap->cman_wred_context_private_n_max = 0;
+	cap->cman_wred_context_shared_n_max = 0;
+	cap->cman_wred_context_shared_n_nodes_per_context_max = 0;
+	cap->cman_wred_context_shared_n_contexts_per_node_max = 0;
+
+	cap->dynamic_update_mask = 0;
+
+	cap->stats_mask = 0;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_level_capabilities_get(struct rte_eth_dev *dev,
+		uint32_t level_id, struct rte_tm_level_capabilities *cap,
+		struct rte_tm_error *error)
+{
+	int32_t ret = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (!cap || !error) {
+		PMD_LOG_ERR(DRV, "sxe2 get tm cap failed, cap or error is NULL.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	if (level_id >= adapter->tm_ctxt.tm_layers) {
+		ret = -EINVAL;
+		error->type = RTE_TM_ERROR_TYPE_LEVEL_ID;
+		error->message = "too deep level";
+		goto l_end;
+	}
+
+	cap->n_nodes_max = sxe2_tm_level_node_num_get(level_id);
+
+	cap->non_leaf_nodes_identical = true;
+
+	cap->leaf_nodes_identical = true;
+
+	if (level_id != adapter->tm_ctxt.tm_layers - 1) {
+		cap->n_nodes_nonleaf_max = cap->n_nodes_max;
+		cap->n_nodes_leaf_max = 0;
+
+		cap->nonleaf.shaper_private_supported = true;
+		cap->nonleaf.shaper_private_dual_rate_supported = true;
+		cap->nonleaf.shaper_private_rate_min = 0;
+
+		cap->nonleaf.shaper_private_rate_max = 12500000000ull;
+		cap->nonleaf.shaper_private_packet_mode_supported = 0;
+		cap->nonleaf.shaper_private_byte_mode_supported = 1;
+
+		cap->nonleaf.shaper_shared_n_max = 0;
+		cap->nonleaf.shaper_shared_packet_mode_supported = 0;
+		cap->nonleaf.shaper_shared_byte_mode_supported = 0;
+
+		cap->nonleaf.sched_n_children_max = SXE2_TM_MAX_CHILDREN_COUNT;
+		cap->nonleaf.sched_sp_n_priorities_max = 7;
+
+		cap->nonleaf.sched_wfq_n_children_per_group_max = 0;
+		cap->nonleaf.sched_wfq_n_groups_max = 0;
+		cap->nonleaf.sched_wfq_weight_max = 0;
+		cap->nonleaf.sched_wfq_packet_mode_supported = 0;
+		cap->nonleaf.sched_wfq_byte_mode_supported = 0;
+
+		cap->nonleaf.stats_mask = 0;
+	} else {
+		cap->n_nodes_nonleaf_max = 0;
+		cap->n_nodes_leaf_max = cap->n_nodes_max;
+
+		cap->leaf.shaper_private_supported = true;
+		cap->leaf.shaper_private_dual_rate_supported = true;
+		cap->leaf.shaper_private_rate_min = 0;
+		cap->leaf.shaper_private_rate_max = 12500000000ull;
+		cap->leaf.shaper_private_packet_mode_supported = 0;
+		cap->leaf.shaper_private_byte_mode_supported = 1;
+
+		cap->leaf.shaper_shared_n_max = 0;
+		cap->leaf.shaper_shared_packet_mode_supported = 0;
+		cap->leaf.shaper_shared_byte_mode_supported = 0;
+
+		cap->leaf.cman_head_drop_supported = false;
+		cap->leaf.cman_wred_context_private_supported = false;
+		cap->leaf.cman_wred_context_shared_n_max = 0;
+
+		cap->leaf.stats_mask = 0;
+	}
+
+l_end:
+	return ret;
+}
+
+static struct sxe2_tm_node *sxe2_tm_find_node(struct sxe2_tm_node *parent, uint32_t id)
+{
+	struct sxe2_tm_node *node = NULL;
+	uint32_t i;
+
+	if (parent == NULL || parent->id == id) {
+		node = parent;
+		goto l_end;
+	}
+
+	for (i = 0; i < parent->child_cnt; i++) {
+		node = sxe2_tm_find_node(parent->children[i], id);
+		if (node)
+			goto l_end;
+	}
+
+l_end:
+	return node;
+}
+
+static int32_t sxe2_node_capabilities_get(struct rte_eth_dev *dev, uint32_t node_id,
+				      struct rte_tm_node_capabilities *cap,
+				      struct rte_tm_error *error)
+{
+	int32_t ret = -EINVAL;
+	struct sxe2_tm_node *tm_node;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (!cap || !error) {
+		PMD_LOG_ERR(DRV, "sxe2 get tm cap failed, cap or error is NULL.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	if (node_id == RTE_TM_NODE_ID_NULL) {
+		PMD_LOG_ERR(DRV, "invalid node id");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "invalid node id";
+		goto l_end;
+	}
+
+	tm_node = sxe2_tm_find_node(adapter->tm_ctxt.root, node_id);
+	if (!tm_node) {
+		PMD_LOG_ERR(DRV, "no such node");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "no such node";
+		goto l_end;
+	}
+
+	cap->shaper_private_supported = true;
+	cap->shaper_private_dual_rate_supported = true;
+	cap->shaper_private_rate_min = 0;
+	cap->shaper_private_rate_max = 12500000000ull;
+	cap->shaper_private_packet_mode_supported = 0;
+	cap->shaper_private_byte_mode_supported = 1;
+
+	cap->shaper_shared_n_max = 0;
+	cap->shaper_shared_packet_mode_supported = 0;
+	cap->shaper_shared_byte_mode_supported = 0;
+
+	if (tm_node->level == adapter->tm_ctxt.tm_layers - 1) {
+		cap->leaf.cman_head_drop_supported = false;
+		cap->leaf.cman_wred_context_private_supported = false;
+		cap->leaf.cman_wred_context_shared_n_max = 0;
+	} else {
+		cap->nonleaf.sched_n_children_max = SXE2_TM_MAX_CHILDREN_COUNT;
+		cap->nonleaf.sched_sp_n_priorities_max = 7;
+		cap->nonleaf.sched_wfq_n_children_per_group_max = 0;
+		cap->nonleaf.sched_wfq_n_groups_max = 0;
+		cap->nonleaf.sched_wfq_weight_max = 0;
+		cap->nonleaf.sched_wfq_packet_mode_supported = 0;
+		cap->nonleaf.sched_wfq_byte_mode_supported = 0;
+	}
+	cap->stats_mask = 0;
+
+	ret = 0;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_shaper_profile_param_check(const struct rte_tm_shaper_params *profile,
+					      struct rte_tm_error *error)
+{
+	int32_t ret = 0;
+
+	if (profile->committed.size) {
+		PMD_LOG_ERR(DRV, "committed bucket size not supported.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_COMMITTED_SIZE;
+		error->message = "committed bucket size not supported";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (profile->peak.size) {
+		PMD_LOG_ERR(DRV, "peak bucket size not supported.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_PEAK_SIZE;
+		error->message = "peak bucket size not supported";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (profile->pkt_length_adjust) {
+		PMD_LOG_ERR(DRV, "packet length adjustment not supported.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_PKT_ADJUST_LEN;
+		error->message = "packet length adjustment not supported";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (profile->committed.rate > SXE2_HW_RATE_MAX ||
+		profile->committed.rate < SXE2_HW_RATE_MIN) {
+		PMD_LOG_ERR(DRV, "The committed rate limit value is required to be in "
+			"the range [%" PRIu64 ", %" PRIu64 "].",
+			(uint64_t)SXE2_HW_RATE_MIN, (uint64_t)SXE2_HW_RATE_MAX);
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_COMMITTED_RATE;
+		error->message = "invalid rate limit: value out of range.";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (profile->peak.rate > SXE2_HW_RATE_MAX ||
+		profile->peak.rate < SXE2_HW_RATE_MIN) {
+		PMD_LOG_ERR(DRV, "The peak rate limit value is required to be in "
+			"the range [%" PRIu64 ", %" PRIu64 "].",
+			(uint64_t)SXE2_HW_RATE_MIN, (uint64_t)SXE2_HW_RATE_MAX);
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_PEAK_RATE;
+		error->message = "invalid rate limit: value out of range.";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (profile->committed.rate > profile->peak.rate) {
+		PMD_LOG_ERR(DRV, "committed rate can't be greater than peak rate.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_COMMITTED_RATE;
+		error->message = "committed rate can't be greater than peak rate.";
+		ret = -EINVAL;
+		goto l_end;
+	}
+l_end:
+	return ret;
+}
+
+static inline struct sxe2_tm_shaper_profile *
+sxe2_tm_shaper_profile_search(struct rte_eth_dev *dev, uint32_t id)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_shaper_profile_list *shaper_profile_list =
+		&adapter->tm_ctxt.profile_list;
+	struct sxe2_tm_shaper_profile *shaper_profile = NULL;
+
+	TAILQ_FOREACH(shaper_profile, shaper_profile_list, node) {
+		if (id == shaper_profile->id)
+			goto l_end;
+	}
+
+	shaper_profile = NULL;
+
+l_end:
+	return shaper_profile;
+}
+
+static int32_t sxe2_tm_shaper_profile_add(struct rte_eth_dev *dev, uint32_t shaper_profile_id,
+				      const struct rte_tm_shaper_params *profile,
+				      struct rte_tm_error *error)
+{
+	int32_t ret = -EINVAL;
+	struct sxe2_tm_shaper_profile *shaper_profile = NULL;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (!profile || !error) {
+		PMD_LOG_ERR(DRV, "Invalid input: profile:0x%p or error:0x%p is null.",
+			profile, error);
+		if (error) {
+			error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+			error->message = "Invalid input: profile or error is null.";
+		}
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	ret = sxe2_tm_shaper_profile_param_check(profile, error);
+	if (ret)
+		goto l_end;
+
+	shaper_profile = sxe2_tm_shaper_profile_search(dev, shaper_profile_id);
+	if (shaper_profile) {
+		PMD_LOG_ERR(DRV, "profile ID exist.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_ID;
+		error->message = "profile ID exist";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	shaper_profile = rte_zmalloc("sxe2_tm_shaper_profile",
+							sizeof(struct sxe2_tm_shaper_profile), 0);
+	if (!shaper_profile) {
+		PMD_LOG_ERR(DRV, "Alloc shaper_profile memory failed.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "Alloc shaper_profile memory failed";
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	rte_memcpy(&shaper_profile->profile, profile,
+					sizeof(struct rte_tm_shaper_params));
+	shaper_profile->id = shaper_profile_id;
+
+	TAILQ_INSERT_TAIL(&adapter->tm_ctxt.profile_list, shaper_profile, node);
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_shaper_profile_del(struct rte_eth_dev *dev,
+		uint32_t id, struct rte_tm_error *error)
+{
+	int32_t ret = -EINVAL;
+	struct sxe2_tm_shaper_profile *shaper_profile = NULL;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	if (!error) {
+		PMD_LOG_ERR(DRV, "Error param is null.");
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	shaper_profile = sxe2_tm_shaper_profile_search(dev, id);
+	if (!shaper_profile) {
+		PMD_LOG_ERR(DRV, "profile ID not exist.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE_ID;
+		error->message = "profile ID not exist";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (shaper_profile->ref_cnt) {
+		PMD_LOG_ERR(DRV, "profile in use.");
+		error->type = RTE_TM_ERROR_TYPE_SHAPER_PROFILE;
+		error->message = "profile in use";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	TAILQ_REMOVE(&adapter->tm_ctxt.profile_list, shaper_profile, node);
+	rte_free(shaper_profile);
+
+	ret = 0;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_node_param_check(struct rte_eth_dev *dev,
+			uint32_t parent_node_type,
+			uint32_t node_id, uint32_t priority, uint32_t weight,
+			const struct rte_tm_node_params *params,
+			bool is_leaf, struct rte_tm_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	int32_t ret = -EINVAL;
+
+	if (node_id == RTE_TM_NODE_ID_NULL) {
+		PMD_LOG_ERR(DRV, "Invalid node id.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "invalid node id";
+		goto l_end;
+	}
+
+	if (parent_node_type == SXE2_TM_NODE_TYPE_VSIG &&
+			priority >= tm_ctxt->prio_max) {
+		PMD_LOG_ERR(DRV, "Priority should be less than %u.", tm_ctxt->prio_max);
+		error->type = RTE_TM_ERROR_TYPE_NODE_PRIORITY;
+		error->message = "The priority is too high.";
+		goto l_end;
+	}
+
+	if (priority > SXE2_TM_PRIO_MAX) {
+		PMD_LOG_ERR(DRV, "Priority should be less than %u.", SXE2_TM_PRIO_MAX);
+		error->type = RTE_TM_ERROR_TYPE_NODE_PRIORITY;
+		error->message = "The priority is too high.";
+		goto l_end;
+	}
+
+	if (weight > SXE2_TM_WEIGHT_MAX || weight < SXE2_TM_WEIGHT_MIN) {
+		PMD_LOG_ERR(DRV, "Weight must be between 1 and 200.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_WEIGHT;
+		error->message = "weight must be between 1 and 200";
+		goto l_end;
+	}
+
+	if (params->shared_shaper_id) {
+		PMD_LOG_ERR(DRV, "Shared shaper not supported.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_SHARED_SHAPER_ID;
+		error->message = "shared shaper not supported";
+		goto l_end;
+	}
+	if (params->n_shared_shapers) {
+		PMD_LOG_ERR(DRV, "Shared shaper not supported..");
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_N_SHARED_SHAPERS;
+		error->message = "shared shaper not supported";
+		goto l_end;
+	}
+
+	if (!is_leaf) {
+		if (node_id <= dev->data->nb_tx_queues) {
+			PMD_LOG_ERR(DRV, "no leaf node id must bigger than queue id.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+			error->message = "no leaf node id must bigger than queue id.";
+			goto l_end;
+		}
+
+		if (params->nonleaf.wfq_weight_mode) {
+			PMD_LOG_ERR(DRV, "WFQ not supported.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_WFQ_WEIGHT_MODE;
+			error->message = "WFQ not supported";
+			goto l_end;
+		}
+
+		if (params->nonleaf.n_sp_priorities != 1) {
+			PMD_LOG_ERR(DRV, "SP priority not supported.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_N_SP_PRIORITIES;
+			error->message = "SP priority not supported";
+			goto l_end;
+		}
+	} else {
+		if (node_id >= dev->data->nb_tx_queues) {
+			PMD_LOG_ERR(DRV, "leaf node id must be queue id.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+			error->message = "leaf node id must be queue id.";
+			goto l_end;
+		}
+
+		if (params->leaf.cman) {
+			PMD_LOG_ERR(DRV, "Congestion management not supported.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_CMAN;
+			error->message = "Congestion management not supported";
+			goto l_end;
+		}
+		if (params->leaf.wred.wred_profile_id != RTE_TM_WRED_PROFILE_ID_NONE) {
+			PMD_LOG_ERR(DRV, "WRED not supported.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_WRED_PROFILE_ID;
+			error->message = "WRED not supported";
+			goto l_end;
+		}
+		if (params->leaf.wred.shared_wred_context_id) {
+			PMD_LOG_ERR(DRV, "WRED not supported.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_SHARED_WRED_CONTEXT_ID;
+			error->message = "WRED not supported";
+			goto l_end;
+		}
+		if (params->leaf.wred.n_shared_wred_contexts) {
+			PMD_LOG_ERR(DRV, "WRED not supported.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_N_SHARED_WRED_CONTEXTS;
+			error->message = "WRED not supported";
+			goto l_end;
+		}
+	}
+	ret = 0;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_add_child(struct sxe2_tm_node *parent,
+		struct sxe2_tm_node *child)
+{
+	int32_t ret = -1;
+	uint32_t i;
+	for (i = 0; i < SXE2_TM_MAX_CHILDREN_COUNT; i++) {
+		if (parent->children[i] == NULL) {
+			parent->children[i] = child;
+			child->index_in_parent = i;
+			parent->child_cnt++;
+			ret = 0;
+			break;
+		}
+	}
+	return ret;
+}
+
+static int32_t sxe2_tm_node_add(struct rte_eth_dev *dev, uint32_t node_id, uint32_t parent_node_id,
+			    uint32_t priority, uint32_t weight, uint32_t level_id,
+			    const struct rte_tm_node_params *params,
+			    struct rte_tm_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_tm_shaper_profile *shaper_profile = NULL;
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	struct sxe2_tm_node *tm_node = NULL;
+	struct sxe2_tm_node *parent_node = NULL;
+	int32_t ret = -EINVAL;
+	bool is_leaf;
+
+	if (!params || !error) {
+		PMD_LOG_ERR(DRV, "Invalid input: params:0x%p or error:0x%p is null.",
+			params, error);
+		if (error) {
+			error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+			error->message = "Invalid input: params or error is null.";
+		}
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	shaper_profile = sxe2_tm_shaper_profile_search(dev, params->shaper_profile_id);
+	if (!shaper_profile) {
+		PMD_LOG_ERR(DRV, "Shaper profile does not exist.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS_SHAPER_PROFILE_ID;
+		error->message = "shaper profile does not exist";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (parent_node_id == RTE_TM_NODE_ID_NULL) {
+		if (level_id != 0) {
+			PMD_LOG_ERR(DRV, "Wrong level, root node (NULL parent) must be at level 0.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS;
+			error->message = "Wrong level, root node (NULL parent) must be at level 0";
+			ret = -EINVAL;
+			goto l_end;
+		}
+
+		if (tm_ctxt->root) {
+			PMD_LOG_ERR(DRV, "Already have a root.");
+			error->type = RTE_TM_ERROR_TYPE_NODE_PARENT_NODE_ID;
+			error->message = "already have a root";
+			ret = -EINVAL;
+			goto l_end;
+		}
+
+		ret = sxe2_tm_node_param_check(dev, SXE2_TM_NODE_TYPE_INVALID, node_id, priority,
+					       weight, params, false, error);
+		if (ret)
+			goto l_end;
+
+		tm_node = rte_zmalloc("tm_node_root", sizeof(struct sxe2_tm_node), 0);
+		if (!tm_node) {
+			PMD_LOG_ERR(DRV, "Alloc tm_node memory failed.");
+			error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+			error->message = "Alloc tm_node memory failed";
+			ret = -ENOMEM;
+			goto l_end;
+		}
+
+		tm_node->id = node_id;
+		tm_node->level = 0;
+		tm_node->parent = NULL;
+		tm_node->child_cnt = 0;
+		tm_node->weight = weight;
+		tm_node->hw_weight = SXE2_TM_WEIGHT_SUM;
+		tm_node->type = SXE2_TM_NODE_TYPE_VSIG;
+		tm_node->priority = priority;
+		tm_node->shaper_profile = shaper_profile;
+
+		tm_node->teid = tm_ctxt->root_teid;
+
+		shaper_profile->ref_cnt++;
+		tm_ctxt->root = tm_node;
+		ret = 0;
+		goto l_end;
+	}
+
+	parent_node = sxe2_tm_find_node(tm_ctxt->root, parent_node_id);
+	if (!parent_node) {
+		PMD_LOG_ERR(DRV, "Parent not exist.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARENT_NODE_ID;
+		error->message = "parent not exist";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (parent_node->child_cnt >= SXE2_TM_MAX_CHILDREN_COUNT ||
+		(parent_node->type == SXE2_TM_NODE_TYPE_VSIG &&
+		 parent_node->child_cnt >= tm_ctxt->root_max_children)) {
+		PMD_LOG_ERR(DRV, "Parent node is full.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS;
+		error->message = "parent node is full";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (level_id == RTE_TM_NODE_LEVEL_ID_ANY) {
+		level_id = parent_node->level + 1;
+	} else if (level_id != parent_node->level + 1) {
+		PMD_LOG_ERR(DRV, "Wrong level.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS;
+		error->message = "Wrong level";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (level_id >= tm_ctxt->tm_layers) {
+		PMD_LOG_ERR(DRV, "The maximum number of TM configuration levels is %d",
+				tm_ctxt->tm_layers);
+		error->type = RTE_TM_ERROR_TYPE_NODE_PARAMS;
+		error->message = "TM level exceeds supported hardware limit";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (level_id + 1 == tm_ctxt->tm_layers)
+		is_leaf = true;
+	else
+		is_leaf = false;
+
+	ret = sxe2_tm_node_param_check(dev, parent_node->type, node_id, priority, weight,
+				       params, is_leaf, error);
+	if (ret)
+		goto l_end;
+
+	if (sxe2_tm_find_node(tm_ctxt->root, node_id)) {
+		PMD_LOG_ERR(DRV, "Node id already used.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "node id already used";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	tm_node = rte_zmalloc("tm_node_no_root", sizeof(struct sxe2_tm_node), 0);
+	if (!tm_node) {
+		PMD_LOG_ERR(DRV, "Alloc tm_node memory failed.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "Alloc tm_node memory failed";
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	if (level_id + 1 != tm_ctxt->tm_layers)
+		tm_node->type = SXE2_TM_NODE_TYPE_MID;
+	else
+		tm_node->type = SXE2_TM_NODE_TYPE_QUEUE;
+	tm_node->id = node_id;
+	tm_node->level = level_id;
+	tm_node->parent = parent_node;
+	tm_node->child_cnt = 0;
+	tm_node->weight = weight;
+	tm_node->priority = priority;
+	tm_node->shaper_profile = shaper_profile;
+	shaper_profile->ref_cnt++;
+
+	ret = sxe2_tm_add_child(parent_node, tm_node);
+	if (ret) {
+		shaper_profile->ref_cnt--;
+		rte_free(tm_node);
+	}
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_tree_delete(struct sxe2_tm_node *tm_node)
+{
+	int32_t ret = 0;
+	uint32_t i, j;
+	struct sxe2_tm_node *parent = NULL;
+
+	if (!tm_node)
+		goto l_end;
+
+	parent = tm_node->parent;
+
+	if (tm_node->child_cnt != 0) {
+		for (i = SXE2_TM_MAX_CHILDREN_COUNT; i > 0; i--) {
+			if (tm_node->children[i - 1])
+				ret = sxe2_tm_tree_delete(tm_node->children[i - 1]);
+		}
+	}
+
+	if (tm_node->shaper_profile)
+		tm_node->shaper_profile->ref_cnt--;
+
+	if (tm_node->type != SXE2_TM_NODE_TYPE_VSIG && parent) {
+		for (i = 0; i < parent->child_cnt; i++) {
+			if (parent->children[i] == tm_node)
+				break;
+		}
+		for (j = i; j < parent->child_cnt - 1; j++)
+			parent->children[j] = parent->children[j + 1];
+
+		parent->children[parent->child_cnt - 1] = NULL;
+		parent->child_cnt--;
+	}
+	rte_free(tm_node);
+	tm_node = NULL;
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_node_delete(struct rte_eth_dev *dev, uint32_t node_id,
+		struct rte_tm_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	struct sxe2_tm_node *tm_node = NULL;
+	int32_t ret = -EINVAL;
+
+	if (!error) {
+		PMD_LOG_ERR(DRV, "Invalid input: error is null.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	if (node_id == RTE_TM_NODE_ID_NULL) {
+		PMD_LOG_ERR(DRV, "Invalid node id. node_id = %u", node_id);
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "invalid node id";
+		goto l_end;
+	}
+
+	tm_node = sxe2_tm_find_node(tm_ctxt->root, node_id);
+	if (!tm_node) {
+		PMD_LOG_ERR(DRV, "No such node.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "no such node";
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_tm_tree_delete(tm_node);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Delete node failed.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "Delete node failed";
+		goto l_end;
+	}
+
+	if (tm_node == tm_ctxt->root)
+		tm_ctxt->root = NULL;
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_node_type_get(struct rte_eth_dev *dev, uint32_t node_id,
+		int32_t *is_leaf, struct rte_tm_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	struct sxe2_tm_node *tm_node = NULL;
+	int32_t ret = -EINVAL;
+
+	if (!is_leaf || !error) {
+		PMD_LOG_ERR(DRV, "Invalid input: is_leaf:0x%p or error:0x%p is null.",
+			is_leaf, error);
+		if (error) {
+			error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+			error->message = "Invalid input: is_leaf or error is null";
+		}
+		goto l_end;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_end;
+	}
+
+	if (node_id == RTE_TM_NODE_ID_NULL) {
+		PMD_LOG_ERR(DRV, "Invalid node id.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "invalid node id";
+		goto l_end;
+	}
+
+	tm_node = sxe2_tm_find_node(tm_ctxt->root, node_id);
+	if (!tm_node) {
+		PMD_LOG_ERR(DRV, "No such node.");
+		error->type = RTE_TM_ERROR_TYPE_NODE_ID;
+		error->message = "no such node";
+		goto l_end;
+	}
+
+	if (tm_node->level + 1 == tm_ctxt->tm_layers)
+		*is_leaf = true;
+	else
+		*is_leaf = false;
+	ret = 0;
+l_end:
+	return ret;
+}
+
+int32_t sxe2_tm_uninit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	struct sxe2_tm_shaper_profile *shaper_profile = NULL;
+	int32_t ret = 0;
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		ret = 0;
+		goto l_end;
+	}
+
+	tm_ctxt->tm_layers = 0;
+	tm_ctxt->root_max_children = 0;
+	tm_ctxt->committed = false;
+
+	(void)sxe2_tm_tree_delete(tm_ctxt->root);
+
+	while ((shaper_profile = TAILQ_FIRST(&tm_ctxt->profile_list))) {
+		TAILQ_REMOVE(&tm_ctxt->profile_list, shaper_profile, node);
+		rte_free(shaper_profile);
+	}
+l_end:
+	return ret;
+}
+
+int32_t sxe2_tm_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_sched_hw_cap *sched_ctxt = &adapter->sched_ctxt;
+	struct sxe2_tm_context *tm_ctxt = &adapter->tm_ctxt;
+	int32_t ret = 0;
+	struct sxe2_tm_shaper_profile *shaper_profile = NULL;
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0)
+		goto l_end;
+
+	tm_ctxt->tm_layers = sched_ctxt->tm_layers;
+	tm_ctxt->root_max_children = sched_ctxt->root_max_children;
+	tm_ctxt->prio_max = sched_ctxt->prio_max;
+	tm_ctxt->committed = false;
+	TAILQ_INIT(&tm_ctxt->profile_list);
+	tm_ctxt->root = NULL;
+
+	shaper_profile = rte_zmalloc("sxe2_tm_shaper_profile",
+		sizeof(struct sxe2_tm_shaper_profile), 0);
+	if (!shaper_profile) {
+		PMD_LOG_ERR(DRV, "Alloc shaper_profile memory failed.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	shaper_profile->id = RTE_TM_SHAPER_PROFILE_ID_NONE;
+	shaper_profile->ref_cnt = 1;
+	shaper_profile->profile.committed.rate = UINT64_MAX;
+	shaper_profile->profile.peak.rate = UINT64_MAX;
+	TAILQ_INSERT_TAIL(&tm_ctxt->profile_list, shaper_profile, node);
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_chk_all_leaf(struct rte_eth_dev *dev)
+{
+	int32_t ret = 0;
+	uint32_t i = 0;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	for (i = 0; i < dev->data->nb_tx_queues; i++) {
+		if (!sxe2_tm_find_node(adapter->tm_ctxt.root, i)) {
+			ret = -1;
+			break;
+		}
+	}
+	return ret;
+}
+
+static int32_t sxe2_tm_weight_calc(struct sxe2_tm_node *tm_node)
+{
+	int32_t ret = 0;
+	int32_t total_weight = 0;
+	int32_t total_weight2 = 0;
+	uint32_t i = 0;
+	uint32_t j = 0;
+	uint32_t k = 0;
+	uint32_t maxindex = 0;
+	uint32_t maxweight = 0;
+	struct sxe2_tm_node *cacl_node[SXE2_TM_MAX_CHILDREN_COUNT] = {NULL};
+
+	if (!tm_node) {
+		PMD_LOG_ERR(DRV, "Invalid input: tm_node is null.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (tm_node->child_cnt == 0)
+		goto l_end;
+
+	for (j = SXE2_TM_PRIO_MIN; j <= SXE2_TM_PRIO_MAX; j++) {
+		k = 0;
+		total_weight = 0;
+		total_weight2 = 0;
+		maxindex = 0;
+		maxweight = 0;
+
+		for (i = 0; i < tm_node->child_cnt; i++) {
+			if (tm_node->children[i]->priority == j)
+				cacl_node[k++] = tm_node->children[i];
+		}
+		if (k == 0)
+			continue;
+
+		for (i = 0; i < k; i++)
+			total_weight += cacl_node[i]->weight;
+
+		for (i = 0; i < k; i++) {
+			cacl_node[i]->hw_weight = cacl_node[i]->weight *
+				SXE2_TM_WEIGHT_SUM / total_weight;
+			total_weight2 += cacl_node[i]->hw_weight;
+			if (cacl_node[i]->hw_weight > maxweight) {
+				maxweight = cacl_node[i]->hw_weight;
+				maxindex = i;
+			}
+		}
+
+		cacl_node[maxindex]->hw_weight += SXE2_TM_WEIGHT_SUM - total_weight2;
+	}
+
+	for (i = 0; i < tm_node->child_cnt; i++) {
+		ret = sxe2_tm_weight_calc(tm_node->children[i]);
+		if (ret)
+			goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_tm_hierarchy_commit(struct rte_eth_dev *dev,
+				     int32_t clear_on_fail, struct rte_tm_error *error)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = -EINVAL;
+
+	if (!error) {
+		PMD_LOG_ERR(DRV, "Invalid input: error is null.");
+		ret = -EINVAL;
+		goto l_clear_on_fail;
+	}
+
+	if ((adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_TM) == 0) {
+		PMD_LOG_ERR(DRV, "The TM capability is not supported.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The TM capability is not supported.";
+		ret = ENOTSUP;
+		goto l_clear_on_fail;
+	}
+
+	if (dev->data->dev_started) {
+		PMD_LOG_ERR(DRV, "Device failed to Stop.");
+		error->message = "Device failed to Stop";
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		ret = -EPERM;
+		goto l_clear_on_fail;
+	}
+
+	ret = sxe2_tm_chk_all_leaf(dev);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "All tx queues need config.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "All tx queues need config.";
+		goto l_clear_on_fail;
+	}
+
+	ret = sxe2_tm_weight_calc(adapter->tm_ctxt.root);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "The weight in tree is wrong.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "The weight in tree is wrong.";
+		goto l_clear_on_fail;
+	}
+
+	ret = sxe2_drv_tm_commit(adapter);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Commit tree to fw failed.");
+		error->type = RTE_TM_ERROR_TYPE_UNSPECIFIED;
+		error->message = "Commit tree to fw failed.";
+		goto l_clear_on_fail;
+	}
+
+	adapter->tm_ctxt.committed = true;
+	ret = 0;
+	goto l_end;
+
+l_clear_on_fail:
+	if (clear_on_fail) {
+		(void)sxe2_tm_uninit(dev);
+		(void)sxe2_tm_init(dev);
+	}
+
+l_end:
+	return ret;
+}
+
+static const struct rte_tm_ops sxe2_tm_ops = {
+	.capabilities_get = sxe2_tm_capabilities_get,
+	.level_capabilities_get = sxe2_level_capabilities_get,
+	.node_capabilities_get = sxe2_node_capabilities_get,
+	.shaper_profile_add = sxe2_tm_shaper_profile_add,
+	.shaper_profile_delete = sxe2_tm_shaper_profile_del,
+	.node_add = sxe2_tm_node_add,
+	.node_delete = sxe2_tm_node_delete,
+	.node_type_get = sxe2_tm_node_type_get,
+
+	.hierarchy_commit = sxe2_tm_hierarchy_commit,
+};
+
+int32_t sxe2_tm_ops_get(struct rte_eth_dev *dev __rte_unused, void *arg)
+{
+	int32_t ret = 0;
+
+	if (!arg) {
+		ret = -EINVAL;
+		PMD_LOG_ERR(DRV, "%s failed because arg is NULL", __func__);
+		goto l_end;
+	}
+	*(const void **)arg = &sxe2_tm_ops;
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_tm.h b/drivers/net/sxe2/sxe2_tm.h
new file mode 100644
index 0000000000..c4f8da6a8e
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_tm.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_TM_H__
+#define __SXE2_TM_H__
+#include <ethdev_driver.h>
+#include <rte_flow_driver.h>
+#include "sxe2_osal.h"
+
+#define SXE2_TM_MAX_LEVEL 7
+#define SXE2_TM_1L_NODE_NUM_MAX 1
+#define SXE2_TM_2L_NODE_NUM_MAX 8
+#define SXE2_TM_3L_NODE_NUM_MAX 64
+#define SXE2_TM_4L_NODE_NUM_MAX 256
+
+#define SXE2_TM_MAX_CHILDREN_COUNT 8
+
+#define SXE2_TM_WEIGHT_MAX  (200)
+#define SXE2_TM_WEIGHT_MIN  (1)
+#define SXE2_TM_WEIGHT_SUM  (32768)
+
+#define SXE2_HW_RATE_MIN 62500ull
+#define SXE2_HW_RATE_MAX 12500000000ull
+
+#define SXE2_TM_PRIO_MAX (7)
+#define SXE2_TM_PRIO_MIN (0)
+
+enum sxe2_tm_node_type {
+	SXE2_TM_NODE_TYPE_VSIG = 0,
+	SXE2_TM_NODE_TYPE_MID,
+	SXE2_TM_NODE_TYPE_QUEUE,
+	SXE2_TM_NODE_TYPE_INVALID,
+};
+
+struct sxe2_tm_shaper_profile {
+	TAILQ_ENTRY(sxe2_tm_shaper_profile) node;
+	uint32_t id;
+	uint32_t ref_cnt;
+	struct rte_tm_shaper_params profile;
+};
+
+TAILQ_HEAD(sxe2_shaper_profile_list, sxe2_tm_shaper_profile);
+
+struct sxe2_tm_node {
+	uint16_t id;
+	uint16_t teid;
+	uint32_t level;
+	uint32_t child_cnt;
+	uint32_t type;
+	uint16_t hw_weight;
+	uint16_t weight;
+	uint8_t priority;
+	struct sxe2_tm_node *parent;
+	uint8_t index_in_parent;
+	struct sxe2_tm_node *children[SXE2_TM_MAX_CHILDREN_COUNT];
+	struct sxe2_tm_shaper_profile *shaper_profile;
+};
+
+struct sxe2_tm_context {
+	uint32_t tm_layers;
+	uint16_t root_teid;
+	uint8_t root_max_children;
+	uint8_t prio_max;
+	bool committed;
+	struct sxe2_tm_node *root;
+	struct sxe2_shaper_profile_list profile_list;
+};
+
+int32_t sxe2_tm_ops_get(struct rte_eth_dev *dev __rte_unused, void *arg);
+
+int32_t sxe2_tm_init(struct rte_eth_dev *dev);
+
+int32_t sxe2_tm_uninit(struct rte_eth_dev *dev);
+
+#endif /* __SXE2_TM_H__ */
diff --git a/drivers/net/sxe2/sxe2_tx.c b/drivers/net/sxe2/sxe2_tx.c
index a05beb8c7a..a280edc9c5 100644
--- a/drivers/net/sxe2/sxe2_tx.c
+++ b/drivers/net/sxe2/sxe2_tx.c
@@ -304,7 +304,6 @@ int32_t __rte_cold sxe2_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	offloads = tx_conf->offloads | dev->data->dev_conf.txmode.offloads;
-
 	txq = sxe2_tx_queue_alloc(dev, queue_idx, nb_desc, socket_id);
 	if (txq == NULL) {
 		PMD_LOG_ERR(TX, "failed to alloc sxe2vf tx queue:%u resource", queue_idx);
-- 
2.47.3


^ permalink raw reply related

* [PATCH v1 09/23] drivers: interrupt handling
From: liujie5 @ 2026-05-24  9:32 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260524093259.397506-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch includes:
- MSI-X vector allocation and mapping logic.
- Support for rx_queue_intr_enable and rx_queue_intr_disable ops.
- Interrupt handler to process admin and queue events.
- Integration with EAL interrupt framework.

RX queue interrupts allow applications to use rte_eth_dev_rx_intr_wait()
for power-efficient packet processing.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/common/sxe2/sxe2_ioctl_chnl.c      | 177 +++-
 drivers/common/sxe2/sxe2_ioctl_chnl_func.h |  18 +
 drivers/net/sxe2/meson.build               |   1 +
 drivers/net/sxe2/sxe2_cmd_chnl.c           |  42 +
 drivers/net/sxe2/sxe2_cmd_chnl.h           |   4 +
 drivers/net/sxe2/sxe2_drv_cmd.h            |   8 +
 drivers/net/sxe2/sxe2_ethdev.c             |  93 +-
 drivers/net/sxe2/sxe2_ethdev.h             |   6 +
 drivers/net/sxe2/sxe2_irq.c                | 941 +++++++++++++++++++++
 drivers/net/sxe2/sxe2_irq.h                |  21 +
 drivers/net/sxe2/sxe2vf_regs.h             |  82 ++
 11 files changed, 1389 insertions(+), 4 deletions(-)
 create mode 100644 drivers/net/sxe2/sxe2_irq.c
 create mode 100644 drivers/net/sxe2/sxe2vf_regs.h

diff --git a/drivers/common/sxe2/sxe2_ioctl_chnl.c b/drivers/common/sxe2/sxe2_ioctl_chnl.c
index 2ffbeb9217..173d8d57ae 100644
--- a/drivers/common/sxe2/sxe2_ioctl_chnl.c
+++ b/drivers/common/sxe2/sxe2_ioctl_chnl.c
@@ -67,6 +67,7 @@ sxe2_drv_cmd_exec(struct sxe2_common_device *cdev,
 	return ret;
 }
 
+
 RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_open)
 int32_t
 sxe2_drv_dev_open(struct sxe2_common_device *cdev, struct rte_pci_device *pci_dev)
@@ -87,7 +88,7 @@ sxe2_drv_dev_open(struct sxe2_common_device *cdev, struct rte_pci_device *pci_de
 	if (fd < 0) {
 		ret = -EBADF;
 		PMD_LOG_ERR(COM, "Fail to open device:%s, ret=%d, err:%s",
-				drv_name, ret, strerror(errno));
+			    drv_name, ret, strerror(errno));
 		goto l_end;
 	}
 
@@ -159,6 +160,177 @@ sxe2_drv_dev_handshake(struct sxe2_common_device *cdev)
 	return ret;
 }
 
+RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_rxq_irq_set)
+int32_t
+sxe2_drv_dev_rxq_irq_set(struct sxe2_common_device *cdev,
+		       uint16_t base_irq, int32_t *efd, uint16_t nb_irq)
+{
+	struct sxe2_ioctl_irq_set cmd_params;
+	int32_t ret = 0;
+	int32_t cmd_fd = 0;
+
+	if (cdev->config.kernel_reset) {
+		ret = -EPERM;
+		PMD_LOG_WARN(COM, "kernel reset, need restart app.");
+		goto l_end;
+	}
+
+	cmd_fd = SXE2_CDEV_TO_CMD_FD(cdev);
+	if (cmd_fd < 0) {
+		ret = -EBADF;
+		PMD_LOG_ERR(COM, "Failed to exec cmd, fd=%d", cmd_fd);
+		goto l_end;
+	}
+
+	PMD_LOG_DEBUG(COM, "Open fd=%d to set rxq irq, base_queue=%d, efds=%p, nb_irq=%d",
+		cmd_fd, base_irq, efd, nb_irq);
+
+	memset(&cmd_params, 0, sizeof(struct sxe2_ioctl_irq_set));
+	cmd_params.base_irq_in_com = base_irq;
+	cmd_params.cnt = nb_irq;
+	cmd_params.event_fd = efd;
+
+	pthread_mutex_lock(&cdev->config.lock);
+	ret = ioctl(cmd_fd, SXE2_COM_CMD_IO_IRQS_REQ, &cmd_params);
+	if (ret < 0) {
+		PMD_LOG_ERR(COM, "Failed to set io irqs, fd=%d, ret=%d, err:%s",
+			    cmd_fd, ret, strerror(errno));
+		pthread_mutex_unlock(&cdev->config.lock);
+		goto l_end;
+	}
+	pthread_mutex_unlock(&cdev->config.lock);
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_other_irq_set)
+int32_t
+sxe2_drv_dev_other_irq_set(struct sxe2_common_device *cdev,
+			int32_t efd, uint64_t event)
+{
+	int32_t ret = 0;
+	int32_t cmd_fd = 0;
+	struct sxe2_ioctl_other_evt_set cmd_params;
+
+	if (cdev->config.kernel_reset) {
+		ret = -EPERM;
+		PMD_LOG_WARN(COM, "kernel reset, need restart app.");
+		goto l_end;
+	}
+
+	cmd_fd = SXE2_CDEV_TO_CMD_FD(cdev);
+	if (cmd_fd < 0) {
+		ret = -EBADF;
+		PMD_LOG_ERR(COM, "Failed to exec cmd, fd=%d", cmd_fd);
+		goto l_end;
+	}
+
+	PMD_LOG_DEBUG(COM, "Open fd=%d to set other irq, efd=%d, event=%"PRIu64"",
+		cmd_fd, efd, event);
+
+	memset(&cmd_params, 0, sizeof(struct sxe2_ioctl_other_evt_set));
+	cmd_params.eventfd = efd;
+	cmd_params.filter_table = event;
+
+	pthread_mutex_lock(&cdev->config.lock);
+	ret = ioctl(cmd_fd, SXE2_COM_CMD_EVT_IRQ_REQ, &cmd_params);
+	if (ret < 0) {
+		PMD_LOG_ERR(COM, "Failed to set others evt, fd=%d, ret=%d, err:%s",
+			   cmd_fd, ret, strerror(errno));
+		ret = -EIO;
+		pthread_mutex_unlock(&cdev->config.lock);
+		goto l_end;
+	}
+	pthread_mutex_unlock(&cdev->config.lock);
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_other_irq_get)
+int32_t
+sxe2_drv_dev_other_irq_get(struct sxe2_common_device *cdev, uint64_t *event)
+{
+	int32_t ret = 0;
+	int32_t cmd_fd = 0;
+	struct sxe2_ioctl_other_evt_get cmd_params;
+
+	if (cdev->config.kernel_reset) {
+		ret = -EPERM;
+		PMD_LOG_WARN(COM, "kernel reset, need restart app.");
+		goto l_end;
+	}
+
+	cmd_fd = SXE2_CDEV_TO_CMD_FD(cdev);
+	if (cmd_fd < 0) {
+		ret = -EBADF;
+		PMD_LOG_ERR(COM, "Failed to exec cmd, fd=%d", cmd_fd);
+		goto l_end;
+	}
+
+	PMD_LOG_DEBUG(COM, "Open fd=%d to get other irq", cmd_fd);
+
+	memset(&cmd_params, 0, sizeof(struct sxe2_ioctl_other_evt_get));
+
+	pthread_mutex_lock(&cdev->config.lock);
+	ret = ioctl(cmd_fd, SXE2_COM_CMD_EVT_CAUSE_GET, &cmd_params);
+	if (ret < 0) {
+		PMD_LOG_ERR(COM, "Failed to set others evt, fd=%d, ret=%d, err:%s",
+			   cmd_fd, ret, strerror(errno));
+		ret = -EIO;
+		pthread_mutex_unlock(&cdev->config.lock);
+		goto l_end;
+	}
+	pthread_mutex_unlock(&cdev->config.lock);
+	*event = cmd_params.evt_cause;
+
+l_end:
+	return ret;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_reset_irq_set)
+int32_t
+sxe2_drv_dev_reset_irq_set(struct sxe2_common_device *cdev, int32_t efd)
+{
+	int32_t ret = 0;
+	int32_t cmd_fd = 0;
+	struct sxe2_ioctl_reset_sub_set cmd_params;
+
+	if (cdev->config.kernel_reset) {
+		ret = -EPERM;
+		PMD_LOG_WARN(COM, "kernel reset, need restart app.");
+		goto l_end;
+	}
+
+	cmd_fd = SXE2_CDEV_TO_CMD_FD(cdev);
+	if (cmd_fd < 0) {
+		ret = -EBADF;
+		PMD_LOG_ERR(COM, "Failed to exec cmd, fd=%d", cmd_fd);
+		goto l_end;
+	}
+
+	PMD_LOG_DEBUG(COM, "Open fd=%d to set reset irq, efd=%d",
+		cmd_fd, efd);
+
+	memset(&cmd_params, 0, sizeof(struct sxe2_ioctl_reset_sub_set));
+	cmd_params.eventfd = efd;
+
+	pthread_mutex_lock(&cdev->config.lock);
+	ret = ioctl(cmd_fd, SXE2_COM_CMD_RST_IRQ_REQ, &cmd_params);
+	if (ret < 0) {
+		PMD_LOG_ERR(COM, "Failed to set reset irqs, fd=%d, ret=%d, err:%s",
+			   cmd_fd, ret, strerror(errno));
+		ret = -EIO;
+		pthread_mutex_unlock(&cdev->config.lock);
+		goto l_end;
+	}
+	pthread_mutex_unlock(&cdev->config.lock);
+
+l_end:
+	return ret;
+}
+
 RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_mmap)
 void
 *sxe2_drv_dev_mmap(struct sxe2_common_device *cdev, uint8_t bar_idx, uint64_t len, uint64_t offset)
@@ -223,7 +395,7 @@ sxe2_drv_dev_munmap(struct sxe2_common_device *cdev, void *virt, uint64_t len)
 RTE_EXPORT_INTERNAL_SYMBOL(sxe2_drv_dev_dma_map)
 int32_t
 sxe2_drv_dev_dma_map(struct sxe2_common_device *cdev, uint64_t vaddr,
-			uint64_t iova, uint64_t size)
+		     uint64_t iova, uint64_t size)
 {
 	struct sxe2_ioctl_iommu_dma_map cmd_params;
 	enum rte_iova_mode iova_mode;
@@ -322,4 +494,3 @@ sxe2_drv_dev_dma_unmap(struct sxe2_common_device *cdev, uint64_t iova)
 l_end:
 	return ret;
 }
-
diff --git a/drivers/common/sxe2/sxe2_ioctl_chnl_func.h b/drivers/common/sxe2/sxe2_ioctl_chnl_func.h
index aed5a5b50d..f29194fc9e 100644
--- a/drivers/common/sxe2/sxe2_ioctl_chnl_func.h
+++ b/drivers/common/sxe2/sxe2_ioctl_chnl_func.h
@@ -29,6 +29,7 @@ int32_t
 sxe2_drv_dev_open(struct sxe2_common_device *cdev,
 		struct rte_pci_device *pci_dev);
 
+
 __rte_internal
 void
 sxe2_drv_dev_close(struct sxe2_common_device *cdev);
@@ -37,6 +38,23 @@ __rte_internal
 int32_t
 sxe2_drv_dev_handshake(struct sxe2_common_device *cdev);
 
+__rte_internal
+int32_t
+sxe2_drv_dev_rxq_irq_set(struct sxe2_common_device *cdev, uint16_t base_irq,
+			 int32_t *efd, uint16_t nb_irq);
+
+__rte_internal
+int32_t
+sxe2_drv_dev_other_irq_set(struct sxe2_common_device *cdev, int32_t efd, uint64_t event);
+
+__rte_internal
+int32_t
+sxe2_drv_dev_other_irq_get(struct sxe2_common_device *cdev, uint64_t *event);
+
+__rte_internal
+int32_t
+sxe2_drv_dev_reset_irq_set(struct sxe2_common_device *cdev, int32_t fd);
+
 __rte_internal
 void
 *sxe2_drv_dev_mmap(struct sxe2_common_device *cdev, uint8_t bar_idx,
diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index d860629def..c73e13bbad 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -68,6 +68,7 @@ sources += files(
         'sxe2_security.c',
         'sxe2_mp.c',
         'sxe2_stats.c',
+        'sxe2_irq.c',
 )
 
 allow_internal_get_api = true
\ No newline at end of file
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index a1fc8a50e3..d1f15084ed 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -348,6 +348,48 @@ int32_t sxe2_drv_mac_link_status_get(struct sxe2_adapter *adapter)
 	return ret;
 }
 
+int32_t sxe2_drv_rxq_bind_irq(struct sxe2_adapter *adapter, uint16_t rxq_idx, uint16_t msix_idx)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_drv_queue_irq_bind_req req = {0};
+	int32_t ret = 0;
+
+	req.msix_idx = msix_idx;
+	req.q_idx = rxq_idx;
+	req.itr_idx = 0;
+	req.bind = true;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_EVT_IRQ_BAND_RXQ,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "rxq bind irq failed, ret=%d", ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_rxq_unbind_irq(struct sxe2_adapter *adapter, uint16_t rxq_idx)
+{
+	struct sxe2_common_device *cdev = adapter->cdev;
+	struct sxe2_drv_cmd_params param = {0};
+	struct sxe2_drv_queue_irq_bind_req req = {0};
+	int32_t ret = 0;
+
+	req.bind = false;
+	req.q_idx = rxq_idx;
+
+	sxe2_drv_cmd_params_fill(adapter, &param, SXE2_DRV_CMD_EVT_IRQ_BAND_RXQ,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &param);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV, "rxq unbind irq failed, ret=%d", ret);
+
+	return ret;
+}
+
 int32_t sxe2_drv_get_mac_stats(struct sxe2_adapter *adapter)
 {
 	int32_t ret = 0;
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index ff73d2f901..c13653e8af 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -118,4 +118,8 @@ int32_t sxe2_drv_rss_hf_clear(struct sxe2_adapter *adapter);
 
 int32_t sxe2_drv_ptp_gettime(struct sxe2_adapter *adapter, struct sxe2_rx_queue *rxq);
 
+int32_t sxe2_drv_rxq_bind_irq(struct sxe2_adapter *adapter, uint16_t rxq_idx, uint16_t msix_idx);
+
+int32_t sxe2_drv_rxq_unbind_irq(struct sxe2_adapter *adapter, uint16_t rxq_idx);
+
 #endif /* __SXE2_CMD_CHNL_H__ */
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index a88e2b24bf..0b2a715000 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -215,6 +215,14 @@ struct sxe2_drv_q_switch_req {
 	uint8_t rsv[2];
 };
 
+struct sxe2_drv_queue_irq_bind_req {
+	__le16 q_idx;
+	__le16 msix_idx;
+	uint8_t itr_idx;
+	uint8_t bind;
+	uint8_t rsv[2];
+};
+
 struct sxe2_drv_vsi_create_req_resp {
 	uint16_t vsi_id;
 	uint16_t vsi_type;
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index dbe1a2bce1..f3fee74ddf 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -22,6 +22,7 @@
 #include <rte_eal_paging.h>
 
 #include "sxe2_ethdev.h"
+#include "sxe2_irq.h"
 #include "sxe2_drv_cmd.h"
 #include "sxe2_cmd_chnl.h"
 #include "sxe2_tx.h"
@@ -107,6 +108,9 @@ static const struct eth_dev_ops sxe2_eth_dev_ops = {
 	.rx_queue_release           = sxe2_rx_queue_release,
 	.tx_queue_setup             = sxe2_tx_queue_setup,
 	.tx_queue_release           = sxe2_tx_queue_release,
+	.rx_queue_intr_enable       = sxe2_rx_queue_intr_enable,
+	.rx_queue_intr_disable      = sxe2_rx_queue_intr_disable,
+
 	.rxq_info_get               = sxe2_rx_queue_info_get,
 	.txq_info_get               = sxe2_tx_queue_info_get,
 	.rx_burst_mode_get          = sxe2_rx_burst_mode_get,
@@ -177,6 +181,8 @@ static int32_t sxe2_dev_stop(struct rte_eth_dev *dev)
 	if (adapter->started == 0)
 		goto l_end;
 
+	sxe2_rxq_intr_disable(dev);
+
 	sxe2_txqs_all_stop(dev);
 	sxe2_rxqs_all_stop(dev);
 
@@ -215,6 +221,12 @@ static int32_t sxe2_dev_start(struct rte_eth_dev *dev)
 		goto l_end;
 	}
 
+	ret = sxe2_rxq_intr_enable(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to enable rx queue intr");
+		goto l_rxq_intr_err;
+	}
+
 	ret = sxe2_queues_start(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "enable queues failed");
@@ -224,7 +236,10 @@ static int32_t sxe2_dev_start(struct rte_eth_dev *dev)
 	dev->data->dev_started = 1;
 	adapter->started = 1;
 	goto l_end;
+
 l_start_queues_err:
+	(void)sxe2_rxq_intr_disable(dev);
+l_rxq_intr_err:
 	(void)sxe2_filter_rule_stop(dev);
 l_end:
 	return ret;
@@ -615,6 +630,8 @@ static int32_t sxe2_func_caps_get(struct sxe2_adapter *adapter)
 
 	sxe2_sw_queue_ctx_hw_cap_set(adapter, &dev_caps.queue_caps);
 
+	sxe2_sw_irq_ctx_hw_cap_set(adapter, &dev_caps.msix_caps);
+
 	sxe2_sw_rss_ctx_hw_cap_set(adapter, &dev_caps.rss_hash_caps);
 
 	sxe2_sw_vsi_ctx_hw_cap_set(adapter, &dev_caps.vsi_caps);
@@ -636,6 +653,41 @@ static int32_t sxe2_dev_caps_get(struct sxe2_adapter *adapter)
 	return ret;
 }
 
+uint32_t sxe2_pci_map_read_reg(struct sxe2_adapter *adapter,
+		enum sxe2_pci_map_resource res_type, uint16_t idx_in_func)
+{
+	void *reg_addr;
+	uint32_t value;
+
+	reg_addr = sxe2_pci_map_addr_get(adapter, res_type, idx_in_func);
+	if (unlikely(reg_addr == NULL)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "reg addr:0x%p is error.", reg_addr);
+		value = SXE2_PCI_MAP_INVALID_VAL;
+		goto l_ret;
+	}
+
+	value = SXE2_PCI_REG_READ(reg_addr);
+
+l_ret:
+	return value;
+}
+
+void sxe2_pci_map_write_reg(struct sxe2_adapter *adapter,
+		enum sxe2_pci_map_resource res_type, uint16_t idx_in_func, uint32_t value)
+{
+	void *reg_addr;
+
+	reg_addr = sxe2_pci_map_addr_get(adapter, res_type, idx_in_func);
+	if (unlikely(reg_addr == NULL)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "reg addr:0x%p is error.", reg_addr);
+		goto l_ret;
+	}
+
+	SXE2_PCI_REG_WRITE_WC(reg_addr, value);
+l_ret:
+	return;
+}
+
 int32_t sxe2_dev_pci_seg_map(struct sxe2_adapter *adapter,
 			     enum sxe2_pci_map_resource res_type,
 			     uint64_t org_len,
@@ -715,6 +767,27 @@ static int32_t sxe2_hw_init(struct rte_eth_dev *dev)
 	return ret;
 }
 
+static int32_t sxe2_sw_init(struct rte_eth_dev *dev)
+{
+	int32_t ret = -1;
+
+	PMD_INIT_FUNC_TRACE();
+
+	ret = sxe2_sw_irq_ctxt_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to sw irq ctxt init, ret=[%d]", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static void sxe2_sw_uninit(struct rte_eth_dev *dev)
+{
+	sxe2_sw_irq_ctxt_uninit(dev);
+}
+
 int32_t sxe2_dev_pci_res_seg_map(struct sxe2_adapter *adapter,
 				 uint32_t res_type,
 				 uint32_t item_cnt,
@@ -1065,6 +1138,18 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_dev_info_err;
 	}
 
+	ret = sxe2_sw_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize sw parameters, ret=[%d]", ret);
+		goto init_sw_err;
+	}
+
+	ret = sxe2_intr_init(dev);
+	if (ret != 0) {
+		PMD_LOG_ERR(INIT, "Failed to initialize interrupt, ret:%d", ret);
+		goto init_irq_err;
+	}
+
 	ret = sxe2_eth_init(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to initialize eth parameters, ret=%d", ret);
@@ -1109,6 +1194,10 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 init_rss_err:
 	sxe2_security_uinit(dev);
 init_security_err:
+	sxe2_intr_uninit(dev);
+init_irq_err:
+	sxe2_sw_uninit(dev);
+init_sw_err:
 	sxe2_eth_uinit(dev);
 init_eth_err:
 init_dev_info_err:
@@ -1132,8 +1221,10 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 	(void)sxe2_sched_uinit(dev);
 	sxe2_vsi_uninit(dev);
 	sxe2_security_uinit(dev);
-	sxe2_dev_pci_map_uinit(dev);
+	sxe2_intr_uninit(dev);
+	sxe2_sw_uninit(dev);
 	sxe2_eth_uinit(dev);
+	sxe2_dev_pci_map_uinit(dev);
 
 l_end:
 	return 0;
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 32a67ed344..65ada44c12 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -354,6 +354,12 @@ int32_t sxe2_dev_pci_res_seg_map(struct sxe2_adapter *adapter,
 				 uint32_t item_cnt,
 				 uint32_t item_base);
 
+void sxe2_pci_map_write_reg(struct sxe2_adapter *adapter,
+		enum sxe2_pci_map_resource res_type, uint16_t idx_in_func, uint32_t value);
+
+uint32_t sxe2_pci_map_read_reg(struct sxe2_adapter *adapter,
+		enum sxe2_pci_map_resource res_type, uint16_t idx_in_func);
+
 void sxe2_dev_pci_seg_unmap(struct sxe2_adapter *adapter, uint32_t res_type);
 
 int32_t sxe2_dev_pci_map_init(struct rte_eth_dev *dev);
diff --git a/drivers/net/sxe2/sxe2_irq.c b/drivers/net/sxe2/sxe2_irq.c
new file mode 100644
index 0000000000..13619500ea
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_irq.c
@@ -0,0 +1,941 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <stdint.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+#include <ethdev_pci.h>
+#include <ethdev_driver.h>
+#include <rte_alarm.h>
+#include <fcntl.h>
+#include <rte_stdatomic.h>
+
+#include "sxe2_ethdev.h"
+#include "sxe2_irq.h"
+#include "sxe2_common_log.h"
+#include "sxe2_queue.h"
+#include "sxe2_drv_cmd.h"
+#include "sxe2_ioctl_chnl_func.h"
+#include "sxe2vf_regs.h"
+#include "sxe2_host_regs.h"
+#include "sxe2_cmd_chnl.h"
+
+#define SXE2_INT_EVENT_OICR_ALL (SXE2_PF_INT_OICR_SWINT | \
+					SXE2_PF_INT_OICR_LAN_TX_ERR | \
+					SXE2_PF_INT_OICR_LAN_RX_ERR | \
+					SXE2_PF_INT_OICR_FW)
+
+#define MAX_EVENT_PENDING (16)
+
+struct sxe2_event_element {
+	TAILQ_ENTRY(sxe2_event_element) next;
+	struct rte_eth_dev *dev;
+};
+
+struct sxe2_event_handler {
+	RTE_ATOMIC(uint16_t)ndev;
+	rte_thread_t tid;
+	int32_t fd[2];
+	rte_spinlock_t  lock;
+	TAILQ_HEAD(event_list, sxe2_event_element) pending;
+};
+static struct sxe2_event_handler event_handler = {
+	.fd = {-1, -1},
+};
+static volatile int32_t event_thread_run;
+
+
+static void sxe2_event_irq_common_handler(struct sxe2_adapter *adapter, uint64_t oicr)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[adapter->dev_info.dev_data->port_id];
+
+	if (oicr & RTE_BIT32(SXE2_COM_EC_LINK_CHG)) {
+		PMD_DEV_LOG_INFO(adapter, DRV, "OICR=%" PRIu64, oicr);
+		(void)sxe2_drv_mac_link_status_get(adapter);
+		if (rte_eal_process_type() == RTE_PROC_PRIMARY)
+			rte_eth_dev_callback_process(dev,
+						     RTE_ETH_EVENT_INTR_LSC,
+						     NULL);
+	}
+}
+
+static uint32_t sxe2_event_intr_handle(void *param __rte_unused)
+{
+	struct sxe2_event_handler *handler = &event_handler;
+	struct sxe2_adapter *adapter;
+	struct sxe2_event_element *pos;
+	struct sxe2_event_element *tmp;
+	int32_t ret = 0;
+	uint64_t oicr = 0;
+	TAILQ_HEAD(event_list, sxe2_event_element) pending;
+	int8_t unused[MAX_EVENT_PENDING];
+	ssize_t nr;
+
+	while (event_thread_run) {
+		nr = read(handler->fd[0], &unused, sizeof(unused));
+		if (nr <= 0)
+			break;
+
+		rte_spinlock_lock(&handler->lock);
+		TAILQ_INIT(&pending);
+		TAILQ_CONCAT(&pending, &handler->pending, next);
+		rte_spinlock_unlock(&handler->lock);
+
+		TAILQ_FOREACH_SAFE(pos, &pending, next, tmp) {
+			TAILQ_REMOVE(&pending, pos, next);
+			adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(pos->dev);
+
+			ret = sxe2_drv_dev_other_irq_get(adapter->cdev, &oicr);
+			sxe2_event_irq_common_handler(adapter, oicr);
+
+			rte_free(pos);
+		}
+	}
+
+	return ret;
+}
+
+static int32_t sxe2_event_intr_handler_init(void)
+{
+	struct sxe2_event_handler *handler = &event_handler;
+	int32_t ret = 0;
+	int err = 0;
+
+	PMD_INIT_FUNC_TRACE();
+	if (rte_atomic_fetch_add_explicit(&handler->ndev, 1, rte_memory_order_acq_rel) >= 1) {
+		ret = 0;
+		PMD_LOG_DEBUG(INIT, "%s: ndev > 1", __func__);
+		goto l_end;
+	}
+
+#if defined(RTE_EXEC_ENV_IS_WINDOWS) && RTE_EXEC_ENV_IS_WINDOWS != 0
+	err = _pipe(handler->fd, MAX_EVENT_PENDING, O_BINARY);
+#else
+	err = pipe(handler->fd);
+#endif
+	if (err != 0) {
+		ret = -ECHILD;
+		rte_atomic_fetch_sub_explicit(&handler->ndev, 1, rte_memory_order_release);
+		PMD_LOG_ERR(INIT, "%s: pipe failed", __func__);
+		goto l_end;
+	}
+
+	event_thread_run = 1;
+
+	TAILQ_INIT(&handler->pending);
+	rte_spinlock_init(&handler->lock);
+
+	if (rte_thread_create_internal_control(&handler->tid, "sxe2-event",
+				sxe2_event_intr_handle, NULL)) {
+		PMD_LOG_ERR(INIT, "%s: thread create failed", __func__);
+		rte_atomic_fetch_sub_explicit(&handler->ndev, 1, rte_memory_order_release);
+		ret = -ECHILD;
+		goto l_end;
+	}
+	ret = 0;
+l_end:
+	return ret;
+}
+
+static void sxe2_event_intr_handler_uinit(void)
+{
+	struct sxe2_event_handler *handler = &event_handler;
+	struct sxe2_event_element *pos;
+	struct sxe2_event_element *tmp;
+	ssize_t nw = 0;
+	int8_t notify_byte = 0;
+
+	PMD_INIT_FUNC_TRACE();
+	if (rte_atomic_fetch_sub_explicit(&handler->ndev, 1, rte_memory_order_acq_rel) > 1) {
+		PMD_LOG_DEBUG(INIT, "event handler uinit, ndev > 0");
+		return;
+	}
+
+	event_thread_run = 0;
+	nw = write(handler->fd[1], &notify_byte, 1);
+	RTE_SET_USED(nw);
+
+	(void)rte_thread_join(handler->tid, NULL);
+
+	if (handler->fd[0] != -1) {
+		close(handler->fd[0]);
+		handler->fd[0] = -1;
+	}
+	if (handler->fd[1] != -1) {
+		close(handler->fd[1]);
+		handler->fd[1] = -1;
+	}
+
+	rte_spinlock_lock(&handler->lock);
+	TAILQ_FOREACH_SAFE(pos, &handler->pending, next, tmp) {
+		TAILQ_REMOVE(&handler->pending, pos, next);
+		rte_free(pos);
+	}
+	rte_spinlock_unlock(&handler->lock);
+}
+
+static void sxe2_event_intr_post(struct rte_eth_dev *dev)
+{
+	struct sxe2_event_handler *handler = &event_handler;
+	struct sxe2_event_element *elem = rte_malloc(NULL, sizeof(struct sxe2_event_element), 0);
+	int8_t notify_byte = 0;
+	ssize_t nw = 0;
+
+	if (!elem)
+		goto l_end;
+
+	elem->dev = dev;
+
+	rte_spinlock_lock(&handler->lock);
+	TAILQ_INSERT_TAIL(&handler->pending, elem, next);
+	rte_spinlock_unlock(&handler->lock);
+
+	nw = write(handler->fd[1], &notify_byte, 1);
+	RTE_SET_USED(nw);
+
+l_end:
+	return;
+}
+
+static void sxe2_interrupt_handler_other(void *arg)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)arg;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t eventfd = adapter->irq_ctxt.other_event_fd;
+	int32_t ret = 0;
+	uint64_t buf = 0;
+
+	ret = read(eventfd, &buf, sizeof(buf));
+	if (ret != sizeof(buf)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "read eventfd[%d] failed, ret:%d, errno:%d.",
+				eventfd, ret, errno);
+	}
+
+	sxe2_event_intr_post(dev);
+}
+
+static void sxe2_interrupt_handler_reset(void *arg)
+{
+	struct rte_eth_dev *dev = (struct rte_eth_dev *)arg;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t resetfd = adapter->irq_ctxt.reset_event_fd;
+	int32_t ret = 0;
+	uint64_t buf = 0;
+
+	ret = read(resetfd, &buf, sizeof(buf));
+	if (ret != sizeof(buf)) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "read resetfd[%d] failed, ret:%d, errno:%d.",
+				resetfd, ret, errno);
+	}
+
+	sxe2_drv_cmd_close(adapter->cdev);
+
+	rte_eth_dev_callback_process(dev, RTE_ETH_EVENT_INTR_RMV, NULL);
+}
+
+int32_t sxe2_sw_irq_ctxt_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	int32_t ret = 0;
+
+	irq_ctxt->rxq_avail_cnt = irq_ctxt->max_cnt_hw;
+	irq_ctxt->rxq_base_idx_in_pf = irq_ctxt->base_idx_in_func;
+	irq_ctxt->reset_event_fd = -1;
+	irq_ctxt->other_event_fd = -1;
+
+	return ret;
+}
+
+void sxe2_sw_irq_ctxt_uninit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+
+	memset(&adapter->irq_ctxt, 0, sizeof(adapter->irq_ctxt));
+	adapter->irq_ctxt.reset_event_fd = -1;
+	adapter->irq_ctxt.other_event_fd = -1;
+}
+
+void sxe2_sw_irq_ctx_hw_cap_set(struct sxe2_adapter *adapter,
+				struct sxe2_drv_msix_caps *msix_caps)
+{
+	adapter->irq_ctxt.max_cnt_hw = msix_caps->msix_vectors_cnt;
+	adapter->irq_ctxt.base_idx_in_func = msix_caps->base_idx_in_func;
+}
+
+static int32_t sxe2_intr_handler_cfg(struct rte_intr_handle *handle,
+		int32_t fd, rte_intr_callback_fn cb, void *cb_arg)
+{
+	int32_t ret = 0;
+
+	ret = rte_intr_fd_set(handle, fd);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to set intr_handle->fd, error %i (%s)",
+			errno, strerror(errno));
+		goto err;
+	}
+
+	ret = rte_intr_type_set(handle, RTE_INTR_HANDLE_EXT);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to set intr_handle->type, error %i (%s)",
+			errno, strerror(errno));
+		goto err;
+	}
+
+	ret = rte_intr_callback_register(handle, cb, cb_arg);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize register intr callback, ret=%d", ret);
+		goto err;
+	}
+err:
+	return ret;
+}
+
+static struct rte_intr_handle *
+sxe2_intr_handler_create(int32_t fd, rte_intr_callback_fn cb, void *cb_arg)
+{
+	struct rte_intr_handle *tmp_intr_handle = NULL;
+	int32_t ret = 0;
+
+	tmp_intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE);
+	if (!tmp_intr_handle) {
+		PMD_LOG_ERR(INIT, "Failed to alloc memory for intr_handler, error %i (%s)",
+			errno, strerror(errno));
+		goto err;
+	}
+
+	ret = rte_intr_fd_set(tmp_intr_handle, fd);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to set intr_handle->fd, error %i (%s)",
+				errno, strerror(errno));
+		goto err;
+	}
+
+	ret = rte_intr_type_set(tmp_intr_handle, RTE_INTR_HANDLE_EXT);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to set intr_handle->type, error %i (%s)",
+				errno, strerror(errno));
+		goto err;
+	}
+
+	ret = rte_intr_callback_register(tmp_intr_handle, cb, cb_arg);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize register intr callback, ret=%d", ret);
+		goto err;
+	}
+
+	return tmp_intr_handle;
+err:
+	rte_intr_instance_free(tmp_intr_handle);
+	return NULL;
+}
+
+static void sxe2_intr_handler_destroy(struct rte_intr_handle *intr_handle,
+				  rte_intr_callback_fn cb, void *cb_arg)
+{
+	if (!intr_handle)
+		return;
+
+	if (rte_intr_fd_get(intr_handle) >= 0)
+		(void)rte_intr_callback_unregister(intr_handle, cb, cb_arg);
+	rte_intr_instance_free(intr_handle);
+}
+
+static int32_t sxe2_event_intr_fd_create(void)
+{
+	int32_t fd = 0;
+
+	fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+	if (fd < 0) {
+		PMD_LOG_ERR(INIT, "Can't setup eventfd, error %i (%s)",
+			errno, strerror(errno));
+		goto err;
+	}
+
+	return fd;
+err:
+	return -EBADF;
+}
+
+static void sxe2_event_intr_fd_destroy(int32_t fd)
+{
+	if (fd >= 0)
+		close(fd);
+}
+
+static int32_t sxe2_other_intr_register(struct rte_eth_dev *dev, int32_t fd)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	uint64_t event = RTE_BIT32(SXE2_COM_EC_LINK_CHG) |
+				RTE_BIT32(SXE2_COM_SW_MODE_LEGACY) |
+				RTE_BIT32(SXE2_COM_SW_MODE_SWITCHDEV) |
+				RTE_BIT32(SXE2_COM_FC_ST_CHANGE);
+
+	ret = sxe2_drv_dev_other_irq_set(adapter->cdev, fd, event);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set other irq, ret=%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static void sxe2_other_intr_unregister(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_drv_dev_other_irq_set(adapter->cdev, -1, 0);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set other irq, ret=%d", ret);
+}
+
+static int32_t sxe2_reset_intr_register(struct rte_eth_dev *dev, int32_t fd)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_drv_dev_reset_irq_set(adapter->cdev, fd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set reset irq, ret=%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static void sxe2_reset_intr_unregister(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+
+	ret = sxe2_drv_dev_reset_irq_set(adapter->cdev, -1);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set reset irq, ret=%d", ret);
+}
+
+int32_t sxe2_intr_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_pci_device *pci_dev = SXE2_DEV_TO_PCI(dev);
+	struct rte_intr_handle *reset_handle = NULL;
+	int32_t ofd = -1;
+	int32_t rfd = -1;
+	int32_t ret = 0;
+
+	PMD_INIT_FUNC_TRACE();
+
+	ofd = sxe2_event_intr_fd_create();
+	if (ofd < 0) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to create event intr fd");
+		ret = -EBADF;
+		goto l_end;
+	}
+
+	ret = sxe2_intr_handler_cfg(pci_dev->intr_handle,
+			ofd, sxe2_interrupt_handler_other, dev);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to create intr handler");
+		goto l_err_create_other_handler;
+	}
+
+	ret = sxe2_event_intr_handler_init();
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Event handler init failed, ret=%d", ret);
+		goto l_err_event_intr_handler_init;
+	}
+
+	ret = sxe2_other_intr_register(dev, ofd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to register other intr, ret=%d", ret);
+		goto l_err_register_other_intr;
+	}
+	adapter->irq_ctxt.other_event_fd = ofd;
+
+	rfd = sxe2_event_intr_fd_create();
+	if (rfd < 0) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to create event intr fd");
+		ret = -EBADF;
+		goto l_err_create_reset_fd;
+	}
+
+	reset_handle = sxe2_intr_handler_create(rfd, sxe2_interrupt_handler_reset, dev);
+	if (!reset_handle) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to create intr handler");
+		ret = -ENOMEM;
+		goto l_err_create_reset_handler;
+	}
+	adapter->irq_ctxt.reset_handle = reset_handle;
+
+	ret = sxe2_reset_intr_register(dev, rfd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to register reset intr, ret=%d", ret);
+		goto l_err_register_reset_intr;
+	}
+	adapter->irq_ctxt.reset_event_fd = rfd;
+
+	goto l_end;
+l_err_register_reset_intr:
+	sxe2_intr_handler_destroy(reset_handle, sxe2_interrupt_handler_reset, dev);
+	adapter->irq_ctxt.reset_handle = NULL;
+l_err_create_reset_handler:
+	sxe2_event_intr_fd_destroy(rfd);
+l_err_create_reset_fd:
+	sxe2_other_intr_unregister(dev);
+	adapter->irq_ctxt.other_event_fd = -1;
+l_err_register_other_intr:
+	sxe2_event_intr_handler_uinit();
+l_err_event_intr_handler_init:
+	sxe2_intr_handler_destroy(pci_dev->intr_handle,
+			sxe2_interrupt_handler_other, dev);
+	pci_dev->intr_handle = NULL;
+l_err_create_other_handler:
+	sxe2_event_intr_fd_destroy(ofd);
+l_end:
+	return ret;
+}
+
+void sxe2_intr_uninit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_pci_device *pci_dev = SXE2_DEV_TO_PCI(dev);
+
+	sxe2_reset_intr_unregister(dev);
+	sxe2_intr_handler_destroy(adapter->irq_ctxt.reset_handle,
+				sxe2_interrupt_handler_reset, dev);
+	sxe2_event_intr_fd_destroy(adapter->irq_ctxt.reset_event_fd);
+	sxe2_other_intr_unregister(dev);
+	sxe2_event_intr_handler_uinit();
+	sxe2_intr_handler_destroy(pci_dev->intr_handle,
+				sxe2_interrupt_handler_other, dev);
+	sxe2_event_intr_fd_destroy(adapter->irq_ctxt.other_event_fd);
+
+	adapter->irq_ctxt.other_event_fd = -1;
+	adapter->irq_ctxt.reset_event_fd = -1;
+	pci_dev->intr_handle = NULL;
+	adapter->irq_ctxt.reset_handle = NULL;
+}
+
+static int32_t sxe2_rxq_intr_efd_alloc(struct rte_eth_dev *dev, int32_t *efd)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	int32_t ret = 0;
+	int32_t fd = 0;
+
+	fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+	if (fd < 0) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Can't setup eventfd, error %i (%s)",
+			errno, strerror(errno));
+		ret = -EBADF;
+		goto l_end;
+	}
+
+	*efd = fd;
+
+l_end:
+	return ret;
+}
+
+static void sxe2_rxq_intr_efd_free(int32_t efd)
+{
+	close(efd);
+}
+
+static void sxe2_pci_hw_int_itr_set(struct sxe2_adapter *adapter, uint16_t msix_idx, uint16_t itr)
+{
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_ITR, msix_idx, itr);
+}
+
+static void sxe2_pci_hw_irq_disable(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	uint32_t value = (SXE2_ITR_IDX_NONE << SXE2_VF_DYN_CTL_ITR_IDX_S);
+
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_DYN, irq_idx, value);
+}
+
+static void sxe2_pci_hw_irq_enable(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	uint32_t value = SXE2_VF_DYN_CTL_INTENABLE |
+		SXE2_VF_DYN_CTL_CLEARPBA |
+			(SXE2_ITR_IDX_NONE << SXE2_VF_DYN_CTL_ITR_IDX_S);
+
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_DYN, irq_idx, value);
+}
+
+static uint32_t sxe2_pci_hw_irq_dyn_ctl_read(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	return sxe2_pci_map_read_reg(adapter, SXE2_PCI_MAP_RES_IRQ_DYN, irq_idx);
+}
+
+static void sxe2_pci_hw_msix_disable(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_MSIX,
+			irq_idx, SXE2VF_BAR4_MSIX_DISABLE);
+}
+
+static void sxe2_pci_hw_msix_enable(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_MSIX,
+			irq_idx, SXE2VF_BAR4_MSIX_ENABLE);
+}
+
+static void sxe2_pci_hw_irq_trigger(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_DYN, irq_idx,
+			(SXE2VF_ITR_IDX_NONE << SXE2VF_DYN_CTL_ITR_IDX_SHIFT) |
+			SXE2VF_DYN_CTL_SWINT_TRIG | SXE2VF_DYN_CTL_INTENABLE_MSK);
+}
+
+static void sxe2_pci_hw_irq_clear_pba(struct sxe2_adapter *adapter, uint16_t irq_idx)
+{
+	sxe2_pci_map_write_reg(adapter, SXE2_PCI_MAP_RES_IRQ_DYN, irq_idx,
+		(SXE2VF_ITR_IDX_NONE << SXE2VF_DYN_CTL_ITR_IDX_SHIFT) |
+		SXE2VF_DYN_CTL_CLEARPBA | SXE2VF_DYN_CTL_INTENABLE_MSK);
+}
+
+static void sxe2_rxq_msix_cfg_unmap(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	uint16_t rxq_cnt = adapter->q_ctxt.qp_cnt_assign;
+	uint16_t i = 0;
+
+	for (i = 0; i < irq_ctxt->rxq_irq_cnt; i++)
+		sxe2_pci_hw_irq_disable(adapter, irq_ctxt->rxq_msix_idx[i]);
+
+	for (i = 0; i < rxq_cnt; i++)
+		(void)sxe2_drv_rxq_unbind_irq(adapter, i);
+}
+
+static int32_t sxe2_rxq_msix_cfg_map(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	int32_t ret = 0;
+	uint32_t val;
+	uint16_t rxq_cnt = dev->data->nb_rx_queues;
+	uint16_t i = 0;
+	uint8_t rx_low_latency = adapter->devargs.rx_low_latency;
+
+	for (i = 0; i < irq_ctxt->rxq_irq_cnt; i++) {
+		sxe2_pci_hw_irq_disable(adapter, irq_ctxt->rxq_msix_idx[i]);
+		if (rx_low_latency) {
+			sxe2_pci_hw_int_itr_set(adapter, irq_ctxt->rxq_msix_idx[i],
+					SXE2_ITR_INTERVAL_LOW);
+		} else {
+			sxe2_pci_hw_int_itr_set(adapter, irq_ctxt->rxq_msix_idx[i],
+					SXE2_ITR_INTERVAL_NORMAL);
+		}
+	}
+
+	for (i = 0; i < rxq_cnt; i++) {
+		ret = sxe2_drv_rxq_bind_irq(adapter, i, irq_ctxt->rxq_msix_idx[i]);
+		if (ret != 0) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "RXQ[%u] bind IRQ[%u] failed.",
+				i, irq_ctxt->rxq_msix_idx[i]);
+			goto l_end;
+		}
+	}
+
+	for (i = 0; i < irq_ctxt->rxq_irq_cnt; i++)
+		sxe2_pci_hw_irq_enable(adapter, irq_ctxt->rxq_msix_idx[i]);
+
+	for (i = 0; i < irq_ctxt->rxq_irq_cnt; i++) {
+		val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, i);
+		if ((val & SXE2VF_DYN_CTL_INTENABLE) == 0)
+			continue;
+
+		sxe2_pci_hw_msix_disable(adapter, i);
+		sxe2_pci_hw_irq_trigger(adapter, i);
+		val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, i);
+		sxe2_pci_hw_irq_clear_pba(adapter, i);
+		val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, i);
+		sxe2_pci_hw_msix_enable(adapter, i);
+	}
+
+l_end:
+	if (ret != 0)
+		sxe2_rxq_msix_cfg_unmap(dev);
+	return ret;
+}
+
+static int32_t sxe2_rxq_map_msix_intr(struct rte_eth_dev *dev,
+		uint16_t msix_base __rte_unused, uint16_t nb_msix,
+		uint16_t base_queue, uint16_t nb_queue)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	uint32_t *msix_tbl = NULL;
+	int32_t ret = 0;
+	uint16_t i;
+
+	if (!nb_queue || !nb_msix || nb_queue < nb_msix) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Queue num[%u] or msix num[%u] is invalid.",
+				nb_queue, nb_msix);
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	msix_tbl = rte_zmalloc(NULL, sizeof(uint32_t) * nb_queue, 0);
+	if (!msix_tbl) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to alloc msix_tbl memory.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	for (i = 0; i < nb_queue; i++) {
+		msix_tbl[i] = i % nb_msix;
+		PMD_DEV_LOG_INFO(adapter, INIT, "Queue %u is binding to vect %u",
+				base_queue + i, msix_tbl[i]);
+	}
+
+	irq_ctxt->rxq_irq_cnt = nb_msix;
+	irq_ctxt->rxq_msix_idx = msix_tbl;
+
+l_end:
+	return ret;
+}
+
+static void sxe2_rxq_unmap_msix_intr(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+
+	rte_free(irq_ctxt->rxq_msix_idx);
+	irq_ctxt->rxq_msix_idx = NULL;
+	irq_ctxt->rxq_irq_cnt = 0;
+}
+
+static int32_t sxe2_rxq_intr_register(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	struct rte_intr_handle *intr_handle = dev->intr_handle;
+	int32_t *efd_tbl = NULL;
+	uint16_t rxq_cnt = dev->data->nb_rx_queues;
+	uint16_t nb_msix = irq_ctxt->rxq_irq_cnt;
+	uint16_t i;
+	int32_t ret = 0;
+
+	if (rte_intr_type_set(intr_handle, RTE_INTR_HANDLE_EXT)) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set intr_handle->type, error %i (%s)",
+				errno, strerror(errno));
+		ret = -EPERM;
+		goto l_end;
+	}
+
+	efd_tbl = rte_zmalloc(NULL, sizeof(int32_t) * nb_msix, 0);
+	if (!efd_tbl) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to alloc efd_tbl memory.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	for (i = 0; i < nb_msix; i++) {
+		ret = sxe2_rxq_intr_efd_alloc(dev, &efd_tbl[i]);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Failed to alloc efd_tbl, ret=%d", ret);
+			goto l_free_efd_tbl;
+		}
+	}
+
+	if (rte_intr_vec_list_alloc(intr_handle, "sxe2 rxq int", rxq_cnt)) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to allocate %d rx_queues intr_vec",
+				rxq_cnt);
+		ret = -ENOMEM;
+		goto l_free_efd_tbl;
+	}
+
+	for	(i = 0; i < rxq_cnt; i++) {
+		ret = rte_intr_vec_list_index_set(intr_handle, i,
+				irq_ctxt->rxq_msix_idx[i] + RTE_INTR_VEC_RXTX_OFFSET);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set msix_tbl, ret=%d", ret);
+			goto l_free_efd_tbl;
+		}
+	}
+
+	for (i = 0; i < irq_ctxt->rxq_irq_cnt; i++) {
+		ret = rte_intr_efds_index_set(intr_handle, i, efd_tbl[i]);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set efd_tbl, ret=%d", ret);
+			goto l_free_efd_tbl;
+		}
+	}
+
+	if (rte_intr_nb_efd_set(intr_handle, rxq_cnt)) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Set intr nb efd failed, error %i (%s)",
+			errno, strerror(errno));
+		ret = -EPERM;
+		goto l_free_efd_tbl;
+	}
+
+	ret = sxe2_drv_dev_rxq_irq_set(adapter->cdev, 0, efd_tbl, nb_msix);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to set rxq irq, ret=%d", ret);
+		goto l_free_efd_tbl;
+	}
+	irq_ctxt->rxq_event_fd = efd_tbl;
+
+	goto l_end;
+
+l_free_efd_tbl:
+	if (efd_tbl) {
+		for (i = 0; i < nb_msix; i++)
+			if (efd_tbl[i] >= 0)
+				sxe2_rxq_intr_efd_free(efd_tbl[i]);
+		rte_free(efd_tbl);
+	}
+	irq_ctxt->rxq_event_fd = NULL;
+
+	rte_intr_vec_list_free(intr_handle);
+l_end:
+	return ret;
+}
+
+static void sxe2_rxq_intr_unregister(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	struct rte_intr_handle *intr_handle = dev->intr_handle;
+	int32_t efd = -1;
+	uint16_t msix_cnt = irq_ctxt->rxq_irq_cnt;
+	uint16_t i;
+
+	if (irq_ctxt->rxq_event_fd) {
+		for (i = 0; i < msix_cnt; i++) {
+			(void)sxe2_drv_dev_rxq_irq_set(adapter->cdev, i, &efd, 1);
+			sxe2_rxq_intr_efd_free(irq_ctxt->rxq_event_fd[i]);
+		}
+	}
+	rte_free(irq_ctxt->rxq_event_fd);
+	irq_ctxt->rxq_event_fd = NULL;
+
+	rte_intr_vec_list_free(intr_handle);
+
+	rte_intr_nb_efd_set(intr_handle, 0);
+	rte_intr_max_intr_set(intr_handle, 0);
+}
+
+int32_t sxe2_rxq_intr_enable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	uint16_t msix_vect = adapter->irq_ctxt.rxq_base_idx_in_pf;
+	uint16_t msix_cnt = adapter->irq_ctxt.rxq_avail_cnt;
+	uint16_t rxq_cnt = dev->data->nb_rx_queues;
+	uint16_t rxq_base = adapter->q_ctxt.base_idx_in_pf;
+	int32_t ret = 0;
+
+	if (!rxq_cnt)
+		goto l_end;
+
+	msix_cnt = RTE_MIN(msix_cnt, rxq_cnt);
+
+	ret = sxe2_rxq_map_msix_intr(dev, msix_vect, msix_cnt, rxq_base, rxq_cnt);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to rxq[%d] map msix[%d] intr, cnt=%d, ret=%d",
+					rxq_base, msix_vect, rxq_cnt, ret);
+		goto l_end;
+	}
+
+	if (dev->data->dev_conf.intr_conf.rxq) {
+		ret = sxe2_rxq_intr_register(dev);
+		if (ret) {
+			PMD_DEV_LOG_ERR(adapter, INIT, "Failed to register rxq[%d] intr, ret=%d",
+					rxq_base, ret);
+			goto l_err_unmap;
+		}
+	}
+
+	ret = sxe2_rxq_msix_cfg_map(dev);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, INIT, "Failed to rxq[%d] map msix[%d] intr, ret=%d",
+					rxq_base, msix_vect, ret);
+		goto l_err_unregister;
+	}
+
+	goto l_end;
+l_err_unregister:
+	if (dev->data->dev_conf.intr_conf.rxq)
+		sxe2_rxq_intr_unregister(dev);
+l_err_unmap:
+	sxe2_rxq_unmap_msix_intr(dev);
+l_end:
+	return ret;
+}
+
+void sxe2_rxq_intr_disable(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+
+	if (!irq_ctxt->rxq_irq_cnt)
+		goto l_end;
+
+	sxe2_rxq_msix_cfg_unmap(dev);
+
+	if (dev->data->dev_conf.intr_conf.rxq)
+		sxe2_rxq_intr_unregister(dev);
+
+	sxe2_rxq_unmap_msix_intr(dev);
+
+l_end:
+	return;
+}
+
+int32_t sxe2_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct sxe2_irq_context *irq_ctxt = &adapter->irq_ctxt;
+	uint64_t buf;
+	uint16_t irq_idx = irq_ctxt->rxq_msix_idx[queue_id];
+	size_t read_ret;
+
+	read_ret = read(irq_ctxt->rxq_event_fd[irq_idx], &buf, sizeof(buf));
+	(void)read_ret;
+	sxe2_pci_hw_irq_enable(adapter, irq_idx);
+	return 0;
+}
+
+int32_t sxe2_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id)
+{
+	struct sxe2_adapter *adapter =
+		SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	uint32_t val;
+	int32_t ret = 0;
+	uint16_t irq_idx = adapter->irq_ctxt.rxq_msix_idx[queue_id];
+
+	val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, irq_idx);
+	if ((val & SXE2VF_DYN_CTL_INTENABLE) == 0) {
+		PMD_DEV_LOG_DEBUG(adapter, INIT, "rxq [%d] interrupt is disabled.", queue_id);
+		goto l_end;
+	}
+
+	sxe2_pci_hw_msix_disable(adapter, irq_idx);
+	sxe2_pci_hw_irq_trigger(adapter, irq_idx);
+	val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, irq_idx);
+	sxe2_pci_hw_irq_clear_pba(adapter, irq_idx);
+	val = sxe2_pci_hw_irq_dyn_ctl_read(adapter, irq_idx);
+	sxe2_pci_hw_msix_enable(adapter, irq_idx);
+l_end:
+	return ret;
+}
diff --git a/drivers/net/sxe2/sxe2_irq.h b/drivers/net/sxe2/sxe2_irq.h
index bb96c6d842..c898c16f84 100644
--- a/drivers/net/sxe2/sxe2_irq.h
+++ b/drivers/net/sxe2/sxe2_irq.h
@@ -45,4 +45,25 @@ struct sxe2_irq_context {
 	int32_t *rxq_event_fd;
 };
 
+uint32_t sxe2_drv_dev_other_cause_get(struct sxe2_adapter *adapter);
+
+int32_t sxe2_intr_init(struct rte_eth_dev *dev);
+
+void sxe2_intr_uninit(struct rte_eth_dev *dev);
+
+int32_t sxe2_sw_irq_ctxt_init(struct rte_eth_dev *dev);
+
+void sxe2_sw_irq_ctxt_uninit(struct rte_eth_dev *dev);
+
+void sxe2_sw_irq_ctx_hw_cap_set(struct sxe2_adapter *adapter,
+		struct sxe2_drv_msix_caps *msix_caps);
+
+int32_t sxe2_rxq_intr_enable(struct rte_eth_dev *dev);
+
+void sxe2_rxq_intr_disable(struct rte_eth_dev *dev);
+
+int32_t sxe2_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id);
+
+int32_t sxe2_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id);
+
 #endif /* __SXE2_IRQ_H__ */
diff --git a/drivers/net/sxe2/sxe2vf_regs.h b/drivers/net/sxe2/sxe2vf_regs.h
new file mode 100644
index 0000000000..503c7ce04f
--- /dev/null
+++ b/drivers/net/sxe2/sxe2vf_regs.h
@@ -0,0 +1,82 @@
+
+#ifndef __SXE2VF_REGS_H__
+#define __SXE2VF_REGS_H__
+
+#define SXE2VF_MBX_Q_LEN_M      0x3FF
+#define SXE2VF_MBX_Q_LEN_VFE_M  RTE_BIT32(28)
+
+#define SXE2VF_MBX_Q_LEN_OVFL_M RTE_BIT32(29)
+#define SXE2VF_MBX_Q_LEN_CRIT_M RTE_BIT32(30)
+#define SXE2VF_MBX_Q_LEN_ENA_M  RTE_BIT32(31)
+
+#define SXE2VF_MBX_RQ_HEAD      (0x00008000)
+#define SXE2VF_MBX_RQ_TAIL      (0x00008400)
+#define SXE2VF_MBX_RQ_LEN       (0x00007C00)
+#define SXE2VF_MBX_RQ_BAH       (0x00007800)
+#define SXE2VF_MBX_RQ_BAL       (0x00007400)
+
+#define SXE2VF_MBX_TQ_HEAD      (0x00006C00)
+#define SXE2VF_MBX_TQ_TAIL      (0x00007000)
+#define SXE2VF_MBX_TQ_LEN       (0x00006800)
+#define SXE2VF_MBX_TQ_BAH       (0x00006400)
+#define SXE2VF_MBX_TQ_BAL       (0x00006000)
+
+#define SXE2VF_RXQ_TAIL(_QRX)                 (0x2000 + ((_QRX) * 4))
+#define SXE2VF_TXQ_TAIL(_QRX)                 (0x1000 + ((_QRX) * 4))
+
+#define SXE2VF_INT_BASE 0x00002800
+
+#define SXE2VF_DYN_CTL0 (SXE2VF_INT_BASE + 0x0)
+
+#define SXE2VF_DYN_CTL(_idx) (SXE2VF_INT_BASE + 0x4 + ((_idx) * 4))
+#define SXE2VF_VF_DYN_CTL(_idx) (SXE2VF_INT_BASE + ((_idx) * 4))
+
+#define SXE2VF_BAR4_MSIX_BASE 0
+#define SXE2VF_BAR4_MSIX_CTL(_idx) (SXE2VF_BAR4_MSIX_BASE + 0xC + ((_idx) * 0x10))
+#define SXE2VF_BAR4_MSIX_ENABLE 0
+#define SXE2VF_BAR4_MSIX_DISABLE 1
+
+#define SXE2VF_DYN_CTL_INTENABLE RTE_BIT32(0)
+#define SXE2VF_DYN_CTL_CLEARPBA   RTE_BIT32(1)
+#define SXE2VF_DYN_CTL_SWINT_TRIG RTE_BIT32(2)
+#define SXE2VF_DYN_CTL_SW_ITR_IDX_ENABLE RTE_BIT32(24)
+
+#define SXE2VF_DYN_CTL_INTENABLE_MSK \
+	RTE_BIT32(31)
+
+#define SXE2VF_DYN_CTL_ITR_IDX_SHIFT 3
+
+enum sxe2vf_itr_idx {
+	SXE2VF_ITR_IDX_0 = 0,
+	SXE2VF_ITR_IDX_1,
+	SXE2VF_ITR_IDX_2,
+	SXE2VF_ITR_IDX_NONE,
+};
+
+#define SXE2VF_INT_ITR0   (0x00002800 + 65 * 0x4)
+#define SXE2VF_INT_ITR(_i, _irq_idx) (SXE2VF_INT_ITR0 + \
+			0x4 + (_i) * 0x104 + ((_irq_idx) * 4))
+#define SXE2VF_VF_INT_ITR(_itr_idx, _irq_idx) (0x00002800 + \
+			(0x104 * (_itr_idx)) + ((_irq_idx) * 4))
+#define SXE2VF_PFG_INT_CTL_ITR_GRAN_0    (2)
+
+#define SXE2VF_PCIE_SYS_READY              0x38c
+#define SXE2VF_PCIE_SYS_READY_CORER_ASSERT RTE_BIT32(0)
+#define SXE2VF_PCIE_SYS_READY_STOP_DROP_DONE RTE_BIT32(2)
+#define SXE2VF_PCIE_SYS_READY_R5        RTE_BIT32(3)
+#define SXE2VF_PCIE_SYS_READY_STOP_DROP RTE_BIT32(16)
+
+#define SXE2VF_PCIE_DEV_CTRL_DEV_STATUS               0x78
+#define SXE2VF_PCIE_DEV_CTRL_DEV_STATUS_TRANS_PENDING RTE_BIT32(21)
+
+#define SXE2VF_VF_VRC_VFGEN_RSTAT             (0x5800)
+#define SXE2VF_VF_VRC_VFGEN_VFRSTAT           GENMASK(1, 0)
+#define SXE2VF_VF_VRC_VFGEN_VFRSTAT_VFR       (0)
+#define SXE2VF_VF_VRC_VFGEN_VFRSTAT_COMPLETE  (RTE_BIT32(0))
+#define SXE2VF_VF_VRC_VFGEN_VFRSTAT_VF_ACTIVE (RTE_BIT32(1))
+
+#define SXE2VF_VF_VRC_VFGEN_VFRSTAT_FORVF_VFR (1)
+#define SXE2VF_VF_VRC_VFGEN_VFRSTAT_FORVF_MASK \
+	(RTE_BIT32(10))
+
+#endif /* SXE2VF_VF_INT_ITR */
-- 
2.47.3


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox