DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] net/mlx5: use port index as representor index
From: Dariusz Sosnowski @ 2026-05-22 10:19 UTC (permalink / raw)
  To: Viacheslav Ovsiienko, Bing Zhao, Ori Kam, Suanming Mou,
	Matan Azrad
  Cc: dev, stable

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Dariusz Sosnowski <dsosnowski@nvidia.com>
Acked-by: Bing Zhao <bingz@nvidia.com>
---
 drivers/net/mlx5/linux/mlx5_os.c |   6 +-
 drivers/net/mlx5/mlx5.h          |  19 +++
 drivers/net/mlx5/mlx5_ethdev.c   | 284 +++++++++++++++++++------------
 3 files changed, 199 insertions(+), 110 deletions(-)

diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
index 0fc721592b..5305523c1b 100644
--- a/drivers/net/mlx5/linux/mlx5_os.c
+++ b/drivers/net/mlx5/linux/mlx5_os.c
@@ -1677,9 +1677,13 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 		err = ENOMEM;
 		goto error;
 	}
+	priv->port_info.type = spawn->info.name_type;
+	priv->port_info.ctrl_num = spawn->info.ctrl_num;
+	priv->port_info.pf_num = spawn->info.pf_num;
+	priv->port_info.port_num = spawn->info.port_name;
 	if (priv->representor) {
 		eth_dev->data->dev_flags |= RTE_ETH_DEV_REPRESENTOR;
-		eth_dev->data->representor_id = priv->representor_id;
+		eth_dev->data->representor_id = eth_dev->data->port_id;
 		MLX5_ETH_FOREACH_DEV(port_id, dpdk_dev) {
 			struct mlx5_priv *opriv =
 				rte_eth_devices[port_id].data->dev_private;
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 49a0c03544..23803b450b 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -1984,6 +1984,24 @@ struct mlx5_quota_ctx {
 	struct mlx5_indexed_pool *quota_ipool; /* Manage quota objects */
 };
 
+/* Stores info parsed from phys_port_name related to given DPDK port. */
+struct mlx5_representor_info {
+	enum mlx5_nl_phys_port_name_type type;
+	/* PCI controller index. 0 if no controller was reported in phys_port_name. */
+	int32_t ctrl_num;
+	/* PF index. */
+	int32_t pf_num;
+	/*
+	 * Representor number:
+	 *
+	 * - For VF/SF - VF/SF index.
+	 * - For PFHPF - -1.
+	 * - For uplink - physical port index.
+	 * - For others - VF representor is assumed, so VF index.
+	 */
+	int32_t port_num;
+};
+
 struct mlx5_nta_sample_ctx;
 struct mlx5_priv {
 	struct rte_eth_dev_data *dev_data;  /* Pointer to device data. */
@@ -2019,6 +2037,7 @@ struct mlx5_priv {
 	uint32_t vport_meta_tag; /* Used for vport index match ove VF LAG. */
 	uint32_t vport_meta_mask; /* Used for vport index field match mask. */
 	uint16_t representor_id; /* UINT16_MAX if not a representor. */
+	struct mlx5_representor_info port_info;
 	int32_t pf_bond; /* >=0, representor owner PF index in bonding. */
 	int32_t mpesw_owner; /* >=0, representor owner PF index in MPESW. */
 	int32_t mpesw_port; /* Related port index of MPESW device. < 0 - no MPESW. */
diff --git a/drivers/net/mlx5/mlx5_ethdev.c b/drivers/net/mlx5/mlx5_ethdev.c
index a29cdeeb50..e14b7f148b 100644
--- a/drivers/net/mlx5/mlx5_ethdev.c
+++ b/drivers/net/mlx5/mlx5_ethdev.c
@@ -345,6 +345,23 @@ mlx5_dev_get_max_wq_size(struct mlx5_dev_ctx_shared *sh)
 	return max_wqe;
 }
 
+/**
+ * Get switch port ID for given DPDK port.
+ *
+ * @param dev
+ *   Pointer to Ethernet device structure.
+ * @return
+ *   Switch port ID reported through rte_eth_dev_info_get().
+ */
+static uint16_t
+mlx5_dev_switch_info_port_id_get(struct rte_eth_dev *dev)
+{
+	if (rte_eth_dev_is_repr(dev))
+		return dev->data->port_id;
+
+	return UINT16_MAX;
+}
+
 /**
  * DPDK callback to get information about the device.
  *
@@ -401,7 +418,7 @@ mlx5_dev_infos_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *info)
 		info->dev_capa |= RTE_ETH_DEV_CAPA_RXQ_SHARE;
 	info->switch_info.name = dev->data->name;
 	info->switch_info.domain_id = priv->domain_id;
-	info->switch_info.port_id = priv->representor_id;
+	info->switch_info.port_id = mlx5_dev_switch_info_port_id_get(dev);
 	info->switch_info.rx_domain = 0; /* No sub Rx domains. */
 	if (priv->representor) {
 		uint16_t port_id;
@@ -472,14 +489,162 @@ mlx5_representor_id_encode(const struct mlx5_switch_info *info,
 	return MLX5_REPRESENTOR_ID(pf, type, repr);
 }
 
+static unsigned int
+mlx5_representor_info_count_one(struct mlx5_priv *priv)
+{
+	switch (priv->port_info.type) {
+	case MLX5_PHYS_PORT_NAME_TYPE_PFHPF:
+		return 2;
+	case MLX5_PHYS_PORT_NAME_TYPE_UPLINK:
+		/* Only representor uplinks should be reported */
+		if (!priv->representor)
+			return 0;
+		return 1;
+	case MLX5_PHYS_PORT_NAME_TYPE_NOTSET:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_LEGACY:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_PFVF:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_PFSF:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_UNKNOWN:
+		/* FALLTHROUGH */
+	default:
+		return 1;
+	}
+}
+
+static unsigned int
+mlx5_representor_info_count(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	uint16_t port_id;
+	unsigned int count = 0;
+
+	MLX5_ETH_FOREACH_DEV(port_id, dev->device) {
+		struct mlx5_priv *opriv = rte_eth_devices[port_id].data->dev_private;
+
+		if (!opriv ||
+		    opriv->sh != priv->sh ||
+		    opriv->domain_id != priv->domain_id)
+			continue;
+
+		count += mlx5_representor_info_count_one(opriv);
+	}
+
+	return count;
+}
+
+static void
+mlx5_representor_info_fill_one(struct mlx5_priv *priv,
+			       struct rte_eth_representor_info *info)
+{
+	struct rte_eth_representor_range *range;
+	unsigned int count;
+
+	count = mlx5_representor_info_count_one(priv);
+	if (count == 0)
+		return;
+
+	if (info->nb_ranges + count > info->nb_ranges_alloc) {
+		DRV_LOG(ERR, "port %u representor info already full", priv->dev_data->port_id);
+		return;
+	}
+
+	range = &info->ranges[info->nb_ranges];
+
+	switch (priv->port_info.type) {
+	case MLX5_PHYS_PORT_NAME_TYPE_UPLINK:
+		range->type = RTE_ETH_REPRESENTOR_PF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.port_num;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%d", range->pf);
+		break;
+	case MLX5_PHYS_PORT_NAME_TYPE_PFSF:
+		/* Secondly, fill in SF variant. */
+		range->type = RTE_ETH_REPRESENTOR_SF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->sf = priv->port_info.port_num;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dsf", range->pf);
+		break;
+	case MLX5_PHYS_PORT_NAME_TYPE_PFHPF:
+		/*
+		 * Host PF can be probed either through VF(0xffff) or SF(0xffff).
+		 * Firstly fill in VF variant.
+		 */
+		range->type = RTE_ETH_REPRESENTOR_VF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->vf = UINT16_MAX;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dvf", range->pf);
+
+		/* Move the SF variant. */
+		range++;
+
+		/* Fill in SF variant. */
+		range->type = RTE_ETH_REPRESENTOR_SF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->sf = UINT16_MAX;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dsf", range->pf);
+		break;
+	case MLX5_PHYS_PORT_NAME_TYPE_PFVF:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_NOTSET:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_LEGACY:
+		/* FALLTHROUGH */
+	case MLX5_PHYS_PORT_NAME_TYPE_UNKNOWN:
+		range->type = RTE_ETH_REPRESENTOR_VF;
+		range->controller = priv->port_info.ctrl_num;
+		range->pf = priv->port_info.pf_num;
+		range->vf = priv->port_info.port_num;
+		range->id_base = priv->dev_data->port_id;
+		range->id_end = range->id_base;
+		snprintf(range->name, sizeof(range->name), "pf%dvf", range->pf);
+		break;
+	}
+
+	info->nb_ranges += count;
+}
+
+static unsigned int
+mlx5_representor_info_fill(struct rte_eth_dev *dev,
+			   struct rte_eth_representor_info *info)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	uint16_t port_id;
+
+	info->controller = priv->port_info.ctrl_num;
+	info->pf = RTE_DEV_TO_PCI(dev->device)->addr.function;
+
+	MLX5_ETH_FOREACH_DEV(port_id, dev->device) {
+		struct mlx5_priv *opriv = rte_eth_devices[port_id].data->dev_private;
+
+		if (!opriv ||
+		    opriv->sh != priv->sh ||
+		    opriv->domain_id != priv->domain_id)
+			continue;
+
+		mlx5_representor_info_fill_one(opriv, info);
+	}
+
+	return info->nb_ranges;
+}
+
 /**
  * DPDK callback to get information about representor.
  *
- * Representor ID bits definition:
- *   vf/sf: 12
- *   type: 2
- *   pf: 2
- *
  * @param dev
  *   Pointer to Ethernet device structure.
  * @param[out] info
@@ -492,110 +657,11 @@ int
 mlx5_representor_info_get(struct rte_eth_dev *dev,
 			  struct rte_eth_representor_info *info)
 {
-	struct mlx5_priv *priv = dev->data->dev_private;
-	/* Representor types: PF, VF, HPF@VF, SF and HPF@SF, total 5. */
-	int n_type = RTE_ETH_REPRESENTOR_PF + 2; /* Maximal type + 2 for HPFs. */
-	int n_pf = 8; /* Maximal number of PFs. */
-	int i = 0, pf;
-	int n_entries;
-
 	if (info == NULL)
-		goto out;
-
-	n_entries = n_type * n_pf;
-	if ((uint32_t)n_entries > info->nb_ranges_alloc)
-		n_entries = info->nb_ranges_alloc;
-
-	info->controller = 0;
-	info->pf = 0;
-	if (mlx5_is_port_on_mpesw_device(priv)) {
-		info->pf = priv->mpesw_port;
-		for (i = 0; i < n_pf; i++) {
-			/* PF range, both ports will show the same information. */
-			info->ranges[i].type = RTE_ETH_REPRESENTOR_PF;
-			info->ranges[i].controller = 0;
-			info->ranges[i].pf = priv->mpesw_owner + i + 1;
-			info->ranges[i].vf = 0;
-			/*
-			 * The representor indexes should be the values set of "priv->mpesw_port".
-			 * In the real case now, only 1 PF/UPLINK representor is supported.
-			 * The port index will always be the value of "owner + 1".
-			 */
-			info->ranges[i].id_base =
-				MLX5_REPRESENTOR_ID(priv->mpesw_owner,
-						    info->ranges[i].type,
-						    info->ranges[i].pf);
-			info->ranges[i].id_end =
-				MLX5_REPRESENTOR_ID(priv->mpesw_owner,
-						    info->ranges[i].type,
-						    info->ranges[i].pf);
-			snprintf(info->ranges[i].name,
-				 sizeof(info->ranges[i].name),
-				 "pf%d", info->ranges[i].pf);
-		}
-	} else if (priv->pf_bond >= 0)
-		info->pf = priv->pf_bond;
-	for (pf = 0; pf < n_pf; ++pf) {
-		/* VF range. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_VF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = 0;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, 0);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dvf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-		/* HPF range of VF type. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_VF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = UINT16_MAX;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dvf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-		/* SF range. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_SF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = 0;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, 0);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dsf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-		/* HPF range of SF type. */
-		info->ranges[i].type = RTE_ETH_REPRESENTOR_SF;
-		info->ranges[i].controller = 0;
-		info->ranges[i].pf = pf;
-		info->ranges[i].vf = UINT16_MAX;
-		info->ranges[i].id_base =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		info->ranges[i].id_end =
-			MLX5_REPRESENTOR_ID(pf, info->ranges[i].type, -1);
-		snprintf(info->ranges[i].name,
-			 sizeof(info->ranges[i].name), "pf%dsf", pf);
-		i++;
-		if (i == n_entries)
-			break;
-	}
-	info->nb_ranges = i;
-out:
-	return n_type * n_pf;
+		return mlx5_representor_info_count(dev);
+
+	return mlx5_representor_info_fill(dev, info);
+
 }
 
 /**
-- 
2.47.3


^ permalink raw reply related

* RE: [PATCH] net/iavf: fix VF reset race and stale ARQ message handling
From: Loftus, Ciara @ 2026-05-22 10:26 UTC (permalink / raw)
  To: Talluri, ChaitanyababuX, dev@dpdk.org, Richardson, Bruce,
	Singh, Aman Deep
  Cc: Wani, Shaiq, Talluri, ChaitanyababuX, stable@dpdk.org
In-Reply-To: <20260518054227.110701-1-chaitanyababux.talluri@intel.com>

> Subject: [PATCH] net/iavf: fix VF reset race and stale ARQ message handling
> 
> During VF reset, multiple issues can lead to initialization instability.
> 
> The first issue is a race condition in iavf_check_vf_reset_done(),
> where VFR state VFACTIVE is treated as both "reset not started" and
> "reset completed". This can allow the VF to proceed before the PF
> has acknowledged the reset, resulting in inconsistent initialization.
> 
> The second issue is the presence of stale messages in the Admin
> Receive Queue (ARQ) after VF reset. These may include opcode 0
> (VIRTCHNL_OP_UNKNOWN) or responses to commands issued before reset,
> which can interfere with API negotiation and cause command mismatch
> errors.
> 
> Additionally, opcode 0 messages generate excessive warning logs,
> causing unnecessary noise during initialization.
> 
> The solution involves:
> 1. Introducing a two-phase reset wait mechanism in
>    iavf_check_vf_reset_done():
>    - Wait for RSTAT to leave VFACTIVE to confirm reset start.
>    - Wait for RSTAT to return to VFACTIVE or COMPLETED to confirm
>      reset completion.
> 
> 2. Draining stale ARQ messages after admin queue initialization
>    and before issuing commands in iavf_init_vf().
> 
> 3. Downgrading opcode 0 message logging to DEBUG level while
>    preserving mismatch detection for other opcodes.
> 
> 4. Refactoring reset-start detection and ARQ drain logic into
>    helper functions (iavf_wait_for_reset_start() and
>    iavf_drain_arq()) to improve readability and reuse.
> 
> This ensures reliable VF reset handling and stable initialization.
> 
> Fixes: 28a1a72eac26 ("net/iavf: add VF initiated reset")

Is the fix only for the VF initiated reset sequence, or does it aim to
fix other reset sequences as well eg. PF initiated?

> Cc: stable@dpdk.org
> 
> Signed-off-by: Talluri Chaitanyababu <chaitanyababux.talluri@intel.com>
> ---
>  drivers/net/intel/iavf/iavf_ethdev.c | 54
> ++++++++++++++++++++++++++++
>  drivers/net/intel/iavf/iavf_vchnl.c  | 16 +++++++--
>  2 files changed, 67 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/intel/iavf/iavf_ethdev.c
> b/drivers/net/intel/iavf/iavf_ethdev.c
> index 1eca20bc9a..692e4455dc 100644
> --- a/drivers/net/intel/iavf/iavf_ethdev.c
> +++ b/drivers/net/intel/iavf/iavf_ethdev.c
> @@ -2002,11 +2002,36 @@ iavf_dev_rx_queue_intr_disable(struct
> rte_eth_dev *dev, uint16_t queue_id)
>  	return 0;
>  }
> 
> +/* Wait until PF acknowledges VF reset (RSTAT leaves VFACTIVE) */
> +static int
> +iavf_wait_for_reset_start(struct iavf_hw *hw)
> +{
> +	int i;
> +	uint32_t rstat;
> +
> +	for (i = 0; i < 100; i++) {
> +		rte_delay_ms(10);
> +
> +		rstat = IAVF_READ_REG(hw, IAVF_VFGEN_RSTAT);
> +		rstat &= IAVF_VFGEN_RSTAT_VFR_STATE_MASK;
> +		rstat >>= IAVF_VFGEN_RSTAT_VFR_STATE_SHIFT;
> +
> +		if (rstat != VIRTCHNL_VFR_VFACTIVE)
> +			return 0;
> +	}
> +
> +	return -1;
> +}
> +
>  static int
>  iavf_check_vf_reset_done(struct iavf_hw *hw)
>  {
>  	int i, reset;
> 
> +	/* Phase 1: wait for reset to start (leave VFACTIVE) */
> +	if (iavf_wait_for_reset_start(hw) != 0)
> +		PMD_DRV_LOG(DEBUG, "VF reset did not start within
> timeout");

There are multiple paths that lead to the iavf_wait_for_reset_start
function being called, including during device initialisation
iavf_init_vf -> iavf_check_vf_reset_done. In this case, we are not
expecting a reset event, just making sure we are not trying to
initialise a VF in the middle of a reset. I think the
iavf_wait_for_reset_start is not needed here, and the associated log
would be incorrect. Can you check that
iavf_wait_for_reset_start is only triggered when absolutely necessary.
Consider these events: device init, pf initiated reset, vf initiated
reset, queue pair change (iavf_queues_req_reset)

> +
>  	for (i = 0; i < IAVF_RESET_WAIT_CNT; i++) {
>  		reset = IAVF_READ_REG(hw, IAVF_VFGEN_RSTAT) &
>  			IAVF_VFGEN_RSTAT_VFR_STATE_MASK;
> @@ -2517,6 +2542,31 @@ iavf_init_proto_xtr(struct rte_eth_dev *dev)
>  	}
>  }
> 
> +/* Drain stale Admin Receive Queue messages after reset */
> +static void
> +iavf_drain_arq(struct iavf_hw *hw, struct iavf_info *vf)
> +{
> +	struct iavf_arq_event_info event;
> +	int drain_count = 0;
> +
> +	memset(&event, 0, sizeof(event));
> +	event.msg_buf = vf->aq_resp;
> +
> +	while (drain_count < IAVF_AQ_LEN) {
> +		event.buf_len = IAVF_AQ_BUF_SZ;
> +
> +		if (iavf_clean_arq_element(hw, &event, NULL) !=
> IAVF_SUCCESS)
> +			break;
> +
> +		drain_count++;
> +	}
> +
> +	if (drain_count > 0)
> +		PMD_INIT_LOG(DEBUG,
> +				"Drained %d stale ARQ messages",
> +				drain_count);
> +}
> +
>  static int
>  iavf_init_vf(struct rte_eth_dev *dev)
>  {
> @@ -2558,6 +2608,10 @@ iavf_init_vf(struct rte_eth_dev *dev)
>  		PMD_INIT_LOG(ERR, "unable to allocate vf_aq_resp
> memory");
>  		goto err_aq;
>  	}
> +
> +	/* Drain stale ARQ messages after VF reset */
> +	iavf_drain_arq(hw, vf);

Would we expect stale messages in the adminq here, it was only just
allocated a few lines above?

> +
>  	if (iavf_check_api_version(adapter) != 0) {
>  		PMD_INIT_LOG(ERR, "check_api version failed");
>  		goto err_api;
> diff --git a/drivers/net/intel/iavf/iavf_vchnl.c
> b/drivers/net/intel/iavf/iavf_vchnl.c
> index 08dd6f2d7f..a014be8b57 100644
> --- a/drivers/net/intel/iavf/iavf_vchnl.c
> +++ b/drivers/net/intel/iavf/iavf_vchnl.c
> @@ -296,11 +296,21 @@ iavf_read_msg_from_pf(struct iavf_adapter
> *adapter, uint16_t buf_len,
>  					__func__, vpe->event);
>  		}
>  	}  else {
> -		/* async reply msg on command issued by vf previously */
> +		/* Async reply for previously issued VF command.
> +		 * Stale messages from before reset are ignored, and polling
> +		 * continues until the expected response is received.
> +		 */
>  		result = IAVF_MSG_CMD;
>  		if (opcode != vf->pend_cmd) {
> -			PMD_DRV_LOG(WARNING, "command mismatch,
> expect %u, get %u",
> -					vf->pend_cmd, opcode);
> +			if (opcode == VIRTCHNL_OP_UNKNOWN)
> +				PMD_DRV_LOG(DEBUG,
> +						"Ignoring stale msg (opcode
> 0), pending cmd %u",
> +						vf->pend_cmd);
> +			else
> +				PMD_DRV_LOG(WARNING,
> +						"command mismatch, expect
> %u, get %u",
> +						vf->pend_cmd, opcode);
> +
>  			result = IAVF_MSG_ERR;
>  		}
>  	}
> --
> 2.43.0


^ permalink raw reply

* [PATCH v2 0/3] extend interactive telemetry script
From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw)
  To: dev; +Cc: fengchengwen, Bruce Richardson
In-Reply-To: <20260521153913.82634-1-bruce.richardson@intel.com>

To simplify interactive telemetry script for general use, i.e. not from
other scripts, we can add two new features to it:

1. Support for FOREACH to allow gathering a set of output values across
   a list of ports or devices, e.g. ethdevs or rawdevs.
2. Support having predefined aliases in a file in the user's home
   directory to simplify the use of more complicated FOREACH commands.

Putting these together, we can create new commands such as "eth_names".

  bruce@host:$ cat ~/.dpdk_telemetry_aliases
  eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name

  bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq
  [
    {
      "index": 0,
      "name": "0000:16:00.0"
    },
    {
      "index": 1,
      "name": "0000:16:00.1"
    }
  ]

---
v2: added third patch with "help" command giving more details on
    how to use the various commands.

Bruce Richardson (3):
  usertools/telemetry: add a FOREACH command
  usertools/telemetry: support using aliases for long commands
  usertools/telemetry: add help support

 doc/guides/howto/telemetry.rst |  76 ++++++++++
 usertools/dpdk-telemetry.py    | 260 ++++++++++++++++++++++++++++++++-
 2 files changed, 330 insertions(+), 6 deletions(-)

--
2.53.0


^ permalink raw reply

* [PATCH v2 1/3] usertools/telemetry: add a FOREACH command
From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw)
  To: dev; +Cc: fengchengwen, Bruce Richardson
In-Reply-To: <20260522133714.133268-1-bruce.richardson@intel.com>

To simplify querying data from multiple devices, e.g. across ethdevs, or
dmadevs, add a FOREACH command to the python script, allowing you to
run, e.g. /ethdev/list, and then run a second command for each item in
the list, gathering the relevant output values, optionally including an
index counter.

Simple examples are given in the documentation:

  --> FOREACH /ethdev/list /ethdev/stats .opackets
  [0, 0]

  --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets
  [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}]

  --> FOREACH i /ethdev/list /ethdev/info,$i .name
  [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}]

  --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets
  [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}]

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 doc/guides/howto/telemetry.rst |  42 +++++++++++
 usertools/dpdk-telemetry.py    | 128 ++++++++++++++++++++++++++++++++-
 2 files changed, 167 insertions(+), 3 deletions(-)

diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst
index 0464c431fe..4bf48c635e 100644
--- a/doc/guides/howto/telemetry.rst
+++ b/doc/guides/howto/telemetry.rst
@@ -88,6 +88,48 @@ and query information using the telemetry client python script.
        {"/help": {"/ethdev/xstats": "Returns the extended stats for a port.
        Parameters: int port_id"}}
 
+   * Run a compound query using ``FOREACH``.
+
+     The ``FOREACH`` command runs a list command, iterates each returned item,
+     runs a second command for each item, and emits combined JSON output.
+
+     Start with the simplest form (no loop variable)::
+
+        FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...]
+
+     To include numbered output, use a loop variable::
+
+        FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...]
+
+     Notes:
+
+     - Field selectors are whitespace-separated tokens, each starting with ``.``.
+     - In no-variable mode, the iter command is called as ``/<iter_cmd>,<item>``.
+     - In loop-variable mode, use ``$<var>`` in the iter command where the
+       item value should be substituted.
+
+     Examples::
+
+        --> FOREACH /ethdev/list /ethdev/stats .opackets
+        [0, 0]
+
+        --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets
+        [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}]
+
+        --> FOREACH i /ethdev/list /ethdev/info,$i .name
+        [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}]
+
+        --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets
+        [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}]
+
+     Output behavior:
+
+     - Without loop variable and one field: returns an array of values.
+     - Without loop variable and multiple fields: returns an array of objects
+       containing named value fields.
+     - With loop variable: returns an array of objects containing the loop
+       variable field and requested value fields.
+
 
 Connecting to Different DPDK Processes
 --------------------------------------
diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py
index 09258a1f7e..2de10cff69 100755
--- a/usertools/dpdk-telemetry.py
+++ b/usertools/dpdk-telemetry.py
@@ -23,6 +23,130 @@
 CMDS = []
 
 
+def send_command(sock, cmd, output_buf_len, echo=False, pretty=False):
+    """Send a telemetry command and return the parsed JSON reply"""
+    sock.send(cmd.encode())
+    return read_socket(sock, output_buf_len, echo, pretty)
+
+
+def get_cmd_payload(reply, cmd):
+    """Return the payload for a command response if present"""
+    if isinstance(reply, dict) and len(reply) == 1:
+        return next(iter(reply.values()))
+    return None
+
+
+def get_path_value(payload, path):
+    """Resolve a dotted path (e.g. '.name' or '.a.b') from a JSON payload"""
+    if not path:
+        return payload
+
+    keys = [k for k in path.lstrip(".").split(".") if k]
+    val = payload
+    for key in keys:
+        if not isinstance(val, dict) or key not in val:
+            return None
+        val = val[key]
+    return val
+
+
+def parse_selectors(selector_text):
+    """Parse whitespace-separated dotted selectors"""
+    selectors = selector_text.split()
+    if not selectors:
+        print("Invalid FOREACH syntax: missing selector")
+        return None
+    if any(not selector.startswith(".") for selector in selectors):
+        print("Invalid FOREACH syntax: selector must start with '.'")
+        return None
+    return selectors
+
+
+def parse_foreach(text):
+    """Parse FOREACH [<var>] /<cmd> /<parameterized cmd> .<value> [.<value> ...]"""
+    try:
+        tokens = text.split(None, 3)
+    except ValueError:
+        print("Invalid FOREACH syntax")
+        return None
+
+    if len(tokens) != 4:
+        print("Invalid FOREACH syntax")
+        return None
+
+    _, arg1, arg2, arg3 = tokens
+    if arg1.startswith("/"):
+        var_name = None
+        list_cmd = arg1
+        iter_cmd = arg2
+        selector_text = arg3
+    else:
+        var_name = arg1
+        list_cmd = arg2
+        try:
+            iter_cmd, selector_text = arg3.split(None, 1)
+        except ValueError:
+            print("Invalid FOREACH syntax")
+            return None
+
+    if not list_cmd.startswith("/") or not iter_cmd.startswith("/"):
+        print("Invalid FOREACH syntax: commands must start with '/'")
+        return None
+
+    selectors = parse_selectors(selector_text)
+    if selectors is None:
+        return None
+
+    return var_name, list_cmd, iter_cmd, selectors
+
+
+def build_foreach_result(item, var_name, payload, selectors):
+    """Build one FOREACH result entry based on selector count and index mode"""
+    values = {selector.lstrip("."): get_path_value(payload, selector) for selector in selectors}
+
+    if var_name is None and len(selectors) == 1:
+        return next(iter(values.values()))
+    if var_name is None:
+        return values
+
+    return {var_name: item, **values}
+
+
+def handle_foreach(sock, output_buf_len, text, pretty=False):
+    """Handle FOREACH queries and print telemetry-like JSON array output"""
+    parsed = parse_foreach(text)
+    if parsed is None:
+        return
+    var_name, list_cmd, iter_cmd, selectors = parsed
+
+    list_reply = send_command(sock, list_cmd, output_buf_len)
+    values = get_cmd_payload(list_reply, list_cmd)
+    if not isinstance(values, list):
+        print("FOREACH source command did not return a JSON array")
+        return
+
+    output = []
+    for item in values:
+        if var_name is None:
+            cmd = "{},{}".format(iter_cmd, item)
+        else:
+            cmd = iter_cmd.replace("$" + var_name, str(item))
+        item_reply = send_command(sock, cmd, output_buf_len)
+        item_payload = get_cmd_payload(item_reply, cmd)
+        output.append(build_foreach_result(item, var_name, item_payload, selectors))
+
+    indent = 2 if pretty else None
+    print(json.dumps(output, indent=indent))
+
+
+def handle_command(sock, output_buf_len, text, pretty=False):
+    """Execute a user command if recognized"""
+    if text.startswith("/"):
+        send_command(sock, text, output_buf_len, echo=True, pretty=pretty)
+    elif text.startswith("FOREACH "):
+        handle_foreach(sock, output_buf_len, text, pretty)
+
+
 def read_socket(sock, buf_len, echo=True, pretty=False):
     """Read data from socket and return it in JSON format"""
     reply = sock.recv(buf_len).decode()
@@ -140,9 +264,7 @@ def handle_socket(args, path):
     try:
         text = input(prompt).strip()
         while text != "quit":
-            if text.startswith("/"):
-                sock.send(text.encode())
-                read_socket(sock, output_buf_len, pretty=prompt)
+            handle_command(sock, output_buf_len, text, pretty=prompt)
             text = input(prompt).strip()
     except EOFError:
         pass
-- 
2.53.0


^ permalink raw reply related

* [PATCH v2 2/3] usertools/telemetry: support using aliases for long commands
From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw)
  To: dev; +Cc: fengchengwen, Bruce Richardson
In-Reply-To: <20260522133714.133268-1-bruce.richardson@intel.com>

Similarly to how shell aliases work, allow specifying of short alias
commands for dpdk-telemetry.py script. The aliases are read from
"$HOME/.dpdk_telemetry_aliases" at startup.

Some examples of use from the docs. Alias file contents:

  # Basic shortcuts
  ls=/ethdev/list
  names=FOREACH i /ethdev/list /ethdev/info,$i .name
  q=quit

Alias use:

   --> ls
  {"/ethdev/list": [0, 1]}

  --> names
  [{"i": 0, "name": "0000:

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 doc/guides/howto/telemetry.rst | 34 +++++++++++++++
 usertools/dpdk-telemetry.py    | 75 ++++++++++++++++++++++++++++++++--
 2 files changed, 105 insertions(+), 4 deletions(-)

diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst
index 4bf48c635e..072289aec8 100644
--- a/doc/guides/howto/telemetry.rst
+++ b/doc/guides/howto/telemetry.rst
@@ -130,6 +130,40 @@ and query information using the telemetry client python script.
      - With loop variable: returns an array of objects containing the loop
        variable field and requested value fields.
 
+   * Use command aliases.
+
+     The telemetry script can load aliases at startup from::
+
+        $HOME/.dpdk_telemetry_aliases
+
+     Each alias entry must be in ``alias=command`` format.
+     Empty lines and lines starting with ``#`` are ignored.
+
+     Example alias file::
+
+        # Basic shortcuts
+        ls=/ethdev/list
+        names=FOREACH i /ethdev/list /ethdev/info,$i .name
+        q=quit
+
+     Alias behavior is intentionally similar to shell aliases:
+
+     - The first token of the entered input is checked for an alias match.
+     - If matched, that first token is replaced with its expansion.
+     - Alias expansion is recursive (aliases can expand to other aliases).
+     - Expansion has a safety limit to prevent infinite loops.
+
+     Examples::
+
+        --> ls
+        {"/ethdev/list": [0, 1]}
+
+        --> names
+        [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}]
+
+        --> q
+        # exits the client
+
 
 Connecting to Different DPDK Processes
 --------------------------------------
diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py
index 2de10cff69..8b976160e0 100755
--- a/usertools/dpdk-telemetry.py
+++ b/usertools/dpdk-telemetry.py
@@ -21,6 +21,70 @@
 SOCKET_NAME = "dpdk_telemetry.{}".format(TELEMETRY_VERSION)
 DEFAULT_PREFIX = "rte"
 CMDS = []
+ALIASES = {}
+ALIAS_FILE = ".dpdk_telemetry_aliases"
+MAX_ALIAS_EXPANSIONS = 32
+
+
+def load_aliases():
+    """Load aliases from $HOME/.dpdk_telemetry_aliases"""
+    aliases = {}
+    home = os.environ.get("HOME")
+    if not home:
+        return aliases
+
+    alias_path = os.path.join(home, ALIAS_FILE)
+    if not os.path.isfile(alias_path):
+        return aliases
+
+    try:
+        with open(alias_path) as alias_file:
+            for line_num, line in enumerate(alias_file, start=1):
+                entry = line.strip()
+                if not entry or entry.startswith("#"):
+                    continue
+                if "=" not in entry:
+                    print(
+                        "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num),
+                        file=sys.stderr,
+                    )
+                    continue
+                name, command = entry.split("=", 1)
+                name = name.strip()
+                command = command.strip()
+                if not name or not command:
+                    print(
+                        "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num),
+                        file=sys.stderr,
+                    )
+                    continue
+                aliases[name] = command
+    except OSError as e:
+        print("Warning: failed to read {}: {}".format(alias_path, e), file=sys.stderr)
+
+    return aliases
+
+
+def expand_aliases(text, aliases):
+    """Expand aliases similarly to shell aliases on the first token"""
+    expanded = text
+    for _ in range(MAX_ALIAS_EXPANSIONS):
+        stripped = expanded.lstrip()
+        if not stripped:
+            return expanded
+
+        parts = stripped.split(None, 1)
+        first = parts[0]
+        rest = parts[1] if len(parts) > 1 else ""
+
+        if first not in aliases:
+            return expanded
+
+        alias_value = aliases[first]
+        expanded = "{} {}".format(alias_value, rest).strip() if rest else alias_value
+
+    print("Warning: alias expansion limit reached", file=sys.stderr)
+    return expanded
 
 
 def send_command(sock, cmd, output_buf_len, echo=False, pretty=False):
@@ -262,10 +326,12 @@ def handle_socket(args, path):
 
     # interactive prompt
     try:
-        text = input(prompt).strip()
-        while text != "quit":
-            handle_command(sock, output_buf_len, text, pretty=prompt)
+        while True:
             text = input(prompt).strip()
+            expanded = expand_aliases(text, ALIASES)
+            if expanded == "quit":
+                break
+            handle_command(sock, output_buf_len, expanded, pretty=prompt)
     except EOFError:
         pass
     finally:
@@ -274,7 +340,7 @@ def handle_socket(args, path):
 
 def readline_complete(text, state):
     """Find any matching commands from the list based on user input"""
-    all_cmds = ["quit"] + CMDS
+    all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS
     if text:
         matches = [c for c in all_cmds if c.startswith(text)]
     else:
@@ -304,6 +370,7 @@ def readline_complete(text, state):
     help="List all possible file-prefixes and exit",
 )
 args = parser.parse_args()
