<div dir="ltr">Sending a CI testing retest for this series because of a suspected false failure on the vlan testsuite.</div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Fri, Aug 8, 2025 at 12:59 PM Stephen Hemminger <<a href="mailto:stephen@networkplumber.org">stephen@networkplumber.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">This adds new feature port mirroring to the ethdev layer.<br>
And standalone tests for those features.<br>
<br>
Signed-off-by: Stephen Hemminger <<a href="mailto:stephen@networkplumber.org" target="_blank">stephen@networkplumber.org</a>><br>
---<br>
app/test/meson.build | 1 +<br>
app/test/test_ethdev_mirror.c | 325 ++++++++++++++++++++<br>
config/rte_config.h | 1 +<br>
lib/ethdev/ethdev_driver.h | 6 +<br>
lib/ethdev/ethdev_private.c | 58 +++-<br>
lib/ethdev/ethdev_private.h | 3 +<br>
lib/ethdev/ethdev_trace.h | 17 ++<br>
lib/ethdev/ethdev_trace_points.c | 6 +<br>
lib/ethdev/meson.build | 2 +<br>
lib/ethdev/rte_ethdev.c | 17 +-<br>
lib/ethdev/rte_ethdev.h | 23 +-<br>
lib/ethdev/rte_ethdev_core.h | 12 +-<br>
lib/ethdev/rte_mirror.c | 508 +++++++++++++++++++++++++++++++<br>
lib/ethdev/rte_mirror.h | 147 +++++++++<br>
14 files changed, 1108 insertions(+), 18 deletions(-)<br>
create mode 100644 app/test/test_ethdev_mirror.c<br>
create mode 100644 lib/ethdev/rte_mirror.c<br>
create mode 100644 lib/ethdev/rte_mirror.h<br>
<br>
diff --git a/app/test/meson.build b/app/test/meson.build<br>
index f3ea3e2b74..87219b869e 100644<br>
--- a/app/test/meson.build<br>
+++ b/app/test/meson.build<br>
@@ -73,6 +73,7 @@ source_file_deps = {<br>
'test_errno.c': [],<br>
'test_ethdev_api.c': ['ethdev'],<br>
'test_ethdev_link.c': ['ethdev'],<br>
+ 'test_ethdev_mirror.c': ['net_ring', 'ethdev', 'bus_vdev'],<br>
'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],<br>
'test_event_dma_adapter.c': ['dmadev', 'eventdev', 'bus_vdev'],<br>
'test_event_eth_rx_adapter.c': ['ethdev', 'eventdev', 'bus_vdev'],<br>
diff --git a/app/test/test_ethdev_mirror.c b/app/test/test_ethdev_mirror.c<br>
new file mode 100644<br>
index 0000000000..bbff1ff6a7<br>
--- /dev/null<br>
+++ b/app/test/test_ethdev_mirror.c<br>
@@ -0,0 +1,325 @@<br>
+/* SPDX-License-Identifier: BSD-3-Clause<br>
+ * Copyright(c) 2025 Stephen Hemminger <<a href="mailto:stephen@networkplumber.org" target="_blank">stephen@networkplumber.org</a>><br>
+ */<br>
+#include "test.h"<br>
+<br>
+#include <stdio.h><br>
+<br>
+#include <rte_eth_ring.h><br>
+#include <rte_ethdev.h><br>
+#include <rte_bus_vdev.h><br>
+<br>
+#define SOCKET0 0<br>
+#define RING_SIZE 256<br>
+#define BURST_SZ 64<br>
+#define NB_MBUF 512<br>
+<br>
+static struct rte_mempool *mirror_pool;<br>
+static const uint32_t pkt_len = 200;<br>
+<br>
+static struct rte_ring *rxtx_ring;<br>
+static const char ring_dev[] = "net_ring0";<br>
+static uint16_t ring_port;<br>
+static const char null_dev[] = "net_null0";<br>
+static uint16_t null_port;<br>
+<br>
+static int<br>
+configure_port(uint16_t port, const char *name)<br>
+{<br>
+ struct rte_eth_conf eth_conf = { 0 };<br>
+<br>
+ if (rte_eth_dev_configure(port, 1, 1, ð_conf) < 0) {<br>
+ fprintf(stderr, "Configure failed for port %u: %s\n", port, name);<br>
+ return -1;<br>
+ }<br>
+<br>
+ /* only single queue */<br>
+ if (rte_eth_tx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL) < 0) {<br>
+ fprintf(stderr, "TX queue setup failed port %u: %s\n", port, name);<br>
+ return -1;<br>
+ }<br>
+<br>
+ if (rte_eth_rx_queue_setup(port, 0, RING_SIZE, SOCKET0, NULL, mirror_pool) < 0) {<br>
+ fprintf(stderr, "RX queue setup failed port %u: %s\n", port, name);<br>
+ return -1;<br>
+ }<br>
+<br>
+ if (rte_eth_dev_start(port) < 0) {<br>
+ fprintf(stderr, "Error starting port %u:%s\n", port, name);<br>
+ return -1;<br>
+ }<br>
+<br>
+ return 0;<br>
+}<br>
+<br>
+static void<br>
+test_cleanup(void)<br>
+{<br>
+ rte_ring_free(rxtx_ring);<br>
+<br>
+ rte_vdev_uninit("net_ring");<br>
+ rte_vdev_uninit("net_null");<br>
+<br>
+ rte_mempool_free(mirror_pool);<br>
+}<br>
+<br>
+/* Make two virtual devices ring and null */<br>
+static int<br>
+test_setup(void)<br>
+{<br>
+ char null_name[] = "net_null0";<br>
+ int ret;<br>
+<br>
+ /* ring must support multiple enqueue for mirror to work */<br>
+ rxtx_ring = rte_ring_create("R0", RING_SIZE, SOCKET0, RING_F_MP_RTS_ENQ | RING_F_SC_DEQ);<br>
+ if (rxtx_ring == NULL) {<br>
+ fprintf(stderr, "rte_ring_create R0 failed\n");<br>
+ goto fail;<br>
+ }<br>
+ ret = rte_eth_from_rings(ring_dev, &rxtx_ring, 1, &rxtx_ring, 1, SOCKET0);<br>
+ if (ret < 0) {<br>
+ fprintf(stderr, "rte_eth_from_rings failed\n");<br>
+ goto fail;<br>
+ }<br>
+ ring_port = ret;<br>
+<br>
+ /* Make a dummy null device to snoop on */<br>
+ if (rte_vdev_init(null_dev, NULL) != 0) {<br>
+ fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);<br>
+ goto fail;<br>
+ }<br>
+ if (rte_eth_dev_get_port_by_name(null_dev, &null_port) != 0) {<br>
+ fprintf(stderr, "cannot find added vdev %s\n", null_name);<br>
+ goto fail;<br>
+ }<br>
+<br>
+ mirror_pool = rte_pktmbuf_pool_create("mirror_pool", NB_MBUF, 32,<br>
+ 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());<br>
+ if (mirror_pool == NULL) {<br>
+ fprintf(stderr, "rte_pktmbuf_pool_create failed\n");<br>
+ goto fail;<br>
+ }<br>
+<br>
+ ret = configure_port(ring_port, ring_dev);<br>
+ if (ret < 0)<br>
+ goto fail;<br>
+<br>
+ ret = configure_port(null_port, null_dev);<br>
+ if (ret < 0)<br>
+ goto fail;<br>
+<br>
+ return 0;<br>
+fail:<br>
+ test_cleanup();<br>
+ return -1;<br>
+}<br>
+<br>
+/* Make sure mirror API checks args */<br>
+static int32_t<br>
+ethdev_mirror_api(void)<br>
+{<br>
+ struct rte_eth_mirror_conf conf = {<br>
+ .mp = mirror_pool,<br>
+ .flags = RTE_ETH_MIRROR_DIRECTION_EGRESS,<br>
+ };<br>
+ struct rte_eth_mirror_stats stats;<br>
+ uint16_t invalid_port = RTE_MAX_ETHPORTS;<br>
+ int ret;<br>
+<br>
+ conf.target = null_port;<br>
+ ret = rte_eth_add_mirror(invalid_port, &conf);<br>
+ TEST_ASSERT(ret != 0, "Created mirror from invalid port");<br>
+<br>
+ conf.target = invalid_port;<br>
+ ret = rte_eth_add_mirror(null_port, &conf);<br>
+ TEST_ASSERT(ret != 0, "Created mirror to invalid port");<br>
+<br>
+ conf.flags = 0;<br>
+ conf.target = ring_port;<br>
+ ret = rte_eth_add_mirror(null_port, &conf);<br>
+ TEST_ASSERT(ret != 0, "Created mirror with invalid flags");<br>
+<br>
+ conf.flags = RTE_ETH_MIRROR_DIRECTION_INGRESS;<br>
+ conf.target = ring_port;<br>
+ ret = rte_eth_add_mirror(ring_port, &conf);<br>
+ TEST_ASSERT(ret != 0, "Created mirror to self");<br>
+<br>
+ ret = rte_eth_mirror_stats_get(invalid_port, null_port, &stats);<br>
+ TEST_ASSERT(ret != 0, "Able to gets mirror stats from invalid port");<br>
+<br>
+ ret = rte_eth_mirror_stats_get(null_port, invalid_port, &stats);<br>
+ TEST_ASSERT(ret != 0, "Able to gets mirror stats from invalid target");<br>
+<br>
+ conf.target = null_port;<br>
+ ret = rte_eth_add_mirror(ring_port, &conf);<br>
+ TEST_ASSERT(ret == 0, "Could not create mirror from ring to null");<br>
+<br>
+ ret = rte_eth_add_mirror(ring_port, &conf);<br>
+ TEST_ASSERT(ret != 0, "Able to create duplicate mirror");<br>
+<br>
+ ret = rte_eth_mirror_stats_get(ring_port, null_port, NULL);<br>
+ TEST_ASSERT(ret == -EINVAL, "Able to get status with NULL");<br>
+<br>
+ ret = rte_eth_mirror_stats_get(null_port, ring_port, &stats);<br>
+ TEST_ASSERT(ret != 0, "Able to get stats with swapped ports");<br>
+<br>
+ ret = rte_eth_mirror_stats_get(ring_port, null_port, &stats);<br>
+ TEST_ASSERT(ret == 0, "Could not get stats");<br>
+<br>
+ ret = rte_eth_remove_mirror(ring_port, null_port);<br>
+ TEST_ASSERT(ret == 0, "Unable to delete mirror");<br>
+<br>
+ ret = rte_eth_remove_mirror(ring_port, null_port);<br>
+ TEST_ASSERT(ret != 0, "Able to delete port without mirror");<br>
+<br>
+ return 0;<br>
+}<br>
+<br>
+static int<br>
+init_mbuf(struct rte_mbuf *m, uint16_t id)<br>
+{<br>
+ struct {<br>
+ struct rte_ether_hdr eth;<br>
+ struct rte_ipv4_hdr ip;<br>
+ struct rte_udp_hdr udp;<br>
+ } hdrs = {<br>
+ .eth = {<br>
+ .dst_addr.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },<br>
+ .ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),<br>
+ },<br>
+ .ip = {<br>
+ .version_ihl = RTE_IPV4_VHL_DEF,<br>
+ .time_to_live = 1,<br>
+ .next_proto_id = IPPROTO_UDP,<br>
+ .src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),<br>
+ .dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),<br>
+ },<br>
+ .udp = {<br>
+ .dst_port = rte_cpu_to_be_16(9), /* Discard port */<br>
+ },<br>
+ };<br>
+<br>
+ rte_eth_random_addr(hdrs.eth.src_addr.addr_bytes);<br>
+ uint16_t plen = pkt_len - sizeof(struct rte_ether_hdr);<br>
+<br>
+ hdrs.ip.packet_id = rte_cpu_to_be_16(id);<br>
+ hdrs.ip.total_length = rte_cpu_to_be_16(plen);<br>
+ hdrs.ip.hdr_checksum = rte_ipv4_cksum(&hdrs.ip);<br>
+<br>
+ plen -= sizeof(struct rte_ipv4_hdr);<br>
+ hdrs.udp.src_port = rte_rand();<br>
+ hdrs.udp.dgram_len = rte_cpu_to_be_16(plen);<br>
+<br>
+ void *data = rte_pktmbuf_append(m, pkt_len);<br>
+ TEST_ASSERT(data != NULL, "could not add header");<br>
+<br>
+ memcpy(data, &hdrs, sizeof(hdrs));<br>
+ return 0;<br>
+}<br>
+<br>
+static int<br>
+init_burst(struct rte_mbuf *pkts[], unsigned int n)<br>
+{<br>
+ for (unsigned int i = 0; i < n; i++) {<br>
+ if (init_mbuf(pkts[i], i) < 0)<br>
+ return -1;<br>
+ }<br>
+ return 0;<br>
+}<br>
+<br>
+static int<br>
+validate_burst(struct rte_mbuf *pkts[], unsigned int n)<br>
+{<br>
+ for (unsigned int i = 0; i < n; i++) {<br>
+ struct rte_mbuf *m = pkts[i];<br>
+<br>
+ rte_mbuf_sanity_check(m, 1);<br>
+ TEST_ASSERT(m->pkt_len == pkt_len,<br>
+ "mirror packet len %u not right len %u",<br>
+ m->pkt_len, pkt_len);<br>
+<br>
+ const struct rte_ether_hdr *eh<br>
+ = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);<br>
+ TEST_ASSERT(rte_is_broadcast_ether_addr(&eh->dst_addr),<br>
+ "mirrored packet is not broadcast");<br>
+<br>
+ }<br>
+ return 0;<br>
+}<br>
+<br>
+static int32_t<br>
+ethdev_mirror_packets(void)<br>
+{<br>
+ struct rte_eth_mirror_conf conf = {<br>
+ .mp = mirror_pool,<br>
+ .target = ring_port,<br>
+ .flags = RTE_ETH_MIRROR_DIRECTION_EGRESS,<br>
+ };<br>
+ struct rte_eth_mirror_stats stats;<br>
+ struct rte_mbuf *tx_pkts[BURST_SZ], *rx_pkts[BURST_SZ];<br>
+ uint16_t nb_tx, nb_rx;<br>
+ int ret;<br>
+<br>
+ ret = rte_pktmbuf_alloc_bulk(mirror_pool, tx_pkts, BURST_SZ);<br>
+ TEST_ASSERT(ret == 0, "Could not allocate mbufs");<br>
+<br>
+ ret = init_burst(tx_pkts, BURST_SZ);<br>
+ TEST_ASSERT(ret == 0, "Init mbufs failed");<br>
+<br>
+ ret = rte_eth_add_mirror(null_port, &conf);<br>
+ TEST_ASSERT(ret == 0, "Could not create mirror from ring to null");<br>
+<br>
+ nb_tx = rte_eth_tx_burst(null_port, 0, tx_pkts, BURST_SZ);<br>
+ TEST_ASSERT(nb_tx == BURST_SZ, "Only sent %u burst to null (vs %u)",<br>
+ nb_tx, BURST_SZ);<br>
+<br>
+ nb_rx = rte_eth_rx_burst(ring_port, 0, rx_pkts, BURST_SZ);<br>
+ TEST_ASSERT(nb_rx == BURST_SZ, "Only received %u of %u packets",<br>
+ nb_rx, BURST_SZ);<br>
+<br>
+ validate_burst(rx_pkts, nb_rx);<br>
+ rte_pktmbuf_free_bulk(rx_pkts, nb_rx);<br>
+<br>
+ ret = rte_eth_mirror_stats_get(null_port, ring_port, &stats);<br>
+ TEST_ASSERT(ret == 0, "Could not get stats: %d", ret);<br>
+<br>
+ TEST_ASSERT(stats.packets == BURST_SZ, "Stats reports %"PRIu64" of %u packets",<br>
+ stats.packets, BURST_SZ);<br>
+ TEST_ASSERT(stats.nombuf == 0, "Stats: no mbufs %"PRIu64, stats.nombuf);<br>
+ TEST_ASSERT(stats.full == 0, "Stats: transmit was full %"PRIu64, stats.full);<br>
+<br>
+ ret = rte_eth_mirror_stats_reset(null_port, ring_port);<br>
+ TEST_ASSERT(ret == 0, "Could not get stats: %d", ret);<br>
+<br>
+ ret = rte_eth_mirror_stats_get(null_port, ring_port, &stats);<br>
+ TEST_ASSERT(ret == 0, "Could not get stats after reset: %d", ret);<br>
+<br>
+ TEST_ASSERT(stats.packets == 0, "Stats reports %"PRIu64" after reset", stats.packets);<br>
+<br>
+ ret = rte_eth_remove_mirror(null_port, ring_port);<br>
+ TEST_ASSERT(ret == 0, "Could not remove mirror");<br>
+<br>
+ return 0;<br>
+}<br>
+<br>
+static struct unit_test_suite ethdev_mirror_suite = {<br>
+ .suite_name = "port mirroring",<br>
+ .setup = test_setup,<br>
+ .teardown = test_cleanup,<br>
+ .unit_test_cases = {<br>
+ TEST_CASE(ethdev_mirror_api),<br>
+ TEST_CASE(ethdev_mirror_packets),<br>
+ TEST_CASES_END()<br>
+ }<br>
+};<br>
+<br>
+static int<br>
+test_ethdev_mirror(void)<br>
+{<br>
+#ifndef RTE_ETHDEV_MIRROR<br>
+ return TEST_SKIPPED;<br>
+#endif<br>
+ return unit_test_suite_runner(ðdev_mirror_suite);<br>
+}<br>
+<br>
+REGISTER_FAST_TEST(ethdev_mirror, true, true, test_ethdev_mirror);<br>
diff --git a/config/rte_config.h b/config/rte_config.h<br>
index 05344e2619..76eb2aa417 100644<br>
--- a/config/rte_config.h<br>
+++ b/config/rte_config.h<br>
@@ -69,6 +69,7 @@<br>
#define RTE_MAX_QUEUES_PER_PORT 1024<br>
#define RTE_ETHDEV_QUEUE_STAT_CNTRS 16 /* max 256 */<br>
#define RTE_ETHDEV_RXTX_CALLBACKS 1<br>
+#define RTE_ETHDEV_MIRROR 1<br>
#define RTE_MAX_MULTI_HOST_CTRLS 4<br>
<br>
/* cryptodev defines */<br>
diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h<br>
index 2b4d2ae9c3..e6aad6d30a 100644<br>
--- a/lib/ethdev/ethdev_driver.h<br>
+++ b/lib/ethdev/ethdev_driver.h<br>
@@ -91,6 +91,12 @@ struct __rte_cache_aligned rte_eth_dev {<br>
*/<br>
RTE_ATOMIC(struct rte_eth_rxtx_callback *) pre_tx_burst_cbs[RTE_MAX_QUEUES_PER_PORT];<br>
<br>
+ /** Receive mirrors */<br>
+ RTE_ATOMIC(struct rte_eth_mirror *) rx_mirror;<br>
+<br>
+ /** Transmit mirrors */<br>
+ RTE_ATOMIC(struct rte_eth_mirror *) tx_mirror;<br>
+<br>
enum rte_eth_dev_state state; /**< Flag indicating the port state */<br>
void *security_ctx; /**< Context for security ops */<br>
};<br>
diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c<br>
index 000b8372d8..e702acc874 100644<br>
--- a/lib/ethdev/ethdev_private.c<br>
+++ b/lib/ethdev/ethdev_private.c<br>
@@ -289,6 +289,8 @@ eth_dev_fp_ops_setup(struct rte_eth_fp_ops *fpo,<br>
fpo->tx_descriptor_status = dev->tx_descriptor_status;<br>
fpo->recycle_tx_mbufs_reuse = dev->recycle_tx_mbufs_reuse;<br>
fpo->recycle_rx_descriptors_refill = dev->recycle_rx_descriptors_refill;<br>
+ fpo->rx_mirror = (struct rte_eth_mirror * __rte_atomic *)(uintptr_t)&dev->rx_mirror;<br>
+ fpo->tx_mirror = (struct rte_eth_mirror * __rte_atomic *)(uintptr_t)&dev->tx_mirror;<br>
<br>
fpo->rxq.data = dev->data->rx_queues;<br>
fpo->rxq.clbk = (void * __rte_atomic *)(uintptr_t)dev->post_rx_burst_cbs;<br>
@@ -490,17 +492,53 @@ eth_dev_tx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues)<br>
}<br>
<br>
static int<br>
-ethdev_handle_request(const struct ethdev_mp_request *req)<br>
+ethdev_handle_request(const struct ethdev_mp_request *req, size_t len)<br>
{<br>
+ len -= sizeof(*req);<br>
+<br>
switch (req->operation) {<br>
case ETH_REQ_START:<br>
+ if (len != 0)<br>
+ return -EINVAL;<br>
+<br>
return rte_eth_dev_start(req->port_id);<br>
<br>
case ETH_REQ_STOP:<br>
+ if (len != 0)<br>
+ return -EINVAL;<br>
return rte_eth_dev_stop(req->port_id);<br>
<br>
+ case ETH_REQ_RESET:<br>
+ if (len != 0)<br>
+ return -EINVAL;<br>
+ return rte_eth_dev_reset(req->port_id);<br>
+<br>
+ case ETH_REQ_ADD_MIRROR:<br>
+ if (len != sizeof(struct rte_eth_mirror_conf)) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR,<br>
+ "add mirror conf wrong size %zu", len);<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ const struct rte_eth_mirror_conf *conf<br>
+ = (const struct rte_eth_mirror_conf *) req->config;<br>
+<br>
+ return rte_eth_add_mirror(req->port_id, conf);<br>
+<br>
+ case ETH_REQ_REMOVE_MIRROR:<br>
+ if (len != sizeof(uint16_t)) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR,<br>
+ "mirror remove wrong size %zu", len);<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ uint16_t target = *(const uint16_t *) req->config;<br>
+ return rte_eth_remove_mirror(req->port_id, target);<br>
+<br>
default:<br>
- return -EINVAL;<br>
+ RTE_ETHDEV_LOG_LINE(ERR,<br>
+ "Unknown mp request operation %u", req->operation);<br>
+ return -ENOTSUP;<br>
}<br>
}<br>
<br>
@@ -513,23 +551,25 @@ static_assert(sizeof(struct ethdev_mp_response) <= RTE_MP_MAX_PARAM_LEN,<br>
int<br>
ethdev_server(const struct rte_mp_msg *mp_msg, const void *peer)<br>
{<br>
- const struct ethdev_mp_request *req<br>
- = (const struct ethdev_mp_request *)mp_msg->param;<br>
-<br>
struct rte_mp_msg mp_resp = {<br>
.name = ETHDEV_MP,<br>
};<br>
struct ethdev_mp_response *resp;<br>
+ const struct ethdev_mp_request *req;<br>
<br>
resp = (struct ethdev_mp_response *)mp_resp.param;<br>
mp_resp.len_param = sizeof(*resp);<br>
- resp->res_op = req->operation;<br>
<br>
/* recv client requests */<br>
- if (mp_msg->len_param != sizeof(*req))<br>
+ if (mp_msg->len_param < (int)sizeof(*req)) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "invalid request from secondary");<br>
resp->err_value = -EINVAL;<br>
- else<br>
- resp->err_value = ethdev_handle_request(req);<br>
+ } else {<br>
+ req = (const struct ethdev_mp_request *)mp_msg->param;<br>
+ resp->res_op = req->operation;<br>
+ resp->err_value = ethdev_handle_request(req, mp_msg->len_param);<br>
+<br>
+ }<br>
<br>
return rte_mp_reply(&mp_resp, peer);<br>
}<br>
diff --git a/lib/ethdev/ethdev_private.h b/lib/ethdev/ethdev_private.h<br>
index f58f161871..d2fdc20057 100644<br>
--- a/lib/ethdev/ethdev_private.h<br>
+++ b/lib/ethdev/ethdev_private.h<br>
@@ -85,6 +85,9 @@ int eth_dev_tx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues);<br>
enum ethdev_mp_operation {<br>
ETH_REQ_START,<br>
ETH_REQ_STOP,<br>
+ ETH_REQ_RESET,<br>
+ ETH_REQ_ADD_MIRROR,<br>
+ ETH_REQ_REMOVE_MIRROR,<br>
};<br>
<br>
struct ethdev_mp_request {<br>
diff --git a/lib/ethdev/ethdev_trace.h b/lib/ethdev/ethdev_trace.h<br>
index 482befc209..e137afcbf7 100644<br>
--- a/lib/ethdev/ethdev_trace.h<br>
+++ b/lib/ethdev/ethdev_trace.h<br>
@@ -1035,6 +1035,23 @@ RTE_TRACE_POINT(<br>
rte_trace_point_emit_int(ret);<br>
)<br>
<br>
+RTE_TRACE_POINT(<br>
+ rte_eth_trace_add_mirror,<br>
+ RTE_TRACE_POINT_ARGS(uint16_t port_id,<br>
+ const struct rte_eth_mirror_conf *conf, int ret),<br>
+ rte_trace_point_emit_u16(port_id);<br>
+ rte_trace_point_emit_u16(conf->target);<br>
+ rte_trace_point_emit_int(ret);<br>
+)<br>
+<br>
+RTE_TRACE_POINT(<br>
+ rte_eth_trace_remove_mirror,<br>
+ RTE_TRACE_POINT_ARGS(uint16_t port_id, uint16_t target_id, int ret),<br>
+ rte_trace_point_emit_u16(port_id);<br>
+ rte_trace_point_emit_u16(target_id);<br>
+ rte_trace_point_emit_int(ret);<br>
+)<br>
+<br>
RTE_TRACE_POINT(<br>
rte_eth_trace_rx_queue_info_get,<br>
RTE_TRACE_POINT_ARGS(uint16_t port_id, uint16_t queue_id,<br>
diff --git a/lib/ethdev/ethdev_trace_points.c b/lib/ethdev/ethdev_trace_points.c<br>
index 071c508327..fa1fd21809 100644<br>
--- a/lib/ethdev/ethdev_trace_points.c<br>
+++ b/lib/ethdev/ethdev_trace_points.c<br>
@@ -389,6 +389,12 @@ RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_rx_callback,<br>
RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_tx_callback,<br>
lib.ethdev.remove_tx_callback)<br>
<br>
+RTE_TRACE_POINT_REGISTER(rte_eth_trace_add_mirror,<br>
+ lib.ethdev.add_mirror)<br>
+<br>
+RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_mirror,<br>
+ lib.ethdev.remove_mirror)<br>
+<br>
RTE_TRACE_POINT_REGISTER(rte_eth_trace_rx_queue_info_get,<br>
lib.ethdev.rx_queue_info_get)<br>
<br>
diff --git a/lib/ethdev/meson.build b/lib/ethdev/meson.build<br>
index 1d68d5348c..eb624bd579 100644<br>
--- a/lib/ethdev/meson.build<br>
+++ b/lib/ethdev/meson.build<br>
@@ -11,6 +11,7 @@ sources = files(<br>
'rte_ethdev_cman.c',<br>
'rte_ethdev_telemetry.c',<br>
'rte_flow.c',<br>
+ 'rte_mirror.c',<br>
'rte_mtr.c',<br>
'rte_tm.c',<br>
'sff_telemetry.c',<br>
@@ -27,6 +28,7 @@ headers = files(<br>
'rte_dev_info.h',<br>
'rte_flow.h',<br>
'rte_flow_driver.h',<br>
+ 'rte_mirror.h',<br>
'rte_mtr.h',<br>
'rte_mtr_driver.h',<br>
'rte_tm.h',<br>
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c<br>
index adeec575be..ac889c220a 100644<br>
--- a/lib/ethdev/rte_ethdev.c<br>
+++ b/lib/ethdev/rte_ethdev.c<br>
@@ -14,6 +14,8 @@<br>
#include <bus_driver.h><br>
#include <eal_export.h><br>
#include <rte_log.h><br>
+#include <rte_cycles.h><br>
+#include <rte_alarm.h><br>
#include <rte_interrupts.h><br>
#include <rte_kvargs.h><br>
#include <rte_memcpy.h><br>
@@ -2041,13 +2043,16 @@ rte_eth_dev_reset(uint16_t port_id)<br>
if (dev->dev_ops->dev_reset == NULL)<br>
return -ENOTSUP;<br>
<br>
- ret = rte_eth_dev_stop(port_id);<br>
- if (ret != 0) {<br>
- RTE_ETHDEV_LOG_LINE(ERR,<br>
- "Failed to stop device (port %u) before reset: %s - ignore",<br>
- port_id, rte_strerror(-ret));<br>
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY) {<br>
+ ret = rte_eth_dev_stop(port_id);<br>
+ if (ret != 0)<br>
+ RTE_ETHDEV_LOG_LINE(ERR,<br>
+ "Failed to stop device (port %u) before reset: %s - ignore",<br>
+ port_id, rte_strerror(-ret));<br>
+ ret = eth_err(port_id, dev->dev_ops->dev_reset(dev));<br>
+ } else {<br>
+ ret = ethdev_request(port_id, ETH_REQ_STOP, NULL, 0);<br>
}<br>
- ret = eth_err(port_id, dev->dev_ops->dev_reset(dev));<br>
<br>
rte_ethdev_trace_reset(port_id, ret);<br>
<br>
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h<br>
index f9fb6ae549..84801a1123 100644<br>
--- a/lib/ethdev/rte_ethdev.h<br>
+++ b/lib/ethdev/rte_ethdev.h<br>
@@ -170,6 +170,7 @@<br>
<br>
#include "rte_ethdev_trace_fp.h"<br>
#include "rte_dev_info.h"<br>
+#include "rte_mirror.h"<br>
<br>
#ifdef __cplusplus<br>
extern "C" {<br>
@@ -1466,7 +1467,6 @@ enum rte_eth_tunnel_type {<br>
RTE_ETH_TUNNEL_TYPE_ECPRI,<br>
RTE_ETH_TUNNEL_TYPE_MAX,<br>
};<br>
-<br>
#ifdef __cplusplus<br>
}<br>
#endif<br>
@@ -6334,6 +6334,17 @@ rte_eth_rx_burst(uint16_t port_id, uint16_t queue_id,<br>
<br>
nb_rx = p->rx_pkt_burst(qd, rx_pkts, nb_pkts);<br>
<br>
+#ifdef RTE_ETHDEV_MIRROR<br>
+ {<br>
+ struct rte_eth_mirror *mirror<br>
+ = rte_atomic_load_explicit(p->rx_mirror, rte_memory_order_relaxed);<br>
+<br>
+ if (unlikely(mirror != NULL))<br>
+ rte_eth_mirror_burst(port_id, queue_id, RTE_ETH_MIRROR_DIRECTION_INGRESS,<br>
+ rx_pkts, nb_rx, mirror);<br>
+ }<br>
+#endif<br>
+<br>
#ifdef RTE_ETHDEV_RXTX_CALLBACKS<br>
{<br>
void *cb;<br>
@@ -6692,6 +6703,16 @@ rte_eth_tx_burst(uint16_t port_id, uint16_t queue_id,<br>
}<br>
#endif<br>
<br>
+#ifdef RTE_ETHDEV_MIRROR<br>
+ {<br>
+ struct rte_eth_mirror *mirror;<br>
+<br>
+ mirror = rte_atomic_load_explicit(p->tx_mirror, rte_memory_order_relaxed);<br>
+ if (unlikely(mirror != NULL))<br>
+ rte_eth_mirror_burst(port_id, queue_id, RTE_ETH_MIRROR_DIRECTION_EGRESS,<br>
+ tx_pkts, nb_pkts, mirror);<br>
+ }<br>
+#endif<br>
nb_pkts = p->tx_pkt_burst(qd, tx_pkts, nb_pkts);<br>
<br>
rte_ethdev_trace_tx_burst(port_id, queue_id, (void **)tx_pkts, nb_pkts);<br>
diff --git a/lib/ethdev/rte_ethdev_core.h b/lib/ethdev/rte_ethdev_core.h<br>
index e55fb42996..2ca1beb239 100644<br>
--- a/lib/ethdev/rte_ethdev_core.h<br>
+++ b/lib/ethdev/rte_ethdev_core.h<br>
@@ -22,6 +22,12 @@ RTE_TAILQ_HEAD(rte_eth_dev_cb_list, rte_eth_dev_callback);<br>
<br>
struct rte_eth_dev;<br>
<br>
+struct rte_eth_mirror;<br>
+<br>
+void rte_eth_mirror_burst(uint16_t port_id, uint16_t quque_id, uint8_t dir,<br>
+ struct rte_mbuf **pkts, uint16_t nb_pkts,<br>
+ struct rte_eth_mirror *mirror);<br>
+<br>
/**<br>
* @internal Retrieve input packets from a receive queue of an Ethernet device.<br>
*/<br>
@@ -101,7 +107,8 @@ struct __rte_cache_aligned rte_eth_fp_ops {<br>
eth_rx_descriptor_status_t rx_descriptor_status;<br>
/** Refill Rx descriptors with the recycling mbufs. */<br>
eth_recycle_rx_descriptors_refill_t recycle_rx_descriptors_refill;<br>
- uintptr_t reserved1[2];<br>
+ uintptr_t reserved1;<br>
+ RTE_ATOMIC(struct rte_eth_mirror *) *rx_mirror;<br>
/**@}*/<br>
<br>
/**@{*/<br>
@@ -121,7 +128,8 @@ struct __rte_cache_aligned rte_eth_fp_ops {<br>
eth_recycle_tx_mbufs_reuse_t recycle_tx_mbufs_reuse;<br>
/** Get the number of used Tx descriptors. */<br>
eth_tx_queue_count_t tx_queue_count;<br>
- uintptr_t reserved2[1];<br>
+ RTE_ATOMIC(struct rte_eth_mirror *) *tx_mirror;<br>
+ uintptr_t reserved2;<br>
/**@}*/<br>
<br>
};<br>
diff --git a/lib/ethdev/rte_mirror.c b/lib/ethdev/rte_mirror.c<br>
new file mode 100644<br>
index 0000000000..27b613b8ff<br>
--- /dev/null<br>
+++ b/lib/ethdev/rte_mirror.c<br>
@@ -0,0 +1,508 @@<br>
+/* SPDX-License-Identifier: BSD-3-Clause<br>
+ * Copyright(c) 2025 Stephen Hemminger <<a href="mailto:stephen@networkplumber.org" target="_blank">stephen@networkplumber.org</a>><br>
+ */<br>
+<br>
+#include <errno.h><br>
+#include <inttypes.h><br>
+#include <stdbool.h><br>
+#include <stdint.h><br>
+#include <stdio.h><br>
+#include <string.h><br>
+<br>
+#include <rte_alarm.h><br>
+#include <rte_bitops.h><br>
+#include <rte_common.h><br>
+#include <rte_config.h><br>
+#include <rte_cycles.h><br>
+#include <rte_eal.h><br>
+#include <rte_ether.h><br>
+#include <rte_log.h><br>
+#include <rte_malloc.h><br>
+#include <rte_mbuf.h><br>
+#include <rte_spinlock.h><br>
+#include <rte_stdatomic.h><br>
+#include <eal_export.h><br>
+<br>
+#include "rte_ethdev.h"<br>
+#include "rte_mirror.h"<br>
+#include "ethdev_driver.h"<br>
+#include "ethdev_private.h"<br>
+#include "ethdev_trace.h"<br>
+<br>
+/* Upper bound of packet bursts redirected */<br>
+#define RTE_MIRROR_BURST_SIZE 64<br>
+<br>
+#ifdef RTE_LIB_BPF<br>
+#include <rte_bpf.h><br>
+#endif<br>
+<br>
+/**<br>
+ * Structure used to hold information mirror port mirrors for a<br>
+ * queue on Rx and Tx.<br>
+ */<br>
+struct rte_eth_mirror {<br>
+ RTE_ATOMIC(struct rte_eth_mirror *) next;<br>
+ struct rte_mempool *mp;<br>
+ struct rte_bpf *bpf;<br>
+ uint32_t snaplen;<br>
+ uint32_t flags;<br>
+ uint16_t target;<br>
+ uint16_t tx_queues;<br>
+ bool need_lock;<br>
+ rte_spinlock_t lock;<br>
+ struct rte_eth_mirror_stats stats;<br>
+};<br>
+<br>
+/* spinlock for setting up mirror ports */<br>
+static rte_spinlock_t mirror_port_lock = RTE_SPINLOCK_INITIALIZER;<br>
+<br>
+/* dynamically assigned offload flag to indicate ingress vs egress */<br>
+static uint64_t mirror_origin_flag;<br>
+static int mirror_origin_offset = -1;<br>
+static uint64_t mirror_ingress_flag;<br>
+static uint64_t mirror_egress_flag;<br>
+<br>
+static uint64_t mbuf_timestamp_dynflag;<br>
+static int mbuf_timestamp_offset = -1;<br>
+<br>
+/* register dynamic mbuf fields, done on first mirror creation */<br>
+static int<br>
+ethdev_dyn_mirror_register(void)<br>
+{<br>
+ const struct rte_mbuf_dynfield field_desc = {<br>
+ .name = RTE_MBUF_DYNFIELD_MIRROR_ORIGIN,<br>
+ .size = sizeof(rte_mbuf_origin_t),<br>
+ .align = sizeof(rte_mbuf_origin_t),<br>
+ };<br>
+ struct rte_mbuf_dynflag flag_desc = {<br>
+ .name = RTE_MBUF_DYNFLAG_MIRROR_ORIGIN,<br>
+ };<br>
+ int offset;<br>
+<br>
+ if (rte_mbuf_dyn_tx_timestamp_register(&mbuf_timestamp_offset,<br>
+ &mbuf_timestamp_dynflag) < 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Failed to register timestamp flag");<br>
+ return -1;<br>
+ }<br>
+<br>
+ offset = rte_mbuf_dynfield_register(&field_desc);<br>
+ if (offset < 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf origin field");<br>
+ return -1;<br>
+ }<br>
+ mirror_origin_offset = offset;<br>
+<br>
+ offset = rte_mbuf_dynflag_register(&flag_desc);<br>
+ if (offset < 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf origin flag");<br>
+ return -1;<br>
+ }<br>
+ mirror_origin_flag = RTE_BIT64(offset);<br>
+<br>
+ strlcpy(<a href="http://flag_desc.name" rel="noreferrer" target="_blank">flag_desc.name</a>, RTE_MBUF_DYNFLAG_MIRROR_INGRESS, sizeof(<a href="http://flag_desc.name" rel="noreferrer" target="_blank">flag_desc.name</a>));<br>
+ offset = rte_mbuf_dynflag_register(&flag_desc);<br>
+ if (offset < 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf ingress flag");<br>
+ return -1;<br>
+ }<br>
+ mirror_ingress_flag = RTE_BIT64(offset);<br>
+<br>
+ strlcpy(<a href="http://flag_desc.name" rel="noreferrer" target="_blank">flag_desc.name</a>, RTE_MBUF_DYNFLAG_MIRROR_EGRESS,<br>
+ sizeof(<a href="http://flag_desc.name" rel="noreferrer" target="_blank">flag_desc.name</a>));<br>
+ offset = rte_mbuf_dynflag_register(&flag_desc);<br>
+ if (offset < 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf egress flag");<br>
+ return -1;<br>
+ }<br>
+ mirror_egress_flag = RTE_BIT64(offset);<br>
+<br>
+ return 0;<br>
+}<br>
+<br>
+/* Add a new mirror entry to the list. */<br>
+static int<br>
+ethdev_insert_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top,<br>
+ const struct rte_eth_mirror_conf *conf,<br>
+ const struct rte_eth_dev_info *info)<br>
+{<br>
+ struct rte_eth_mirror *mirror;<br>
+ ssize_t filter_len = 0;<br>
+<br>
+ /* Don't allow multiple mirrors from same source to target */<br>
+ while ((mirror = *top) != NULL) {<br>
+ if (mirror->target == conf->target) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR,<br>
+ "Mirror to port %u already exists", conf->target);<br>
+ return -EEXIST;<br>
+ }<br>
+ }<br>
+<br>
+ if (conf->filter) {<br>
+#ifdef RTE_LIB_BPF<br>
+ filter_len = rte_bpf_buf_size(conf->filter);<br>
+ if (filter_len < 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Invalid BPF filter: %s",<br>
+ rte_strerror(rte_errno));<br>
+ return -EINVAL;<br>
+ }<br>
+#else<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "BPF filter not supported");<br>
+ return -ENOTSUP;<br>
+#endif<br>
+ }<br>
+<br>
+ /*<br>
+ * Allocate space for both fast path mirror structure<br>
+ * and filter bpf code (if any).<br>
+ */<br>
+ mirror = rte_zmalloc(NULL, sizeof(*mirror) + filter_len, 0);<br>
+ if (mirror == NULL)<br>
+ return -ENOMEM;<br>
+<br>
+ mirror->mp = conf->mp;<br>
+ mirror->target = conf->target;<br>
+ mirror->flags = conf->flags;<br>
+ rte_spinlock_init(&mirror->lock);<br>
+ mirror->need_lock = !(info->tx_offload_capa & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE);<br>
+ mirror->tx_queues = info->nb_tx_queues;<br>
+<br>
+ if (conf->snaplen == 0) /* specifying 0 implies the full packet */<br>
+ mirror->snaplen = UINT32_MAX;<br>
+ else<br>
+ mirror->snaplen = conf->snaplen;<br>
+<br>
+#ifdef RTE_LIB_BPF<br>
+ if (filter_len > 0) {<br>
+ /* reserved space for BPF is after mirror structure */<br>
+ void *buf = (uint8_t *)mirror + sizeof(*mirror);<br>
+<br>
+ /*<br>
+ * Copy filter internal representation into space<br>
+ * allocated in huge pages to allow access from any process.<br>
+ */<br>
+ mirror->bpf = rte_bpf_buf_load(conf->filter, buf, filter_len);<br>
+ if (mirror->bpf == NULL) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Failed to load BPF filter: %s",<br>
+ rte_strerror(rte_errno));<br>
+ rte_free(mirror);<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ }<br>
+#endif<br>
+<br>
+ mirror->next = *top;<br>
+<br>
+ rte_atomic_store_explicit(top, mirror, rte_memory_order_relaxed);<br>
+ return 0;<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_add_mirror, 25.11)<br>
+int<br>
+rte_eth_add_mirror(uint16_t port_id, const struct rte_eth_mirror_conf *conf)<br>
+{<br>
+#ifndef RTE_ETHDEV_MIRROR<br>
+ return -ENOTSUP;<br>
+#endif<br>
+<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+<br>
+ struct rte_eth_dev *dev = &rte_eth_devices[port_id];<br>
+ struct rte_eth_dev_info dev_info;<br>
+<br>
+ if (conf == NULL) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Missing configuration information");<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ if (conf->mp == NULL) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "not a valid mempool");<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ if (conf->flags & ~(RTE_ETH_MIRROR_DIRECTION_MASK | RTE_ETH_MIRROR_FLAG_MASK)) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "unsupported flags");<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ if ((conf->flags & RTE_ETH_MIRROR_DIRECTION_MASK) == 0) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "missing direction ingress or egress");<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ /* Checks that target exists */<br>
+ int ret = rte_eth_dev_info_get(conf->target, &dev_info);<br>
+ if (ret != 0)<br>
+ return ret;<br>
+<br>
+ /* Loopback mirror could create packet storm */<br>
+ if (conf->target == port_id) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Cannot mirror port to self");<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY) {<br>
+ /* Register dynamic fields once */<br>
+ if (mirror_origin_offset < 0) {<br>
+ ret = ethdev_dyn_mirror_register();<br>
+ if (ret < 0)<br>
+ return ret;<br>
+ }<br>
+<br>
+ rte_spinlock_lock(&mirror_port_lock);<br>
+ ret = 0;<br>
+<br>
+ if (conf->flags & RTE_ETH_MIRROR_DIRECTION_INGRESS)<br>
+ ret = ethdev_insert_mirror(&dev->rx_mirror, conf, &dev_info);<br>
+ if (ret == 0 && (conf->flags & RTE_ETH_MIRROR_DIRECTION_EGRESS))<br>
+ ret = ethdev_insert_mirror(&dev->tx_mirror, conf, &dev_info);<br>
+ rte_spinlock_unlock(&mirror_port_lock);<br>
+ } else {<br>
+ /* in secondary, proxy to primary */<br>
+ ret = ethdev_request(port_id, ETH_REQ_ADD_MIRROR, conf, sizeof(*conf));<br>
+ if (ret != 0)<br>
+ return ret;<br>
+ }<br>
+<br>
+ rte_eth_trace_add_mirror(port_id, conf, ret);<br>
+ return ret;<br>
+}<br>
+<br>
+static struct rte_eth_mirror *<br>
+ethdev_find_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *head, uint16_t target_id)<br>
+{<br>
+<br>
+ for (;;) {<br>
+ struct rte_eth_mirror *mirror<br>
+ = rte_atomic_load_explicit(head, rte_memory_order_relaxed);<br>
+ if (mirror == NULL)<br>
+ return NULL; /* reached end of list */<br>
+<br>
+ if (mirror->target == target_id)<br>
+ return mirror;<br>
+<br>
+ head = &mirror->next;<br>
+ }<br>
+}<br>
+<br>
+static bool<br>
+ethdev_delete_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top, uint16_t target_id)<br>
+{<br>
+ struct rte_eth_mirror *mirror;<br>
+<br>
+ mirror = ethdev_find_mirror(top, target_id);<br>
+ if (mirror == NULL)<br>
+ return false;<br>
+<br>
+ /* unlink from list */<br>
+ rte_atomic_store_explicit(top, mirror->next, rte_memory_order_relaxed);<br>
+<br>
+ /*<br>
+ * Defer freeing the mirror until after one second to allow for active threads<br>
+ * that are using it. Assumes no PMD takes more than one second to transmit a burst.<br>
+ * Alternative would be RCU, but RCU in DPDK is optional and requires application changes.<br>
+ */<br>
+ rte_eal_alarm_set(US_PER_S, rte_free, mirror);<br>
+ return true;<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_remove_mirror, 25.11)<br>
+int<br>
+rte_eth_remove_mirror(uint16_t port_id, uint16_t target_id)<br>
+{<br>
+#ifndef RTE_ETHDEV_MIRROR<br>
+ return -ENOTSUP;<br>
+#endif<br>
+ int ret = 0;<br>
+<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);<br>
+<br>
+ struct rte_eth_dev *dev = &rte_eth_devices[port_id];<br>
+<br>
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY) {<br>
+ bool found;<br>
+<br>
+ rte_spinlock_lock(&mirror_port_lock);<br>
+ found = ethdev_delete_mirror(&dev->rx_mirror, target_id);<br>
+ found |= ethdev_delete_mirror(&dev->tx_mirror, target_id);<br>
+ rte_spinlock_unlock(&mirror_port_lock);<br>
+ if (!found)<br>
+ ret = -ENOENT; /* no mirror present */<br>
+ } else {<br>
+ ret = ethdev_request(port_id, ETH_REQ_REMOVE_MIRROR,<br>
+ &target_id, sizeof(target_id));<br>
+ }<br>
+<br>
+ rte_eth_trace_remove_mirror(port_id, target_id, ret);<br>
+ return ret;<br>
+}<br>
+<br>
+static inline void<br>
+eth_dev_mirror(uint16_t port_id, uint16_t queue_id, uint8_t direction,<br>
+ struct rte_mbuf **pkts, uint16_t nb_pkts,<br>
+ struct rte_eth_mirror *mirror)<br>
+{<br>
+ struct rte_mbuf *tosend[RTE_MIRROR_BURST_SIZE];<br>
+ unsigned int count = 0;<br>
+ unsigned int nsent;<br>
+<br>
+#ifdef RTE_LIB_BPF<br>
+ uint64_t rcs[RTE_MIRROR_BURST_SIZE];<br>
+ if (mirror->bpf)<br>
+ rte_bpf_exec_burst(mirror->bpf, (void **)pkts, rcs, nb_pkts);<br>
+#endif<br>
+<br>
+ for (unsigned int i = 0; i < nb_pkts; i++) {<br>
+#ifdef RTE_LIB_BPF<br>
+ /*<br>
+ * This uses same BPF return value convention as socket filter<br>
+ * and pcap_offline_filter. If program returns zero<br>
+ * then packet doesn't match the filter (will be ignored).<br>
+ */<br>
+ if (mirror->bpf && rcs[i] == 0) {<br>
+ ++mirror->stats.filtered;<br>
+ continue;<br>
+ }<br>
+#endif<br>
+<br>
+ struct rte_mbuf *m = pkts[i];<br>
+ struct rte_mbuf *mc = rte_pktmbuf_copy(m, mirror->mp, 0, mirror->snaplen);<br>
+ if (unlikely(mc == NULL)) {<br>
+ ++mirror->stats.nombuf;<br>
+ continue;<br>
+ }<br>
+<br>
+ /* Put info about origin of the packet */<br>
+ if (mirror->flags & RTE_ETH_MIRROR_ORIGIN_FLAG) {<br>
+ struct rte_mbuf_origin *origin<br>
+ = RTE_MBUF_DYNFIELD(mc, mirror_origin_offset, rte_mbuf_origin_t *);<br>
+ origin->original_len = m->pkt_len;<br>
+ origin->port_id = port_id;<br>
+ origin->queue_id = queue_id;<br>
+ mc->ol_flags |= mirror_origin_flag;<br>
+ }<br>
+<br>
+ /* Insert timestamp into packet */<br>
+ if (mirror->flags & RTE_ETH_MIRROR_TIMESTAMP_FLAG) {<br>
+ *RTE_MBUF_DYNFIELD(m, mbuf_timestamp_offset, rte_mbuf_timestamp_t *)<br>
+ = rte_get_tsc_cycles();<br>
+ mc->ol_flags |= mbuf_timestamp_dynflag;<br>
+ }<br>
+<br>
+ mc->ol_flags &= ~(mirror_ingress_flag | mirror_egress_flag);<br>
+ if (direction & RTE_ETH_MIRROR_DIRECTION_INGRESS)<br>
+ mc->ol_flags |= mirror_ingress_flag;<br>
+ else if (direction & RTE_ETH_MIRROR_DIRECTION_EGRESS)<br>
+ mc->ol_flags |= mirror_egress_flag;<br>
+<br>
+ tosend[count++] = mc;<br>
+ }<br>
+<br>
+ if (mirror->need_lock) {<br>
+ uint16_t txq = queue_id % mirror->tx_queues;<br>
+<br>
+ rte_spinlock_lock(&mirror->lock);<br>
+ nsent = rte_eth_tx_burst(mirror->target, txq, tosend, count);<br>
+ rte_spinlock_unlock(&mirror->lock);<br>
+ } else {<br>
+ nsent = rte_eth_tx_burst(mirror->target, 0, tosend, count);<br>
+ }<br>
+<br>
+ mirror->stats.packets += nsent;<br>
+<br>
+ if (unlikely(nsent < count)) {<br>
+ uint16_t drop = count - nsent;<br>
+<br>
+ mirror->stats.full += drop;<br>
+ rte_pktmbuf_free_bulk(pkts + nsent, drop);<br>
+ }<br>
+}<br>
+<br>
+/* This function is really internal but used from inline */<br>
+RTE_EXPORT_SYMBOL(rte_eth_mirror_burst)<br>
+void<br>
+rte_eth_mirror_burst(uint16_t port_id, uint16_t queue_id, uint8_t direction,<br>
+ struct rte_mbuf **pkts, uint16_t nb_pkts,<br>
+ struct rte_eth_mirror *mirror)<br>
+{<br>
+<br>
+ while (mirror != NULL) {<br>
+ for (uint16_t i = 0; i < nb_pkts; i += RTE_MIRROR_BURST_SIZE) {<br>
+ uint16_t burst = RTE_MIN(nb_pkts - i, RTE_MIRROR_BURST_SIZE);<br>
+<br>
+ eth_dev_mirror(port_id, queue_id, direction,<br>
+ pkts + i, burst, mirror);<br>
+ }<br>
+<br>
+ mirror = rte_atomic_load_explicit(&mirror->next, rte_memory_order_relaxed);<br>
+ }<br>
+}<br>
+<br>
+static int<br>
+ethdev_mirror_stats_get(RTE_ATOMIC(struct rte_eth_mirror *) *head, uint16_t target_id,<br>
+ struct rte_eth_mirror_stats *stats)<br>
+{<br>
+ const struct rte_eth_mirror *mirror;<br>
+<br>
+ mirror = ethdev_find_mirror(head, target_id);<br>
+ if (mirror == NULL)<br>
+ return -1;<br>
+<br>
+ stats->packets += mirror->stats.packets;<br>
+ stats->nombuf += mirror->stats.nombuf;<br>
+ stats->full += mirror->stats.full;<br>
+ return 0;<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_mirror_stats_get, 25.11)<br>
+int<br>
+rte_eth_mirror_stats_get(uint16_t port_id, uint16_t target_id,<br>
+ struct rte_eth_mirror_stats *stats)<br>
+{<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);<br>
+<br>
+ if (stats == NULL) {<br>
+ RTE_ETHDEV_LOG_LINE(ERR, "Mirror port stats is NULL");<br>
+ return -EINVAL;<br>
+ }<br>
+<br>
+ memset(stats, 0, sizeof(*stats));<br>
+<br>
+ struct rte_eth_dev *dev = &rte_eth_devices[port_id];<br>
+ int rx_ret = ethdev_mirror_stats_get(&dev->rx_mirror, target_id, stats);<br>
+ int tx_ret = ethdev_mirror_stats_get(&dev->tx_mirror, target_id, stats);<br>
+<br>
+ /* if rx or tx mirror is valid return 0 */<br>
+ return (tx_ret == 0 || rx_ret == 0) ? 0 : -ENOENT;<br>
+}<br>
+<br>
+static int<br>
+ethdev_mirror_stats_reset(RTE_ATOMIC(struct rte_eth_mirror *) *head, uint16_t target_id)<br>
+{<br>
+ struct rte_eth_mirror *mirror;<br>
+<br>
+ mirror = ethdev_find_mirror(head, target_id);<br>
+ if (mirror == NULL)<br>
+ return -1;<br>
+<br>
+ memset(&mirror->stats, 0, sizeof(struct rte_eth_mirror_stats));<br>
+ return 0;<br>
+}<br>
+<br>
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_mirror_stats_reset, 25.11)<br>
+int<br>
+rte_eth_mirror_stats_reset(uint16_t port_id, uint16_t target_id)<br>
+{<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br>
+ RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV);<br>
+<br>
+ struct rte_eth_dev *dev = &rte_eth_devices[port_id];<br>
+ int rx_ret = ethdev_mirror_stats_reset(&dev->rx_mirror, target_id);<br>
+ int tx_ret = ethdev_mirror_stats_reset(&dev->tx_mirror, target_id);<br>
+<br>
+ /* if rx or tx mirror is valid return 0 */<br>
+ return (tx_ret == 0 || rx_ret == 0) ? 0 : -ENOENT;<br>
+<br>
+}<br>
diff --git a/lib/ethdev/rte_mirror.h b/lib/ethdev/rte_mirror.h<br>
new file mode 100644<br>
index 0000000000..593ef477b6<br>
--- /dev/null<br>
+++ b/lib/ethdev/rte_mirror.h<br>
@@ -0,0 +1,147 @@<br>
+/* SPDX-License-Identifier: BSD-3-Clause<br>
+ * Copyright(c) 2025 Stephen Hemminger <<a href="mailto:stephen@networkplumber.org" target="_blank">stephen@networkplumber.org</a>><br>
+ */<br>
+<br>
+#ifndef RTE_MIRROR_H_<br>
+#define RTE_MIRROR_H_<br>
+<br>
+/**<br>
+ * @file<br>
+ * Ethdev port mirroring<br>
+ *<br>
+ * This interface provides the ability to duplicate packets to another port.<br>
+ */<br>
+<br>
+#include <stdint.h><br>
+<br>
+#include <rte_compat.h><br>
+#include <rte_mbuf.h><br>
+<br>
+#ifdef __cplusplus<br>
+extern "C" {<br>
+#endif<br>
+<br>
+/* Definitions for ethdev mirror flags */<br>
+#define RTE_ETH_MIRROR_DIRECTION_INGRESS 1<br>
+#define RTE_ETH_MIRROR_DIRECTION_EGRESS 2<br>
+#define RTE_ETH_MIRROR_DIRECTION_MASK (RTE_ETH_MIRROR_DIRECTION_INGRESS | \<br>
+ RTE_ETH_MIRROR_DIRECTION_EGRESS)<br>
+<br>
+#define RTE_ETH_MIRROR_TIMESTAMP_FLAG 4 /**< insert timestamp into mirrored packet */<br>
+#define RTE_ETH_MIRROR_ORIGIN_FLAG 8 /**< insert rte_mbuf_origin into mirrored packet */<br>
+<br>
+#define RTE_ETH_MIRROR_FLAG_MASK (RTE_ETH_MIRROR_TIMESTAMP_FLAG | \<br>
+ RTE_ETH_MIRROR_ORIGIN_FLAG)<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this structure may change without prior notice.<br>
+ *<br>
+ * This dynamic field is added to mbuf's when they are copied to<br>
+ * the port mirror.<br>
+ */<br>
+typedef struct rte_mbuf_origin {<br>
+ uint32_t original_len; /**< Packet length before copy */<br>
+ uint16_t port_id; /**< Port where packet originated */<br>
+ uint16_t queue_id; /**< Queue used for Tx or Rx */<br>
+} rte_mbuf_origin_t;<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this structure may change without prior notice.<br>
+ *<br>
+ * Structure used to configure ethdev Switched Port Analyzer (MIRROR)<br>
+ */<br>
+struct rte_bpf_prm;<br>
+struct rte_eth_mirror_conf {<br>
+ struct rte_mempool *mp; /**< Memory pool for copies, If NULL then cloned. */<br>
+ struct rte_bpf_prm *filter; /**< Optional packet filter */<br>
+ uint32_t snaplen; /**< Upper limit on number of bytes to copy */<br>
+ uint32_t flags; /**< bitmask of RTE_ETH_MIRROR_XXX_FLAG's */<br>
+ uint16_t target; /**< Destination port */<br>
+};<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this structure may change without prior notice.<br>
+ *<br>
+ * Structure returned by rte_mirror_stats.<br>
+ */<br>
+struct rte_eth_mirror_stats {<br>
+ uint64_t packets; /**< Number of mirrored packets. */<br>
+ uint64_t filtered; /**< Packets filtered by BPF program */<br>
+ uint64_t nombuf; /**< Rx mbuf allocation failures. */<br>
+ uint64_t full; /**< Target port transmit full. */<br>
+};<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice<br>
+ *<br>
+ * Create a port mirror instance.<br>
+ *<br>
+ * @param port_id<br>
+ * The port identifier of the source Ethernet device.<br>
+ * @param conf<br>
+ * Settings for this MIRROR instance.<br>
+ * @return<br>
+ * Negative errno value on error, 0 on success.<br>
+ */<br>
+__rte_experimental<br>
+int<br>
+rte_eth_add_mirror(uint16_t port_id, const struct rte_eth_mirror_conf *conf);<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice<br>
+ *<br>
+ * Break port existing port mirroring.<br>
+ * After this call no more packets will be sent from origin port to the target port.<br>
+ *<br>
+ * @param port_id<br>
+ * The port identifier of the source Ethernet device.<br>
+ * @param target_id<br>
+ * The identifier of the destination port.<br>
+ * @return<br>
+ * Negative errno value on error, 0 on success.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_remove_mirror(uint16_t port_id, uint16_t target_id);<br>
+<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice<br>
+ *<br>
+ * Query statistics for a mirror.<br>
+ *<br>
+ * @param port_id<br>
+ * The port identifier of the source Ethernet device.<br>
+ * @param target_id<br>
+ * The identifier of the destination port.<br>
+ * @param stats<br>
+ * A pointer to a structure of type *rte_eth_mirror_stats* to be filled.<br>
+ *<br>
+ * @return<br>
+ * Negative errno value on error, 0 on success.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_mirror_stats_get(uint16_t port_id, uint16_t target_id,<br>
+ struct rte_eth_mirror_stats *stats);<br>
+/**<br>
+ * @warning<br>
+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice<br>
+ *<br>
+ * Reset statistics for mirror.<br>
+ *<br>
+ * @param port_id<br>
+ * The port identifier of the source Ethernet device.<br>
+ * @param target_id<br>
+ * The identifier of the destination port.<br>
+ */<br>
+__rte_experimental<br>
+int rte_eth_mirror_stats_reset(uint16_t port_id, uint16_t target_id);<br>
+<br>
+#ifdef __cplusplus<br>
+}<br>
+#endif<br>
+<br>
+#endif /* RTE_MIRROR_H_ */<br>
-- <br>
2.47.2<br>
<br>
</blockquote></div>