DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 2/6] net/iavf: fix leak of queue to traffic class mapping data
From: Bruce Richardson @ 2026-07-10 10:33 UTC (permalink / raw)
  To: dev; +Cc: ciara.loftus, Bruce Richardson, stable
In-Reply-To: <20260710103403.1548864-1-bruce.richardson@intel.com>

On iavf TM hierarchy commit, the queue to traffic class mapping is
generated and stored in a pointer from the VF structure. However, that
data is never later freed. The data is also unnecessarily allocated from
hugepage memory.

Fix these issues by replacing rte_zmalloc with calloc, and then
appropriately freeing the memory at a) uninit of the device, b) at
failure of apply of the new settings and c) replacement of old data by
new.

Fixes: 3fd32df381f8 ("net/iavf: check Tx packet with correct UP and queue")
Cc: stable@dpdk.org

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 drivers/net/intel/iavf/iavf_ethdev.c |  2 ++
 drivers/net/intel/iavf/iavf_tm.c     | 11 ++++++++---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/drivers/net/intel/iavf/iavf_ethdev.c b/drivers/net/intel/iavf/iavf_ethdev.c
index 80e740ef29..1cd8c88384 100644
--- a/drivers/net/intel/iavf/iavf_ethdev.c
+++ b/drivers/net/intel/iavf/iavf_ethdev.c
@@ -2755,6 +2755,8 @@ iavf_uninit_vf(struct rte_eth_dev *dev)
 
 	rte_free(vf->qos_cap);
 	vf->qos_cap = NULL;
+	free(vf->qtc_map);
+	vf->qtc_map = NULL;
 
 	rte_free(vf->rss_lut);
 	vf->rss_lut = NULL;
diff --git a/drivers/net/intel/iavf/iavf_tm.c b/drivers/net/intel/iavf/iavf_tm.c
index 5f888d654f..faa2d4b8a0 100644
--- a/drivers/net/intel/iavf/iavf_tm.c
+++ b/drivers/net/intel/iavf/iavf_tm.c
@@ -97,6 +97,9 @@ iavf_tm_conf_uninit(struct rte_eth_dev *dev)
 			     shaper_profile, node);
 		rte_free(shaper_profile);
 	}
+
+	free(vf->qtc_map);
+	vf->qtc_map = NULL;
 }
 
 static inline struct iavf_tm_node *
@@ -801,7 +804,8 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 	struct virtchnl_queues_bw_cfg *q_bw = NULL;
 	struct iavf_tm_node_list *queue_list = &vf->tm_conf.queue_list;
 	struct iavf_tm_node *tm_node;
-	struct iavf_qtc_map *qtc_map;
+	struct iavf_qtc_map *qtc_map = NULL;
+	struct iavf_qtc_map *old_qtc_map = vf->qtc_map; /* to free memory if new map assigned */
 	uint16_t size, size_q;
 	int index = 0, node_committed = 0;
 	int i, ret_val = IAVF_SUCCESS;
@@ -889,8 +893,7 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 		goto fail_clear;
 
 	/* store the queue TC mapping info */
-	qtc_map = rte_zmalloc("qtc_map",
-		sizeof(struct iavf_qtc_map) * q_tc_mapping->num_tc, 0);
+	qtc_map = calloc(q_tc_mapping->num_tc, sizeof(struct iavf_qtc_map));
 	if (!qtc_map) {
 		ret_val = IAVF_ERR_NO_MEMORY;
 		goto fail_clear;
@@ -910,6 +913,7 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 		goto fail_clear;
 
 	vf->qtc_map = qtc_map;
+	free(old_qtc_map);
 	if (adapter->stopped == 1)
 		vf->tm_conf.committed = true;
 	free(q_bw);
@@ -925,5 +929,6 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 err:
 	free(q_bw);
 	free(q_tc_mapping);
+	free(qtc_map);
 	return ret_val;
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 1/6] net/iavf: fix local memory leaks in TM hierarchy commit
From: Bruce Richardson @ 2026-07-10 10:33 UTC (permalink / raw)
  To: dev; +Cc: ciara.loftus, Bruce Richardson, stable
In-Reply-To: <20260710103403.1548864-1-bruce.richardson@intel.com>

The iavf_hierachy_commit function uses a number of temporary variables,
which, though small, are still leaked at function end. Clean this up by
freeing them before the function returns. Since these are not variables
that need to be in hugepage memory, also switch from using rte_zmalloc
to calloc.

Fixes: 44d0a720a538 ("net/iavf: query QoS capabilities and set queue TC mapping")
Fixes: 5779a8894d15 ("net/iavf: support queue rate limit configuration")
Cc: stable@dpdk.org

Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
 drivers/net/intel/iavf/iavf_tm.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/net/intel/iavf/iavf_tm.c b/drivers/net/intel/iavf/iavf_tm.c
index e3492ec491..5f888d654f 100644
--- a/drivers/net/intel/iavf/iavf_tm.c
+++ b/drivers/net/intel/iavf/iavf_tm.c
@@ -1,6 +1,8 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2010-2017 Intel Corporation
  */
+#include <stdlib.h>
+
 #include <rte_tm_driver.h>
 
 #include "iavf.h"
@@ -795,8 +797,8 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 	struct iavf_info *vf = IAVF_DEV_PRIVATE_TO_VF(dev->data->dev_private);
 	struct iavf_adapter *adapter =
 		IAVF_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct virtchnl_queue_tc_mapping *q_tc_mapping;
-	struct virtchnl_queues_bw_cfg *q_bw;
+	struct virtchnl_queue_tc_mapping *q_tc_mapping = NULL;
+	struct virtchnl_queues_bw_cfg *q_bw = NULL;
 	struct iavf_tm_node_list *queue_list = &vf->tm_conf.queue_list;
 	struct iavf_tm_node *tm_node;
 	struct iavf_qtc_map *qtc_map;
@@ -832,7 +834,7 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 
 	size = sizeof(*q_tc_mapping) + sizeof(q_tc_mapping->tc[0]) *
 		(vf->qos_cap->num_elem - 1);
-	q_tc_mapping = rte_zmalloc("q_tc", size, 0);
+	q_tc_mapping = calloc(1, size);
 	if (!q_tc_mapping) {
 		ret_val = IAVF_ERR_NO_MEMORY;
 		goto fail_clear;
@@ -840,7 +842,7 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 
 	size_q = sizeof(*q_bw) + sizeof(q_bw->cfg[0]) *
 		(vf->num_queue_pairs - 1);
-	q_bw = rte_zmalloc("q_bw", size_q, 0);
+	q_bw = calloc(1, size_q);
 	if (!q_bw) {
 		ret_val = IAVF_ERR_NO_MEMORY;
 		goto fail_clear;
@@ -889,8 +891,10 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 	/* store the queue TC mapping info */
 	qtc_map = rte_zmalloc("qtc_map",
 		sizeof(struct iavf_qtc_map) * q_tc_mapping->num_tc, 0);
-	if (!qtc_map)
-		return IAVF_ERR_NO_MEMORY;
+	if (!qtc_map) {
+		ret_val = IAVF_ERR_NO_MEMORY;
+		goto fail_clear;
+	}
 
 	for (i = 0; i < q_tc_mapping->num_tc; i++) {
 		q_tc_mapping->tc[i].req.start_queue_id = index;
@@ -908,6 +912,8 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 	vf->qtc_map = qtc_map;
 	if (adapter->stopped == 1)
 		vf->tm_conf.committed = true;
+	free(q_bw);
+	free(q_tc_mapping);
 	return ret_val;
 
 fail_clear:
@@ -917,5 +923,7 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
 		iavf_tm_conf_init(dev);
 	}
 err:
+	free(q_bw);
+	free(q_tc_mapping);
 	return ret_val;
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 0/6] net/intel: fix some small memory leaks
From: Bruce Richardson @ 2026-07-10 10:33 UTC (permalink / raw)
  To: dev; +Cc: ciara.loftus, Bruce Richardson
In-Reply-To: <20260703083335.4058936-1-bruce.richardson@intel.com>

AI review of a patch to traffic management code identified a couple of
small memory leaks in that function. When fixing those, I then asked AI
to identify other similar leaks other Intel drivers, leading to this
patchset.

V3: update based on feedback on v2:
    - fix log message on patch 3,
    - add additional free on close in patch 5
V2: expand from 2 patches to 6 to cover some more leaks

Bruce Richardson (6):
  net/iavf: fix local memory leaks in TM hierarchy commit
  net/iavf: fix leak of queue to traffic class mapping data
  net/iavf: fix memory leak on error when adding flow parser
  net/ice: fix buffer leak in config of Tx queue TM node
  net/iavf: fix leak of flex metadata extraction field
  net/iavf: fix leak of IPsec crypto capabilities array

 drivers/net/intel/iavf/iavf_ethdev.c       |  7 +++++
 drivers/net/intel/iavf/iavf_generic_flow.c |  1 +
 drivers/net/intel/iavf/iavf_ipsec_crypto.c |  2 ++
 drivers/net/intel/iavf/iavf_tm.c           | 31 +++++++++++++++-------
 drivers/net/intel/ice/ice_tm.c             |  6 +++--
 5 files changed, 36 insertions(+), 11 deletions(-)

--
2.53.0


^ permalink raw reply

* Re: [PATCH v2 0/3] net/intel: fix link status when interrupt delivery unavailable
From: Bruce Richardson @ 2026-07-10  9:22 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev
In-Reply-To: <20260709112556.1765262-1-ciara.loftus@intel.com>

On Thu, Jul 09, 2026 at 11:25:53AM +0000, Ciara Loftus wrote:
> On FreeBSD with nic_uio, interrupt registration fails silently and
> hardware events, including link state notifications, are never processed
> via the interrupt path.
> 
> For ice, this affects the typical polling-mode configuration: the
> interrupt handler is never invoked, AdminQ messages go unprocessed,
> and link status is not reliable. A periodic alarm is added to drain
> the AdminQ when interrupt delivery is unavailable. For i40e, an
> alarm already runs unconditionally in polling mode, so that
> configuration is unaffected. The gap is the interrupt-mode
> configuration, where the existing alarm was not activated as a
> fallback when interrupt enable fails; this patch extends it to do so.
> 
> v2:
>  * Added warning on rte_eal_alarm_set failure (i40e & ice)
>  * Only set use_aq_polling if rte_eal_alarm_set succeeds (i40e & ice)
>  * Move alarm cancel in ice_dev_stop to prevent a race (ice)
>  * Remove unnecessary alarm cancel from ice_dev_start error path (ice)
>  * Added general fix for ice dev_start error path
> 
> Ciara Loftus (3):
>   net/ice: poll AdminQ if interrupt delivery unavailable
>   net/i40e: activate alarm if interrupt delivery unavailable
>   net/ice: fix adapter stopped on device start error
> 
Series-acked-by: Bruce Richardson <bruce.richardson@intel.com>

Applied to next-net-intel.

Thanks,
/Bruce
>  drivers/net/intel/i40e/i40e_ethdev.c | 11 ++++++-
>  drivers/net/intel/i40e/i40e_ethdev.h |  2 ++
>  drivers/net/intel/ice/ice_ethdev.c   | 44 +++++++++++++++++++++++++++-
>  drivers/net/intel/ice/ice_ethdev.h   |  1 +
>  4 files changed, 56 insertions(+), 2 deletions(-)
> 
> -- 
> 2.43.0
> 

^ permalink raw reply

* Re: [PATCH v5 0/4] dma/ae4dma: add AMD AE4DMA DMA PMD
From: fengchengwen @ 2026-07-10  7:58 UTC (permalink / raw)
  To: Raghavendra Ningoji, dev
  Cc: david.marchand, bruce.richardson, selwin.sebastian,
	bhagyada.modali, rjarry, thomas
In-Reply-To: <20260710073608.3034898-1-raghavendra.ningoji@amd.com>

Series-acked-by: Chengwen Feng <fengchengwen@huawei.com>

On 7/10/2026 3:36 PM, Raghavendra Ningoji wrote:
> This series adds a new dmadev poll-mode driver for the AMD AE4DMA
> hardware DMA engine, split into introduction, control path and data
> path patches, plus a small dmadev test-suite patch.


^ permalink raw reply

* Re: [PATCH v4 3/4] dma/ae4dma: add data path operations
From: fengchengwen @ 2026-07-10  7:57 UTC (permalink / raw)
  To: Raghavendra Ningoji, dev
  Cc: David Marchand, Bruce Richardson, Selwin.Sebastian,
	Bhagyada Modali, Robin Jarry, Thomas Monjalon
In-Reply-To: <20260710072250.3034265-2-raghavendra.ningoji@amd.com>

On 7/10/2026 3:22 PM, Raghavendra Ningoji wrote:
> On Mon, 7 Jul 2026 at 06:49, fengchengwen <fengchengwen@huawei.com> wrote:
>>
>>> +	if (last_idx != NULL)
>>> +		*last_idx = (uint16_t)(cmd_q->next_read - 1);
>>
>> 1\ the last_idx always non-NULL for driver, so no need for
>>    'if (last_idx != NULL)'
>> 2\ last_idx should be the last success completed index
> 
> Both fixed in v5. The NULL check is dropped, and last_idx now reports
> the ring_idx of the last *successfully* completed op.
> 
>> Consider four reqests, the hardware mark each request as following:
>>     req1   success
>>     req2   err
>>     req3   success
>>     req4   err
>> the cpl_count will be 4, and err_count will be 2
>> It will return 2 in current impl, and last_idx will be the req4's
>> But in this function, it should return 1, and last_idx should be req1's
> 
> Agreed - thanks for the clear example. rte_dma_completed() is reworked
> in v5 to stop at the first failed descriptor: it returns only the run of
> successful ops preceding the error (1 in your example), sets *has_error,
> and leaves the failed op in place with last_idx pointing at the last
> success (req1). The failed op and everything after it are then drained
> and reported by rte_dma_completed_status().
> 
> On a related note, while implementing the above I found the last_idx
> documentation for rte_dma_completed() and rte_dma_completed_status()
> to be identical:
> 
>     @param[out] last_idx
>       The last completed operation's ring_idx.
> 
> Since rte_dma_completed() only reports operations that completed
> *successfully* (it stops at the first error), its last_idx is really the
> last successfully completed operation's ring_idx, whereas for
> rte_dma_completed_status() it is the last completed operation regardless
> of status. Would a small doc clarification along these lines be welcome
> (as a separate patch)?

I think it is necessary to update.
Please also update rte_dma_completed_status, both in a separate patch

Thanks

> 
>     rte_dma_completed():
>     @param[out] last_idx
>       The last successfully completed operation's ring_idx.
> 
> I'm happy to send that as a separate dmadev doc patch if you agree.
> 
> Thanks,
> Raghavendra


^ permalink raw reply

* Re: [PATCH v4 10/10] net/mlx5: accept more unicast MAC addresses
From: David Marchand @ 2026-07-10  7:48 UTC (permalink / raw)
  To: dev
  Cc: rjarry, cfontain, Dariusz Sosnowski, Viacheslav Ovsiienko,
	Bing Zhao, Ori Kam, Suanming Mou, Matan Azrad
In-Reply-To: <20260709160247.1798575-11-david.marchand@redhat.com>

On Thu, 9 Jul 2026 at 18:04, David Marchand <david.marchand@redhat.com> wrote:
> diff --git a/drivers/common/mlx5/mlx5_devx_cmds.h b/drivers/common/mlx5/mlx5_devx_cmds.h
> index 90beb2e9e6..7fe89bc6a4 100644
> --- a/drivers/common/mlx5/mlx5_devx_cmds.h
> +++ b/drivers/common/mlx5/mlx5_devx_cmds.h
> @@ -356,6 +356,8 @@ struct mlx5_hca_attr {
>         uint8_t tx_sw_owner_v2:1;
>         uint8_t esw_sw_owner:1;
>         uint8_t esw_sw_owner_v2:1;
> +       uint32_t log_max_current_uc_list:5;
> +       uint32_t log_max_current_mc_list:5;

uint8_t is enough.


>  };
>
>  /* LAG Context. */

[...]

> diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
> index f7ba8df108..1fcc40fade 100644
> --- a/drivers/net/mlx5/mlx5.h
> +++ b/drivers/net/mlx5/mlx5.h
> @@ -217,6 +217,9 @@ struct mlx5_dev_cap {
>         } mprq; /* Capability for Multi-Packet RQ. */
>         char fw_ver[64]; /* Firmware version of this device. */
>         struct flow_hw_port_info esw_info; /* E-switch manager reg_c0. */
> +       uint16_t max_uc_mac_addrs; /* Maximum unicast MAC addresses. */
> +       uint16_t max_mc_mac_addrs; /* Maximum multicast MAC addresses. */
> +       uint16_t max_mac_addrs; /* Total maximum MAC addresses. */
>  };
>
>  #define MLX5_MPESW_PORT_INVALID (-1)

The firmware provides a log value on 5 bits, so max value for uc and
mc would be 1 << 31, and the sum would be up to 1 << 32.
In theory, ethdev caps max_mac_addrs to 1 << 32 - 1, but the firmware
only allows up to 4k uc macs and extending this limit has been said
not possible easily.
So reaching 1 << 32 as a total is not going to happen soon and
uint32_t is enough for those 3 fields.

In next revision.


-- 
David Marchand


^ permalink raw reply

* [PATCH v5 4/4] test/dma: skip instance suite on low burst capacity
From: Raghavendra Ningoji @ 2026-07-10  7:36 UTC (permalink / raw)
  To: dev
  Cc: fengchengwen, david.marchand, bruce.richardson, selwin.sebastian,
	bhagyada.modali, rjarry, thomas, Raghavendra Ningoji
In-Reply-To: <20260710073608.3034898-1-raghavendra.ningoji@amd.com>

test_dmadev_setup() aborts the entire "DMA dev instance" test suite
with a hard failure when rte_dma_burst_capacity() reports fewer than
32 descriptors. Some DMA engines expose a small hardware ring; for
example a 32-entry ring that reserves one slot to distinguish a full
ring from an empty one leaves only 31 descriptors usable. Such a
device genuinely cannot run the instance tests, which enqueue 32-deep
bursts, but it should be reported as skipped rather than failed.

Return TEST_SKIPPED instead, matching test_dmadev_burst_setup() which
already skips (rather than fails) when the capacity is below 64.

Signed-off-by: Raghavendra Ningoji <raghavendra.ningoji@amd.com>
---
 app/test/test_dmadev.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/app/test/test_dmadev.c b/app/test/test_dmadev.c
index 5488a1af33..1cfa8c1e10 100644
--- a/app/test/test_dmadev.c
+++ b/app/test/test_dmadev.c
@@ -1397,8 +1397,12 @@ test_dmadev_setup(void)
 	if (rte_dma_stats_get(dev_id, vchan, &stats) != 0)
 		ERR_RETURN("Error with rte_dma_stats_get()\n");
 
-	if (rte_dma_burst_capacity(dev_id, vchan) < 32)
-		ERR_RETURN("Error: Device does not have sufficient burst capacity to run tests");
+	if (rte_dma_burst_capacity(dev_id, vchan) < 32) {
+		RTE_LOG(ERR, USER1,
+			"DMA Dev %u: insufficient burst capacity (32 required), skipping tests\n",
+			dev_id);
+		return TEST_SKIPPED;
+	}
 
 	if (stats.completed != 0 || stats.submitted != 0 || stats.errors != 0)
 		ERR_RETURN("Error device stats are not all zero: completed = %"PRIu64", "
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 3/4] dma/ae4dma: add data path operations
From: Raghavendra Ningoji @ 2026-07-10  7:36 UTC (permalink / raw)
  To: dev
  Cc: fengchengwen, david.marchand, bruce.richardson, selwin.sebastian,
	bhagyada.modali, rjarry, thomas, Raghavendra Ningoji
In-Reply-To: <20260710073608.3034898-1-raghavendra.ningoji@amd.com>

Implement the dmadev fast path (copy, submit, completed,
completed_status and burst_capacity) for the AMD AE4DMA PMD and wire
the entry points through fp_obj in ae4dma_dmadev_create().

The framework-visible ring index is a free-running 16-bit value; the
HW ring slot is derived as (hw_base + idx) & (nb_desc - 1), which is
why the vchan ring depth is kept a power of two. completed() and
completed_status() always report the last completed ring_idx via
last_idx, even when no new op is reaped, as required by the dmadev
API. Only memory-to-memory copy is advertised, so fp_obj->fill is
left zero-initialised.

AE4DMA-specific completion handling: the engine reports completion by
advancing the per-queue read_idx register rather than reliably writing
a status word back, so the driver uses read_idx as the source of truth
and only reads the descriptor err_code byte to flag failures.

Signed-off-by: Raghavendra Ningoji <raghavendra.ningoji@amd.com>
---
 doc/guides/dmadevs/ae4dma.rst      |  24 +++
 drivers/dma/ae4dma/ae4dma_dmadev.c | 249 +++++++++++++++++++++++++++++
 2 files changed, 273 insertions(+)

diff --git a/doc/guides/dmadevs/ae4dma.rst b/doc/guides/dmadevs/ae4dma.rst
index eeed55c283..10446ca2e8 100644
--- a/doc/guides/dmadevs/ae4dma.rst
+++ b/doc/guides/dmadevs/ae4dma.rst
@@ -51,3 +51,27 @@ On probe the PMD performs the following steps for each PCI function:
   IOVA-contiguous memory, programs the queue base address and ring
   depth into the per-queue registers, and enables the queue.
 * Interrupts are masked; completion is polled by the application.
+
+Usage
+-----
+
+Once a dmadev has been started, copies are submitted with
+``rte_dma_copy()`` and completions are reaped with ``rte_dma_completed()``
+or ``rte_dma_completed_status()``. See the
+:ref:`Enqueue / Dequeue API <dmadev_enqueue_dequeue>` section of the
+dmadev library documentation for details.
+
+Limitations
+-----------
+
+* Only memory-to-memory copies are supported. Fill, scatter-gather and
+  any other operation types are not advertised in
+  ``rte_dma_info::dev_capa``.
+* The maximum number of descriptors per virtual channel is fixed by
+  hardware at 32, and the engine reserves one slot to distinguish a full
+  ring from an empty one, so at most 31 operations can be outstanding
+  (``rte_dma_burst_capacity()`` returns at most 31). The PMD rounds the
+  requested ring size up to a power of two and clamps it to 32.
+* Only a single virtual channel per dmadev is supported; use the 16
+  per-PCI-function dmadevs to obtain channel-level parallelism.
+* Interrupt-driven completion is not supported.
diff --git a/drivers/dma/ae4dma/ae4dma_dmadev.c b/drivers/dma/ae4dma/ae4dma_dmadev.c
index 9910e115b2..70c7c21ddf 100644
--- a/drivers/dma/ae4dma/ae4dma_dmadev.c
+++ b/drivers/dma/ae4dma/ae4dma_dmadev.c
@@ -162,6 +162,76 @@ ae4dma_dev_close(struct rte_dma_dev *dev)
 	return 0;
 }
 
+/* Ring the doorbell: publish all enqueued descriptors to the hardware. */
+static inline void
+__submit(struct ae4dma_dmadev *ae4dma)
+{
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t nb = cmd_q->qcfg.nb_desc;
+
+	if (nb == 0)
+		return;
+
+	/* The HW producer index is the ring slot of the next free descriptor. */
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->write_idx,
+			(cmd_q->hw_base + cmd_q->next_write) & (nb - 1));
+	cmd_q->stats.submitted += (uint16_t)(cmd_q->next_write - cmd_q->last_write);
+	cmd_q->last_write = cmd_q->next_write;
+}
+
+static int
+ae4dma_submit(void *dev_private, uint16_t vchan __rte_unused)
+{
+	struct ae4dma_dmadev *ae4dma = dev_private;
+
+	__submit(ae4dma);
+	return 0;
+}
+
+/* Write a copy descriptor; returns the free-running ring_idx assigned to it. */
+static inline int
+__write_desc_copy(void *dev_private, rte_iova_t src, rte_iova_t dst,
+		uint32_t len, uint64_t flags)
+{
+	struct ae4dma_dmadev *ae4dma = dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t ring_idx = cmd_q->next_write;
+	uint16_t nb = cmd_q->qcfg.nb_desc;
+	struct ae4dma_desc *dma_desc;
+	uint16_t in_flight;
+
+	if (nb == 0)
+		return -EINVAL;
+
+	/* Reserve one slot to distinguish full from empty on the ring. */
+	in_flight = (uint16_t)(cmd_q->next_write - cmd_q->next_read);
+	if (in_flight >= nb - 1)
+		return -ENOSPC;
+
+	dma_desc = &cmd_q->qbase_desc[(cmd_q->hw_base + ring_idx) & (nb - 1)];
+	memset(dma_desc, 0, sizeof(*dma_desc));
+	dma_desc->length = len;
+	dma_desc->src_hi = upper_32_bits(src);
+	dma_desc->src_lo = lower_32_bits(src);
+	dma_desc->dst_hi = upper_32_bits(dst);
+	dma_desc->dst_lo = lower_32_bits(dst);
+
+	cmd_q->next_write++;
+	if (flags & RTE_DMA_OP_FLAG_SUBMIT)
+		__submit(ae4dma);
+
+	/* dmadev ring_idx is a free-running 16-bit value, not a ring slot. */
+	return ring_idx;
+}
+
+/* Enqueue a copy operation onto the ae4dma device. */
+static int
+ae4dma_enqueue_copy(void *dev_private, uint16_t vchan __rte_unused,
+		rte_iova_t src, rte_iova_t dst, uint32_t length, uint64_t flags)
+{
+	return __write_desc_copy(dev_private, src, dst, length, flags);
+}
+
 static int
 ae4dma_dev_dump(const struct rte_dma_dev *dev, FILE *f)
 {
@@ -192,6 +262,178 @@ ae4dma_dev_dump(const struct rte_dma_dev *dev, FILE *f)
 	return 0;
 }
 
+/* Translate an AE4DMA descriptor error code to the dmadev status code. */
+static inline enum rte_dma_status_code
+__translate_status_ae4dma_to_dma(enum ae4dma_dma_err status)
+{
+	switch (status) {
+	case AE4DMA_DMA_ERR_NO_ERR:
+		return RTE_DMA_STATUS_SUCCESSFUL;
+	case AE4DMA_DMA_ERR_INV_LEN:
+		return RTE_DMA_STATUS_INVALID_LENGTH;
+	case AE4DMA_DMA_ERR_INV_SRC:
+		return RTE_DMA_STATUS_INVALID_SRC_ADDR;
+	case AE4DMA_DMA_ERR_INV_DST:
+		return RTE_DMA_STATUS_INVALID_DST_ADDR;
+	default:
+		return RTE_DMA_STATUS_ERROR_UNKNOWN;
+	}
+}
+
+/* True if the descriptor at ring slot has been flagged as failed by HW. */
+static inline bool
+ae4dma_desc_failed(volatile struct ae4dma_desc *hw_desc)
+{
+	/*
+	 * read_idx advancing is the definitive completion signal. The
+	 * per-descriptor status byte is informational and may not yet be
+	 * written when we observe it, so a slot is only a failure if the
+	 * HW flagged DESC_ERROR or set a non-zero err_code.
+	 */
+	return hw_desc->dw1.status == AE4DMA_DMA_DESC_ERROR ||
+			hw_desc->dw1.err_code != AE4DMA_DMA_ERR_NO_ERR;
+}
+
+/*
+ * Number of descriptors the HW has completed since our last reap, without
+ * consuming them (non-blocking, no state change).
+ *
+ * The AE4DMA engine signals completion by advancing the per-queue read_idx
+ * register; it does not (reliably) write a status word back into the
+ * descriptor. We therefore use the HW read_idx register as the source of
+ * truth for how many ops are done.
+ *
+ * next_read/next_write are free-running 16-bit indices; the HW ring slot is
+ * ((hw_base + idx) & mask) and the number of in-flight ops is
+ * (next_write - next_read), both relying on nb_desc being a power of two.
+ */
+static inline uint16_t
+ae4dma_pending(struct ae4dma_cmd_queue *cmd_q)
+{
+	uint16_t nb = cmd_q->qcfg.nb_desc;
+	uint16_t hw_read_pos, tail_pos;
+	uint16_t newly_done, in_flight;
+	uint16_t mask;
+
+	if (nb == 0)
+		return 0;
+	mask = nb - 1;
+
+	in_flight = (uint16_t)(cmd_q->next_write - cmd_q->next_read);
+	if (in_flight == 0)
+		return 0;
+
+	/* Descriptors HW has consumed since our last visit: [tail, read). */
+	hw_read_pos = (uint16_t)(AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx) & mask);
+	tail_pos = (uint16_t)((cmd_q->hw_base + cmd_q->next_read) & mask);
+	newly_done = (uint16_t)((hw_read_pos - tail_pos) & mask);
+
+	return RTE_MIN(newly_done, in_flight);
+}
+
+/*
+ * Report ops that completed successfully, stopping at the first failure.
+ *
+ * Per the dmadev contract, this reaps only the run of successful ops at the
+ * head of the completed region and stops as soon as it meets a failed one.
+ * The failed op is left in place (not consumed) and *has_error is set so the
+ * application switches to rte_dma_completed_status() to retrieve its status.
+ * last_idx is the ring_idx of the last successfully completed op.
+ */
+static uint16_t
+ae4dma_completed(void *dev_private, uint16_t vchan __rte_unused,
+		const uint16_t max_ops, uint16_t *last_idx, bool *has_error)
+{
+	struct ae4dma_dmadev *ae4dma = dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t mask = cmd_q->qcfg.nb_desc - 1;
+	uint16_t ready, count;
+
+	*has_error = false;
+
+	ready = ae4dma_pending(cmd_q);
+	if (ready > max_ops)
+		ready = max_ops;
+
+	for (count = 0; count < ready; count++) {
+		uint16_t slot = (cmd_q->hw_base + cmd_q->next_read + count) & mask;
+
+		if (ae4dma_desc_failed(&cmd_q->qbase_desc[slot])) {
+			*has_error = true;
+			break;
+		}
+	}
+
+	cmd_q->next_read += count;
+	cmd_q->stats.completed += count;
+	*last_idx = (uint16_t)(cmd_q->next_read - 1);
+
+	return count;
+}
+
+/*
+ * Report per-op status for all completed ops, including failures.
+ *
+ * Unlike ae4dma_completed(), this consumes failed ops too so the application
+ * can drain past an error. last_idx is the ring_idx of the last op reported.
+ */
+static uint16_t
+ae4dma_completed_status(void *dev_private, uint16_t vchan __rte_unused,
+		uint16_t max_ops, uint16_t *last_idx,
+		enum rte_dma_status_code *status)
+{
+	struct ae4dma_dmadev *ae4dma = dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t mask = cmd_q->qcfg.nb_desc - 1;
+	uint16_t ready, fails = 0;
+	uint16_t i;
+
+	ready = ae4dma_pending(cmd_q);
+	if (ready > max_ops)
+		ready = max_ops;
+
+	for (i = 0; i < ready; i++) {
+		uint16_t slot = (cmd_q->hw_base + cmd_q->next_read + i) & mask;
+		volatile struct ae4dma_desc *hw_desc = &cmd_q->qbase_desc[slot];
+
+		if (ae4dma_desc_failed(hw_desc)) {
+			status[i] = __translate_status_ae4dma_to_dma(
+					(enum ae4dma_dma_err)hw_desc->dw1.err_code);
+			AE4DMA_PMD_WARN("Desc failed: status=%u err=%u",
+					hw_desc->dw1.status, hw_desc->dw1.err_code);
+			fails++;
+		} else {
+			status[i] = RTE_DMA_STATUS_SUCCESSFUL;
+		}
+	}
+
+	cmd_q->next_read += ready;
+	cmd_q->stats.completed += ready;
+	cmd_q->stats.errors += fails;
+	*last_idx = (uint16_t)(cmd_q->next_read - 1);
+
+	return ready;
+}
+
+/* Get the remaining capacity of the ring. */
+static uint16_t
+ae4dma_burst_capacity(const void *dev_private, uint16_t vchan __rte_unused)
+{
+	const struct ae4dma_dmadev *ae4dma = dev_private;
+	const struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t nb = cmd_q->qcfg.nb_desc;
+	uint16_t in_flight;
+
+	if (nb == 0)
+		return 0;
+
+	/* One slot reserved to distinguish full from empty. */
+	in_flight = (uint16_t)(cmd_q->next_write - cmd_q->next_read);
+	if (in_flight >= nb - 1)
+		return 0;
+	return (uint16_t)(nb - 1 - in_flight);
+}
+
 static int
 ae4dma_stats_get(const struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
 		struct rte_dma_stats *rte_stats, uint32_t size __rte_unused)
@@ -339,6 +581,13 @@ ae4dma_dmadev_create(const char *name, struct rte_pci_device *dev, uint8_t qn)
 	dmadev->fp_obj->dev_private = dmadev->data->dev_private;
 	dmadev->dev_ops = &ae4dma_dmadev_ops;
 
+	dmadev->fp_obj->burst_capacity = ae4dma_burst_capacity;
+	dmadev->fp_obj->completed = ae4dma_completed;
+	dmadev->fp_obj->completed_status = ae4dma_completed_status;
+	dmadev->fp_obj->copy = ae4dma_enqueue_copy;
+	dmadev->fp_obj->submit = ae4dma_submit;
+	/* fill capability not advertised: leave fp_obj->fill as zero-initialised. */
+
 	ae4dma = dmadev->data->dev_private;
 
 	if (ae4dma_add_queue(ae4dma, dev, qn, name) != 0)
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 2/4] dma/ae4dma: add control path operations
From: Raghavendra Ningoji @ 2026-07-10  7:36 UTC (permalink / raw)
  To: dev
  Cc: fengchengwen, david.marchand, bruce.richardson, selwin.sebastian,
	bhagyada.modali, rjarry, thomas, Raghavendra Ningoji
In-Reply-To: <20260710073608.3034898-1-raghavendra.ningoji@amd.com>

Implement the dmadev control path for the AMD AE4DMA PMD.

This commit adds:
 - dev_configure / vchan_setup: accept a single virtual channel per
   dmadev and a ring size of 2 to 32 descriptors (rounded up to a
   power of two so the ring slot can be derived by masking). vchan_setup
   also resets the free-running ring indices.
 - dev_start / dev_stop / dev_close: enable the hardware queue and reset
   the ring indices on start (read_idx is HW-owned, so index 0 is
   anchored to the current HW consumer slot), disable the queue on stop,
   and release the descriptor ring memzone on close.
 - dev_info_get: advertise RTE_DMA_CAPA_MEM_TO_MEM and
   RTE_DMA_CAPA_OPS_COPY, the single virtual channel and the 2 to 32
   descriptor range.
 - dev_dump: print the queue identifiers, ring layout and software
   completion counters.
 - stats_get / stats_reset: expose submitted / completed / errors
   counters maintained by the driver.
 - vchan_status: report IDLE / ACTIVE based on hardware read_idx vs
   write_idx, and HALTED_ERROR when the queue is not enabled.

The dmadev framework is wired through dev_ops in ae4dma_dmadev_create().

Signed-off-by: Raghavendra Ningoji <raghavendra.ningoji@amd.com>
---
 drivers/dma/ae4dma/ae4dma_dmadev.c | 228 +++++++++++++++++++++++++++++
 1 file changed, 228 insertions(+)

diff --git a/drivers/dma/ae4dma/ae4dma_dmadev.c b/drivers/dma/ae4dma/ae4dma_dmadev.c
index bf0d3bb2bd..9910e115b2 100644
--- a/drivers/dma/ae4dma/ae4dma_dmadev.c
+++ b/drivers/dma/ae4dma/ae4dma_dmadev.c
@@ -35,6 +35,220 @@ ae4dma_queue_dma_zone_reserve(const char *queue_name,
 			socket_id, RTE_MEMZONE_IOVA_CONTIG, queue_size);
 }
 
+static int
+ae4dma_dev_configure(struct rte_dma_dev *dev __rte_unused,
+		const struct rte_dma_conf *dev_conf,
+		uint32_t conf_sz)
+{
+	if (conf_sz < sizeof(struct rte_dma_conf))
+		return -EINVAL;
+
+	if (dev_conf->nb_vchans != 1)
+		return -EINVAL;
+
+	return 0;
+}
+
+/* Setup a virtual channel for AE4DMA, only 1 vchan is supported per dmadev. */
+static int
+ae4dma_vchan_setup(struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
+		const struct rte_dma_vchan_conf *qconf, uint32_t qconf_sz)
+{
+	struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t max_desc = qconf->nb_desc;
+
+	if (qconf_sz < sizeof(struct rte_dma_vchan_conf))
+		return -EINVAL;
+
+	/*
+	 * The framework already clamps nb_desc to the advertised [min_desc,
+	 * max_desc] range. The HW ring slot is computed by masking a
+	 * free-running 16-bit index, so the depth must be a power of two:
+	 * round up here when the application asks for a non-power-of-two size.
+	 */
+	if (!rte_is_power_of_2(max_desc))
+		max_desc = rte_align32pow2(max_desc);
+
+	cmd_q->qcfg = *qconf;
+	cmd_q->qcfg.nb_desc = max_desc;
+
+	/* Reset ring indices and stats when (re)configuring the vchan.
+	 * hw_base is (re)sampled from the HW consumer index in dev_start.
+	 */
+	cmd_q->next_write = 0;
+	cmd_q->next_read = 0;
+	cmd_q->last_write = 0;
+	cmd_q->hw_base = 0;
+	memset(&cmd_q->stats, 0, sizeof(cmd_q->stats));
+	return 0;
+}
+
+static int
+ae4dma_dev_start(struct rte_dma_dev *dev)
+{
+	struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint16_t nb = cmd_q->qcfg.nb_desc;
+	uint16_t hw_pos;
+
+	if (nb == 0)
+		return -EBUSY;
+
+	/* Program ring depth and (re)enable the HW queue. On HW that resets
+	 * the queue indices on enable, this must happen before we sample
+	 * read_idx below.
+	 */
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->max_idx, nb);
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
+			AE4DMA_CMD_QUEUE_ENABLE);
+
+	/*
+	 * The dmadev ring index is a free-running 16-bit counter that restarts
+	 * from 0 on every (re)start. read_idx is HW-owned (read-only), so we
+	 * cannot force it to 0; instead anchor index 0 to wherever the HW
+	 * consumer currently sits (hw_base) and derive the ring slot as
+	 * (hw_base + idx) & (nb - 1). write_idx is set equal to read_idx so the
+	 * queue starts empty without touching the read-only read_idx register.
+	 */
+	hw_pos = (uint16_t)(AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx) & (nb - 1));
+	cmd_q->hw_base = hw_pos;
+	cmd_q->next_write = 0;
+	cmd_q->next_read = 0;
+	cmd_q->last_write = 0;
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->write_idx, hw_pos);
+	return 0;
+}
+
+static int
+ae4dma_dev_stop(struct rte_dma_dev *dev)
+{
+	struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+
+	if (cmd_q->hwq_regs != NULL)
+		AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
+				AE4DMA_CMD_QUEUE_DISABLE);
+	return 0;
+}
+
+static int
+ae4dma_dev_info_get(const struct rte_dma_dev *dev __rte_unused,
+		struct rte_dma_info *info, uint32_t size __rte_unused)
+{
+	info->dev_capa = RTE_DMA_CAPA_MEM_TO_MEM | RTE_DMA_CAPA_OPS_COPY;
+	info->max_vchans = 1;
+	info->min_desc = 2;
+	info->max_desc = AE4DMA_DESCRIPTORS_PER_CMDQ;
+	info->nb_vchans = 1;
+	return 0;
+}
+
+static int
+ae4dma_dev_close(struct rte_dma_dev *dev)
+{
+	struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+
+	if (cmd_q->hwq_regs != NULL)
+		AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
+				AE4DMA_CMD_QUEUE_DISABLE);
+
+	rte_memzone_free(cmd_q->mz);
+	cmd_q->mz = NULL;
+	cmd_q->qbase_desc = NULL;
+	cmd_q->qbase_addr = NULL;
+	cmd_q->qbase_phys_addr = 0;
+	return 0;
+}
+
+static int
+ae4dma_dev_dump(const struct rte_dma_dev *dev, FILE *f)
+{
+	struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	void *mmio = ae4dma->io_regs;
+
+	fprintf(f, "cmd_q->id              = %" PRIx64 "\n", cmd_q->id);
+	fprintf(f, "cmd_q->qidx            = %" PRIx64 "\n", cmd_q->qidx);
+	fprintf(f, "cmd_q->qsize           = %" PRIx64 "\n", cmd_q->qsize);
+	fprintf(f, "mmio_base_addr	= %p\n", mmio);
+	fprintf(f, "queues per ae4dma engine     = %d\n", AE4DMA_READ_REG_OFFSET(
+				mmio, AE4DMA_COMMON_CONFIG_OFFSET));
+	fprintf(f, "== Private Data ==\n");
+	fprintf(f, "  Config: { ring_size: %u }\n", cmd_q->qcfg.nb_desc);
+	fprintf(f, "  Ring virt: %p\tphys: %#" PRIx64 "\n",
+			(void *)cmd_q->qbase_desc,
+			(uint64_t)cmd_q->qbase_phys_addr);
+	fprintf(f, "  Next write: %u\n", cmd_q->next_write);
+	fprintf(f, "  Next read: %u\n", cmd_q->next_read);
+	fprintf(f, "  current queue depth: %u\n",
+			(uint16_t)(cmd_q->next_write - cmd_q->next_read));
+	fprintf(f, "  }\n");
+	fprintf(f, "  Key Stats { submitted: %" PRIu64 ", comp: %" PRIu64 ", failed: %" PRIu64 " }\n",
+		cmd_q->stats.submitted,
+		cmd_q->stats.completed,
+		cmd_q->stats.errors);
+	return 0;
+}
+
+static int
+ae4dma_stats_get(const struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
+		struct rte_dma_stats *rte_stats, uint32_t size __rte_unused)
+{
+	const struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	const struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+
+	*rte_stats = cmd_q->stats;
+	return 0;
+}
+
+static int
+ae4dma_stats_reset(struct rte_dma_dev *dev, uint16_t vchan __rte_unused)
+{
+	struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+
+	memset(&cmd_q->stats, 0, sizeof(cmd_q->stats));
+	return 0;
+}
+
+/*
+ * Report channel state to the dmadev framework.
+ *
+ *   RTE_DMA_VCHAN_HALTED_ERROR - HW queue is disabled (never started, or
+ *                                stopped via dev_stop()).
+ *   RTE_DMA_VCHAN_IDLE         - HW has caught up: read_idx == write_idx,
+ *                                no descriptors in flight.
+ *   RTE_DMA_VCHAN_ACTIVE       - HW still has descriptors to process.
+ */
+static int
+ae4dma_vchan_status(const struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
+		enum rte_dma_vchan_status *status)
+{
+	const struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
+	const struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
+	uint32_t ctrl, hw_read, hw_write;
+
+	if (cmd_q->hwq_regs == NULL) {
+		*status = RTE_DMA_VCHAN_HALTED_ERROR;
+		return 0;
+	}
+
+	ctrl = AE4DMA_READ_REG(&cmd_q->hwq_regs->control_reg.control_raw);
+	if ((ctrl & AE4DMA_CMD_QUEUE_ENABLE) == 0) {
+		*status = RTE_DMA_VCHAN_HALTED_ERROR;
+		return 0;
+	}
+
+	hw_read  = AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx);
+	hw_write = AE4DMA_READ_REG(&cmd_q->hwq_regs->write_idx);
+
+	*status = (hw_read == hw_write) ? RTE_DMA_VCHAN_IDLE
+					: RTE_DMA_VCHAN_ACTIVE;
+	return 0;
+}
+
 static int
 ae4dma_add_queue(struct ae4dma_dmadev *dev, struct rte_pci_device *pci,
 		uint8_t qn, const char *pci_name)
@@ -96,6 +310,19 @@ ae4dma_channel_dev_name(char *out, size_t outlen, const char *pci_name,
 static int
 ae4dma_dmadev_create(const char *name, struct rte_pci_device *dev, uint8_t qn)
 {
+	static const struct rte_dma_dev_ops ae4dma_dmadev_ops = {
+		.dev_close = ae4dma_dev_close,
+		.dev_configure = ae4dma_dev_configure,
+		.dev_dump = ae4dma_dev_dump,
+		.dev_info_get = ae4dma_dev_info_get,
+		.dev_start = ae4dma_dev_start,
+		.dev_stop = ae4dma_dev_stop,
+		.stats_get = ae4dma_stats_get,
+		.stats_reset = ae4dma_stats_reset,
+		.vchan_status = ae4dma_vchan_status,
+		.vchan_setup = ae4dma_vchan_setup,
+	};
+
 	char hwq_dev_name[RTE_DEV_NAME_MAX_LEN] = {0};
 	struct ae4dma_dmadev *ae4dma;
 	struct rte_dma_dev *dmadev;
@@ -110,6 +337,7 @@ ae4dma_dmadev_create(const char *name, struct rte_pci_device *dev, uint8_t qn)
 	}
 	dmadev->device = &dev->device;
 	dmadev->fp_obj->dev_private = dmadev->data->dev_private;
+	dmadev->dev_ops = &ae4dma_dmadev_ops;
 
 	ae4dma = dmadev->data->dev_private;
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 1/4] dma/ae4dma: introduce AMD AE4DMA DMA PMD
From: Raghavendra Ningoji @ 2026-07-10  7:36 UTC (permalink / raw)
  To: dev
  Cc: fengchengwen, david.marchand, bruce.richardson, selwin.sebastian,
	bhagyada.modali, rjarry, thomas, Raghavendra Ningoji
In-Reply-To: <20260710073608.3034898-1-raghavendra.ningoji@amd.com>

Add the skeleton of a new dmadev poll-mode driver for the AMD AE4DMA
hardware DMA engine, providing only PCI probe/remove and per-queue
hardware initialisation. An AE4DMA engine exposes 16 hardware command
queues, each with a 32-entry descriptor ring; the PMD maps each
hardware channel to its own dmadev with a single virtual channel,
so a PCI function appears as 16 dmadevs named "<pci-bdf>-ch0" ..
"<pci-bdf>-ch15".

This patch only registers the PCI driver, allocates the dmadev
objects, reserves the per-queue descriptor rings and programs the
hardware queue base addresses. Control and data path operations are
added in subsequent patches.

Signed-off-by: Raghavendra Ningoji <raghavendra.ningoji@amd.com>
---
 .mailmap                               |   1 +
 MAINTAINERS                            |   5 +
 doc/guides/dmadevs/ae4dma.rst          |  53 +++++++
 doc/guides/dmadevs/index.rst           |   1 +
 doc/guides/rel_notes/release_26_07.rst |   7 +
 drivers/dma/ae4dma/ae4dma_dmadev.c     | 200 +++++++++++++++++++++++++
 drivers/dma/ae4dma/ae4dma_hw_defs.h    | 149 ++++++++++++++++++
 drivers/dma/ae4dma/ae4dma_internal.h   | 113 ++++++++++++++
 drivers/dma/ae4dma/meson.build         |  13 ++
 drivers/dma/meson.build                |   1 +
 usertools/dpdk-devbind.py              |   5 +-
 11 files changed, 547 insertions(+), 1 deletion(-)
 create mode 100644 doc/guides/dmadevs/ae4dma.rst
 create mode 100644 drivers/dma/ae4dma/ae4dma_dmadev.c
 create mode 100644 drivers/dma/ae4dma/ae4dma_hw_defs.h
 create mode 100644 drivers/dma/ae4dma/ae4dma_internal.h
 create mode 100644 drivers/dma/ae4dma/meson.build

diff --git a/.mailmap b/.mailmap
index 89ba6ffccc..71a62564fa 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1329,6 +1329,7 @@ Radu Bulie <radu-andrei.bulie@nxp.com>
 Radu Nicolau <radu.nicolau@intel.com>
 Rafael Ávila de Espíndola <espindola@scylladb.com>
 Rafal Kozik <rk@semihalf.com>
+Raghavendra Ningoji <raghavendra.ningoji@amd.com>
 Ragothaman Jayaraman <rjayaraman@caviumnetworks.com>
 Rahul Bhansali <rbhansali@marvell.com>
 Rahul Gupta <rahul.gupta@broadcom.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index 9143d028bc..2e27af49f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1361,6 +1361,11 @@ F: doc/guides/compressdevs/features/zsda.ini
 DMAdev Drivers
 --------------
 
+AMD AE4DMA
+M: Bhagyada Modali <bhagyada.modali@amd.com>
+F: drivers/dma/ae4dma/
+F: doc/guides/dmadevs/ae4dma.rst
+
 Intel IDXD - EXPERIMENTAL
 M: Bruce Richardson <bruce.richardson@intel.com>
 M: Kevin Laatz <kevin.laatz@intel.com>
diff --git a/doc/guides/dmadevs/ae4dma.rst b/doc/guides/dmadevs/ae4dma.rst
new file mode 100644
index 0000000000..eeed55c283
--- /dev/null
+++ b/doc/guides/dmadevs/ae4dma.rst
@@ -0,0 +1,53 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Advanced Micro Devices, Inc.
+
+.. include:: <isonum.txt>
+
+AMD AE4DMA DMA Device Driver
+============================
+
+The ``ae4dma`` dmadev driver is a poll-mode driver (PMD) for the
+AMD AE4DMA hardware DMA engine. The engine exposes 16 independent
+hardware command queues, each with a ring of 32 descriptors. The PMD
+maps each hardware command queue to a separate DPDK dmadev with a
+single virtual channel, so a single PCI function appears as 16 dmadevs
+named ``<pci-bdf>-ch0`` through ``<pci-bdf>-ch15``.
+
+The driver supports memory-to-memory copy operations only.
+
+Hardware Requirements
+---------------------
+
+The ``dpdk-devbind.py`` script can be used to list AE4DMA devices on
+the system::
+
+   dpdk-devbind.py --status-dev dma
+
+AE4DMA devices appear with vendor ID ``0x1022`` and device ID
+``0x149b``.
+
+Compilation
+-----------
+
+The driver is built as part of the standard DPDK build on x86 platforms
+using ``meson`` and ``ninja``; no extra configuration is required.
+
+Device Setup
+------------
+
+The AE4DMA device must be bound to a DPDK-compatible kernel module such
+as ``vfio-pci`` before it can be used::
+
+   dpdk-devbind.py -b vfio-pci <pci-bdf>
+
+Initialization
+~~~~~~~~~~~~~~
+
+On probe the PMD performs the following steps for each PCI function:
+
+* Reads BAR0 and programs the common configuration register with the
+  number of hardware queues to enable (16).
+* For each hardware queue it allocates a 32-entry descriptor ring in
+  IOVA-contiguous memory, programs the queue base address and ring
+  depth into the per-queue registers, and enables the queue.
+* Interrupts are masked; completion is polled by the application.
diff --git a/doc/guides/dmadevs/index.rst b/doc/guides/dmadevs/index.rst
index 56beb1733f..97399590f6 100644
--- a/doc/guides/dmadevs/index.rst
+++ b/doc/guides/dmadevs/index.rst
@@ -11,6 +11,7 @@ an application through DMA API.
    :maxdepth: 1
    :numbered:
 
+   ae4dma
    cnxk
    dpaa
    dpaa2
diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index f012d47a4b..9a78a7ef62 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -63,6 +63,13 @@ New Features
     ``rte_eal_init`` and the application is responsible for probing each device,
   * ``--auto-probing`` enables the initial bus probing, which is the current default behavior.
 
+* **Added AMD AE4DMA DMA PMD.**
+
+  Added a new ``dma/ae4dma`` driver for the AMD AE4DMA hardware DMA engine.
+  Each PCI function exposes 16 hardware command queues; the PMD registers one
+  dmadev per channel with a single virtual channel and supports
+  memory-to-memory copy operations.
+
 
 Removed Items
 -------------
diff --git a/drivers/dma/ae4dma/ae4dma_dmadev.c b/drivers/dma/ae4dma/ae4dma_dmadev.c
new file mode 100644
index 0000000000..bf0d3bb2bd
--- /dev/null
+++ b/drivers/dma/ae4dma/ae4dma_dmadev.c
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <rte_bus_pci.h>
+#include <bus_pci_driver.h>
+#include <rte_dmadev_pmd.h>
+#include <rte_malloc.h>
+
+#include "ae4dma_internal.h"
+
+/*
+ * One dmadev per AE4DMA hardware channel; each dmadev has exactly one
+ * virtual channel. The HW's per-queue register block must be densely
+ * packed right after the engine-common config register at BAR0+0; the
+ * build-time check below catches an accidental layout change.
+ */
+static_assert(sizeof(struct ae4dma_hwq_regs) == 32,
+		"ae4dma_hwq_regs stride changed; per-queue offset math will break");
+
+RTE_LOG_REGISTER_DEFAULT(ae4dma_pmd_logtype, INFO);
+
+#define AE4DMA_PMD_NAME dmadev_ae4dma
+
+static const struct rte_memzone *
+ae4dma_queue_dma_zone_reserve(const char *queue_name,
+		uint32_t queue_size, int socket_id)
+{
+	return rte_memzone_reserve_aligned(queue_name, queue_size,
+			socket_id, RTE_MEMZONE_IOVA_CONTIG, queue_size);
+}
+
+static int
+ae4dma_add_queue(struct ae4dma_dmadev *dev, struct rte_pci_device *pci,
+		uint8_t qn, const char *pci_name)
+{
+	const struct rte_memzone *q_mz;
+	struct ae4dma_cmd_queue *cmd_q;
+	uint32_t dma_addr_lo, dma_addr_hi;
+
+	dev->io_regs = pci->mem_resource[AE4DMA_PCIE_BAR].addr;
+
+	cmd_q = &dev->cmd_q;
+	cmd_q->id = qn;
+	cmd_q->qidx = 0;
+	cmd_q->qsize = AE4DMA_QUEUE_SIZE(AE4DMA_QUEUE_DESC_SIZE);
+	cmd_q->hwq_regs = (volatile struct ae4dma_hwq_regs *)dev->io_regs + (qn + 1);
+
+	/*
+	 * Memzone name must be globally unique. Embed PCI BDF so multiple
+	 * PCI functions probed concurrently don't collide.
+	 */
+	snprintf(cmd_q->memz_name, sizeof(cmd_q->memz_name),
+			"ae4dma_%s_q%u", pci_name, (unsigned int)qn);
+
+	q_mz = ae4dma_queue_dma_zone_reserve(cmd_q->memz_name,
+			cmd_q->qsize, rte_socket_id());
+	if (q_mz == NULL) {
+		AE4DMA_PMD_ERR("memzone reserve failed for %s", cmd_q->memz_name);
+		return -ENOMEM;
+	}
+
+	cmd_q->mz = q_mz;
+	cmd_q->qbase_addr = q_mz->addr;
+	cmd_q->qbase_desc = q_mz->addr;
+	cmd_q->qbase_phys_addr = q_mz->iova;
+
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->max_idx, AE4DMA_DESCRIPTORS_PER_CMDQ);
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
+			AE4DMA_CMD_QUEUE_ENABLE);
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->intr_status_reg.intr_status_raw,
+			AE4DMA_DISABLE_INTR);
+	cmd_q->next_write = AE4DMA_READ_REG(&cmd_q->hwq_regs->write_idx);
+	cmd_q->next_read = AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx);
+
+	dma_addr_lo = lower_32_bits(cmd_q->qbase_phys_addr);
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->qbase_lo, dma_addr_lo);
+	dma_addr_hi = upper_32_bits(cmd_q->qbase_phys_addr);
+	AE4DMA_WRITE_REG(&cmd_q->hwq_regs->qbase_hi, dma_addr_hi);
+
+	return 0;
+}
+
+static void
+ae4dma_channel_dev_name(char *out, size_t outlen, const char *pci_name,
+		unsigned int ch)
+{
+	snprintf(out, outlen, "%s-ch%u", pci_name, ch);
+}
+
+static int
+ae4dma_dmadev_create(const char *name, struct rte_pci_device *dev, uint8_t qn)
+{
+	char hwq_dev_name[RTE_DEV_NAME_MAX_LEN] = {0};
+	struct ae4dma_dmadev *ae4dma;
+	struct rte_dma_dev *dmadev;
+
+	ae4dma_channel_dev_name(hwq_dev_name, sizeof(hwq_dev_name), name, qn);
+
+	dmadev = rte_dma_pmd_allocate(hwq_dev_name, dev->device.numa_node,
+			sizeof(struct ae4dma_dmadev));
+	if (dmadev == NULL) {
+		AE4DMA_PMD_ERR("Unable to allocate dma device");
+		return -ENOMEM;
+	}
+	dmadev->device = &dev->device;
+	dmadev->fp_obj->dev_private = dmadev->data->dev_private;
+
+	ae4dma = dmadev->data->dev_private;
+
+	if (ae4dma_add_queue(ae4dma, dev, qn, name) != 0)
+		goto init_error;
+	return 0;
+
+init_error:
+	AE4DMA_PMD_ERR("Failed to create dmadev %s", hwq_dev_name);
+	rte_dma_pmd_release(hwq_dev_name);
+	return -ENOMEM;
+}
+
+static int
+ae4dma_dmadev_probe(struct rte_pci_driver *drv __rte_unused,
+		struct rte_pci_device *dev)
+{
+	char chname[RTE_DEV_NAME_MAX_LEN];
+	uint32_t q_per_eng;
+	void *mmio_base;
+	char name[32];
+	int ret = 0;
+	uint8_t i;
+
+	rte_pci_device_name(&dev->addr, name, sizeof(name));
+	AE4DMA_PMD_INFO("Init %s on NUMA node %d", name, dev->device.numa_node);
+
+	mmio_base = dev->mem_resource[AE4DMA_PCIE_BAR].addr;
+	if (mmio_base == NULL) {
+		AE4DMA_PMD_ERR("%s: BAR%d not mapped", name, AE4DMA_PCIE_BAR);
+		return -ENODEV;
+	}
+
+	/* Program the per-engine HW queue count once. */
+	AE4DMA_WRITE_REG_OFFSET(mmio_base, AE4DMA_COMMON_CONFIG_OFFSET,
+			AE4DMA_MAX_HW_QUEUES);
+	q_per_eng = AE4DMA_READ_REG_OFFSET(mmio_base, AE4DMA_COMMON_CONFIG_OFFSET);
+	AE4DMA_PMD_INFO("%s: AE4DMA queues per engine = %u", name, q_per_eng);
+
+	for (i = 0; i < AE4DMA_MAX_HW_QUEUES; i++) {
+		ret = ae4dma_dmadev_create(name, dev, i);
+		if (ret != 0) {
+			AE4DMA_PMD_ERR("%s create dmadev %u failed!", name, i);
+			while (i > 0) {
+				i--;
+				ae4dma_channel_dev_name(chname, sizeof(chname), name, i);
+				rte_dma_pmd_release(chname);
+			}
+			break;
+		}
+	}
+	return ret;
+}
+
+static int
+ae4dma_dmadev_remove(struct rte_pci_device *dev)
+{
+	char chname[RTE_DEV_NAME_MAX_LEN];
+	unsigned int i;
+	char name[32];
+
+	rte_pci_device_name(&dev->addr, name, sizeof(name));
+
+	AE4DMA_PMD_INFO("Closing %s on NUMA node %d",
+			name, dev->device.numa_node);
+
+	for (i = 0; i < AE4DMA_MAX_HW_QUEUES; i++) {
+		ae4dma_channel_dev_name(chname, sizeof(chname), name, i);
+		rte_dma_pmd_release(chname);
+	}
+	return 0;
+}
+
+static const struct rte_pci_id pci_id_ae4dma_map[] = {
+	{ RTE_PCI_DEVICE(AMD_VENDOR_ID, AE4DMA_DEVICE_ID) },
+	{ .vendor_id = 0, /* sentinel */ },
+};
+
+static struct rte_pci_driver ae4dma_pmd_drv = {
+	.id_table = pci_id_ae4dma_map,
+	.drv_flags = RTE_PCI_DRV_NEED_MAPPING,
+	.probe = ae4dma_dmadev_probe,
+	.remove = ae4dma_dmadev_remove,
+};
+
+RTE_PMD_REGISTER_PCI(AE4DMA_PMD_NAME, ae4dma_pmd_drv);
+RTE_PMD_REGISTER_PCI_TABLE(AE4DMA_PMD_NAME, pci_id_ae4dma_map);
+RTE_PMD_REGISTER_KMOD_DEP(AE4DMA_PMD_NAME, "* igb_uio | uio_pci_generic | vfio-pci");
diff --git a/drivers/dma/ae4dma/ae4dma_hw_defs.h b/drivers/dma/ae4dma/ae4dma_hw_defs.h
new file mode 100644
index 0000000000..d20f3a599b
--- /dev/null
+++ b/drivers/dma/ae4dma/ae4dma_hw_defs.h
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved.
+ */
+
+#ifndef __AE4DMA_HW_DEFS_H__
+#define __AE4DMA_HW_DEFS_H__
+
+#include <stdint.h>
+
+#include <rte_bitops.h>
+
+/* ae4dma device details */
+#define AMD_VENDOR_ID	0x1022
+#define AE4DMA_DEVICE_ID	0x149b
+#define AE4DMA_PCIE_BAR 0
+
+/*
+ * An AE4DMA engine has 16 DMA queues. Each queue supports up to 32
+ * descriptors (31 outstanding).
+ */
+#define AE4DMA_MAX_HW_QUEUES        16
+#define AE4DMA_QUEUE_START_INDEX    0
+#define AE4DMA_CMD_QUEUE_ENABLE		0x1
+#define AE4DMA_CMD_QUEUE_DISABLE	0x0
+
+/* Common to all queues */
+#define AE4DMA_COMMON_CONFIG_OFFSET 0x00
+
+#define AE4DMA_DISABLE_INTR 0x01
+
+/* Descriptor status */
+enum ae4dma_dma_status {
+	AE4DMA_DMA_DESC_SUBMITTED = 0,
+	AE4DMA_DMA_DESC_VALIDATED = 1,
+	AE4DMA_DMA_DESC_PROCESSED = 2,
+	AE4DMA_DMA_DESC_COMPLETED = 3,
+	AE4DMA_DMA_DESC_ERROR = 4,
+};
+
+/* Descriptor error-code */
+enum ae4dma_dma_err {
+	AE4DMA_DMA_ERR_NO_ERR = 0,
+	AE4DMA_DMA_ERR_INV_HEADER = 1,
+	AE4DMA_DMA_ERR_INV_STATUS = 2,
+	AE4DMA_DMA_ERR_INV_LEN = 3,
+	AE4DMA_DMA_ERR_INV_SRC = 4,
+	AE4DMA_DMA_ERR_INV_DST = 5,
+	AE4DMA_DMA_ERR_INV_ALIGN = 6,
+	AE4DMA_DMA_ERR_UNKNOWN = 7,
+};
+
+/* HW Queue status */
+enum ae4dma_hwqueue_status {
+	AE4DMA_HWQUEUE_EMPTY = 0,
+	AE4DMA_HWQUEUE_FULL = 1,
+	AE4DMA_HWQUEUE_NOT_EMPTY = 4,
+};
+/*
+ * descriptor for AE4DMA commands
+ * 8 32-bit words:
+ * word 0: source memory type; destination memory type ; control bits
+ * word 1: desc_id; error code; status
+ * word 2: length
+ * word 3: reserved
+ * word 4: upper 32 bits of source pointer
+ * word 5: low 32 bits of source pointer
+ * word 6: upper 32 bits of destination pointer
+ * word 7: low 32 bits of destination pointer
+ */
+
+/* AE4DMA Descriptor - DWORD0 - Controls bits: Reserved for future use */
+#define AE4DMA_DWORD0_STOP_ON_COMPLETION	RTE_BIT32(0)
+#define AE4DMA_DWORD0_INTERRUPT_ON_COMPLETION	RTE_BIT32(1)
+#define AE4DMA_DWORD0_START_OF_MESSAGE		RTE_BIT32(3)
+#define AE4DMA_DWORD0_END_OF_MESSAGE		RTE_BIT32(4)
+#define AE4DMA_DWORD0_DESTINATION_MEMORY_TYPE	RTE_GENMASK64(5, 4)
+#define AE4DMA_DWORD0_SOURCE_MEMEORY_TYPE	RTE_GENMASK64(7, 6)
+
+#define AE4DMA_DWORD0_DESTINATION_MEMORY_TYPE_MEMORY    (0x0)
+#define AE4DMA_DWORD0_DESTINATION_MEMORY_TYPE_IOMEMORY  RTE_BIT32(4)
+#define AE4DMA_DWORD0_SOURCE_MEMEORY_TYPE_MEMORY    (0x0)
+#define AE4DMA_DWORD0_SOURCE_MEMEORY_TYPE_IOMEMORY  RTE_BIT32(6)
+
+struct ae4dma_desc_dword0 {
+	uint8_t byte0;
+	uint8_t byte1;
+	uint16_t timestamp;
+};
+
+struct ae4dma_desc_dword1 {
+	uint8_t status;
+	uint8_t err_code;
+	uint16_t desc_id;
+};
+
+struct ae4dma_desc {
+	struct ae4dma_desc_dword0 dw0;
+	struct ae4dma_desc_dword1 dw1;
+	uint32_t length;
+	uint32_t reserved;
+	uint32_t src_lo;
+	uint32_t src_hi;
+	uint32_t dst_lo;
+	uint32_t dst_hi;
+};
+
+/*
+ * Registers for each queue :4 bytes length
+ * Effective address : offset + reg
+ */
+struct ae4dma_hwq_regs {
+	union {
+		uint32_t control_raw;
+		struct {
+			uint32_t queue_enable: 1;
+			uint32_t reserved_internal: 31;
+		} control;
+	} control_reg;
+
+	union {
+		uint32_t status_raw;
+		struct {
+			uint32_t reserved0: 1;
+			/* 0–empty, 1–full, 2–stopped, 3–error , 4–Not Empty */
+			uint32_t queue_status: 2;
+			uint32_t reserved1: 21;
+			uint32_t interrupt_type: 4;
+			uint32_t reserved2: 4;
+		} status;
+	} status_reg;
+
+	uint32_t max_idx;
+	uint32_t read_idx;
+	uint32_t write_idx;
+
+	union {
+		uint32_t intr_status_raw;
+		struct {
+			uint32_t intr_status: 1;
+			uint32_t reserved: 31;
+		} intr_status;
+	} intr_status_reg;
+
+	uint32_t qbase_lo;
+	uint32_t qbase_hi;
+
+};
+
+#endif /* AE4DMA_HW_DEFS_H */
diff --git a/drivers/dma/ae4dma/ae4dma_internal.h b/drivers/dma/ae4dma/ae4dma_internal.h
new file mode 100644
index 0000000000..bf69a45bf9
--- /dev/null
+++ b/drivers/dma/ae4dma/ae4dma_internal.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved.
+ */
+
+#ifndef _AE4DMA_INTERNAL_H_
+#define _AE4DMA_INTERNAL_H_
+
+#include <stdint.h>
+
+#include <rte_byteorder.h>
+#include <rte_dmadev.h>
+#include <rte_io.h>
+#include <rte_memzone.h>
+
+#include "ae4dma_hw_defs.h"
+
+/* Return bits 32-63 of a 64-bit number. */
+#define upper_32_bits(n) ((uint32_t)(((n) >> 16) >> 16))
+
+/* Return bits 0-31 of a 64-bit number. */
+#define lower_32_bits(n) ((uint32_t)((n) & 0xffffffff))
+
+/*
+ * Hardware ring depth (slots per queue); fixed at 32 per the AE4DMA
+ * spec and must be a power of two. The engine reserves one slot to tell
+ * a full ring from an empty one (full when (write + 1) % max == read),
+ * so at most nb_desc - 1 (31) descriptors can be outstanding.
+ */
+#define AE4DMA_DESCRIPTORS_PER_CMDQ	32
+#define AE4DMA_QUEUE_DESC_SIZE		sizeof(struct ae4dma_desc)
+#define AE4DMA_QUEUE_SIZE(n)		(AE4DMA_DESCRIPTORS_PER_CMDQ * (n))
+
+/* AE4DMA registers Write/Read */
+static inline void ae4dma_pci_reg_write(void *base, int offset,
+		uint32_t value)
+{
+	volatile void *reg_addr = ((uint8_t *)base + offset);
+
+	rte_write32((rte_cpu_to_le_32(value)), reg_addr);
+}
+
+static inline uint32_t ae4dma_pci_reg_read(void *base, int offset)
+{
+	volatile void *reg_addr = ((uint8_t *)base + offset);
+
+	return rte_le_to_cpu_32(rte_read32(reg_addr));
+}
+
+#define AE4DMA_READ_REG_OFFSET(hw_addr, reg_offset) \
+	ae4dma_pci_reg_read(hw_addr, reg_offset)
+
+#define AE4DMA_WRITE_REG_OFFSET(hw_addr, reg_offset, value) \
+	ae4dma_pci_reg_write(hw_addr, reg_offset, value)
+
+#define AE4DMA_READ_REG(hw_addr) \
+	ae4dma_pci_reg_read((void *)(uintptr_t)(hw_addr), 0)
+
+#define AE4DMA_WRITE_REG(hw_addr, value) \
+	ae4dma_pci_reg_write((void *)(uintptr_t)(hw_addr), 0, value)
+
+/* A structure describing an AE4DMA command queue. */
+struct __rte_cache_aligned ae4dma_cmd_queue {
+	char memz_name[RTE_MEMZONE_NAMESIZE];
+	const struct rte_memzone *mz;
+	volatile struct ae4dma_hwq_regs *hwq_regs;
+
+	struct rte_dma_vchan_conf qcfg;
+	struct rte_dma_stats stats;
+	/* Queue address */
+	struct ae4dma_desc *qbase_desc;
+	void *qbase_addr;
+	rte_iova_t qbase_phys_addr;
+	/* Queue identifier */
+	uint64_t id;    /* queue id */
+	uint64_t qidx;  /* queue index */
+	uint64_t qsize; /* queue size */
+	/*
+	 * Free-running 16-bit ring indices (wrap at 2^16), as required by the
+	 * dmadev API. The HW ring slot is derived as ((hw_base + idx) &
+	 * (nb_desc - 1)), which is why nb_desc is rounded up to a power of two.
+	 */
+	uint16_t next_read;  /* ring_idx of the next completion to reap */
+	uint16_t next_write; /* ring_idx assigned to the next enqueue */
+	uint16_t last_write; /* next_write at last submit; for submitted stat */
+	/*
+	 * HW ring slot that free-running index 0 maps to. read_idx is HW-owned
+	 * (read-only), so on start we anchor to wherever the HW consumer sits
+	 * instead of forcing the HW indices to 0.
+	 */
+	uint16_t hw_base;
+};
+
+/*
+ * One dmadev per AE4DMA hardware channel: probe creates AE4DMA_MAX_HW_QUEUES
+ * dmadevs per PCI function, each owning a single HW command queue.
+ */
+struct ae4dma_dmadev {
+	void *io_regs;
+	struct ae4dma_cmd_queue cmd_q; /* single HW queue owned by this dmadev */
+};
+
+extern int ae4dma_pmd_logtype;
+#define RTE_LOGTYPE_AE4DMA_PMD ae4dma_pmd_logtype
+
+#define AE4DMA_PMD_LOG(level, ...) \
+	RTE_LOG_LINE_PREFIX(level, AE4DMA_PMD, "%s(): ", __func__, __VA_ARGS__)
+
+#define AE4DMA_PMD_DEBUG(...)  AE4DMA_PMD_LOG(DEBUG, __VA_ARGS__)
+#define AE4DMA_PMD_INFO(...)   AE4DMA_PMD_LOG(INFO, __VA_ARGS__)
+#define AE4DMA_PMD_ERR(...)    AE4DMA_PMD_LOG(ERR, __VA_ARGS__)
+#define AE4DMA_PMD_WARN(...)   AE4DMA_PMD_LOG(WARNING, __VA_ARGS__)
+
+#endif /* _AE4DMA_INTERNAL_H_ */
diff --git a/drivers/dma/ae4dma/meson.build b/drivers/dma/ae4dma/meson.build
new file mode 100644
index 0000000000..9f43141b47
--- /dev/null
+++ b/drivers/dma/ae4dma/meson.build
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2026 Advanced Micro Devices, Inc. All rights reserved.
+
+if not is_linux
+    build = false
+    reason = 'only supported on Linux'
+    subdir_done()
+endif
+
+build = dpdk_conf.has('RTE_ARCH_X86')
+reason = 'only supported on x86'
+sources = files('ae4dma_dmadev.c')
+deps += ['bus_pci', 'dmadev']
diff --git a/drivers/dma/meson.build b/drivers/dma/meson.build
index e0d94db967..c230ac5a06 100644
--- a/drivers/dma/meson.build
+++ b/drivers/dma/meson.build
@@ -2,6 +2,7 @@
 # Copyright 2021 HiSilicon Limited
 
 drivers = [
+        'ae4dma',
         'cnxk',
         'dpaa',
         'dpaa2',
diff --git a/usertools/dpdk-devbind.py b/usertools/dpdk-devbind.py
index 93f2383dff..7d09f155de 100755
--- a/usertools/dpdk-devbind.py
+++ b/usertools/dpdk-devbind.py
@@ -86,6 +86,9 @@
 cn9k_ree = {'Class': '08', 'Vendor': '177d', 'Device': 'a0f4',
             'SVendor': None, 'SDevice': None}
 
+amd_ae4dma = {'Class': '08', 'Vendor': '1022', 'Device': '149b',
+              'SVendor': None, 'SDevice': None}
+
 virtio_blk = {'Class': '01', 'Vendor': "1af4", 'Device': '1001,1042',
               'SVendor': None, 'SDevice': None}
 
@@ -95,7 +98,7 @@
 network_devices = [network_class, cavium_pkx, avp_vnic, ifpga_class]
 baseband_devices = [acceleration_class]
 crypto_devices = [encryption_class, intel_processor_class]
-dma_devices = [cnxk_dma, hisilicon_dma,
+dma_devices = [amd_ae4dma, cnxk_dma, hisilicon_dma,
                intel_idxd_gnrd, intel_idxd_dmr, intel_idxd_spr,
                intel_ioat_bdw, intel_ioat_icx, intel_ioat_skx,
                odm_dma]
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 0/4] dma/ae4dma: add AMD AE4DMA DMA PMD
From: Raghavendra Ningoji @ 2026-07-10  7:36 UTC (permalink / raw)
  To: dev
  Cc: fengchengwen, david.marchand, bruce.richardson, selwin.sebastian,
	bhagyada.modali, rjarry, thomas, Raghavendra Ningoji

This series adds a new dmadev poll-mode driver for the AMD AE4DMA
hardware DMA engine, split into introduction, control path and data
path patches, plus a small dmadev test-suite patch.

v5 addresses the v4 review comments from Chengwen Feng.

Changes since v4:

Patch 1/4 (introduce):
 - Drop the custom AE4DMA_BIT() macro and use RTE_BIT32() from
   rte_bitops.h for the DWORD0 control-bit and memory-type defines.
 - Remove the now-unused per-queue status[] array from
   struct ae4dma_cmd_queue (completion status is derived directly from
   the descriptors in patch 3).

Patch 3/4 (data path):
 - Fix rte_dma_completed(): stop at the first failed descriptor and
   return only the run of successful ops preceding it, set *has_error,
   and leave the failed op to be reported by rte_dma_completed_status().
   last_idx now reports the last successfully completed ring_idx.
 - Drop the redundant 'if (last_idx != NULL)' check; the framework
   always passes a non-NULL last_idx.

Patch 4/4 (test):
 - Unchanged; retains TEST_SKIPPED for sub-32 burst capacity.

Changes since v3 (carried over, for reference):

Patch 1/4 (introduce):
 - Fix copyright years (2025/2024 -> 2026) in ae4dma.rst and meson.build.
 - Drop the unnecessary memzone lookup/reuse logic; rings are reserved
   once at probe.
 - Trim ae4dma_hw_defs.h includes; move the rest to ae4dma_internal.h.
 - Remove redundant double blank lines in ae4dma_internal.h.
 - Use an initialiser instead of memset() for hwq_dev_name and reorder
   local declarations in descending length.
 - Make the probe-failure log message more descriptive.
 - Build only on Linux (add the is_linux guard to meson.build).
 - Add a hw_base field to anchor the free-running ring index.

Patch 2/4 (control path):
 - Advertise RTE_DMA_CAPA_OPS_COPY in dev_info_get.
 - Use 'conf_sz < sizeof(...)' instead of '!=' to stay ABI compatible.
 - Drop checks already performed by the dmadev framework.
 - Reset the free-running ring indices in vchan_setup and dev_start,
   anchoring index 0 to the current HW consumer slot (hw_base).
 - Reword the commit log for the 2 to 32 descriptor range.

Patch 3/4 (data path):
 - Return free-running 16-bit ring_idx values from rte_dma_copy() and
   completed()/completed_status(); HW slot = (hw_base + idx) & mask.
 - Map the AE4DMA alignment error to RTE_DMA_STATUS_ERROR_UNKNOWN.
 - Drop the redundant power-of-two/range check in burst_capacity().
 - Document the 32-descriptor / 31-outstanding ring-depth limitation.

Patch 4/4 (test) - added in v4:
 - Return TEST_SKIPPED instead of failing the instance suite when a
   device reports burst capacity below 32, matching the existing < 64
   skip used by the burst_capacity test.

Note: the power-of-two rounding of nb_desc in vchan_setup is retained
on purpose: the free-running ring index is mapped to a HW ring slot by
masking, which is only correct for a power-of-two depth.

Raghavendra Ningoji (4):
  dma/ae4dma: introduce AMD AE4DMA DMA PMD
  dma/ae4dma: add control path operations
  dma/ae4dma: add data path operations
  test/dma: skip instance suite on low burst capacity

 .mailmap                               |   1 +
 MAINTAINERS                            |   5 +
 app/test/test_dmadev.c                 |   8 +-
 doc/guides/dmadevs/ae4dma.rst          |  77 +++
 doc/guides/dmadevs/index.rst           |   1 +
 doc/guides/rel_notes/release_26_07.rst |   7 +
 drivers/dma/ae4dma/ae4dma_dmadev.c     | 677 +++++++++++++++++++++++++
 drivers/dma/ae4dma/ae4dma_hw_defs.h    | 149 ++++++
 drivers/dma/ae4dma/ae4dma_internal.h   | 113 +++++
 drivers/dma/ae4dma/meson.build         |  13 +
 drivers/dma/meson.build                |   1 +
 usertools/dpdk-devbind.py              |   5 +-
 12 files changed, 1054 insertions(+), 3 deletions(-)
 create mode 100644 doc/guides/dmadevs/ae4dma.rst
 create mode 100644 drivers/dma/ae4dma/ae4dma_dmadev.c
 create mode 100644 drivers/dma/ae4dma/ae4dma_hw_defs.h
 create mode 100644 drivers/dma/ae4dma/ae4dma_internal.h
 create mode 100644 drivers/dma/ae4dma/meson.build

-- 
2.34.1


^ permalink raw reply

* Re: [PATCH v4 4/4] test/dma: skip instance suite on low burst capacity
From: Raghavendra Ningoji @ 2026-07-10  7:22 UTC (permalink / raw)
  To: fengchengwen, dev
  Cc: Raghavendra Ningoji, David Marchand, Bruce Richardson,
	Selwin.Sebastian, Bhagyada Modali, Robin Jarry, Thomas Monjalon
In-Reply-To: <44a99399-6050-4374-9f3f-64f6b8ed70d2@huawei.com>

On Mon, 7 Jul 2026 at 07:11, fengchengwen <fengchengwen@huawei.com> wrote:
>
> This approach is also acceptable, but I think this test, as a community
> "free gift", should be supported as much as possible. So I would suggest
> you modify the relevant code to support this test, but it is not mandatory.

Thanks. Keeping the TEST_SKIPPED approach as-is for this series.

Thanks,
Raghavendra

^ permalink raw reply

* Re: [PATCH v4 3/4] dma/ae4dma: add data path operations
From: Raghavendra Ningoji @ 2026-07-10  7:22 UTC (permalink / raw)
  To: fengchengwen, dev
  Cc: Raghavendra Ningoji, David Marchand, Bruce Richardson,
	Selwin.Sebastian, Bhagyada Modali, Robin Jarry, Thomas Monjalon
In-Reply-To: <aa1b5a05-976f-4dfa-89bc-4cea18b56d33@huawei.com>

On Mon, 7 Jul 2026 at 06:49, fengchengwen <fengchengwen@huawei.com> wrote:
>
> > +	if (last_idx != NULL)
> > +		*last_idx = (uint16_t)(cmd_q->next_read - 1);
>
> 1\ the last_idx always non-NULL for driver, so no need for
>    'if (last_idx != NULL)'
> 2\ last_idx should be the last success completed index

Both fixed in v5. The NULL check is dropped, and last_idx now reports
the ring_idx of the last *successfully* completed op.

> Consider four reqests, the hardware mark each request as following:
>     req1   success
>     req2   err
>     req3   success
>     req4   err
> the cpl_count will be 4, and err_count will be 2
> It will return 2 in current impl, and last_idx will be the req4's
> But in this function, it should return 1, and last_idx should be req1's

Agreed - thanks for the clear example. rte_dma_completed() is reworked
in v5 to stop at the first failed descriptor: it returns only the run of
successful ops preceding the error (1 in your example), sets *has_error,
and leaves the failed op in place with last_idx pointing at the last
success (req1). The failed op and everything after it are then drained
and reported by rte_dma_completed_status().

On a related note, while implementing the above I found the last_idx
documentation for rte_dma_completed() and rte_dma_completed_status()
to be identical:

    @param[out] last_idx
      The last completed operation's ring_idx.

Since rte_dma_completed() only reports operations that completed
*successfully* (it stops at the first error), its last_idx is really the
last successfully completed operation's ring_idx, whereas for
rte_dma_completed_status() it is the last completed operation regardless
of status. Would a small doc clarification along these lines be welcome
(as a separate patch)?

    rte_dma_completed():
    @param[out] last_idx
      The last successfully completed operation's ring_idx.

I'm happy to send that as a separate dmadev doc patch if you agree.

Thanks,
Raghavendra

^ permalink raw reply

* Re: [PATCH v4 1/4] dma/ae4dma: introduce AMD AE4DMA DMA PMD
From: Raghavendra Ningoji @ 2026-07-10  7:22 UTC (permalink / raw)
  To: fengchengwen, dev
  Cc: Raghavendra Ningoji, David Marchand, Bruce Richardson,
	Selwin.Sebastian, Bhagyada Modali, Robin Jarry, Thomas Monjalon
In-Reply-To: <30d17d21-5dc2-49e4-9104-61d8c475b6c7@huawei.com>

On Mon, 7 Jul 2026 at 06:54, fengchengwen <fengchengwen@huawei.com> wrote:
>
> > +#define AE4DMA_BIT(nr)			(1UL << (nr))
>
> Why not use RTE_BIT32() in rte_bitops.h which already included ?

Good point. The custom AE4DMA_BIT macro is removed in v5 and the
DWORD0 control-bit defines now use RTE_BIT32() directly
(rte_bitops.h is already included).

> > +#define AE4DMA_DWORD0_SOURCE_MEMEORY_TYPE_IOMEMORY  (1<<6)
>
> How about use RTE_BIT32(6) ?

Done. Both the destination and source IOMEMORY defines now use
RTE_BIT32(4) / RTE_BIT32(6) instead of open-coded shifts.

Thanks,
Raghavendra

^ permalink raw reply

* Re: [PATCH v4 10/10] net/mlx5: accept more unicast MAC addresses
From: David Marchand @ 2026-07-10  6:44 UTC (permalink / raw)
  To: dev
  Cc: rjarry, cfontain, Dariusz Sosnowski, Viacheslav Ovsiienko,
	Bing Zhao, Ori Kam, Suanming Mou, Matan Azrad, Thomas Monjalon
In-Reply-To: <20260709160247.1798575-11-david.marchand@redhat.com>

On Thu, 9 Jul 2026 at 18:04, David Marchand <david.marchand@redhat.com> wrote:
> diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
> index 1cad6e1091..23632f55af 100644
> --- a/drivers/net/mlx5/linux/mlx5_os.c
> +++ b/drivers/net/mlx5/linux/mlx5_os.c

[snip]

> @@ -1762,8 +1788,8 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
>                 mlx5_nl_mac_addr_sync(priv->nl_socket_route,
>                                       mlx5_ifindex(eth_dev),
>                                       eth_dev->data->mac_addrs,
> -                                     MLX5_MAX_UC_MAC_ADDRESSES,
> -                                     MLX5_MAX_MAC_ADDRESSES);
> +                                     sh->dev_cap.max_mac_addrs,
> +                                     sh->dev_cap.max_uc_mac_addrs);
>         priv->ctrl_flows = 0;
>         rte_spinlock_init(&priv->flow_list_lock);
>         TAILQ_INIT(&priv->flow_meters);

Oops, I swapped those two arguments when cleaning up the patches...

I'll need a new revision anyway when sending the series rebased on 26.11-rc0.


-- 
David Marchand


^ permalink raw reply

* Re: [PATCH v6] dts: add support for no link topology
From: Patrick Robb @ 2026-07-10  2:54 UTC (permalink / raw)
  To: Andrew Bailey; +Cc: dev, luca.vizzarro, knimoji, lylavoie
In-Reply-To: <20260709175312.1018085-1-abailey@iol.unh.edu>

[-- Attachment #1: Type: text/plain, Size: 39 bytes --]

Applied to next-dts. Thank you Andrew.

[-- Attachment #2: Type: text/html, Size: 60 bytes --]

^ permalink raw reply

* Re: [PATCH v5 1/2] dts: add code coverage reporting to DTS
From: Patrick Robb @ 2026-07-10  2:29 UTC (permalink / raw)
  To: Koushik Bhargav Nimoji; +Cc: luca.vizzarro, dev, abailey, ahassick, lylavoie
In-Reply-To: <20260709172823.972163-1-knimoji@iol.unh.edu>

[-- Attachment #1: Type: text/plain, Size: 2857 bytes --]

At this time I cannot generate a code coverage report by
passion --code-coverage when I run DTS, so I cannot merge the patch this
evening. See the ls of the meson-logs dir at the end of this email. I am
using iol-dts-tester.dpdklab.iol.unh.edu as SUT. I believe the dependencies
are met as I manually checked for them, and also there was no exception
raised for mission coverage dependencies, which your patch did add.

I will take another stab at it later and if it is user error on my part I
will apply the patch to next-dts.

2026/07/10 02:08:07 - test_run_teardown - dts.iol-TG - INFO - Sending:
'lshw -quiet -json -C network'
2026/07/10 02:08:08 - test_run_teardown - dts.iol-SUT dpdk_build - INFO -
Sending: 'lcov --version | grep -oP '\d+\.\d+''
2026/07/10 02:08:08 - test_run_teardown - dts.iol-SUT dpdk_build - INFO -
Sending: 'gcov --version | head -n 1 | grep -oP '\d+\.\d+' | tail -n 1'
2026/07/10 02:08:08 - test_run_teardown - dts.iol-SUT dpdk_build - INFO -
Sending: 'ninja -C /tmp/dts.uNWxq/dpdk/x86_64-linux-gcc coverage-html'
2026/07/10 02:11:59 - test_run_teardown - dts.iol-SUT dpdk_build - INFO -
Sending: 'tar caf /tmp/dts.uNWxq/dpdk/x86_64-linux-gcc/meson-logs.tar -C
/tmp/dts.uNWxq/dpdk/x86_64-linux-gcc meson-logs'
2026/07/10 02:12:02 - test_run_teardown - dts.iol-SUT dpdk_build - INFO -
Sending: 'rm -f /tmp/dts.uNWxq/dpdk/x86_64-linux-gcc/meson-logs.tar'
2026/07/10 02:12:02 - test_run_teardown - dts - INFO - Coverage HTML report
generated, available at output/meson-logs/coveragereport/index.html
2026/07/10 02:12:02 - test_run_teardown - dts.iol-SUT - INFO - Sending: 'rm
-rf /tmp/dts.uNWxq/dpdk'
2026/07/10 02:12:04 - test_run_teardown - dts.iol-SUT - INFO - Sending: 'rm
-f /tmp/dts.uNWxq/dpdk-26.03.tar.xz'
2026/07/10 02:12:05 - test_run_teardown - dts.iol-TG - INFO - Sending: 'rm
-rf /tmp/dts.L4Yz1'
2026/07/10 02:12:05 - test_run_teardown - dts.iol-SUT - INFO - Sending: 'rm
-rf /tmp/dts.uNWxq'
2026/07/10 02:12:05 - test_run_teardown - dts - INFO - Moving from stage
'test_run_teardown' to stage 'post_run'.
2026/07/10 02:12:05 - post_run - dts - INFO - DTS execution has ended.

Results
=======
test_suites: PASS
  rx_tx_offload: PASS
    test_mbuf_fast_free_configuration_per_port: PASS
    test_mbuf_fast_free_configuration_per_queue: SKIP
      reason: Required capability '{QUEUE_TX_OFFLOAD_MBUF_FAST_FREE}' not
found.
  blocklist: PASS
    all_but_one_port_blocklisted: PASS
    no_blocklisted: PASS
    one_port_blocklisted: PASS
  promisc_support: PASS
    promisc_packets: PASS

Test Cases Summary
==================
SKIP      = 1
PASS      = 5
BLOCK     = 0
FAIL      = 0
ERROR     = 0
PASS RATE = 100%

root@df31b2adb6c2:/dpdk/dts# ls output
output/        output.23.bak/
root@df31b2adb6c2:/dpdk/dts# ls output/meson-logs/
coverage.info.initial  coverage.info.run  meson-log.txt
root@df31b2adb6c2:/dpdk/dts#

[-- Attachment #2: Type: text/html, Size: 3273 bytes --]

^ permalink raw reply

* Re: [PATCH v6] dts: report dut/NIC info during DTS run
From: Patrick Robb @ 2026-07-10  0:56 UTC (permalink / raw)
  To: Koushik Bhargav Nimoji; +Cc: luca.vizzarro, dev, abailey, ahassick, lylavoie
In-Reply-To: <20260709134814.805721-1-knimoji@iol.unh.edu>

[-- Attachment #1: Type: text/plain, Size: 72 bytes --]

Applied to next-dts with a commit reword. Thanks for the patch Koushik.

[-- Attachment #2: Type: text/html, Size: 95 bytes --]

^ permalink raw reply

* [PATCH v6] dts: add support for no link topology
From: Andrew Bailey @ 2026-07-09 17:53 UTC (permalink / raw)
  To: dev, luca.vizzarro, patrickrobb1997; +Cc: knimoji, lylavoie, Andrew Bailey
In-Reply-To: <20260220192510.37163-1-abailey@iol.unh.edu>

Add support for running DTS with no traffic generator node and no ethdev
interfaces. Some applications, like dpdk-test-crypto-perf run without
ethdev interfaces and no traffic generator. In these cases, it is
beneficial to remove the overhead of creating a node and ports that are
not used. The specified option to build DPDK with 16 byte descriptors
for ice drivers is removed in the case of a no link topology since it is
not required when traffic is not present. Additionally, leaving the logic
as is would cause DTS to crash when the topology was no link but does
not with this change.

Signed-off-by: Andrew Bailey <abailey@iol.unh.edu>
---

v4:
* Made it possible to run no link topology even if a TG node is
  present in the config.
* Updated the test run example config file to properly document how no
  link topology is run.
v5:
* Rebased on main to resolve merge conflicts
* Ran the format script with the new updated tooling
v6:
* Updated commit message and typo in comment

 dts/api/test.py                          | 11 ++-
 dts/configurations/test_run.example.yaml | 10 ++-
 dts/framework/config/__init__.py         | 36 ++++-----
 dts/framework/config/test_run.py         |  4 +-
 dts/framework/context.py                 |  2 +-
 dts/framework/remote_session/dpdk.py     |  4 +-
 dts/framework/runner.py                  |  6 +-
 dts/framework/test_run.py                | 95 ++++++++++++++----------
 dts/framework/testbed_model/topology.py  | 17 ++++-
 9 files changed, 112 insertions(+), 73 deletions(-)

diff --git a/dts/api/test.py b/dts/api/test.py
index e17babe0ca..7947c407d2 100644
--- a/dts/api/test.py
+++ b/dts/api/test.py
@@ -10,6 +10,7 @@
 from datetime import datetime

 from api.artifact import Artifact
+from api.capabilities import LinkTopology
 from framework.context import get_ctx
 from framework.exception import InternalError, SkippedTestException, TestCaseVerifyError
 from framework.logger import DTSLogger
@@ -109,12 +110,14 @@ def fail(failure_description: str) -> None:
     Raises:
         TestCaseVerifyError: Always raised to indicate the test case failed.
     """
+    ctx = get_ctx()
     get_logger().debug("A test case failed, showing the last 10 commands executed on SUT:")
-    for command_res in get_ctx().sut_node.main_session.remote_session.history[-10:]:
-        get_logger().debug(command_res.command)
-    get_logger().debug("A test case failed, showing the last 10 commands executed on TG:")
-    for command_res in get_ctx().tg_node.main_session.remote_session.history[-10:]:
+    for command_res in ctx.sut_node.main_session.remote_session.history[-10:]:
         get_logger().debug(command_res.command)
+    if ctx.topology.type is not LinkTopology.NO_LINK and ctx.tg_node is not None:
+        get_logger().debug("A test case failed, showing the last 10 commands executed on TG:")
+        for command_res in ctx.tg_node.main_session.remote_session.history[-10:]:
+            get_logger().debug(command_res.command)
     raise TestCaseVerifyError(failure_description)


diff --git a/dts/configurations/test_run.example.yaml b/dts/configurations/test_run.example.yaml
index ee641f5dce..51f9ab8237 100644
--- a/dts/configurations/test_run.example.yaml
+++ b/dts/configurations/test_run.example.yaml
@@ -25,6 +25,12 @@
 #       By removing the `test_suites` field, this test run will run every test suite available.
 #   `vdevs`:
 #       Uncomment to add a specific virtual device to run on the SUT node.
+#   `port_topology`:
+#   	By providing an empty list, DTS will run in no link topology mode and will not allocate a TG
+#   	node.
+#   `traffic_generator_node`:
+#   	This field may be None When `port_topology` is specified as a no link topology, Otherwise it
+#   	is required.

 # Define the test run environment
 dpdk:
@@ -58,8 +64,8 @@ test_suites: # see `Optional Fields`; the following test suites will be run in t
 # The machine running the DPDK test executable
 system_under_test_node: "SUT 1"
 # Traffic generator node to use for this execution environment
-traffic_generator_node: "TG 1"
-port_topology:
+traffic_generator_node: "TG 1" # see `Optional Fields`
+port_topology: # see `Optional Fields`
   - sut.port-0 <-> tg.port-0  # explicit link. `sut` and `tg` are special identifiers that refer
                               # to the respective test run's configured nodes.
   - port-1 <-> port-1         # implicit link, left side is always SUT, right side is always TG.
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index a8861894b7..573f55471a 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -94,17 +94,19 @@ def validate_port_links(self) -> Self:
                 f"already linked to port {sut_node_port_peer[0]}.{sut_node_port_peer[1]}."
             )

-            tg_node_port_peer = existing_port_links.get(
-                (self.test_run.traffic_generator_node, link.tg_port), None
-            )
-            assert tg_node_port_peer is not None, (
-                f"Invalid TG node port specified for link port_topology.{link_idx}."
-            )
-
-            assert tg_node_port_peer is False or sut_node_port_peer == link.left, (
-                f"The TG node port for link port_topology.{link_idx} is "
-                f"already linked to port {tg_node_port_peer[0]}.{tg_node_port_peer[1]}."
-            )
+            if self.test_run.port_topology != []:
+                assert self.test_run.traffic_generator_node is not None, "No TG node specified."
+                tg_node_port_peer = existing_port_links.get(
+                    (self.test_run.traffic_generator_node, link.tg_port), None
+                )
+                assert tg_node_port_peer is not None, (
+                    f"Invalid TG node port specified for link port_topology.{link_idx}."
+                )
+
+                assert tg_node_port_peer is False or sut_node_port_peer == link.left, (
+                    f"The TG node port for link port_topology.{link_idx} is "
+                    f"already linked to port {tg_node_port_peer[0]}.{tg_node_port_peer[1]}."
+                )

             existing_port_links[link.left] = link.right
             existing_port_links[link.right] = link.left
@@ -121,13 +123,13 @@ def validate_test_run_against_nodes(self) -> Self:
             f"The system_under_test_node {sut_node_name} is not a valid node name."
         )

-        tg_node_name = self.test_run.traffic_generator_node
-        tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)
-
-        assert tg_node is not None, (
-            f"The traffic_generator_name {tg_node_name} is not a valid node name."
-        )
+        if self.test_run.port_topology != []:
+            tg_node_name = self.test_run.traffic_generator_node
+            tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)

+            assert tg_node is not None, (
+                f"The traffic_generator_name {tg_node_name} is not a valid node name."
+            )
         return self


diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py
index 3cd643981d..81630df77d 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -492,11 +492,11 @@ class TestRunConfiguration(FrozenModel):
     #: The SUT node name to use in this test run.
     system_under_test_node: str
     #: The TG node name to use in this test run.
-    traffic_generator_node: str
+    traffic_generator_node: str | None = Field(default=None)
     #: The seed to use for pseudo-random generation.
     random_seed: int | None = None
     #: The port links between the specified nodes to use.
-    port_topology: list[PortLinkConfig] = Field(max_length=2)
+    port_topology: list[PortLinkConfig] = Field(default=[], max_length=2)

     fields_from_settings = model_validator(mode="before")(
         load_fields_from_settings("test_suites", "random_seed")
diff --git a/dts/framework/context.py b/dts/framework/context.py
index efe9af0645..b1c36f90a6 100644
--- a/dts/framework/context.py
+++ b/dts/framework/context.py
@@ -72,7 +72,7 @@ class Context:
     """Runtime context."""

     sut_node: Node
-    tg_node: Node
+    tg_node: Node | None
     topology: Topology
     dpdk_build: "DPDKBuildEnvironment"
     dpdk: "DPDKRuntimeEnvironment"
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index c3575cfcaf..e43e1f2123 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -13,6 +13,7 @@
 from pathlib import Path, PurePath
 from typing import ClassVar, Final

+from api.capabilities import LinkTopology
 from framework.config.test_run import (
     DPDKBuildConfiguration,
     DPDKBuildOptionsConfiguration,
@@ -263,7 +264,8 @@ def _build_dpdk(self) -> None:
         ctx = get_ctx()
         # If the SUT is an ice driver device, make sure to build with 16B descriptors.
         if (
-            ctx.topology.sut_port_ingress
+            ctx.topology.type is not LinkTopology.NO_LINK
+            and ctx.topology.sut_port_ingress
             and ctx.topology.sut_port_ingress.config.os_driver == "ice"
         ):
             meson_args = MesonArgs(
diff --git a/dts/framework/runner.py b/dts/framework/runner.py
index 6ea4749ff4..fa4f06844e 100644
--- a/dts/framework/runner.py
+++ b/dts/framework/runner.py
@@ -61,7 +61,11 @@ def run(self) -> None:
             self._check_dts_python_version()

             for node_config in self._configuration.nodes:
-                nodes.append(Node(node_config))
+                if self._configuration.test_run.port_topology == []:
+                    if node_config.name == self._configuration.test_run.system_under_test_node:
+                        nodes.append(Node(node_config))
+                else:
+                    nodes.append(Node(node_config))

             test_run = TestRun(
                 self._configuration.test_run,
diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py
index 94dc6023a7..9b973532da 100644
--- a/dts/framework/test_run.py
+++ b/dts/framework/test_run.py
@@ -106,6 +106,7 @@
 from types import MethodType
 from typing import ClassVar, Protocol, Union

+from api.capabilities import LinkTopology
 from framework.config.test_run import TestRunConfiguration
 from framework.context import Context, init_ctx
 from framework.exception import InternalError, SkippedTestException, TestCaseVerifyError
@@ -190,24 +191,33 @@ def __init__(
         self.logger = get_dts_logger()

         sut_node = next(n for n in nodes if n.name == config.system_under_test_node)
-        tg_node = next(n for n in nodes if n.name == config.traffic_generator_node)
-
-        topology = Topology.from_port_links(
-            PortLink(sut_node.ports_by_name[link.sut_port], tg_node.ports_by_name[link.tg_port])
-            for link in self.config.port_topology
-        )
+        if config.port_topology != []:
+            tg_node = next(n for n in nodes if n.name == config.traffic_generator_node)
+            topology = Topology.from_port_links(
+                PortLink(sut_node.ports_by_name[link.sut_port], tg_node.ports_by_name[link.tg_port])
+                for link in self.config.port_topology
+            )
+        else:
+            tg_node = None
+            topology = Topology.from_port_links(iter([]))

         dpdk_build_env = DPDKBuildEnvironment(config.dpdk.build, sut_node)
         dpdk_runtime_env = DPDKRuntimeEnvironment(config.dpdk, sut_node, dpdk_build_env)

         func_traffic_generator = (
             create_traffic_generator(config.func_traffic_generator, tg_node)
-            if config.func and config.func_traffic_generator
+            if config.func
+            and config.func_traffic_generator
+            and topology.type is not LinkTopology.NO_LINK
+            and tg_node is not None
             else None
         )
         perf_traffic_generator = (
             create_traffic_generator(config.perf_traffic_generator, tg_node)
-            if config.perf and config.perf_traffic_generator
+            if config.perf
+            and config.perf_traffic_generator
+            and topology.type is not LinkTopology.NO_LINK
+            and tg_node is not None
             else None
         )

@@ -336,39 +346,40 @@ def description(self) -> str:
     def next(self) -> State | None:
         """Process state and return the next one."""
         test_run = self.test_run
-        init_ctx(test_run.ctx)
+        ctx = test_run.ctx
+        init_ctx(ctx)

-        self.logger.info(f"Running on SUT node '{test_run.ctx.sut_node.name}'.")
+        self.logger.info(f"Running on SUT node '{ctx.sut_node.name}'.")
         test_run.init_random_seed()
         test_run.remaining_tests = deque(test_run.selected_tests)

-        test_run.ctx.sut_node.setup()
-        test_run.ctx.tg_node.setup()
-        test_run.ctx.dpdk.setup()
-        test_run.ctx.topology.setup()
+        ctx.sut_node.setup()
+        if ctx.topology.type is not LinkTopology.NO_LINK and ctx.tg_node is not None:
+            ctx.tg_node.setup()
+        ctx.dpdk.setup()
+        ctx.topology.setup()

         if test_run.config.use_virtual_functions:
-            test_run.ctx.topology.instantiate_vf_ports()
-        if test_run.ctx.sut_node.cryptodevs and test_run.config.crypto:
-            test_run.ctx.topology.instantiate_crypto_ports()
-            test_run.ctx.topology.bind_cryptodevs("dpdk")
+            ctx.topology.instantiate_vf_ports()
+        if ctx.sut_node.cryptodevs and test_run.config.crypto:
+            ctx.topology.instantiate_crypto_ports()
+            ctx.topology.bind_cryptodevs("dpdk")

-        test_run.ctx.topology.configure_ports("sut", "dpdk")
-        if test_run.ctx.func_tg:
-            test_run.ctx.func_tg.setup(test_run.ctx.topology)
-        if test_run.ctx.perf_tg:
-            test_run.ctx.perf_tg.setup(test_run.ctx.topology)
+        ctx.topology.configure_ports("sut", "dpdk")
+        if ctx.func_tg and ctx.topology.type is not LinkTopology.NO_LINK:
+            ctx.func_tg.setup(ctx.topology)
+        if ctx.perf_tg and ctx.topology.type is not LinkTopology.NO_LINK:
+            ctx.perf_tg.setup(ctx.topology)

         self.result.ports = [
-            port.to_dict()
-            for port in test_run.ctx.topology.sut_ports + test_run.ctx.topology.tg_ports
+            port.to_dict() for port in ctx.topology.sut_ports + ctx.topology.tg_ports
         ]
-        self.result.sut_session_info = test_run.ctx.sut_node.node_info
-        self.result.dpdk_build_info = test_run.ctx.dpdk_build.get_dpdk_build_info()
+        self.result.sut_session_info = ctx.sut_node.node_info
+        self.result.dpdk_build_info = ctx.dpdk_build.get_dpdk_build_info()

         self.logger.debug(f"Found capabilities to check: {test_run.required_capabilities}")
         test_run.supported_capabilities = get_supported_capabilities(
-            test_run.ctx.sut_node, test_run.ctx.topology, test_run.required_capabilities
+            ctx.sut_node, ctx.topology, test_run.required_capabilities
         )
         return TestRunExecution(test_run, self.result)

@@ -443,20 +454,22 @@ def description(self) -> str:

     def next(self) -> State | None:
         """Next state."""
+        ctx = self.test_run.ctx
         if self.test_run.config.use_virtual_functions:
-            self.test_run.ctx.topology.delete_vf_ports()
-        if self.test_run.ctx.sut_node.cryptodevs:
-            self.test_run.ctx.topology.delete_crypto_vf_ports()
-
-        self.test_run.ctx.shell_pool.terminate_current_pool()
-        if self.test_run.ctx.func_tg and self.test_run.ctx.func_tg.is_setup:
-            self.test_run.ctx.func_tg.teardown()
-        if self.test_run.ctx.perf_tg and self.test_run.ctx.perf_tg.is_setup:
-            self.test_run.ctx.perf_tg.teardown()
-        self.test_run.ctx.topology.teardown()
-        self.test_run.ctx.dpdk.teardown()
-        self.test_run.ctx.tg_node.teardown()
-        self.test_run.ctx.sut_node.teardown()
+            ctx.topology.delete_vf_ports()
+        if ctx.sut_node.cryptodevs:
+            ctx.topology.delete_crypto_vf_ports()
+
+        ctx.shell_pool.terminate_current_pool()
+        if ctx.func_tg is not None and ctx.func_tg.is_setup:
+            ctx.func_tg.teardown()
+        if ctx.perf_tg is not None and ctx.perf_tg.is_setup:
+            ctx.perf_tg.teardown()
+        ctx.topology.teardown()
+        ctx.dpdk.teardown()
+        if ctx.topology.type is not LinkTopology.NO_LINK and ctx.tg_node is not None:
+            ctx.tg_node.teardown()
+        ctx.sut_node.teardown()
         return None

     def on_error(self, ex: BaseException) -> State | None:
diff --git a/dts/framework/testbed_model/topology.py b/dts/framework/testbed_model/topology.py
index 34862c4d2e..1db444fc01 100644
--- a/dts/framework/testbed_model/topology.py
+++ b/dts/framework/testbed_model/topology.py
@@ -73,6 +73,8 @@ def from_port_links(cls, port_links: Iterator[PortLink]) -> Self:
             ConfigurationError: If an unsupported link topology is supplied.
         """
         type = LinkTopology.NO_LINK
+        sut_ports = []
+        tg_ports = []

         if port_link := next(port_links, None):
             type = LinkTopology.ONE_LINK
@@ -103,10 +105,12 @@ def node_and_ports_from_id(self, node_identifier: NodeIdentifier) -> tuple[Node,
             case "sut":
                 return ctx.sut_node, self.sut_ports
             case "tg":
-                return ctx.tg_node, self.tg_ports
-            case _:
-                msg = f"Invalid node `{node_identifier}` given."
+                if ctx.tg_node is not None:
+                    return ctx.tg_node, self.tg_ports
+                msg = "node tg does not exist with current topology."
                 raise InternalError(msg)
+        msg = f"Invalid node `{node_identifier}` given."
+        raise InternalError(msg)

     def get_crypto_vfs(self, num_vfs: int) -> list[Port]:
         """Retrieve virtual functions from active crypto vfs.
@@ -139,6 +143,8 @@ def setup(self) -> None:

         Binds all the ports to the right kernel driver to retrieve MAC addresses and logical names.
         """
+        if self.type is LinkTopology.NO_LINK:
+            return
         self._prepare_devbind_script()
         self._setup_ports("sut")
         self._setup_ports("tg")
@@ -148,6 +154,8 @@ def teardown(self) -> None:

         Restores all the ports to their original drivers before the test run.
         """
+        if self.type is LinkTopology.NO_LINK:
+            return
         self._restore_ports_original_drivers("sut")
         self._restore_ports_original_drivers("tg")

@@ -338,7 +346,8 @@ def prepare_node(node: Node) -> None:
             node.main_session.devbind_script_path = devbind_script_path

         ctx = get_ctx()
-        prepare_node(ctx.tg_node)
+        if ctx.tg_node:
+            prepare_node(ctx.tg_node)
         prepare_node(ctx.sut_node)

     @property
--
2.54.0


^ permalink raw reply related

* [PATCH v5 2/2] dts: add build arguments to test run configuration
From: Koushik Bhargav Nimoji @ 2026-07-09 17:28 UTC (permalink / raw)
  To: luca.vizzarro, patrickrobb1997
  Cc: dev, abailey, ahassick, lylavoie, Koushik Bhargav Nimoji
In-Reply-To: <20260709172823.972163-1-knimoji@iol.unh.edu>

This patch adds the ability to specify build arguments when building DPDK
through DTS. Doing so allows users to build DPDK with the desired build
arguments, which allows for a more configurable DTS run.

Signed-off-by: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
---
v5:
    *Resolved an issue where existing build arguments get overwritten
     by those specified in the test_run.yaml config file
---
 dts/configurations/test_run.example.yaml | 13 +++++++++++++
 dts/framework/config/test_run.py         |  2 ++
 dts/framework/remote_session/dpdk.py     | 16 ++++++++++------
 dts/framework/utils.py                   | 21 ++++++++++++++++++++-
 4 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/dts/configurations/test_run.example.yaml b/dts/configurations/test_run.example.yaml
index ee641f5dce..0bd5151801 100644
--- a/dts/configurations/test_run.example.yaml
+++ b/dts/configurations/test_run.example.yaml
@@ -16,6 +16,8 @@
 #       `precompiled_build_dir` or `build_options` can be defined, but not both.
 #   `compiler_wrapper`:
 #       Optional, adds a compiler wrapper if present.
+#   `build_args`:
+#       The additional build arguments to be used when building DPDK.
 #   `func_traffic_generator` & `perf_traffic_generator`:
 #       Define `func_traffic_generator` when `func` set to true.
 #       Define `perf_traffic_generator` when `perf` set to true.
@@ -40,6 +42,17 @@ dpdk:
       # the combination of the following two makes CC="ccache gcc"
       compiler: gcc
       compiler_wrapper: ccache # see `Optional Fields`
+      # arguments to be used when building DPDK
+      # build_args:
+      #   c_args:
+      #     - O3
+      #     - g
+      #   b_coverage:
+      #     - "true"
+      #   buildtype:
+      #     - release
+      #   flags:
+      #     - strip
 func_traffic_generator:
   type: SCAPY
 # perf_traffic_generator:
diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py
index 3cd643981d..5968b953a9 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -191,6 +191,8 @@ class DPDKBuildOptionsConfiguration(FrozenModel):
     #: This string will be put in front of the compiler when executing the build. Useful for adding
     #: wrapper commands, such as ``ccache``.
     compiler_wrapper: str = ""
+    #: The build arguments to build dpdk with
+    build_args: dict[str, list[str]] = {}
 
 
 class DPDKUncompiledBuildConfiguration(BaseDPDKBuildConfiguration):
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index 31610f14c4..f822033f95 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -108,8 +108,8 @@ def setup(self) -> None:
                         "Cannot create code coverage report using a precompiled build directory."
                     )
                 self._set_remote_dpdk_build_dir(build_dir)
-            case DPDKUncompiledBuildConfiguration(build_options=build_options):
-                self._configure_dpdk_build(build_options)
+            case DPDKUncompiledBuildConfiguration():
+                self._configure_dpdk_build(self.config.build_options)
                 self._build_dpdk()
 
     def teardown(self) -> None:
@@ -289,16 +289,20 @@ def _build_dpdk(self) -> None:
         `remote_dpdk_tree_path` has already been set on the SUT node.
         """
         ctx = get_ctx()
+        build_options = getattr(self.config, "build_options")
         # If the SUT is an ice driver device, make sure to build with 16B descriptors.
         if (
             ctx.topology.sut_port_ingress
             and ctx.topology.sut_port_ingress.config.os_driver == "ice"
         ):
-            meson_args = MesonArgs(
-                default_library="static", libdir="lib", c_args="-DRTE_NET_INTEL_USE_16BYTE_DESC"
-            )
+            if "c_args" in build_options.build_args:
+                build_options.build_args["c_args"].append("DRTE_NET_INTEL_USE_16BYTE_DESC")
+            else:
+                build_options.build_args["c_args"] = ["DRTE_NET_INTEL_USE_16BYTE_DESC"]
+
+            meson_args = MesonArgs(build_options.build_args, default_library="static", libdir="lib")
         else:
-            meson_args = MesonArgs(default_library="static", libdir="lib")
+            meson_args = MesonArgs(build_options.build_args, default_library="static", libdir="lib")
 
         if SETTINGS.code_coverage:
             meson_args._add_arg("-Db_coverage=true")
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 55d6dfc337..0e41f3f48c 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -101,10 +101,16 @@ class MesonArgs:
 
     _default_library: str
 
-    def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):
+    def __init__(
+        self,
+        dpdk_build_args: dict[str, list[str]],
+        default_library: str | None = None,
+        **dpdk_args: str | bool,
+    ):
         """Initialize the meson arguments.
 
         Args:
+            dpdk_build_args: The DPDK build arguments specified in the test run configuration file.
             default_library: The default library type, Meson supports ``shared``, ``static`` and
                 ``both``. Defaults to :data:`None`, in which case the argument won't be used.
             dpdk_args: The arguments found in ``meson_options.txt`` in root DPDK directory.
@@ -123,6 +129,19 @@ def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):
             )
         )
 
+        arguments = []
+        for option, value in dpdk_build_args.items():
+            if option == "c_args":
+                values = " ".join(f"-{val}" for val in value)
+                arguments.append(f'-D{option}="{values}"')
+            elif option == "flags":
+                values = " ".join(f"--{val}" for val in value)
+                arguments.append(values)
+            else:
+                arguments.append(f" -D{option}={value[0]}")
+
+        self._dpdk_args = " ".join(f"{self._dpdk_args} {' '.join(arguments)}".split())
+
     def __str__(self) -> str:
         """The actual args."""
         return " ".join(f"{self._default_library} {self._dpdk_args}".split())
-- 
2.54.0


^ permalink raw reply related

* [PATCH v5 1/2] dts: add code coverage reporting to DTS
From: Koushik Bhargav Nimoji @ 2026-07-09 17:28 UTC (permalink / raw)
  To: luca.vizzarro, patrickrobb1997
  Cc: dev, abailey, ahassick, lylavoie, Koushik Bhargav Nimoji
In-Reply-To: <20260522154637.952588-1-knimoji@iol.unh.edu>

Previously, DTS had no code coverage. This patch adds a command line
argument in order to build DPDK with code coverage enabled. This allows
users to create and view code coverage reports of what code and functions
were called during a DTS run.

Signed-off-by: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
---
v2:
    *Fixed error in lcov/gcov tool detection
v3:
    *Fixed type hints and error message typos
v4:
    *Fixed documentation and docstring comments
    *Added a check to make sure code coverage is
     not enabled on a DTS run with a precompiled
     build directory
v5:
    *Fixed issue where teardown fails when code
     coverage report generation fails
---
 .mailmap                                      |  1 +
 doc/guides/tools/dts.rst                      | 18 +++++++++++
 dts/README.md                                 |  5 +++
 dts/framework/remote_session/dpdk.py          | 31 +++++++++++++++++++
 .../remote_session/remote_session.py          |  5 ++-
 dts/framework/settings.py                     | 10 ++++++
 dts/framework/testbed_model/os_session.py     | 10 ++++++
 dts/framework/testbed_model/posix_session.py  | 22 +++++++++++++
 dts/framework/utils.py                        |  8 +++++
 9 files changed, 109 insertions(+), 1 deletion(-)

diff --git a/.mailmap b/.mailmap
index 05a55c0bd6..ed1553c6c3 100644
--- a/.mailmap
+++ b/.mailmap
@@ -887,6 +887,7 @@ Klaus Degner <kd@allegro-packets.com>
 Kommula Shiva Shankar <kshankar@marvell.com>
 Konstantin Ananyev <konstantin.ananyev@huawei.com> <konstantin.v.ananyev@yandex.ru>
 Konstantin Ananyev <konstantin.ananyev@huawei.com> <konstantin.ananyev@intel.com>
+Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
 Krishna Murthy <krishna.j.murthy@intel.com>
 Krzysztof Galazka <krzysztof.galazka@intel.com>
 Krzysztof Kanas <kkanas@marvell.com> <krzysztof.kanas@caviumnetworks.com>
diff --git a/doc/guides/tools/dts.rst b/doc/guides/tools/dts.rst
index 5b9a348016..0ffebdc713 100644
--- a/doc/guides/tools/dts.rst
+++ b/doc/guides/tools/dts.rst
@@ -352,6 +352,10 @@ DTS is run with ``main.py`` located in the ``dts`` directory using the ``poetry
      --precompiled-build-dir DIR_NAME
                            [DTS_PRECOMPILED_BUILD_DIR] Define the subdirectory under the DPDK tree root directory or tarball where the pre-
                            compiled binaries are located. (default: None)
+     --code-coverage       Builds DPDK on the SUT node with code coverage enabled. Generates a code coverage report which can be found on
+                           the DTS execution hosts local filesystem at dts/output/coverage_reports/meson-logs/coveragereport/index.html,
+                           or the specified output directory. To use code coverage, please ensure lcov v1.15 and gcov v8.0 or higher
+                           (included in gcc package) are installed on the SUT node.
 
 
 The brackets contain the names of environment variables that set the same thing.
@@ -367,6 +371,20 @@ Results are stored in the output dir by default
 which be changed with the ``--output-dir`` command line argument.
 The results contain basic statistics of passed/failed test cases and DPDK version.
 
+Code Coverage
+~~~~~~~~~~~~~
+
+DTS has the ablilty to track code usage during test runs, and generate an HTML
+coverage report which shows the coverage percentage for the various DPDK
+libraries and drivers utilized during execution. The DPDK build directory must
+be compiled on the SUT node, as a pre-built build directory may not be properly
+configured for code coverage. Code coverage can be enabled by using the
+"--code-coverage" CLI parameter when running DTS.
+
+To use code coverage, please make sure the following dependencies are available
+on the SUT node:
+- lcov v1.15 or greater
+- gcov v8.0 or greater (included in gcc package)
 
 Contributing to DTS
 -------------------
diff --git a/dts/README.md b/dts/README.md
index d257b7a167..51f824e077 100644
--- a/dts/README.md
+++ b/dts/README.md
@@ -64,6 +64,11 @@ $ poetry run ./main.py
 These commands will give you a bash shell inside a docker container
 with all DTS Python dependencies installed.
 
+# Code Coverage
+
+To generate code coverage reports, ensure the SUT has lcov v1.15 and gcov v8.0 or greater
+installed, and that DTS is run using the '--code-coverage' argument.
+
 ## Visual Studio Code
 
 Usage of VScode devcontainers is NOT required for developing on DTS and running DTS,
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index c3575cfcaf..31610f14c4 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -29,6 +29,7 @@
 from framework.logger import DTSLogger, get_dts_logger
 from framework.params.eal import EalParams
 from framework.remote_session.remote_session import CommandResult
+from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCore, LogicalCoreCount, LogicalCoreList, lcore_filter
 from framework.testbed_model.node import Node
 from framework.testbed_model.os_session import OSSession
@@ -80,6 +81,10 @@ def setup(self) -> None:
         DPDK setup includes setting all internals needed for the build, the copying of DPDK
         sources and then building DPDK or using the exist ones from the `dpdk_location`. The drivers
         are bound to those that DPDK needs.
+
+        Raises:
+            ConfigurationError: When DTS is run with code coverage enabled, but is also provided
+            a precompiled build directory.
         """
         if not isinstance(self.config.dpdk_location, RemoteDPDKTreeLocation):
             self._node.main_session.create_directory(self.remote_dpdk_tree_path)
@@ -98,6 +103,10 @@ def setup(self) -> None:
 
         match self.config:
             case DPDKPrecompiledBuildConfiguration(precompiled_build_dir=build_dir):
+                if SETTINGS.code_coverage:
+                    raise ConfigurationError(
+                        "Cannot create code coverage report using a precompiled build directory."
+                    )
                 self._set_remote_dpdk_build_dir(build_dir)
             case DPDKUncompiledBuildConfiguration(build_options=build_options):
                 self._configure_dpdk_build(build_options)
@@ -107,7 +116,26 @@ def teardown(self) -> None:
         """Teardown the DPDK build on the target node.
 
         Removes the DPDK tree and/or build directory/tarball depending on the configuration.
+        If code coverage is enabled, the coverage report and .info file are generated and
+        copied onto the local filesystem before teardown.
         """
+        try:
+            if SETTINGS.code_coverage:
+                report_folder = PurePath(self.remote_dpdk_build_dir / "meson-logs")
+                output_dir = SETTINGS.output_dir
+                Path(output_dir).mkdir(parents=True, exist_ok=True)
+
+                coverage_status = self._session.generate_coverage_report(self.remote_dpdk_build_dir)
+                if coverage_status:
+                    self._session.copy_dir_from(report_folder, output_dir)
+                    self._logger.info(
+                        "Coverage HTML report generated, "
+                        f"available at {output_dir}/meson-logs/coveragereport/index.html"
+                    )
+
+        except Exception as e:
+            self._logger.info(f"Unable to create code coverage report due to an error: {e}")
+
         match self.config.dpdk_location:
             case LocalDPDKTreeLocation():
                 self._node.main_session.remove_remote_dir(self.remote_dpdk_tree_path)
@@ -272,6 +300,9 @@ def _build_dpdk(self) -> None:
         else:
             meson_args = MesonArgs(default_library="static", libdir="lib")
 
+        if SETTINGS.code_coverage:
+            meson_args._add_arg("-Db_coverage=true")
+
         self._session.build_dpdk(
             self._env_vars,
             meson_args,
diff --git a/dts/framework/remote_session/remote_session.py b/dts/framework/remote_session/remote_session.py
index fb5f6fedf5..cc1f1f6a4f 100644
--- a/dts/framework/remote_session/remote_session.py
+++ b/dts/framework/remote_session/remote_session.py
@@ -250,7 +250,10 @@ def copy_from(self, source_file: str | PurePath, destination_dir: str | Path) ->
             destination_dir: The directory path on the local filesystem where the `source_file`
                 will be saved.
         """
-        self.session.get(str(source_file), str(destination_dir))
+        source_file = PurePath(source_file)
+        destination_dir = Path(destination_dir)
+        local_path = destination_dir / source_file.name
+        self.session.get(str(source_file), str(local_path))
 
     def copy_to(self, source_file: str | Path, destination_dir: str | PurePath) -> None:
         """Copy a file from local filesystem to the remote Node.
diff --git a/dts/framework/settings.py b/dts/framework/settings.py
index f329677804..6e2fc78b78 100644
--- a/dts/framework/settings.py
+++ b/dts/framework/settings.py
@@ -159,6 +159,8 @@ class Settings:
     re_run: int = 0
     #:
     random_seed: int | None = None
+    #:
+    code_coverage: bool = False
 
 
 SETTINGS: Settings = Settings()
@@ -489,6 +491,14 @@ def _get_parser() -> _DTSArgumentParser:
     )
     _add_env_var_to_action(action)
 
+    action = parser.add_argument(
+        "--code-coverage",
+        action="store_true",
+        default=False,
+        help="Used to build DPDK with code coverage enabled.",
+    )
+    _add_env_var_to_action(action)
+
     return parser
 
 
diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py
index f2dc9b20a9..c2874051a7 100644
--- a/dts/framework/testbed_model/os_session.py
+++ b/dts/framework/testbed_model/os_session.py
@@ -480,6 +480,16 @@ def build_dpdk(
             timeout: Wait at most this long in seconds for the build execution to complete.
         """
 
+    @abstractmethod
+    def generate_coverage_report(self, remote_build_dir: PurePath | None) -> bool:
+        """Generates a code coverage report for a DTS run.
+
+        Args:
+            remote_build_dir: The remote DPDK build directory
+        Returns:
+            Whether the coverage report was able to be created or not.
+        """
+
     @abstractmethod
     def get_dpdk_version(self, version_path: str | PurePath) -> str:
         """Inspect the DPDK version on the remote node.
diff --git a/dts/framework/testbed_model/posix_session.py b/dts/framework/testbed_model/posix_session.py
index dec952685a..0a281f658c 100644
--- a/dts/framework/testbed_model/posix_session.py
+++ b/dts/framework/testbed_model/posix_session.py
@@ -295,6 +295,28 @@ def build_dpdk(
         except RemoteCommandExecutionError as e:
             raise DPDKBuildError(f"DPDK build failed when doing '{e.command}'.")
 
+    def generate_coverage_report(self, remote_build_dir: PurePath | None) -> bool:
+        """Overrides :meth:`~.os_session.OSSession.generate_coverage_report`."""
+        command_result = self.send_command(r"lcov --version | grep -oP '\d+\.\d+'")
+        lcov_version = float(
+            command_result.stdout if command_result.return_code == 0 and command_result else -1
+        )
+        command_result = self.send_command(
+            r"gcov --version | head -n 1 | grep -oP '\d+\.\d+' | tail -n 1"
+        )
+        gcov_version = float(
+            command_result.stdout if command_result.return_code == 0 and command_result else -1
+        )
+
+        if lcov_version >= 1.15 and gcov_version >= 8.0:
+            self.send_command(f"ninja -C {remote_build_dir} coverage-html", timeout=600)
+            return True
+        else:
+            self._logger.info(
+                "Unable to generate code coverage report, ensure at least lcov v1.15 and gcov v8.0"
+            )
+            return False
+
     def get_dpdk_version(self, build_dir: str | PurePath) -> str:
         """Overrides :meth:`~.os_session.OSSession.get_dpdk_version`."""
         out = self.send_command(f"cat {self.join_remote_path(build_dir, 'VERSION')}", verify=True)
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 5753c1b7fe..55d6dfc337 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -127,6 +127,14 @@ def __str__(self) -> str:
         """The actual args."""
         return " ".join(f"{self._default_library} {self._dpdk_args}".split())
 
+    def _add_arg(self, arg: str):
+        """Adds an argument to the meson setup command.
+
+        Args:
+            arg: The meson build argument to be added.
+        """
+        self._dpdk_args = self._dpdk_args + " " + arg
+
 
 class TarCompressionFormat(StrEnum):
     """Compression formats that tar can use.
-- 
2.54.0


^ permalink raw reply related

* RE: Inquiry about status of accepted patch net/bnxt/tf_core: fix null deref on pool use allocation
From: Kishore Padmanabha @ 2026-07-09 16:09 UTC (permalink / raw)
  To: 20260603041557.115956-1-denserg.edu
  Cc: dev, Ajit Kumar Khaparde, stable, denserg.edu
In-Reply-To: <EAYPR13MB7316899D71B96849EDFB8BC0B4F2F02@EAYPR13MB731689.namprd13.prod.outlook.com>

[-- Attachment #1: Type: text/plain, Size: 823 bytes --]

Hi Simakov,

Will get back once I check with the maintainers.

Thanks,
Kishore


-----Original Message-----
From: Simakov Aleksey <bigalex934@gmail.com>
Sent: Tuesday, July 7, 2026 4:05 AM
To: kishore.padmanabha@broadcom.com
Cc: dev@dpdk.org; ajit.khaparde@broadcom.com; stable@dpdk.org;
denserg.edu@gmail.com
Subject: Inquiry about status of accepted patch net/bnxt/tf_core: fix null
deref on pool use allocation

Hello!

I hope this message finds you well.

I am writing to inquire about the status of patch
https://patches.dpdk.org/project/dpdk/patch/20260603041557.115956-1-denser
g.edu@gmail.com/
It was marked as "Accepted" in Patchwork over a month ago, but I noticed
that it has not yet been merged into any of the DPDK branches.

Could you please clarify the current status of this patch?

Thank you for your time

[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5493 bytes --]

^ permalink raw reply

* [PATCH v4 10/10] net/mlx5: accept more unicast MAC addresses
From: David Marchand @ 2026-07-09 16:02 UTC (permalink / raw)
  To: dev
  Cc: rjarry, cfontain, Dariusz Sosnowski, Viacheslav Ovsiienko,
	Bing Zhao, Ori Kam, Suanming Mou, Matan Azrad
In-Reply-To: <20260709160247.1798575-1-david.marchand@redhat.com>

Starting firmware version 22.49.1014, the number of mac addresses
per VF is not capped to 128 anymore.

The value can be increased via devlink:
$ devlink dev param set pci/0000:3b:00.2 name max_macs value 4096 \
	cmode driverinit
$ devlink dev reload pci/0000:3b:00.2

On the DPDK side, we must retrieve the maximum number of unicast
and multicast addresses supported with a query to the firmware.

Then, dynamically allocate the mac addresses arrays and report the
limit instead of the previous hardcoded value.

Signed-off-by: David Marchand <david.marchand@redhat.com>
---
 drivers/common/mlx5/mlx5_devx_cmds.c |  4 +++
 drivers/common/mlx5/mlx5_devx_cmds.h |  2 ++
 drivers/net/mlx5/linux/mlx5_os.c     | 42 ++++++++++++++++++++++------
 drivers/net/mlx5/mlx5.c              |  9 ++----
 drivers/net/mlx5/mlx5.h              |  8 ++++--
 drivers/net/mlx5/mlx5_ethdev.c       |  2 +-
 drivers/net/mlx5/mlx5_mac.c          | 22 +++++++++------
 drivers/net/mlx5/mlx5_trigger.c      | 10 +++----
 drivers/net/mlx5/windows/mlx5_os.c   | 40 ++++++++++++++++++++------
 9 files changed, 99 insertions(+), 40 deletions(-)

diff --git a/drivers/common/mlx5/mlx5_devx_cmds.c b/drivers/common/mlx5/mlx5_devx_cmds.c
index 140b057ab4..e5d9c92779 100644
--- a/drivers/common/mlx5/mlx5_devx_cmds.c
+++ b/drivers/common/mlx5/mlx5_devx_cmds.c
@@ -1110,6 +1110,10 @@ mlx5_devx_cmd_query_hca_attr(void *ctx,
 	attr->log_max_pd = MLX5_GET(cmd_hca_cap, hcattr, log_max_pd);
 	attr->log_max_srq = MLX5_GET(cmd_hca_cap, hcattr, log_max_srq);
 	attr->log_max_srq_sz = MLX5_GET(cmd_hca_cap, hcattr, log_max_srq_sz);
+	attr->log_max_current_uc_list = MLX5_GET(cmd_hca_cap, hcattr,
+						 log_max_current_uc_list);
+	attr->log_max_current_mc_list = MLX5_GET(cmd_hca_cap, hcattr,
+						 log_max_current_mc_list);
 	attr->reg_c_preserve =
 		MLX5_GET(cmd_hca_cap, hcattr, reg_c_preserve);
 	attr->mmo_regex_qp_en = MLX5_GET(cmd_hca_cap, hcattr, regexp_mmo_qp);
diff --git a/drivers/common/mlx5/mlx5_devx_cmds.h b/drivers/common/mlx5/mlx5_devx_cmds.h
index 90beb2e9e6..7fe89bc6a4 100644
--- a/drivers/common/mlx5/mlx5_devx_cmds.h
+++ b/drivers/common/mlx5/mlx5_devx_cmds.h
@@ -356,6 +356,8 @@ struct mlx5_hca_attr {
 	uint8_t tx_sw_owner_v2:1;
 	uint8_t esw_sw_owner:1;
 	uint8_t esw_sw_owner_v2:1;
+	uint32_t log_max_current_uc_list:5;
+	uint32_t log_max_current_mc_list:5;
 };
 
 /* LAG Context. */
diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
index 1cad6e1091..23632f55af 100644
--- a/drivers/net/mlx5/linux/mlx5_os.c
+++ b/drivers/net/mlx5/linux/mlx5_os.c
@@ -389,6 +389,16 @@ mlx5_os_capabilities_prepare(struct mlx5_dev_ctx_shared *sh)
 	sh->dev_cap.esw_info.regc_mask = 0;
 #endif
 	sh->dev_cap.esw_info.is_set = 1;
+	if (hca_attr->log_max_current_uc_list > 0)
+		sh->dev_cap.max_uc_mac_addrs = 1u << hca_attr->log_max_current_uc_list;
+	else
+		sh->dev_cap.max_uc_mac_addrs = MLX5_MAX_UC_MAC_ADDRESSES;
+	if (hca_attr->log_max_current_mc_list > 0)
+		sh->dev_cap.max_mc_mac_addrs = 1u << hca_attr->log_max_current_mc_list;
+	else
+		sh->dev_cap.max_mc_mac_addrs = MLX5_MAX_MC_MAC_ADDRESSES;
+	sh->dev_cap.max_mac_addrs =
+		sh->dev_cap.max_uc_mac_addrs + sh->dev_cap.max_mc_mac_addrs;
 	return 0;
 }
 
@@ -1464,6 +1474,22 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 	priv->sh = sh;
 	priv->dev_port = spawn->phys_port;
 	priv->pci_dev = spawn->pci_dev;
+	priv->mac = mlx5_malloc(MLX5_MEM_ZERO | MLX5_MEM_RTE,
+				sizeof(*priv->mac) * sh->dev_cap.max_mac_addrs,
+				RTE_CACHE_LINE_SIZE, SOCKET_ID_ANY);
+	if (priv->mac == NULL) {
+		DRV_LOG(ERR, "Failed to allocate MAC address array.");
+		err = ENOMEM;
+		goto error;
+	}
+	priv->mac_own = mlx5_malloc(MLX5_MEM_ZERO | MLX5_MEM_RTE,
+				    RTE_BITSET_SIZE(sh->dev_cap.max_mac_addrs),
+				    RTE_CACHE_LINE_SIZE, SOCKET_ID_ANY);
+	if (priv->mac_own == NULL) {
+		DRV_LOG(ERR, "Failed to allocate MAC ownership bitmap.");
+		err = ENOMEM;
+		goto error;
+	}
 	/* Some internal functions rely on Netlink sockets, open them now. */
 	priv->nl_socket_rdma = nl_rdma;
 	priv->nl_socket_route =	mlx5_nl_init(NETLINK_ROUTE, 0);
@@ -1762,8 +1788,8 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 		mlx5_nl_mac_addr_sync(priv->nl_socket_route,
 				      mlx5_ifindex(eth_dev),
 				      eth_dev->data->mac_addrs,
-				      MLX5_MAX_UC_MAC_ADDRESSES,
-				      MLX5_MAX_MAC_ADDRESSES);
+				      sh->dev_cap.max_mac_addrs,
+				      sh->dev_cap.max_uc_mac_addrs);
 	priv->ctrl_flows = 0;
 	rte_spinlock_init(&priv->flow_list_lock);
 	TAILQ_INIT(&priv->flow_meters);
@@ -1963,17 +1989,15 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 			mlx5_flex_item_port_cleanup(eth_dev);
 		mlx5_free(priv->ext_rxqs);
 		mlx5_free(priv->ext_txqs);
+		mlx5_free(priv->mac);
+		eth_dev->data->mac_addrs = NULL;
+		mlx5_free(priv->mac_own);
 		mlx5_free(priv);
 		if (eth_dev != NULL)
 			eth_dev->data->dev_private = NULL;
 	}
-	if (eth_dev != NULL) {
-		/* mac_addrs must not be freed alone because part of
-		 * dev_private
-		 **/
-		eth_dev->data->mac_addrs = NULL;
+	if (eth_dev != NULL)
 		rte_eth_dev_release_port(eth_dev);
-	}
 	if (sh)
 		mlx5_free_shared_dev_ctx(sh);
 	if (nl_rdma >= 0)
@@ -3497,7 +3521,7 @@ mlx5_os_mac_addr_flush(struct rte_eth_dev *dev)
 	const int vf = priv->sh->dev_cap.vf;
 	int i;
 
-	for (i = MLX5_MAX_MAC_ADDRESSES - 1; i >= 0; --i) {
+	for (i = priv->sh->dev_cap.max_mac_addrs - 1; i >= 0; --i) {
 		if (rte_bitset_test(priv->mac_own, i)) {
 			if (vf)
 				mlx5_nl_mac_addr_remove(priv->nl_socket_route,
diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index 61c26d1206..1323ba49ac 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -2546,6 +2546,9 @@ mlx5_dev_close(struct rte_eth_dev *dev)
 		mlx5_list_destroy(priv->hrxqs);
 	mlx5_free(priv->ext_rxqs);
 	mlx5_free(priv->ext_txqs);
+	mlx5_free(priv->mac);
+	dev->data->mac_addrs = NULL;
+	mlx5_free(priv->mac_own);
 	sh->port[priv->dev_port - 1].nl_ih_port_id = RTE_MAX_ETHPORTS;
 	/*
 	 * The interrupt handler port id must be reset before priv is reset
@@ -2580,12 +2583,6 @@ mlx5_dev_close(struct rte_eth_dev *dev)
 	mlx5_flow_pools_destroy(priv);
 	memset(priv, 0, sizeof(*priv));
 	priv->domain_id = RTE_ETH_DEV_SWITCH_DOMAIN_ID_INVALID;
-	/*
-	 * Reset mac_addrs to NULL such that it is not freed as part of
-	 * rte_eth_dev_release_port(). mac_addrs is part of dev_private so
-	 * it is freed when dev_private is freed.
-	 */
-	dev->data->mac_addrs = NULL;
 	return 0;
 }
 
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index f7ba8df108..1fcc40fade 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -217,6 +217,9 @@ struct mlx5_dev_cap {
 	} mprq; /* Capability for Multi-Packet RQ. */
 	char fw_ver[64]; /* Firmware version of this device. */
 	struct flow_hw_port_info esw_info; /* E-switch manager reg_c0. */
+	uint16_t max_uc_mac_addrs; /* Maximum unicast MAC addresses. */
+	uint16_t max_mc_mac_addrs; /* Maximum multicast MAC addresses. */
+	uint16_t max_mac_addrs; /* Total maximum MAC addresses. */
 };
 
 #define MLX5_MPESW_PORT_INVALID (-1)
@@ -2018,9 +2021,8 @@ struct mlx5_priv {
 	struct mlx5_dev_ctx_shared *sh; /* Shared device context. */
 	uint32_t dev_port; /* Device port number. */
 	struct rte_pci_device *pci_dev; /* Backend PCI device. */
-	struct rte_ether_addr mac[MLX5_MAX_MAC_ADDRESSES]; /* MAC addresses. */
-	RTE_BITSET_DECLARE(mac_own, MLX5_MAX_MAC_ADDRESSES);
-	/* Bit-field of MAC addresses owned by the PMD. */
+	struct rte_ether_addr *mac; /* MAC addresses. */
+	uint64_t *mac_own; /* Bit-field of MAC addresses owned by the PMD. */
 	uint16_t vlan_filter[MLX5_MAX_VLAN_IDS]; /* VLAN filters table. */
 	unsigned int vlan_filter_n; /* Number of configured VLAN filters. */
 	/* Device properties. */
diff --git a/drivers/net/mlx5/mlx5_ethdev.c b/drivers/net/mlx5/mlx5_ethdev.c
index 8160d10e7e..306c1cd734 100644
--- a/drivers/net/mlx5/mlx5_ethdev.c
+++ b/drivers/net/mlx5/mlx5_ethdev.c
@@ -392,7 +392,7 @@ mlx5_dev_infos_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *info)
 	max = RTE_MIN(max, (unsigned int)UINT16_MAX);
 	info->max_rx_queues = max;
 	info->max_tx_queues = max;
-	info->max_mac_addrs = MLX5_MAX_UC_MAC_ADDRESSES;
+	info->max_mac_addrs = priv->sh->dev_cap.max_uc_mac_addrs;
 	info->rx_queue_offload_capa = mlx5_get_rx_queue_offloads(dev);
 	info->rx_seg_capa.max_nseg = MLX5_MAX_RXQ_NSEG;
 	info->rx_seg_capa.multi_pools = !priv->config.mprq.enabled;
diff --git a/drivers/net/mlx5/mlx5_mac.c b/drivers/net/mlx5/mlx5_mac.c
index 0e5d2be530..2ce7cfd407 100644
--- a/drivers/net/mlx5/mlx5_mac.c
+++ b/drivers/net/mlx5/mlx5_mac.c
@@ -36,7 +36,9 @@ mlx5_internal_mac_addr_remove(struct rte_eth_dev *dev,
 			      uint32_t index,
 			      struct rte_ether_addr *addr)
 {
-	MLX5_ASSERT(index < MLX5_MAX_MAC_ADDRESSES);
+	struct mlx5_priv *priv = dev->data->dev_private;
+
+	MLX5_ASSERT(index < priv->sh->dev_cap.max_mac_addrs);
 	if (rte_is_zero_ether_addr(&dev->data->mac_addrs[index]))
 		return false;
 	mlx5_os_mac_addr_remove(dev, index);
@@ -63,16 +65,17 @@ static int
 mlx5_internal_mac_addr_add(struct rte_eth_dev *dev, struct rte_ether_addr *mac,
 			   uint32_t index)
 {
+	struct mlx5_priv *priv = dev->data->dev_private;
 	unsigned int i;
 	int ret;
 
-	MLX5_ASSERT(index < MLX5_MAX_MAC_ADDRESSES);
+	MLX5_ASSERT(index < priv->sh->dev_cap.max_mac_addrs);
 	if (rte_is_zero_ether_addr(mac)) {
 		rte_errno = EINVAL;
 		return -rte_errno;
 	}
 	/* First, make sure this address isn't already configured. */
-	for (i = 0; (i != MLX5_MAX_MAC_ADDRESSES); ++i) {
+	for (i = 0; i != priv->sh->dev_cap.max_mac_addrs; ++i) {
 		/* Skip this index, it's going to be reconfigured. */
 		if (i == index)
 			continue;
@@ -101,10 +104,11 @@ mlx5_internal_mac_addr_add(struct rte_eth_dev *dev, struct rte_ether_addr *mac,
 void
 mlx5_mac_addr_remove(struct rte_eth_dev *dev, uint32_t index)
 {
+	struct mlx5_priv *priv = dev->data->dev_private;
 	struct rte_ether_addr addr = { 0 };
 	int ret;
 
-	if (index >= MLX5_MAX_UC_MAC_ADDRESSES)
+	if (index >= priv->sh->dev_cap.max_uc_mac_addrs)
 		return;
 	if (mlx5_internal_mac_addr_remove(dev, index, &addr)) {
 		ret = mlx5_traffic_mac_remove(dev, &addr);
@@ -133,9 +137,10 @@ int
 mlx5_mac_addr_add(struct rte_eth_dev *dev, struct rte_ether_addr *mac,
 		  uint32_t index, uint32_t vmdq __rte_unused)
 {
+	struct mlx5_priv *priv = dev->data->dev_private;
 	int ret;
 
-	if (index >= MLX5_MAX_UC_MAC_ADDRESSES) {
+	if (index >= priv->sh->dev_cap.max_uc_mac_addrs) {
 		rte_errno = EINVAL;
 		return -rte_errno;
 	}
@@ -217,16 +222,17 @@ int
 mlx5_set_mc_addr_list(struct rte_eth_dev *dev,
 		      struct rte_ether_addr *mc_addr_set, uint32_t nb_mc_addr)
 {
+	struct mlx5_priv *priv = dev->data->dev_private;
 	uint32_t i;
 	int ret;
 
-	if (nb_mc_addr >= MLX5_MAX_MC_MAC_ADDRESSES) {
+	if (nb_mc_addr >= priv->sh->dev_cap.max_mc_mac_addrs) {
 		rte_errno = ENOSPC;
 		return -rte_errno;
 	}
-	for (i = MLX5_MAX_UC_MAC_ADDRESSES; i != MLX5_MAX_MAC_ADDRESSES; ++i)
+	for (i = priv->sh->dev_cap.max_uc_mac_addrs; i != priv->sh->dev_cap.max_mac_addrs; ++i)
 		mlx5_internal_mac_addr_remove(dev, i, NULL);
-	i = MLX5_MAX_UC_MAC_ADDRESSES;
+	i = priv->sh->dev_cap.max_uc_mac_addrs;
 	while (nb_mc_addr--) {
 		ret = mlx5_internal_mac_addr_add(dev, mc_addr_set++, i++);
 		if (ret)
diff --git a/drivers/net/mlx5/mlx5_trigger.c b/drivers/net/mlx5/mlx5_trigger.c
index e41e4643e0..e535e3e5be 100644
--- a/drivers/net/mlx5/mlx5_trigger.c
+++ b/drivers/net/mlx5/mlx5_trigger.c
@@ -1902,7 +1902,7 @@ mlx5_traffic_enable(struct rte_eth_dev *dev)
 		}
 	}
 	/* Add MAC address flows. */
-	for (i = 0; i != MLX5_MAX_MAC_ADDRESSES; ++i) {
+	for (i = 0; i != priv->sh->dev_cap.max_mac_addrs; ++i) {
 		struct rte_ether_addr *mac = &dev->data->mac_addrs[i];
 
 		/* Add flows for unicast and multicast mac addresses added by API. */
@@ -2172,7 +2172,7 @@ mlx5_traffic_vlan_add(struct rte_eth_dev *dev, const uint16_t vid)
 		return 0;
 
 	/* Add all unicast DMAC flow rules with new VLAN attached. */
-	for (i = 0; i != MLX5_MAX_MAC_ADDRESSES; ++i) {
+	for (i = 0; i != priv->sh->dev_cap.max_mac_addrs; ++i) {
 		struct rte_ether_addr *mac = &dev->data->mac_addrs[i];
 
 		if (rte_is_zero_ether_addr(mac))
@@ -2189,7 +2189,7 @@ mlx5_traffic_vlan_add(struct rte_eth_dev *dev, const uint16_t vid)
 		 * Removing after creating VLAN rules so that traffic "gap" is not introduced.
 		 */
 
-		for (i = 0; i != MLX5_MAX_MAC_ADDRESSES; ++i) {
+		for (i = 0; i != priv->sh->dev_cap.max_mac_addrs; ++i) {
 			struct rte_ether_addr *mac = &dev->data->mac_addrs[i];
 
 			if (rte_is_zero_ether_addr(mac))
@@ -2227,7 +2227,7 @@ mlx5_traffic_vlan_remove(struct rte_eth_dev *dev, const uint16_t vid)
 		 * Recreating first to ensure no traffic "gap".
 		 */
 
-		for (i = 0; i != MLX5_MAX_MAC_ADDRESSES; ++i) {
+		for (i = 0; i != priv->sh->dev_cap.max_mac_addrs; ++i) {
 			struct rte_ether_addr *mac = &dev->data->mac_addrs[i];
 
 			if (rte_is_zero_ether_addr(mac))
@@ -2240,7 +2240,7 @@ mlx5_traffic_vlan_remove(struct rte_eth_dev *dev, const uint16_t vid)
 	}
 
 	/* Remove all unicast DMAC flow rules with this VLAN. */
-	for (i = 0; i != MLX5_MAX_MAC_ADDRESSES; ++i) {
+	for (i = 0; i != priv->sh->dev_cap.max_mac_addrs; ++i) {
 		struct rte_ether_addr *mac = &dev->data->mac_addrs[i];
 
 		if (rte_is_zero_ether_addr(mac))
diff --git a/drivers/net/mlx5/windows/mlx5_os.c b/drivers/net/mlx5/windows/mlx5_os.c
index 15de5c22a9..30d99b8b64 100644
--- a/drivers/net/mlx5/windows/mlx5_os.c
+++ b/drivers/net/mlx5/windows/mlx5_os.c
@@ -261,6 +261,16 @@ mlx5_os_capabilities_prepare(struct mlx5_dev_ctx_shared *sh)
 		 MLX5_GET(initial_seg, pv_iseg, fw_rev_subminor));
 	DRV_LOG(DEBUG, "Packet pacing is not supported.");
 	mlx5_rt_timestamp_config(sh, hca_attr);
+	if (hca_attr->log_max_current_uc_list > 0)
+		sh->dev_cap.max_uc_mac_addrs = 1u << hca_attr->log_max_current_uc_list;
+	else
+		sh->dev_cap.max_uc_mac_addrs = MLX5_MAX_UC_MAC_ADDRESSES;
+	if (hca_attr->log_max_current_mc_list > 0)
+		sh->dev_cap.max_mc_mac_addrs = 1u << hca_attr->log_max_current_mc_list;
+	else
+		sh->dev_cap.max_mc_mac_addrs = MLX5_MAX_MC_MAC_ADDRESSES;
+	sh->dev_cap.max_mac_addrs =
+		sh->dev_cap.max_uc_mac_addrs + sh->dev_cap.max_mc_mac_addrs;
 	return 0;
 }
 
@@ -396,6 +406,22 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 	priv->sh = sh;
 	priv->dev_port = spawn->phys_port;
 	priv->pci_dev = spawn->pci_dev;
+	priv->mac = mlx5_malloc(MLX5_MEM_ZERO | MLX5_MEM_RTE,
+				sizeof(*priv->mac) * sh->dev_cap.max_mac_addrs,
+				RTE_CACHE_LINE_SIZE, SOCKET_ID_ANY);
+	if (priv->mac == NULL) {
+		DRV_LOG(ERR, "Failed to allocate MAC address array.");
+		err = ENOMEM;
+		goto error;
+	}
+	priv->mac_own = mlx5_malloc(MLX5_MEM_ZERO | MLX5_MEM_RTE,
+				    RTE_BITSET_SIZE(sh->dev_cap.max_mac_addrs),
+				    RTE_CACHE_LINE_SIZE, SOCKET_ID_ANY);
+	if (priv->mac_own == NULL) {
+		DRV_LOG(ERR, "Failed to allocate MAC ownership bitmap.");
+		err = ENOMEM;
+		goto error;
+	}
 	priv->mp_id.port_id = port_id;
 	strlcpy(priv->mp_id.name, MLX5_MP_NAME, RTE_MP_MAX_NAME_LEN);
 	priv->representor = !!switch_info->representor;
@@ -612,17 +638,15 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 			mlx5_l3t_destroy(priv->mtr_profile_tbl);
 		if (own_domain_id)
 			claim_zero(rte_eth_switch_domain_free(priv->domain_id));
+		mlx5_free(priv->mac);
+		eth_dev->data->mac_addrs = NULL;
+		mlx5_free(priv->mac_own);
 		mlx5_free(priv);
 		if (eth_dev != NULL)
 			eth_dev->data->dev_private = NULL;
 	}
-	if (eth_dev != NULL) {
-		/* mac_addrs must not be freed alone because part of
-		 * dev_private
-		 **/
-		eth_dev->data->mac_addrs = NULL;
+	if (eth_dev != NULL)
 		rte_eth_dev_release_port(eth_dev);
-	}
 	if (sh)
 		mlx5_free_shared_dev_ctx(sh);
 	MLX5_ASSERT(err > 0);
@@ -698,7 +722,7 @@ mlx5_os_mac_addr_flush(struct rte_eth_dev *dev)
 	struct mlx5_priv *priv = dev->data->dev_private;
 	int i;
 
-	for (i = MLX5_MAX_MAC_ADDRESSES - 1; i >= 0; --i) {
+	for (i = priv->sh->dev_cap.max_mac_addrs - 1; i >= 0; --i) {
 		if (rte_bitset_test(priv->mac_own, i))
 			rte_bitset_clear(priv->mac_own, i);
 	}
@@ -718,7 +742,7 @@ mlx5_os_mac_addr_remove(struct rte_eth_dev *dev, uint32_t index)
 {
 	struct mlx5_priv *priv = dev->data->dev_private;
 
-	if (index < MLX5_MAX_MAC_ADDRESSES)
+	if (index < priv->sh->dev_cap.max_mac_addrs)
 		rte_bitset_clear(priv->mac_own, index);
 }
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH v4 09/10] net/mlx5: use bitset for tracking MAC addresses
From: David Marchand @ 2026-07-09 16:02 UTC (permalink / raw)
  To: dev
  Cc: rjarry, cfontain, Dariusz Sosnowski, Viacheslav Ovsiienko,
	Bing Zhao, Ori Kam, Suanming Mou, Matan Azrad
In-Reply-To: <20260709160247.1798575-1-david.marchand@redhat.com>

EAL provides bitset that does the same as this set of mlx5 macros.

Signed-off-by: David Marchand <david.marchand@redhat.com>
---
 drivers/common/mlx5/mlx5_common.h  | 16 ----------------
 drivers/net/mlx5/linux/mlx5_os.c   |  8 ++++----
 drivers/net/mlx5/mlx5.h            |  3 ++-
 drivers/net/mlx5/mlx5_trigger.c    |  2 +-
 drivers/net/mlx5/windows/mlx5_os.c |  8 ++++----
 5 files changed, 11 insertions(+), 26 deletions(-)

diff --git a/drivers/common/mlx5/mlx5_common.h b/drivers/common/mlx5/mlx5_common.h
index dbc06aff7e..71985794fa 100644
--- a/drivers/common/mlx5/mlx5_common.h
+++ b/drivers/common/mlx5/mlx5_common.h
@@ -30,22 +30,6 @@
 #define MLX5_PCI_DRIVER_NAME "mlx5_pci"
 #define MLX5_AUXILIARY_DRIVER_NAME "mlx5_auxiliary"
 
-/* Bit-field manipulation. */
-#define BITFIELD_DECLARE(bf, type, size) \
-	type bf[(((size_t)(size) / (sizeof(type) * CHAR_BIT)) + \
-		!!((size_t)(size) % (sizeof(type) * CHAR_BIT)))]
-#define BITFIELD_DEFINE(bf, type, size) \
-	BITFIELD_DECLARE((bf), type, (size)) = { 0 }
-#define BITFIELD_SET(bf, b) \
-	(void)((bf)[((b) / (sizeof((bf)[0]) * CHAR_BIT))] |= \
-		((size_t)1 << ((b) % (sizeof((bf)[0]) * CHAR_BIT))))
-#define BITFIELD_RESET(bf, b) \
-	(void)((bf)[((b) / (sizeof((bf)[0]) * CHAR_BIT))] &= \
-		~((size_t)1 << ((b) % (sizeof((bf)[0]) * CHAR_BIT))))
-#define BITFIELD_ISSET(bf, b) \
-	!!(((bf)[((b) / (sizeof((bf)[0]) * CHAR_BIT))] & \
-		((size_t)1 << ((b) % (sizeof((bf)[0]) * CHAR_BIT)))))
-
 /*
  * Helper macros to work around __VA_ARGS__ limitations in a C99 compliant
  * manner.
diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
index 5b6e45df2a..1cad6e1091 100644
--- a/drivers/net/mlx5/linux/mlx5_os.c
+++ b/drivers/net/mlx5/linux/mlx5_os.c
@@ -3384,7 +3384,7 @@ mlx5_os_mac_addr_remove(struct rte_eth_dev *dev, uint32_t index)
 		mlx5_nl_mac_addr_remove(priv->nl_socket_route,
 					mlx5_ifindex(dev),
 					&dev->data->mac_addrs[index]);
-	BITFIELD_RESET(priv->mac_own, index);
+	rte_bitset_clear(priv->mac_own, index);
 }
 
 /**
@@ -3413,7 +3413,7 @@ mlx5_os_mac_addr_add(struct rte_eth_dev *dev, struct rte_ether_addr *mac,
 					   mlx5_ifindex(dev),
 					   mac);
 	if (!ret)
-		BITFIELD_SET(priv->mac_own, index);
+		rte_bitset_set(priv->mac_own, index);
 
 	return ret;
 }
@@ -3498,12 +3498,12 @@ mlx5_os_mac_addr_flush(struct rte_eth_dev *dev)
 	int i;
 
 	for (i = MLX5_MAX_MAC_ADDRESSES - 1; i >= 0; --i) {
-		if (BITFIELD_ISSET(priv->mac_own, i)) {
+		if (rte_bitset_test(priv->mac_own, i)) {
 			if (vf)
 				mlx5_nl_mac_addr_remove(priv->nl_socket_route,
 							mlx5_ifindex(dev),
 							&dev->data->mac_addrs[i]);
-			BITFIELD_RESET(priv->mac_own, i);
+			rte_bitset_clear(priv->mac_own, i);
 		}
 	}
 }
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 3ad8bad02f..f7ba8df108 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -14,6 +14,7 @@
 
 #include <rte_pci.h>
 #include <rte_ether.h>
+#include <rte_bitset.h>
 #include <ethdev_driver.h>
 #include <rte_rwlock.h>
 #include <rte_interrupts.h>
@@ -2018,7 +2019,7 @@ struct mlx5_priv {
 	uint32_t dev_port; /* Device port number. */
 	struct rte_pci_device *pci_dev; /* Backend PCI device. */
 	struct rte_ether_addr mac[MLX5_MAX_MAC_ADDRESSES]; /* MAC addresses. */
-	BITFIELD_DECLARE(mac_own, uint64_t, MLX5_MAX_MAC_ADDRESSES);
+	RTE_BITSET_DECLARE(mac_own, MLX5_MAX_MAC_ADDRESSES);
 	/* Bit-field of MAC addresses owned by the PMD. */
 	uint16_t vlan_filter[MLX5_MAX_VLAN_IDS]; /* VLAN filters table. */
 	unsigned int vlan_filter_n; /* Number of configured VLAN filters. */
diff --git a/drivers/net/mlx5/mlx5_trigger.c b/drivers/net/mlx5/mlx5_trigger.c
index 25847c8ba2..e41e4643e0 100644
--- a/drivers/net/mlx5/mlx5_trigger.c
+++ b/drivers/net/mlx5/mlx5_trigger.c
@@ -1907,7 +1907,7 @@ mlx5_traffic_enable(struct rte_eth_dev *dev)
 
 		/* Add flows for unicast and multicast mac addresses added by API. */
 		if (!memcmp(mac, &cmp, sizeof(*mac)) ||
-		    !BITFIELD_ISSET(priv->mac_own, i) ||
+		    !rte_bitset_test(priv->mac_own, i) ||
 		    (dev->data->all_multicast && rte_is_multicast_ether_addr(mac)))
 			continue;
 		memcpy(&unicast.hdr.dst_addr.addr_bytes,
diff --git a/drivers/net/mlx5/windows/mlx5_os.c b/drivers/net/mlx5/windows/mlx5_os.c
index 9acfa8ec84..15de5c22a9 100644
--- a/drivers/net/mlx5/windows/mlx5_os.c
+++ b/drivers/net/mlx5/windows/mlx5_os.c
@@ -699,8 +699,8 @@ mlx5_os_mac_addr_flush(struct rte_eth_dev *dev)
 	int i;
 
 	for (i = MLX5_MAX_MAC_ADDRESSES - 1; i >= 0; --i) {
-		if (BITFIELD_ISSET(priv->mac_own, i))
-			BITFIELD_RESET(priv->mac_own, i);
+		if (rte_bitset_test(priv->mac_own, i))
+			rte_bitset_clear(priv->mac_own, i);
 	}
 }
 
@@ -719,7 +719,7 @@ mlx5_os_mac_addr_remove(struct rte_eth_dev *dev, uint32_t index)
 	struct mlx5_priv *priv = dev->data->dev_private;
 
 	if (index < MLX5_MAX_MAC_ADDRESSES)
-		BITFIELD_RESET(priv->mac_own, index);
+		rte_bitset_clear(priv->mac_own, index);
 }
 
 /**
@@ -757,7 +757,7 @@ mlx5_os_mac_addr_add(struct rte_eth_dev *dev, struct rte_ether_addr *mac,
 		return -ENOTSUP;
 	}
 	/* Mark this MAC address as owned by the PMD */
-	BITFIELD_SET(priv->mac_own, index);
+	rte_bitset_set(priv->mac_own, index);
 	return 0;
 }
 
-- 
2.54.0


^ 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