+ALIASES = load_aliases()
 if args.list:
     list_fp()
     sys.exit(0)
-- 
2.53.0


^ permalink raw reply related

* [PATCH v2 3/3] usertools/telemetry: add help support
From: Bruce Richardson @ 2026-05-22 13:37 UTC (permalink / raw)
  To: dev; +Cc: fengchengwen, Bruce Richardson
In-Reply-To: <20260522133714.133268-1-bruce.richardson@intel.com>

While the telemetry infrastructure supported using "/help,/<cmd>" to get
help on specific commands, with the addition of script-specific
commands, we needed better help support to explain the use of FOREACH,
and to allow e.g. "help /<cmd>" using space separation, which is more
intuitive. This patch adds that help support.

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 usertools/dpdk-telemetry.py | 61 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 60 insertions(+), 1 deletion(-)

diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py
index 8b976160e0..2c88b91517 100755
--- a/usertools/dpdk-telemetry.py
+++ b/usertools/dpdk-telemetry.py
@@ -25,6 +25,26 @@
 ALIAS_FILE = ".dpdk_telemetry_aliases"
 MAX_ALIAS_EXPANSIONS = 32
 
+BASIC_HELP_TEXT = """Basic usage:
+    /<command>[,<params>]      Send a telemetry command to the app
+    FOREACH ...                Run a compound query over list items
+    help                       Show this help
+    help /<command>            Show app-provided help for a command
+    help FOREACH               Show FOREACH usage and examples
+    quit                       Exit the client
+"""
+
+FOREACH_HELP_TEXT = """FOREACH usage:
+    FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...]
+    FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...]
+
+Examples:
+    FOREACH /ethdev/list /ethdev/stats .opackets
+    FOREACH /ethdev/list /ethdev/stats .ipackets .opackets
+    FOREACH i /ethdev/list /ethdev/info,$i .name
+    FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets
+"""
+
 
 def load_aliases():
     """Load aliases from $HOME/.dpdk_telemetry_aliases"""
@@ -203,10 +223,49 @@ def handle_foreach(sock, output_buf_len, text, pretty=False):
     print(json.dumps(output, indent=indent))
 
 
+def command_exists(cmd):
+    """Check if a telemetry command exists in the command list"""
+    return cmd in CMDS
+
+
+def app_help_command_for(target_cmd):
+    """Build a '/help,<command>' query for app-side command help"""
+    if not target_cmd:
+        return None
+    normalized = target_cmd.strip()
+    if not normalized.startswith("/"):
+        return None
+    if not command_exists(normalized):
+        print("Unknown command for help: {}".format(normalized))
+        return None
+    return "/help,{}".format(normalized)
+
+
+def handle_user_help(sock, output_buf_len, text, pretty=False):
+    """Handle local 'help' command and command-specific help lookup"""
+    parts = text.split(None, 1)
+    if len(parts) == 1:
+        print(BASIC_HELP_TEXT, end="")
+        return
+
+    help_arg = parts[1].strip()
+    if help_arg.upper() == "FOREACH":
+        print(FOREACH_HELP_TEXT, end="")
+        return
+
+    cmd = app_help_command_for(help_arg)
+    if cmd is None:
+        print("Usage: help [FOREACH|/<command>]")
+        return
+    send_command(sock, cmd, output_buf_len, echo=True, pretty=pretty)
+
+
 def handle_command(sock, output_buf_len, text, pretty=False):
     """Execute a user command if recognized"""
     if text.startswith("/"):
         send_command(sock, text, output_buf_len, echo=True, pretty=pretty)
+    elif text == "help" or text.startswith("help "):
+        handle_user_help(sock, output_buf_len, text, pretty)
     elif text.startswith("FOREACH "):
         handle_foreach(sock, output_buf_len, text, pretty)
 
@@ -340,7 +399,7 @@ def handle_socket(args, path):
 
 def readline_complete(text, state):
     """Find any matching commands from the list based on user input"""
-    all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS
+    all_cmds = ["quit", "help"] + list(ALIASES.keys()) + CMDS
     if text:
         matches = [c for c in all_cmds if c.startswith(text)]
     else:
-- 
2.53.0


^ permalink raw reply related

* Re: [RFC v2 00/11] prepare deprecation of rte_atomicNN_*() family
From: Bruce Richardson @ 2026-05-22 14:19 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev
In-Reply-To: <20260521180706.678377-1-stephen@networkplumber.org>

On Thu, May 21, 2026 at 11:04:12AM -0700, Stephen Hemminger wrote:
> The goal is to land every deprecation currently listed in the release
> notes by the 26.11 ABI bump. Functions to be removed in 26.11 need to be
> marked __rte_deprecated by 26.07, with all in-tree users converted off
> them first so CI stays clean.
> 
> This is the first step. After this series there are no remaining in-tree
> users of rte_atomic64. Expected follow-ups:
> 
>   - convert remaining rte_atomic32 users (dpaa/fslmc, netvsc, vmbus,
>   sw_evdev, txgbe, ifc, hinic, bnx2x, vhost) - convert remaining
>   rte_atomic16 users (dpaa/fslmc, qman) - mark the rte_atomicNN_*()
>   family __rte_deprecated - remove the legacy test_atomic.c - remove the
>   API itself at 26.11
> 
> Patch 1 deletes the inline-asm atomic fallbacks across arm, ppc,
> loongarch, riscv, and x86 now that RTE_FORCE_INTRINSICS has been the
> default everywhere for years. Largest patch by line count and the one
> most worth review attention.
> 
> Patch 2 retires the rte_smp_*mb deprecation notice (open since 2021) by
> reimplementing those APIs as inline wrappers over
> rte_atomic_thread_fence; the API is preserved for readability.
> 
> Patch 3 is the load-bearing change for lib/: the last caller of
> rte_atomic32_cmpset() is converted, with explicit acquire/release
> orderings matching the existing HTS/RTS ring pattern.
> 
> Driver conversions (patches 4-11) match each rte_atomic64 use to its best
> fit rather than blanket seq_cst: software stats become plain assignment
> (DPDK convention, torn reads accepted); CAS loops setting a flag collapse
> to fetch_or or exchange; open-coded link-status CAS in net/pfe and
> net/sfc moves to the existing rte_eth_linkstatus helpers; genuine
> synchronization stays atomic with explicit ordering.
> 
> v2 - fix clang build - replace rte_atomic64 in more drivers - incorporate
> feedback on rte_smp and ring - drop zxdh change (only caused by
> intrinsics in spinlock)
> 
> Stephen Hemminger (11): eal: use intrinsics for rte_atomic on all
> platforms eal: reimplement rte_smp_*mb with rte_atomic_thread_fence ring:
> use C11 atomic operations for MP/SP head/tail net/bonding: use stdatomic
> net/nbl: remove unused rte_atomic16 field net/ena: replace use of
> rte_atomicNN net/failsafe: convert to stdatomic net/enic: do not use
> deprecated rte_atomic64 net/pfe: use ethdev linkstatus helpers net/sfc:
> replace rte_atomic with stdatomic crypto/ccp: replace use of rte_atomic64
> with stdatomic
> 
I decided to test this patchset with the ring_perf_autotest (using only two
cores on same socket) to see how performance may be affected on x86 with
this change. On an initial once-off test to compare performance
with/without this patchset for MP/MC cases, it looks like smaller enq/deq
burst e.g. 8/32 are slower after this set, while larger bursts e.g. 128/256
are slightly faster.

I then ran two more tests with the patches applied and again without, and
got AI to analyse the set of 6 results to come up with more meaningful
conclusions after a little bit more numeric analysis. Below is some of the
summary.

While not necessarily a deal-breaker, the regressions seen are cause for
pause. We probably want to benchmark on a few other x86 (both Intel and
AMD) systems to see if this is a consistent picture.

/Bruce

---

Section-level picture (stable changes only):

Testing burst enq/deq:
  10 consistent regressions, 0 consistent improvements.
Testing bulk enq/deq:
  10 consistent regressions, 1 consistent improvement.
Testing using two physical cores:
  mixed, but regressions outnumber improvements (5 vs 2).
Zero-copy and compression sections:
  mostly inconclusive due high variance, with one stable regression.
Empty bulk deq and single-element:
  mixed small set, with isolated improvements and regressions.

Largest consistent regressions (examples):

  elem MP/MC burst n=32: -39.19%
  elem MP/MC bulk n=32: -37.84%
  elem MP/MC two-core bulk n=8: -36.58%
  elem MP/MC two-core bulk n=32: -29.46%
  elem MP/MC burst n=8: -29.43%
  legacy MP/MC burst n=8: -19.04%


Largest consistent improvements (examples):

  elem MP/MC two-core bulk n=128: +28.16%
  elem MP/MC two-core bulk n=256: +25.91%
  legacy SP/SC empty bulk deq n=8: +23.41%
  elem SP/SC bulk n=32: +16.05%
  legacy MP/MC single: +4.65%


Bottom line:

There is a real and mostly consistent regression trend in cycle-per-element
performance after replacing rte_atomicNN usage, especially in MP/MC burst
and bulk paths.  A few benchmarks improve, but they are fewer than
regressions.  All-worker total-count throughput appears statistically flat
in this 3-run sample.


^ permalink raw reply

* Re: [PATCH v1 1/1] net/iavf: fix large VF IRQ mapping
From: Bruce Richardson @ 2026-05-22 14:34 UTC (permalink / raw)
  To: Anatoly Burakov; +Cc: dev, Vladimir Medvedkin, david.marchand, mb
In-Reply-To: <agyL7MGOLfl-GdIr@bricha3-mobl1.ger.corp.intel.com>

On Tue, May 19, 2026 at 05:12:28PM +0100, Bruce Richardson wrote:
> On Wed, May 06, 2026 at 03:07:03PM +0100, Anatoly Burakov wrote:
> > The PF will check buffer size for being too big, and the chunk sizing code
> > correctly calls that out. However, the size was actually still too big
> > because `struct virtchnl_queue_vector_maps` already had one queue vector
> > as part of its definition, so `chunk_sz` was too big by 1.
> > 
> > Fixes: 292d3b781ac4 ("net/iavf: replace unnecessary hugepage memory allocations")
> > 
> > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> > ---
> 
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> 
Applied to next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* Re: [RFC v2 00/11] prepare deprecation of rte_atomicNN_*() family
From: Stephen Hemminger @ 2026-05-22 14:45 UTC (permalink / raw)
  To: Bruce Richardson; +Cc: dev
In-Reply-To: <ahBl1GytfB3f17hV@bricha3-mobl1.ger.corp.intel.com>

On Fri, 22 May 2026 15:19:00 +0100
Bruce Richardson <bruce.richardson@intel.com> wrote:

> >   
> I decided to test this patchset with the ring_perf_autotest (using only two
> cores on same socket) to see how performance may be affected on x86 with
> this change. On an initial once-off test to compare performance
> with/without this patchset for MP/MC cases, it looks like smaller enq/deq
> burst e.g. 8/32 are slower after this set, while larger bursts e.g. 128/256
> are slightly faster.
> 
> I then ran two more tests with the patches applied and again without, and
> got AI to analyse the set of 6 results to come up with more meaningful
> conclusions after a little bit more numeric analysis. Below is some of the
> summary.
> 
> While not necessarily a deal-breaker, the regressions seen are cause for
> pause. We probably want to benchmark on a few other x86 (both Intel and
> AMD) systems to see if this is a consistent picture.
> 
> /Bruce

Could you see if problem is the use of intrinsics on x86 or the
changes to rte_ring_pvt?

I am not convinced that deprecation of these function is hard requirement.
This patchset is more of a what-if experiment.
The other alternative is remove the deprecation notice and just leave
well enough alone.

But some of the places actually benefit from the change over because
the are using flags as lock and using other memory orders should
be faster on Arm.

^ permalink raw reply

* Re: [PATCH] FIX: excessive logging in I40E driver
From: Bruce Richardson @ 2026-05-22 14:49 UTC (permalink / raw)
  To: Arin Kharkar; +Cc: dev
In-Reply-To: <20260512234227.218761-1-arinkharkar@gmail.com>

On Tue, May 12, 2026 at 04:42:27PM -0700, Arin Kharkar wrote:
> Resolved bug #1936 by changing the log level in i40e_set_tx_function from NOTICE to DEBUG
> ---
>  drivers/net/intel/i40e/i40e_rxtx.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/net/intel/i40e/i40e_rxtx.c b/drivers/net/intel/i40e/i40e_rxtx.c
> index c5ac75e0f0..bc48303a25 100644
> --- a/drivers/net/intel/i40e/i40e_rxtx.c
> +++ b/drivers/net/intel/i40e/i40e_rxtx.c
> @@ -3124,7 +3124,7 @@ i40e_set_tx_function(struct rte_eth_dev *dev)
>  					 i40e_tx_path_infos[ad->tx_func_type].pkt_burst;
>  	dev->tx_pkt_prepare = i40e_tx_path_infos[ad->tx_func_type].pkt_prep;
>  
> -	PMD_DRV_LOG(NOTICE, "Using %s (port %d).",
> +	PMD_DRV_LOG(DEBUG, "Using %s (port %d).",
>  		i40e_tx_path_infos[ad->tx_func_type].info, dev->data->port_id);
>  
>  	if (ad->tx_func_type == I40E_TX_SCALAR_SIMPLE ||

Hi, thanks for the fix. 

Unfortunately, with the recent changes in the drivers to use a different
path selection mechanism, this patch is no longer relevant, I believe. The
log messages for path selection in the driver on "next-net-intel" tree are
now all at DEBUG level.

Marking as not-applicable in patchwork.

Thanks,
/Bruce

^ permalink raw reply

* Re: [PATCH] net/ice: improve log messages for DDP loading
From: Bruce Richardson @ 2026-05-22 14:53 UTC (permalink / raw)
  To: David Marchand; +Cc: dev, patrick.mahan, Anatoly Burakov
In-Reply-To: <ag3Y1401SlX2hcTm@bricha3-mobl1.ger.corp.intel.com>

On Wed, May 20, 2026 at 04:52:55PM +0100, Bruce Richardson wrote:
> On Sat, May 16, 2026 at 12:19:41PM +0200, David Marchand wrote:
> > Some nics may not provide a serial number (PCI capability
> > RTE_PCI_EXT_CAP_ID_DSN).
> > 
> > This results in a confusing ERROR log:
> > ICE_INIT: ice_dev_init(): Failed to read device serial number
> > 
> > This is confusing as DDP loading does *not* require the serial number to
> > be present for the port to be functional afterwards.
> > 
> > Besides, after trying various path, if the default DDP is not present on
> > the runtime system, the port initialisation ends up with a vague error:
> > ICE_INIT: ice_load_pkg(): failed to search file path
> > 
> > Improve the situation with adjusting the log level when reading the
> > SN fails, then add more debug context to DDP file loading and end up
> > with a ERROR log mentioning the expected file.
> > 
> > ICE_INIT: ice_firmware_read(): Cannot read DDP file
> > 	/lib/firmware/updates/intel/ice/ddp/ice-b49691ffffe6e69c.pkg
> > ICE_INIT: ice_firmware_read(): Cannot read DDP file
> > 	/lib/firmware/intel/ice/ddp/ice-b49691ffffe6e69c.pkg
> > ICE_INIT: ice_firmware_read(): Cannot read DDP file
> > 	/lib/firmware/updates/intel/ice/ddp/ice.pkg
> > ICE_INIT: ice_firmware_read(): Cannot read DDP file
> > 	/lib/firmware/intel/ice/ddp/ice.pkg
> > ICE_INIT: ice_load_pkg(): Failed to load default DDP package
> > 	/lib/firmware/intel/ice/ddp/ice.pkg
> > 
> > Signed-off-by: David Marchand <david.marchand@redhat.com>
> > ---
> >  drivers/net/intel/ice/ice_ethdev.c | 31 ++++++++++++++++++++----------
> >  1 file changed, 21 insertions(+), 10 deletions(-)
> > 
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>

Applied to dpdk-next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* Re: [PATCH v2] net/ice: fix TM node ID validation against configured queues
From: Bruce Richardson @ 2026-05-22 14:57 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev, stable
In-Reply-To: <ag3SIFeXpwhWKocg@bricha3-mobl1.ger.corp.intel.com>

On Wed, May 20, 2026 at 04:24:16PM +0100, Bruce Richardson wrote:
> On Wed, May 20, 2026 at 03:07:16PM +0000, Ciara Loftus wrote:
> > The leaf node ID boundary is checked against the compile-time constant
> > `RTE_MAX_QUEUES_PER_PORT` (1024) rather than the number of configured Tx
> > queues. The rte_tm specification reserves IDs 0 to N-1 for leaf nodes
> > where N is the configured queue count, so using the constant produces
> > wrong results whenever N is less than 1024.
> > 
> > Fix by using `nb_tx_queues` as the boundary when queues have been
> > configured, falling back to `RTE_MAX_QUEUES_PER_PORT` when `nb_tx_queues`
> > is zero. The zero case arises when the TM hierarchy is built before port
> > queue configuration, which is required to support queue counts beyond
> > the hardware default.
> > 
> > Also add an explicit check in the non-leaf validation path that rejects
> > IDs in the leaf-reserved range. This condition can be triggered two ways:
> > adding a leaf node before its parent chain is complete (the node resolves
> > to a non-leaf level), or assigning a leaf-range ID to a node intended
> > as non-leaf.
> > 
> > Fixes: 715d449a965b ("net/ice: enhance Tx scheduler hierarchy support")
> > Cc: stable@dpdk.org
> > 
> > Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> > ---
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>

Applied to dpdk-next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* [PATCH v1 0/5] fib6: fix tbl8 reservation drift
From: Maxime Leroy @ 2026-05-22 14:58 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, stable, Maxime Leroy
In-Reply-To: <cover.1778146229.git.maxime@leroys.fr>

This v1 supersedes the earlier RFC. The RFC dropped rsvd_tbl8s and
used tbl8_pool_pos in the pre-check, which loses the worst-case
envelope: a compressed /48 under a /28 allocates zero tbl8s but must
reserve the boundaries the /48 would need if the /28 is later
removed (DEL forces mid-flight decompression in modify_dp() with no
rollback).

This v1 keeps rsvd_tbl8s and computes it the way dir24_8 already
does for IPv4. dir24_8 counts /24 supernets that contain at least
one /25..32 prefix: that count is invariant under unrelated RIB
changes, so the counter cannot drift. trie6 has the same need at
13 levels instead of 1 (byte boundaries 24, 32, ..., 120), so v1
counts, for each L in that set, the /L supernets containing at
least one prefix with depth > L. ADD/DEL pairs are symmetric by
construction.

Patch 1 is the minimal self-contained fix (Fixes: + Cc: stable).
Patches 2-3 add the reproducer and extended regression tests.
Patches 4-5 are an optimization (not for stable): valid_descendants
in rte_rib6 + single-descent helper, so trie_modify() walks once
instead of up to 13 times per ADD/DEL.

Validated on a live BGP router (grout + FRR, 127 IPv6 prefixes):
RSVD_TBL8 returned to its pre-cycle value after a zebra-kill /
reconverge cycle.

Maxime Leroy (5):
  fib6: fix tbl8 reservation drift in trie
  test/fib6: add reproducer for tbl8 reservation drift
  test/fib6: extended drift test cases
  rib: track valid descendant count per node
  fib6: speed up tbl8 reservation accounting

 app/test/test_fib6.c    | 335 ++++++++++++++++++++++++++++++++++++++++
 app/test/test_rib6.c    |  92 +++++++++++
 lib/fib/trie.c          |  47 +-----
 lib/rib/rib6_internal.h |  37 +++++
 lib/rib/rte_rib6.c      |  80 ++++++++++
 5 files changed, 552 insertions(+), 39 deletions(-)
 create mode 100644 lib/rib/rib6_internal.h

---
v1:
* Keep rsvd_tbl8s; recompute it via topology-stable empty-supernet
  count (dir24_8 pattern at 13 levels) instead of RIB-derived
  depth_diff.
* Drop RFC patch 3/3 (no longer needed).
* Add extended regression tests.
* Add patches 4-5: RIB valid_descendants + single-descent helper
  (optional perf optimization; not for stable).
* Production-validated on a live BGP router.

--
2.43.0

^ permalink raw reply

* [PATCH v1 1/5] fib6: fix tbl8 reservation drift in trie
From: Maxime Leroy @ 2026-05-22 14:58 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, stable, Maxime Leroy
In-Reply-To: <20260522145855.1748406-1-maxime@leroys.fr>

trie_modify() maintained rsvd_tbl8s by computing a depth_diff from
the current RIB topology at both ADD and DEL. The two values diverge
when the RIB changes between an ADD and its later DEL (a covering
parent added or removed), and rsvd_tbl8s eventually wraps to
UINT32_MAX, rejecting all subsequent /25+ ADDs with -ENOSPC.

Replace the depth_diff arithmetic with the dir24_8 approach
generalised to multiple byte boundaries: for each byte boundary L
below depth, the supernet at level L needs a tbl8 if and only if at
least one prefix with depth > L exists in that supernet. The supernet
identity at level L is stable across unrelated RIB modifications, so
ADD/DEL pairs are symmetric by construction.

A helper count_empty_levels() scans byte boundaries from 24 upward,
stopping at the first level where the supernet has no descendant. A
NULL answer at level L propagates upward because narrower supernets
are subsets, so the remaining boundaries can be tallied in one shot
without further queries.

  - On ADD, count the empty levels and increment rsvd_tbl8s by that
    count. The capacity pre-check uses the exact post-operation
    envelope.

  - On DEL, after removing the prefix, count the levels that became
    empty and decrement.

Fixes: c3e12e0f0354 ("fib: add dataplane algorithm for IPv6")
Cc: stable@dpdk.org

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 lib/fib/trie.c | 71 +++++++++++++++++++++++---------------------------
 1 file changed, 32 insertions(+), 39 deletions(-)

diff --git a/lib/fib/trie.c b/lib/fib/trie.c
index fa5d9ec6b0..44b90f72ff 100644
--- a/lib/fib/trie.c
+++ b/lib/fib/trie.c
@@ -534,19 +534,43 @@ modify_dp(struct rte_trie_tbl *dp, struct rte_rib6 *rib,
 	return 0;
 }
 
+/*
+ * Count byte boundaries between 24 and CEIL(depth, 8) where the
+ * supernet of ip has no descendant in the RIB. This is the number of
+ * new tbl8 levels an ADD of ip/depth would introduce, or the number
+ * to free at DEL once the prefix has been removed from the RIB.
+ *
+ * A NULL answer at level L propagates upwards: narrower supernets at
+ * L+8, L+16, ... are subsets of S_L and cannot contain descendants
+ * either. The loop stops at the first NULL and tallies the remaining
+ * boundaries in one shot.
+ */
+static uint8_t
+count_empty_levels(struct rte_rib6 *rib, const struct rte_ipv6_addr *ip,
+	uint8_t depth)
+{
+	uint8_t level, top = RTE_ALIGN_CEIL(depth, 8);
+
+	for (level = 24; level < top; level += 8) {
+		if (rte_rib6_get_nxt(rib, ip, level, NULL,
+				RTE_RIB6_GET_NXT_COVER) == NULL)
+			return (top - level) >> 3;
+	}
+	return 0;
+}
+
 int
 trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 	uint8_t depth, uint64_t next_hop, int op)
 {
 	struct rte_trie_tbl *dp;
 	struct rte_rib6 *rib;
-	struct rte_rib6_node *tmp = NULL;
 	struct rte_rib6_node *node;
 	struct rte_rib6_node *parent;
-	struct rte_ipv6_addr ip_masked, tmp_ip;
+	struct rte_ipv6_addr ip_masked;
 	int ret = 0;
 	uint64_t par_nh, node_nh;
-	uint8_t tmp_depth, depth_diff = 0, parent_depth = 24;
+	uint8_t new_levels;
 
 	if ((fib == NULL) || (ip == NULL) || (depth > RTE_IPV6_MAX_DEPTH))
 		return -EINVAL;
@@ -559,37 +583,6 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 	ip_masked = *ip;
 	rte_ipv6_addr_mask(&ip_masked, depth);
 
-	if (depth > 24) {
-		tmp = rte_rib6_get_nxt(rib, &ip_masked,
-			RTE_ALIGN_FLOOR(depth, 8), NULL,
-			RTE_RIB6_GET_NXT_ALL);
-		if (tmp && op == RTE_FIB6_DEL) {
-			/* in case of delete operation, skip the prefix we are going to delete */
-			rte_rib6_get_ip(tmp, &tmp_ip);
-			rte_rib6_get_depth(tmp, &tmp_depth);
-			if (rte_ipv6_addr_eq(&ip_masked, &tmp_ip) && depth == tmp_depth)
-				tmp = rte_rib6_get_nxt(rib, &ip_masked,
-					RTE_ALIGN_FLOOR(depth, 8), tmp, RTE_RIB6_GET_NXT_ALL);
-		}
-
-		if (tmp == NULL) {
-			tmp = rte_rib6_lookup(rib, ip);
-			/**
-			 * in case of delete operation, lookup returns the prefix
-			 * we are going to delete. Find the parent.
-			 */
-			if (tmp && op == RTE_FIB6_DEL)
-				tmp = rte_rib6_lookup_parent(tmp);
-
-			if (tmp != NULL) {
-				rte_rib6_get_depth(tmp, &tmp_depth);
-				parent_depth = RTE_MAX(tmp_depth, 24);
-			}
-			depth_diff = RTE_ALIGN_CEIL(depth, 8) -
-				RTE_ALIGN_CEIL(parent_depth, 8);
-			depth_diff = depth_diff >> 3;
-		}
-	}
 	node = rte_rib6_lookup_exact(rib, &ip_masked, depth);
 	switch (op) {
 	case RTE_FIB6_ADD:
@@ -603,7 +596,8 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 			return 0;
 		}
 
-		if ((depth > 24) && (dp->rsvd_tbl8s + depth_diff > dp->number_tbl8s))
+		new_levels = count_empty_levels(rib, &ip_masked, depth);
+		if (dp->rsvd_tbl8s + new_levels > dp->number_tbl8s)
 			return -ENOSPC;
 
 		node = rte_rib6_insert(rib, &ip_masked, depth);
@@ -622,7 +616,7 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 			return ret;
 		}
 successfully_added:
-		dp->rsvd_tbl8s += depth_diff;
+		dp->rsvd_tbl8s += new_levels;
 		return 0;
 	case RTE_FIB6_DEL:
 		if (node == NULL)
@@ -640,9 +634,8 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 
 		if (ret != 0)
 			return ret;
-		rte_rib6_remove(rib, ip, depth);
-
-		dp->rsvd_tbl8s -= depth_diff;
+		rte_rib6_remove(rib, &ip_masked, depth);
+		dp->rsvd_tbl8s -= count_empty_levels(rib, &ip_masked, depth);
 		return 0;
 	default:
 		break;
-- 
2.43.0


^ permalink raw reply related

* [PATCH v1 2/5] test/fib6: add reproducer for tbl8 reservation drift
From: Maxime Leroy @ 2026-05-22 14:58 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, stable, Maxime Leroy
In-Reply-To: <20260522145855.1748406-1-maxime@leroys.fr>

test_drift covers the asymmetric ADD parent / ADD children / DEL
parent / DEL children sequence that wraps rsvd_tbl8s past zero in a
single iteration. After the wrap the next /25+ ADD is rejected with
-ENOSPC even though the tbl8 pool is empty. With the preceding fix
in place the final ADD succeeds.

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 app/test/test_fib6.c | 74 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/app/test/test_fib6.c b/app/test/test_fib6.c
index fffb590dbf..c4283f3f2d 100644
--- a/app/test/test_fib6.c
+++ b/app/test/test_fib6.c
@@ -25,6 +25,7 @@ static int32_t test_get_invalid(void);
 static int32_t test_lookup(void);
 static int32_t test_invalid_rcu(void);
 static int32_t test_fib_rcu_sync_rw(void);
+static int32_t test_drift(void);
 
 #define MAX_ROUTES	(1 << 16)
 /** Maximum number of tbl8 for 2-byte entries */
@@ -599,6 +600,78 @@ test_fib_rcu_sync_rw(void)
 	return status == 0 ? TEST_SUCCESS : TEST_FAILED;
 }
 
+/*
+ * Reproducer for the rsvd_tbl8s drift bug. depth_diff used to maintain
+ * rsvd_tbl8s is computed from the current RIB state, so it is not
+ * invariant between the ADD of a prefix and its later DEL when a
+ * covering parent prefix is removed in between.
+ *
+ * Layout: one /28 parent (fcde::/28) and three /48 siblings under it
+ * (fcde:0:6000::/48, fcde:1:6000::/48, fcde:2:6000::/48). The second
+ * hextet's high 12 bits are zero, so the three /48 IPs all fall inside
+ * the /28.
+ *
+ * One asymmetric sequence is enough to wrap the counter:
+ *   ADD /28                                  rsvd_tbl8s += 1
+ *   ADD /48 child_0,1,2 (with /28 parent)    rsvd_tbl8s += 2 each (+6)
+ *   DEL /28 (sibling /48 found)              rsvd_tbl8s -= 0
+ *   DEL /48 child_0,1,2 (no parent left)     rsvd_tbl8s -= 3 each (-9)
+ */
+static int32_t
+test_drift(void)
+{
+	struct rte_fib6_conf config = { 0 };
+	struct rte_fib6 *fib;
+	struct rte_ipv6_addr parent =
+		RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr child[3] = {
+		RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 1, 0x6000, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 2, 0x6000, 0, 0, 0, 0, 0),
+	};
+	unsigned int c;
+	int ret;
+
+	config.max_routes = 1024;
+	config.rib_ext_sz = 0;
+	config.default_nh = 0;
+	config.type = RTE_FIB6_TRIE;
+	config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+	config.trie.num_tbl8 = 256;
+
+	fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	ret = rte_fib6_add(fib, &parent, 28, 0xa);
+	RTE_TEST_ASSERT(ret == 0, "ADD /28 failed (ret=%d)\n", ret);
+
+	for (c = 0; c < 3; c++) {
+		ret = rte_fib6_add(fib, &child[c], 48, 0xb + c);
+		RTE_TEST_ASSERT(ret == 0,
+			"ADD /48 child %u failed (ret=%d)\n", c, ret);
+	}
+
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "DEL /28 failed (ret=%d)\n", ret);
+
+	for (c = 0; c < 3; c++) {
+		ret = rte_fib6_delete(fib, &child[c], 48);
+		RTE_TEST_ASSERT(ret == 0,
+			"DEL /48 child %u failed (ret=%d)\n", c, ret);
+	}
+
+	/* Pre-fix: -ENOSPC. Post-fix: succeeds. */
+	ret = rte_fib6_add(fib, &parent, 28, 0xe);
+	RTE_TEST_ASSERT(ret == 0,
+		"Fresh ADD /28 spuriously failed (ret=%d)\n", ret);
+
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "Final DEL /28 failed (ret=%d)\n", ret);
+
+	rte_fib6_free(fib);
+	return TEST_SUCCESS;
+}
+
 static struct unit_test_suite fib6_fast_tests = {
 	.suite_name = "fib6 autotest",
 	.setup = NULL,
@@ -611,6 +684,7 @@ static struct unit_test_suite fib6_fast_tests = {
 	TEST_CASE(test_lookup),
 	TEST_CASE(test_invalid_rcu),
 	TEST_CASE(test_fib_rcu_sync_rw),
+	TEST_CASE(test_drift),
 	TEST_CASES_END()
 	}
 };
-- 
2.43.0


^ permalink raw reply related

* [PATCH v1 3/5] test/fib6: extended drift test cases
From: Maxime Leroy @ 2026-05-22 14:58 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, stable, Maxime Leroy
In-Reply-To: <20260522145855.1748406-1-maxime@leroys.fr>

Four additional test cases exercise scenarios touched by the
multi-level supernet counting:

  - test_drift_compression: parent + compressed child, DEL parent
    forces decompression, re-ADD ancestor re-compresses.

  - test_drift_multilevel: /28 + /48 + /96 chain with mixed
    compressed and non-compressed links, then DEL of the middle
    prefix.

  - test_drift_stress: pseudo-random ADD/DEL sequence checking that
    no operation returns -ENOSPC under a leaked rsvd_tbl8s.

  - test_drift_tight_pool: pool sized to exactly the legitimate
    envelope, re-ADD after decompression must succeed.

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 app/test/test_fib6.c | 269 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 265 insertions(+), 4 deletions(-)

diff --git a/app/test/test_fib6.c b/app/test/test_fib6.c
index c4283f3f2d..ad68645428 100644
--- a/app/test/test_fib6.c
+++ b/app/test/test_fib6.c
@@ -26,6 +26,10 @@ static int32_t test_lookup(void);
 static int32_t test_invalid_rcu(void);
 static int32_t test_fib_rcu_sync_rw(void);
 static int32_t test_drift(void);
+static int32_t test_drift_compression(void);
+static int32_t test_drift_multilevel(void);
+static int32_t test_drift_stress(void);
+static int32_t test_drift_tight_pool(void);
 
 #define MAX_ROUTES	(1 << 16)
 /** Maximum number of tbl8 for 2-byte entries */
@@ -601,10 +605,9 @@ test_fib_rcu_sync_rw(void)
 }
 
 /*
- * Reproducer for the rsvd_tbl8s drift bug. depth_diff used to maintain
- * rsvd_tbl8s is computed from the current RIB state, so it is not
- * invariant between the ADD of a prefix and its later DEL when a
- * covering parent prefix is removed in between.
+ * Reproducer for the rsvd_tbl8s drift bug. The tbl8 reservation
+ * accounting must remain balanced even when a covering parent prefix
+ * is removed between an ADD and its later matching DEL.
  *
  * Layout: one /28 parent (fcde::/28) and three /48 siblings under it
  * (fcde:0:6000::/48, fcde:1:6000::/48, fcde:2:6000::/48). The second
@@ -672,6 +675,260 @@ test_drift(void)
 	return TEST_SUCCESS;
 }
 
+/*
+ * Exercise compression (same nh as parent), forced decompression on
+ * DEL parent, then re-compression after re-adding the same ancestor.
+ * The tbl8 reservation accounting must remain balanced even though
+ * the child is physically decompressed/recompressed in the dataplane.
+ *
+ * Layout: parent fcde::/28 and child fcde:0:6000::/48, both nh=1.
+ *
+ *   ADD /28 (no ancestor)                    rsvd_tbl8s += 1
+ *   ADD /48 (compressed under /28)           rsvd_tbl8s += 2
+ *   DEL /28 (decompresses /48)               rsvd unchanged (/48 keeps)
+ *   re-ADD /28 (re-compresses /48)           rsvd unchanged
+ *   DEL /48                                  rsvd_tbl8s -= 2
+ *   DEL /28                                  rsvd_tbl8s -= 1
+ */
+static int32_t
+test_drift_compression(void)
+{
+	struct rte_fib6_conf config = { 0 };
+	struct rte_fib6 *fib;
+	struct rte_ipv6_addr parent = RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr child = RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0, 0, 0);
+	int ret;
+
+	config.max_routes = 1024;
+	config.rib_ext_sz = 0;
+	config.default_nh = 0;
+	config.type = RTE_FIB6_TRIE;
+	config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+	config.trie.num_tbl8 = 256;
+
+	fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	/* Compressed: child shares the parent's nh, modify_dp is skipped */
+	ret = rte_fib6_add(fib, &parent, 28, 1);
+	RTE_TEST_ASSERT(ret == 0, "ADD /28 failed\n");
+	ret = rte_fib6_add(fib, &child, 48, 1);
+	RTE_TEST_ASSERT(ret == 0, "ADD /48 (compressed) failed\n");
+
+	/* DEL parent forces decompression: child must be materialized */
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "DEL /28 (decompression) failed\n");
+
+	/* Re-add parent with same nh: child becomes compressed again */
+	ret = rte_fib6_add(fib, &parent, 28, 1);
+	RTE_TEST_ASSERT(ret == 0, "Re-ADD /28 failed\n");
+
+	ret = rte_fib6_delete(fib, &child, 48);
+	RTE_TEST_ASSERT(ret == 0, "DEL /48 failed\n");
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "DEL /28 final failed\n");
+
+	rte_fib6_free(fib);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Three-level nesting with compressed and non-compressed paths, then
+ * DEL of the middle prefix. The byte-boundary supernet accounting
+ * must remain balanced through the chain.
+ *
+ * Layout: grand fcde::/28 nh=1, mid fcde:0:6000::/48 nh=1 (compressed
+ * under grand), leaf fcde:0:6000::4000::/96 nh=2 (not compressed).
+ *
+ *   ADD /28 (no ancestor)                    rsvd_tbl8s += 1
+ *   ADD /48 (compressed under /28)           rsvd_tbl8s += 2
+ *   ADD /96 (not compressed under /48)       rsvd_tbl8s += 6
+ *   DEL /48 (leaf /96 still covers 32, 40)   rsvd_tbl8s -= 0
+ *   DEL /28 (only level 24 was solely /28's) rsvd_tbl8s -= 0
+ *   DEL /96 (last route gone, all freed)     rsvd_tbl8s -= 9
+ *
+ * Boundaries get refunded only on the DEL that makes them empty;
+ * intermediate DELs that leave a covering descendant are refund-free.
+ */
+static int32_t
+test_drift_multilevel(void)
+{
+	struct rte_fib6_conf config = { 0 };
+	struct rte_fib6 *fib;
+	struct rte_ipv6_addr grand = RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr mid =   RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr leaf =  RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0x4000, 0, 0);
+	int ret;
+
+	config.max_routes = 1024;
+	config.rib_ext_sz = 0;
+	config.default_nh = 0;
+	config.type = RTE_FIB6_TRIE;
+	config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+	config.trie.num_tbl8 = 256;
+
+	fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	ret = rte_fib6_add(fib, &grand, 28, 1);
+	RTE_TEST_ASSERT(ret == 0, "ADD /28 failed\n");
+	ret = rte_fib6_add(fib, &mid, 48, 1);  /* compressed under /28 */
+	RTE_TEST_ASSERT(ret == 0, "ADD /48 failed\n");
+	ret = rte_fib6_add(fib, &leaf, 96, 2); /* non-compressed under /48 */
+	RTE_TEST_ASSERT(ret == 0, "ADD /96 failed\n");
+
+	/* DEL the middle prefix: byte-boundary accounting must stay
+	 * coherent so the subsequent operations succeed.
+	 */
+	ret = rte_fib6_delete(fib, &mid, 48);
+	RTE_TEST_ASSERT(ret == 0, "DEL /48 failed\n");
+
+	ret = rte_fib6_delete(fib, &grand, 28);
+	RTE_TEST_ASSERT(ret == 0, "DEL /28 failed\n");
+	ret = rte_fib6_delete(fib, &leaf, 96);
+	RTE_TEST_ASSERT(ret == 0, "DEL /96 failed\n");
+
+	rte_fib6_free(fib);
+	return TEST_SUCCESS;
+}
+
+/*
+ * Pseudo-random ADD/DEL sequence over 8 prefixes with varying depths
+ * and next-hops. A hand-rolled LCG (not rte_rand) makes the sequence
+ * reproducible across runs and DPDK versions. After all prefixes are
+ * removed, a final ADD/DEL pair must succeed - it would fail under a
+ * leaked rsvd_tbl8s.
+ *
+ * depths[1] and depths[6] both use /36 on purpose: ips[1] and ips[6]
+ * are distinct prefixes, so this exercises two parallel /36 ADD/DEL
+ * paths that share byte boundaries 24 and 32.
+ */
+static int32_t
+test_drift_stress(void)
+{
+	uint8_t depths[8] = { 28, 36, 40, 48, 64, 80, 36, 128 };
+	struct rte_fib6_conf config = { 0 };
+	struct rte_ipv6_addr ips[8] = {
+		RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x1, 0, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x2, 0, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x2, 0x4000, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x2, 0x4000, 0x1000, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x2, 0x4000, 0x1000, 0x1, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x3, 0, 0, 0, 0, 0, 0),
+		RTE_IPV6(0xfcde, 0x3, 0, 0, 0, 0, 0, 0x1),
+	};
+	uint8_t live[8] = { 0 };
+	struct rte_fib6 *fib;
+	uint32_t seed = 0x4242;
+	unsigned int i, idx;
+	int ret;
+
+	config.max_routes = 64;
+	config.rib_ext_sz = 0;
+	config.default_nh = 0;
+	config.type = RTE_FIB6_TRIE;
+	config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+	config.trie.num_tbl8 = 256;
+
+	fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	for (i = 0; i < 2000; i++) {
+		seed = seed * 1103515245u + 12345u;
+		idx = (seed >> 8) & 7;
+		if (live[idx]) {
+			ret = rte_fib6_delete(fib, &ips[idx], depths[idx]);
+			RTE_TEST_ASSERT(ret == 0,
+				"DEL idx %u (depth /%u) failed (ret=%d)\n",
+				idx, depths[idx], ret);
+			live[idx] = 0;
+		} else {
+			uint64_t nh = ((seed >> 16) & 0xff) + 1;
+			ret = rte_fib6_add(fib, &ips[idx], depths[idx], nh);
+			RTE_TEST_ASSERT(ret == 0,
+				"ADD idx %u (depth /%u nh=%" PRIu64 ") failed (ret=%d)\n",
+				idx, depths[idx], nh, ret);
+			live[idx] = 1;
+		}
+	}
+
+	/* Drain everything */
+	for (i = 0; i < RTE_DIM(live); i++) {
+		if (live[i]) {
+			ret = rte_fib6_delete(fib, &ips[i], depths[i]);
+			RTE_TEST_ASSERT(ret == 0,
+				"final drain DEL idx %u failed (ret=%d)\n",
+				i, ret);
+		}
+	}
+
+	/* If rsvd_tbl8s had leaked, this fresh ADD would fail */
+	ret = rte_fib6_add(fib, &ips[0], depths[0], 0xff);
+	RTE_TEST_ASSERT(ret == 0,
+		"post-drain ADD failed (rsvd leaked?) (ret=%d)\n", ret);
+	ret = rte_fib6_delete(fib, &ips[0], depths[0]);
+	RTE_TEST_ASSERT(ret == 0, "post-drain DEL failed\n");
+
+	rte_fib6_free(fib);
+	return TEST_SUCCESS;
+}
+
+/* Tight-pool re-compression scenario. Pool sized to exactly the
+ * highest legitimate envelope: an ADD that becomes a closer ancestor
+ * of an existing descendant must succeed because the byte-boundary
+ * supernet accounting reports the same envelope post-operation.
+ *
+ *   num_tbl8 = 3
+ *   ADD /28 nh=1            rsvd = 1
+ *   ADD /48 nh=1 (compr.)   rsvd = 3 (/48 reserves 2 new boundaries)
+ *   DEL /28                 rsvd unchanged (/48 still holds them)
+ *   RE-ADD /28 nh=1         rsvd unchanged (already reserved)
+ *                           (pre-fix: pre-check rejects)
+ */
+static int32_t
+test_drift_tight_pool(void)
+{
+	struct rte_fib6_conf config = { 0 };
+	struct rte_fib6 *fib;
+	struct rte_ipv6_addr parent = RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr child = RTE_IPV6(0xfcde, 0, 0x6000, 0, 0, 0, 0, 0);
+	int ret;
+
+	config.max_routes = 16;
+	config.rib_ext_sz = 0;
+	config.default_nh = 0;
+	config.type = RTE_FIB6_TRIE;
+	config.trie.nh_sz = RTE_FIB6_TRIE_2B;
+	config.trie.num_tbl8 = 3;
+
+	fib = rte_fib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(fib != NULL, "Failed to create FIB\n");
+
+	ret = rte_fib6_add(fib, &parent, 28, 1);
+	RTE_TEST_ASSERT(ret == 0, "ADD /28 failed (ret=%d)\n", ret);
+	ret = rte_fib6_add(fib, &child, 48, 1);
+	RTE_TEST_ASSERT(ret == 0, "ADD /48 failed (ret=%d)\n", ret);
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "DEL /28 failed (ret=%d)\n", ret);
+
+	/* Re-add /28: byte boundary 24 is already occupied by the /48,
+	 * so the re-added /28 introduces no new reservation. The
+	 * envelope stays at 3 and still fits the pool of 3.
+	 */
+	ret = rte_fib6_add(fib, &parent, 28, 1);
+	RTE_TEST_ASSERT(ret == 0,
+		"Re-ADD /28 spuriously failed (ret=%d)\n", ret);
+
+	ret = rte_fib6_delete(fib, &child, 48);
+	RTE_TEST_ASSERT(ret == 0, "DEL /48 failed (ret=%d)\n", ret);
+	ret = rte_fib6_delete(fib, &parent, 28);
+	RTE_TEST_ASSERT(ret == 0, "Final DEL /28 failed (ret=%d)\n", ret);
+
+	rte_fib6_free(fib);
+	return TEST_SUCCESS;
+}
+
 static struct unit_test_suite fib6_fast_tests = {
 	.suite_name = "fib6 autotest",
 	.setup = NULL,
@@ -685,6 +942,10 @@ static struct unit_test_suite fib6_fast_tests = {
 	TEST_CASE(test_invalid_rcu),
 	TEST_CASE(test_fib_rcu_sync_rw),
 	TEST_CASE(test_drift),
+	TEST_CASE(test_drift_compression),
+	TEST_CASE(test_drift_multilevel),
+	TEST_CASE(test_drift_stress),
+	TEST_CASE(test_drift_tight_pool),
 	TEST_CASES_END()
 	}
 };
-- 
2.43.0


^ permalink raw reply related

* [PATCH v1 4/5] rib: track valid descendant count per node
From: Maxime Leroy @ 2026-05-22 14:58 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, stable, Maxime Leroy
In-Reply-To: <20260522145855.1748406-1-maxime@leroys.fr>

Add a uint32_t field to struct rte_rib6_node that counts valid prefixes
in the node's subtree (excluding self). Maintained incrementally on
insert (when a node becomes valid) and remove (when a node becomes
invalid), in O(tree depth) per operation.

The field fits in the padding before ext[]; the node size and ext[]
offset are unchanged. uint32_t is required because a single subtree
may hold more than 65535 BGP routes.

Expose an __rte_internal helper rte_rib6_count_empty_supernets()
via a new private header lib/rib/rib6_internal.h that descends the
tree once and reports how many byte boundaries below a given prefix
have no valid descendant. lib/fib uses this to maintain tbl8
reservation accounting in a single tree walk instead of one
rte_rib6_get_nxt() call per byte boundary.

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 app/test/test_rib6.c    | 92 +++++++++++++++++++++++++++++++++++++++++
 lib/rib/rib6_internal.h | 37 +++++++++++++++++
 lib/rib/rte_rib6.c      | 80 +++++++++++++++++++++++++++++++++++
 3 files changed, 209 insertions(+)
 create mode 100644 lib/rib/rib6_internal.h

diff --git a/app/test/test_rib6.c b/app/test/test_rib6.c
index 0295a9640c..a1a6ab17f4 100644
--- a/app/test/test_rib6.c
+++ b/app/test/test_rib6.c
@@ -8,6 +8,7 @@
 #include <stdlib.h>
 #include <rte_ip6.h>
 #include <rte_rib6.h>
+#include <rib6_internal.h>
 
 #include "test.h"
 
@@ -20,6 +21,7 @@ static int32_t test_insert_invalid(void);
 static int32_t test_get_fn(void);
 static int32_t test_basic(void);
 static int32_t test_tree_traversal(void);
+static int32_t test_empty_supernets(void);
 
 #define MAX_DEPTH 128
 #define MAX_RULES (1 << 22)
@@ -322,6 +324,95 @@ test_tree_traversal(void)
 	return TEST_SUCCESS;
 }
 
+/*
+ * Exercise rte_rib6_count_empty_supernets() which depends on the
+ * valid_descendants counter maintained on insert/remove.
+ */
+int32_t
+test_empty_supernets(void)
+{
+	struct rte_rib6 *rib = NULL;
+	struct rte_rib6_conf config;
+	struct rte_ipv6_addr ip = RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 0);
+	struct rte_ipv6_addr sibling = RTE_IPV6(0xfcde, 0, 0, 0, 0, 0, 0, 1);
+	uint8_t cnt;
+
+	config.max_nodes = 64;
+	config.ext_sz = 0;
+
+	rib = rte_rib6_create(__func__, SOCKET_ID_ANY, &config);
+	RTE_TEST_ASSERT(rib != NULL, "Failed to create RIB\n");
+
+	/* depth <= 24: no byte boundaries above to inspect. */
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 24);
+	RTE_TEST_ASSERT(cnt == 0, "depth 24 must return 0, got %u\n", cnt);
+
+	/* Empty RIB, /128 query: 13 byte boundaries (24..120) all empty. */
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 13, "empty RIB /128 must return 13, got %u\n", cnt);
+
+	/* Insert a /32 ancestor: level 24 now has a descendant, levels
+	 * 32..120 still empty -> 12.
+	 */
+	RTE_TEST_ASSERT(rte_rib6_insert(rib, &ip, 32) != NULL,
+		"Failed to insert /32\n");
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 12, "after /32 ADD: expected 12, got %u\n", cnt);
+
+	/* Insert a /48 below: levels 24, 32 and 40 see /48 as descendant,
+	 * 48..120 empty -> 10.
+	 */
+	RTE_TEST_ASSERT(rte_rib6_insert(rib, &ip, 48) != NULL,
+		"Failed to insert /48\n");
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 10, "after /48 ADD: expected 10, got %u\n", cnt);
+
+	/* Insert a sibling /128 that shares a long common prefix with ip
+	 * but differs in the last bits. This forces creation of a
+	 * common_node and exercises the inherited valid_descendants of
+	 * that synthesized intermediate.
+	 */
+	RTE_TEST_ASSERT(rte_rib6_insert(rib, &sibling, 128) != NULL,
+		"Failed to insert sibling /128\n");
+	/* sibling shares prefix with ip down to bit 127; for the query on
+	 * ip/128, all byte boundaries 24..120 have descendants (the /32,
+	 * /48 and the sibling /128 chain) -> 0 empty levels.
+	 */
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 0, "fully populated chain: expected 0, got %u\n",
+		cnt);
+
+	/* Remove the /32 ancestor: /48 and /128 sibling still cover all
+	 * byte boundaries 24..120 -> still 0 empty levels.
+	 */
+	rte_rib6_remove(rib, &ip, 32);
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 0,
+		"after /32 DEL still covered: expected 0, got %u\n", cnt);
+
+	/* Remove the /48: only the /128 sibling remains. For an ip/128
+	 * query, levels 24..120 each see the sibling as descendant, so
+	 * count is still 0.
+	 */
+	rte_rib6_remove(rib, &ip, 48);
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 0,
+		"after /48 DEL still covered: expected 0, got %u\n", cnt);
+
+	/* Remove the sibling /128: RIB now empty again. */
+	rte_rib6_remove(rib, &sibling, 128);
+	cnt = rte_rib6_count_empty_supernets(rib, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 13,
+		"after final DEL: expected 13, got %u\n", cnt);
+
+	/* Invalid input: NULL rib. */
+	cnt = rte_rib6_count_empty_supernets(NULL, &ip, 128);
+	RTE_TEST_ASSERT(cnt == 0, "NULL rib must return 0, got %u\n", cnt);
+
+	rte_rib6_free(rib);
+	return TEST_SUCCESS;
+}
+
 static struct unit_test_suite rib6_tests = {
 	.suite_name = "rib6 autotest",
 	.setup = NULL,
@@ -333,6 +424,7 @@ static struct unit_test_suite rib6_tests = {
 		TEST_CASE(test_get_fn),
 		TEST_CASE(test_basic),
 		TEST_CASE(test_tree_traversal),
+		TEST_CASE(test_empty_supernets),
 		TEST_CASES_END()
 	}
 };
diff --git a/lib/rib/rib6_internal.h b/lib/rib/rib6_internal.h
new file mode 100644
index 0000000000..8246626de9
--- /dev/null
+++ b/lib/rib/rib6_internal.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Maxime Leroy, Free Mobile
+ */
+
+#ifndef _RIB6_INTERNAL_H_
+#define _RIB6_INTERNAL_H_
+
+#include <stdint.h>
+
+#include <rte_compat.h>
+#include <rte_ip6.h>
+
+struct rte_rib6;
+
+/**
+ * @internal
+ * Count byte boundaries L in {24, 32, 40, ..., RTE_ALIGN_CEIL(depth, 8) - 8}
+ * for which the supernet of ip at level L has no valid descendant with
+ * depth > L. Used by lib/fib to maintain tbl8 reservation accounting in
+ * a single descent of the binary tree.
+ *
+ * @param rib
+ *  RIB object handle
+ * @param ip
+ *  IPv6 prefix address
+ * @param depth
+ *  prefix length
+ * @return
+ *  number of empty byte boundaries (0 if all levels have descendants
+ *  or depth <= 24)
+ */
+__rte_internal
+uint8_t
+rte_rib6_count_empty_supernets(struct rte_rib6 *rib,
+	const struct rte_ipv6_addr *ip, uint8_t depth);
+
+#endif /* _RIB6_INTERNAL_H_ */
diff --git a/lib/rib/rte_rib6.c b/lib/rib/rte_rib6.c
index ec8ff68e87..ae8aba6563 100644
--- a/lib/rib/rte_rib6.c
+++ b/lib/rib/rte_rib6.c
@@ -18,6 +18,7 @@
 
 #include <rte_rib6.h>
 
+#include "rib6_internal.h"
 #include "rib_log.h"
 
 #define RTE_RIB_VALID_NODE	1
@@ -36,6 +37,7 @@ struct rte_rib6_node {
 	struct rte_rib6_node	*parent;
 	uint64_t		nh;
 	struct rte_ipv6_addr	ip;
+	uint32_t		valid_descendants;
 	uint8_t			depth;
 	uint8_t			flag;
 	uint64_t ext[];
@@ -104,10 +106,35 @@ node_alloc(struct rte_rib6 *rib)
 	ret = rte_mempool_get(rib->node_pool, (void *)&ent);
 	if (unlikely(ret != 0))
 		return NULL;
+	ent->valid_descendants = 0;
 	++rib->cur_nodes;
 	return ent;
 }
 
+/* Increment valid_descendants along the parent chain when node becomes valid. */
+static inline void
+inc_valid_descendants(struct rte_rib6_node *node)
+{
+	struct rte_rib6_node *p = node->parent;
+
+	while (p != NULL) {
+		p->valid_descendants++;
+		p = p->parent;
+	}
+}
+
+/* Decrement valid_descendants along the parent chain when node becomes invalid. */
+static inline void
+dec_valid_descendants(struct rte_rib6_node *node)
+{
+	struct rte_rib6_node *p = node->parent;
+
+	while (p != NULL) {
+		p->valid_descendants--;
+		p = p->parent;
+	}
+}
+
 static void
 node_free(struct rte_rib6 *rib, struct rte_rib6_node *ent)
 {
@@ -250,6 +277,7 @@ rte_rib6_remove(struct rte_rib6 *rib,
 
 	--rib->cur_routes;
 	cur->flag &= ~RTE_RIB_VALID_NODE;
+	dec_valid_descendants(cur);
 	while (!is_valid_node(cur)) {
 		if ((cur->left != NULL) && (cur->right != NULL))
 			return;
@@ -320,6 +348,7 @@ rte_rib6_insert(struct rte_rib6 *rib,
 			*tmp = new_node;
 			new_node->parent = prev;
 			++rib->cur_routes;
+			inc_valid_descendants(new_node);
 			return *tmp;
 		}
 		/*
@@ -332,6 +361,7 @@ rte_rib6_insert(struct rte_rib6 *rib,
 			node_free(rib, new_node);
 			(*tmp)->flag |= RTE_RIB_VALID_NODE;
 			++rib->cur_routes;
+			inc_valid_descendants(*tmp);
 			return *tmp;
 		}
 
@@ -371,6 +401,9 @@ rte_rib6_insert(struct rte_rib6 *rib,
 			new_node->left = *tmp;
 		new_node->parent = (*tmp)->parent;
 		(*tmp)->parent = new_node;
+		/* new_node inherits *tmp's subtree */
+		new_node->valid_descendants = (is_valid_node(*tmp) ? 1 : 0) +
+			(*tmp)->valid_descendants;
 		*tmp = new_node;
 	} else {
 		/* create intermediate node */
@@ -386,6 +419,11 @@ rte_rib6_insert(struct rte_rib6 *rib,
 		common_node->parent = (*tmp)->parent;
 		new_node->parent = common_node;
 		(*tmp)->parent = common_node;
+		/* common_node inherits *tmp's subtree (new_node will be
+		 * counted by inc_valid_descendants below).
+		 */
+		common_node->valid_descendants = (is_valid_node(*tmp) ? 1 : 0) +
+			(*tmp)->valid_descendants;
 		if (get_dir(&(*tmp)->ip, common_depth) == 1) {
 			common_node->left = new_node;
 			common_node->right = *tmp;
@@ -396,6 +434,7 @@ rte_rib6_insert(struct rte_rib6 *rib,
 		*tmp = common_node;
 	}
 	++rib->cur_routes;
+	inc_valid_descendants(new_node);
 	return new_node;
 }
 
@@ -606,3 +645,44 @@ rte_rib6_free(struct rte_rib6 *rib)
 	rte_free(rib);
 	rte_free(te);
 }
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_rib6_count_empty_supernets)
+uint8_t
+rte_rib6_count_empty_supernets(struct rte_rib6 *rib,
+	const struct rte_ipv6_addr *ip, uint8_t depth)
+{
+	uint8_t top = RTE_ALIGN_CEIL(depth, 8);
+	struct rte_rib6_node *cur;
+	bool has_descendant;
+	uint8_t level;
+
+	if (unlikely(rib == NULL || ip == NULL || depth > RTE_IPV6_MAX_DEPTH))
+		return 0;
+	if (depth <= 24)
+		return 0;
+
+	cur = rib->tree;
+
+	/* Single descent through the binary tree, checking each byte
+	 * boundary on the way. NULL at level L propagates upward, so we
+	 * stop at the first empty supernet and tally the remaining levels.
+	 */
+	for (level = 24; level < top; level += 8) {
+		while (cur != NULL && cur->depth < level)
+			cur = get_nxt_node(cur, ip);
+
+		if (cur == NULL || !rte_ipv6_addr_eq_prefix(&cur->ip, ip, level))
+			return (top - level) >> 3;
+
+		if (cur->depth > level)
+			has_descendant = is_valid_node(cur) ||
+				cur->valid_descendants > 0;
+		else
+			has_descendant = cur->valid_descendants > 0;
+
+		if (!has_descendant)
+			return (top - level) >> 3;
+	}
+	return 0;
+}
+
-- 
2.43.0


^ permalink raw reply related

* [PATCH v1 5/5] fib6: speed up tbl8 reservation accounting
From: Maxime Leroy @ 2026-05-22 14:58 UTC (permalink / raw)
  To: Vladimir Medvedkin; +Cc: dev, stable, Maxime Leroy
In-Reply-To: <20260522145855.1748406-1-maxime@leroys.fr>

Replace the local count_empty_levels() helper, which issued up to 13
rte_rib6_get_nxt() calls each descending the binary tree from root,
with the new __rte_internal rte_rib6_count_empty_supernets(). The
latter descends once and consults the valid_descendants counter
maintained inside rte_rib6, answering all byte boundary questions
in O(tree depth) with O(1) work per boundary.

For a /128 ADD with ancestors at every byte boundary, this reduces
the worst-case query cost from 13 trie descents to 1.

Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
 lib/fib/trie.c | 30 +++---------------------------
 1 file changed, 3 insertions(+), 27 deletions(-)

diff --git a/lib/fib/trie.c b/lib/fib/trie.c
index 44b90f72ff..b6ef626fd4 100644
--- a/lib/fib/trie.c
+++ b/lib/fib/trie.c
@@ -13,6 +13,7 @@
 
 #include <rte_rib6.h>
 #include <rte_fib6.h>
+#include <rib6_internal.h>
 #include "fib_log.h"
 #include "trie.h"
 
@@ -534,31 +535,6 @@ modify_dp(struct rte_trie_tbl *dp, struct rte_rib6 *rib,
 	return 0;
 }
 
-/*
- * Count byte boundaries between 24 and CEIL(depth, 8) where the
- * supernet of ip has no descendant in the RIB. This is the number of
- * new tbl8 levels an ADD of ip/depth would introduce, or the number
- * to free at DEL once the prefix has been removed from the RIB.
- *
- * A NULL answer at level L propagates upwards: narrower supernets at
- * L+8, L+16, ... are subsets of S_L and cannot contain descendants
- * either. The loop stops at the first NULL and tallies the remaining
- * boundaries in one shot.
- */
-static uint8_t
-count_empty_levels(struct rte_rib6 *rib, const struct rte_ipv6_addr *ip,
-	uint8_t depth)
-{
-	uint8_t level, top = RTE_ALIGN_CEIL(depth, 8);
-
-	for (level = 24; level < top; level += 8) {
-		if (rte_rib6_get_nxt(rib, ip, level, NULL,
-				RTE_RIB6_GET_NXT_COVER) == NULL)
-			return (top - level) >> 3;
-	}
-	return 0;
-}
-
 int
 trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 	uint8_t depth, uint64_t next_hop, int op)
@@ -596,7 +572,7 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 			return 0;
 		}
 
-		new_levels = count_empty_levels(rib, &ip_masked, depth);
+		new_levels = rte_rib6_count_empty_supernets(rib, &ip_masked, depth);
 		if (dp->rsvd_tbl8s + new_levels > dp->number_tbl8s)
 			return -ENOSPC;
 
@@ -635,7 +611,7 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 		if (ret != 0)
 			return ret;
 		rte_rib6_remove(rib, &ip_masked, depth);
-		dp->rsvd_tbl8s -= count_empty_levels(rib, &ip_masked, depth);
+		dp->rsvd_tbl8s -= rte_rib6_count_empty_supernets(rib, &ip_masked, depth);
 		return 0;
 	default:
 		break;
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2 0/2] Update IDPF Base Code
From: Bruce Richardson @ 2026-05-22 15:09 UTC (permalink / raw)
  To: Soumyadeep Hore; +Cc: manoj.kumar.subbarao, aman.deep.singh, dev
In-Reply-To: <20260520220306.85273-1-soumyadeep.hore@intel.com>

On Wed, May 20, 2026 at 06:03:04PM -0400, Soumyadeep Hore wrote:
> Update idpf base code with compatible configuration on MEV.
> 
> ---
> v2:
> - lists only LTS releases
> - lists releases in descending order, so that latest is on top
> - reworks table to use the new RST table format
> ---
> 
> Christopher Pau (1):
>   net/idpf/base: add MMG device IDs
> 
> Soumyadeep Hore (1):
>   doc: update recommended matching versions for cpfl and idpf
> 
Applied to next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* [PATCH v4 00/26] Add common flow attr/action parsing infrastructure to Intel PMD's
From: Anatoly Burakov @ 2026-05-22 15:14 UTC (permalink / raw)
  To: dev
In-Reply-To: <cover.1770819433.git.anatoly.burakov@intel.com>

This patchset introduces common flow attr/action checking infrastructure to
some Intel PMD's (IXGBE, I40E, IAVF, and ICE). The aim is to reduce code
duplication, simplify implementation of new parsers/verification of existing
ones, and make action/attr handling more consistent across drivers.

v4:
- First few commits were integrated as part of a different patchset
- Added more logging to common infrastructure
- RSS validation now allows discontiguous queue lists by default
- Fixed some checks being too stringent and failing common validation

v3:
- Rebase on latest next-net-intel
- Added 4 new commits that have to do with not using `rte_eth_dev` in rte_flow
- Converted the remaining commits to use adapter structures everywhere
- Minor fixes in how return values are handled
- Fixed incorrect check in i40e RSS validation

v2:
- Rebase on latest main
- Now depends on series 37585 [1]

[1] https://patches.dpdk.org/project/dpdk/list/?series=37585

Anatoly Burakov (21):
  eal/common: add token concatenation macro
  net/intel/common: add common flow action parsing
  net/intel/common: add common flow attr validation
  net/ixgbe: use common checks in ethertype filter
  net/ixgbe: use common checks in syn filter
  net/ixgbe: use common checks in L2 tunnel filter
  net/ixgbe: use common checks in ntuple filter
  net/ixgbe: use common checks in security filter
  net/ixgbe: use common checks in FDIR filters
  net/ixgbe: use common checks in RSS filter
  net/i40e: use common flow attribute checks
  net/i40e: refactor RSS flow parameter checks
  net/i40e: use common action checks for ethertype
  net/i40e: use common action checks for FDIR
  net/i40e: use common action checks for tunnel
  net/iavf: use common flow attribute checks
  net/iavf: use common action checks for IPsec
  net/iavf: use common action checks for hash
  net/iavf: use common action checks for FDIR
  net/iavf: use common action checks for fsub
  net/iavf: use common action checks for flow query

