* [PATCH v8 4/9] common/mlx5: add null MR functions
From: Thomas Monjalon @ 2026-06-04 19:30 UTC (permalink / raw)
To: dev
Cc: Stephen Hemminger, Gregory Etelson, Dariusz Sosnowski,
Viacheslav Ovsiienko, Bing Zhao, Ori Kam, Suanming Mou,
Matan Azrad
In-Reply-To: <20260604193324.1996141-1-thomas@monjalon.net>
From: Gregory Etelson <getelson@nvidia.com>
Add functions to allocate and free a null Memory Region (MR)
using ibverbs on Linux.
There is no implementation for DevX on Windows.
Signed-off-by: Gregory Etelson <getelson@nvidia.com>
Signed-off-by: Thomas Monjalon <thomas@monjalon.net>
---
drivers/common/mlx5/linux/mlx5_common_verbs.c | 35 +++++++++++++++++++
drivers/common/mlx5/mlx5_common_mr.h | 9 +++++
drivers/common/mlx5/windows/mlx5_common_os.c | 16 +++++++++
3 files changed, 60 insertions(+)
diff --git a/drivers/common/mlx5/linux/mlx5_common_verbs.c b/drivers/common/mlx5/linux/mlx5_common_verbs.c
index 2322d9d033..6d44e1f566 100644
--- a/drivers/common/mlx5/linux/mlx5_common_verbs.c
+++ b/drivers/common/mlx5/linux/mlx5_common_verbs.c
@@ -161,3 +161,38 @@ mlx5_os_set_reg_mr_cb(mlx5_reg_mr_t *reg_mr_cb, mlx5_dereg_mr_t *dereg_mr_cb)
*reg_mr_cb = mlx5_common_verbs_reg_mr;
*dereg_mr_cb = mlx5_common_verbs_dereg_mr;
}
+
+RTE_EXPORT_INTERNAL_SYMBOL(mlx5_os_alloc_null_mr)
+struct mlx5_pmd_mr *
+mlx5_os_alloc_null_mr(struct rte_device *dev, void *pd)
+{
+ struct ibv_mr *ibv_mr;
+ struct mlx5_pmd_mr *null_mr;
+
+ null_mr = mlx5_malloc(MLX5_MEM_ZERO, sizeof(*null_mr), 0, dev->numa_node);
+ if (!null_mr)
+ return NULL;
+ ibv_mr = mlx5_glue->alloc_null_mr(pd);
+ if (!ibv_mr) {
+ mlx5_free(null_mr);
+ return NULL;
+ }
+ *null_mr = (struct mlx5_pmd_mr) {
+ .lkey = rte_cpu_to_be_32(ibv_mr->lkey),
+ .addr = ibv_mr->addr,
+ .len = ibv_mr->length,
+ .obj = (void *)ibv_mr,
+ };
+ return null_mr;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(mlx5_os_free_null_mr)
+void
+mlx5_os_free_null_mr(struct mlx5_pmd_mr *null_mr)
+{
+ if (!null_mr)
+ return;
+ if (null_mr->obj)
+ claim_zero(mlx5_glue->dereg_mr(null_mr->obj));
+ mlx5_free(null_mr);
+}
diff --git a/drivers/common/mlx5/mlx5_common_mr.h b/drivers/common/mlx5/mlx5_common_mr.h
index cf7c685e9b..00f3d832c3 100644
--- a/drivers/common/mlx5/mlx5_common_mr.h
+++ b/drivers/common/mlx5/mlx5_common_mr.h
@@ -21,6 +21,8 @@
#include "mlx5_common_mp.h"
#include "mlx5_common_defs.h"
+struct rte_device;
+
/* mlx5 PMD MR struct. */
struct mlx5_pmd_mr {
uint32_t lkey;
@@ -258,6 +260,13 @@ __rte_internal
void
mlx5_os_set_reg_mr_cb(mlx5_reg_mr_t *reg_mr_cb, mlx5_dereg_mr_t *dereg_mr_cb);
+__rte_internal
+struct mlx5_pmd_mr *
+mlx5_os_alloc_null_mr(struct rte_device *dev, void *pd);
+__rte_internal
+void
+mlx5_os_free_null_mr(struct mlx5_pmd_mr *null_mr);
+
__rte_internal
int
mlx5_mr_mempool_register(struct mlx5_common_device *cdev,
diff --git a/drivers/common/mlx5/windows/mlx5_common_os.c b/drivers/common/mlx5/windows/mlx5_common_os.c
index 16fcc5f9fc..692517a9bf 100644
--- a/drivers/common/mlx5/windows/mlx5_common_os.c
+++ b/drivers/common/mlx5/windows/mlx5_common_os.c
@@ -454,6 +454,22 @@ mlx5_os_set_reg_mr_cb(mlx5_reg_mr_t *reg_mr_cb, mlx5_dereg_mr_t *dereg_mr_cb)
*dereg_mr_cb = mlx5_os_dereg_mr;
}
+RTE_EXPORT_INTERNAL_SYMBOL(mlx5_os_alloc_null_mr)
+struct mlx5_pmd_mr *
+mlx5_os_alloc_null_mr(struct rte_device *dev, void *pd)
+{
+ RTE_SET_USED(dev);
+ RTE_SET_USED(pd);
+ return NULL;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(mlx5_os_free_null_mr)
+void
+mlx5_os_free_null_mr(struct mlx5_pmd_mr *null_mr)
+{
+ RTE_SET_USED(null_mr);
+}
+
/*
* In Windows, no need to wrap the MR, no known issue for it in kernel.
* Use the regular function to create direct MR.
--
2.54.0
^ permalink raw reply related
* [PATCH v8 3/9] app/testpmd: support selective Rx
From: Thomas Monjalon @ 2026-06-04 19:30 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Gregory Etelson, Aman Singh
In-Reply-To: <20260604193324.1996141-1-thomas@monjalon.net>
From: Gregory Etelson <getelson@nvidia.com>
Add support for selective Rx using existing rxpkts and mbuf-size
command line parameters.
When a segment is specified with rxpkts and a matching 0 mbuf-size
on PMDs supporting selective Rx,
testpmd set the mempool of the segment to NULL,
meaning the segment won't be received.
Example usage to receive only Ethernet header and 64 bytes at offset 128:
--rxpkts=14,114,64,0 --mbuf-size=256,0,256,0
This creates segments:
- [0-13]: 14 bytes with mempool (received)
- [14-127]: 114 bytes with NULL mempool (discarded)
- [128-191]: 64 bytes with mempool (received)
- [192-max]: remaining bytes with NULL mempool (discarded)
Note: RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT is required for this feature
and is checked at ethdev API level.
This check is removed from testpmd to allow negative testing of the API.
Signed-off-by: Gregory Etelson <getelson@nvidia.com>
Signed-off-by: Thomas Monjalon <thomas@monjalon.net>
---
app/test-pmd/parameters.c | 5 ++-
app/test-pmd/testpmd.c | 34 +++++++++++++--------
doc/guides/testpmd_app_ug/run_app.rst | 16 ++++++++++
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 3 +-
4 files changed, 42 insertions(+), 16 deletions(-)
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c
index ecbd618f00..337d8fc8ac 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -1170,10 +1170,9 @@ launch_args_parse(int argc, char** argv)
rte_exit(EXIT_FAILURE,
"bad mbuf-size\n");
for (i = 0; i < nb_segs; i++) {
- if (mb_sz[i] <= 0 || mb_sz[i] > 0xFFFF)
+ if (mb_sz[i] > 0xFFFF)
rte_exit(EXIT_FAILURE,
- "mbuf-size should be "
- "> 0 and < 65536\n");
+ "mbuf-size should be < 65536\n");
mbuf_data_size[i] = (uint16_t) mb_sz[i];
}
mbuf_data_size_n = nb_segs;
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index a9b35f530a..7d29a0117c 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -1806,19 +1806,25 @@ init_config(void)
uint8_t i, j;
for (i = 0; i < num_sockets; i++)
- for (j = 0; j < mbuf_data_size_n; j++)
+ for (j = 0; j < mbuf_data_size_n; j++) {
+ if (mbuf_data_size[j] == 0)
+ continue;
mempools[i * MAX_SEGS_BUFFER_SPLIT + j] =
mbuf_pool_create(mbuf_data_size[j],
nb_mbuf_per_pool,
socket_ids[i], j);
+ }
} else {
uint8_t i;
- for (i = 0; i < mbuf_data_size_n; i++)
+ for (i = 0; i < mbuf_data_size_n; i++) {
+ if (mbuf_data_size[i] == 0)
+ continue;
mempools[i] = mbuf_pool_create
(mbuf_data_size[i],
nb_mbuf_per_pool,
SOCKET_ID_ANY, i);
+ }
}
init_port_config();
@@ -2744,31 +2750,35 @@ rx_queue_setup(uint16_t port_id, uint16_t rx_queue_id,
uint32_t prev_hdrs = 0;
int ret;
- if ((rx_pkt_nb_segs > 1) &&
- (rx_conf->offloads & RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT)) {
+ if (multi_rx_mempool == 0 &&
+ (rx_pkt_nb_segs > 1 || mbuf_data_size_n > 1)) {
+ unsigned int nb_segs = RTE_MAX(rx_pkt_nb_segs, (uint8_t)mbuf_data_size_n);
+
/* multi-segment configuration */
- for (i = 0; i < rx_pkt_nb_segs; i++) {
+ for (i = 0; i < nb_segs; i++) {
struct rte_eth_rxseg_split *rx_seg = &rx_useg[i].split;
/*
* Use last valid pool for the segments with number
* exceeding the pool index.
*/
mp_n = (i >= mbuf_data_size_n) ? mbuf_data_size_n - 1 : i;
- mpx = mbuf_pool_find(socket_id, mp_n);
- /* Handle zero as mbuf data buffer size. */
rx_seg->offset = i < rx_pkt_nb_offs ?
rx_pkt_seg_offsets[i] : 0;
- rx_seg->mp = mpx ? mpx : mp;
+ if (mbuf_data_size[mp_n] == 0) {
+ rx_seg->mp = NULL;
+ } else {
+ mpx = mbuf_pool_find(socket_id, mp_n);
+ rx_seg->mp = mpx ? mpx : mp;
+ }
if (rx_pkt_hdr_protos[i] != 0 && rx_pkt_seg_lengths[i] == 0) {
rx_seg->proto_hdr = rx_pkt_hdr_protos[i] & ~prev_hdrs;
prev_hdrs |= rx_seg->proto_hdr;
} else {
- rx_seg->length = rx_pkt_seg_lengths[i] ?
- rx_pkt_seg_lengths[i] :
- mbuf_data_size[mp_n];
+ rx_seg->length = i < rx_pkt_nb_segs ?
+ rx_pkt_seg_lengths[i] : 0;
}
}
- rx_conf->rx_nseg = rx_pkt_nb_segs;
+ rx_conf->rx_nseg = nb_segs;
rx_conf->rx_seg = rx_useg;
rx_conf->rx_mempools = NULL;
rx_conf->rx_nmempool = 0;
diff --git a/doc/guides/testpmd_app_ug/run_app.rst b/doc/guides/testpmd_app_ug/run_app.rst
index 1a4a4b6c12..d654484546 100644
--- a/doc/guides/testpmd_app_ug/run_app.rst
+++ b/doc/guides/testpmd_app_ug/run_app.rst
@@ -127,6 +127,7 @@ The command line options are:
The default value is 2048. If multiple mbuf-size values are specified the
extra memory pools will be created for allocating mbufs to receive packets
with buffer splitting features.
+ A value of 0 indicates a discarded segment in buffer split.
* ``--total-num-mbufs=N``
@@ -372,6 +373,21 @@ The command line options are:
Optionally the multiple memory pools can be specified with --mbuf-size
command line parameter and the mbufs to receive will be allocated
sequentially from these extra memory pools.
+ A length of 0 means maximum length: rest of the segment
+ or all remaining packet data in case of a discard segment.
+
+ To receive only the Ethernet header (14 bytes)
+ and a 64-byte segment starting at offset 128,
+ while discarding the rest::
+
+ --rxpkts=14,114,64,0 --mbuf-size=256,0,256,0
+
+ This configuration will:
+
+ * Receive 14 bytes (Ethernet header)
+ * Discard 114 bytes (NULL mempool segment)
+ * Receive 64 bytes
+ * Discard remaining bytes (NULL mempool segment, length=0)
* ``--txpkts=X[,Y]``
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index d50921258a..f0f2b0758b 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -850,7 +850,8 @@ mbuf for remaining segments will be allocated from the last valid pool).
testpmd> set rxpkts (x[,y]*)
Where x[,y]* represents a CSV list of values, without white space. Zero value
-means to use the corresponding memory pool data buffer size.
+means to use the corresponding memory pool data buffer size,
+or to discard all remaining packet data for a discard segment (mbuf-size=0).
set rxhdrs
~~~~~~~~~~
--
2.54.0
^ permalink raw reply related
* [PATCH v8 2/9] ethdev: introduce selective Rx
From: Thomas Monjalon @ 2026-06-04 19:30 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Gregory Etelson, Andrew Rybchenko, Aman Singh
In-Reply-To: <20260604193324.1996141-1-thomas@monjalon.net>
From: Gregory Etelson <getelson@nvidia.com>
Receiving an entire packet is not always needed.
The Rx performance can be improved by receiving only partial data
and safely discard the rest of the packet data,
because it reduces the PCI bandwidth and the memory consumption.
Selective Rx allows an application to receive
only pre-configured packet segments and discard the rest.
For example:
- Deliver the first N bytes only.
- Deliver the last N bytes only.
- Deliver N1 bytes from offset Off1 and N2 bytes from offset Off2.
Selective Rx is implemented on top of the Rx buffer split API:
- rte_eth_rxseg_split uses the null mempool for segments
that should be discarded.
- the PMD does not create mbuf segments if no data read.
For example: Deliver Ethernet header only
Rx queue segments configuration:
struct rte_eth_rxseg_split split[2] = {
{
.mp = <some mempool>,
.length = sizeof(struct rte_ether_hdr)
},
{
.mp = NULL, /* discard data */
.length = 0 /* default to buffer size */
}
};
Received mbuf:
pkt_len = sizeof(struct rte_ether_hdr);
data_len = sizeof(struct rte_ether_hdr);
next = NULL; /* The next segment did not deliver data */
After selective Rx, the mbuf packet length reflects only the data
that was actually received,
and can be less than the original wire packet length.
A PMD activates the selective Rx capability by setting
the rte_eth_rxseg_capa.selective_rx bit.
This new capability bit is inserted in a bitmap hole
of the struct rte_eth_rxseg_capa,
but it needs to be ignored in the ABI check as libabigail sees a change.
Signed-off-by: Gregory Etelson <getelson@nvidia.com>
Signed-off-by: Thomas Monjalon <thomas@monjalon.net>
Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
---
app/test-pmd/config.c | 1 +
devtools/libabigail.abignore | 7 +++++++
doc/guides/nics/features.rst | 14 ++++++++++++++
doc/guides/nics/features/default.ini | 1 +
doc/guides/rel_notes/release_26_07.rst | 7 +++++++
lib/ethdev/rte_ethdev.c | 24 ++++++++++++++++--------
lib/ethdev/rte_ethdev.h | 17 +++++++++++++++--
7 files changed, 61 insertions(+), 10 deletions(-)
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 55d1c6d696..9d457ca88e 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -925,6 +925,7 @@ port_infos_display(portid_t port_id)
print_bool_capa("\tBuffer offset", dev_info.rx_seg_capa.offset_allowed);
printf("\tOffset alignment: %u\n",
RTE_BIT32(dev_info.rx_seg_capa.offset_align_log2));
+ print_bool_capa("\tSelective Rx", dev_info.rx_seg_capa.selective_rx);
}
if (dev_info.max_vfs)
diff --git a/devtools/libabigail.abignore b/devtools/libabigail.abignore
index 21b8cd6113..2a0efd718e 100644
--- a/devtools/libabigail.abignore
+++ b/devtools/libabigail.abignore
@@ -33,3 +33,10 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Temporary exceptions till next major ABI version ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+; Ignore new bit selective_rx in rte_eth_rxseg_capa bitmap hole
+[suppress_type]
+ name = rte_eth_rxseg_capa
+ type_kind = struct
+ has_size_change = no
+ has_data_member_inserted_at = 6
diff --git a/doc/guides/nics/features.rst b/doc/guides/nics/features.rst
index a075c057ec..26357036ca 100644
--- a/doc/guides/nics/features.rst
+++ b/doc/guides/nics/features.rst
@@ -199,6 +199,20 @@ Scatters the packets being received on specified boundaries to segmented mbufs.
* **[related] API**: ``rte_eth_rx_queue_setup()``, ``rte_eth_buffer_split_get_supported_hdr_ptypes()``.
+.. _nic_features_selective_rx:
+
+Selective Rx
+------------
+
+Discards some segments of buffer split on Rx.
+
+* **[uses] rte_eth_rxconf,rte_eth_rxmode**: ``offloads:RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT``.
+* **[uses] rte_eth_rxconf**: ``rx_seg.mp = NULL`` to discard segments.
+* **[provides] rte_eth_dev_info**: ``rx_offload_capa:RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT``.
+* **[provides] rte_eth_dev_info**: ``rx_seg_capa.selective_rx``.
+* **[related] API**: ``rte_eth_rx_queue_setup()``.
+
+
.. _nic_features_lro:
LRO
diff --git a/doc/guides/nics/features/default.ini b/doc/guides/nics/features/default.ini
index e50514d750..8303a530c1 100644
--- a/doc/guides/nics/features/default.ini
+++ b/doc/guides/nics/features/default.ini
@@ -25,6 +25,7 @@ Burst mode info =
Power mgmt address monitor =
MTU update =
Buffer split on Rx =
+Selective Rx =
Scattered Rx =
LRO =
TSO =
diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index d2563ac503..46a8fe2cc1 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -87,6 +87,13 @@ New Features
Added no-IOMMU mode for devices without or not enabling IOMMU/SVA.
+* **Added selective Rx in ethdev API.**
+
+ Some parts of packets may be discarded in Rx
+ by configuring a split of packets received in a queue,
+ and assigning no mempool to some configuration segments.
+ This is a driver capability advertised in the ``selective_rx`` bit.
+
* **Added LinkData sxe2 ethernet driver.**
Added network driver for the LinkData network adapters.
diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c
index d0273e3f7b..67d609820c 100644
--- a/lib/ethdev/rte_ethdev.c
+++ b/lib/ethdev/rte_ethdev.c
@@ -2129,7 +2129,7 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
const struct rte_eth_dev_info *dev_info)
{
const struct rte_eth_rxseg_capa *seg_capa = &dev_info->rx_seg_capa;
- struct rte_mempool *mp_first;
+ struct rte_mempool *mp_first = NULL;
uint32_t offset_mask;
uint16_t seg_idx;
int ret = 0;
@@ -2148,7 +2148,6 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
* Check the sizes and offsets against buffer sizes
* for each segment specified in extended configuration.
*/
- mp_first = rx_seg[0].mp;
offset_mask = RTE_BIT32(seg_capa->offset_align_log2) - 1;
ptypes = NULL;
@@ -2160,13 +2159,17 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
uint32_t offset = rx_seg[seg_idx].offset;
uint32_t proto_hdr = rx_seg[seg_idx].proto_hdr;
- if (mpl == NULL) {
- RTE_ETHDEV_LOG_LINE(ERR, "null mempool pointer");
- ret = -EINVAL;
- goto out;
+ if (mpl == NULL) { /* discarded segment */
+ if (seg_capa->selective_rx == 0) { /* not supported */
+ RTE_ETHDEV_LOG_LINE(ERR, "null mempool pointer");
+ ret = -EINVAL;
+ goto out;
+ }
+ continue; /* next checks are not relevant if no mempool */
}
- if (seg_idx != 0 && mp_first != mpl &&
- seg_capa->multi_pools == 0) {
+ if (mp_first == NULL)
+ mp_first = mpl;
+ if (mp_first != mpl && seg_capa->multi_pools == 0) {
RTE_ETHDEV_LOG_LINE(ERR, "Receiving to multiple pools is not supported");
ret = -ENOTSUP;
goto out;
@@ -2233,6 +2236,11 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
if (ret != 0)
goto out;
}
+ if (mp_first == NULL) {
+ RTE_ETHDEV_LOG_LINE(ERR, "At least one Rx segment must have a mempool");
+ ret = -EINVAL;
+ goto out;
+ }
out:
free(ptypes);
return ret;
diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h
index dedbc05554..ee400b386f 100644
--- a/lib/ethdev/rte_ethdev.h
+++ b/lib/ethdev/rte_ethdev.h
@@ -1073,6 +1073,7 @@ struct rte_eth_txmode {
* - The first network buffer will be allocated from the memory pool,
* specified in the first array element, the second buffer, from the
* pool in the second element, and so on.
+ * If the pool is NULL, the segment will be discarded, i.e. not received.
*
* - The proto_hdrs in the elements define the split position of
* received packets.
@@ -1090,7 +1091,8 @@ struct rte_eth_txmode {
*
* - If the length in the segment description element is zero
* the actual buffer size will be deduced from the appropriate
- * memory pool properties.
+ * memory pool properties, or from the remaining packet length
+ * in case of no memory pool to discard the end of the packet.
*
* - If there is not enough elements to describe the buffer for entire
* packet of maximal length the following parameters will be used
@@ -1121,7 +1123,15 @@ struct rte_eth_txmode {
* The rest will be put into the last valid pool.
*/
struct rte_eth_rxseg_split {
- struct rte_mempool *mp; /**< Memory pool to allocate segment from. */
+ /**
+ * Memory pool to allocate segment from.
+ *
+ * NULL means discarded segment.
+ * Length of discarded segment is not reflected in mbuf packet length
+ * and not accounted in ibytes statistics.
+ * @see rte_eth_rxseg_capa::selective_rx
+ */
+ struct rte_mempool *mp;
uint16_t length; /**< Segment data length, configures split point. */
uint16_t offset; /**< Data offset from beginning of mbuf data buffer. */
/**
@@ -1752,12 +1762,15 @@ struct rte_eth_switch_info {
* @b EXPERIMENTAL: this structure may change without prior notice.
*
* Ethernet device Rx buffer segmentation capabilities.
+ *
+ * @see rte_eth_rxseg_split
*/
struct rte_eth_rxseg_capa {
__extension__
uint32_t multi_pools:1; /**< Supports receiving to multiple pools.*/
uint32_t offset_allowed:1; /**< Supports buffer offsets. */
uint32_t offset_align_log2:4; /**< Required offset alignment. */
+ uint32_t selective_rx:1; /**< Supports discarding segment. */
uint16_t max_nseg; /**< Maximum amount of segments to split. */
uint16_t reserved; /**< Reserved field. */
};
--
2.54.0
^ permalink raw reply related
* [PATCH v8 1/9] app/testpmd: print Rx split capabilities
From: Thomas Monjalon @ 2026-06-04 19:30 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Aman Singh
In-Reply-To: <20260604193324.1996141-1-thomas@monjalon.net>
The capabilities from rte_eth_rxseg_capa are added
to the command "show port info".
Signed-off-by: Thomas Monjalon <thomas@monjalon.net>
---
app/test-pmd/config.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index c950793aaf..55d1c6d696 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -790,6 +790,12 @@ rss_offload_types_display(uint64_t offload_types, uint16_t char_num_per_line)
printf("\n");
}
+static void
+print_bool_capa(const char *label, int value)
+{
+ printf("%s: %s\n", label, value ? "supported" : "not supported");
+}
+
void
port_infos_display(portid_t port_id)
{
@@ -911,6 +917,16 @@ port_infos_display(portid_t port_id)
dev_info.max_rx_pktlen);
printf("Maximum configurable size of LRO aggregated packet: %u\n",
dev_info.max_lro_pkt_size);
+
+ printf("Rx split:\n");
+ printf("\tMax segments: %hu\n", dev_info.rx_seg_capa.max_nseg);
+ if (dev_info.rx_seg_capa.max_nseg > 0) {
+ print_bool_capa("\tMulti-pool", dev_info.rx_seg_capa.multi_pools);
+ print_bool_capa("\tBuffer offset", dev_info.rx_seg_capa.offset_allowed);
+ printf("\tOffset alignment: %u\n",
+ RTE_BIT32(dev_info.rx_seg_capa.offset_align_log2));
+ }
+
if (dev_info.max_vfs)
printf("Maximum number of VFs: %u\n", dev_info.max_vfs);
if (dev_info.max_vmdq_pools)
--
2.54.0
^ permalink raw reply related
* [PATCH v8 0/9] selective Rx
From: Thomas Monjalon @ 2026-06-04 19:30 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260202160903.254621-1-getelson@nvidia.com>
This is a new feature in ethdev with tests and mlx5 implementation.
Selective Rx allows to receive partial data,
saving some hardware bandwidth.
v2: rework after Gregory
v3: fix bugs found with AI by Stephen
v4: fix packet type in DTS test
v5: fix mlx5 Rx to handle discarding first segment
v6: fix reindent patch
v7: fix mlx5 CQE error handling + outdated mcqe + redundant assignment
v8: use --mbuf-size 0 in testpmd instead of changing --rxoffs behaviour
Gregory Etelson (4):
ethdev: introduce selective Rx
app/testpmd: support selective Rx
common/mlx5: add null MR functions
net/mlx5: support selective Rx
Thomas Monjalon (5):
app/testpmd: print Rx split capabilities
net/mlx5: fix Rx split segment counter type
common/mlx5: remove callbacks for MR registration
dts: fix topology capability comparison
dts: add selective Rx tests
app/test-pmd/config.c | 17 ++
app/test-pmd/parameters.c | 5 +-
app/test-pmd/testpmd.c | 34 ++-
devtools/libabigail.abignore | 7 +
doc/guides/nics/features.rst | 14 +
doc/guides/nics/features/default.ini | 1 +
doc/guides/nics/features/mlx5.ini | 1 +
doc/guides/nics/mlx5.rst | 86 ++++--
doc/guides/rel_notes/release_26_07.rst | 11 +
doc/guides/testpmd_app_ug/run_app.rst | 16 ++
doc/guides/testpmd_app_ug/testpmd_funcs.rst | 3 +-
drivers/common/mlx5/linux/mlx5_common_verbs.c | 53 ++--
drivers/common/mlx5/mlx5_common.c | 6 +-
drivers/common/mlx5/mlx5_common_mr.c | 37 ++-
drivers/common/mlx5/mlx5_common_mr.h | 29 +-
drivers/common/mlx5/windows/mlx5_common_os.c | 31 +-
drivers/compress/mlx5/mlx5_compress.c | 4 +-
drivers/crypto/mlx5/mlx5_crypto.h | 2 -
drivers/crypto/mlx5/mlx5_crypto_gcm.c | 6 +-
drivers/net/mlx5/mlx5.c | 7 +
drivers/net/mlx5/mlx5.h | 4 +-
drivers/net/mlx5/mlx5_ethdev.c | 25 ++
drivers/net/mlx5/mlx5_flow_aso.c | 21 +-
drivers/net/mlx5/mlx5_flow_hw.c | 11 +-
drivers/net/mlx5/mlx5_flow_quota.c | 6 +-
drivers/net/mlx5/mlx5_hws_cnt.c | 19 +-
drivers/net/mlx5/mlx5_rx.c | 187 +++++++-----
drivers/net/mlx5/mlx5_rx.h | 5 +-
drivers/net/mlx5/mlx5_rxq.c | 95 +++++--
drivers/net/mlx5/mlx5_trigger.c | 64 ++++-
dts/api/capabilities.py | 2 +
dts/api/testpmd/__init__.py | 17 ++
dts/api/testpmd/types.py | 6 +
dts/framework/testbed_model/capability.py | 10 +-
dts/tests/TestSuite_rx_split.py | 265 ++++++++++++++++++
lib/ethdev/rte_ethdev.c | 24 +-
lib/ethdev/rte_ethdev.h | 17 +-
37 files changed, 859 insertions(+), 289 deletions(-)
create mode 100644 dts/tests/TestSuite_rx_split.py
--
2.54.0
^ permalink raw reply
* Re: [PATCH v1] dts: report dut/NIC info during DTS run
From: Andrew Bailey @ 2026-06-04 18:33 UTC (permalink / raw)
To: Koushik Bhargav Nimoji
Cc: luca.vizzarro, patrickrobb1997, dev, ahassick, lylavoie
In-Reply-To: <20260602163647.101815-1-knimoji@iol.unh.edu>
[-- Attachment #1: Type: text/plain, Size: 40 bytes --]
Recheck-request: iol-unit-arm64-testing
[-- Attachment #2: Type: text/html, Size: 61 bytes --]
^ permalink raw reply
* Re: [PATCH v1 2/2] dts: add build arguments to test run configuration
From: Andrew Bailey @ 2026-06-04 18:23 UTC (permalink / raw)
To: Koushik Bhargav Nimoji
Cc: luca.vizzarro, patrickrobb1997, dev, ahassick, lylavoie
In-Reply-To: <20260522154637.952588-2-knimoji@iol.unh.edu>
[-- Attachment #1: Type: text/plain, Size: 40 bytes --]
Recheck-request: iol-unit-arm64-testing
[-- Attachment #2: Type: text/html, Size: 61 bytes --]
^ permalink raw reply
* Re: [PATCH 4/4] net/bnxt: fix RSS hash mode configuration for VFs
From: Kishore Padmanabha @ 2026-06-04 17:42 UTC (permalink / raw)
To: Mohammad Shuab Siddique; +Cc: dev, stable
In-Reply-To: <20260603175137.1990204-5-Mohammad-Shuab.Siddique@broadcom.com>
[-- Attachment #1.1: Type: text/plain, Size: 2317 bytes --]
On Wed, Jun 3, 2026 at 1:50 PM Mohammad Shuab Siddique <
mohammad-shuab.siddique@broadcom.com> wrote:
> From: Mohammad Shuab Siddique <mohammad-shuab.siddique@broadcom.com>
>
> Fixed VFs attempting global RSS configuration which is not
> permitted by firmware. VFs (including trusted VFs) must use
> per-VNIC RSS configuration with actual vnic_id and rss_ctx_idx
> values.
>
> Cc: stable@dpdk.org
> Signed-off-by: Mohammad Shuab Siddique <
> mohammad-shuab.siddique@broadcom.com>
>
Acked-by: Kishore Padmanabha <kishore.padmanabha@broadcom.com>
> ---
> drivers/net/bnxt/bnxt_hwrm.c | 18 ++++++++++++++++--
> 1 file changed, 16 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/bnxt/bnxt_hwrm.c b/drivers/net/bnxt/bnxt_hwrm.c
> index 0c82935de9..afc948ac29 100644
> --- a/drivers/net/bnxt/bnxt_hwrm.c
> +++ b/drivers/net/bnxt/bnxt_hwrm.c
> @@ -2970,8 +2970,22 @@ bnxt_hwrm_vnic_rss_cfg_hash_mode_p5(struct bnxt
> *bp, struct bnxt_vnic_info *vnic
> req.hash_mode_flags = BNXT_HASH_MODE_INNERMOST;
> else
> req.hash_mode_flags = vnic->hash_mode;
> - req.vnic_id = rte_cpu_to_le_16(BNXT_DFLT_VNIC_ID_INVALID);
> - req.rss_ctx_idx = rte_cpu_to_le_16(BNXT_RSS_CTX_IDX_INVALID);
> +
> + /* VFs must use actual vnic_id for per-VNIC configuration.
> + * PFs can use INVALID vnic_id for global configuration.
> + * This is because VFs don't have permission to configure
> + * global hash mode, even if they're trusted.
> + */
> + if (BNXT_VF(bp)) {
> + req.vnic_id = rte_cpu_to_le_16(vnic->fw_vnic_id);
> + req.rss_ctx_idx = rte_cpu_to_le_16(vnic->fw_grp_ids[0]);
> + PMD_DRV_LOG_LINE(DEBUG, "VF using per-VNIC RSS config
> (vnic_id=%u)",
> + vnic->fw_vnic_id);
> + } else {
> + req.vnic_id = rte_cpu_to_le_16(BNXT_DFLT_VNIC_ID_INVALID);
> + req.rss_ctx_idx =
> rte_cpu_to_le_16(BNXT_RSS_CTX_IDX_INVALID);
> + PMD_DRV_LOG_LINE(DEBUG, "PF using global RSS config");
> + }
>
> PMD_DRV_LOG_LINE(DEBUG, "RSS CFG: Hash level %d",
> req.hash_mode_flags);
> rc = bnxt_hwrm_send_message(bp, &req, sizeof(req),
> --
> 2.47.3
>
>
[-- Attachment #1.2: Type: text/html, Size: 3679 bytes --]
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5493 bytes --]
^ permalink raw reply
* Re: [PATCH 3/4] net/bnxt: remove implicit integer sign-extension
From: Kishore Padmanabha @ 2026-06-04 17:41 UTC (permalink / raw)
To: Mohammad Shuab Siddique; +Cc: dev, stable, Zoe Cheimets
In-Reply-To: <20260603175137.1990204-4-Mohammad-Shuab.Siddique@broadcom.com>
[-- Attachment #1.1: Type: text/plain, Size: 1941 bytes --]
On Wed, Jun 3, 2026 at 1:50 PM Mohammad Shuab Siddique <
mohammad-shuab.siddique@broadcom.com> wrote:
> From: Zoe Cheimets <zoe.cheimets@broadcom.com>
>
> In bnxt_ring.c, the result on line 389 was auto-sign extended by
> the compiler because the arithmetic result is an int, but the
> dpi_offset is uint64_t. Fix by casting the result to uint64_t
> before the multiplication forces extension. To ensure that a
> negative integer is not being cast to uint64_t, add a check in
> the if-statement.
>
> Fixes: 7a1f9c782b50 ("net/bnxt: add multi-doorbell support")
> Cc: stable@dpdk.org
> Signed-off-by: Zoe Cheimets <zoe.cheimets@broadcom.com>
> Signed-off-by: Mohammad Shuab Siddique <
> mohammad-shuab.siddique@broadcom.com>
>
Acked-by: Kishore Padmanabha <kishore.padmanabha@broadcom.com>
> ---
> drivers/net/bnxt/bnxt_ring.c | 7 ++++---
> 1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/net/bnxt/bnxt_ring.c b/drivers/net/bnxt/bnxt_ring.c
> index ccca779b97..579b73d2ce 100644
> --- a/drivers/net/bnxt/bnxt_ring.c
> +++ b/drivers/net/bnxt/bnxt_ring.c
> @@ -385,9 +385,10 @@ void bnxt_set_db(struct bnxt *bp,
> db->doorbell = (char *)bp->doorbell_base + db_offset;
>
> if (bp->fw_cap & BNXT_FW_CAP_MULTI_DB &&
> - dpi != BNXT_PRIVILEGED_DPI) {
> - dpi_offset = (dpi - bp->nq_dpi_start) *
> - bp->db_page_size;
> + dpi != BNXT_PRIVILEGED_DPI &&
> + dpi >= bp->nq_dpi_start) {
> + dpi_offset = (uint64_t)(dpi - bp->nq_dpi_start) *
> + bp->db_page_size;
> db->doorbell = (char *)db->doorbell + dpi_offset;
> }
> db->db_key64 |= (uint64_t)fid << DBR_XID_SFT;
> --
> 2.47.3
>
>
[-- Attachment #1.2: Type: text/html, Size: 3397 bytes --]
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5493 bytes --]
^ permalink raw reply
* Re: [PATCH 2/4] net/bnxt: fix QP resource count in backing store config
From: Kishore Padmanabha @ 2026-06-04 17:41 UTC (permalink / raw)
To: Mohammad Shuab Siddique; +Cc: dev, stable, Ajit Khaparde
In-Reply-To: <20260603175137.1990204-3-Mohammad-Shuab.Siddique@broadcom.com>
[-- Attachment #1.1: Type: text/plain, Size: 2220 bytes --]
On Wed, Jun 3, 2026 at 1:49 PM Mohammad Shuab Siddique <
mohammad-shuab.siddique@broadcom.com> wrote:
> From: Ajit Khaparde <ajit.khaparde@broadcom.com>
>
> The driver is not passing the QP1 count while
> configuring backing store for QP type.
> This can result in a smaller set of entries allocated by the
> driver with the firmware.
>
> The number of entries is provided by the firmware as a
> part of backing store qcaps v2 HWRM command.
>
> Fixes: 5b5d398434f9 ("net/bnxt: add support for backing store v2")
> Cc: stable@dpdk.org
> Signed-off-by: Ajit Khaparde <ajit.khaparde@broadcom.com>
> Signed-off-by: Mohammad Shuab Siddique <
> mohammad-shuab.siddique@broadcom.com>
>
Acked-by: Kishore Padmanabha <kishore.padmanabha@broadcom.com>
> ---
> drivers/net/bnxt/bnxt_ethdev.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/bnxt/bnxt_ethdev.c
> b/drivers/net/bnxt/bnxt_ethdev.c
> index b677f9491d..7d70d0f3ec 100644
> --- a/drivers/net/bnxt/bnxt_ethdev.c
> +++ b/drivers/net/bnxt/bnxt_ethdev.c
> @@ -5404,11 +5404,11 @@ int bnxt_alloc_ctx_pg_tbls(struct bnxt *bp)
> if (ctxm->type ==
> HWRM_FUNC_BACKING_STORE_CFG_V2_INPUT_TYPE_CQ)
> entries = ctxm->cq_l2_entries;
> else if (ctxm->type ==
> HWRM_FUNC_BACKING_STORE_CFG_V2_INPUT_TYPE_QP)
> - entries = ctxm->qp_l2_entries;
> + entries = ctxm->qp_l2_entries +
> ctxm->qp_qp1_entries;
> else if (ctxm->type ==
> HWRM_FUNC_BACKING_STORE_CFG_V2_INPUT_TYPE_MRAV)
> entries = ctxm->mrav_av_entries;
> else if (ctxm->type ==
> HWRM_FUNC_BACKING_STORE_CFG_V2_INPUT_TYPE_TIM)
> - entries = ctx2->qp_l2_entries;
> + entries = ctx2->qp_l2_entries +
> ctx2->qp_qp1_entries;
> entries = clamp_t(uint32_t, entries,
> ctxm->min_entries,
> ctxm->max_entries);
> ctx_pg[i].entries = entries;
> --
> 2.47.3
>
>
[-- Attachment #1.2: Type: text/html, Size: 3673 bytes --]
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5493 bytes --]
^ permalink raw reply
* Re: [PATCH 1/4] net/bnxt: modify check for short Tx BDs
From: Kishore Padmanabha @ 2026-06-04 17:36 UTC (permalink / raw)
To: Mohammad Shuab Siddique; +Cc: dev, stable, Ajit Khaparde
In-Reply-To: <20260603175137.1990204-2-Mohammad-Shuab.Siddique@broadcom.com>
[-- Attachment #1.1: Type: text/plain, Size: 1718 bytes --]
On Wed, Jun 3, 2026 at 1:49 PM Mohammad Shuab Siddique <
mohammad-shuab.siddique@broadcom.com> wrote:
> From: Ajit Khaparde <ajit.khaparde@broadcom.com>
>
> There is no need to use the long BDs for transmits
> where only checksum offload is needed.
> Modify the check for long BD and use long BDs only in cases
> where TSO and other offloads are requested.
>
> Fixes: 527b10089cc5 ("net/bnxt: optimize Tx completion handling")
> Cc: stable@dpdk.org
>
> Signed-off-by: Ajit Khaparde <ajit.khaparde@broadcom.com>
> Signed-off-by: Mohammad Shuab Siddique <
> mohammad-shuab.siddique@broadcom.com>
>
Acked-by: Kishore Padmanabha <kishore.padmanabha@broadcom.com>
> ---
> drivers/net/bnxt/bnxt_txr.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/drivers/net/bnxt/bnxt_txr.c b/drivers/net/bnxt/bnxt_txr.c
> index 27758898b0..7ef5b15ae8 100644
> --- a/drivers/net/bnxt/bnxt_txr.c
> +++ b/drivers/net/bnxt/bnxt_txr.c
> @@ -111,8 +111,7 @@ int bnxt_init_tx_ring_struct(struct bnxt_tx_queue
> *txq, unsigned int socket_id)
> static bool
> bnxt_xmit_need_long_bd(struct rte_mbuf *tx_pkt, struct bnxt_tx_queue *txq)
> {
> - if (tx_pkt->ol_flags & (RTE_MBUF_F_TX_TCP_SEG |
> RTE_MBUF_F_TX_TCP_CKSUM |
> - RTE_MBUF_F_TX_UDP_CKSUM |
> RTE_MBUF_F_TX_IP_CKSUM |
> + if (tx_pkt->ol_flags & (RTE_MBUF_F_TX_TCP_SEG |
> RTE_MBUF_F_TX_VLAN |
> RTE_MBUF_F_TX_OUTER_IP_CKSUM |
> RTE_MBUF_F_TX_TUNNEL_GRE |
> RTE_MBUF_F_TX_TUNNEL_VXLAN |
> RTE_MBUF_F_TX_TUNNEL_GENEVE |
> RTE_MBUF_F_TX_IEEE1588_TMST |
> --
> 2.47.3
>
>
[-- Attachment #1.2: Type: text/html, Size: 3023 bytes --]
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5493 bytes --]
^ permalink raw reply
* Re: [PATCH v2] net/ark: use standard IPv4 address parser
From: Stephen Hemminger @ 2026-06-04 16:55 UTC (permalink / raw)
To: Denis Sergeev; +Cc: dev, shepard.siegel, ed.czeck, john.miller, stable
In-Reply-To: <20260604034837.250221-1-denserg.edu@gmail.com>
On Thu, 4 Jun 2026 06:48:37 +0300
Denis Sergeev <denserg.edu@gmail.com> wrote:
> The IPv4 parsing helper used by pktgen and pktchkr read each octet with
> "%u", which accepts values above 255 from the configuration file and
> encodes them into unintended device register values.
>
> Replace the hand-rolled parser in both modules with inet_pton(), which
> validates the dotted-quad format and the octet range, and matches the
> IPv4 parsing already used by other DPDK drivers. For valid input the
> returned value is byte-order identical to the previous helper, so the
> register contents are unchanged.
>
> Fixes: 9c7188a68d7b ("net/ark: provide API for hardware modules pktchkr and pktgen")
> Cc: stable@dpdk.org
>
> Signed-off-by: Denis Sergeev <denserg.edu@gmail.com>
> ---
I think you need an additional header.
FAILED: drivers/libtmp_rte_net_ark.a.p/net_ark_ark_pktchkr.c.o
cc -Idrivers/libtmp_rte_net_ark.a.p -Idrivers -I../drivers -Idrivers/net/ark -I../drivers/net/ark -Ilib/ethdev -I../lib/ethdev -Ilib/eal/common -I../lib/eal/common -I. -I.. -Iconfig -I../config -Ilib/eal/include -I../lib/eal/include -Ilib/eal/freebsd/include -I../lib/eal/freebsd/include -Ilib/eal/x86/include -I../lib/eal/x86/include -Ilib/eal -I../lib/eal -Ilib/kvargs -I../lib/kvargs -Ilib/log -I../lib/log -Ilib/metrics -I../lib/metrics -Ilib/telemetry -I../lib/telemetry -Ilib/argparse -I../lib/argparse -Ilib/net -I../lib/net -Ilib/mbuf -I../lib/mbuf -Ilib/mempool -I../lib/mempool -Ilib/ring -I../lib/ring -Ilib/meter -I../lib/meter -Idrivers/bus/pci -I../drivers/bus/pci -I../drivers/bus/pci/bsd -Ilib/pci -I../lib/pci -Idrivers/bus/vdev -I../drivers/bus/vdev -Xclang -fcolor-diagnostics -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -Werror -std=c11 -O3 -include rte_config.h -Wvla -Wcast-qual -Wcomma -Wdeprecated -Wformat -Wformat-nonliteral -Wformat-security -Wmissing-declar
ations -Wmissing-prototypes -Wnested-externs -Wold-style-definition -Wpointer-arith -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wwrite-strings -Wno-missing-field-initializers -D_GNU_SOURCE -D__BSD_VISIBLE -fPIC -march=native -mrtm -DALLOW_EXPERIMENTAL_API -DALLOW_INTERNAL_API -Wno-format-truncation -Wno-address-of-packed-member -DRTE_LOG_DEFAULT_LOGTYPE=pmd.net.ark -DRTE_ANNOTATE_LOCKS -Wthread-safety -MD -MQ drivers/libtmp_rte_net_ark.a.p/net_ark_ark_pktchkr.c.o -MF drivers/libtmp_rte_net_ark.a.p/net_ark_ark_pktchkr.c.o.d -o drivers/libtmp_rte_net_ark.a.p/net_ark_ark_pktchkr.c.o -c ../drivers/net/ark/ark_pktchkr.c
../drivers/net/ark/ark_pktchkr.c:381:16: error: use of undeclared identifier 'AF_INET'
381 | if (inet_pton(AF_INET, ip_address, &addr) != 1)
^ permalink raw reply
* Re: [PATCH v2] dts: add retry loop to trex traffic generation
From: Andrew Bailey @ 2026-06-04 16:46 UTC (permalink / raw)
To: luca.vizzarro, Patrick Robb; +Cc: dev, lylavoie, knimoji, ahassick
In-Reply-To: <20260518175424.253600-1-abailey@iol.unh.edu>
[-- Attachment #1: Type: text/plain, Size: 40 bytes --]
Recheck-request: iol-unit-arm64-testing
[-- Attachment #2: Type: text/html, Size: 61 bytes --]
^ permalink raw reply
* [PATCH v2 3/3] ring: cleanup the C11 code
From: Stephen Hemminger @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev, Wathsala Vithanage
In-Reply-To: <20260604163656.1226902-1-stephen@networkplumber.org>
Put the C11 code in the rte_ring_elem_pvt.h file
and only have the GCC vs C11 code split in separate includes.
The internal functions to update tail of ring no longer use
the enqueue flag argument.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
lib/ring/rte_ring_c11_pvt.h | 88 ----------------------------
lib/ring/rte_ring_elem_pvt.h | 98 +++++++++++++++++++++++++++++---
lib/ring/rte_ring_gcc_pvt.h | 84 ---------------------------
lib/ring/rte_ring_hts_elem_pvt.h | 8 +--
lib/ring/soring.c | 10 ++--
5 files changed, 98 insertions(+), 190 deletions(-)
diff --git a/lib/ring/rte_ring_c11_pvt.h b/lib/ring/rte_ring_c11_pvt.h
index 5afc14dec9..d232e5ac34 100644
--- a/lib/ring/rte_ring_c11_pvt.h
+++ b/lib/ring/rte_ring_c11_pvt.h
@@ -19,94 +19,6 @@
* For more information please refer to <rte_ring.h>.
*/
-/**
- * @internal This function updates tail values.
- */
-static __rte_always_inline void
-__rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
- uint32_t new_val, uint32_t single, uint32_t enqueue)
-{
- RTE_SET_USED(enqueue);
-
- /*
- * If there are other enqueues/dequeues in progress that preceded us,
- * we need to wait for them to complete
- */
- if (!single)
- rte_wait_until_equal_32((uint32_t *)(uintptr_t)&ht->tail, old_val,
- rte_memory_order_relaxed);
-
- /*
- * R0: Establishes a synchronizing edge with load-acquire of tail at A1.
- * Ensures that memory effects by this thread on ring elements array
- * is observed by a different thread of the other type.
- */
- rte_atomic_store_explicit(&ht->tail, new_val, rte_memory_order_release);
-}
-
-/**
- * @internal This is a helper function that moves the producer/consumer head
- * optimized for single threaded case
- *
- * @param d
- * A pointer to the headtail structure with head value to be moved
- * @param s
- * A pointer to the counter-part headtail structure. Note that this
- * function only reads tail value from it
- * @param capacity
- * Either ring capacity value (for producer), or zero (for consumer)
- * @param n
- * The number of elements we want to move head value on
- * @param behavior
- * RTE_RING_QUEUE_FIXED: Move on a fixed number of items
- * RTE_RING_QUEUE_VARIABLE: Move on as many items as possible
- * @param old_head
- * Returns head value as it was before the move
- * @param new_head
- * Returns the new head value
- * @param entries
- * Returns the number of ring entries available BEFORE head was moved
- * @return
- * Actual number of objects the head was moved on
- * If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
- */
-static __rte_always_inline unsigned int
-__rte_ring_headtail_move_head_st(struct rte_ring_headtail *d,
- const struct rte_ring_headtail *s, uint32_t capacity,
- unsigned int n,
- enum rte_ring_queue_behavior behavior,
- uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
-{
- uint32_t stail;
-
- /* Single producer: only this thread writes d->head,
- * so a relaxed load is sufficient.
- */
- *old_head = rte_atomic_load_explicit(&d->head, rte_memory_order_relaxed);
-
- /* Acquire pairs with the consumer's release-store of tail in __rte_ring_update_tail,
- * ensuring the consumer's ring-element reads are complete before
- * we observe the updated tail.
- */
- stail = rte_atomic_load_explicit(&s->tail, rte_memory_order_acquire);
-
- /* Unsigned subtraction is modulo 2^32, so entries is always in
- * [0, capacity) even if old_head > stail.
- */
- *entries = capacity + stail - *old_head;
-
- /* check that we have enough room in ring */
- if (unlikely(n > *entries))
- n = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : *entries;
-
- if (n > 0) {
- *new_head = *old_head + n;
- rte_atomic_store_explicit(&d->head, *new_head, rte_memory_order_relaxed);
- }
-
- return n;
-}
-
/**
* @internal This is a helper function that moves the producer/consumer head
* for use in multi-thread safe path
diff --git a/lib/ring/rte_ring_elem_pvt.h b/lib/ring/rte_ring_elem_pvt.h
index 9a0170c4f0..17ec450b8a 100644
--- a/lib/ring/rte_ring_elem_pvt.h
+++ b/lib/ring/rte_ring_elem_pvt.h
@@ -299,12 +299,94 @@ __rte_ring_dequeue_elems(struct rte_ring *r, uint32_t cons_head,
cons_head & r->mask, esize, num);
}
-/* Between load and load. there might be cpu reorder in weak model
- * (powerpc/arm).
- * There are 2 choices for the users
- * 1.use rmb() memory barrier
- * 2.use one-direction load_acquire/store_release barrier
- * It depends on performance test results.
+static __rte_always_inline void
+__rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
+ uint32_t new_val, uint32_t single)
+{
+ /*
+ * If there are other enqueues/dequeues in progress that preceded us,
+ * we need to wait for them to complete
+ */
+ if (!single)
+ rte_wait_until_equal_32((uint32_t *)(uintptr_t)&ht->tail, old_val,
+ rte_memory_order_relaxed);
+
+ /*
+ * R0: Establishes a synchronizing edge with load-acquire of tail at A1.
+ * Ensures that memory effects by this thread on ring elements array
+ * is observed by a different thread of the other type.
+ */
+ rte_atomic_store_explicit(&ht->tail, new_val, rte_memory_order_release);
+}
+
+/**
+ * @internal This is a helper function that moves the producer/consumer head
+ * optimized for single threaded case
+ *
+ * @param d
+ * A pointer to the headtail structure with head value to be moved
+ * @param s
+ * A pointer to the counter-part headtail structure. Note that this
+ * function only reads tail value from it
+ * @param capacity
+ * Either ring capacity value (for producer), or zero (for consumer)
+ * @param n
+ * The number of elements we want to move head value on
+ * @param behavior
+ * RTE_RING_QUEUE_FIXED: Move on a fixed number of items
+ * RTE_RING_QUEUE_VARIABLE: Move on as many items as possible
+ * @param old_head
+ * Returns head value as it was before the move
+ * @param new_head
+ * Returns the new head value
+ * @param entries
+ * Returns the number of ring entries available BEFORE head was moved
+ * @return
+ * Actual number of objects the head was moved on
+ * If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
+ */
+static __rte_always_inline unsigned int
+__rte_ring_headtail_move_head_st(struct rte_ring_headtail *d,
+ const struct rte_ring_headtail *s, uint32_t capacity,
+ unsigned int n,
+ enum rte_ring_queue_behavior behavior,
+ uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
+{
+ uint32_t stail;
+
+ /* Single producer: only this thread writes d->head,
+ * so a relaxed load is sufficient.
+ */
+ *old_head = rte_atomic_load_explicit(&d->head, rte_memory_order_relaxed);
+
+ /* Acquire pairs with the consumer's release-store of tail in __rte_ring_update_tail,
+ * ensuring the consumer's ring-element reads are complete before
+ * we observe the updated tail.
+ */
+ stail = rte_atomic_load_explicit(&s->tail, rte_memory_order_acquire);
+
+ /* Unsigned subtraction is modulo 2^32, so entries is always in
+ * [0, capacity) even if old_head > stail.
+ */
+ *entries = capacity + stail - *old_head;
+
+ /* check that we have enough room in ring */
+ if (unlikely(n > *entries))
+ n = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : *entries;
+
+ if (n > 0) {
+ *new_head = *old_head + n;
+ rte_atomic_store_explicit(&d->head, *new_head, rte_memory_order_relaxed);
+ }
+
+ return n;
+}
+
+/*
+ * The function __rte_ring_headtail_move_head_mt has two versions
+ * based on what is most efficient on a given architecture.
+ *
+ * The C11 is preferred but on x86 GCC has 10% performance drop.
*/
#ifdef RTE_USE_C11_MEM_MODEL
#include "rte_ring_c11_pvt.h"
@@ -426,7 +508,7 @@ __rte_ring_do_enqueue_elem(struct rte_ring *r, const void *obj_table,
__rte_ring_enqueue_elems(r, prod_head, obj_table, esize, n);
- __rte_ring_update_tail(&r->prod, prod_head, prod_next, is_sp, 1);
+ __rte_ring_update_tail(&r->prod, prod_head, prod_next, is_sp);
end:
if (free_space != NULL)
*free_space = free_entries - n;
@@ -473,7 +555,7 @@ __rte_ring_do_dequeue_elem(struct rte_ring *r, void *obj_table,
__rte_ring_dequeue_elems(r, cons_head, obj_table, esize, n);
- __rte_ring_update_tail(&r->cons, cons_head, cons_next, is_sc, 0);
+ __rte_ring_update_tail(&r->cons, cons_head, cons_next, is_sc);
end:
if (available != NULL)
diff --git a/lib/ring/rte_ring_gcc_pvt.h b/lib/ring/rte_ring_gcc_pvt.h
index 68ab1355e8..70fb4c3fcb 100644
--- a/lib/ring/rte_ring_gcc_pvt.h
+++ b/lib/ring/rte_ring_gcc_pvt.h
@@ -18,31 +18,6 @@
* For more information please refer to <rte_ring.h>.
*/
-/**
- * @internal This function updates tail values.
- */
-static __rte_always_inline void
-__rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
- uint32_t new_val, uint32_t single, uint32_t enqueue)
-{
- RTE_SET_USED(enqueue);
-
- /*
- * If there are other enqueues/dequeues in progress that preceded us,
- * we need to wait for them to complete
- */
- if (!single)
- rte_wait_until_equal_32((volatile uint32_t *)(uintptr_t)&ht->tail, old_val,
- rte_memory_order_relaxed);
-
- /*
- * R0: Establishes a synchronizing edge with load-acquire of tail at A1.
- * Ensures that memory effects by this thread on ring elements array
- * is observed by a different thread of the other type.
- */
- __atomic_store_n(&ht->tail, new_val, __ATOMIC_RELEASE);
-}
-
/**
* @internal This is a helper function that moves the producer/consumer head
* for use in multi-thread safe path
@@ -115,63 +90,4 @@ __rte_ring_headtail_move_head_mt(struct rte_ring_headtail *d,
return n;
}
-/**
- * @internal This is a helper function that moves the producer/consumer head
- * optimized for single threaded case
- *
- * @param d
- * A pointer to the headtail structure with head value to be moved
- * @param s
- * A pointer to the counter-part headtail structure. Note that this
- * function only reads tail value from it
- * @param capacity
- * Either ring capacity value (for producer), or zero (for consumer)
- * @param n
- * The number of elements we want to move head value on
- * @param behavior
- * RTE_RING_QUEUE_FIXED: Move on a fixed number of items
- * RTE_RING_QUEUE_VARIABLE: Move on as many items as possible
- * @param old_head
- * Returns head value as it was before the move
- * @param new_head
- * Returns the new head value
- * @param entries
- * Returns the number of ring entries available BEFORE head was moved
- * @return
- * Actual number of objects the head was moved on
- * If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
- */
-static __rte_always_inline unsigned int
-__rte_ring_headtail_move_head_st(struct rte_ring_headtail *d,
- const struct rte_ring_headtail *s, uint32_t capacity,
- unsigned int n,
- enum rte_ring_queue_behavior behavior,
- uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
-{
- *old_head = d->head;
-
- /* add rmb barrier to avoid load/load reorder in weak
- * memory model. It is noop on x86
- */
- rte_smp_rmb();
-
- /*
- * The subtraction is done between two unsigned 32bits value
- * (the result is always modulo 32 bits even if we have
- * *old_head > s->tail). So 'entries' is always between 0
- * and capacity (which is < size).
- */
- *entries = (capacity + s->tail - *old_head);
-
- /* check that we have enough room in ring */
- if (unlikely(n > *entries))
- n = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : *entries;
-
- if (likely(n > 0)) {
- *new_head = *old_head + n;
- d->head = *new_head;
- }
- return n;
-}
-
#endif /* _RTE_RING_GCC_PVT_H_ */
diff --git a/lib/ring/rte_ring_hts_elem_pvt.h b/lib/ring/rte_ring_hts_elem_pvt.h
index a01089d15d..97ae240e2e 100644
--- a/lib/ring/rte_ring_hts_elem_pvt.h
+++ b/lib/ring/rte_ring_hts_elem_pvt.h
@@ -25,12 +25,10 @@
*/
static __rte_always_inline void
__rte_ring_hts_update_tail(struct rte_ring_hts_headtail *ht, uint32_t old_tail,
- uint32_t num, uint32_t enqueue)
+ uint32_t num)
{
uint32_t tail;
- RTE_SET_USED(enqueue);
-
tail = old_tail + num;
/*
@@ -217,7 +215,7 @@ __rte_ring_do_hts_enqueue_elem(struct rte_ring *r, const void *obj_table,
if (n != 0) {
__rte_ring_enqueue_elems(r, head, obj_table, esize, n);
- __rte_ring_hts_update_tail(&r->hts_prod, head, n, 1);
+ __rte_ring_hts_update_tail(&r->hts_prod, head, n);
}
if (free_space != NULL)
@@ -258,7 +256,7 @@ __rte_ring_do_hts_dequeue_elem(struct rte_ring *r, void *obj_table,
if (n != 0) {
__rte_ring_dequeue_elems(r, head, obj_table, esize, n);
- __rte_ring_hts_update_tail(&r->hts_cons, head, n, 0);
+ __rte_ring_hts_update_tail(&r->hts_cons, head, n);
}
if (available != NULL)
diff --git a/lib/ring/soring.c b/lib/ring/soring.c
index 22f9c60e9c..45292c0f78 100644
--- a/lib/ring/soring.c
+++ b/lib/ring/soring.c
@@ -202,21 +202,21 @@ __rte_soring_move_cons_head(struct rte_soring *r, uint32_t stage, uint32_t num,
static __rte_always_inline void
__rte_soring_update_tail(struct __rte_ring_headtail *rht,
- enum rte_ring_sync_type st, uint32_t head, uint32_t next, uint32_t enq)
+ enum rte_ring_sync_type st, uint32_t head, uint32_t next)
{
uint32_t n;
switch (st) {
case RTE_RING_SYNC_ST:
case RTE_RING_SYNC_MT:
- __rte_ring_update_tail(&rht->ht, head, next, st, enq);
+ __rte_ring_update_tail(&rht->ht, head, next, st);
break;
case RTE_RING_SYNC_MT_RTS:
__rte_ring_rts_update_tail(&rht->rts);
break;
case RTE_RING_SYNC_MT_HTS:
n = next - head;
- __rte_ring_hts_update_tail(&rht->hts, head, n, enq);
+ __rte_ring_hts_update_tail(&rht->hts, head, n);
break;
default:
/* unsupported mode, shouldn't be here */
@@ -295,7 +295,7 @@ soring_enqueue(struct rte_soring *r, const void *objs,
&prod_head, &prod_next, &nb_free);
if (n != 0) {
__enqueue_elems(r, objs, meta, prod_head, n);
- __rte_soring_update_tail(&r->prod, st, prod_head, prod_next, 1);
+ __rte_soring_update_tail(&r->prod, st, prod_head, prod_next);
}
if (free_space != NULL)
@@ -401,7 +401,7 @@ soring_dequeue(struct rte_soring *r, void *objs, void *meta,
/* we have some elems to consume */
if (n != 0) {
__dequeue_elems(r, objs, meta, cons_head, n);
- __rte_soring_update_tail(&r->cons, st, cons_head, cons_next, 0);
+ __rte_soring_update_tail(&r->cons, st, cons_head, cons_next);
}
if (available != NULL)
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/3] ring: use GCC builtin as alternative to rte_atomic32
From: Stephen Hemminger @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev, Wathsala Vithanage
In-Reply-To: <20260604163656.1226902-1-stephen@networkplumber.org>
This patch replaces use of the deprecated rte_atomic32 code with
GCC builtin atomic operations.
Although it would be preferable to use C11 version on all architectures,
there is a performance loss if we do it that way:
Measured on i9-13900H, two physical cores MP/MC bulk n=128, 10 runs:
with C11 builtin: 5.86 cycles/elem
with __sync builtin: 5.36 cycles/elem (-9.4%)
The C11 __atomic_compare_exchange_n builtin writes the actual value back
to its expected pointer on failure. On x86 this forces GCC
to emit extra instructions on the critical path between the CAS
and the success-test.
__sync_bool_compare_and_swap returns a plain bool with no pointer
writeback, allowing GCC to emit tighter code.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/ring/meson.build | 2 +-
lib/ring/rte_ring_elem_pvt.h | 2 +-
..._ring_generic_pvt.h => rte_ring_gcc_pvt.h} | 33 +++++++++++--------
3 files changed, 21 insertions(+), 16 deletions(-)
rename lib/ring/{rte_ring_generic_pvt.h => rte_ring_gcc_pvt.h} (88%)
diff --git a/lib/ring/meson.build b/lib/ring/meson.build
index 21f2c12989..2ba160b178 100644
--- a/lib/ring/meson.build
+++ b/lib/ring/meson.build
@@ -9,7 +9,7 @@ indirect_headers += files (
'rte_ring_elem.h',
'rte_ring_elem_pvt.h',
'rte_ring_c11_pvt.h',
- 'rte_ring_generic_pvt.h',
+ 'rte_ring_gcc_pvt.h',
'rte_ring_hts.h',
'rte_ring_hts_elem_pvt.h',
'rte_ring_peek.h',
diff --git a/lib/ring/rte_ring_elem_pvt.h b/lib/ring/rte_ring_elem_pvt.h
index a0fdec9812..9a0170c4f0 100644
--- a/lib/ring/rte_ring_elem_pvt.h
+++ b/lib/ring/rte_ring_elem_pvt.h
@@ -309,7 +309,7 @@ __rte_ring_dequeue_elems(struct rte_ring *r, uint32_t cons_head,
#ifdef RTE_USE_C11_MEM_MODEL
#include "rte_ring_c11_pvt.h"
#else
-#include "rte_ring_generic_pvt.h"
+#include "rte_ring_gcc_pvt.h"
#endif
/**
diff --git a/lib/ring/rte_ring_generic_pvt.h b/lib/ring/rte_ring_gcc_pvt.h
similarity index 88%
rename from lib/ring/rte_ring_generic_pvt.h
rename to lib/ring/rte_ring_gcc_pvt.h
index c044b0824f..68ab1355e8 100644
--- a/lib/ring/rte_ring_generic_pvt.h
+++ b/lib/ring/rte_ring_gcc_pvt.h
@@ -7,11 +7,11 @@
* Used as BSD-3 Licensed with permission from Kip Macy.
*/
-#ifndef _RTE_RING_GENERIC_PVT_H_
-#define _RTE_RING_GENERIC_PVT_H_
+#ifndef _RTE_RING_GCC_PVT_H_
+#define _RTE_RING_GCC_PVT_H_
/**
- * @file rte_ring_generic_pvt.h
+ * @file rte_ring_gcc_pvt.h
* It is not recommended to include this file directly,
* include <rte_ring.h> instead.
* Contains internal helper functions for MP/SP and MC/SC ring modes.
@@ -25,10 +25,8 @@ static __rte_always_inline void
__rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
uint32_t new_val, uint32_t single, uint32_t enqueue)
{
- if (enqueue)
- rte_smp_wmb();
- else
- rte_smp_rmb();
+ RTE_SET_USED(enqueue);
+
/*
* If there are other enqueues/dequeues in progress that preceded us,
* we need to wait for them to complete
@@ -37,7 +35,12 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
rte_wait_until_equal_32((volatile uint32_t *)(uintptr_t)&ht->tail, old_val,
rte_memory_order_relaxed);
- ht->tail = new_val;
+ /*
+ * R0: Establishes a synchronizing edge with load-acquire of tail at A1.
+ * Ensures that memory effects by this thread on ring elements array
+ * is observed by a different thread of the other type.
+ */
+ __atomic_store_n(&ht->tail, new_val, __ATOMIC_RELEASE);
}
/**
@@ -73,7 +76,7 @@ __rte_ring_headtail_move_head_mt(struct rte_ring_headtail *d,
uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
{
unsigned int max = n;
- int success;
+ bool success;
do {
/* Reset n to the initial burst count */
@@ -81,10 +84,10 @@ __rte_ring_headtail_move_head_mt(struct rte_ring_headtail *d,
*old_head = d->head;
- /* add rmb barrier to avoid load/load reorder in weak
+ /* add fence to avoid load/load reorder in weak
* memory model. It is noop on x86
*/
- rte_smp_rmb();
+ __atomic_thread_fence(__ATOMIC_ACQUIRE);
/*
* The subtraction is done between two unsigned 32bits value
@@ -103,10 +106,12 @@ __rte_ring_headtail_move_head_mt(struct rte_ring_headtail *d,
return 0;
*new_head = *old_head + n;
- success = rte_atomic32_cmpset(
+
+ success = __sync_bool_compare_and_swap(
(uint32_t *)(uintptr_t)&d->head,
*old_head, *new_head);
- } while (unlikely(success == 0));
+ } while (unlikely(!success));
+
return n;
}
@@ -169,4 +174,4 @@ __rte_ring_headtail_move_head_st(struct rte_ring_headtail *d,
return n;
}
-#endif /* _RTE_RING_GENERIC_PVT_H_ */
+#endif /* _RTE_RING_GCC_PVT_H_ */
--
2.53.0
^ permalink raw reply related
* [PATCH v2 1/3] ring: split single thread vs multi-thread cases
From: Stephen Hemminger @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev, Wathsala Vithanage
In-Reply-To: <20260604163656.1226902-1-stephen@networkplumber.org>
The move head function has optimization for updating when
being used on single threaded ring. Code is cleaner if the two
cases are split into separate functions.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
Tested-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
lib/ring/rte_ring_c11_pvt.h | 100 +++++++++++++++++++++++++-------
lib/ring/rte_ring_elem_pvt.h | 16 +++--
lib/ring/rte_ring_generic_pvt.h | 77 ++++++++++++++++++++----
lib/ring/soring.c | 24 +++++---
4 files changed, 171 insertions(+), 46 deletions(-)
diff --git a/lib/ring/rte_ring_c11_pvt.h b/lib/ring/rte_ring_c11_pvt.h
index 07b6efc416..5afc14dec9 100644
--- a/lib/ring/rte_ring_c11_pvt.h
+++ b/lib/ring/rte_ring_c11_pvt.h
@@ -46,6 +46,7 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
/**
* @internal This is a helper function that moves the producer/consumer head
+ * optimized for single threaded case
*
* @param d
* A pointer to the headtail structure with head value to be moved
@@ -54,8 +55,6 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
* function only reads tail value from it
* @param capacity
* Either ring capacity value (for producer), or zero (for consumer)
- * @param is_st
- * Indicates whether multi-thread safe path is needed or not
* @param n
* The number of elements we want to move head value on
* @param behavior
@@ -72,14 +71,77 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
* If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
*/
static __rte_always_inline unsigned int
-__rte_ring_headtail_move_head(struct rte_ring_headtail *d,
+__rte_ring_headtail_move_head_st(struct rte_ring_headtail *d,
const struct rte_ring_headtail *s, uint32_t capacity,
- unsigned int is_st, unsigned int n,
+ unsigned int n,
enum rte_ring_queue_behavior behavior,
uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
{
uint32_t stail;
- int success;
+
+ /* Single producer: only this thread writes d->head,
+ * so a relaxed load is sufficient.
+ */
+ *old_head = rte_atomic_load_explicit(&d->head, rte_memory_order_relaxed);
+
+ /* Acquire pairs with the consumer's release-store of tail in __rte_ring_update_tail,
+ * ensuring the consumer's ring-element reads are complete before
+ * we observe the updated tail.
+ */
+ stail = rte_atomic_load_explicit(&s->tail, rte_memory_order_acquire);
+
+ /* Unsigned subtraction is modulo 2^32, so entries is always in
+ * [0, capacity) even if old_head > stail.
+ */
+ *entries = capacity + stail - *old_head;
+
+ /* check that we have enough room in ring */
+ if (unlikely(n > *entries))
+ n = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : *entries;
+
+ if (n > 0) {
+ *new_head = *old_head + n;
+ rte_atomic_store_explicit(&d->head, *new_head, rte_memory_order_relaxed);
+ }
+
+ return n;
+}
+
+/**
+ * @internal This is a helper function that moves the producer/consumer head
+ * for use in multi-thread safe path
+ *
+ * @param d
+ * A pointer to the headtail structure with head value to be moved
+ * @param s
+ * A pointer to the counter-part headtail structure. Note that this
+ * function only reads tail value from it
+ * @param capacity
+ * Either ring capacity value (for producer), or zero (for consumer)
+ * @param n
+ * The number of elements we want to move head value on
+ * @param behavior
+ * RTE_RING_QUEUE_FIXED: Move on a fixed number of items
+ * RTE_RING_QUEUE_VARIABLE: Move on as many items as possible
+ * @param old_head
+ * Returns head value as it was before the move
+ * @param new_head
+ * Returns the new head value
+ * @param entries
+ * Returns the number of ring entries available BEFORE head was moved
+ * @return
+ * Actual number of objects the head was moved on
+ * If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
+ */
+static __rte_always_inline unsigned int
+__rte_ring_headtail_move_head_mt(struct rte_ring_headtail *d,
+ const struct rte_ring_headtail *s, uint32_t capacity,
+ unsigned int n,
+ enum rte_ring_queue_behavior behavior,
+ uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
+{
+ uint32_t stail;
+ bool success;
unsigned int max = n;
/*
@@ -120,25 +182,21 @@ __rte_ring_headtail_move_head(struct rte_ring_headtail *d,
return 0;
*new_head = *old_head + n;
- if (is_st) {
- d->head = *new_head;
- success = 1;
- } else
- /* on failure, *old_head is updated */
- /*
- * R1/A2.
- * R1: Establishes a synchronizing edge with A0 of a
- * different thread.
- * A2: Establishes a synchronizing edge with R1 of a
- * different thread to observe same value for stail
- * observed by that thread on CAS failure (to retry
- * with an updated *old_head).
- */
- success = rte_atomic_compare_exchange_strong_explicit(
+ /* on failure, *old_head is updated */
+ /*
+ * R1/A2.
+ * R1: Establishes a synchronizing edge with A0 of a
+ * different thread.
+ * A2: Establishes a synchronizing edge with R1 of a
+ * different thread to observe same value for stail
+ * observed by that thread on CAS failure (to retry
+ * with an updated *old_head).
+ */
+ success = rte_atomic_compare_exchange_strong_explicit(
&d->head, old_head, *new_head,
rte_memory_order_release,
rte_memory_order_acquire);
- } while (unlikely(success == 0));
+ } while (unlikely(!success));
return n;
}
diff --git a/lib/ring/rte_ring_elem_pvt.h b/lib/ring/rte_ring_elem_pvt.h
index 6eafae121f..a0fdec9812 100644
--- a/lib/ring/rte_ring_elem_pvt.h
+++ b/lib/ring/rte_ring_elem_pvt.h
@@ -341,8 +341,12 @@ __rte_ring_move_prod_head(struct rte_ring *r, unsigned int is_sp,
uint32_t *old_head, uint32_t *new_head,
uint32_t *free_entries)
{
- return __rte_ring_headtail_move_head(&r->prod, &r->cons, r->capacity,
- is_sp, n, behavior, old_head, new_head, free_entries);
+ if (is_sp)
+ return __rte_ring_headtail_move_head_st(&r->prod, &r->cons, r->capacity,
+ n, behavior, old_head, new_head, free_entries);
+ else
+ return __rte_ring_headtail_move_head_mt(&r->prod, &r->cons, r->capacity,
+ n, behavior, old_head, new_head, free_entries);
}
/**
@@ -374,8 +378,12 @@ __rte_ring_move_cons_head(struct rte_ring *r, unsigned int is_sc,
uint32_t *old_head, uint32_t *new_head,
uint32_t *entries)
{
- return __rte_ring_headtail_move_head(&r->cons, &r->prod, 0,
- is_sc, n, behavior, old_head, new_head, entries);
+ if (is_sc)
+ return __rte_ring_headtail_move_head_st(&r->cons, &r->prod, 0,
+ n, behavior, old_head, new_head, entries);
+ else
+ return __rte_ring_headtail_move_head_mt(&r->cons, &r->prod, 0,
+ n, behavior, old_head, new_head, entries);
}
/**
diff --git a/lib/ring/rte_ring_generic_pvt.h b/lib/ring/rte_ring_generic_pvt.h
index affd2d5ba7..c044b0824f 100644
--- a/lib/ring/rte_ring_generic_pvt.h
+++ b/lib/ring/rte_ring_generic_pvt.h
@@ -42,6 +42,7 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
/**
* @internal This is a helper function that moves the producer/consumer head
+ * for use in multi-thread safe path
*
* @param d
* A pointer to the headtail structure with head value to be moved
@@ -50,8 +51,6 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
* function only reads tail value from it
* @param capacity
* Either ring capacity value (for producer), or zero (for consumer)
- * @param is_st
- * Indicates whether multi-thread safe path is needed or not
* @param n
* The number of elements we want to move head value on
* @param behavior
@@ -68,10 +67,9 @@ __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
* If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
*/
static __rte_always_inline unsigned int
-__rte_ring_headtail_move_head(struct rte_ring_headtail *d,
+__rte_ring_headtail_move_head_mt(struct rte_ring_headtail *d,
const struct rte_ring_headtail *s, uint32_t capacity,
- unsigned int is_st, unsigned int n,
- enum rte_ring_queue_behavior behavior,
+ unsigned int n, enum rte_ring_queue_behavior behavior,
uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
{
unsigned int max = n;
@@ -105,15 +103,70 @@ __rte_ring_headtail_move_head(struct rte_ring_headtail *d,
return 0;
*new_head = *old_head + n;
- if (is_st) {
- d->head = *new_head;
- success = 1;
- } else
- success = rte_atomic32_cmpset(
- (uint32_t *)(uintptr_t)&d->head,
- *old_head, *new_head);
+ success = rte_atomic32_cmpset(
+ (uint32_t *)(uintptr_t)&d->head,
+ *old_head, *new_head);
} while (unlikely(success == 0));
return n;
}
+/**
+ * @internal This is a helper function that moves the producer/consumer head
+ * optimized for single threaded case
+ *
+ * @param d
+ * A pointer to the headtail structure with head value to be moved
+ * @param s
+ * A pointer to the counter-part headtail structure. Note that this
+ * function only reads tail value from it
+ * @param capacity
+ * Either ring capacity value (for producer), or zero (for consumer)
+ * @param n
+ * The number of elements we want to move head value on
+ * @param behavior
+ * RTE_RING_QUEUE_FIXED: Move on a fixed number of items
+ * RTE_RING_QUEUE_VARIABLE: Move on as many items as possible
+ * @param old_head
+ * Returns head value as it was before the move
+ * @param new_head
+ * Returns the new head value
+ * @param entries
+ * Returns the number of ring entries available BEFORE head was moved
+ * @return
+ * Actual number of objects the head was moved on
+ * If behavior == RTE_RING_QUEUE_FIXED, this will be 0 or n only
+ */
+static __rte_always_inline unsigned int
+__rte_ring_headtail_move_head_st(struct rte_ring_headtail *d,
+ const struct rte_ring_headtail *s, uint32_t capacity,
+ unsigned int n,
+ enum rte_ring_queue_behavior behavior,
+ uint32_t *old_head, uint32_t *new_head, uint32_t *entries)
+{
+ *old_head = d->head;
+
+ /* add rmb barrier to avoid load/load reorder in weak
+ * memory model. It is noop on x86
+ */
+ rte_smp_rmb();
+
+ /*
+ * The subtraction is done between two unsigned 32bits value
+ * (the result is always modulo 32 bits even if we have
+ * *old_head > s->tail). So 'entries' is always between 0
+ * and capacity (which is < size).
+ */
+ *entries = (capacity + s->tail - *old_head);
+
+ /* check that we have enough room in ring */
+ if (unlikely(n > *entries))
+ n = (behavior == RTE_RING_QUEUE_FIXED) ? 0 : *entries;
+
+ if (likely(n > 0)) {
+ *new_head = *old_head + n;
+ d->head = *new_head;
+ }
+ return n;
+}
+
#endif /* _RTE_RING_GENERIC_PVT_H_ */
diff --git a/lib/ring/soring.c b/lib/ring/soring.c
index e9c75619fe..22f9c60e9c 100644
--- a/lib/ring/soring.c
+++ b/lib/ring/soring.c
@@ -135,9 +135,12 @@ __rte_soring_move_prod_head(struct rte_soring *r, uint32_t num,
switch (st) {
case RTE_RING_SYNC_ST:
+ n = __rte_ring_headtail_move_head_st(&r->prod.ht, &r->cons.ht,
+ r->capacity, num, behavior, head, next, free);
+ break;
case RTE_RING_SYNC_MT:
- n = __rte_ring_headtail_move_head(&r->prod.ht, &r->cons.ht,
- r->capacity, st, num, behavior, head, next, free);
+ n = __rte_ring_headtail_move_head_mt(&r->prod.ht, &r->cons.ht,
+ r->capacity, num, behavior, head, next, free);
break;
case RTE_RING_SYNC_MT_RTS:
n = __rte_ring_rts_move_head(&r->prod.rts, &r->cons.ht,
@@ -168,9 +171,13 @@ __rte_soring_move_cons_head(struct rte_soring *r, uint32_t stage, uint32_t num,
switch (st) {
case RTE_RING_SYNC_ST:
+ n = __rte_ring_headtail_move_head_st(&r->cons.ht,
+ &r->stage[stage].ht, 0, num, behavior,
+ head, next, avail);
+ break;
case RTE_RING_SYNC_MT:
- n = __rte_ring_headtail_move_head(&r->cons.ht,
- &r->stage[stage].ht, 0, st, num, behavior,
+ n = __rte_ring_headtail_move_head_mt(&r->cons.ht,
+ &r->stage[stage].ht, 0, num, behavior,
head, next, avail);
break;
case RTE_RING_SYNC_MT_RTS:
@@ -309,9 +316,8 @@ soring_enqueue_start(struct rte_soring *r, uint32_t num,
switch (st) {
case RTE_RING_SYNC_ST:
- n = __rte_ring_headtail_move_head(&r->prod.ht, &r->cons.ht,
- r->capacity, RTE_RING_SYNC_ST, num, behavior,
- &head, &next, &free);
+ n = __rte_ring_headtail_move_head_st(&r->prod.ht, &r->cons.ht,
+ r->capacity, num, behavior, &head, &next, &free);
break;
case RTE_RING_SYNC_MT_HTS:
n = __rte_ring_hts_move_head(&r->prod.hts, &r->cons.ht,
@@ -419,8 +425,8 @@ soring_dequeue_start(struct rte_soring *r, void *objs, void *meta,
switch (st) {
case RTE_RING_SYNC_ST:
- n = __rte_ring_headtail_move_head(&r->cons.ht, &r->stage[ns].ht,
- 0, RTE_RING_SYNC_ST, num, behavior, &head, &next,
+ n = __rte_ring_headtail_move_head_st(&r->cons.ht, &r->stage[ns].ht,
+ 0, num, behavior, &head, &next,
&avail);
break;
case RTE_RING_SYNC_MT_HTS:
--
2.53.0
^ permalink raw reply related
* [PATCH v2] ring: convert to C11 atomics where practical
From: Stephen Hemminger @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260602171552.686349-1-stephen@networkplumber.org>
This is split out from the atomic deprecation series. It converts lib/ring
off rte_atomic32 and onto the C11 memory model, except where the C11 version
has a noticeable performance drop on x86 with GCC.
The pre-existing C11 and GCC-builtin paths lived in separate headers with
substantial duplication. After this series, only the MP head CAS
(__rte_ring_headtail_move_head_mt) retains separate implementations;
everything else is shared. Patch 2 documents the reason for keeping the GCC
builtin on the MP head CAS.
The default RTE_USE_C11_MEM_MODEL selection per architecture is unchanged.
v2 - consolidate cleanup patches
- fix the memory order on first load in _st case.
it was going back/forth across the patches
Stephen Hemminger (3):
ring: split single thread vs multi-thread cases
ring: use GCC builtin as alternative to rte_atomic32
ring: cleanup the C11 code
lib/ring/meson.build | 2 +-
lib/ring/rte_ring_c11_pvt.h | 62 +++-------
lib/ring/rte_ring_elem_pvt.h | 116 ++++++++++++++++--
..._ring_generic_pvt.h => rte_ring_gcc_pvt.h} | 58 +++------
lib/ring/rte_ring_hts_elem_pvt.h | 8 +-
lib/ring/soring.c | 34 ++---
6 files changed, 159 insertions(+), 121 deletions(-)
rename lib/ring/{rte_ring_generic_pvt.h => rte_ring_gcc_pvt.h} (65%)
--
2.53.0
^ permalink raw reply
* [PATCH v3 5/5] eal: avoid deadlock in async IPC alarm callback
From: Anatoly Burakov @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev, Jianfeng Tan
In-Reply-To: <2bc77b94493d94b53a28ea535ed96d92a157a7c7.1780590727.git.anatoly.burakov@intel.com>
async_reply_handle_thread_unsafe() can run while holding
pending_requests.lock and currently calls rte_eal_alarm_cancel().
rte_eal_alarm_cancel() may spin-wait for an executing callback, which can
deadlock if that callback is blocked on the same lock.
Remove callback-side alarm cancellation. It is safe to do so, because any
callback triggered without a pending request becomes a noop.
Fixes: daf9bfca717e ("ipc: remove thread for async requests")
Cc: stable@dpdk.org
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
lib/eal/common/eal_common_proc.c | 21 ++++++---------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
index f023991438..012a03440c 100644
--- a/lib/eal/common/eal_common_proc.c
+++ b/lib/eal/common/eal_common_proc.c
@@ -549,19 +549,6 @@ async_reply_handle_thread_unsafe(struct pending_request *req)
TAILQ_REMOVE(&pending_requests.requests, req, next);
- if (rte_eal_alarm_cancel(async_reply_handle,
- (void *)(uintptr_t)req->id) < 0) {
- /* if we failed to cancel the alarm because it's already in
- * progress, don't proceed because otherwise we will end up
- * handling the same message twice.
- */
- if (rte_errno == EINPROGRESS) {
- EAL_LOG(DEBUG, "Request handling is already in progress");
- goto no_trigger;
- }
- EAL_LOG(ERR, "Failed to cancel alarm");
- }
-
if (action == ACTION_TRIGGER)
return req;
no_trigger:
@@ -910,8 +897,12 @@ mp_request_async(const char *dst, struct rte_mp_msg *req,
return -1;
}
- /* Set alarm before allocating or sending so request timeout tracking
- * is active as soon as this request ID is reserved.
+ /* Set alarm before allocating or sending. The alarm is never cancelled:
+ * rte_eal_alarm_cancel spin-waits for an executing callback to finish,
+ * which deadlocks if we hold pending_requests.lock while the callback
+ * is blocked on it. Instead, let stale alarms fire; with ID-based
+ * lookup the callback will simply not find the request and return
+ * harmlessly.
*/
id = ++next_request_id;
if (rte_eal_alarm_set(ts->tv_sec * 1000000 + ts->tv_nsec / 1000,
--
2.47.3
^ permalink raw reply related
* [PATCH v3 4/5] eal: fix async IPC resource leaks on partial failure
From: Anatoly Burakov @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev, Jianfeng Tan
In-Reply-To: <2bc77b94493d94b53a28ea535ed96d92a157a7c7.1780590727.git.anatoly.burakov@intel.com>
When rte_mp_request_async() fails to send requests to all peers,
copy and param can lose ownership and leak.
On partial failure, some requests may already be queued and still
reference copy and param, so freeing them directly on the error
path can cause use-after-free when those requests are later handled.
Fix this by rolling back queued requests from the current batch,
resetting nb_sent to 0, and freeing copy/param only after rollback.
Use a numeric request ID for alarm callback lookup so stale callbacks
from rolled-back requests become harmless no-ops.
Coverity issue: 501503
Fixes: f05e26051c15 ("eal: add IPC asynchronous request")
Cc: stable@dpdk.org
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
lib/eal/common/eal_common_proc.c | 113 +++++++++++++++++++++++--------
1 file changed, 85 insertions(+), 28 deletions(-)
diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
index 02adcb9ba8..f023991438 100644
--- a/lib/eal/common/eal_common_proc.c
+++ b/lib/eal/common/eal_common_proc.c
@@ -74,6 +74,7 @@ struct async_request_param {
struct pending_request {
TAILQ_ENTRY(pending_request) next;
+ unsigned long id;
enum {
REQUEST_TYPE_SYNC,
REQUEST_TYPE_ASYNC
@@ -92,6 +93,8 @@ struct pending_request {
};
};
+static unsigned long next_request_id;
+
TAILQ_HEAD(pending_request_list, pending_request);
static struct {
@@ -111,9 +114,9 @@ mp_send(struct rte_mp_msg *msg, const char *peer, int type);
static void
async_reply_handle(void *arg);
-/* for use with process_msg */
+/* for use with alarm callback and process_msg */
static struct pending_request *
-async_reply_handle_thread_unsafe(void *arg);
+async_reply_handle_thread_unsafe(struct pending_request *req);
static void
trigger_async_action(struct pending_request *req);
@@ -132,6 +135,19 @@ find_pending_request(const char *dst, const char *act_name)
return r;
}
+static struct pending_request *
+find_async_request_by_id(unsigned long id)
+{
+ struct pending_request *r;
+
+ TAILQ_FOREACH(r, &pending_requests.requests, next) {
+ if (r->id == id && r->type == REQUEST_TYPE_ASYNC)
+ return r;
+ }
+
+ return NULL;
+}
+
/*
* Combine prefix and name(optional) to return unix domain socket path
* return the number of characters that would have been put into buffer.
@@ -519,9 +535,8 @@ trigger_async_action(struct pending_request *sr)
}
static struct pending_request *
-async_reply_handle_thread_unsafe(void *arg)
+async_reply_handle_thread_unsafe(struct pending_request *req)
{
- struct pending_request *req = (struct pending_request *)arg;
enum async_action action;
struct timespec ts_now;
@@ -534,7 +549,8 @@ async_reply_handle_thread_unsafe(void *arg)
TAILQ_REMOVE(&pending_requests.requests, req, next);
- if (rte_eal_alarm_cancel(async_reply_handle, req) < 0) {
+ if (rte_eal_alarm_cancel(async_reply_handle,
+ (void *)(uintptr_t)req->id) < 0) {
/* if we failed to cancel the alarm because it's already in
* progress, don't proceed because otherwise we will end up
* handling the same message twice.
@@ -557,9 +573,13 @@ static void
async_reply_handle(void *arg)
{
struct pending_request *req;
+ /* alarm arg carries the request ID packed into a void * via uintptr_t */
+ unsigned long id = (uintptr_t)arg;
pthread_mutex_lock(&pending_requests.lock);
- req = async_reply_handle_thread_unsafe(arg);
+ req = find_async_request_by_id(id);
+ if (req != NULL)
+ req = async_reply_handle_thread_unsafe(req);
pthread_mutex_unlock(&pending_requests.lock);
if (req != NULL)
@@ -878,7 +898,29 @@ mp_request_async(const char *dst, struct rte_mp_msg *req,
{
struct rte_mp_msg *reply_msg;
struct pending_request *pending_req, *exist;
- int ret = -1;
+ unsigned long id;
+ int ret;
+
+ /* queue already locked by caller */
+
+ exist = find_pending_request(dst, req->name);
+ if (exist) {
+ EAL_LOG(ERR, "A pending request %s:%s", dst, req->name);
+ rte_errno = EEXIST;
+ return -1;
+ }
+
+ /* Set alarm before allocating or sending so request timeout tracking
+ * is active as soon as this request ID is reserved.
+ */
+ id = ++next_request_id;
+ if (rte_eal_alarm_set(ts->tv_sec * 1000000 + ts->tv_nsec / 1000,
+ async_reply_handle,
+ (void *)(uintptr_t)id) < 0) {
+ EAL_LOG(ERR, "Fail to set alarm for request %s:%s",
+ dst, req->name);
+ return -1;
+ }
pending_req = calloc(1, sizeof(*pending_req));
reply_msg = calloc(1, sizeof(*reply_msg));
@@ -890,21 +932,12 @@ mp_request_async(const char *dst, struct rte_mp_msg *req,
}
pending_req->type = REQUEST_TYPE_ASYNC;
+ pending_req->id = id;
strlcpy(pending_req->dst, dst, sizeof(pending_req->dst));
pending_req->request = req;
pending_req->reply = reply_msg;
pending_req->async.param = param;
- /* queue already locked by caller */
-
- exist = find_pending_request(dst, req->name);
- if (exist) {
- EAL_LOG(ERR, "A pending request %s:%s", dst, req->name);
- rte_errno = EEXIST;
- ret = -1;
- goto fail;
- }
-
ret = send_msg(dst, req, MP_REQ);
if (ret < 0) {
EAL_LOG(ERR, "Fail to send request %s:%s",
@@ -917,14 +950,6 @@ mp_request_async(const char *dst, struct rte_mp_msg *req,
}
param->user_reply.nb_sent++;
- /* if alarm set fails, we simply ignore the reply */
- if (rte_eal_alarm_set(ts->tv_sec * 1000000 + ts->tv_nsec / 1000,
- async_reply_handle, pending_req) < 0) {
- EAL_LOG(ERR, "Fail to set alarm for request %s:%s",
- dst, req->name);
- ret = -1;
- goto fail;
- }
TAILQ_INSERT_TAIL(&pending_requests.requests, pending_req, next);
return 0;
@@ -1178,6 +1203,7 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
* it, and put it on the queue if we don't send any requests.
*/
dummy->type = REQUEST_TYPE_ASYNC;
+ dummy->id = ++next_request_id;
dummy->request = copy;
dummy->reply = NULL;
dummy->async.param = param;
@@ -1194,8 +1220,8 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
TAILQ_INSERT_TAIL(&pending_requests.requests, dummy,
next);
dummy_used = true;
-
- if (rte_eal_alarm_set(1, async_reply_handle, dummy) < 0) {
+ if (rte_eal_alarm_set(1, async_reply_handle,
+ (void *)(uintptr_t)dummy->id) < 0) {
EAL_LOG(ERR, "Fail to set alarm for dummy request");
/* roll back the changes */
TAILQ_REMOVE(&pending_requests.requests, dummy, next);
@@ -1244,13 +1270,39 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
} else if (mp_request_async(path, copy, param, ts))
ret = -1;
}
+
+ /*
+ * On partial failure, roll back all queued requests. We hold the lock
+ * so no one else touches the queue. All requests in this batch share
+ * the same param pointer. Stale alarms will fire and harmlessly find
+ * nothing via ID-based lookup.
+ */
+ if (ret != 0 && reply->nb_sent > 0) {
+ struct pending_request *r, *next;
+
+ for (r = TAILQ_FIRST(&pending_requests.requests);
+ r != NULL; r = next) {
+ next = TAILQ_NEXT(r, next);
+ if (r->type == REQUEST_TYPE_ASYNC &&
+ r->async.param == param) {
+ TAILQ_REMOVE(&pending_requests.requests,
+ r, next);
+ free(r->reply);
+ /* r->request == copy, freed below after the loop */
+ free(r);
+ }
+ }
+ reply->nb_sent = 0;
+ }
+
/* if we didn't send anything, put dummy request on the queue
* and set a minimum-delay alarm so the callback fires immediately.
*/
if (ret == 0 && reply->nb_sent == 0) {
TAILQ_INSERT_HEAD(&pending_requests.requests, dummy, next);
dummy_used = true;
- if (rte_eal_alarm_set(1, async_reply_handle, dummy) < 0)
+ if (rte_eal_alarm_set(1, async_reply_handle,
+ (void *)(uintptr_t)dummy->id) < 0)
EAL_LOG(ERR, "Fail to set alarm for dummy request");
}
@@ -1266,6 +1318,11 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
/* if dummy was unused, free it */
if (!dummy_used)
free(dummy);
+ /* if nothing was sent, nobody owns copy/param */
+ if (ret != 0) {
+ free(param);
+ free(copy);
+ }
return ret;
closedir_fail:
--
2.47.3
^ permalink raw reply related
* [PATCH v3 3/5] eal: fix memory leak in async IPC secondary path
From: Anatoly Burakov @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev, Jianfeng Tan
In-Reply-To: <2bc77b94493d94b53a28ea535ed96d92a157a7c7.1780590727.git.anatoly.burakov@intel.com>
When rte_mp_request_async() succeeds on the secondary process path, the
dummy request is freed only if it was inserted into the queue. However,
when the actual request was sent successfully (nb_sent > 0), the dummy is
not used and the function returns without freeing it.
Free dummy before returning on the success path when it was not inserted
into the queue.
Fixes: f05e26051c15 ("eal: add IPC asynchronous request")
Cc: stable@dpdk.org
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
lib/eal/common/eal_common_proc.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
index 5cc15a0f78..02adcb9ba8 100644
--- a/lib/eal/common/eal_common_proc.c
+++ b/lib/eal/common/eal_common_proc.c
@@ -1209,6 +1209,8 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
/* if we couldn't send anything, clean up */
if (ret != 0)
goto fail;
+ if (!dummy_used)
+ free(dummy);
return 0;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v3 2/5] eal: fix async IPC callback not fired when no peers
From: Anatoly Burakov @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev, Jianfeng Tan
In-Reply-To: <2bc77b94493d94b53a28ea535ed96d92a157a7c7.1780590727.git.anatoly.burakov@intel.com>
Currently, when rte_mp_request_async() is called and no peer processes
are connected (nb_sent == 0), the user callback is never invoked.
The original implementation used a dedicated background thread and
pthread_cond_signal() to wake it after queuing the dummy request. When
that thread was replaced with per-message alarms, no alarm was set for
the dummy request, silently breaking the nb_sent == 0 path.
This was not noticed because async requests are used while handling
secondary process requests, where peers are typically already present.
Fix it by setting a 1us alarm on the dummy request, so the callback path
immediately triggers and processes it.
Fixes: daf9bfca717e ("ipc: remove thread for async requests")
Cc: stable@dpdk.org
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
lib/eal/common/eal_common_proc.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
index 799c6e81b0..5cc15a0f78 100644
--- a/lib/eal/common/eal_common_proc.c
+++ b/lib/eal/common/eal_common_proc.c
@@ -1187,11 +1187,21 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
ret = mp_request_async(eal_mp_socket_path(), copy, param, ts);
- /* if we didn't send anything, put dummy request on the queue */
+ /* if we didn't send anything, put dummy request on the queue
+ * and set a minimum-delay alarm so the callback fires immediately.
+ */
if (ret == 0 && reply->nb_sent == 0) {
TAILQ_INSERT_TAIL(&pending_requests.requests, dummy,
next);
dummy_used = true;
+
+ if (rte_eal_alarm_set(1, async_reply_handle, dummy) < 0) {
+ EAL_LOG(ERR, "Fail to set alarm for dummy request");
+ /* roll back the changes */
+ TAILQ_REMOVE(&pending_requests.requests, dummy, next);
+ dummy_used = false;
+ ret = -1;
+ }
}
pthread_mutex_unlock(&pending_requests.lock);
@@ -1232,10 +1242,14 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
} else if (mp_request_async(path, copy, param, ts))
ret = -1;
}
- /* if we didn't send anything, put dummy request on the queue */
+ /* if we didn't send anything, put dummy request on the queue
+ * and set a minimum-delay alarm so the callback fires immediately.
+ */
if (ret == 0 && reply->nb_sent == 0) {
TAILQ_INSERT_HEAD(&pending_requests.requests, dummy, next);
dummy_used = true;
+ if (rte_eal_alarm_set(1, async_reply_handle, dummy) < 0)
+ EAL_LOG(ERR, "Fail to set alarm for dummy request");
}
/* finally, unlock the queue */
--
2.47.3
^ permalink raw reply related
* [PATCH v3 1/5] eal: fix wrong log message in async IPC request
From: Anatoly Burakov @ 2026-06-04 16:32 UTC (permalink / raw)
To: dev, Jianfeng Tan
In-Reply-To: <740b39c5098b4d40cafb9881ad70865a3c889012.1773936429.git.anatoly.burakov@intel.com>
The allocation failure log message in mp_request_async() says "sync
request" but the function handles asynchronous requests.
Fix the log to say "async request".
Fixes: f05e26051c15 ("eal: add IPC asynchronous request")
Cc: stable@dpdk.org
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
lib/eal/common/eal_common_proc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
index 06f151818c..799c6e81b0 100644
--- a/lib/eal/common/eal_common_proc.c
+++ b/lib/eal/common/eal_common_proc.c
@@ -883,7 +883,7 @@ mp_request_async(const char *dst, struct rte_mp_msg *req,
pending_req = calloc(1, sizeof(*pending_req));
reply_msg = calloc(1, sizeof(*reply_msg));
if (pending_req == NULL || reply_msg == NULL) {
- EAL_LOG(ERR, "Could not allocate space for sync request");
+ EAL_LOG(ERR, "Could not allocate space for async request");
rte_errno = ENOMEM;
ret = -1;
goto fail;
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v2 2/5] eal: fix async IPC callback not fired when no peers
From: Burakov, Anatoly @ 2026-06-04 16:21 UTC (permalink / raw)
To: Thomas Monjalon; +Cc: dev, Jianfeng Tan
In-Reply-To: <FGTuNPDMQtmtEID8twKsTA@monjalon.net>
On 6/1/2026 2:40 PM, Thomas Monjalon wrote:
> 01/06/2026 14:21, Thomas Monjalon:
>> 29/05/2026 17:26, Anatoly Burakov:
>>> Currently, when rte_mp_request_async() is called and no peer processes
>>> are connected (nb_sent == 0), the user callback is never invoked.
>>>
>>> The original implementation used a dedicated background thread and
>>> pthread_cond_signal() to wake it after queuing the dummy request. When
>>> that thread was replaced with per-message alarms, no alarm was set for
>>> the dummy request, silently breaking the nb_sent == 0 path.
>>>
>>> This was not noticed because async requests are used while handling
>>> secondary process requests, where peers are typically already present.
>>>
>>> Fix it by setting a 1us alarm on the dummy request, so the callback path
>>> immediately triggers and processes it.
>>>
>>> Fixes: daf9bfca717e ("ipc: remove thread for async requests")
>>> Cc: stable@dpdk.org
>>>
>>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>>> ---
>>> lib/eal/common/eal_common_proc.c | 12 ++++++++++--
>>> 1 file changed, 10 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
>>> index 799c6e81b0..0ec79336a5 100644
>>> --- a/lib/eal/common/eal_common_proc.c
>>> +++ b/lib/eal/common/eal_common_proc.c
>>> @@ -1187,11 +1187,15 @@ rte_mp_request_async(struct rte_mp_msg *req, const struct timespec *ts,
>>> if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
>>> ret = mp_request_async(eal_mp_socket_path(), copy, param, ts);
>>>
>>> - /* if we didn't send anything, put dummy request on the queue */
>>> + /* if we didn't send anything, put dummy request on the queue
>>> + * and set a minimum-delay alarm so the callback fires immediately.
>>> + */
>>> if (ret == 0 && reply->nb_sent == 0) {
>>> TAILQ_INSERT_TAIL(&pending_requests.requests, dummy,
>>> next);
>>> dummy_used = true;
>>> + if (rte_eal_alarm_set(1, async_reply_handle, dummy) < 0)
>>> + EAL_LOG(ERR, "Fail to set alarm for dummy request");
>>
>> Shouldn't we return an error?
>
> AI suggests this:
>
> if (rte_eal_alarm_set(1, async_reply_handle,
> (void *)(uintptr_t)dummy->id) < 0) {
> EAL_LOG(ERR, "Fail to set alarm for dummy request");
> TAILQ_REMOVE(&pending_requests.requests, dummy, next);
> dummy_used = false;
> ret = -1;
> }
>
Dummy id is not added till later in the series, but I'll integrate this
otherwise.
--
Thanks,
Anatoly
^ permalink raw reply
* [PATCH v7 27/27] net/ice: use common action checks for ACL
From: Anatoly Burakov @ 2026-06-04 16:19 UTC (permalink / raw)
To: dev, Bruce Richardson
In-Reply-To: <cover.1780589913.git.anatoly.burakov@intel.com>
From: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Use the common flow action checking parsing infrastructure for checking
flow actions for ACL filter.
Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
drivers/net/intel/ice/ice_acl_filter.c | 143 +++++++++++++++----------
1 file changed, 84 insertions(+), 59 deletions(-)
diff --git a/drivers/net/intel/ice/ice_acl_filter.c b/drivers/net/intel/ice/ice_acl_filter.c
index 0b50f06c46..30ff1254c9 100644
--- a/drivers/net/intel/ice/ice_acl_filter.c
+++ b/drivers/net/intel/ice/ice_acl_filter.c
@@ -645,60 +645,6 @@ ice_acl_filter_free(struct rte_flow *flow)
flow->rule = NULL;
}
-static int
-ice_acl_parse_action(__rte_unused struct ice_adapter *ad,
- const struct rte_flow_action actions[],
- struct rte_flow_error *error,
- struct ice_acl_conf *filter)
-{
- struct ice_pf *pf = &ad->pf;
- const struct rte_flow_action_queue *act_q;
- uint32_t dest_num = 0;
-
- for (; actions->type != RTE_FLOW_ACTION_TYPE_END; actions++) {
- switch (actions->type) {
- case RTE_FLOW_ACTION_TYPE_VOID:
- break;
- case RTE_FLOW_ACTION_TYPE_DROP:
- dest_num++;
-
- filter->input.dest_ctl =
- ICE_FLTR_PRGM_DESC_DEST_DROP_PKT;
- break;
- case RTE_FLOW_ACTION_TYPE_QUEUE:
- dest_num++;
-
- act_q = actions->conf;
- filter->input.q_index = act_q->index;
- if (filter->input.q_index >=
- pf->dev_data->nb_rx_queues) {
- rte_flow_error_set(error, EINVAL,
- RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid queue for FDIR.");
- return -rte_errno;
- }
- filter->input.dest_ctl =
- ICE_FLTR_PRGM_DESC_DEST_DIRECT_PKT_QINDEX;
- break;
- default:
- rte_flow_error_set(error, EINVAL,
- RTE_FLOW_ERROR_TYPE_ACTION, actions,
- "Invalid action.");
- return -rte_errno;
- }
- }
-
- if (dest_num == 0 || dest_num >= 2) {
- rte_flow_error_set(error, EINVAL,
- RTE_FLOW_ERROR_TYPE_ACTION, actions,
- "Unsupported action combination");
- return -rte_errno;
- }
-
- return 0;
-}
-
static int
ice_acl_parse_pattern(__rte_unused struct ice_adapter *ad,
const struct rte_flow_item pattern[],
@@ -966,6 +912,69 @@ ice_acl_parse_pattern(__rte_unused struct ice_adapter *ad,
return 0;
}
+static int
+ice_acl_parse_action(const struct ci_flow_actions *actions,
+ struct ice_acl_conf *filter,
+ struct rte_flow_error *error)
+{
+ const struct rte_flow_action *act = actions->actions[0];
+
+ switch (act->type) {
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ filter->input.dest_ctl =
+ ICE_FLTR_PRGM_DESC_DEST_DROP_PKT;
+ break;
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ {
+ const struct rte_flow_action_queue *act_q = act->conf;
+
+ filter->input.q_index = act_q->index;
+ filter->input.dest_ctl =
+ ICE_FLTR_PRGM_DESC_DEST_DIRECT_PKT_QINDEX;
+ break;
+ }
+ default:
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, act,
+ "Invalid action.");
+ }
+
+ return 0;
+}
+
+static int
+ice_acl_parse_action_check(const struct ci_flow_actions *actions,
+ const struct ci_flow_actions_check_param *param,
+ struct rte_flow_error *error)
+{
+ struct ice_adapter *ad = param->driver_ctx;
+ struct ice_pf *pf = &ad->pf;
+ const struct rte_flow_action *act = actions->actions[0];
+
+ switch (act->type) {
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ break;
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ {
+ const struct rte_flow_action_queue *act_q = act->conf;
+
+ if (act_q->index >= pf->dev_data->nb_rx_queues) {
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, act,
+ "Invalid queue for ACL.");
+ }
+ break;
+ }
+ default:
+ /* shouldn't happen */
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, act,
+ "Invalid action.");
+ }
+
+ return 0;
+}
+
static int
ice_acl_parse(struct ice_adapter *ad,
struct ice_pattern_match_item *array,
@@ -976,17 +985,33 @@ ice_acl_parse(struct ice_adapter *ad,
void **meta,
struct rte_flow_error *error)
{
+ struct ci_flow_actions parsed_actions = {0};
+ struct ci_flow_actions_check_param param = {
+ .allowed_types = (enum rte_flow_action_type[]){
+ RTE_FLOW_ACTION_TYPE_DROP,
+ RTE_FLOW_ACTION_TYPE_QUEUE,
+ RTE_FLOW_ACTION_TYPE_END
+ },
+ .max_actions = 1,
+ .check = ice_acl_parse_action_check,
+ .driver_ctx = ad,
+ };
struct ice_pf *pf = &ad->pf;
struct ice_acl_conf *filter = &pf->acl.conf;
struct ice_pattern_match_item *item = NULL;
uint64_t input_set;
int ret;
- ret = ci_flow_check_attr(attr, NULL, error);
- if (ret)
- return ret;
-
memset(filter, 0, sizeof(*filter));
+
+ ret = ci_flow_check_attr(attr, NULL, error);
+ if (ret)
+ return ret;
+
+ ret = ci_flow_check_actions(actions, ¶m, &parsed_actions, error);
+ if (ret)
+ return ret;
+
item = ice_search_pattern_match_item(ad, pattern, array, array_len,
error);
if (!item)
@@ -1005,7 +1030,7 @@ ice_acl_parse(struct ice_adapter *ad,
goto error;
}
- ret = ice_acl_parse_action(ad, actions, error, filter);
+ ret = ice_acl_parse_action(&parsed_actions, filter, error);
if (ret)
goto error;
--
2.47.3
^ permalink raw reply related
* [PATCH v7 26/27] net/ice: use common action checks for switch
From: Anatoly Burakov @ 2026-06-04 16:19 UTC (permalink / raw)
To: dev, Bruce Richardson
In-Reply-To: <cover.1780589913.git.anatoly.burakov@intel.com>
From: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Use the common flow action checking parsing infrastructure for checking
flow actions for switch filter.
Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
drivers/net/intel/ice/ice_switch_filter.c | 371 +++++++++++-----------
1 file changed, 185 insertions(+), 186 deletions(-)
diff --git a/drivers/net/intel/ice/ice_switch_filter.c b/drivers/net/intel/ice/ice_switch_filter.c
index 67943b591c..5d9e0062af 100644
--- a/drivers/net/intel/ice/ice_switch_filter.c
+++ b/drivers/net/intel/ice/ice_switch_filter.c
@@ -35,6 +35,8 @@
#define ICE_IPV4_PROTO_NVGRE 0x002F
#define ICE_SW_PRI_BASE 6
+#define ICE_SW_MAX_QUEUES 128
+
#define ICE_SW_INSET_ETHER ( \
ICE_INSET_DMAC | ICE_INSET_SMAC | ICE_INSET_ETHERTYPE)
#define ICE_SW_INSET_MAC_VLAN ( \
@@ -1527,85 +1529,38 @@ ice_switch_parse_pattern(const struct rte_flow_item pattern[],
}
static int
-ice_switch_parse_dcf_action(struct ice_dcf_adapter *ad,
- const struct rte_flow_action *actions,
+ice_switch_parse_dcf_action(const struct rte_flow_action *action,
uint32_t priority,
struct rte_flow_error *error,
struct ice_adv_rule_info *rule_info)
{
const struct rte_flow_action_ethdev *act_ethdev;
- const struct rte_flow_action *action;
const struct rte_eth_dev *repr_dev;
enum rte_flow_action_type action_type;
- uint16_t rule_port_id, backer_port_id;
- for (action = actions; action->type !=
- RTE_FLOW_ACTION_TYPE_END; action++) {
- action_type = action->type;
- switch (action_type) {
- case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
- rule_info->sw_act.fltr_act = ICE_FWD_TO_VSI;
- act_ethdev = action->conf;
-
- if (!rte_eth_dev_is_valid_port(act_ethdev->port_id))
- goto invalid_port_id;
-
- /* For traffic to original DCF port */
- rule_port_id = ad->parent.pf.dev_data->port_id;
-
- if (rule_port_id != act_ethdev->port_id)
- goto invalid_port_id;
-
- rule_info->sw_act.vsi_handle = 0;
-
- break;
-
-invalid_port_id:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid port_id");
- return -rte_errno;
-
- case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
- rule_info->sw_act.fltr_act = ICE_FWD_TO_VSI;
- act_ethdev = action->conf;
-
- if (!rte_eth_dev_is_valid_port(act_ethdev->port_id))
- goto invalid;
-
- repr_dev = &rte_eth_devices[act_ethdev->port_id];
-
- if (!repr_dev->data)
- goto invalid;
-
- rule_port_id = ad->parent.pf.dev_data->port_id;
- backer_port_id = repr_dev->data->backer_port_id;
-
- if (backer_port_id != rule_port_id)
- goto invalid;
-
- rule_info->sw_act.vsi_handle = repr_dev->data->representor_id;
- break;
-
-invalid:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid ethdev_port_id");
- return -rte_errno;
-
- case RTE_FLOW_ACTION_TYPE_DROP:
- rule_info->sw_act.fltr_act = ICE_DROP_PACKET;
- break;
-
- default:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid action type");
- return -rte_errno;
- }
+ action_type = action->type;
+ switch (action_type) {
+ case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
+ rule_info->sw_act.fltr_act = ICE_FWD_TO_VSI;
+ rule_info->sw_act.vsi_handle = 0;
+ break;
+
+ case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
+ rule_info->sw_act.fltr_act = ICE_FWD_TO_VSI;
+ act_ethdev = action->conf;
+ repr_dev = &rte_eth_devices[act_ethdev->port_id];
+ rule_info->sw_act.vsi_handle = repr_dev->data->representor_id;
+ break;
+
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ rule_info->sw_act.fltr_act = ICE_DROP_PACKET;
+ break;
+
+ default:
+ /* Should never reach */
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+ action, "Invalid action type");
+ return -rte_errno;
}
rule_info->sw_act.src = rule_info->sw_act.vsi_handle;
@@ -1621,73 +1576,38 @@ ice_switch_parse_dcf_action(struct ice_dcf_adapter *ad,
static int
ice_switch_parse_action(struct ice_pf *pf,
- const struct rte_flow_action *actions,
+ const struct rte_flow_action *action,
uint32_t priority,
struct rte_flow_error *error,
struct ice_adv_rule_info *rule_info)
{
struct ice_vsi *vsi = pf->main_vsi;
- struct rte_eth_dev_data *dev_data = pf->adapter->pf.dev_data;
const struct rte_flow_action_queue *act_q;
const struct rte_flow_action_rss *act_qgrop;
- uint16_t base_queue, i;
- const struct rte_flow_action *action;
+ uint16_t base_queue;
enum rte_flow_action_type action_type;
- uint16_t valid_qgrop_number[MAX_QGRP_NUM_TYPE] = {
- 2, 4, 8, 16, 32, 64, 128};
base_queue = pf->base_queue + vsi->base_queue;
- for (action = actions; action->type !=
- RTE_FLOW_ACTION_TYPE_END; action++) {
- action_type = action->type;
- switch (action_type) {
- case RTE_FLOW_ACTION_TYPE_RSS:
- act_qgrop = action->conf;
- if (act_qgrop->queue_num <= 1)
- goto error;
- rule_info->sw_act.fltr_act =
- ICE_FWD_TO_QGRP;
- rule_info->sw_act.fwd_id.q_id =
- base_queue + act_qgrop->queue[0];
- for (i = 0; i < MAX_QGRP_NUM_TYPE; i++) {
- if (act_qgrop->queue_num ==
- valid_qgrop_number[i])
- break;
- }
- if (i == MAX_QGRP_NUM_TYPE)
- goto error;
- if ((act_qgrop->queue[0] +
- act_qgrop->queue_num) >
- dev_data->nb_rx_queues)
- goto error1;
- for (i = 0; i < act_qgrop->queue_num - 1; i++)
- if (act_qgrop->queue[i + 1] !=
- act_qgrop->queue[i] + 1)
- goto error2;
- rule_info->sw_act.qgrp_size =
- act_qgrop->queue_num;
- break;
- case RTE_FLOW_ACTION_TYPE_QUEUE:
- act_q = action->conf;
- if (act_q->index >= dev_data->nb_rx_queues)
- goto error;
- rule_info->sw_act.fltr_act =
- ICE_FWD_TO_Q;
- rule_info->sw_act.fwd_id.q_id =
- base_queue + act_q->index;
- break;
-
- case RTE_FLOW_ACTION_TYPE_DROP:
- rule_info->sw_act.fltr_act =
- ICE_DROP_PACKET;
- break;
-
- case RTE_FLOW_ACTION_TYPE_VOID:
- break;
-
- default:
- goto error;
- }
+ action_type = action->type;
+ switch (action_type) {
+ case RTE_FLOW_ACTION_TYPE_RSS:
+ act_qgrop = action->conf;
+ rule_info->sw_act.fltr_act = ICE_FWD_TO_QGRP;
+ rule_info->sw_act.fwd_id.q_id = base_queue + act_qgrop->queue[0];
+ rule_info->sw_act.qgrp_size = act_qgrop->queue_num;
+ break;
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ act_q = action->conf;
+ rule_info->sw_act.fltr_act = ICE_FWD_TO_Q;
+ rule_info->sw_act.fwd_id.q_id = base_queue + act_q->index;
+ break;
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ rule_info->sw_act.fltr_act = ICE_DROP_PACKET;
+ break;
+ default:
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+ action, "Invalid action type or queue number");
+ return -rte_errno;
}
rule_info->sw_act.vsi_handle = vsi->idx;
@@ -1699,65 +1619,120 @@ ice_switch_parse_action(struct ice_pf *pf,
rule_info->priority = ICE_SW_PRI_BASE - priority;
return 0;
-
-error:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid action type or queue number");
- return -rte_errno;
-
-error1:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid queue region indexes");
- return -rte_errno;
-
-error2:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Discontinuous queue region");
- return -rte_errno;
}
static int
-ice_switch_check_action(const struct rte_flow_action *actions,
- struct rte_flow_error *error)
+ice_switch_dcf_action_check(const struct ci_flow_actions *actions,
+ const struct ci_flow_actions_check_param *param,
+ struct rte_flow_error *error)
{
+ struct ice_dcf_adapter *ad = param->driver_ctx;
const struct rte_flow_action *action;
enum rte_flow_action_type action_type;
- uint16_t actions_num = 0;
-
- for (action = actions; action->type !=
- RTE_FLOW_ACTION_TYPE_END; action++) {
- action_type = action->type;
- switch (action_type) {
- case RTE_FLOW_ACTION_TYPE_RSS:
- case RTE_FLOW_ACTION_TYPE_QUEUE:
- case RTE_FLOW_ACTION_TYPE_DROP:
- case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
- case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
- actions_num++;
- break;
- case RTE_FLOW_ACTION_TYPE_VOID:
- continue;
- default:
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid action type");
- return -rte_errno;
+ const struct rte_flow_action_ethdev *act_ethdev;
+ const struct rte_eth_dev *repr_dev;
+
+ action = actions->actions[0];
+ action_type = action->type;
+
+ switch (action_type) {
+ case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
+ case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
+ {
+ uint16_t expected_port_id, backer_port_id;
+ act_ethdev = action->conf;
+
+ if (!rte_eth_dev_is_valid_port(act_ethdev->port_id))
+ goto invalid_port_id;
+
+ expected_port_id = ad->parent.pf.dev_data->port_id;
+
+ if (action_type == RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR) {
+ if (expected_port_id != act_ethdev->port_id)
+ goto invalid_port_id;
+ } else {
+ repr_dev = &rte_eth_devices[act_ethdev->port_id];
+
+ if (!repr_dev->data)
+ goto invalid_port_id;
+
+ backer_port_id = repr_dev->data->backer_port_id;
+
+ if (backer_port_id != expected_port_id)
+ goto invalid_port_id;
}
+
+ break;
+invalid_port_id:
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, action,
+ "Invalid port ID");
+ }
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ break;
+ default:
+ /* Should never reach */
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, action,
+ "Invalid action type");
}
- if (actions_num != 1) {
- rte_flow_error_set(error,
- EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
- actions,
- "Invalid action number");
- return -rte_errno;
+ return 0;
+}
+
+static int
+ice_switch_action_check(const struct ci_flow_actions *actions,
+ const struct ci_flow_actions_check_param *param,
+ struct rte_flow_error *error)
+{
+ struct ice_adapter *ad = param->driver_ctx;
+ struct ice_pf *pf = &ad->pf;
+ struct rte_eth_dev_data *dev_data = pf->dev_data;
+ const struct rte_flow_action *action = actions->actions[0];
+
+ switch (action->type) {
+ case RTE_FLOW_ACTION_TYPE_RSS:
+ {
+ const struct rte_flow_action_rss *act_qgrop;
+ act_qgrop = action->conf;
+
+ /* Check bounds on number of queues */
+ if (act_qgrop->queue_num < 2 || act_qgrop->queue_num > ICE_SW_MAX_QUEUES)
+ goto err_rss;
+
+ /* must be power of 2 */
+ if (!rte_is_power_of_2(act_qgrop->queue_num))
+ goto err_rss;
+
+ /* queues are monotonous and contiguous so check last queue */
+ if ((act_qgrop->queue[0] + act_qgrop->queue_num) > dev_data->nb_rx_queues)
+ goto err_rss;
+
+ break;
+err_rss:
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, action,
+ "Invalid queue region");
+ }
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ {
+ const struct rte_flow_action_queue *act_q;
+ act_q = action->conf;
+ if (act_q->index >= dev_data->nb_rx_queues) {
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, action,
+ "Invalid queue");
+ }
+
+ break;
+ }
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ break;
+ default:
+ /* Should never reach */
+ return rte_flow_error_set(error, EINVAL,
+ RTE_FLOW_ERROR_TYPE_ACTION, action,
+ "Invalid action type");
}
return 0;
@@ -1788,11 +1763,39 @@ ice_switch_parse_pattern_action(struct ice_adapter *ad,
struct ci_flow_attr_check_param attr_param = {
.allow_priority = true,
};
+ struct ci_flow_actions parsed_actions = {0};
+ struct ci_flow_actions_check_param dcf_param = {
+ .allowed_types = (enum rte_flow_action_type[]){
+ RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT,
+ RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR,
+ RTE_FLOW_ACTION_TYPE_DROP,
+ RTE_FLOW_ACTION_TYPE_END
+ },
+ .max_actions = 1,
+ .check = ice_switch_dcf_action_check,
+ };
+ struct ci_flow_actions_check_param param = {
+ .allowed_types = (enum rte_flow_action_type[]){
+ RTE_FLOW_ACTION_TYPE_RSS,
+ RTE_FLOW_ACTION_TYPE_QUEUE,
+ RTE_FLOW_ACTION_TYPE_DROP,
+ RTE_FLOW_ACTION_TYPE_END
+ },
+ .max_actions = 1,
+ .check = ice_switch_action_check,
+ .driver_ctx = ad,
+ .rss_queues_contig = true,
+ };
ret = ci_flow_check_attr(attr, &attr_param, error);
if (ret)
return ret;
+ ret = ci_flow_check_actions(actions, (ad->hw.dcf_enabled) ? &dcf_param : ¶m,
+ &parsed_actions, error);
+ if (ret)
+ goto error;
+
/* Allow only two priority values - 0 or 1 */
if (attr->priority > 1) {
rte_flow_error_set(error, EINVAL,
@@ -1870,16 +1873,12 @@ ice_switch_parse_pattern_action(struct ice_adapter *ad,
memset(&rule_info, 0, sizeof(rule_info));
rule_info.tun_type = tun_type;
- ret = ice_switch_check_action(actions, error);
- if (ret)
- goto error;
-
if (ad->hw.dcf_enabled)
- ret = ice_switch_parse_dcf_action((void *)ad, actions, attr->priority,
- error, &rule_info);
+ ret = ice_switch_parse_dcf_action(parsed_actions.actions[0],
+ attr->priority, error, &rule_info);
else
- ret = ice_switch_parse_action(pf, actions, attr->priority, error,
- &rule_info);
+ ret = ice_switch_parse_action(pf, parsed_actions.actions[0],
+ attr->priority, error, &rule_info);
if (ret)
goto error;
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox