From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 69D8546BE2; Wed, 23 Jul 2025 04:13:31 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 12019402CA; Wed, 23 Jul 2025 04:13:31 +0200 (CEST) Received: from agw.arknetworks.am (agw.arknetworks.am [79.141.165.80]) by mails.dpdk.org (Postfix) with ESMTP id 52AFC4026D for ; Wed, 23 Jul 2025 04:13:30 +0200 (CEST) Received: from debian (unknown [78.109.70.60]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by agw.arknetworks.am (Postfix) with ESMTPSA id 9E52FE080B; Wed, 23 Jul 2025 06:13:28 +0400 (+04) DKIM-Filter: OpenDKIM Filter v2.11.0 agw.arknetworks.am 9E52FE080B DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arknetworks.am; s=default; t=1753236809; bh=IkmPJPsSI5eTst9nP9wUHRJXoTAqfO7p/sLfXAhkq84=; h=Date:From:To:cc:Subject:In-Reply-To:References:From; b=vqw6LbQ3YjJKa0V32PNqLT1BTKx22iyk96hwpaejy0GD4H5SW02qvw0FkkRCVR1p1 ztXPkNd2zGvv8NjnNp5VtIpi4tvW2gQ1TBwRG5uir70Xjg+vAzgBRzTMhzeLdHgMx0 +/dTeUlKgICMva7Bd46f0+1BMQy+2/nqa4RbOU9jsC2TpkisYBb3LL3rIu6vgwg88t l9iYvY69Wfo+jSJa2vVfSbjtx/rloU+hqpf7iS3av4VPMFYvQntCNCrQk8lmUQWTjQ O2j8p9mcFpvBMvMzCaTITfw3oYK/08wwTmhK/V3E4x/HvTFRf3qjJEU8ru+ZKUhOvX mY5tVBp00y3bg== Date: Wed, 23 Jul 2025 06:13:27 +0400 (+04) From: Ivan Malov To: Stephen Hemminger cc: dev@dpdk.org, Bruce Richardson , Thomas Monjalon , Andrew Rybchenko , Anatoly Burakov Subject: Re: [PATCH v6 07/13] ethdev: add port mirroring feature In-Reply-To: <20250722173552.184141-8-stephen@networkplumber.org> Message-ID: References: <20250411234927.114568-1-stephen@networkplumber.org> <20250722173552.184141-1-stephen@networkplumber.org> <20250722173552.184141-8-stephen@networkplumber.org> MIME-Version: 1.0 Content-Type: text/plain; charset=US-ASCII; format=flowed X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Hi Stephen, (please see below) On Tue, 22 Jul 2025, Stephen Hemminger wrote: > This adds new feature port mirroring to the ethdev layer. > > Signed-off-by: Stephen Hemminger > --- > config/rte_config.h | 1 + > lib/ethdev/ethdev_driver.h | 6 + > lib/ethdev/ethdev_private.c | 58 ++++- > lib/ethdev/ethdev_private.h | 3 + > lib/ethdev/ethdev_trace.h | 17 ++ > lib/ethdev/ethdev_trace_points.c | 6 + > lib/ethdev/meson.build | 1 + > lib/ethdev/rte_ethdev.c | 17 +- > lib/ethdev/rte_ethdev.h | 23 +- > lib/ethdev/rte_ethdev_core.h | 12 +- > lib/ethdev/rte_mirror.c | 375 +++++++++++++++++++++++++++++++ > lib/ethdev/rte_mirror.h | 99 ++++++++ > 12 files changed, 600 insertions(+), 18 deletions(-) > create mode 100644 lib/ethdev/rte_mirror.c > create mode 100644 lib/ethdev/rte_mirror.h > > diff --git a/config/rte_config.h b/config/rte_config.h > index 05344e2619..76eb2aa417 100644 > --- a/config/rte_config.h > +++ b/config/rte_config.h > @@ -69,6 +69,7 @@ > #define RTE_MAX_QUEUES_PER_PORT 1024 > #define RTE_ETHDEV_QUEUE_STAT_CNTRS 16 /* max 256 */ > #define RTE_ETHDEV_RXTX_CALLBACKS 1 > +#define RTE_ETHDEV_MIRROR 1 > #define RTE_MAX_MULTI_HOST_CTRLS 4 > > /* cryptodev defines */ > diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h > index 2b4d2ae9c3..e6aad6d30a 100644 > --- a/lib/ethdev/ethdev_driver.h > +++ b/lib/ethdev/ethdev_driver.h > @@ -91,6 +91,12 @@ struct __rte_cache_aligned rte_eth_dev { > */ > RTE_ATOMIC(struct rte_eth_rxtx_callback *) pre_tx_burst_cbs[RTE_MAX_QUEUES_PER_PORT]; > > + /** Receive mirrors */ > + RTE_ATOMIC(struct rte_eth_mirror *) rx_mirror; > + > + /** Transmit mirrors */ > + RTE_ATOMIC(struct rte_eth_mirror *) tx_mirror; > + > enum rte_eth_dev_state state; /**< Flag indicating the port state */ > void *security_ctx; /**< Context for security ops */ > }; > diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c > index fffffe2625..dd144db993 100644 > --- a/lib/ethdev/ethdev_private.c > +++ b/lib/ethdev/ethdev_private.c > @@ -289,6 +289,8 @@ eth_dev_fp_ops_setup(struct rte_eth_fp_ops *fpo, > fpo->tx_descriptor_status = dev->tx_descriptor_status; > fpo->recycle_tx_mbufs_reuse = dev->recycle_tx_mbufs_reuse; > fpo->recycle_rx_descriptors_refill = dev->recycle_rx_descriptors_refill; > + fpo->rx_mirror = (struct rte_eth_mirror * __rte_atomic *)(uintptr_t)&dev->rx_mirror; > + fpo->tx_mirror = (struct rte_eth_mirror * __rte_atomic *)(uintptr_t)&dev->tx_mirror; > > fpo->rxq.data = dev->data->rx_queues; > fpo->rxq.clbk = (void * __rte_atomic *)(uintptr_t)dev->post_rx_burst_cbs; > @@ -490,17 +492,53 @@ eth_dev_tx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues) > } > > static int > -ethdev_handle_request(const struct ethdev_mp_request *req) > +ethdev_handle_request(const struct ethdev_mp_request *req, size_t len) > { > + len -= sizeof(*req); > + > switch (req->operation) { > case ETH_REQ_START: > + if (len != 0) > + return -EINVAL; > + > return rte_eth_dev_start(req->port_id); > > case ETH_REQ_STOP: > + if (len != 0) > + return -EINVAL; > return rte_eth_dev_stop(req->port_id); > > + case ETH_REQ_RESET: > + if (len != 0) > + return -EINVAL; > + return rte_eth_dev_reset(req->port_id); > + > + case ETH_REQ_ADD_MIRROR: > + if (len != sizeof(struct rte_eth_mirror_conf)) { > + RTE_ETHDEV_LOG_LINE(ERR, > + "add mirror conf wrong size %zu", len); > + return -EINVAL; > + } > + > + const struct rte_eth_mirror_conf *conf > + = (const struct rte_eth_mirror_conf *) req->config; > + > + return rte_eth_add_mirror(req->port_id, conf); > + > + case ETH_REQ_REMOVE_MIRROR: > + if (len != sizeof(uint16_t)) { > + RTE_ETHDEV_LOG_LINE(ERR, > + "mirror remove wrong size %zu", len); > + return -EINVAL; > + } > + > + uint16_t target = *(const uint16_t *) req->config; > + return rte_eth_remove_mirror(req->port_id, target); > + > default: > - return -EINVAL; > + RTE_ETHDEV_LOG_LINE(ERR, > + "Unknown mp request operation %u", req->operation); > + return -ENOTSUP; > } > } > > @@ -513,23 +551,25 @@ static_assert(sizeof(struct ethdev_mp_response) <= RTE_MP_MAX_PARAM_LEN, > int > ethdev_server(const struct rte_mp_msg *mp_msg, const void *peer) > { > - const struct ethdev_mp_request *req > - = (const struct ethdev_mp_request *)mp_msg->param; > - > struct rte_mp_msg mp_resp = { > .name = ETHDEV_MP, > }; > struct ethdev_mp_response *resp; > + const struct ethdev_mp_request *req; > > resp = (struct ethdev_mp_response *)mp_resp.param; > mp_resp.len_param = sizeof(*resp); > - resp->res_op = req->operation; > > /* recv client requests */ > - if (mp_msg->len_param != sizeof(*req)) > + if (mp_msg->len_param < (int)sizeof(*req)) { > + RTE_ETHDEV_LOG_LINE(ERR, "invalid request from secondary"); > resp->err_value = -EINVAL; > - else > - resp->err_value = ethdev_handle_request(req); > + } else { > + req = (const struct ethdev_mp_request *)mp_msg->param; > + resp->res_op = req->operation; > + resp->err_value = ethdev_handle_request(req, mp_msg->len_param); > + > + } > > return rte_mp_reply(&mp_resp, peer); > } > diff --git a/lib/ethdev/ethdev_private.h b/lib/ethdev/ethdev_private.h > index f58f161871..d2fdc20057 100644 > --- a/lib/ethdev/ethdev_private.h > +++ b/lib/ethdev/ethdev_private.h > @@ -85,6 +85,9 @@ int eth_dev_tx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues); > enum ethdev_mp_operation { > ETH_REQ_START, > ETH_REQ_STOP, > + ETH_REQ_RESET, > + ETH_REQ_ADD_MIRROR, > + ETH_REQ_REMOVE_MIRROR, > }; > > struct ethdev_mp_request { > diff --git a/lib/ethdev/ethdev_trace.h b/lib/ethdev/ethdev_trace.h > index 482befc209..e137afcbf7 100644 > --- a/lib/ethdev/ethdev_trace.h > +++ b/lib/ethdev/ethdev_trace.h > @@ -1035,6 +1035,23 @@ RTE_TRACE_POINT( > rte_trace_point_emit_int(ret); > ) > > +RTE_TRACE_POINT( > + rte_eth_trace_add_mirror, > + RTE_TRACE_POINT_ARGS(uint16_t port_id, > + const struct rte_eth_mirror_conf *conf, int ret), > + rte_trace_point_emit_u16(port_id); > + rte_trace_point_emit_u16(conf->target); > + rte_trace_point_emit_int(ret); > +) > + > +RTE_TRACE_POINT( > + rte_eth_trace_remove_mirror, > + RTE_TRACE_POINT_ARGS(uint16_t port_id, uint16_t target_id, int ret), > + rte_trace_point_emit_u16(port_id); > + rte_trace_point_emit_u16(target_id); > + rte_trace_point_emit_int(ret); > +) > + > RTE_TRACE_POINT( > rte_eth_trace_rx_queue_info_get, > RTE_TRACE_POINT_ARGS(uint16_t port_id, uint16_t queue_id, > diff --git a/lib/ethdev/ethdev_trace_points.c b/lib/ethdev/ethdev_trace_points.c > index 071c508327..fa1fd21809 100644 > --- a/lib/ethdev/ethdev_trace_points.c > +++ b/lib/ethdev/ethdev_trace_points.c > @@ -389,6 +389,12 @@ RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_rx_callback, > RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_tx_callback, > lib.ethdev.remove_tx_callback) > > +RTE_TRACE_POINT_REGISTER(rte_eth_trace_add_mirror, > + lib.ethdev.add_mirror) > + > +RTE_TRACE_POINT_REGISTER(rte_eth_trace_remove_mirror, > + lib.ethdev.remove_mirror) > + > RTE_TRACE_POINT_REGISTER(rte_eth_trace_rx_queue_info_get, > lib.ethdev.rx_queue_info_get) > > diff --git a/lib/ethdev/meson.build b/lib/ethdev/meson.build > index f1d2586591..3672b6a35b 100644 > --- a/lib/ethdev/meson.build > +++ b/lib/ethdev/meson.build > @@ -11,6 +11,7 @@ sources = files( > 'rte_ethdev_cman.c', > 'rte_ethdev_telemetry.c', > 'rte_flow.c', > + 'rte_mirror.c', > 'rte_mtr.c', > 'rte_tm.c', > 'sff_telemetry.c', > diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c > index 41363af2c3..6602a771bd 100644 > --- a/lib/ethdev/rte_ethdev.c > +++ b/lib/ethdev/rte_ethdev.c > @@ -14,6 +14,8 @@ > #include > #include > #include > +#include > +#include > #include > #include > #include > @@ -2041,13 +2043,16 @@ rte_eth_dev_reset(uint16_t port_id) > if (dev->dev_ops->dev_reset == NULL) > return -ENOTSUP; > > - ret = rte_eth_dev_stop(port_id); > - if (ret != 0) { > - RTE_ETHDEV_LOG_LINE(ERR, > - "Failed to stop device (port %u) before reset: %s - ignore", > - port_id, rte_strerror(-ret)); > + if (rte_eal_process_type() == RTE_PROC_PRIMARY) { > + ret = rte_eth_dev_stop(port_id); > + if (ret != 0) > + RTE_ETHDEV_LOG_LINE(ERR, > + "Failed to stop device (port %u) before reset: %s - ignore", > + port_id, rte_strerror(-ret)); > + ret = eth_err(port_id, dev->dev_ops->dev_reset(dev)); > + } else { > + ret = ethdev_request(port_id, ETH_REQ_STOP, NULL, 0); > } > - ret = eth_err(port_id, dev->dev_ops->dev_reset(dev)); > > rte_ethdev_trace_reset(port_id, ret); > > diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h > index f9fb6ae549..4101402266 100644 > --- a/lib/ethdev/rte_ethdev.h > +++ b/lib/ethdev/rte_ethdev.h > @@ -170,6 +170,7 @@ > > #include "rte_ethdev_trace_fp.h" > #include "rte_dev_info.h" > +#include "rte_mirror.h" > > #ifdef __cplusplus > extern "C" { > @@ -1466,7 +1467,6 @@ enum rte_eth_tunnel_type { > RTE_ETH_TUNNEL_TYPE_ECPRI, > RTE_ETH_TUNNEL_TYPE_MAX, > }; > - > #ifdef __cplusplus > } > #endif > @@ -6334,6 +6334,17 @@ rte_eth_rx_burst(uint16_t port_id, uint16_t queue_id, > > nb_rx = p->rx_pkt_burst(qd, rx_pkts, nb_pkts); > > +#ifdef RTE_ETHDEV_MIRROR > + { > + const struct rte_eth_mirror *mirror > + = rte_atomic_load_explicit(p->rx_mirror, rte_memory_order_relaxed); > + > + if (unlikely(mirror != NULL)) > + rte_eth_mirror_burst(port_id, queue_id, RTE_ETH_MIRROR_DIRECTION_INGRESS, > + rx_pkts, nb_rx, mirror); > + } > +#endif > + > #ifdef RTE_ETHDEV_RXTX_CALLBACKS > { > void *cb; > @@ -6692,6 +6703,16 @@ rte_eth_tx_burst(uint16_t port_id, uint16_t queue_id, > } > #endif > > +#ifdef RTE_ETHDEV_MIRROR > + { > + const struct rte_eth_mirror *mirror; > + > + mirror = rte_atomic_load_explicit(p->tx_mirror, rte_memory_order_relaxed); > + if (unlikely(mirror != NULL)) > + rte_eth_mirror_burst(port_id, queue_id, RTE_ETH_MIRROR_DIRECTION_EGRESS, > + tx_pkts, nb_pkts, mirror); > + } > +#endif > nb_pkts = p->tx_pkt_burst(qd, tx_pkts, nb_pkts); > > rte_ethdev_trace_tx_burst(port_id, queue_id, (void **)tx_pkts, nb_pkts); > diff --git a/lib/ethdev/rte_ethdev_core.h b/lib/ethdev/rte_ethdev_core.h > index e55fb42996..b72050d41b 100644 > --- a/lib/ethdev/rte_ethdev_core.h > +++ b/lib/ethdev/rte_ethdev_core.h > @@ -22,6 +22,12 @@ RTE_TAILQ_HEAD(rte_eth_dev_cb_list, rte_eth_dev_callback); > > struct rte_eth_dev; > > +struct rte_eth_mirror; > + > +void rte_eth_mirror_burst(uint16_t port_id, uint16_t quque_id, uint8_t dir, > + struct rte_mbuf **pkts, uint16_t nb_pkts, > + const struct rte_eth_mirror *mirror); > + > /** > * @internal Retrieve input packets from a receive queue of an Ethernet device. > */ > @@ -101,7 +107,8 @@ struct __rte_cache_aligned rte_eth_fp_ops { > eth_rx_descriptor_status_t rx_descriptor_status; > /** Refill Rx descriptors with the recycling mbufs. */ > eth_recycle_rx_descriptors_refill_t recycle_rx_descriptors_refill; > - uintptr_t reserved1[2]; > + uintptr_t reserved1; > + RTE_ATOMIC(struct rte_eth_mirror *) *rx_mirror; > /**@}*/ > > /**@{*/ > @@ -121,7 +128,8 @@ struct __rte_cache_aligned rte_eth_fp_ops { > eth_recycle_tx_mbufs_reuse_t recycle_tx_mbufs_reuse; > /** Get the number of used Tx descriptors. */ > eth_tx_queue_count_t tx_queue_count; > - uintptr_t reserved2[1]; > + RTE_ATOMIC(struct rte_eth_mirror *) *tx_mirror; > + uintptr_t reserved2; > /**@}*/ > > }; > diff --git a/lib/ethdev/rte_mirror.c b/lib/ethdev/rte_mirror.c > new file mode 100644 > index 0000000000..9140306199 > --- /dev/null > +++ b/lib/ethdev/rte_mirror.c > @@ -0,0 +1,375 @@ > +/* SPDX-License-Identifier: BSD-3-Clause > + * Copyright(c) 2025 Stephen Hemminger > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "rte_ethdev.h" > +#include "rte_mirror.h" > +#include "ethdev_driver.h" > +#include "ethdev_private.h" > +#include "ethdev_trace.h" > + > +/* Upper bound of packet bursts redirected */ > +#define RTE_MIRROR_BURST_SIZE 64 > + > +/* spinlock for setting up mirror ports */ > +static rte_spinlock_t mirror_port_lock = RTE_SPINLOCK_INITIALIZER; > + > +/* dynamically assigned offload flag to indicate ingress vs egress */ > +static uint64_t mirror_origin_flag; > +static int mirror_origin_offset = -1; > +static uint64_t mirror_ingress_flag; > +static uint64_t mirror_egress_flag; > + > +static uint64_t mbuf_timestamp_dynflag; > +static int mbuf_timestamp_offset = -1; > + > +#ifdef RTE_ETHDEV_MIRROR > + > +/* register dynamic mbuf fields, done on first mirror creation */ > +static int > +ethdev_dyn_mirror_register(void) > +{ > + const struct rte_mbuf_dynfield field_desc = { > + .name = RTE_MBUF_DYNFIELD_MIRROR_ORIGIN, > + .size = sizeof(rte_mbuf_origin_t), > + .align = sizeof(rte_mbuf_origin_t), > + }; > + struct rte_mbuf_dynflag flag_desc = { > + .name = RTE_MBUF_DYNFLAG_MIRROR_ORIGIN, > + }; > + int offset; > + > + if (rte_mbuf_dyn_tx_timestamp_register(&mbuf_timestamp_offset, > + &mbuf_timestamp_dynflag) < 0) { > + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register timestamp flag"); > + return -1; > + } > + > + offset = rte_mbuf_dynfield_register(&field_desc); > + if (offset < 0) { > + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf origin field"); > + return -1; > + } > + mirror_origin_offset = offset; > + > + offset = rte_mbuf_dynflag_register(&flag_desc); > + if (offset < 0) { > + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf origin flag"); > + return -1; > + } > + mirror_origin_flag = RTE_BIT64(offset); > + > + strlcpy(flag_desc.name, RTE_MBUF_DYNFLAG_MIRROR_INGRESS, sizeof(flag_desc.name)); > + offset = rte_mbuf_dynflag_register(&flag_desc); > + if (offset < 0) { > + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf ingress flag"); > + return -1; > + } > + mirror_ingress_flag = RTE_BIT64(offset); > + > + strlcpy(flag_desc.name, RTE_MBUF_DYNFLAG_MIRROR_EGRESS, > + sizeof(flag_desc.name)); > + offset = rte_mbuf_dynflag_register(&flag_desc); > + if (offset < 0) { > + RTE_ETHDEV_LOG_LINE(ERR, "Failed to register mbuf egress flag"); > + return -1; > + } > + mirror_egress_flag = RTE_BIT64(offset); > + > + return 0; > +} > + > +/** > + * Structure used to hold information mirror port mirrors for a > + * queue on Rx and Tx. > + */ > +struct rte_eth_mirror { > + RTE_ATOMIC(struct rte_eth_mirror *) next; > + struct rte_eth_mirror_conf conf; > +}; > + > +/* Add a new mirror entry to the list. */ > +static int > +ethdev_insert_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top, > + const struct rte_eth_mirror_conf *conf) > +{ > + struct rte_eth_mirror *mirror; > + > + /* Don't allow multiple mirrors from same source to target */ > + while ((mirror = *top) != NULL) { > + if (mirror->conf.target == conf->target) { > + RTE_ETHDEV_LOG_LINE(ERR, > + "Mirror to port %u already exists", conf->target); > + return -EEXIST; > + } > + } > + > + mirror = rte_zmalloc(NULL, sizeof(*mirror), 0); > + if (mirror == NULL) > + return -ENOMEM; > + > + mirror->conf = *conf; > + /* specifying 0 implies the full packet */ > + if (conf->snaplen == 0) > + mirror->conf.snaplen = UINT32_MAX; > + > + mirror->next = *top; > + > + rte_atomic_store_explicit(top, mirror, rte_memory_order_relaxed); > + return 0; > +} > + > +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_add_mirror, 25.11) > +int > +rte_eth_add_mirror(uint16_t port_id, const struct rte_eth_mirror_conf *conf) > +{ > + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV); > + > + struct rte_eth_dev *dev = &rte_eth_devices[port_id]; > + struct rte_eth_dev_info dev_info; > + > + if (conf == NULL) { > + RTE_ETHDEV_LOG_LINE(ERR, "Missing configuration information"); > + return -EINVAL; > + } > + > + if (conf->direction == 0 || conf->direction > > + (RTE_ETH_MIRROR_DIRECTION_INGRESS | RTE_ETH_MIRROR_DIRECTION_EGRESS)) { > + RTE_ETHDEV_LOG_LINE(ERR, "Invalid direction %#x", conf->direction); > + return -EINVAL; > + } > + > + if (conf->mp == NULL) { > + RTE_ETHDEV_LOG_LINE(ERR, "not a valid mempool"); > + return -EINVAL; > + } > + > + if (conf->flags & ~RTE_ETH_MIRROR_FLAG_MASK) { > + RTE_ETHDEV_LOG_LINE(ERR, "unsupported flags"); > + return -EINVAL; > + } > + > + /* Checks that target exists */ > + int ret = rte_eth_dev_info_get(conf->target, &dev_info); > + if (ret != 0) > + return ret; > + > + /* Loopback mirror could create packet storm */ > + if (conf->target == port_id) { > + RTE_ETHDEV_LOG_LINE(ERR, "Cannot mirror port to self"); > + return -EINVAL; > + } > + > + /* Because multiple directions and multiple queues will all going to the mirror port Perhaps use /* * comment */ style (up to you). Also, 'will all BE going'? > + * need the transmit path to be lockfree. > + */ > + if (!(dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE)) { > + RTE_ETHDEV_LOG_LINE(ERR, "Mirror needs lockfree transmit"); > + return -ENOTSUP; > + } > + > + if (rte_eal_process_type() == RTE_PROC_PRIMARY) { > + /* Register dynamic fields once */ > + if (mirror_origin_offset < 0) { > + ret = ethdev_dyn_mirror_register(); > + if (ret < 0) > + return ret; > + } > + > + rte_spinlock_lock(&mirror_port_lock); > + ret = 0; > + if (conf->direction & RTE_ETH_MIRROR_DIRECTION_INGRESS) > + ret = ethdev_insert_mirror(&dev->rx_mirror, conf); > + if (ret == 0 && (conf->direction & RTE_ETH_MIRROR_DIRECTION_EGRESS)) > + ret = ethdev_insert_mirror(&dev->tx_mirror, conf); > + rte_spinlock_unlock(&mirror_port_lock); > + } else { > + /* in secondary, proxy to primary */ > + ret = ethdev_request(port_id, ETH_REQ_ADD_MIRROR, conf, sizeof(*conf)); > + if (ret != 0) > + return ret; > + } > + > + rte_eth_trace_add_mirror(port_id, conf, ret); > + return ret; > +} > + > +static bool > +ethdev_delete_mirror(RTE_ATOMIC(struct rte_eth_mirror *) *top, uint16_t target_id) > +{ > + struct rte_eth_mirror *mirror; > + > + while ((mirror = *top) != NULL) { > + if (mirror->conf.target == target_id) > + goto found; > + top = &mirror->next; > + } > + /* not found in list */ > + return false; > + > +found: > + /* unlink from list */ > + rte_atomic_store_explicit(top, mirror->next, rte_memory_order_relaxed); > + > + /* Defer freeing the mirror until after one second Perhaps use /* * comment */ style (up to you). > + * to allow for active threads that are using it. > + * Assumes no PMD takes more than one second to transmit a burst. > + * Alternative would be RCU, but RCU in DPDK is optional. > + */ > + rte_eal_alarm_set(US_PER_S, rte_free, mirror); > + return true; > +} > + > +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_remove_mirror, 25.11) > +int > +rte_eth_remove_mirror(uint16_t port_id, uint16_t target_id) > +{ > + int ret = 0; > + > + RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV); > + struct rte_eth_dev *dev = &rte_eth_devices[port_id]; > + > + RTE_ETH_VALID_PORTID_OR_ERR_RET(target_id, -ENODEV); > + > + if (rte_eal_process_type() == RTE_PROC_PRIMARY) { > + bool found; > + > + rte_spinlock_lock(&mirror_port_lock); > + found = ethdev_delete_mirror(&dev->rx_mirror, target_id); > + found |= ethdev_delete_mirror(&dev->tx_mirror, target_id); > + rte_spinlock_unlock(&mirror_port_lock); > + if (!found) > + ret = -ENOENT; /* no mirror present */ > + } else { > + ret = ethdev_request(port_id, ETH_REQ_REMOVE_MIRROR, > + &target_id, sizeof(target_id)); > + } > + > + rte_eth_trace_remove_mirror(port_id, target_id, ret); > + return ret; > +} > + > +static inline void > +eth_dev_mirror(uint16_t port_id, uint16_t queue_id, uint8_t direction, > + struct rte_mbuf **pkts, uint16_t nb_pkts, > + const struct rte_eth_mirror_conf *conf) > +{ > + struct rte_mbuf *tosend[RTE_MIRROR_BURST_SIZE]; > + unsigned int count = 0; > + unsigned int i; > + > + for (i = 0; i < nb_pkts; i++) { > + struct rte_mbuf *m = pkts[i]; > + struct rte_mbuf *mc; > + > + if (conf->flags & RTE_ETH_MIRROR_INDIRECT_FLAG) { > + mc = rte_pktmbuf_alloc(conf->mp); Can 'rte_pktmbuf_alloc_bulk' be used prior to the 'for' loop? I do not insist. > + if (unlikely(mc == NULL)) > + continue; > + > + /* Make both mbuf's point to the same data */ > + rte_pktmbuf_attach(mc, m); > + } else { > + mc = rte_pktmbuf_copy(m, conf->mp, 0, conf->snaplen); > + /* TODO: dropped stats? */ > + if (unlikely(mc == NULL)) > + continue; > + } > + > + /* Put info about origin of the packet */ > + if (conf->flags & RTE_ETH_MIRROR_ORIGIN_FLAG) { > + struct rte_mbuf_origin *origin > + = RTE_MBUF_DYNFIELD(mc, mirror_origin_offset, rte_mbuf_origin_t *); > + origin->original_len = m->pkt_len; > + origin->port_id = port_id; > + origin->queue_id = queue_id; > + mc->ol_flags |= mirror_origin_flag; > + } > + > + /* Insert timestamp into packet */ > + if (conf->flags & RTE_ETH_MIRROR_TIMESTAMP_FLAG) { > + *RTE_MBUF_DYNFIELD(m, mbuf_timestamp_offset, rte_mbuf_timestamp_t *) > + = rte_get_tsc_cycles(); > + mc->ol_flags |= mbuf_timestamp_dynflag; > + } > + > + mc->ol_flags &= ~(mirror_ingress_flag | mirror_egress_flag); > + if (direction & RTE_ETH_MIRROR_DIRECTION_INGRESS) > + mc->ol_flags |= mirror_ingress_flag; > + else if (direction & RTE_ETH_MIRROR_DIRECTION_EGRESS) > + mc->ol_flags |= mirror_egress_flag; > + > + tosend[count++] = mc; > + } > + > + uint16_t nsent = rte_eth_tx_burst(conf->target, 0, tosend, count); > + if (unlikely(nsent < count)) { > + uint16_t drop = count - nsent; > + > + /* TODO: need some stats here? */ > + rte_pktmbuf_free_bulk(pkts + nsent, drop); > + } > +} > + > +/* This function is really internal but used from inline */ > +RTE_EXPORT_SYMBOL(rte_eth_mirror_burst) > +void > +rte_eth_mirror_burst(uint16_t port_id, uint16_t queue_id, uint8_t direction, > + struct rte_mbuf **pkts, uint16_t nb_pkts, > + const struct rte_eth_mirror *mirror) > +{ > + unsigned int i; Perhaps move this to the inside of 'while' below. > + > + while (mirror != NULL) { > + for (i = 0; i < nb_pkts; i += RTE_MIRROR_BURST_SIZE) { > + uint16_t left = nb_pkts - i; > + uint16_t burst = RTE_MIN(left, RTE_MIRROR_BURST_SIZE); > + > + eth_dev_mirror(port_id, queue_id, direction, > + pkts + i, burst, &mirror->conf); > + } > + > + mirror = rte_atomic_load_explicit(&mirror->next, rte_memory_order_relaxed); > + } > +} > + > +#else > + > +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_add_mirror, 25.11) > +int > +rte_eth_add_mirror(uint16_t port_id __rte_unused, > + const struct rte_eth_mirror_conf *conf __rte_unused) > +{ > + return -ENOTSUP; > +} > + > + > +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_remove_mirror, 25.11) > +int > +rte_eth_remove_mirror(uint16_t port_id __rte_unused, uint16_t target_id __rte_unused) > +{ > + return -ENOTSUP; > + > +} > +#endif /* RTE_ETHDEV_MIRROR */ > diff --git a/lib/ethdev/rte_mirror.h b/lib/ethdev/rte_mirror.h > new file mode 100644 > index 0000000000..9369309958 > --- /dev/null > +++ b/lib/ethdev/rte_mirror.h > @@ -0,0 +1,99 @@ > +/* SPDX-License-Identifier: BSD-3-Clause > + * Copyright(c) 2025 Stephen Hemminger > + */ > + > +#ifndef RTE_MIRROR_H_ > +#define RTE_MIRROR_H_ > + > +#include > + > +#include > +#include > + > +#ifdef __cplusplus > +extern "C" { > +#endif > + > +/** > + * @file > + * Ethdev port mirroring > + * > + * This interface provides the ability to duplicate packets to another port. > + */ > + > +/* Definitions for ethdev analyzer direction */ > +#define RTE_ETH_MIRROR_DIRECTION_INGRESS 1 > +#define RTE_ETH_MIRROR_DIRECTION_EGRESS 2 > + > +/** > + * @warning > + * @b EXPERIMENTAL: this structure may change without prior notice. > + * > + * This dynamic field is added to mbuf's when they are copied to > + * the port mirror. > + */ > +typedef struct rte_mbuf_origin { > + uint32_t original_len; /**< Packet length before copy */ > + uint16_t port_id; /**< Port where packet originated */ > + uint16_t queue_id; /**< Queue used for Tx or Rx */ > +} rte_mbuf_origin_t; > + > +/** > + * @warning > + * @b EXPERIMENTAL: this structure may change without prior notice. > + * > + * Structure used to configure ethdev Switched Port Analyzer (MIRROR) > + */ > +struct rte_eth_mirror_conf { > + struct rte_mempool *mp; /**< Memory pool for copies, If NULL then cloned. */ > + uint32_t snaplen; /**< Upper limit on number of bytes to copy */ > + uint32_t flags; /**< bitmask of RTE_ETH_MIRROR_XXX_FLAG's */ > + uint16_t target; /**< Destination port */ > + uint8_t direction; /**< bitmask of RTE_ETH_MIRROR_DIRECTION_XXX */ > +}; > + > +#define RTE_ETH_MIRROR_TIMESTAMP_FLAG 1 /**< insert timestamp into mirrored packet */ > +#define RTE_ETH_MIRROR_ORIGIN_FLAG 2 /**< insert rte_mbuf_origin into mirrored packet */ > +#define RTE_ETH_MIRROR_INDIRECT_FLAG 4 /**< use rte_mbuf_attach rather than copy */ > + > +#define RTE_ETH_MIRROR_FLAG_MASK 7 > + > +/** > + * @warning > + * @b EXPERIMENTAL: this API may change, or be removed, without prior notice > + * > + * Create a Switched Port Analyzer (MIRROR) instance. > + * > + * @param port_id > + * The port identifier of the source Ethernet device. > + * @param conf > + * Settings for this MIRROR instance.. Redundant 'period' sign at the end. > + * @return > + * Negative errno value on error, 0 on success. > + */ > +__rte_experimental > +int > +rte_eth_add_mirror(uint16_t port_id, const struct rte_eth_mirror_conf *conf); > + > +/** > + * @warning > + * @b EXPERIMENTAL: this API may change, or be removed, without prior notice > + * > + * Break port mirrorning. Could be a typo: mirror'n'ing. > + * After this call no more packets will be sent the target port. Perhaps 'to the target port'? It's up to you. Thank you. > + * > + * @param port_id > + * The port identifier of the source Ethernet device. > + * @param target_id > + * The identifier of the destination port. > + * @return > + * Negative errno value on error, 0 on success. > + */ > +__rte_experimental > +int rte_eth_remove_mirror(uint16_t port_id, uint16_t target_id); > + > +#ifdef __cplusplus > +} > +#endif > + > +#endif /* RTE_MIRROR_H_ */ > -- > 2.47.2 > >