Vladimir Medvedkin (5):
  net/ice: use common flow attribute checks
  net/ice: use common action checks for hash
  net/ice: use common action checks for FDIR
  net/ice: use common action checks for switch
  net/ice: use common action checks for ACL

 drivers/net/intel/common/flow_check.h      |  347 ++++++
 drivers/net/intel/common/log.h             |   40 +
 drivers/net/intel/i40e/i40e_ethdev.h       |    1 -
 drivers/net/intel/i40e/i40e_flow.c         |  433 +++-----
 drivers/net/intel/i40e/i40e_hash.c         |  437 +++++---
 drivers/net/intel/i40e/i40e_hash.h         |    2 +-
 drivers/net/intel/iavf/iavf_fdir.c         |  367 +++---
 drivers/net/intel/iavf/iavf_fsub.c         |  261 ++---
 drivers/net/intel/iavf/iavf_generic_flow.c |  107 +-
 drivers/net/intel/iavf/iavf_generic_flow.h |    2 +-
 drivers/net/intel/iavf/iavf_hash.c         |  153 +--
 drivers/net/intel/iavf/iavf_ipsec_crypto.c |   43 +-
 drivers/net/intel/ice/ice_acl_filter.c     |  146 ++-
 drivers/net/intel/ice/ice_fdir_filter.c    |  384 ++++---
 drivers/net/intel/ice/ice_generic_flow.c   |   59 +-
 drivers/net/intel/ice/ice_generic_flow.h   |    2 +-
 drivers/net/intel/ice/ice_hash.c           |  190 ++--
 drivers/net/intel/ice/ice_switch_filter.c  |  388 +++----
 drivers/net/intel/ixgbe/ixgbe_flow.c       | 1166 +++++++-------------
 lib/eal/include/rte_common.h               |    4 +
 20 files changed, 2264 insertions(+), 2268 deletions(-)
 create mode 100644 drivers/net/intel/common/flow_check.h
 create mode 100644 drivers/net/intel/common/log.h

-- 
2.47.3


^ permalink raw reply

* [PATCH v4 01/26] eal/common: add token concatenation macro
From: Anatoly Burakov @ 2026-05-22 15:14 UTC (permalink / raw)
  To: dev
In-Reply-To: <cover.1779462698.git.anatoly.burakov@intel.com>

Add `RTE_CONCAT()` macro to enable token concatenation with proper macro
expansion.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/eal/include/rte_common.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/eal/include/rte_common.h b/lib/eal/include/rte_common.h
index 0a356abae2..05e72e500e 100644
--- a/lib/eal/include/rte_common.h
+++ b/lib/eal/include/rte_common.h
@@ -918,6 +918,10 @@ __extension__ typedef uint64_t RTE_MARKER64[0];
 /** Take a macro value and get a string version of it */
 #define RTE_STR(x) _RTE_STR(x)
 
+/** Concatenate two tokens after expanding macros in both. */
+#define _RTE_CONCAT2(a, b) a ## b
+#define RTE_CONCAT(a, b) _RTE_CONCAT2(a, b)
+
 /**
  * ISO C helpers to modify format strings using variadic macros.
  * This is a replacement for the ", ## __VA_ARGS__" GNU extension.
-- 
2.47.3


^ permalink raw reply related

* [PATCH v4 02/26] net/intel/common: add common flow action parsing
From: Anatoly Burakov @ 2026-05-22 15:14 UTC (permalink / raw)
  To: dev, Bruce Richardson
In-Reply-To: <cover.1779462698.git.anatoly.burakov@intel.com>

Currently, each driver has their own code for action parsing, which results
in a lot of duplication and subtle mismatches in behavior between drivers.

Add common infrastructure, based on the following assumptions:

- All drivers support at most 4 actions at once, but usually less
- Not every action is supported by all drivers
- We can check a few common things to filter out obviously wrong actions
- Driver performs semantic checks on all valid actions

So, the intention is to reject everything we can reasonably reject at the
outset without knowing anything about the drivers, parametrize what is
trivial to parametrize, and leave the rest for the driver to implement.

While we're at it, also add logging infrastructure for Intel common code,
using the new component name defines that are automatically passed to each
DPDK driver as it is being built.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 drivers/net/intel/common/flow_check.h | 278 ++++++++++++++++++++++++++
 drivers/net/intel/common/log.h        |  40 ++++
 2 files changed, 318 insertions(+)
 create mode 100644 drivers/net/intel/common/flow_check.h
 create mode 100644 drivers/net/intel/common/log.h

diff --git a/drivers/net/intel/common/flow_check.h b/drivers/net/intel/common/flow_check.h
new file mode 100644
index 0000000000..74fb28ae3d
--- /dev/null
+++ b/drivers/net/intel/common/flow_check.h
@@ -0,0 +1,278 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#ifndef _COMMON_INTEL_FLOW_CHECK_H_
+#define _COMMON_INTEL_FLOW_CHECK_H_
+
+#include <bus_pci_driver.h>
+#include <ethdev_driver.h>
+
+#include "log.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Common attr and action validation code for Intel drivers.
+ */
+
+/**
+ * Maximum number of actions that can be stored in a parsed action list.
+ */
+#define CI_FLOW_PARSED_ACTIONS_MAX 4
+
+/* Actions that are reasonably expected to have a conf structure */
+static const enum rte_flow_action_type need_conf[] = {
+	RTE_FLOW_ACTION_TYPE_QUEUE,
+	RTE_FLOW_ACTION_TYPE_RSS,
+	RTE_FLOW_ACTION_TYPE_VF,
+	RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR,
+	RTE_FLOW_ACTION_TYPE_COUNT,
+	RTE_FLOW_ACTION_TYPE_MARK,
+	RTE_FLOW_ACTION_TYPE_SECURITY,
+	RTE_FLOW_ACTION_TYPE_END
+};
+
+/**
+ * Is action type in this list of action types?
+ */
+static inline bool
+ci_flow_action_type_in_list(const enum rte_flow_action_type type,
+		const enum rte_flow_action_type list[])
+{
+	size_t i = 0;
+	while (list[i] != RTE_FLOW_ACTION_TYPE_END) {
+		if (type == list[i])
+			return true;
+		i++;
+	}
+	return false;
+}
+
+/* Forward declarations */
+struct ci_flow_actions;
+struct ci_flow_actions_check_param;
+
+static inline const char *
+ci_flow_action_type_to_str(enum rte_flow_action_type type)
+{
+	const char *name = NULL;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+			&name, sizeof(name), (const void *)(uintptr_t)type, NULL);
+	if (ret < 0 || name == NULL)
+		return "UNKNOWN";
+
+	return name;
+}
+
+/**
+ * Driver-specific action list validation callback.
+ *
+ * Performs driver-specific validation of action parameter list.
+ * Called after all actions have been parsed and added to the list,
+ * allowing validation based on the complete action set.
+ *
+ * @param actions
+ *   The complete list of parsed actions (for context-dependent validation).
+ * @param driver_ctx
+ *   Opaque driver context (e.g., adapter/queue configuration).
+ * @param error
+ *   Pointer to rte_flow_error for reporting failures.
+ * @return
+ *   0 on success, negative errno on failure.
+ */
+typedef int (*ci_flow_actions_check_fn)(const struct ci_flow_actions *actions,
+		const struct ci_flow_actions_check_param *param,
+		struct rte_flow_error *error);
+
+/**
+ * List of actions that we know we've validated.
+ */
+struct ci_flow_actions {
+	/* Number of actions in the list. */
+	uint8_t count;
+	/* Parsed actions array. */
+	struct rte_flow_action const *actions[CI_FLOW_PARSED_ACTIONS_MAX];
+};
+
+/**
+ * Parameters for action list validation. Any element can be NULL/0 as checks are only performed
+ * against constraints specified.
+ */
+struct ci_flow_actions_check_param {
+	/**
+	 * Driver-specific context pointer (e.g., adapter/queue configuration). Can be NULL.
+	 */
+	void *driver_ctx;
+	/**
+	 * Driver-specific action list validation callback. Can be NULL.
+	 */
+	ci_flow_actions_check_fn check;
+	/**
+	 * Allowed action types for this parse parameter. Must be terminated with
+	 * RTE_FLOW_ACTION_TYPE_END. Can be NULL.
+	 */
+	const enum rte_flow_action_type *allowed_types;
+	size_t max_actions;                 /**< Maximum number of actions allowed. */
+	bool rss_queues_contig;             /**< If true, RSS queues must be contiguous. */
+};
+
+static inline int
+__flow_action_check_rss(const struct rte_flow_action_rss *rss,
+		const struct ci_flow_actions_check_param *param,
+		struct rte_flow_error *error)
+{
+	uint32_t qnum, q;
+
+	qnum = rss->queue_num;
+
+	/* either we have both queues and queue number, or we have neither */
+	if ((qnum == 0) != (rss->queue == NULL)) {
+		return rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION_CONF, rss,
+			"If queue number is specified, queue array must also be specified");
+	}
+	/* check if queues are monotonic */
+	for (q = 1; q < qnum; q++) {
+		if (rss->queue[q] < rss->queue[q - 1]) {
+			return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION_CONF, rss,
+				"RSS queues must be in ascending order");
+		}
+		/* if user has requested contiguousness, check that as well */
+		if (param == NULL || !param->rss_queues_contig)
+			continue;
+		if (rss->queue[q] != rss->queue[0] + q) {
+			return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION_CONF, rss,
+				"RSS queues must be contiguous");
+		}
+	}
+	/* if user has requested to check for queue contiguousness, do it */
+	if (param != NULL && param->rss_queues_contig) {
+	}
+
+	/* either we have both key and key length, or we have neither */
+	if ((rss->key_len == 0) != (rss->key == NULL)) {
+		return rte_flow_error_set(error, EINVAL,
+			RTE_FLOW_ERROR_TYPE_ACTION_CONF, rss,
+			"If RSS key is specified, key length must also be specified");
+	}
+	return 0;
+}
+
+static inline int
+__flow_action_check_generic(const struct rte_flow_action *action,
+		const struct ci_flow_actions_check_param *param,
+		struct rte_flow_error *error)
+{
+	/* is this action in our allowed list? */
+	if (param != NULL && param->allowed_types != NULL &&
+			!ci_flow_action_type_in_list(action->type, param->allowed_types)) {
+		return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				action, "Unsupported action");
+	}
+	/* do we need to validate presence of conf? */
+	if (ci_flow_action_type_in_list(action->type, need_conf)) {
+		if (action->conf == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+						  RTE_FLOW_ERROR_TYPE_ACTION_CONF, action,
+						  "Action requires configuration");
+		}
+	}
+
+	/* type-specific validation */
+	switch (action->type) {
+	case RTE_FLOW_ACTION_TYPE_RSS:
+	{
+		const struct rte_flow_action_rss *rss =
+				(const struct rte_flow_action_rss *)action->conf;
+		int ret;
+
+		ret = __flow_action_check_rss(rss, param, error);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+	default:
+		/* no specific validation */
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * Validate and parse a list of rte_flow_action into a parsed action list.
+ *
+ * @param actions pointer to array of rte_flow_action, terminated by RTE_FLOW_ACTION_TYPE_END
+ * @param param pointer to ci_flow_actions_check_param structure (can be NULL)
+ * @param parsed_actions pointer to ci_flow_actions structure to store parsed actions
+ * @param error pointer to rte_flow_error structure for error reporting
+ *
+ * @return 0 on success, negative errno on failure.
+ */
+static inline int
+ci_flow_check_actions(const struct rte_flow_action *actions,
+	const struct ci_flow_actions_check_param *param,
+	struct ci_flow_actions *parsed_actions,
+	struct rte_flow_error *error)
+{
+	size_t i = 0;
+	int ret;
+
+	if (actions == NULL) {
+		return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+				NULL, "Missing actions");
+	}
+
+	/* reset the list */
+	*parsed_actions = (struct ci_flow_actions){0};
+
+	while (actions[i].type != RTE_FLOW_ACTION_TYPE_END) {
+		const struct rte_flow_action *action = &actions[i++];
+
+		/* skip VOID actions */
+		if (action->type == RTE_FLOW_ACTION_TYPE_VOID)
+			continue;
+
+		/* generic validation for actions - this will check against param as well */
+		ret = __flow_action_check_generic(action, param, error);
+		if (ret < 0)
+			return ret;
+
+		/* add action to the list */
+		if (parsed_actions->count >= RTE_DIM(parsed_actions->actions)) {
+			return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+							action, "Too many actions");
+		}
+		/* user may have specified a maximum number of actions */
+		if (param != NULL && param->max_actions != 0 &&
+		    parsed_actions->count >= param->max_actions) {
+			return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+							action, "Too many actions");
+		}
+		CI_DRV_LOG(DEBUG, "Parsed action %u: type=%s", parsed_actions->count,
+			   ci_flow_action_type_to_str(action->type));
+		parsed_actions->actions[parsed_actions->count++] = action;
+	}
+
+	/* now, call into user validation if specified */
+	if (param != NULL && param->check != NULL) {
+		ret = param->check(parsed_actions, param, error);
+		if (ret < 0)
+			return ret;
+	}
+	/* if we didn't parse anything, valid action list is empty */
+	return parsed_actions->count == 0 ? -EINVAL : 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _COMMON_INTEL_FLOW_CHECK_H_ */
diff --git a/drivers/net/intel/common/log.h b/drivers/net/intel/common/log.h
new file mode 100644
index 0000000000..d99e4d1a37
--- /dev/null
+++ b/drivers/net/intel/common/log.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _COMMON_INTEL_LOG_H_
+#define _COMMON_INTEL_LOG_H_
+
+#include <rte_log.h>
+
+/*
+ * Common logging for shared Intel driver code.
+ *
+ * This header must only be included from driver translation units, where the
+ * build system has already defined RTE_COMPONENT_NAME (e.g. "iavf"). It uses
+ * that token to reference the per-driver logtype variable that every Intel
+ * driver registers (e.g. iavf_logtype_driver), so no additional setup is
+ * needed beyond what each driver already provides.
+ *
+ * Usage: CI_DRV_LOG(DEBUG, "format %s", arg);
+ */
+
+#ifndef RTE_COMPONENT_NAME
+/* CI_DRV_LOG is a no-op when included outside a driver build context. */
+#define CI_DRV_LOG(level, fmt, ...) do { } while (0)
+#else /* RTE_COMPONENT_NAME */
+
+/* Resolves to the per-driver logtype variable, e.g. iavf_logtype_driver. */
+#define CI_DRV_LOGTYPE RTE_CONCAT(RTE_COMPONENT_NAME, _logtype_driver)
+
+/* Forward-declare the logtype so shared headers need not include driver headers. */
+extern int CI_DRV_LOGTYPE;
+
+#define CI_DRV_LOG(level, fmt, ...) \
+	rte_log(RTE_LOG_##level, CI_DRV_LOGTYPE, \
+		"PMD_INTEL_COMMON: %s(): " fmt "\n", \
+		__func__, ##__VA_ARGS__)
+
+#endif /* RTE_COMPONENT_NAME */
+
+#endif /* _COMMON_INTEL_LOG_H_ */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v4 03/26] net/intel/common: add common flow attr validation
From: Anatoly Burakov @ 2026-05-22 15:14 UTC (permalink / raw)
  To: dev, Bruce Richardson
In-Reply-To: <cover.1779462698.git.anatoly.burakov@intel.com>

There are a lot of commonalities between what kinds of flow attr each Intel
driver supports. Add a helper function that will validate attr based on
common requirements and (optional) parameter checks.

Things we check for:
- Rejecting NULL attr (obviously)
- Default to ingress flows
- Transfer, group, priority, and egress are not allowed unless requested

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 drivers/net/intel/common/flow_check.h | 69 +++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/drivers/net/intel/common/flow_check.h b/drivers/net/intel/common/flow_check.h
index 74fb28ae3d..0572028664 100644
--- a/drivers/net/intel/common/flow_check.h
+++ b/drivers/net/intel/common/flow_check.h
@@ -54,6 +54,7 @@ ci_flow_action_type_in_list(const enum rte_flow_action_type type,
 /* Forward declarations */
 struct ci_flow_actions;
 struct ci_flow_actions_check_param;
+struct ci_flow_attr_check_param;
 
 static inline const char *
 ci_flow_action_type_to_str(enum rte_flow_action_type type)
@@ -271,6 +272,74 @@ ci_flow_check_actions(const struct rte_flow_action *actions,
 	return parsed_actions->count == 0 ? -EINVAL : 0;
 }
 
+/**
+ * Parameter structure for attr check.
+ */
+struct ci_flow_attr_check_param {
+	bool allow_priority; /**< True if priority attribute is allowed. */
+	bool allow_transfer; /**< True if transfer attribute is allowed. */
+	bool allow_group;    /**< True if group attribute is allowed. */
+	bool expect_egress;  /**< True if egress attribute is expected. */
+};
+
+/**
+ * Validate rte_flow_attr structure against specified constraints.
+ *
+ * @param attr Pointer to rte_flow_attr structure to validate.
+ * @param attr_param Pointer to ci_flow_attr_check_param structure specifying constraints.
+ * @param error Pointer to rte_flow_error structure for error reporting.
+ *
+ * @return 0 on success, negative errno on failure.
+ */
+static inline int
+ci_flow_check_attr(const struct rte_flow_attr *attr,
+		const struct ci_flow_attr_check_param *attr_param,
+		struct rte_flow_error *error)
+{
+	if (attr == NULL) {
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ATTR, attr,
+					  "NULL attribute");
+	}
+
+	/* Direction must be either ingress or egress */
+	if (attr->ingress == attr->egress) {
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ATTR, attr,
+					  "Either ingress or egress must be set");
+	}
+
+	/* Expect ingress by default */
+	if (attr->egress && (attr_param == NULL || !attr_param->expect_egress)) {
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ATTR_EGRESS, attr,
+					  "Egress not supported");
+	}
+
+	/* May not be supported */
+	if (attr->transfer && (attr_param == NULL || !attr_param->allow_transfer)) {
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ATTR_TRANSFER, attr,
+					  "Transfer not supported");
+	}
+
+	/* May not be supported */
+	if (attr->group && (attr_param == NULL || !attr_param->allow_group)) {
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ATTR_GROUP, attr,
+					  "Group not supported");
+	}
+
+	/* May not be supported */
+	if (attr->priority && (attr_param == NULL || !attr_param->allow_priority)) {
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY, attr,
+					  "Priority not supported");
+	}
+
+	return 0;
+}
+
 #ifdef __cplusplus
 }
 #endif
-- 
2.47.3


^ permalink raw reply related

* [PATCH v4 04/26] net/ixgbe: use common checks in ethertype filter
From: Anatoly Burakov @ 2026-05-22 15:14 UTC (permalink / raw)
  To: dev, Vladimir Medvedkin
In-Reply-To: <cover.1779462698.git.anatoly.burakov@intel.com>

Use the common attr and action parsing infrastructure in ethertype. This
allows us to remove some checks as they are no longer necessary (such as
whether DROP flag was set - if we do not accept DROP actions, we do not set
the DROP flag), as well as make some checks more stringent (such as
rejecting more than one action).

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 drivers/net/intel/ixgbe/ixgbe_flow.c | 190 ++++++++++-----------------
 1 file changed, 73 insertions(+), 117 deletions(-)

diff --git a/drivers/net/intel/ixgbe/ixgbe_flow.c b/drivers/net/intel/ixgbe/ixgbe_flow.c
index 9038aae001..5816b35f55 100644
--- a/drivers/net/intel/ixgbe/ixgbe_flow.c
+++ b/drivers/net/intel/ixgbe/ixgbe_flow.c
@@ -46,6 +46,8 @@
 #include "base/ixgbe_phy.h"
 #include "rte_pmd_ixgbe.h"
 
+#include "../common/flow_check.h"
+
 
 #define IXGBE_MIN_N_TUPLE_PRIO 1
 #define IXGBE_MAX_N_TUPLE_PRIO 7
@@ -124,6 +126,41 @@ const struct rte_flow_action *next_no_void_action(
 	}
 }
 
+/*
+ * All ixgbe engines mostly check the same stuff, so use a common check.
+ */
+static int
+ixgbe_flow_actions_check(const struct ci_flow_actions *actions,
+		const struct ci_flow_actions_check_param *param,
+		struct rte_flow_error *error)
+{
+	const struct rte_flow_action *action;
+	struct rte_eth_dev_data *dev_data = param->driver_ctx;
+	size_t idx;
+
+	for (idx = 0; idx < actions->count; idx++) {
+		action = actions->actions[idx];
+
+		switch (action->type) {
+		case RTE_FLOW_ACTION_TYPE_QUEUE:
+		{
+			const struct rte_flow_action_queue *queue = action->conf;
+			if (queue->index >= dev_data->nb_rx_queues) {
+				return rte_flow_error_set(error, EINVAL,
+						RTE_FLOW_ERROR_TYPE_ACTION,
+						action,
+						"queue index out of range");
+			}
+			break;
+		}
+		default:
+			/* no specific validation */
+			break;
+		}
+	}
+	return 0;
+}
+
 /**
  * Please be aware there's an assumption for all the parsers.
  * rte_flow_item is using big endian, rte_flow_attr and
@@ -725,38 +762,14 @@ ixgbe_parse_ntuple_filter(struct rte_eth_dev *dev,
  * item->last should be NULL.
  */
 static int
-cons_parse_ethertype_filter(const struct rte_flow_attr *attr,
-			    const struct rte_flow_item *pattern,
-			    const struct rte_flow_action *actions,
-			    struct rte_eth_ethertype_filter *filter,
-			    struct rte_flow_error *error)
+cons_parse_ethertype_filter(const struct rte_flow_item *pattern,
+		const struct rte_flow_action *action,
+		struct rte_eth_ethertype_filter *filter,
+		struct rte_flow_error *error)
 {
 	const struct rte_flow_item *item;
-	const struct rte_flow_action *act;
 	const struct rte_flow_item_eth *eth_spec;
 	const struct rte_flow_item_eth *eth_mask;
-	const struct rte_flow_action_queue *act_q;
-
-	if (!pattern) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ITEM_NUM,
-				NULL, "NULL pattern.");
-		return -rte_errno;
-	}
-
-	if (!actions) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION_NUM,
-				NULL, "NULL action.");
-		return -rte_errno;
-	}
-
-	if (!attr) {
-		rte_flow_error_set(error, EINVAL,
-				   RTE_FLOW_ERROR_TYPE_ATTR,
-				   NULL, "NULL attribute.");
-		return -rte_errno;
-	}
 
 	item = next_no_void_pattern(pattern, NULL);
 	/* The first non-void item should be MAC. */
@@ -826,87 +839,30 @@ cons_parse_ethertype_filter(const struct rte_flow_attr *attr,
 		return -rte_errno;
 	}
 
-	/* Parse action */
-
-	act = next_no_void_action(actions, NULL);
-	if (act->type != RTE_FLOW_ACTION_TYPE_QUEUE &&
-	    act->type != RTE_FLOW_ACTION_TYPE_DROP) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION,
-				act, "Not supported action.");
-		return -rte_errno;
-	}
-
-	if (act->type == RTE_FLOW_ACTION_TYPE_QUEUE) {
-		act_q = (const struct rte_flow_action_queue *)act->conf;
-		filter->queue = act_q->index;
-	} else {
-		filter->flags |= RTE_ETHTYPE_FLAGS_DROP;
-	}
-
-	/* Check if the next non-void item is END */
-	act = next_no_void_action(actions, act);
-	if (act->type != RTE_FLOW_ACTION_TYPE_END) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION,
-				act, "Not supported action.");
-		return -rte_errno;
-	}
-
-	/* Parse attr */
-	/* Must be input direction */
-	if (!attr->ingress) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
-				attr, "Only support ingress.");
-		return -rte_errno;
-	}
-
-	/* Not supported */
-	if (attr->egress) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ATTR_EGRESS,
-				attr, "Not support egress.");
-		return -rte_errno;
-	}
-
-	/* Not supported */
-	if (attr->transfer) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ATTR_TRANSFER,
-				attr, "No support for transfer.");
-		return -rte_errno;
-	}
-
-	/* Not supported */
-	if (attr->priority) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
-				attr, "Not support priority.");
-		return -rte_errno;
-	}
-
-	/* Not supported */
-	if (attr->group) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
-				attr, "Not support group.");
-		return -rte_errno;
-	}
+	filter->queue = ((const struct rte_flow_action_queue *)action->conf)->index;
 
 	return 0;
 }
 
 static int
-ixgbe_parse_ethertype_filter(struct rte_eth_dev *dev,
-				 const struct rte_flow_attr *attr,
-			     const struct rte_flow_item pattern[],
-			     const struct rte_flow_action actions[],
-			     struct rte_eth_ethertype_filter *filter,
-			     struct rte_flow_error *error)
+ixgbe_parse_ethertype_filter(struct rte_eth_dev *dev, const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[], const struct rte_flow_action actions[],
+		struct rte_eth_ethertype_filter *filter, struct rte_flow_error *error)
 {
 	int ret;
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ci_flow_actions parsed_actions;
+	struct ci_flow_actions_check_param ap_param = {
+		.allowed_types = (const enum rte_flow_action_type[]){
+			/* only queue is allowed here */
+			RTE_FLOW_ACTION_TYPE_QUEUE,
+			RTE_FLOW_ACTION_TYPE_END
+		},
+		.max_actions = 1,
+		.driver_ctx = dev->data,
+		.check = ixgbe_flow_actions_check
+	};
+	const struct rte_flow_action *action;
 
 	if (hw->mac.type != ixgbe_mac_82599EB &&
 			hw->mac.type != ixgbe_mac_X540 &&
@@ -916,19 +872,27 @@ ixgbe_parse_ethertype_filter(struct rte_eth_dev *dev,
 			hw->mac.type != ixgbe_mac_E610)
 		return -ENOTSUP;
 
-	ret = cons_parse_ethertype_filter(attr, pattern,
-					actions, filter, error);
+	/* validate attributes */
+	ret = ci_flow_check_attr(attr, NULL, error);
+	if (ret)
+		return ret;
 
+	/* parse requested actions */
+	ret = ci_flow_check_actions(actions, &ap_param, &parsed_actions, error);
 	if (ret)
 		return ret;
 
-	if (filter->queue >= dev->data->nb_rx_queues) {
-		memset(filter, 0, sizeof(struct rte_eth_ethertype_filter));
-		rte_flow_error_set(error, EINVAL,
-			RTE_FLOW_ERROR_TYPE_ITEM,
-			NULL, "queue index much too big");
-		return -rte_errno;
+	/* only one action is supported */
+	if (parsed_actions.count > 1) {
+		return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+					  parsed_actions.actions[1],
+					  "Only one action can be specified at a time");
 	}
+	action = parsed_actions.actions[0];
+
+	ret = cons_parse_ethertype_filter(pattern, action, filter, error);
+	if (ret)
+		return ret;
 
 	if (filter->ether_type == RTE_ETHER_TYPE_IPV4 ||
 		filter->ether_type == RTE_ETHER_TYPE_IPV6) {
@@ -947,14 +911,6 @@ ixgbe_parse_ethertype_filter(struct rte_eth_dev *dev,
 		return -rte_errno;
 	}
 
-	if (filter->flags & RTE_ETHTYPE_FLAGS_DROP) {
-		memset(filter, 0, sizeof(struct rte_eth_ethertype_filter));
-		rte_flow_error_set(error, EINVAL,
-			RTE_FLOW_ERROR_TYPE_ITEM,
-			NULL, "drop option is unsupported");
-		return -rte_errno;
-	}
-
 	return 0;
 }
 
-- 
2.47.3


^ permalink raw reply related

* [PATCH v4 05/26] net/ixgbe: use common checks in syn filter
From: Anatoly Burakov @ 2026-05-22 15:14 UTC (permalink / raw)
  To: dev, Vladimir Medvedkin
In-Reply-To: <cover.1779462698.git.anatoly.burakov@intel.com>

Use the common attr and action parsing infrastructure in syn filter. Some
checks have been rearranged or become more stringent due to using common
infrastructure. In particular, group attr was ignored previously but is
now explicitly rejected.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 drivers/net/intel/ixgbe/ixgbe_flow.c | 130 +++++++--------------------
 1 file changed, 32 insertions(+), 98 deletions(-)

diff --git a/drivers/net/intel/ixgbe/ixgbe_flow.c b/drivers/net/intel/ixgbe/ixgbe_flow.c
index 5816b35f55..fe4ef84dda 100644
--- a/drivers/net/intel/ixgbe/ixgbe_flow.c
+++ b/drivers/net/intel/ixgbe/ixgbe_flow.c
@@ -935,38 +935,13 @@ ixgbe_parse_ethertype_filter(struct rte_eth_dev *dev, const struct rte_flow_attr
  * item->last should be NULL.
  */
 static int
-cons_parse_syn_filter(const struct rte_flow_attr *attr,
-				const struct rte_flow_item pattern[],
-				const struct rte_flow_action actions[],
-				struct rte_eth_syn_filter *filter,
-				struct rte_flow_error *error)
+cons_parse_syn_filter(const struct rte_flow_attr *attr, const struct rte_flow_item pattern[],
+		const struct rte_flow_action_queue *q_act, struct rte_eth_syn_filter *filter,
+		struct rte_flow_error *error)
 {
 	const struct rte_flow_item *item;
-	const struct rte_flow_action *act;
 	const struct rte_flow_item_tcp *tcp_spec;
 	const struct rte_flow_item_tcp *tcp_mask;
-	const struct rte_flow_action_queue *act_q;
-
-	if (!pattern) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ITEM_NUM,
-				NULL, "NULL pattern.");
-		return -rte_errno;
-	}
-
-	if (!actions) {
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION_NUM,
-				NULL, "NULL action.");
-		return -rte_errno;
-	}
-
-	if (!attr) {
-		rte_flow_error_set(error, EINVAL,
-				   RTE_FLOW_ERROR_TYPE_ATTR,
-				   NULL, "NULL attribute.");
-		return -rte_errno;
-	}
 
 
 	/* the first not void item should be MAC or IPv4 or IPv6 or TCP */
@@ -1074,63 +1049,7 @@ cons_parse_syn_filter(const struct rte_flow_attr *attr,
 		return -rte_errno;
 	}
 
-	/* check if the first not void action is QUEUE. */
-	act = next_no_void_action(actions, NULL);
-	if (act->type != RTE_FLOW_ACTION_TYPE_QUEUE) {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION,
-				act, "Not supported action.");
-		return -rte_errno;
-	}
-
-	act_q = (const struct rte_flow_action_queue *)act->conf;
-	filter->queue = act_q->index;
-	if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM) {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION,
-				act, "Not supported action.");
-		return -rte_errno;
-	}
-
-	/* check if the next not void item is END */
-	act = next_no_void_action(actions, act);
-	if (act->type != RTE_FLOW_ACTION_TYPE_END) {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-				RTE_FLOW_ERROR_TYPE_ACTION,
-				act, "Not supported action.");
-		return -rte_errno;
-	}
-
-	/* parse attr */
-	/* must be input direction */
-	if (!attr->ingress) {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-			RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
-			attr, "Only support ingress.");
-		return -rte_errno;
-	}
-
-	/* not supported */
-	if (attr->egress) {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-			RTE_FLOW_ERROR_TYPE_ATTR_EGRESS,
-			attr, "Not support egress.");
-		return -rte_errno;
-	}
-
-	/* not supported */
-	if (attr->transfer) {
-		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
-		rte_flow_error_set(error, EINVAL,
-			RTE_FLOW_ERROR_TYPE_ATTR_TRANSFER,
-			attr, "No support for transfer.");
-		return -rte_errno;
-	}
+	filter->queue = q_act->index;
 
 	/* Support 2 priorities, the lowest or highest. */
 	if (!attr->priority) {
@@ -1141,7 +1060,7 @@ cons_parse_syn_filter(const struct rte_flow_attr *attr,
 		memset(filter, 0, sizeof(struct rte_eth_syn_filter));
 		rte_flow_error_set(error, EINVAL,
 			RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
-			attr, "Not support priority.");
+			attr, "Priority can be 0 or 0xFFFFFFFF");
 		return -rte_errno;
 	}
 
@@ -1149,15 +1068,27 @@ cons_parse_syn_filter(const struct rte_flow_attr *attr,
 }
 
 static int
-ixgbe_parse_syn_filter(struct rte_eth_dev *dev,
-				 const struct rte_flow_attr *attr,
-			     const struct rte_flow_item pattern[],
-			     const struct rte_flow_action actions[],
-			     struct rte_eth_syn_filter *filter,
-			     struct rte_flow_error *error)
+ixgbe_parse_syn_filter(struct rte_eth_dev *dev, const struct rte_flow_attr *attr,
+		const struct rte_flow_item pattern[], const struct rte_flow_action actions[],
+		struct rte_eth_syn_filter *filter, struct rte_flow_error *error)
 {
 	int ret;
 	struct ixgbe_hw *hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+	struct ci_flow_actions parsed_actions;
+	struct ci_flow_actions_check_param ap_param = {
+		.allowed_types = (const enum rte_flow_action_type[]){
+			/* only queue is allowed here */
+			RTE_FLOW_ACTION_TYPE_QUEUE,
+			RTE_FLOW_ACTION_TYPE_END
+		},
+		.driver_ctx = dev->data,
+		.check = ixgbe_flow_actions_check,
+		.max_actions = 1,
+	};
+	struct ci_flow_attr_check_param attr_param = {
+		.allow_priority = true,
+	};
+	const struct rte_flow_action *action;
 
 	if (hw->mac.type != ixgbe_mac_82599EB &&
 			hw->mac.type != ixgbe_mac_X540 &&
@@ -1167,16 +1098,19 @@ ixgbe_parse_syn_filter(struct rte_eth_dev *dev,
 			hw->mac.type != ixgbe_mac_E610)
 		return -ENOTSUP;
 
-	ret = cons_parse_syn_filter(attr, pattern,
-					actions, filter, error);
-
-	if (filter->queue >= dev->data->nb_rx_queues)
-		return -rte_errno;
+	/* validate attributes */
+	ret = ci_flow_check_attr(attr, &attr_param, error);
+	if (ret)
+		return ret;
 
+	/* parse requested actions */
+	ret = ci_flow_check_actions(actions, &ap_param, &parsed_actions, error);
 	if (ret)
 		return ret;
 
-	return 0;
+	action = parsed_actions.actions[0];
+
+	return cons_parse_syn_filter(attr, pattern, action->conf, filter, error);
 }
 
 /**
-- 
2.47.3


^ permalink raw reply related


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