DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] fix pthread mutexes for multi-process
@ 2026-04-13 17:14 Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 1/6] ethdev: fix flow_ops_mutex " Stephen Hemminger
                   ` (9 more replies)
  0 siblings, 10 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Several drivers and the ethdev layer initialize pthread mutexes
in shared memory with default (process-private) attributes.
This is undefined behavior when secondary processes use them.

This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
All are on control paths (firmware mailbox, hotplug, flow ops,
PHY negotiation) where sleeping is acceptable.

See POSIX spec:
https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexattr_getpshared.html

Bugzilla ID: 662

Stephen Hemminger (6):
  ethdev: fix flow_ops_mutex for multi-process
  net/failsafe: fix hotplug_mutex for multi-process
  net/atlantic: fix mbox_mutex for multi-process
  net/axgbe: fix mutexes for multi-process
  net/bnxt: fix mutexes for multi-process
  net/hinic: fix mutexes for multi-process

 drivers/net/atlantic/atl_ethdev.c      | 14 +++++++++++++-
 drivers/net/axgbe/axgbe_ethdev.c       | 19 +++++++++++++++----
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
 drivers/net/bnxt/bnxt_util.h           |  2 ++
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 drivers/net/failsafe/failsafe.c        | 15 ++++++++++++---
 drivers/net/hinic/base/hinic_compat.h  | 13 ++++++++++++-
 lib/ethdev/ethdev_driver.c             | 18 +++++++++++++++++-
 12 files changed, 95 insertions(+), 19 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH 1/6] ethdev: fix flow_ops_mutex for multi-process
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
@ 2026-04-13 17:14 ` Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 2/6] net/failsafe: fix hotplug_mutex " Stephen Hemminger
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Thomas Monjalon, Andrew Rybchenko,
	Matan Azrad, Ajit Khaparde, Ori Kam, Suanming Mou

The flow_ops_mutex in rte_eth_dev_data is located in shared memory
that is accessed by both primary and secondary processes. However,
the mutex is initialized without PTHREAD_PROCESS_SHARED attribute,
which means synchronization between processes is undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

This bug has existed since the flow_ops_mutex was added and affects
any multi-process DPDK application using rte_flow APIs.

Bugzilla ID: 662
Fixes: 80d1a9aff7f6 ("ethdev: make flow API thread safe")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/ethdev/ethdev_driver.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/lib/ethdev/ethdev_driver.c b/lib/ethdev/ethdev_driver.c
index 2ec665be0f..d12395efe0 100644
--- a/lib/ethdev/ethdev_driver.c
+++ b/lib/ethdev/ethdev_driver.c
@@ -89,6 +89,22 @@ eth_dev_set_dummy_fops(struct rte_eth_dev *eth_dev)
 	eth_dev->recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy;
 }
 
+/*
+ * Initialize mutex for process-shared access.
+ * The rte_eth_dev_data structure is in shared memory, so any mutex
+ * protecting it must use PTHREAD_PROCESS_SHARED.
+ */
+static void
+eth_dev_flow_ops_mutex_init(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+
 RTE_EXPORT_INTERNAL_SYMBOL(rte_eth_dev_allocate)
 struct rte_eth_dev *
 rte_eth_dev_allocate(const char *name)
@@ -135,7 +151,7 @@ rte_eth_dev_allocate(const char *name)
 	eth_dev->data->port_id = port_id;
 	eth_dev->data->backer_port_id = RTE_MAX_ETHPORTS;
 	eth_dev->data->mtu = RTE_ETHER_MTU;
-	pthread_mutex_init(&eth_dev->data->flow_ops_mutex, NULL);
+	eth_dev_flow_ops_mutex_init(&eth_dev->data->flow_ops_mutex);
 	RTE_ASSERT(rte_eal_process_type() == RTE_PROC_PRIMARY);
 	eth_dev_shared_data->allocated_ports++;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH 2/6] net/failsafe: fix hotplug_mutex for multi-process
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 1/6] ethdev: fix flow_ops_mutex " Stephen Hemminger
@ 2026-04-13 17:14 ` Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 3/6] net/atlantic: fix mbox_mutex " Stephen Hemminger
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Gaetan Rivet, Matan Azrad

The failsafe driver supports secondary process attachment via
rte_eth_dev_attach_secondary() in rte_pmd_failsafe_probe(). The
hotplug_mutex in fs_priv protects shared state but is initialized
without PTHREAD_PROCESS_SHARED attribute.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures accessed by multiple processes,
pthread_mutexattr_setpshared() must be called with PTHREAD_PROCESS_SHARED.

Also add missing pthread_mutexattr_destroy() call to avoid resource leak.

Bugzilla ID: 662
Fixes: 655fcd68c7d2 ("net/failsafe: fix hotplug races")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/failsafe/failsafe.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/net/failsafe/failsafe.c b/drivers/net/failsafe/failsafe.c
index 3e590d38f7..aa3fe53b22 100644
--- a/drivers/net/failsafe/failsafe.c
+++ b/drivers/net/failsafe/failsafe.c
@@ -144,11 +144,20 @@ fs_mutex_init(struct fs_priv *priv)
 	/* Allow mutex relocks for the thread holding the mutex. */
 	ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
 	if (ret) {
-		ERROR("Cannot set mutex type - %s", strerror(ret));
-		return ret;
+		ERROR("Cannot set mutex recursive - %s", strerror(ret));
+		goto out;
+	}
+	/* Allow mutex to be shared between processes. */
+	ret = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	if (ret) {
+		ERROR("Cannot set mutex pshared - %s", strerror(ret));
+		goto out;
 	}
+	pthread_mutex_init(&priv->hotplug_mutex, &attr);
 
-	return pthread_mutex_init(&priv->hotplug_mutex, &attr);
+out:
+	pthread_mutexattr_destroy(&attr);
+	return ret;
 }
 
 static int
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH 3/6] net/atlantic: fix mbox_mutex for multi-process
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 1/6] ethdev: fix flow_ops_mutex " Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 2/6] net/failsafe: fix hotplug_mutex " Stephen Hemminger
@ 2026-04-13 17:14 ` Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 4/6] net/axgbe: fix mutexes " Stephen Hemminger
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Igor Russkikh, Pavel Belous

The Atlantic driver supports secondary processes, as shown by the
explicit check in eth_atl_dev_init(). The mbox_mutex in aq_hw_s
is located in dev_private which is in shared memory accessible
by both primary and secondary processes.

However, the mutex is initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

Bugzilla ID: 662
Fixes: e9924638f5c9 ("net/atlantic: add FW mailbox guard mutex")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/atlantic/atl_ethdev.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/net/atlantic/atl_ethdev.c b/drivers/net/atlantic/atl_ethdev.c
index 2925dc2478..7282f9b691 100644
--- a/drivers/net/atlantic/atl_ethdev.c
+++ b/drivers/net/atlantic/atl_ethdev.c
@@ -3,6 +3,7 @@
  */
 
 #include <rte_string_fns.h>
+#include <pthread.h>
 #include <ethdev_pci.h>
 #include <rte_alarm.h>
 
@@ -355,6 +356,17 @@ atl_disable_intr(struct aq_hw_s *hw)
 	hw_atl_itr_irq_msk_clearlsw_set(hw, 0xffffffff);
 }
 
+static void
+atl_init_mutex(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+
 static int
 eth_atl_dev_init(struct rte_eth_dev *eth_dev)
 {
@@ -405,7 +417,7 @@ eth_atl_dev_init(struct rte_eth_dev *eth_dev)
 
 	hw->aq_nic_cfg = &adapter->hw_cfg;
 
-	pthread_mutex_init(&hw->mbox_mutex, NULL);
+	atl_init_mutex(&hw->mbox_mutex);
 
 	/* disable interrupt */
 	atl_disable_intr(hw);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH 4/6] net/axgbe: fix mutexes for multi-process
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (2 preceding siblings ...)
  2026-04-13 17:14 ` [PATCH 3/6] net/atlantic: fix mbox_mutex " Stephen Hemminger
@ 2026-04-13 17:14 ` Stephen Hemminger
  2026-04-13 17:14 ` [PATCH 5/6] net/bnxt: " Stephen Hemminger
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Selwin Sebastian, Ravi Kumar

The AXGBE driver supports secondary processes, as shown by the
explicit check in eth_axgbe_dev_init(). The xpcs_mutex, i2c_mutex,
an_mutex, and phy_mutex in axgbe_port are located in dev_private
which is in shared memory accessible by both primary and secondary
processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

Bugzilla ID: 662
Fixes: 572890ef6625 ("net/axgbe: add structs for MAC init and reset")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/axgbe/axgbe_ethdev.c | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/drivers/net/axgbe/axgbe_ethdev.c b/drivers/net/axgbe/axgbe_ethdev.c
index cfcd880961..4aadf0993e 100644
--- a/drivers/net/axgbe/axgbe_ethdev.c
+++ b/drivers/net/axgbe/axgbe_ethdev.c
@@ -194,6 +194,17 @@ static const struct axgbe_xstats axgbe_xstats_strings[] = {
 #define AMD_PCI_AXGBE_DEVICE_V2A 0x1458
 #define AMD_PCI_AXGBE_DEVICE_V2B 0x1459
 
+static void
+axgbe_init_mutex(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+
 static const struct rte_pci_id pci_id_axgbe_map[] = {
 	{RTE_PCI_DEVICE(AMD_PCI_VENDOR_ID, AMD_PCI_AXGBE_DEVICE_V2A)},
 	{RTE_PCI_DEVICE(AMD_PCI_VENDOR_ID, AMD_PCI_AXGBE_DEVICE_V2B)},
@@ -2419,10 +2430,10 @@ eth_axgbe_dev_init(struct rte_eth_dev *eth_dev)
 
 	pdata->tx_desc_count = AXGBE_MAX_RING_DESC;
 	pdata->rx_desc_count = AXGBE_MAX_RING_DESC;
-	pthread_mutex_init(&pdata->xpcs_mutex, NULL);
-	pthread_mutex_init(&pdata->i2c_mutex, NULL);
-	pthread_mutex_init(&pdata->an_mutex, NULL);
-	pthread_mutex_init(&pdata->phy_mutex, NULL);
+	axgbe_init_mutex(&pdata->xpcs_mutex);
+	axgbe_init_mutex(&pdata->i2c_mutex);
+	axgbe_init_mutex(&pdata->an_mutex);
+	axgbe_init_mutex(&pdata->phy_mutex);
 
 	ret = pdata->phy_if.phy_init(pdata);
 	if (ret) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH 5/6] net/bnxt: fix mutexes for multi-process
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (3 preceding siblings ...)
  2026-04-13 17:14 ` [PATCH 4/6] net/axgbe: fix mutexes " Stephen Hemminger
@ 2026-04-13 17:14 ` Stephen Hemminger
  2026-04-14 18:51   ` Kishore Padmanabha
  2026-04-13 17:14 ` [PATCH 6/6] net/hinic: " Stephen Hemminger
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Kishore Padmanabha, Ajit Khaparde,
	Venkat Duvvuru, Kalesh AP, Somnath Kotur

The BNXT driver supports secondary processes, as shown by the
explicit check in bnxt_dev_init(). Multiple mutexes are located
in structures allocated in shared memory (dev_private and
rte_zmalloc'd structures) that are accessible by both primary
and secondary processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

This patch adds a helper function and updates all mutex
initializations in the BNXT driver:
  - flow_lock, def_cp_lock, health_check_lock, err_recovery_lock
    in struct bnxt (bnxt_ethdev.c)
  - vfr_start_lock in rep_info (bnxt_ethdev.c)
  - txq_lock in struct bnxt_tx_queue (bnxt_txq.c)
  - bnxt_ulp_mutex in session state (bnxt_ulp.c)
  - flow_db_lock in cfg_data (bnxt_ulp_tf.c, bnxt_ulp_tfc.c)

Bugzilla ID: 662
Fixes: 1cb3d39a48f7 ("net/bnxt: synchronize between flow related functions")
Fixes: 5526c8025d4d ("net/bnxt: fix race between interrupt handler and dev config")
Fixes: b59e4be2b6a7 ("net/bnxt: fix VF representor port add")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
 drivers/net/bnxt/bnxt_util.h           |  2 ++
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 7 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/drivers/net/bnxt/bnxt_ethdev.c b/drivers/net/bnxt/bnxt_ethdev.c
index b677f9491d..3f55ad041d 100644
--- a/drivers/net/bnxt/bnxt_ethdev.c
+++ b/drivers/net/bnxt/bnxt_ethdev.c
@@ -5899,10 +5899,10 @@ static int bnxt_get_config(struct bnxt *bp)
 static int
 bnxt_init_locks(struct bnxt *bp)
 {
-	pthread_mutex_init(&bp->flow_lock, NULL);
-	pthread_mutex_init(&bp->def_cp_lock, NULL);
-	pthread_mutex_init(&bp->health_check_lock, NULL);
-	pthread_mutex_init(&bp->err_recovery_lock, NULL);
+	bnxt_init_mutex(&bp->flow_lock);
+	bnxt_init_mutex(&bp->def_cp_lock);
+	bnxt_init_mutex(&bp->health_check_lock);
+	bnxt_init_mutex(&bp->err_recovery_lock);
 
 	return 0;
 }
@@ -6920,7 +6920,8 @@ static int bnxt_init_rep_info(struct bnxt *bp)
 	for (i = 0; i < BNXT_MAX_CFA_CODE; i++)
 		bp->cfa_code_map[i] = BNXT_VF_IDX_INVALID;
 
-	return pthread_mutex_init(&bp->rep_info->vfr_start_lock, NULL);
+	bnxt_init_mutex(&bp->rep_info->vfr_start_lock);
+	return 0;
 }
 
 static int bnxt_rep_port_probe(struct rte_pci_device *pci_dev,
diff --git a/drivers/net/bnxt/bnxt_txq.c b/drivers/net/bnxt/bnxt_txq.c
index 7752f06eb7..8c834acb1d 100644
--- a/drivers/net/bnxt/bnxt_txq.c
+++ b/drivers/net/bnxt/bnxt_txq.c
@@ -204,7 +204,8 @@ int bnxt_tx_queue_setup_op(struct rte_eth_dev *eth_dev,
 		goto err;
 	}
 
-	return pthread_mutex_init(&txq->txq_lock, NULL);
+	bnxt_init_mutex(&txq->txq_lock);
+	return 0;
 err:
 	bnxt_tx_queue_release_op(eth_dev, queue_idx);
 	return rc;
diff --git a/drivers/net/bnxt/bnxt_util.c b/drivers/net/bnxt/bnxt_util.c
index aa184496c2..c3f0a03f25 100644
--- a/drivers/net/bnxt/bnxt_util.c
+++ b/drivers/net/bnxt/bnxt_util.c
@@ -3,6 +3,7 @@
  * All rights reserved.
  */
 
+#include <pthread.h>
 #include <inttypes.h>
 #include <rte_ether.h>
 
@@ -37,3 +38,15 @@ uint8_t hweight32(uint32_t word32)
 	res = res + (res >> 8);
 	return (res + (res >> 16)) & 0x000000FF;
 }
+
+/* Initialize mutex so that it can be shared between processes */
+void
+bnxt_init_mutex(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
diff --git a/drivers/net/bnxt/bnxt_util.h b/drivers/net/bnxt/bnxt_util.h
index 416a6a2609..e1b45d1bb5 100644
--- a/drivers/net/bnxt/bnxt_util.h
+++ b/drivers/net/bnxt/bnxt_util.h
@@ -16,4 +16,6 @@
 int bnxt_check_zero_bytes(const uint8_t *bytes, int len);
 void bnxt_eth_hw_addr_random(uint8_t *mac_addr);
 uint8_t hweight32(uint32_t word32);
+void bnxt_init_mutex(pthread_mutex_t *mutex);
+
 #endif /* _BNXT_UTIL_H_ */
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
index 0c03ae7a83..c2c93859ff 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
@@ -214,7 +214,7 @@ ulp_session_init(struct bnxt *bp,
 			session->pci_info.domain = pci_addr->domain;
 			session->pci_info.bus = pci_addr->bus;
 			memcpy(session->dsn, bp->dsn, sizeof(session->dsn));
-			pthread_mutex_init(&session->bnxt_ulp_mutex, NULL);
+			bnxt_init_mutex(&session->bnxt_ulp_mutex);
 			STAILQ_INSERT_TAIL(&bnxt_ulp_session_list,
 					   session, next);
 		}
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
index bc347de202..d87120aea3 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
@@ -1469,7 +1469,7 @@ ulp_tf_init(struct bnxt *bp,
 		goto jump_to_error;
 	}
 
-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	bnxt_init_mutex(&bp->ulp_ctx->cfg_data->flow_db_lock);
 
 	/* Initialize ulp dparms with values devargs passed */
 	rc = ulp_tf_dparms_init(bp, bp->ulp_ctx);
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
index ad44ec93ca..0c3d1f7dae 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
@@ -1038,7 +1038,7 @@ ulp_tfc_init(struct bnxt *bp,
 		goto jump_to_error;
 	}
 
-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	bnxt_init_mutex(&bp->ulp_ctx->cfg_data->flow_db_lock);
 
 	rc = ulp_tfc_dparms_init(bp, bp->ulp_ctx, ulp_dev_id);
 	if (rc) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH 6/6] net/hinic: fix mutexes for multi-process
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (4 preceding siblings ...)
  2026-04-13 17:14 ` [PATCH 5/6] net/bnxt: " Stephen Hemminger
@ 2026-04-13 17:14 ` Stephen Hemminger
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-13 17:14 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Xiaoyun Wang, Ziyang Xuan

The hinic driver supports secondary processes, as shown by the
explicit check in hinic_dev_init(). Multiple mutexes are located
in structures allocated in shared memory (dev_private and
rte_zmalloc'd structures) that are accessible by both primary
and secondary processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

Fix the hinic_mutex_init() wrapper function to set the
PTHREAD_PROCESS_SHARED attribute on all mutexes.

Note: all callers to hinic_mutex_init() pass NULL as
second argument, so it could be simplified but that would
entail more changes to base vendor code.

Bugzilla ID: 662
Fixes: ae865766b334 ("net/hinic: replace spinlock with mutex")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/hinic/base/hinic_compat.h | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/net/hinic/base/hinic_compat.h b/drivers/net/hinic/base/hinic_compat.h
index d3994c50e9..18148a119e 100644
--- a/drivers/net/hinic/base/hinic_compat.h
+++ b/drivers/net/hinic/base/hinic_compat.h
@@ -197,10 +197,21 @@ static inline u16 ilog2(u32 n)
 	return res;
 }
 
+/*
+ * Initialize mutex for process-shared access.
+ * Structures may be in shared memory accessible by multiple processes,
+ * so mutexes must use PTHREAD_PROCESS_SHARED.
+ */
 static inline int hinic_mutex_init(pthread_mutex_t *pthreadmutex,
 					const pthread_mutexattr_t *mattr)
 {
-	return pthread_mutex_init(pthreadmutex, mattr);
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(pthreadmutex, mattr ? mattr : &attr);
+	pthread_mutexattr_destroy(&attr);
+	return 0;
 }
 
 static inline int hinic_mutex_destroy(pthread_mutex_t *pthreadmutex)
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v2 0/6] fix process shared pthread mutexes
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (5 preceding siblings ...)
  2026-04-13 17:14 ` [PATCH 6/6] net/hinic: " Stephen Hemminger
@ 2026-04-14 14:39 ` Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 1/6] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
                     ` (8 more replies)
  2026-04-15 14:51 ` [PATCH v3 0/2] af_packet: cleanup and test Stephen Hemminger
                   ` (2 subsequent siblings)
  9 siblings, 9 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Several drivers and the ethdev layer initialize pthread mutexes
in shared memory with default (process-private) attributes.
This is undefined behavior when secondary processes use them.

This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
All are on control paths (firmware mailbox, hotplug, flow ops,
PHY negotiation) where sleeping is acceptable.

See POSIX spec:
https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexattr_getpshared.html

Bugzilla ID: 662

v2 - fix build on Windows which does not need this.

Stephen Hemminger (6):
  ethdev: fix flow_ops_mutex for multi-process
  net/failsafe: fix hotplug_mutex for multi-process
  net/atlantic: fix mbox_mutex for multi-process
  net/axgbe: fix mutexes for multi-process
  net/bnxt: fix mutexes for multi-process
  net/hinic: fix mutexes for multi-process

 drivers/net/atlantic/atl_ethdev.c      | 14 +++++++++++++-
 drivers/net/axgbe/axgbe_ethdev.c       | 19 +++++++++++++++----
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
 drivers/net/bnxt/bnxt_util.h           |  2 ++
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 drivers/net/failsafe/failsafe.c        | 15 ++++++++++++---
 drivers/net/hinic/base/hinic_compat.h  | 13 ++++++++++++-
 lib/ethdev/ethdev_driver.c             | 22 +++++++++++++++++++++-
 12 files changed, 99 insertions(+), 19 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH v2 1/6] ethdev: fix flow_ops_mutex for multi-process
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
@ 2026-04-14 14:39   ` Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 2/6] net/failsafe: fix hotplug_mutex " Stephen Hemminger
                     ` (7 subsequent siblings)
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Thomas Monjalon, Andrew Rybchenko,
	Matan Azrad, Ajit Khaparde, Suanming Mou, Ori Kam

The flow_ops_mutex in rte_eth_dev_data is located in shared memory
that is accessed by both primary and secondary processes. However,
the mutex is initialized without PTHREAD_PROCESS_SHARED attribute,
which means synchronization between processes is undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

This bug has existed since the flow_ops_mutex was added and affects
any multi-process DPDK application using rte_flow APIs.

Bugzilla ID: 662
Fixes: 80d1a9aff7f6 ("ethdev: make flow API thread safe")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/ethdev/ethdev_driver.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/lib/ethdev/ethdev_driver.c b/lib/ethdev/ethdev_driver.c
index 2ec665be0f..60d0246226 100644
--- a/lib/ethdev/ethdev_driver.c
+++ b/lib/ethdev/ethdev_driver.c
@@ -89,6 +89,26 @@ eth_dev_set_dummy_fops(struct rte_eth_dev *eth_dev)
 	eth_dev->recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy;
 }
 
+/*
+ * Initialize mutex for process-shared access.
+ * The rte_eth_dev_data structure is in shared memory, so any mutex
+ * protecting it must use PTHREAD_PROCESS_SHARED.
+ */
+static void
+eth_dev_flow_ops_mutex_init(pthread_mutex_t *mutex)
+{
+#ifndef RTE_EXEC_ENV_WINDOWS
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+#else
+	pthread_mutex_init(mutex, NULL);
+#endif
+}
+
 RTE_EXPORT_INTERNAL_SYMBOL(rte_eth_dev_allocate)
 struct rte_eth_dev *
 rte_eth_dev_allocate(const char *name)
@@ -135,7 +155,7 @@ rte_eth_dev_allocate(const char *name)
 	eth_dev->data->port_id = port_id;
 	eth_dev->data->backer_port_id = RTE_MAX_ETHPORTS;
 	eth_dev->data->mtu = RTE_ETHER_MTU;
-	pthread_mutex_init(&eth_dev->data->flow_ops_mutex, NULL);
+	eth_dev_flow_ops_mutex_init(&eth_dev->data->flow_ops_mutex);
 	RTE_ASSERT(rte_eal_process_type() == RTE_PROC_PRIMARY);
 	eth_dev_shared_data->allocated_ports++;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v2 2/6] net/failsafe: fix hotplug_mutex for multi-process
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 1/6] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
@ 2026-04-14 14:39   ` Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 3/6] net/atlantic: fix mbox_mutex " Stephen Hemminger
                     ` (6 subsequent siblings)
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Gaetan Rivet, Matan Azrad

The failsafe driver supports secondary process attachment via
rte_eth_dev_attach_secondary() in rte_pmd_failsafe_probe(). The
hotplug_mutex in fs_priv protects shared state but is initialized
without PTHREAD_PROCESS_SHARED attribute.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures accessed by multiple processes,
pthread_mutexattr_setpshared() must be called with PTHREAD_PROCESS_SHARED.

Also add missing pthread_mutexattr_destroy() call to avoid resource leak.

Bugzilla ID: 662
Fixes: 655fcd68c7d2 ("net/failsafe: fix hotplug races")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/failsafe/failsafe.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/net/failsafe/failsafe.c b/drivers/net/failsafe/failsafe.c
index 3e590d38f7..aa3fe53b22 100644
--- a/drivers/net/failsafe/failsafe.c
+++ b/drivers/net/failsafe/failsafe.c
@@ -144,11 +144,20 @@ fs_mutex_init(struct fs_priv *priv)
 	/* Allow mutex relocks for the thread holding the mutex. */
 	ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
 	if (ret) {
-		ERROR("Cannot set mutex type - %s", strerror(ret));
-		return ret;
+		ERROR("Cannot set mutex recursive - %s", strerror(ret));
+		goto out;
+	}
+	/* Allow mutex to be shared between processes. */
+	ret = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	if (ret) {
+		ERROR("Cannot set mutex pshared - %s", strerror(ret));
+		goto out;
 	}
+	pthread_mutex_init(&priv->hotplug_mutex, &attr);
 
-	return pthread_mutex_init(&priv->hotplug_mutex, &attr);
+out:
+	pthread_mutexattr_destroy(&attr);
+	return ret;
 }
 
 static int
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v2 3/6] net/atlantic: fix mbox_mutex for multi-process
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 1/6] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 2/6] net/failsafe: fix hotplug_mutex " Stephen Hemminger
@ 2026-04-14 14:39   ` Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 4/6] net/axgbe: fix mutexes " Stephen Hemminger
                     ` (5 subsequent siblings)
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Igor Russkikh, Pavel Belous

The Atlantic driver supports secondary processes, as shown by the
explicit check in eth_atl_dev_init(). The mbox_mutex in aq_hw_s
is located in dev_private which is in shared memory accessible
by both primary and secondary processes.

However, the mutex is initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

Bugzilla ID: 662
Fixes: e9924638f5c9 ("net/atlantic: add FW mailbox guard mutex")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/atlantic/atl_ethdev.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/net/atlantic/atl_ethdev.c b/drivers/net/atlantic/atl_ethdev.c
index 2925dc2478..7282f9b691 100644
--- a/drivers/net/atlantic/atl_ethdev.c
+++ b/drivers/net/atlantic/atl_ethdev.c
@@ -3,6 +3,7 @@
  */
 
 #include <rte_string_fns.h>
+#include <pthread.h>
 #include <ethdev_pci.h>
 #include <rte_alarm.h>
 
@@ -355,6 +356,17 @@ atl_disable_intr(struct aq_hw_s *hw)
 	hw_atl_itr_irq_msk_clearlsw_set(hw, 0xffffffff);
 }
 
+static void
+atl_init_mutex(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+
 static int
 eth_atl_dev_init(struct rte_eth_dev *eth_dev)
 {
@@ -405,7 +417,7 @@ eth_atl_dev_init(struct rte_eth_dev *eth_dev)
 
 	hw->aq_nic_cfg = &adapter->hw_cfg;
 
-	pthread_mutex_init(&hw->mbox_mutex, NULL);
+	atl_init_mutex(&hw->mbox_mutex);
 
 	/* disable interrupt */
 	atl_disable_intr(hw);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v2 4/6] net/axgbe: fix mutexes for multi-process
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-04-14 14:39   ` [PATCH v2 3/6] net/atlantic: fix mbox_mutex " Stephen Hemminger
@ 2026-04-14 14:39   ` Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 5/6] net/bnxt: " Stephen Hemminger
                     ` (4 subsequent siblings)
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Selwin Sebastian, Ravi Kumar

The AXGBE driver supports secondary processes, as shown by the
explicit check in eth_axgbe_dev_init(). The xpcs_mutex, i2c_mutex,
an_mutex, and phy_mutex in axgbe_port are located in dev_private
which is in shared memory accessible by both primary and secondary
processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

Bugzilla ID: 662
Fixes: 572890ef6625 ("net/axgbe: add structs for MAC init and reset")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/axgbe/axgbe_ethdev.c | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/drivers/net/axgbe/axgbe_ethdev.c b/drivers/net/axgbe/axgbe_ethdev.c
index cfcd880961..4aadf0993e 100644
--- a/drivers/net/axgbe/axgbe_ethdev.c
+++ b/drivers/net/axgbe/axgbe_ethdev.c
@@ -194,6 +194,17 @@ static const struct axgbe_xstats axgbe_xstats_strings[] = {
 #define AMD_PCI_AXGBE_DEVICE_V2A 0x1458
 #define AMD_PCI_AXGBE_DEVICE_V2B 0x1459
 
+static void
+axgbe_init_mutex(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+
 static const struct rte_pci_id pci_id_axgbe_map[] = {
 	{RTE_PCI_DEVICE(AMD_PCI_VENDOR_ID, AMD_PCI_AXGBE_DEVICE_V2A)},
 	{RTE_PCI_DEVICE(AMD_PCI_VENDOR_ID, AMD_PCI_AXGBE_DEVICE_V2B)},
@@ -2419,10 +2430,10 @@ eth_axgbe_dev_init(struct rte_eth_dev *eth_dev)
 
 	pdata->tx_desc_count = AXGBE_MAX_RING_DESC;
 	pdata->rx_desc_count = AXGBE_MAX_RING_DESC;
-	pthread_mutex_init(&pdata->xpcs_mutex, NULL);
-	pthread_mutex_init(&pdata->i2c_mutex, NULL);
-	pthread_mutex_init(&pdata->an_mutex, NULL);
-	pthread_mutex_init(&pdata->phy_mutex, NULL);
+	axgbe_init_mutex(&pdata->xpcs_mutex);
+	axgbe_init_mutex(&pdata->i2c_mutex);
+	axgbe_init_mutex(&pdata->an_mutex);
+	axgbe_init_mutex(&pdata->phy_mutex);
 
 	ret = pdata->phy_if.phy_init(pdata);
 	if (ret) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v2 5/6] net/bnxt: fix mutexes for multi-process
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-04-14 14:39   ` [PATCH v2 4/6] net/axgbe: fix mutexes " Stephen Hemminger
@ 2026-04-14 14:39   ` Stephen Hemminger
  2026-04-14 14:39   ` [PATCH v2 6/6] net/hinic: " Stephen Hemminger
                     ` (3 subsequent siblings)
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Kishore Padmanabha, Ajit Khaparde,
	Kalesh AP, Venkat Duvvuru, Somnath Kotur

The BNXT driver supports secondary processes, as shown by the
explicit check in bnxt_dev_init(). Multiple mutexes are located
in structures allocated in shared memory (dev_private and
rte_zmalloc'd structures) that are accessible by both primary
and secondary processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

This patch adds a helper function and updates all mutex
initializations in the BNXT driver:
  - flow_lock, def_cp_lock, health_check_lock, err_recovery_lock
    in struct bnxt (bnxt_ethdev.c)
  - vfr_start_lock in rep_info (bnxt_ethdev.c)
  - txq_lock in struct bnxt_tx_queue (bnxt_txq.c)
  - bnxt_ulp_mutex in session state (bnxt_ulp.c)
  - flow_db_lock in cfg_data (bnxt_ulp_tf.c, bnxt_ulp_tfc.c)

Bugzilla ID: 662
Fixes: 1cb3d39a48f7 ("net/bnxt: synchronize between flow related functions")
Fixes: 5526c8025d4d ("net/bnxt: fix race between interrupt handler and dev config")
Fixes: b59e4be2b6a7 ("net/bnxt: fix VF representor port add")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
 drivers/net/bnxt/bnxt_util.h           |  2 ++
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 7 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/drivers/net/bnxt/bnxt_ethdev.c b/drivers/net/bnxt/bnxt_ethdev.c
index b677f9491d..3f55ad041d 100644
--- a/drivers/net/bnxt/bnxt_ethdev.c
+++ b/drivers/net/bnxt/bnxt_ethdev.c
@@ -5899,10 +5899,10 @@ static int bnxt_get_config(struct bnxt *bp)
 static int
 bnxt_init_locks(struct bnxt *bp)
 {
-	pthread_mutex_init(&bp->flow_lock, NULL);
-	pthread_mutex_init(&bp->def_cp_lock, NULL);
-	pthread_mutex_init(&bp->health_check_lock, NULL);
-	pthread_mutex_init(&bp->err_recovery_lock, NULL);
+	bnxt_init_mutex(&bp->flow_lock);
+	bnxt_init_mutex(&bp->def_cp_lock);
+	bnxt_init_mutex(&bp->health_check_lock);
+	bnxt_init_mutex(&bp->err_recovery_lock);
 
 	return 0;
 }
@@ -6920,7 +6920,8 @@ static int bnxt_init_rep_info(struct bnxt *bp)
 	for (i = 0; i < BNXT_MAX_CFA_CODE; i++)
 		bp->cfa_code_map[i] = BNXT_VF_IDX_INVALID;
 
-	return pthread_mutex_init(&bp->rep_info->vfr_start_lock, NULL);
+	bnxt_init_mutex(&bp->rep_info->vfr_start_lock);
+	return 0;
 }
 
 static int bnxt_rep_port_probe(struct rte_pci_device *pci_dev,
diff --git a/drivers/net/bnxt/bnxt_txq.c b/drivers/net/bnxt/bnxt_txq.c
index 7752f06eb7..8c834acb1d 100644
--- a/drivers/net/bnxt/bnxt_txq.c
+++ b/drivers/net/bnxt/bnxt_txq.c
@@ -204,7 +204,8 @@ int bnxt_tx_queue_setup_op(struct rte_eth_dev *eth_dev,
 		goto err;
 	}
 
-	return pthread_mutex_init(&txq->txq_lock, NULL);
+	bnxt_init_mutex(&txq->txq_lock);
+	return 0;
 err:
 	bnxt_tx_queue_release_op(eth_dev, queue_idx);
 	return rc;
diff --git a/drivers/net/bnxt/bnxt_util.c b/drivers/net/bnxt/bnxt_util.c
index aa184496c2..c3f0a03f25 100644
--- a/drivers/net/bnxt/bnxt_util.c
+++ b/drivers/net/bnxt/bnxt_util.c
@@ -3,6 +3,7 @@
  * All rights reserved.
  */
 
+#include <pthread.h>
 #include <inttypes.h>
 #include <rte_ether.h>
 
@@ -37,3 +38,15 @@ uint8_t hweight32(uint32_t word32)
 	res = res + (res >> 8);
 	return (res + (res >> 16)) & 0x000000FF;
 }
+
+/* Initialize mutex so that it can be shared between processes */
+void
+bnxt_init_mutex(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
diff --git a/drivers/net/bnxt/bnxt_util.h b/drivers/net/bnxt/bnxt_util.h
index 416a6a2609..e1b45d1bb5 100644
--- a/drivers/net/bnxt/bnxt_util.h
+++ b/drivers/net/bnxt/bnxt_util.h
@@ -16,4 +16,6 @@
 int bnxt_check_zero_bytes(const uint8_t *bytes, int len);
 void bnxt_eth_hw_addr_random(uint8_t *mac_addr);
 uint8_t hweight32(uint32_t word32);
+void bnxt_init_mutex(pthread_mutex_t *mutex);
+
 #endif /* _BNXT_UTIL_H_ */
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
index 0c03ae7a83..c2c93859ff 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
@@ -214,7 +214,7 @@ ulp_session_init(struct bnxt *bp,
 			session->pci_info.domain = pci_addr->domain;
 			session->pci_info.bus = pci_addr->bus;
 			memcpy(session->dsn, bp->dsn, sizeof(session->dsn));
-			pthread_mutex_init(&session->bnxt_ulp_mutex, NULL);
+			bnxt_init_mutex(&session->bnxt_ulp_mutex);
 			STAILQ_INSERT_TAIL(&bnxt_ulp_session_list,
 					   session, next);
 		}
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
index bc347de202..d87120aea3 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
@@ -1469,7 +1469,7 @@ ulp_tf_init(struct bnxt *bp,
 		goto jump_to_error;
 	}
 
-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	bnxt_init_mutex(&bp->ulp_ctx->cfg_data->flow_db_lock);
 
 	/* Initialize ulp dparms with values devargs passed */
 	rc = ulp_tf_dparms_init(bp, bp->ulp_ctx);
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
index ad44ec93ca..0c3d1f7dae 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
@@ -1038,7 +1038,7 @@ ulp_tfc_init(struct bnxt *bp,
 		goto jump_to_error;
 	}
 
-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	bnxt_init_mutex(&bp->ulp_ctx->cfg_data->flow_db_lock);
 
 	rc = ulp_tfc_dparms_init(bp, bp->ulp_ctx, ulp_dev_id);
 	if (rc) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v2 6/6] net/hinic: fix mutexes for multi-process
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-04-14 14:39   ` [PATCH v2 5/6] net/bnxt: " Stephen Hemminger
@ 2026-04-14 14:39   ` Stephen Hemminger
  2026-04-23 15:05   ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (2 subsequent siblings)
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-14 14:39 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Xiaoyun Wang, Ziyang Xuan

The hinic driver supports secondary processes, as shown by the
explicit check in hinic_dev_init(). Multiple mutexes are located
in structures allocated in shared memory (dev_private and
rte_zmalloc'd structures) that are accessible by both primary
and secondary processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is
undefined behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are
accessed by multiple processes, pthread_mutexattr_setpshared() must
be called with PTHREAD_PROCESS_SHARED.

Fix the hinic_mutex_init() wrapper function to set the
PTHREAD_PROCESS_SHARED attribute on all mutexes.

Note: all callers to hinic_mutex_init() pass NULL as
second argument, so it could be simplified but that would
entail more changes to base vendor code.

Bugzilla ID: 662
Fixes: ae865766b334 ("net/hinic: replace spinlock with mutex")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/hinic/base/hinic_compat.h | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/net/hinic/base/hinic_compat.h b/drivers/net/hinic/base/hinic_compat.h
index d3994c50e9..18148a119e 100644
--- a/drivers/net/hinic/base/hinic_compat.h
+++ b/drivers/net/hinic/base/hinic_compat.h
@@ -197,10 +197,21 @@ static inline u16 ilog2(u32 n)
 	return res;
 }
 
+/*
+ * Initialize mutex for process-shared access.
+ * Structures may be in shared memory accessible by multiple processes,
+ * so mutexes must use PTHREAD_PROCESS_SHARED.
+ */
 static inline int hinic_mutex_init(pthread_mutex_t *pthreadmutex,
 					const pthread_mutexattr_t *mattr)
 {
-	return pthread_mutex_init(pthreadmutex, mattr);
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(pthreadmutex, mattr ? mattr : &attr);
+	pthread_mutexattr_destroy(&attr);
+	return 0;
 }
 
 static inline int hinic_mutex_destroy(pthread_mutex_t *pthreadmutex)
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* RE: [PATCH 5/6] net/bnxt: fix mutexes for multi-process
  2026-04-13 17:14 ` [PATCH 5/6] net/bnxt: " Stephen Hemminger
@ 2026-04-14 18:51   ` Kishore Padmanabha
  0 siblings, 0 replies; 33+ messages in thread
From: Kishore Padmanabha @ 2026-04-14 18:51 UTC (permalink / raw)
  To: Stephen Hemminger, dev; +Cc: stable, Kalesh Anakkur Purayil

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

-----Original Message-----
From: Stephen Hemminger <stephen@networkplumber.org>
Sent: Monday, April 13, 2026 1:15 PM
To: dev@dpdk.org
Cc: Stephen Hemminger <stephen@networkplumber.org>; stable@dpdk.org;
Kishore Padmanabha <kishore.padmanabha@broadcom.com>; Ajit Khaparde
<ajit.khaparde@broadcom.com>; Venkat Duvvuru
<venkatkumar.duvvuru@broadcom.com>; Kalesh AP
<kalesh-anakkur.purayil@broadcom.com>; Somnath Kotur
<somnath.kotur@broadcom.com>
Subject: [PATCH 5/6] net/bnxt: fix mutexes for multi-process

The BNXT driver supports secondary processes, as shown by the explicit
check in bnxt_dev_init(). Multiple mutexes are located in structures
allocated in shared memory (dev_private and rte_zmalloc'd structures) that
are accessible by both primary and secondary processes.

However, the mutexes are initialized without PTHREAD_PROCESS_SHARED
attribute, which means synchronization between processes is undefined
behavior.

POSIX mutexes are by default private to the process creating them.
When a mutex protects data structures in shared memory that are accessed
by multiple processes, pthread_mutexattr_setpshared() must be called with
PTHREAD_PROCESS_SHARED.

This patch adds a helper function and updates all mutex initializations in
the BNXT driver:
  - flow_lock, def_cp_lock, health_check_lock, err_recovery_lock
    in struct bnxt (bnxt_ethdev.c)
  - vfr_start_lock in rep_info (bnxt_ethdev.c)
  - txq_lock in struct bnxt_tx_queue (bnxt_txq.c)
  - bnxt_ulp_mutex in session state (bnxt_ulp.c)
  - flow_db_lock in cfg_data (bnxt_ulp_tf.c, bnxt_ulp_tfc.c)

Bugzilla ID: 662
Fixes: 1cb3d39a48f7 ("net/bnxt: synchronize between flow related
functions")
Fixes: 5526c8025d4d ("net/bnxt: fix race between interrupt handler and dev
config")
Fixes: b59e4be2b6a7 ("net/bnxt: fix VF representor port add")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by:  Kishore Padmanabha <kishore.padmanabha@broadcom.com>
---
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
 drivers/net/bnxt/bnxt_util.h           |  2 ++
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 7 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/drivers/net/bnxt/bnxt_ethdev.c
b/drivers/net/bnxt/bnxt_ethdev.c index b677f9491d..3f55ad041d 100644
--- a/drivers/net/bnxt/bnxt_ethdev.c
+++ b/drivers/net/bnxt/bnxt_ethdev.c
@@ -5899,10 +5899,10 @@ static int bnxt_get_config(struct bnxt *bp)
static int  bnxt_init_locks(struct bnxt *bp)  {
-	pthread_mutex_init(&bp->flow_lock, NULL);
-	pthread_mutex_init(&bp->def_cp_lock, NULL);
-	pthread_mutex_init(&bp->health_check_lock, NULL);
-	pthread_mutex_init(&bp->err_recovery_lock, NULL);
+	bnxt_init_mutex(&bp->flow_lock);
+	bnxt_init_mutex(&bp->def_cp_lock);
+	bnxt_init_mutex(&bp->health_check_lock);
+	bnxt_init_mutex(&bp->err_recovery_lock);

 	return 0;
 }
@@ -6920,7 +6920,8 @@ static int bnxt_init_rep_info(struct bnxt *bp)
 	for (i = 0; i < BNXT_MAX_CFA_CODE; i++)
 		bp->cfa_code_map[i] = BNXT_VF_IDX_INVALID;

-	return pthread_mutex_init(&bp->rep_info->vfr_start_lock, NULL);
+	bnxt_init_mutex(&bp->rep_info->vfr_start_lock);
+	return 0;
 }

 static int bnxt_rep_port_probe(struct rte_pci_device *pci_dev, diff --git
a/drivers/net/bnxt/bnxt_txq.c b/drivers/net/bnxt/bnxt_txq.c index
7752f06eb7..8c834acb1d 100644
--- a/drivers/net/bnxt/bnxt_txq.c
+++ b/drivers/net/bnxt/bnxt_txq.c
@@ -204,7 +204,8 @@ int bnxt_tx_queue_setup_op(struct rte_eth_dev
*eth_dev,
 		goto err;
 	}

-	return pthread_mutex_init(&txq->txq_lock, NULL);
+	bnxt_init_mutex(&txq->txq_lock);
+	return 0;
 err:
 	bnxt_tx_queue_release_op(eth_dev, queue_idx);
 	return rc;
diff --git a/drivers/net/bnxt/bnxt_util.c b/drivers/net/bnxt/bnxt_util.c
index aa184496c2..c3f0a03f25 100644
--- a/drivers/net/bnxt/bnxt_util.c
+++ b/drivers/net/bnxt/bnxt_util.c
@@ -3,6 +3,7 @@
  * All rights reserved.
  */

+#include <pthread.h>
 #include <inttypes.h>
 #include <rte_ether.h>

@@ -37,3 +38,15 @@ uint8_t hweight32(uint32_t word32)
 	res = res + (res >> 8);
 	return (res + (res >> 16)) & 0x000000FF;  }
+
+/* Initialize mutex so that it can be shared between processes */ void
+bnxt_init_mutex(pthread_mutex_t *mutex) {
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
diff --git a/drivers/net/bnxt/bnxt_util.h b/drivers/net/bnxt/bnxt_util.h
index 416a6a2609..e1b45d1bb5 100644
--- a/drivers/net/bnxt/bnxt_util.h
+++ b/drivers/net/bnxt/bnxt_util.h
@@ -16,4 +16,6 @@
 int bnxt_check_zero_bytes(const uint8_t *bytes, int len);  void
bnxt_eth_hw_addr_random(uint8_t *mac_addr);  uint8_t hweight32(uint32_t
word32);
+void bnxt_init_mutex(pthread_mutex_t *mutex);
+
 #endif /* _BNXT_UTIL_H_ */
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
index 0c03ae7a83..c2c93859ff 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
@@ -214,7 +214,7 @@ ulp_session_init(struct bnxt *bp,
 			session->pci_info.domain = pci_addr->domain;
 			session->pci_info.bus = pci_addr->bus;
 			memcpy(session->dsn, bp->dsn,
sizeof(session->dsn));
-			pthread_mutex_init(&session->bnxt_ulp_mutex,
NULL);
+			bnxt_init_mutex(&session->bnxt_ulp_mutex);
 			STAILQ_INSERT_TAIL(&bnxt_ulp_session_list,
 					   session, next);
 		}
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
index bc347de202..d87120aea3 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
@@ -1469,7 +1469,7 @@ ulp_tf_init(struct bnxt *bp,
 		goto jump_to_error;
 	}

-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	bnxt_init_mutex(&bp->ulp_ctx->cfg_data->flow_db_lock);

 	/* Initialize ulp dparms with values devargs passed */
 	rc = ulp_tf_dparms_init(bp, bp->ulp_ctx); diff --git
a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
index ad44ec93ca..0c3d1f7dae 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
@@ -1038,7 +1038,7 @@ ulp_tfc_init(struct bnxt *bp,
 		goto jump_to_error;
 	}

-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	bnxt_init_mutex(&bp->ulp_ctx->cfg_data->flow_db_lock);

 	rc = ulp_tfc_dparms_init(bp, bp->ulp_ctx, ulp_dev_id);
 	if (rc) {
--
2.53.0

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

^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 0/2] af_packet: cleanup and test
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (6 preceding siblings ...)
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
@ 2026-04-15 14:51 ` Stephen Hemminger
  2026-04-15 14:51   ` [PATCH v3 1/2] net/af_packet: fix indentation Stephen Hemminger
  2026-04-15 14:51   ` [PATCH v3 2/2] test: add test for af_packet Stephen Hemminger
  2026-04-16 17:57 ` [PATCH v4 0/2] af_packet: cleanup and test Stephen Hemminger
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
  9 siblings, 2 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-15 14:51 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

A couple of small patches for the af_packet PMD.
Fix indentation and add a basic test.

Stephen Hemminger (2):
  net/af_packet: fix indentation
  test: add test for af_packet

v3 - fix unchecked return value in test

 app/test/meson.build                      |    1 +
 app/test/test_pmd_af_packet.c             | 1174 +++++++++++++++++++++
 drivers/net/af_packet/rte_eth_af_packet.c |   64 +-
 3 files changed, 1207 insertions(+), 32 deletions(-)
 create mode 100644 app/test/test_pmd_af_packet.c

-- 
2.53.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH v3 1/2] net/af_packet: fix indentation
  2026-04-15 14:51 ` [PATCH v3 0/2] af_packet: cleanup and test Stephen Hemminger
@ 2026-04-15 14:51   ` Stephen Hemminger
  2026-04-15 14:51   ` [PATCH v3 2/2] test: add test for af_packet Stephen Hemminger
  1 sibling, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-15 14:51 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, John W. Linville

DPDK code is supposed to be indented with TABS not spaces.
Also, used "unsigned int" to keep checkpatch happy.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/af_packet/rte_eth_af_packet.c | 64 +++++++++++------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 0ee94e71ea..8303ff5ca9 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -79,7 +79,7 @@ struct __rte_cache_aligned pkt_tx_queue {
 };
 
 struct pmd_internals {
-	unsigned nb_queues;
+	unsigned int nb_queues;
 
 	int if_index;
 	char *if_name;
@@ -602,7 +602,7 @@ eth_dev_close(struct rte_eth_dev *dev)
 
 static int
 eth_link_update(struct rte_eth_dev *dev,
-                int wait_to_complete __rte_unused)
+		int wait_to_complete __rte_unused)
 {
 	const struct pmd_internals *internals = dev->data->dev_private;
 	struct rte_eth_link *dev_link = &dev->data->dev_link;
@@ -622,11 +622,11 @@ eth_link_update(struct rte_eth_dev *dev,
 
 static int
 eth_rx_queue_setup(struct rte_eth_dev *dev,
-                   uint16_t rx_queue_id,
-                   uint16_t nb_rx_desc __rte_unused,
-                   unsigned int socket_id __rte_unused,
-                   const struct rte_eth_rxconf *rx_conf __rte_unused,
-                   struct rte_mempool *mb_pool)
+		   uint16_t rx_queue_id,
+		   uint16_t nb_rx_desc __rte_unused,
+		   unsigned int socket_id __rte_unused,
+		   const struct rte_eth_rxconf *rx_conf __rte_unused,
+		   struct rte_mempool *mb_pool)
 {
 	struct pmd_internals *internals = dev->data->dev_private;
 	struct pkt_rx_queue *pkt_q = &internals->rx_queue[rx_queue_id];
@@ -660,10 +660,10 @@ eth_rx_queue_setup(struct rte_eth_dev *dev,
 
 static int
 eth_tx_queue_setup(struct rte_eth_dev *dev,
-                   uint16_t tx_queue_id,
-                   uint16_t nb_tx_desc __rte_unused,
-                   unsigned int socket_id __rte_unused,
-                   const struct rte_eth_txconf *tx_conf __rte_unused)
+		   uint16_t tx_queue_id,
+		   uint16_t nb_tx_desc __rte_unused,
+		   unsigned int socket_id __rte_unused,
+		   const struct rte_eth_txconf *tx_conf __rte_unused)
 {
 
 	struct pmd_internals *internals = dev->data->dev_private;
@@ -790,8 +790,8 @@ static const struct eth_dev_ops ops = {
  */
 static int
 open_packet_iface(const char *key __rte_unused,
-                  const char *value __rte_unused,
-                  void *extra_args)
+		  const char *value __rte_unused,
+		  void *extra_args)
 {
 	int *sockfd = extra_args;
 
@@ -854,17 +854,17 @@ get_fanout(const char *fanout_mode, int if_index)
 
 static int
 rte_pmd_init_internals(struct rte_vdev_device *dev,
-                       const int sockfd,
-                       const unsigned nb_queues,
-                       unsigned int blocksize,
-                       unsigned int blockcnt,
-                       unsigned int framesize,
-                       unsigned int framecnt,
+		       const int sockfd,
+		       const unsigned int nb_queues,
+		       unsigned int blocksize,
+		       unsigned int blockcnt,
+		       unsigned int framesize,
+		       unsigned int framecnt,
 		       unsigned int qdisc_bypass,
 		       const char *fanout_mode,
-                       struct pmd_internals **internals,
-                       struct rte_eth_dev **eth_dev,
-                       struct rte_kvargs *kvlist)
+		       struct pmd_internals **internals,
+		       struct rte_eth_dev **eth_dev,
+		       struct rte_kvargs *kvlist)
 {
 	const char *name = rte_vdev_device_name(dev);
 	const unsigned int numa_node = dev->device.numa_node;
@@ -890,7 +890,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
 	if (pair == NULL) {
 		PMD_LOG(ERR,
 			"%s: no interface specified for AF_PACKET ethdev",
-		        name);
+			name);
 		return -1;
 	}
 
@@ -899,7 +899,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
 		name, numa_node);
 
 	*internals = rte_zmalloc_socket(name, sizeof(**internals),
-	                                0, numa_node);
+					0, numa_node);
 	if (*internals == NULL)
 		return -1;
 
@@ -1140,8 +1140,8 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
 
 static int
 rte_eth_from_packet(struct rte_vdev_device *dev,
-                    int const *sockfd,
-                    struct rte_kvargs *kvlist)
+		    int const *sockfd,
+		    struct rte_kvargs *kvlist)
 {
 	const char *name = rte_vdev_device_name(dev);
 	struct pmd_internals *internals = NULL;
@@ -1172,7 +1172,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (qpairs < 1) {
 				PMD_LOG(ERR,
 					"%s: invalid qpairs value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1182,7 +1182,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (!blocksize) {
 				PMD_LOG(ERR,
 					"%s: invalid blocksize value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1192,7 +1192,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (!framesize) {
 				PMD_LOG(ERR,
 					"%s: invalid framesize value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1202,7 +1202,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (!framecount) {
 				PMD_LOG(ERR,
 					"%s: invalid framecount value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1226,7 +1226,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 	if (framesize > blocksize) {
 		PMD_LOG(ERR,
 			"%s: AF_PACKET MMAP frame size exceeds block size!",
-		        name);
+			name);
 		return -1;
 	}
 
@@ -1301,7 +1301,7 @@ rte_pmd_af_packet_probe(struct rte_vdev_device *dev)
 	if (rte_kvargs_count(kvlist, ETH_AF_PACKET_IFACE_ARG) == 1) {
 
 		ret = rte_kvargs_process(kvlist, ETH_AF_PACKET_IFACE_ARG,
-		                         &open_packet_iface, &sockfd);
+					 &open_packet_iface, &sockfd);
 		if (ret < 0)
 			goto exit;
 	}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 2/2] test: add test for af_packet
  2026-04-15 14:51 ` [PATCH v3 0/2] af_packet: cleanup and test Stephen Hemminger
  2026-04-15 14:51   ` [PATCH v3 1/2] net/af_packet: fix indentation Stephen Hemminger
@ 2026-04-15 14:51   ` Stephen Hemminger
  1 sibling, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-15 14:51 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a test for AF_PACKET PMD.
This test was generated with Claude AI based off of existing
test_pmd_ring.c with some cleanup afterwards.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build          |    1 +
 app/test/test_pmd_af_packet.c | 1174 +++++++++++++++++++++++++++++++++
 2 files changed, 1175 insertions(+)
 create mode 100644 app/test/test_pmd_af_packet.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c07..24f71fb987 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -141,6 +141,7 @@ source_file_deps = {
     'test_per_lcore.c': [],
     'test_pflock.c': [],
     'test_pie.c': ['sched'],
+    'test_pmd_af_packet.c': ['net_af_packet', 'ethdev', 'bus_vdev'],
     'test_pmd_perf.c': ['ethdev', 'net'] + packet_burst_generator_deps,
     'test_pmd_ring.c': ['net_ring', 'ethdev', 'bus_vdev'],
     'test_pmd_ring_perf.c': ['ethdev', 'net_ring', 'bus_vdev'],
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
new file mode 100644
index 0000000000..9cae573846
--- /dev/null
+++ b/app/test/test_pmd_af_packet.c
@@ -0,0 +1,1174 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/sockios.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_config.h>
+#include <rte_cycles.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_lcore.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_stdatomic.h>
+
+#include "test.h"
+
+#ifndef RTE_EXEC_ENV_LINUX
+static int
+test_pmd_af_packet(void)
+{
+	printf("af_packet only supported on Linux, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else
+
+#define NUM_MBUFS 512
+#define MBUF_CACHE_SIZE 32
+#define BURST_SIZE 32
+#define RING_SIZE 256
+
+/* Polling and timeout constants */
+#define STATS_POLL_INTERVAL_US 100
+#define STATS_POLL_TIMEOUT_US 100000  /* 100ms overall timeout */
+#define LOOPBACK_TIMEOUT_US 500000    /* 500ms for loopback test */
+
+/* Multi-threaded test constants */
+#define MT_NUM_PACKETS 100
+#define MT_TEST_DURATION_SEC 2
+
+/* Test device names */
+#define AF_PACKET_DEV_NAME "net_af_packet_test"
+#define TAP_DEV_NAME "dpdkafptest"
+
+static struct rte_mempool *mp;
+static uint16_t port_id = RTE_MAX_ETHPORTS;
+static int tap_fd = -1;
+static bool tap_created;
+static bool port_created;
+static bool port_started;
+
+/*
+ * Create a TAP interface for testing.
+ * Returns fd on success, -1 on failure.
+ */
+static int
+create_tap_interface(const char *name)
+{
+	struct ifreq ifr;
+	int fd, ret;
+
+	fd = open("/dev/net/tun", O_RDWR);
+	if (fd < 0) {
+		printf("Cannot open /dev/net/tun: %s\n", strerror(errno));
+		printf("(Are you running as root?)\n");
+		return -1;
+	}
+
+	memset(&ifr, 0, sizeof(ifr));
+	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+	snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
+
+	ret = ioctl(fd, TUNSETIFF, &ifr);
+	if (ret < 0) {
+		printf("Cannot create TAP interface '%s': %s\n",
+		       name, strerror(errno));
+		close(fd);
+		return -1;
+	}
+
+	/* Bring the interface up */
+	int sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock >= 0) {
+		memset(&ifr, 0, sizeof(ifr));
+		snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
+
+		/* Get current flags */
+		if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
+			ifr.ifr_flags |= IFF_UP;
+			ioctl(sock, SIOCSIFFLAGS, &ifr);
+		}
+		close(sock);
+	}
+
+	printf("Created TAP interface '%s'\n", name);
+	return fd;
+}
+
+static void
+destroy_tap_interface(int fd)
+{
+	if (fd >= 0)
+		close(fd);
+}
+
+static int
+create_af_packet_port(const char *name, const char *args, uint16_t *out_port_id)
+{
+	int ret;
+
+	ret = rte_vdev_init(name, args);
+	if (ret != 0) {
+		printf("Failed to create af_packet device '%s': %d\n", name, ret);
+		return ret;
+	}
+
+	ret = rte_eth_dev_get_port_by_name(name, out_port_id);
+	if (ret != 0) {
+		printf("Failed to get port id for '%s': %d\n", name, ret);
+		rte_vdev_uninit(name);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int
+configure_af_packet_port(uint16_t pid, uint16_t nb_rx_q, uint16_t nb_tx_q)
+{
+	struct rte_eth_conf port_conf = {0};
+	struct rte_eth_dev_info dev_info;
+	int ret;
+	uint16_t q;
+
+	ret = rte_eth_dev_info_get(pid, &dev_info);
+	if (ret != 0) {
+		printf("Failed to get device info for port %u: %d\n", pid, ret);
+		return ret;
+	}
+
+	ret = rte_eth_dev_configure(pid, nb_rx_q, nb_tx_q, &port_conf);
+	if (ret != 0) {
+		printf("Failed to configure port %u: %d\n", pid, ret);
+		return ret;
+	}
+
+	for (q = 0; q < nb_rx_q; q++) {
+		ret = rte_eth_rx_queue_setup(pid, q, RING_SIZE,
+					     rte_eth_dev_socket_id(pid),
+					     NULL, mp);
+		if (ret != 0) {
+			printf("Failed to setup RX queue %u for port %u: %d\n",
+			       q, pid, ret);
+			return ret;
+		}
+	}
+
+	for (q = 0; q < nb_tx_q; q++) {
+		ret = rte_eth_tx_queue_setup(pid, q, RING_SIZE,
+					     rte_eth_dev_socket_id(pid),
+					     NULL);
+		if (ret != 0) {
+			printf("Failed to setup TX queue %u for port %u: %d\n",
+			       q, pid, ret);
+			return ret;
+		}
+	}
+
+	ret = rte_eth_dev_start(pid);
+	if (ret != 0) {
+		printf("Failed to start port %u: %d\n", pid, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Helper: Allocate and prepare a burst of TX mbufs with minimal Ethernet frames.
+ * Returns the number of successfully allocated mbufs.
+ */
+static unsigned int
+alloc_tx_mbufs(struct rte_mbuf **bufs, unsigned int count)
+{
+	unsigned int i;
+	int ret;
+
+	ret = rte_pktmbuf_alloc_bulk(mp, bufs, count);
+	if (ret != 0)
+		return 0;
+
+	for (i = 0; i < count; i++) {
+		struct rte_ether_hdr *eth_hdr;
+		eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(bufs[i],
+				sizeof(struct rte_ether_hdr) + 46);
+		if (eth_hdr == NULL) {
+			/* Free all allocated mbufs on failure */
+			rte_pktmbuf_free_bulk(bufs, count);
+			return 0;
+		}
+
+		/* Set broadcast destination and zero source MAC */
+		memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN);
+		memset(&eth_hdr->src_addr, 0x00, RTE_ETHER_ADDR_LEN);
+		eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+	}
+
+	return count;
+}
+
+/*
+ * Helper: Transmit packets and free unsent mbufs.
+ * Returns the number of packets successfully transmitted.
+ */
+static uint16_t
+do_tx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
+	    unsigned int count)
+{
+	uint16_t nb_tx;
+
+	nb_tx = rte_eth_tx_burst(pid, queue_id, bufs, count);
+
+	/* Free any unsent mbufs */
+	rte_pktmbuf_free_bulk(&bufs[nb_tx], count - nb_tx);
+
+	return nb_tx;
+}
+
+/*
+ * Helper: Receive packets and free received mbufs.
+ * Returns the number of packets received.
+ */
+static uint16_t
+do_rx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
+	    unsigned int count)
+{
+	uint16_t nb_rx;
+
+	nb_rx = rte_eth_rx_burst(pid, queue_id, bufs, count);
+	rte_pktmbuf_free_bulk(bufs, nb_rx);
+
+	return nb_rx;
+}
+
+/*
+ * Helper: Wait for stats to update by polling at intervals.
+ * Returns 0 on success (stats updated), -1 on timeout.
+ */
+static int
+wait_for_stats_update(uint16_t pid, uint64_t expected_opackets,
+		      uint64_t timeout_us)
+{
+	struct rte_eth_stats stats;
+	uint64_t elapsed = 0;
+
+	while (elapsed < timeout_us) {
+		if (rte_eth_stats_get(pid, &stats) == 0) {
+			if (stats.opackets >= expected_opackets)
+				return 0;
+		}
+		rte_delay_us_block(STATS_POLL_INTERVAL_US);
+		elapsed += STATS_POLL_INTERVAL_US;
+	}
+
+	return -1;
+}
+
+static int
+test_af_packet_setup(void)
+{
+	char devargs[256];
+	int ret;
+
+	/* Create mempool for mbufs */
+	mp = rte_pktmbuf_pool_create("af_packet_test_pool", NUM_MBUFS,
+				     MBUF_CACHE_SIZE, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE,
+				     rte_socket_id());
+	if (mp == NULL) {
+		printf("Failed to create mempool\n");
+		return -1;
+	}
+
+	/* Create TAP interface for testing */
+	tap_fd = create_tap_interface(TAP_DEV_NAME);
+	if (tap_fd >= 0)
+		tap_created = true;
+	else {
+		printf("TAP interface creation failed - tests will be skipped\n");
+		return 0; /* Return success to allow skipped tests */
+	}
+
+	/* Create and configure af_packet port */
+	snprintf(devargs, sizeof(devargs), "iface=%s", TAP_DEV_NAME);
+	ret = create_af_packet_port(AF_PACKET_DEV_NAME, devargs, &port_id);
+	if (ret != 0) {
+		printf("Failed to create af_packet port\n");
+		return -1;
+	}
+	port_created = true;
+
+	ret = configure_af_packet_port(port_id, 1, 1);
+	if (ret != 0) {
+		printf("Failed to configure af_packet port\n");
+		return -1;
+	}
+	port_started = true;
+
+	return 0;
+}
+
+static void
+test_af_packet_teardown(void)
+{
+	/* Stop and close test port */
+	if (port_started) {
+		rte_eth_dev_stop(port_id);
+		port_started = false;
+	}
+
+	if (port_created) {
+		rte_eth_dev_close(port_id);
+		rte_vdev_uninit(AF_PACKET_DEV_NAME);
+		port_id = RTE_MAX_ETHPORTS;
+		port_created = false;
+	}
+
+	/* Destroy TAP interface */
+	if (tap_created) {
+		destroy_tap_interface(tap_fd);
+		tap_fd = -1;
+		tap_created = false;
+	}
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+}
+
+/*
+ * Test: Device info verification
+ */
+static int
+test_af_packet_dev_info(void)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	/* Verify expected device info values */
+	TEST_ASSERT(dev_info.max_mac_addrs == 1,
+		    "Expected max_mac_addrs=1, got %u", dev_info.max_mac_addrs);
+
+	/* The driver has default internal frame size of 2K */
+	TEST_ASSERT(dev_info.max_rx_pktlen >= RTE_ETHER_MAX_LEN,
+		    "max_rx_pktlen %u < %u",
+		    dev_info.max_rx_pktlen, RTE_ETHER_MAX_LEN);
+	TEST_ASSERT(dev_info.max_mtu >= RTE_ETHER_MTU,
+		    "max_mtu %u < %u", dev_info.max_mtu, RTE_ETHER_MTU);
+	TEST_ASSERT(dev_info.min_rx_bufsize == 0,
+		    "Expected min_rx_bufsize=0, got %u", dev_info.min_rx_bufsize);
+	TEST_ASSERT(dev_info.max_rx_queues >= 1, "No RX queues available");
+	TEST_ASSERT(dev_info.max_tx_queues >= 1, "No TX queues available");
+	TEST_ASSERT(dev_info.if_index > 0, "Invalid interface index");
+
+	/* Check TX offload capabilities */
+	TEST_ASSERT(dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MULTI_SEGS,
+		    "Expected MULTI_SEGS TX offload capability");
+	TEST_ASSERT(dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_VLAN_INSERT,
+		    "Expected VLAN_INSERT TX offload capability");
+
+	/* Check RX offload capabilities */
+	TEST_ASSERT(dev_info.rx_offload_capa & RTE_ETH_RX_OFFLOAD_VLAN_STRIP,
+		    "Expected VLAN_STRIP RX offload capability");
+	TEST_ASSERT(dev_info.rx_offload_capa & RTE_ETH_RX_OFFLOAD_TIMESTAMP,
+		    "Expected TIMESTAMP RX offload capability");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Link status
+ * Note: af_packet PMD link status reflects the underlying interface state,
+ * not the DPDK device start/stop state.
+ */
+static int
+test_af_packet_link_status(void)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_link_get_nowait(port_id, &link);
+	TEST_ASSERT(ret == 0, "Failed to get link status");
+
+	/* TAP interface was brought up during setup, so link should be UP */
+	TEST_ASSERT(link.link_status == RTE_ETH_LINK_UP,
+		    "Expected link UP (TAP interface is up)");
+	TEST_ASSERT(link.link_speed == RTE_ETH_SPEED_NUM_10G,
+		    "Expected 10G link speed");
+	TEST_ASSERT(link.link_duplex == RTE_ETH_LINK_FULL_DUPLEX,
+		    "Expected full duplex");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Statistics initial state
+ */
+static int
+test_af_packet_stats_init(void)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Reset stats */
+	ret = rte_eth_stats_reset(port_id);
+	TEST_ASSERT(ret == 0, "Failed to reset stats");
+
+	/* Get initial stats */
+	ret = rte_eth_stats_get(port_id, &stats);
+	TEST_ASSERT(ret == 0, "Failed to get stats");
+
+	/* After reset, stats should be zero */
+	TEST_ASSERT(stats.ipackets == 0,
+		    "Expected ipackets=0 after reset, got %"PRIu64,
+		    stats.ipackets);
+	TEST_ASSERT(stats.opackets == 0,
+		    "Expected opackets=0 after reset, got %"PRIu64,
+		    stats.opackets);
+	TEST_ASSERT(stats.ibytes == 0,
+		    "Expected ibytes=0 after reset, got %"PRIu64,
+		    stats.ibytes);
+	TEST_ASSERT(stats.obytes == 0,
+		    "Expected obytes=0 after reset, got %"PRIu64,
+		    stats.obytes);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: TX packets (packets will be sent to TAP interface)
+ */
+static int
+test_af_packet_tx(void)
+{
+	struct rte_mbuf *bufs[BURST_SIZE];
+	struct rte_eth_stats stats;
+	uint16_t nb_tx;
+	unsigned int allocated;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Reset stats */
+	ret = rte_eth_stats_reset(port_id);
+	TEST_ASSERT(ret == 0, "Failed to reset stats");
+
+	/* Allocate and prepare mbufs for TX using helper */
+	allocated = alloc_tx_mbufs(bufs, BURST_SIZE);
+	TEST_ASSERT(allocated == BURST_SIZE, "Failed to allocate all mbufs");
+
+	/* TX burst using helper */
+	nb_tx = do_tx_burst(port_id, 0, bufs, BURST_SIZE);
+
+	/* Poll for stats update instead of fixed delay */
+	if (nb_tx > 0) {
+		ret = wait_for_stats_update(port_id, nb_tx, STATS_POLL_TIMEOUT_US);
+		if (ret == 0) {
+			ret = rte_eth_stats_get(port_id, &stats);
+			TEST_ASSERT(ret == 0, "Failed to get stats");
+			TEST_ASSERT(stats.opackets > 0,
+				    "Expected opackets > 0 after TX");
+		}
+		/* Timeout is acceptable - stats may not update immediately */
+	}
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: RX packets (non-blocking, may not receive anything)
+ */
+static int
+test_af_packet_rx(void)
+{
+	struct rte_mbuf *bufs[BURST_SIZE];
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Try to receive packets (non-blocking) using helper */
+	do_rx_burst(port_id, 0, bufs, BURST_SIZE);
+
+	/* RX from tap interface without external traffic will return 0 */
+	/* This test just verifies the RX path doesn't crash */
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Loopback - TX packets and receive them back via TAP interface.
+ * This exercises both the TX and RX paths in a round-trip.
+ */
+static int
+test_af_packet_loopback(void)
+{
+	struct rte_mbuf *tx_bufs[BURST_SIZE];
+	struct rte_mbuf *rx_bufs[BURST_SIZE];
+	uint16_t nb_tx, total_rx = 0;
+	uint64_t elapsed = 0;
+	unsigned int allocated;
+	char tap_buf[2048];
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Set TAP fd to non-blocking for reading */
+	int flags = fcntl(tap_fd, F_GETFL, 0);
+	fcntl(tap_fd, F_SETFL, flags | O_NONBLOCK);
+
+	/* Allocate and send packets */
+	allocated = alloc_tx_mbufs(tx_bufs, BURST_SIZE);
+	if (allocated == 0) {
+		printf("SKIPPED: Could not allocate mbufs\n");
+		return TEST_SKIPPED;
+	}
+
+	nb_tx = do_tx_burst(port_id, 0, tx_bufs, allocated);
+	if (nb_tx == 0) {
+		printf("SKIPPED: No packets transmitted\n");
+		return TEST_SKIPPED;
+	}
+
+	/*
+	 * Read packets from TAP interface (they were sent there by af_packet).
+	 * Then write them back to TAP so af_packet can receive them.
+	 */
+	while (elapsed < LOOPBACK_TIMEOUT_US) {
+		ssize_t bytes_read, bytes_written;
+
+		/* Read from TAP (what af_packet sent) */
+		bytes_read = read(tap_fd, tap_buf, sizeof(tap_buf));
+		if (bytes_read > 0) {
+			/* Write back to TAP for af_packet to receive */
+			bytes_written = write(tap_fd, tap_buf, bytes_read);
+			TEST_ASSERT(bytes_read == bytes_written, "TAP write %zd != %zd",
+				    bytes_read, bytes_written);
+		}
+
+		/* Try to receive via af_packet RX */
+		uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, rx_bufs, BURST_SIZE);
+		if (nb_rx > 0) {
+			total_rx += nb_rx;
+			rte_pktmbuf_free_bulk(rx_bufs, nb_rx);
+		}
+
+		/* Check if we've received enough */
+		if (total_rx >= nb_tx)
+			break;
+
+		rte_delay_us_block(STATS_POLL_INTERVAL_US);
+		elapsed += STATS_POLL_INTERVAL_US;
+	}
+
+	/* Restore TAP fd flags */
+	fcntl(tap_fd, F_SETFL, flags);
+
+	/*
+	 * Note: We may not receive all packets due to timing, but receiving
+	 * any packets proves the loopback path works.
+	 */
+	printf("Loopback: TX=%u, RX=%u\n", nb_tx, total_rx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Promiscuous mode
+ */
+static int
+test_af_packet_promiscuous(void)
+{
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port_id);
+	TEST_ASSERT(ret == 0, "Failed to enable promiscuous mode");
+
+	ret = rte_eth_promiscuous_get(port_id);
+	TEST_ASSERT(ret == 1, "Expected promiscuous mode enabled");
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port_id);
+	TEST_ASSERT(ret == 0, "Failed to disable promiscuous mode");
+
+	ret = rte_eth_promiscuous_get(port_id);
+	TEST_ASSERT(ret == 0, "Expected promiscuous mode disabled");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MAC address operations
+ */
+static int
+test_af_packet_mac_addr(void)
+{
+	struct rte_ether_addr mac_addr;
+	struct rte_ether_addr new_mac = {
+		.addr_bytes = {0x02, 0x11, 0x22, 0x33, 0x44, 0x55}
+	};
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Get current MAC address */
+	ret = rte_eth_macaddr_get(port_id, &mac_addr);
+	TEST_ASSERT(ret == 0, "Failed to get MAC address");
+
+	/* Set new MAC address (use locally administered address) */
+	ret = rte_eth_dev_default_mac_addr_set(port_id, &new_mac);
+	TEST_ASSERT(ret == 0, "Failed to set MAC address");
+
+	/* Verify MAC was set */
+	ret = rte_eth_macaddr_get(port_id, &mac_addr);
+	TEST_ASSERT(ret == 0, "Failed to get MAC address after set");
+
+	TEST_ASSERT(memcmp(&mac_addr, &new_mac, sizeof(mac_addr)) == 0,
+		    "MAC address mismatch after set");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MTU operations
+ * Get min/max supported MTU from device info and verify setting works.
+ */
+static int
+test_af_packet_mtu(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint16_t mtu, original_mtu;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Get device info for min/max MTU */
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port_id, &original_mtu);
+	TEST_ASSERT(ret == 0, "Failed to get MTU");
+
+	printf("MTU: current=%u, min=%u, max=%u\n",
+	       original_mtu, dev_info.min_mtu, dev_info.max_mtu);
+
+	/* Test setting minimum MTU if supported and different from current */
+	if (dev_info.min_mtu > 0 && dev_info.min_mtu != original_mtu) {
+		ret = rte_eth_dev_set_mtu(port_id, dev_info.min_mtu);
+		if (ret == 0) {
+			ret = rte_eth_dev_get_mtu(port_id, &mtu);
+			TEST_ASSERT(ret == 0, "Failed to get MTU after set to min");
+			TEST_ASSERT(mtu == dev_info.min_mtu,
+				    "MTU not set to min: expected %u, got %u",
+				    dev_info.min_mtu, mtu);
+		}
+		/* MTU set may fail depending on interface, that's acceptable */
+	}
+
+	/* Test setting maximum MTU if supported and different from current */
+	if (dev_info.max_mtu > 0 && dev_info.max_mtu != original_mtu &&
+	    dev_info.max_mtu != dev_info.min_mtu) {
+		ret = rte_eth_dev_set_mtu(port_id, dev_info.max_mtu);
+		if (ret == 0) {
+			ret = rte_eth_dev_get_mtu(port_id, &mtu);
+			TEST_ASSERT(ret == 0, "Failed to get MTU after set to max");
+			TEST_ASSERT(mtu == dev_info.max_mtu,
+				    "MTU not set to max: expected %u, got %u",
+				    dev_info.max_mtu, mtu);
+		}
+		/* MTU set may fail depending on interface capabilities */
+	}
+
+	/* Restore original MTU */
+	rte_eth_dev_set_mtu(port_id, original_mtu);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Stats reset verification
+ */
+static int
+test_af_packet_stats_reset(void)
+{
+	struct rte_eth_stats stats;
+	struct rte_mbuf *bufs[BURST_SIZE / 2];
+	uint16_t nb_tx;
+	unsigned int allocated;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Generate some TX traffic using helpers */
+	allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 2);
+	nb_tx = do_tx_burst(port_id, 0, bufs, allocated);
+
+	/* Poll for stats to update */
+	if (nb_tx > 0)
+		wait_for_stats_update(port_id, nb_tx, STATS_POLL_TIMEOUT_US);
+
+	/* Reset stats */
+	ret = rte_eth_stats_reset(port_id);
+	TEST_ASSERT(ret == 0, "Failed to reset stats");
+
+	/* Verify stats are zero */
+	ret = rte_eth_stats_get(port_id, &stats);
+	TEST_ASSERT(ret == 0, "Failed to get stats after reset");
+
+	TEST_ASSERT(stats.ipackets == 0,
+		    "Expected ipackets=0, got %"PRIu64, stats.ipackets);
+	TEST_ASSERT(stats.opackets == 0,
+		    "Expected opackets=0, got %"PRIu64, stats.opackets);
+	TEST_ASSERT(stats.ibytes == 0,
+		    "Expected ibytes=0, got %"PRIu64, stats.ibytes);
+	TEST_ASSERT(stats.obytes == 0,
+		    "Expected obytes=0, got %"PRIu64, stats.obytes);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Invalid configuration handling - missing iface
+ */
+static int
+test_af_packet_invalid_no_iface(void)
+{
+	int ret;
+
+	/* Test without iface argument (should fail) */
+	ret = rte_vdev_init("net_af_packet_invalid1", "");
+	TEST_ASSERT(ret != 0, "Expected failure without iface argument");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Invalid configuration handling - non-existent interface
+ */
+static int
+test_af_packet_invalid_bad_iface(void)
+{
+	int ret;
+
+	/* Test with non-existent interface (should fail) */
+	ret = rte_vdev_init("net_af_packet_invalid2",
+			    "iface=nonexistent_iface_12345");
+	TEST_ASSERT(ret != 0, "Expected failure with non-existent interface");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Invalid configuration handling - invalid qpairs
+ */
+static int
+test_af_packet_invalid_qpairs(void)
+{
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Test with invalid qpairs (should fail) */
+	ret = rte_vdev_init("net_af_packet_invalid3",
+			    "iface=" TAP_DEV_NAME ",qpairs=0");
+	TEST_ASSERT(ret != 0, "Expected failure with qpairs=0");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Custom frame size configuration
+ */
+static int
+test_af_packet_frame_config(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint16_t test_port;
+	char devargs[256];
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Create with custom frame parameters */
+	snprintf(devargs, sizeof(devargs),
+		 "iface=%s,blocksz=4096,framesz=2048,framecnt=256",
+		 TAP_DEV_NAME);
+
+	ret = rte_vdev_init("net_af_packet_frame_test", devargs);
+	if (ret != 0) {
+		printf("SKIPPED: Could not create port with custom frame config\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_get_port_by_name("net_af_packet_frame_test",
+					   &test_port);
+	TEST_ASSERT(ret == 0, "Failed to get port id");
+
+	ret = rte_eth_dev_info_get(test_port, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	/* Cleanup */
+	rte_eth_dev_close(test_port);
+	rte_vdev_uninit("net_af_packet_frame_test");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Qdisc bypass configuration
+ */
+static int
+test_af_packet_qdisc_bypass(void)
+{
+	uint16_t test_port;
+	char devargs[256];
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Create with qdisc_bypass enabled */
+	snprintf(devargs, sizeof(devargs), "iface=%s,qdisc_bypass=1",
+		 TAP_DEV_NAME);
+
+	ret = rte_vdev_init("net_af_packet_qdisc_test", devargs);
+	if (ret != 0) {
+		printf("SKIPPED: qdisc_bypass may not be supported\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_get_port_by_name("net_af_packet_qdisc_test",
+					   &test_port);
+	TEST_ASSERT(ret == 0, "Failed to get port id");
+
+	/* Cleanup */
+	rte_eth_dev_close(test_port);
+	rte_vdev_uninit("net_af_packet_qdisc_test");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Multiple queue pairs
+ */
+static int
+test_af_packet_multi_queue(void)
+{
+	struct rte_eth_dev_info dev_info;
+	struct rte_eth_conf port_conf = {0};
+	struct rte_mbuf *bufs[BURST_SIZE];
+	uint16_t test_port;
+	char devargs[256];
+	const uint16_t num_queues = 2;
+	unsigned int allocated, q;
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	snprintf(devargs, sizeof(devargs), "iface=%s,qpairs=%u",
+		 TAP_DEV_NAME, num_queues);
+	ret = rte_vdev_init("net_af_packet_multi_q", devargs);
+	if (ret != 0) {
+		printf("SKIPPED: Could not create multi-queue port\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_get_port_by_name("net_af_packet_multi_q", &test_port);
+	TEST_ASSERT(ret == 0, "Failed to get port id for multi-queue port");
+
+	ret = rte_eth_dev_info_get(test_port, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	TEST_ASSERT(dev_info.max_rx_queues >= num_queues,
+		    "Expected at least %u RX queues, got %u",
+		    num_queues, dev_info.max_rx_queues);
+	TEST_ASSERT(dev_info.max_tx_queues >= num_queues,
+		    "Expected at least %u TX queues, got %u",
+		    num_queues, dev_info.max_tx_queues);
+
+	/* Configure the port */
+	ret = rte_eth_dev_configure(test_port, num_queues, num_queues, &port_conf);
+	TEST_ASSERT(ret == 0, "Failed to configure multi-queue port");
+
+	for (q = 0; q < num_queues; q++) {
+		ret = rte_eth_rx_queue_setup(test_port, q, RING_SIZE,
+					     rte_eth_dev_socket_id(test_port),
+					     NULL, mp);
+		TEST_ASSERT(ret == 0, "Failed to setup RX queue %u", q);
+
+		ret = rte_eth_tx_queue_setup(test_port, q, RING_SIZE,
+					     rte_eth_dev_socket_id(test_port),
+					     NULL);
+		TEST_ASSERT(ret == 0, "Failed to setup TX queue %u", q);
+	}
+
+	ret = rte_eth_dev_start(test_port);
+	TEST_ASSERT(ret == 0, "Failed to start multi-queue port");
+
+	/* Test TX on different queues using shared helpers */
+	for (q = 0; q < num_queues; q++) {
+		allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 2);
+		if (allocated > 0)
+			do_tx_burst(test_port, q, bufs, allocated);
+	}
+
+	/* Test RX on different queues */
+	for (q = 0; q < num_queues; q++)
+		do_rx_burst(test_port, q, bufs, BURST_SIZE);
+
+	/* Cleanup */
+	rte_eth_dev_stop(test_port);
+	rte_eth_dev_close(test_port);
+	rte_vdev_uninit("net_af_packet_multi_q");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Multi-threaded test context
+ */
+struct mt_test_ctx {
+	uint16_t port_id;
+	uint32_t stop;
+	uint32_t tx_count;
+	uint32_t rx_count;
+	int tx_error;
+	int rx_error;
+};
+
+/*
+ * Writer thread function for multi-threaded test
+ */
+static void *
+mt_writer_thread(void *arg)
+{
+	struct mt_test_ctx *ctx = arg;
+	struct rte_mbuf *bufs[BURST_SIZE];
+	unsigned int allocated;
+	uint16_t nb_tx;
+
+	while (!rte_atomic_load_explicit(&ctx->stop, rte_memory_order_acquire)) {
+		allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 4);
+		if (allocated == 0) {
+			ctx->tx_error = -ENOMEM;
+			break;
+		}
+
+		nb_tx = do_tx_burst(ctx->port_id, 0, bufs, allocated);
+		rte_atomic_fetch_add_explicit(&ctx->tx_count, nb_tx,
+					      rte_memory_order_relaxed);
+
+		/* Small delay between bursts */
+		rte_delay_us_block(100);
+	}
+
+	return NULL;
+}
+
+/*
+ * Reader thread function for multi-threaded test
+ */
+static void *
+mt_reader_thread(void *arg)
+{
+	struct mt_test_ctx *ctx = arg;
+	struct rte_mbuf *bufs[BURST_SIZE];
+	uint16_t nb_rx;
+
+	while (!rte_atomic_load_explicit(&ctx->stop, rte_memory_order_acquire)) {
+		nb_rx = rte_eth_rx_burst(ctx->port_id, 0, bufs, BURST_SIZE);
+		if (nb_rx > 0) {
+			rte_atomic_fetch_add_explicit(&ctx->rx_count, nb_rx,
+						      rte_memory_order_relaxed);
+			rte_pktmbuf_free_bulk(bufs, nb_rx);
+		}
+
+		/* Small delay between polls */
+		rte_delay_us_block(50);
+	}
+
+	return NULL;
+}
+
+/*
+ * Test: Multi-threaded concurrent TX and RX
+ * This test creates separate writer and reader threads to exercise
+ * concurrent access patterns to the af_packet PMD.
+ */
+static int
+test_af_packet_multithread(void)
+{
+	struct mt_test_ctx ctx = {0};
+	pthread_t writer_tid, reader_tid;
+	struct timespec start, now;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Initialize context */
+	ctx.port_id = port_id;
+	rte_atomic_store_explicit(&ctx.stop, 0, rte_memory_order_release);
+	rte_atomic_store_explicit(&ctx.tx_count, 0, rte_memory_order_release);
+	rte_atomic_store_explicit(&ctx.rx_count, 0, rte_memory_order_release);
+	ctx.tx_error = 0;
+	ctx.rx_error = 0;
+
+	/* Start writer thread */
+	ret = pthread_create(&writer_tid, NULL, mt_writer_thread, &ctx);
+	if (ret != 0) {
+		printf("SKIPPED: Failed to create writer thread: %d\n", ret);
+		return TEST_SKIPPED;
+	}
+
+	/* Start reader thread */
+	ret = pthread_create(&reader_tid, NULL, mt_reader_thread, &ctx);
+	if (ret != 0) {
+		rte_atomic_store_explicit(&ctx.stop, 1, rte_memory_order_release);
+		pthread_join(writer_tid, NULL);
+		printf("SKIPPED: Failed to create reader thread: %d\n", ret);
+		return TEST_SKIPPED;
+	}
+
+	/* Run for configured duration */
+	clock_gettime(CLOCK_MONOTONIC, &start);
+	do {
+		rte_delay_us_block(100000); /* 100ms */
+		clock_gettime(CLOCK_MONOTONIC, &now);
+	} while ((now.tv_sec - start.tv_sec) < MT_TEST_DURATION_SEC);
+
+	/* Signal threads to stop */
+	rte_atomic_store_explicit(&ctx.stop, 1, rte_memory_order_release);
+
+	/* Wait for threads to finish */
+	pthread_join(writer_tid, NULL);
+	pthread_join(reader_tid, NULL);
+
+	/* Report results */
+	printf("Multi-threaded test: TX=%u, RX=%u (duration=%ds)\n",
+	       rte_atomic_load_explicit(&ctx.tx_count, rte_memory_order_relaxed),
+	       rte_atomic_load_explicit(&ctx.rx_count, rte_memory_order_relaxed),
+	       MT_TEST_DURATION_SEC);
+
+	/* Check for errors */
+	TEST_ASSERT(ctx.tx_error == 0,
+		    "Writer thread encountered error: %d", ctx.tx_error);
+	TEST_ASSERT(ctx.rx_error == 0,
+		    "Reader thread encountered error: %d", ctx.rx_error);
+
+	/* Verify some packets were transmitted */
+	TEST_ASSERT(rte_atomic_load_explicit(&ctx.tx_count,
+					     rte_memory_order_relaxed) > 0,
+		    "Expected some packets to be transmitted");
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite af_packet_test_suite = {
+	.suite_name = "AF_PACKET PMD Unit Test Suite",
+	.setup = test_af_packet_setup,
+	.teardown = test_af_packet_teardown,
+	.unit_test_cases = {
+		/* Tests that don't modify device state */
+		TEST_CASE(test_af_packet_dev_info),
+		TEST_CASE(test_af_packet_link_status),
+		TEST_CASE(test_af_packet_stats_init),
+		TEST_CASE(test_af_packet_tx),
+		TEST_CASE(test_af_packet_rx),
+		TEST_CASE(test_af_packet_loopback),
+		TEST_CASE(test_af_packet_promiscuous),
+		TEST_CASE(test_af_packet_mac_addr),
+		TEST_CASE(test_af_packet_mtu),
+		TEST_CASE(test_af_packet_stats_reset),
+		TEST_CASE(test_af_packet_multithread),
+
+		/* Tests that create their own devices */
+		TEST_CASE(test_af_packet_invalid_no_iface),
+		TEST_CASE(test_af_packet_invalid_bad_iface),
+		TEST_CASE(test_af_packet_invalid_qpairs),
+		TEST_CASE(test_af_packet_frame_config),
+		TEST_CASE(test_af_packet_qdisc_bypass),
+		TEST_CASE(test_af_packet_multi_queue),
+
+		TEST_CASES_END() /**< NULL terminate unit test array */
+	}
+};
+
+static int
+test_pmd_af_packet(void)
+{
+	return unit_test_suite_runner(&af_packet_test_suite);
+}
+#endif
+
+REGISTER_FAST_TEST(af_packet_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_af_packet);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 0/2] af_packet: cleanup and test
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (7 preceding siblings ...)
  2026-04-15 14:51 ` [PATCH v3 0/2] af_packet: cleanup and test Stephen Hemminger
@ 2026-04-16 17:57 ` Stephen Hemminger
  2026-04-16 17:57   ` [PATCH v4 1/2] net/af_packet: fix indentation Stephen Hemminger
  2026-04-16 17:57   ` [PATCH v4 2/2] test: add test for af_packet Stephen Hemminger
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
  9 siblings, 2 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-16 17:57 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

A couple of small patches for the af_packet PMD.
Fix indentation and add a basic test.

Stephen Hemminger (2):
  net/af_packet: fix indentation
  test: add test for af_packet

v4 - fix test to use stdatomic correctly

Stephen Hemminger (2):
  net/af_packet: fix indentation
  test: add test for af_packet

 app/test/meson.build                      |    1 +
 app/test/test_pmd_af_packet.c             | 1122 +++++++++++++++++++++
 drivers/net/af_packet/rte_eth_af_packet.c |   64 +-
 3 files changed, 1155 insertions(+), 32 deletions(-)
 create mode 100644 app/test/test_pmd_af_packet.c

-- 
2.53.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH v4 1/2] net/af_packet: fix indentation
  2026-04-16 17:57 ` [PATCH v4 0/2] af_packet: cleanup and test Stephen Hemminger
@ 2026-04-16 17:57   ` Stephen Hemminger
  2026-04-16 17:57   ` [PATCH v4 2/2] test: add test for af_packet Stephen Hemminger
  1 sibling, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-16 17:57 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, John W. Linville

DPDK code is supposed to be indented with TABS not spaces.
Also, used "unsigned int" to keep checkpatch happy.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/af_packet/rte_eth_af_packet.c | 64 +++++++++++------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/drivers/net/af_packet/rte_eth_af_packet.c b/drivers/net/af_packet/rte_eth_af_packet.c
index 0ee94e71ea..8303ff5ca9 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -79,7 +79,7 @@ struct __rte_cache_aligned pkt_tx_queue {
 };
 
 struct pmd_internals {
-	unsigned nb_queues;
+	unsigned int nb_queues;
 
 	int if_index;
 	char *if_name;
@@ -602,7 +602,7 @@ eth_dev_close(struct rte_eth_dev *dev)
 
 static int
 eth_link_update(struct rte_eth_dev *dev,
-                int wait_to_complete __rte_unused)
+		int wait_to_complete __rte_unused)
 {
 	const struct pmd_internals *internals = dev->data->dev_private;
 	struct rte_eth_link *dev_link = &dev->data->dev_link;
@@ -622,11 +622,11 @@ eth_link_update(struct rte_eth_dev *dev,
 
 static int
 eth_rx_queue_setup(struct rte_eth_dev *dev,
-                   uint16_t rx_queue_id,
-                   uint16_t nb_rx_desc __rte_unused,
-                   unsigned int socket_id __rte_unused,
-                   const struct rte_eth_rxconf *rx_conf __rte_unused,
-                   struct rte_mempool *mb_pool)
+		   uint16_t rx_queue_id,
+		   uint16_t nb_rx_desc __rte_unused,
+		   unsigned int socket_id __rte_unused,
+		   const struct rte_eth_rxconf *rx_conf __rte_unused,
+		   struct rte_mempool *mb_pool)
 {
 	struct pmd_internals *internals = dev->data->dev_private;
 	struct pkt_rx_queue *pkt_q = &internals->rx_queue[rx_queue_id];
@@ -660,10 +660,10 @@ eth_rx_queue_setup(struct rte_eth_dev *dev,
 
 static int
 eth_tx_queue_setup(struct rte_eth_dev *dev,
-                   uint16_t tx_queue_id,
-                   uint16_t nb_tx_desc __rte_unused,
-                   unsigned int socket_id __rte_unused,
-                   const struct rte_eth_txconf *tx_conf __rte_unused)
+		   uint16_t tx_queue_id,
+		   uint16_t nb_tx_desc __rte_unused,
+		   unsigned int socket_id __rte_unused,
+		   const struct rte_eth_txconf *tx_conf __rte_unused)
 {
 
 	struct pmd_internals *internals = dev->data->dev_private;
@@ -790,8 +790,8 @@ static const struct eth_dev_ops ops = {
  */
 static int
 open_packet_iface(const char *key __rte_unused,
-                  const char *value __rte_unused,
-                  void *extra_args)
+		  const char *value __rte_unused,
+		  void *extra_args)
 {
 	int *sockfd = extra_args;
 
@@ -854,17 +854,17 @@ get_fanout(const char *fanout_mode, int if_index)
 
 static int
 rte_pmd_init_internals(struct rte_vdev_device *dev,
-                       const int sockfd,
-                       const unsigned nb_queues,
-                       unsigned int blocksize,
-                       unsigned int blockcnt,
-                       unsigned int framesize,
-                       unsigned int framecnt,
+		       const int sockfd,
+		       const unsigned int nb_queues,
+		       unsigned int blocksize,
+		       unsigned int blockcnt,
+		       unsigned int framesize,
+		       unsigned int framecnt,
 		       unsigned int qdisc_bypass,
 		       const char *fanout_mode,
-                       struct pmd_internals **internals,
-                       struct rte_eth_dev **eth_dev,
-                       struct rte_kvargs *kvlist)
+		       struct pmd_internals **internals,
+		       struct rte_eth_dev **eth_dev,
+		       struct rte_kvargs *kvlist)
 {
 	const char *name = rte_vdev_device_name(dev);
 	const unsigned int numa_node = dev->device.numa_node;
@@ -890,7 +890,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
 	if (pair == NULL) {
 		PMD_LOG(ERR,
 			"%s: no interface specified for AF_PACKET ethdev",
-		        name);
+			name);
 		return -1;
 	}
 
@@ -899,7 +899,7 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
 		name, numa_node);
 
 	*internals = rte_zmalloc_socket(name, sizeof(**internals),
-	                                0, numa_node);
+					0, numa_node);
 	if (*internals == NULL)
 		return -1;
 
@@ -1140,8 +1140,8 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
 
 static int
 rte_eth_from_packet(struct rte_vdev_device *dev,
-                    int const *sockfd,
-                    struct rte_kvargs *kvlist)
+		    int const *sockfd,
+		    struct rte_kvargs *kvlist)
 {
 	const char *name = rte_vdev_device_name(dev);
 	struct pmd_internals *internals = NULL;
@@ -1172,7 +1172,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (qpairs < 1) {
 				PMD_LOG(ERR,
 					"%s: invalid qpairs value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1182,7 +1182,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (!blocksize) {
 				PMD_LOG(ERR,
 					"%s: invalid blocksize value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1192,7 +1192,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (!framesize) {
 				PMD_LOG(ERR,
 					"%s: invalid framesize value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1202,7 +1202,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 			if (!framecount) {
 				PMD_LOG(ERR,
 					"%s: invalid framecount value",
-				        name);
+					name);
 				return -1;
 			}
 			continue;
@@ -1226,7 +1226,7 @@ rte_eth_from_packet(struct rte_vdev_device *dev,
 	if (framesize > blocksize) {
 		PMD_LOG(ERR,
 			"%s: AF_PACKET MMAP frame size exceeds block size!",
-		        name);
+			name);
 		return -1;
 	}
 
@@ -1301,7 +1301,7 @@ rte_pmd_af_packet_probe(struct rte_vdev_device *dev)
 	if (rte_kvargs_count(kvlist, ETH_AF_PACKET_IFACE_ARG) == 1) {
 
 		ret = rte_kvargs_process(kvlist, ETH_AF_PACKET_IFACE_ARG,
-		                         &open_packet_iface, &sockfd);
+					 &open_packet_iface, &sockfd);
 		if (ret < 0)
 			goto exit;
 	}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 2/2] test: add test for af_packet
  2026-04-16 17:57 ` [PATCH v4 0/2] af_packet: cleanup and test Stephen Hemminger
  2026-04-16 17:57   ` [PATCH v4 1/2] net/af_packet: fix indentation Stephen Hemminger
@ 2026-04-16 17:57   ` Stephen Hemminger
  1 sibling, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-16 17:57 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add a test for AF_PACKET PMD.
This test was generated with Claude AI based off of existing
test_pmd_ring.c with some cleanup afterwards.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build          |    1 +
 app/test/test_pmd_af_packet.c | 1122 +++++++++++++++++++++++++++++++++
 2 files changed, 1123 insertions(+)
 create mode 100644 app/test/test_pmd_af_packet.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 7d458f9c07..24f71fb987 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -141,6 +141,7 @@ source_file_deps = {
     'test_per_lcore.c': [],
     'test_pflock.c': [],
     'test_pie.c': ['sched'],
+    'test_pmd_af_packet.c': ['net_af_packet', 'ethdev', 'bus_vdev'],
     'test_pmd_perf.c': ['ethdev', 'net'] + packet_burst_generator_deps,
     'test_pmd_ring.c': ['net_ring', 'ethdev', 'bus_vdev'],
     'test_pmd_ring_perf.c': ['ethdev', 'net_ring', 'bus_vdev'],
diff --git a/app/test/test_pmd_af_packet.c b/app/test/test_pmd_af_packet.c
new file mode 100644
index 0000000000..abd60fe18c
--- /dev/null
+++ b/app/test/test_pmd_af_packet.c
@@ -0,0 +1,1122 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/sockios.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_config.h>
+#include <rte_cycles.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_lcore.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_stdatomic.h>
+
+#include "test.h"
+
+#ifndef RTE_EXEC_ENV_LINUX
+static int
+test_pmd_af_packet(void)
+{
+	printf("af_packet only supported on Linux, skipping test\n");
+	return TEST_SKIPPED;
+}
+
+#else
+
+#define NUM_MBUFS 512
+#define MBUF_CACHE_SIZE 32
+#define BURST_SIZE 32
+#define RING_SIZE 256
+
+/* Polling and timeout constants */
+#define STATS_POLL_INTERVAL_US 100
+#define STATS_POLL_TIMEOUT_US 100000  /* 100ms overall timeout */
+#define LOOPBACK_TIMEOUT_US 500000    /* 500ms for loopback test */
+
+/* Multi-threaded test constants */
+#define MT_NUM_PACKETS 100
+#define MT_TEST_DURATION_SEC 2
+
+/* Test device names */
+#define AF_PACKET_DEV_NAME "net_af_packet_test"
+#define TAP_DEV_NAME "dpdkafptest"
+
+static struct rte_mempool *mp;
+static uint16_t port_id = RTE_MAX_ETHPORTS;
+static int tap_fd = -1;
+static bool tap_created;
+static bool port_created;
+static bool port_started;
+
+/*
+ * Create a TAP interface for testing.
+ * Returns fd on success, -1 on failure.
+ */
+static int
+create_tap_interface(const char *name)
+{
+	struct ifreq ifr;
+	int fd, ret;
+
+	fd = open("/dev/net/tun", O_RDWR);
+	if (fd < 0) {
+		printf("Cannot open /dev/net/tun: %s\n", strerror(errno));
+		printf("(Are you running as root?)\n");
+		return -1;
+	}
+
+	memset(&ifr, 0, sizeof(ifr));
+	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+	snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
+
+	ret = ioctl(fd, TUNSETIFF, &ifr);
+	if (ret < 0) {
+		printf("Cannot create TAP interface '%s': %s\n",
+		       name, strerror(errno));
+		close(fd);
+		return -1;
+	}
+
+	/* Bring the interface up */
+	int sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sock >= 0) {
+		memset(&ifr, 0, sizeof(ifr));
+		snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
+
+		/* Get current flags */
+		if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
+			ifr.ifr_flags |= IFF_UP;
+			ioctl(sock, SIOCSIFFLAGS, &ifr);
+		}
+		close(sock);
+	}
+
+	printf("Created TAP interface '%s'\n", name);
+	return fd;
+}
+
+static void
+destroy_tap_interface(int fd)
+{
+	if (fd >= 0)
+		close(fd);
+}
+
+static int
+create_af_packet_port(const char *name, const char *args, uint16_t *out_port_id)
+{
+	int ret;
+
+	ret = rte_vdev_init(name, args);
+	if (ret != 0) {
+		printf("Failed to create af_packet device '%s': %d\n", name, ret);
+		return ret;
+	}
+
+	ret = rte_eth_dev_get_port_by_name(name, out_port_id);
+	if (ret != 0) {
+		printf("Failed to get port id for '%s': %d\n", name, ret);
+		rte_vdev_uninit(name);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int
+configure_af_packet_port(uint16_t pid, uint16_t nb_rx_q, uint16_t nb_tx_q)
+{
+	struct rte_eth_conf port_conf = {0};
+	struct rte_eth_dev_info dev_info;
+	int ret;
+	uint16_t q;
+
+	ret = rte_eth_dev_info_get(pid, &dev_info);
+	if (ret != 0) {
+		printf("Failed to get device info for port %u: %d\n", pid, ret);
+		return ret;
+	}
+
+	ret = rte_eth_dev_configure(pid, nb_rx_q, nb_tx_q, &port_conf);
+	if (ret != 0) {
+		printf("Failed to configure port %u: %d\n", pid, ret);
+		return ret;
+	}
+
+	for (q = 0; q < nb_rx_q; q++) {
+		ret = rte_eth_rx_queue_setup(pid, q, RING_SIZE,
+					     rte_eth_dev_socket_id(pid),
+					     NULL, mp);
+		if (ret != 0) {
+			printf("Failed to setup RX queue %u for port %u: %d\n",
+			       q, pid, ret);
+			return ret;
+		}
+	}
+
+	for (q = 0; q < nb_tx_q; q++) {
+		ret = rte_eth_tx_queue_setup(pid, q, RING_SIZE,
+					     rte_eth_dev_socket_id(pid),
+					     NULL);
+		if (ret != 0) {
+			printf("Failed to setup TX queue %u for port %u: %d\n",
+			       q, pid, ret);
+			return ret;
+		}
+	}
+
+	ret = rte_eth_dev_start(pid);
+	if (ret != 0) {
+		printf("Failed to start port %u: %d\n", pid, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Helper: Allocate and prepare a burst of TX mbufs with minimal Ethernet frames.
+ * Returns the number of successfully allocated mbufs.
+ */
+static unsigned int
+alloc_tx_mbufs(struct rte_mbuf **bufs, unsigned int count)
+{
+	unsigned int i;
+	int ret;
+
+	ret = rte_pktmbuf_alloc_bulk(mp, bufs, count);
+	if (ret != 0)
+		return 0;
+
+	for (i = 0; i < count; i++) {
+		struct rte_ether_hdr *eth_hdr;
+		eth_hdr = (struct rte_ether_hdr *)rte_pktmbuf_append(bufs[i],
+				sizeof(struct rte_ether_hdr) + 46);
+		if (eth_hdr == NULL) {
+			/* Free all allocated mbufs on failure */
+			rte_pktmbuf_free_bulk(bufs, count);
+			return 0;
+		}
+
+		/* Set broadcast destination and zero source MAC */
+		memset(&eth_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN);
+		memset(&eth_hdr->src_addr, 0x00, RTE_ETHER_ADDR_LEN);
+		eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+	}
+
+	return count;
+}
+
+/*
+ * Helper: Transmit packets and free unsent mbufs.
+ * Returns the number of packets successfully transmitted.
+ */
+static uint16_t
+do_tx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
+	    unsigned int count)
+{
+	uint16_t nb_tx;
+
+	nb_tx = rte_eth_tx_burst(pid, queue_id, bufs, count);
+
+	/* Free any unsent mbufs */
+	rte_pktmbuf_free_bulk(&bufs[nb_tx], count - nb_tx);
+
+	return nb_tx;
+}
+
+/*
+ * Helper: Receive packets and free received mbufs.
+ * Returns the number of packets received.
+ */
+static uint16_t
+do_rx_burst(uint16_t pid, uint16_t queue_id, struct rte_mbuf **bufs,
+	    unsigned int count)
+{
+	uint16_t nb_rx;
+
+	nb_rx = rte_eth_rx_burst(pid, queue_id, bufs, count);
+	rte_pktmbuf_free_bulk(bufs, nb_rx);
+
+	return nb_rx;
+}
+
+/*
+ * Helper: Wait for stats to update by polling at intervals.
+ * Returns 0 on success (stats updated), -1 on timeout.
+ */
+static int
+wait_for_stats_update(uint16_t pid, uint64_t expected_opackets,
+		      uint64_t timeout_us)
+{
+	struct rte_eth_stats stats;
+	uint64_t elapsed = 0;
+
+	while (elapsed < timeout_us) {
+		if (rte_eth_stats_get(pid, &stats) == 0) {
+			if (stats.opackets >= expected_opackets)
+				return 0;
+		}
+		rte_delay_us_block(STATS_POLL_INTERVAL_US);
+		elapsed += STATS_POLL_INTERVAL_US;
+	}
+
+	return -1;
+}
+
+static int
+test_af_packet_setup(void)
+{
+	char devargs[256];
+	int ret;
+
+	/* Create mempool for mbufs */
+	mp = rte_pktmbuf_pool_create("af_packet_test_pool", NUM_MBUFS,
+				     MBUF_CACHE_SIZE, 0,
+				     RTE_MBUF_DEFAULT_BUF_SIZE,
+				     rte_socket_id());
+	if (mp == NULL) {
+		printf("Failed to create mempool\n");
+		return -1;
+	}
+
+	/* Create TAP interface for testing */
+	tap_fd = create_tap_interface(TAP_DEV_NAME);
+	if (tap_fd >= 0)
+		tap_created = true;
+	else {
+		printf("TAP interface creation failed - tests will be skipped\n");
+		return 0; /* Return success to allow skipped tests */
+	}
+
+	/* Create and configure af_packet port */
+	snprintf(devargs, sizeof(devargs), "iface=%s", TAP_DEV_NAME);
+	ret = create_af_packet_port(AF_PACKET_DEV_NAME, devargs, &port_id);
+	if (ret != 0) {
+		printf("Failed to create af_packet port\n");
+		return -1;
+	}
+	port_created = true;
+
+	ret = configure_af_packet_port(port_id, 1, 1);
+	if (ret != 0) {
+		printf("Failed to configure af_packet port\n");
+		return -1;
+	}
+	port_started = true;
+
+	return 0;
+}
+
+static void
+test_af_packet_teardown(void)
+{
+	/* Stop and close test port */
+	if (port_started) {
+		rte_eth_dev_stop(port_id);
+		port_started = false;
+	}
+
+	if (port_created) {
+		rte_eth_dev_close(port_id);
+		rte_vdev_uninit(AF_PACKET_DEV_NAME);
+		port_id = RTE_MAX_ETHPORTS;
+		port_created = false;
+	}
+
+	/* Destroy TAP interface */
+	if (tap_created) {
+		destroy_tap_interface(tap_fd);
+		tap_fd = -1;
+		tap_created = false;
+	}
+
+	if (mp != NULL) {
+		rte_mempool_free(mp);
+		mp = NULL;
+	}
+}
+
+/*
+ * Test: Device info verification
+ */
+static int
+test_af_packet_dev_info(void)
+{
+	struct rte_eth_dev_info dev_info;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	/* Verify expected device info values */
+	TEST_ASSERT(dev_info.max_mac_addrs == 1,
+		    "Expected max_mac_addrs=1, got %u", dev_info.max_mac_addrs);
+
+	/* The driver has default internal frame size of 2K */
+	TEST_ASSERT(dev_info.max_rx_pktlen >= RTE_ETHER_MAX_LEN,
+		    "max_rx_pktlen %u < %u",
+		    dev_info.max_rx_pktlen, RTE_ETHER_MAX_LEN);
+	TEST_ASSERT(dev_info.max_mtu >= RTE_ETHER_MTU,
+		    "max_mtu %u < %u", dev_info.max_mtu, RTE_ETHER_MTU);
+	TEST_ASSERT(dev_info.min_rx_bufsize == 0,
+		    "Expected min_rx_bufsize=0, got %u", dev_info.min_rx_bufsize);
+	TEST_ASSERT(dev_info.max_rx_queues >= 1, "No RX queues available");
+	TEST_ASSERT(dev_info.max_tx_queues >= 1, "No TX queues available");
+	TEST_ASSERT(dev_info.if_index > 0, "Invalid interface index");
+
+	/* Check TX offload capabilities */
+	TEST_ASSERT(dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MULTI_SEGS,
+		    "Expected MULTI_SEGS TX offload capability");
+	TEST_ASSERT(dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_VLAN_INSERT,
+		    "Expected VLAN_INSERT TX offload capability");
+
+	/* Check RX offload capabilities */
+	TEST_ASSERT(dev_info.rx_offload_capa & RTE_ETH_RX_OFFLOAD_VLAN_STRIP,
+		    "Expected VLAN_STRIP RX offload capability");
+	TEST_ASSERT(dev_info.rx_offload_capa & RTE_ETH_RX_OFFLOAD_TIMESTAMP,
+		    "Expected TIMESTAMP RX offload capability");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Link status
+ * Note: af_packet PMD link status reflects the underlying interface state,
+ * not the DPDK device start/stop state.
+ */
+static int
+test_af_packet_link_status(void)
+{
+	struct rte_eth_link link;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_link_get_nowait(port_id, &link);
+	TEST_ASSERT(ret == 0, "Failed to get link status");
+
+	/* TAP interface was brought up during setup, so link should be UP */
+	TEST_ASSERT(link.link_status == RTE_ETH_LINK_UP,
+		    "Expected link UP (TAP interface is up)");
+	TEST_ASSERT(link.link_speed == RTE_ETH_SPEED_NUM_10G,
+		    "Expected 10G link speed");
+	TEST_ASSERT(link.link_duplex == RTE_ETH_LINK_FULL_DUPLEX,
+		    "Expected full duplex");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Statistics initial state
+ */
+static int
+test_af_packet_stats_init(void)
+{
+	struct rte_eth_stats stats;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Reset stats */
+	ret = rte_eth_stats_reset(port_id);
+	TEST_ASSERT(ret == 0, "Failed to reset stats");
+
+	/* Get initial stats */
+	ret = rte_eth_stats_get(port_id, &stats);
+	TEST_ASSERT(ret == 0, "Failed to get stats");
+
+	/* After reset, stats should be zero */
+	TEST_ASSERT(stats.ipackets == 0,
+		    "Expected ipackets=0 after reset, got %"PRIu64,
+		    stats.ipackets);
+	TEST_ASSERT(stats.opackets == 0,
+		    "Expected opackets=0 after reset, got %"PRIu64,
+		    stats.opackets);
+	TEST_ASSERT(stats.ibytes == 0,
+		    "Expected ibytes=0 after reset, got %"PRIu64,
+		    stats.ibytes);
+	TEST_ASSERT(stats.obytes == 0,
+		    "Expected obytes=0 after reset, got %"PRIu64,
+		    stats.obytes);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: TX packets (packets will be sent to TAP interface)
+ */
+static int
+test_af_packet_tx(void)
+{
+	struct rte_mbuf *bufs[BURST_SIZE];
+	struct rte_eth_stats stats;
+	uint16_t nb_tx;
+	unsigned int allocated;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Reset stats */
+	ret = rte_eth_stats_reset(port_id);
+	TEST_ASSERT(ret == 0, "Failed to reset stats");
+
+	/* Allocate and prepare mbufs for TX using helper */
+	allocated = alloc_tx_mbufs(bufs, BURST_SIZE);
+	TEST_ASSERT(allocated == BURST_SIZE, "Failed to allocate all mbufs");
+
+	/* TX burst using helper */
+	nb_tx = do_tx_burst(port_id, 0, bufs, BURST_SIZE);
+
+	/* Poll for stats update instead of fixed delay */
+	if (nb_tx > 0) {
+		ret = wait_for_stats_update(port_id, nb_tx, STATS_POLL_TIMEOUT_US);
+		if (ret == 0) {
+			ret = rte_eth_stats_get(port_id, &stats);
+			TEST_ASSERT(ret == 0, "Failed to get stats");
+			TEST_ASSERT(stats.opackets > 0,
+				    "Expected opackets > 0 after TX");
+		}
+		/* Timeout is acceptable - stats may not update immediately */
+	}
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: RX packets (non-blocking, may not receive anything)
+ */
+static int
+test_af_packet_rx(void)
+{
+	struct rte_mbuf *bufs[BURST_SIZE];
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Try to receive packets (non-blocking) using helper */
+	do_rx_burst(port_id, 0, bufs, BURST_SIZE);
+
+	/* RX from tap interface without external traffic will return 0 */
+	/* This test just verifies the RX path doesn't crash */
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Loopback - TX packets and receive them back via TAP interface.
+ * This exercises both the TX and RX paths in a round-trip.
+ */
+static int
+test_af_packet_loopback(void)
+{
+	struct rte_mbuf *tx_bufs[BURST_SIZE];
+	struct rte_mbuf *rx_bufs[BURST_SIZE];
+	uint16_t nb_tx, total_rx = 0;
+	uint64_t elapsed = 0;
+	unsigned int allocated;
+	char tap_buf[2048];
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Set TAP fd to non-blocking for reading */
+	int flags = fcntl(tap_fd, F_GETFL, 0);
+	fcntl(tap_fd, F_SETFL, flags | O_NONBLOCK);
+
+	/* Allocate and send packets */
+	allocated = alloc_tx_mbufs(tx_bufs, BURST_SIZE);
+	if (allocated == 0) {
+		printf("SKIPPED: Could not allocate mbufs\n");
+		return TEST_SKIPPED;
+	}
+
+	nb_tx = do_tx_burst(port_id, 0, tx_bufs, allocated);
+	if (nb_tx == 0) {
+		printf("SKIPPED: No packets transmitted\n");
+		return TEST_SKIPPED;
+	}
+
+	/*
+	 * Read packets from TAP interface (they were sent there by af_packet).
+	 * Then write them back to TAP so af_packet can receive them.
+	 */
+	while (elapsed < LOOPBACK_TIMEOUT_US) {
+		ssize_t bytes_read, bytes_written;
+
+		/* Read from TAP (what af_packet sent) */
+		bytes_read = read(tap_fd, tap_buf, sizeof(tap_buf));
+		if (bytes_read > 0) {
+			/* Write back to TAP for af_packet to receive */
+			bytes_written = write(tap_fd, tap_buf, bytes_read);
+			TEST_ASSERT(bytes_read == bytes_written, "TAP write %zd != %zd",
+				    bytes_read, bytes_written);
+		}
+
+		/* Try to receive via af_packet RX */
+		uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, rx_bufs, BURST_SIZE);
+		if (nb_rx > 0) {
+			total_rx += nb_rx;
+			rte_pktmbuf_free_bulk(rx_bufs, nb_rx);
+		}
+
+		/* Check if we've received enough */
+		if (total_rx >= nb_tx)
+			break;
+
+		rte_delay_us_block(STATS_POLL_INTERVAL_US);
+		elapsed += STATS_POLL_INTERVAL_US;
+	}
+
+	/* Restore TAP fd flags */
+	fcntl(tap_fd, F_SETFL, flags);
+
+	/*
+	 * Note: We may not receive all packets due to timing, but receiving
+	 * any packets proves the loopback path works.
+	 */
+	printf("Loopback: TX=%u, RX=%u\n", nb_tx, total_rx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Promiscuous mode
+ */
+static int
+test_af_packet_promiscuous(void)
+{
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Enable promiscuous mode */
+	ret = rte_eth_promiscuous_enable(port_id);
+	TEST_ASSERT(ret == 0, "Failed to enable promiscuous mode");
+
+	ret = rte_eth_promiscuous_get(port_id);
+	TEST_ASSERT(ret == 1, "Expected promiscuous mode enabled");
+
+	/* Disable promiscuous mode */
+	ret = rte_eth_promiscuous_disable(port_id);
+	TEST_ASSERT(ret == 0, "Failed to disable promiscuous mode");
+
+	ret = rte_eth_promiscuous_get(port_id);
+	TEST_ASSERT(ret == 0, "Expected promiscuous mode disabled");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MAC address operations
+ */
+static int
+test_af_packet_mac_addr(void)
+{
+	struct rte_ether_addr mac_addr;
+	struct rte_ether_addr new_mac = {
+		.addr_bytes = {0x02, 0x11, 0x22, 0x33, 0x44, 0x55}
+	};
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Get current MAC address */
+	ret = rte_eth_macaddr_get(port_id, &mac_addr);
+	TEST_ASSERT(ret == 0, "Failed to get MAC address");
+
+	/* Set new MAC address (use locally administered address) */
+	ret = rte_eth_dev_default_mac_addr_set(port_id, &new_mac);
+	TEST_ASSERT(ret == 0, "Failed to set MAC address");
+
+	/* Verify MAC was set */
+	ret = rte_eth_macaddr_get(port_id, &mac_addr);
+	TEST_ASSERT(ret == 0, "Failed to get MAC address after set");
+
+	TEST_ASSERT(memcmp(&mac_addr, &new_mac, sizeof(mac_addr)) == 0,
+		    "MAC address mismatch after set");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: MTU operations
+ * Get min/max supported MTU from device info and verify setting works.
+ */
+static int
+test_af_packet_mtu(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint16_t mtu, original_mtu;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Get device info for min/max MTU */
+	ret = rte_eth_dev_info_get(port_id, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	/* Get current MTU */
+	ret = rte_eth_dev_get_mtu(port_id, &original_mtu);
+	TEST_ASSERT(ret == 0, "Failed to get MTU");
+
+	printf("MTU: current=%u, min=%u, max=%u\n",
+	       original_mtu, dev_info.min_mtu, dev_info.max_mtu);
+
+	/* Test setting minimum MTU if supported and different from current */
+	if (dev_info.min_mtu > 0 && dev_info.min_mtu != original_mtu) {
+		ret = rte_eth_dev_set_mtu(port_id, dev_info.min_mtu);
+		if (ret == 0) {
+			ret = rte_eth_dev_get_mtu(port_id, &mtu);
+			TEST_ASSERT(ret == 0, "Failed to get MTU after set to min");
+			TEST_ASSERT(mtu == dev_info.min_mtu,
+				    "MTU not set to min: expected %u, got %u",
+				    dev_info.min_mtu, mtu);
+		}
+		/* MTU set may fail depending on interface, that's acceptable */
+	}
+
+	/* Test setting maximum MTU if supported and different from current */
+	if (dev_info.max_mtu > 0 && dev_info.max_mtu != original_mtu &&
+	    dev_info.max_mtu != dev_info.min_mtu) {
+		ret = rte_eth_dev_set_mtu(port_id, dev_info.max_mtu);
+		if (ret == 0) {
+			ret = rte_eth_dev_get_mtu(port_id, &mtu);
+			TEST_ASSERT(ret == 0, "Failed to get MTU after set to max");
+			TEST_ASSERT(mtu == dev_info.max_mtu,
+				    "MTU not set to max: expected %u, got %u",
+				    dev_info.max_mtu, mtu);
+		}
+		/* MTU set may fail depending on interface capabilities */
+	}
+
+	/* Restore original MTU */
+	rte_eth_dev_set_mtu(port_id, original_mtu);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Stats reset verification
+ */
+static int
+test_af_packet_stats_reset(void)
+{
+	struct rte_eth_stats stats;
+	struct rte_mbuf *bufs[BURST_SIZE / 2];
+	uint16_t nb_tx;
+	unsigned int allocated;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Generate some TX traffic using helpers */
+	allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 2);
+	nb_tx = do_tx_burst(port_id, 0, bufs, allocated);
+
+	/* Poll for stats to update */
+	if (nb_tx > 0)
+		wait_for_stats_update(port_id, nb_tx, STATS_POLL_TIMEOUT_US);
+
+	/* Reset stats */
+	ret = rte_eth_stats_reset(port_id);
+	TEST_ASSERT(ret == 0, "Failed to reset stats");
+
+	/* Verify stats are zero */
+	ret = rte_eth_stats_get(port_id, &stats);
+	TEST_ASSERT(ret == 0, "Failed to get stats after reset");
+
+	TEST_ASSERT(stats.ipackets == 0,
+		    "Expected ipackets=0, got %"PRIu64, stats.ipackets);
+	TEST_ASSERT(stats.opackets == 0,
+		    "Expected opackets=0, got %"PRIu64, stats.opackets);
+	TEST_ASSERT(stats.ibytes == 0,
+		    "Expected ibytes=0, got %"PRIu64, stats.ibytes);
+	TEST_ASSERT(stats.obytes == 0,
+		    "Expected obytes=0, got %"PRIu64, stats.obytes);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Invalid configuration handling - missing iface
+ */
+static int
+test_af_packet_invalid_no_iface(void)
+{
+	int ret;
+
+	/* Test without iface argument (should fail) */
+	ret = rte_vdev_init("net_af_packet_invalid1", "");
+	TEST_ASSERT(ret != 0, "Expected failure without iface argument");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Invalid configuration handling - non-existent interface
+ */
+static int
+test_af_packet_invalid_bad_iface(void)
+{
+	int ret;
+
+	/* Test with non-existent interface (should fail) */
+	ret = rte_vdev_init("net_af_packet_invalid2",
+			    "iface=nonexistent_iface_12345");
+	TEST_ASSERT(ret != 0, "Expected failure with non-existent interface");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Invalid configuration handling - invalid qpairs
+ */
+static int
+test_af_packet_invalid_qpairs(void)
+{
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Test with invalid qpairs (should fail) */
+	ret = rte_vdev_init("net_af_packet_invalid3",
+			    "iface=" TAP_DEV_NAME ",qpairs=0");
+	TEST_ASSERT(ret != 0, "Expected failure with qpairs=0");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Custom frame size configuration
+ */
+static int
+test_af_packet_frame_config(void)
+{
+	struct rte_eth_dev_info dev_info;
+	uint16_t test_port;
+	char devargs[256];
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Create with custom frame parameters */
+	snprintf(devargs, sizeof(devargs),
+		 "iface=%s,blocksz=4096,framesz=2048,framecnt=256",
+		 TAP_DEV_NAME);
+
+	ret = rte_vdev_init("net_af_packet_frame_test", devargs);
+	if (ret != 0) {
+		printf("SKIPPED: Could not create port with custom frame config\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_get_port_by_name("net_af_packet_frame_test",
+					   &test_port);
+	TEST_ASSERT(ret == 0, "Failed to get port id");
+
+	ret = rte_eth_dev_info_get(test_port, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	/* Cleanup */
+	rte_eth_dev_close(test_port);
+	rte_vdev_uninit("net_af_packet_frame_test");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Qdisc bypass configuration
+ */
+static int
+test_af_packet_qdisc_bypass(void)
+{
+	uint16_t test_port;
+	char devargs[256];
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	/* Create with qdisc_bypass enabled */
+	snprintf(devargs, sizeof(devargs), "iface=%s,qdisc_bypass=1",
+		 TAP_DEV_NAME);
+
+	ret = rte_vdev_init("net_af_packet_qdisc_test", devargs);
+	if (ret != 0) {
+		printf("SKIPPED: qdisc_bypass may not be supported\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_get_port_by_name("net_af_packet_qdisc_test",
+					   &test_port);
+	TEST_ASSERT(ret == 0, "Failed to get port id");
+
+	/* Cleanup */
+	rte_eth_dev_close(test_port);
+	rte_vdev_uninit("net_af_packet_qdisc_test");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Test: Multiple queue pairs
+ */
+static int
+test_af_packet_multi_queue(void)
+{
+	struct rte_eth_dev_info dev_info;
+	struct rte_eth_conf port_conf = {0};
+	struct rte_mbuf *bufs[BURST_SIZE];
+	uint16_t test_port;
+	char devargs[256];
+	const uint16_t num_queues = 2;
+	unsigned int allocated, q;
+	int ret;
+
+	if (!tap_created) {
+		printf("SKIPPED: TAP interface not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	snprintf(devargs, sizeof(devargs), "iface=%s,qpairs=%u",
+		 TAP_DEV_NAME, num_queues);
+	ret = rte_vdev_init("net_af_packet_multi_q", devargs);
+	if (ret != 0) {
+		printf("SKIPPED: Could not create multi-queue port\n");
+		return TEST_SKIPPED;
+	}
+
+	ret = rte_eth_dev_get_port_by_name("net_af_packet_multi_q", &test_port);
+	TEST_ASSERT(ret == 0, "Failed to get port id for multi-queue port");
+
+	ret = rte_eth_dev_info_get(test_port, &dev_info);
+	TEST_ASSERT(ret == 0, "Failed to get device info");
+
+	TEST_ASSERT(dev_info.max_rx_queues >= num_queues,
+		    "Expected at least %u RX queues, got %u",
+		    num_queues, dev_info.max_rx_queues);
+	TEST_ASSERT(dev_info.max_tx_queues >= num_queues,
+		    "Expected at least %u TX queues, got %u",
+		    num_queues, dev_info.max_tx_queues);
+
+	/* Configure the port */
+	ret = rte_eth_dev_configure(test_port, num_queues, num_queues, &port_conf);
+	TEST_ASSERT(ret == 0, "Failed to configure multi-queue port");
+
+	for (q = 0; q < num_queues; q++) {
+		ret = rte_eth_rx_queue_setup(test_port, q, RING_SIZE,
+					     rte_eth_dev_socket_id(test_port),
+					     NULL, mp);
+		TEST_ASSERT(ret == 0, "Failed to setup RX queue %u", q);
+
+		ret = rte_eth_tx_queue_setup(test_port, q, RING_SIZE,
+					     rte_eth_dev_socket_id(test_port),
+					     NULL);
+		TEST_ASSERT(ret == 0, "Failed to setup TX queue %u", q);
+	}
+
+	ret = rte_eth_dev_start(test_port);
+	TEST_ASSERT(ret == 0, "Failed to start multi-queue port");
+
+	/* Test TX on different queues using shared helpers */
+	for (q = 0; q < num_queues; q++) {
+		allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 2);
+		if (allocated > 0)
+			do_tx_burst(test_port, q, bufs, allocated);
+	}
+
+	/* Test RX on different queues */
+	for (q = 0; q < num_queues; q++)
+		do_rx_burst(test_port, q, bufs, BURST_SIZE);
+
+	/* Cleanup */
+	rte_eth_dev_stop(test_port);
+	rte_eth_dev_close(test_port);
+	rte_vdev_uninit("net_af_packet_multi_q");
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Multi-threaded test context
+ */
+struct mt_test_ctx {
+	uint16_t port_id;
+	RTE_ATOMIC(uint32_t) stop;
+	uint32_t rx_count;
+};
+
+/*
+ * Reader thread function for multi-threaded test
+ */
+static void *
+mt_reader_thread(void *arg)
+{
+	struct mt_test_ctx *ctx = arg;
+	struct rte_mbuf *bufs[BURST_SIZE];
+	uint16_t nb_rx;
+
+	while (!rte_atomic_load_explicit(&ctx->stop, rte_memory_order_relaxed)) {
+		nb_rx = rte_eth_rx_burst(ctx->port_id, 0, bufs, BURST_SIZE);
+		if (nb_rx > 0) {
+			ctx->rx_count += nb_rx;
+			rte_pktmbuf_free_bulk(bufs, nb_rx);
+		}
+
+		rte_delay_us_block(50);
+	}
+
+	return NULL;
+}
+
+/*
+ * Test: Multi-threaded concurrent TX and RX
+ * Main thread sends packets while a reader thread receives,
+ * exercising concurrent access to the af_packet PMD.
+ */
+static int
+test_af_packet_multithread(void)
+{
+	struct mt_test_ctx ctx = {0};
+	struct rte_mbuf *bufs[BURST_SIZE];
+	pthread_t reader_tid;
+	struct timespec start, now;
+	uint32_t tx_count = 0;
+	unsigned int allocated;
+	uint16_t nb_tx;
+	int ret;
+
+	if (!tap_created || port_id >= RTE_MAX_ETHPORTS) {
+		printf("SKIPPED: Port not available (need root)\n");
+		return TEST_SKIPPED;
+	}
+
+	ctx.port_id = port_id;
+
+	/* Start reader thread */
+	ret = pthread_create(&reader_tid, NULL, mt_reader_thread, &ctx);
+	if (ret != 0) {
+		printf("SKIPPED: Failed to create reader thread: %d\n", ret);
+		return TEST_SKIPPED;
+	}
+
+	/* Main thread sends packets for the configured duration */
+	clock_gettime(CLOCK_MONOTONIC, &start);
+	do {
+		allocated = alloc_tx_mbufs(bufs, BURST_SIZE / 4);
+		if (allocated == 0)
+			break;
+
+		nb_tx = do_tx_burst(port_id, 0, bufs, allocated);
+		tx_count += nb_tx;
+
+		rte_delay_us_block(100);
+		clock_gettime(CLOCK_MONOTONIC, &now);
+	} while ((now.tv_sec - start.tv_sec) < MT_TEST_DURATION_SEC);
+
+	/* Signal reader to stop */
+	rte_atomic_store_explicit(&ctx.stop, 1, rte_memory_order_relaxed);
+	pthread_join(reader_tid, NULL);
+
+	printf("Multi-threaded test: TX=%u, RX=%u (duration=%ds)\n",
+	       tx_count, ctx.rx_count, MT_TEST_DURATION_SEC);
+
+	TEST_ASSERT(tx_count > 0,
+		    "Expected some packets to be transmitted");
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite af_packet_test_suite = {
+	.suite_name = "AF_PACKET PMD Unit Test Suite",
+	.setup = test_af_packet_setup,
+	.teardown = test_af_packet_teardown,
+	.unit_test_cases = {
+		/* Tests that don't modify device state */
+		TEST_CASE(test_af_packet_dev_info),
+		TEST_CASE(test_af_packet_link_status),
+		TEST_CASE(test_af_packet_stats_init),
+		TEST_CASE(test_af_packet_tx),
+		TEST_CASE(test_af_packet_rx),
+		TEST_CASE(test_af_packet_loopback),
+		TEST_CASE(test_af_packet_promiscuous),
+		TEST_CASE(test_af_packet_mac_addr),
+		TEST_CASE(test_af_packet_mtu),
+		TEST_CASE(test_af_packet_stats_reset),
+		TEST_CASE(test_af_packet_multithread),
+
+		/* Tests that create their own devices */
+		TEST_CASE(test_af_packet_invalid_no_iface),
+		TEST_CASE(test_af_packet_invalid_bad_iface),
+		TEST_CASE(test_af_packet_invalid_qpairs),
+		TEST_CASE(test_af_packet_frame_config),
+		TEST_CASE(test_af_packet_qdisc_bypass),
+		TEST_CASE(test_af_packet_multi_queue),
+
+		TEST_CASES_END() /**< NULL terminate unit test array */
+	}
+};
+
+static int
+test_pmd_af_packet(void)
+{
+	return unit_test_suite_runner(&af_packet_test_suite);
+}
+#endif
+
+REGISTER_FAST_TEST(af_packet_pmd_autotest, NOHUGE_OK, ASAN_OK, test_pmd_af_packet);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* Re: [PATCH v2 0/6] fix process shared pthread mutexes
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-04-14 14:39   ` [PATCH v2 6/6] net/hinic: " Stephen Hemminger
@ 2026-04-23 15:05   ` Stephen Hemminger
  2026-04-23 17:29   ` Konstantin Ananyev
  2026-05-08 16:39   ` Stephen Hemminger
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-23 15:05 UTC (permalink / raw)
  To: dev

On Tue, 14 Apr 2026 07:39:52 -0700
Stephen Hemminger <stephen@networkplumber.org> wrote:

> Several drivers and the ethdev layer initialize pthread mutexes
> in shared memory with default (process-private) attributes.
> This is undefined behavior when secondary processes use them.
> 
> This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
> All are on control paths (firmware mailbox, hotplug, flow ops,
> PHY negotiation) where sleeping is acceptable.
> 
> See POSIX spec:
> https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexattr_getpshared.html
> 
> Bugzilla ID: 662

I would like someone to review and ack some or all of these patches.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* RE: [PATCH v2 0/6] fix process shared pthread mutexes
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-04-23 15:05   ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
@ 2026-04-23 17:29   ` Konstantin Ananyev
  2026-05-08 16:39   ` Stephen Hemminger
  8 siblings, 0 replies; 33+ messages in thread
From: Konstantin Ananyev @ 2026-04-23 17:29 UTC (permalink / raw)
  To: Stephen Hemminger, dev@dpdk.org



> Several drivers and the ethdev layer initialize pthread mutexes
> in shared memory with default (process-private) attributes.
> This is undefined behavior when secondary processes use them.
> 
> This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
> All are on control paths (firmware mailbox, hotplug, flow ops,
> PHY negotiation) where sleeping is acceptable.
> 
> See POSIX spec:
> https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexat
> tr_getpshared.html
> 
> Bugzilla ID: 662
> 
> v2 - fix build on Windows which does not need this.
> 
> Stephen Hemminger (6):
>   ethdev: fix flow_ops_mutex for multi-process
>   net/failsafe: fix hotplug_mutex for multi-process
>   net/atlantic: fix mbox_mutex for multi-process
>   net/axgbe: fix mutexes for multi-process
>   net/bnxt: fix mutexes for multi-process
>   net/hinic: fix mutexes for multi-process
> 
>  drivers/net/atlantic/atl_ethdev.c      | 14 +++++++++++++-
>  drivers/net/axgbe/axgbe_ethdev.c       | 19 +++++++++++++++----
>  drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
>  drivers/net/bnxt/bnxt_txq.c            |  3 ++-
>  drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
>  drivers/net/bnxt/bnxt_util.h           |  2 ++
>  drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
>  drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
>  drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
>  drivers/net/failsafe/failsafe.c        | 15 ++++++++++++---
>  drivers/net/hinic/base/hinic_compat.h  | 13 ++++++++++++-
>  lib/ethdev/ethdev_driver.c             | 22 +++++++++++++++++++++-
>  12 files changed, 99 insertions(+), 19 deletions(-)
> 
> --

LGTM
Just as a generic thought :
- as these new functions is practically identical in all drivers, would it make sense
  to create some helper function in drivcers/common or somewhere in other place
  and use it everywhere. 
With or without suggested change:
Series-Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>

> 2.53.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH v3 0/7] fix use of pthread mutex between processes
  2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
                   ` (8 preceding siblings ...)
  2026-04-16 17:57 ` [PATCH v4 0/2] af_packet: cleanup and test Stephen Hemminger
@ 2026-04-29 18:46 ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 1/7] eal: add helper to initialize process-shared mutex Stephen Hemminger
                     ` (7 more replies)
  9 siblings, 8 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Several drivers and the ethdev layer initialize pthread mutexes
in shared memory with default (process-private) attributes.
This is undefined behavior when secondary processes use them.

This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
All are on control paths (firmware mailbox, hotplug, flow ops,
PHY negotiation) where sleeping is acceptable.

See POSIX spec:
https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexattr_getpshared.html


Stephen Hemminger (7):
  eal: add helper to initialize process-shared mutex
  ethdev: fix flow_ops_mutex for multi-process
  net/failsafe: fix hotplug_mutex for multi-process
  net/atlantic: fix mbox_mutex for multi-process
  net/axgbe: fix mutexes for multi-process
  net/bnxt: fix mutexes for multi-process
  net/hinic: fix mutexes for multi-process

 drivers/net/atlantic/atl_ethdev.c      |  3 ++-
 drivers/net/axgbe/axgbe_ethdev.c       | 14 +++++++-------
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 drivers/net/failsafe/failsafe.c        | 15 ++++++++++++---
 drivers/net/hinic/base/hinic_compat.h  |  6 ++++--
 lib/eal/include/rte_thread.h           | 15 +++++++++++++++
 lib/eal/unix/rte_thread.c              | 12 ++++++++++++
 lib/eal/windows/rte_thread.c           | 10 ++++++++++
 lib/ethdev/ethdev_driver.c             |  2 +-
 13 files changed, 74 insertions(+), 23 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH v3 1/7] eal: add helper to initialize process-shared mutex
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 2/7] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Dmitry Kozlyuk

Drivers and libraries that place a pthread_mutex_t in shared memory
to coordinate primary and secondary DPDK processes must initialize
the mutex with PTHREAD_PROCESS_SHARED, otherwise behaviour is
undefined. Add an internal helper so this is done in one place rather
than copied into every driver.

Errors from the attribute calls are ignored: on platforms that do not
support cross-process mutex sharing the mutex still works within a
single process.

Bugzilla ID: 662
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/eal/include/rte_thread.h | 15 +++++++++++++++
 lib/eal/unix/rte_thread.c    | 12 ++++++++++++
 lib/eal/windows/rte_thread.c | 10 ++++++++++
 3 files changed, 37 insertions(+)

diff --git a/lib/eal/include/rte_thread.h b/lib/eal/include/rte_thread.h
index 8da9d4d3fb..27d5b9c594 100644
--- a/lib/eal/include/rte_thread.h
+++ b/lib/eal/include/rte_thread.h
@@ -3,6 +3,7 @@
  * Copyright (C) 2022 Microsoft Corporation
  */
 
+#include <pthread.h>
 #include <stdint.h>
 
 #include <rte_os.h>
@@ -463,6 +464,20 @@ int rte_thread_value_set(rte_thread_key key, const void *value);
  */
 void *rte_thread_value_get(rte_thread_key key);
 
+/**
+ * Initialize a pthread mutex for use in shared memory accessible by
+ * multiple DPDK processes.
+ *
+ * Sets PTHREAD_PROCESS_SHARED so the mutex can synchronize threads
+ * across DPDK primary and secondary processes when the mutex resides
+ * in shared memory (e.g. hugepage memory).
+ *
+ * @param mutex
+ *   The mutex to initialize.
+ */
+__rte_internal
+void rte_thread_mutex_init_shared(pthread_mutex_t *mutex);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/eal/unix/rte_thread.c b/lib/eal/unix/rte_thread.c
index 950c0848ba..f3a6bd2ad3 100644
--- a/lib/eal/unix/rte_thread.c
+++ b/lib/eal/unix/rte_thread.c
@@ -419,3 +419,15 @@ rte_thread_get_affinity_by_id(rte_thread_t thread_id,
 	return pthread_getaffinity_np((pthread_t)thread_id.opaque_id,
 		sizeof(*cpuset), cpuset);
 }
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_thread_mutex_init_shared)
+void
+rte_thread_mutex_init_shared(pthread_mutex_t *mutex)
+{
+	pthread_mutexattr_t attr;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(mutex, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
diff --git a/lib/eal/windows/rte_thread.c b/lib/eal/windows/rte_thread.c
index 85e5a57346..19bb9ed61d 100644
--- a/lib/eal/windows/rte_thread.c
+++ b/lib/eal/windows/rte_thread.c
@@ -621,3 +621,13 @@ rte_thread_get_affinity_by_id(rte_thread_t thread_id,
 	}
 	return ret;
 }
+
+/* Note: Windows does not have PTHREAD_PROCESS_SHARED
+ * and DPDK on Windows only supports single process model.
+ */
+RTE_EXPORT_INTERNAL_SYMBOL(rte_thread_mutex_init_shared)
+void
+rte_thread_mutex_init_shared(pthread_mutex_t *mutex)
+{
+	pthread_mutex_init(mutex, NULL);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 2/7] ethdev: fix flow_ops_mutex for multi-process
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 1/7] eal: add helper to initialize process-shared mutex Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 3/7] net/failsafe: fix hotplug_mutex " Stephen Hemminger
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Thomas Monjalon, Andrew Rybchenko,
	Ori Kam, Suanming Mou, Ajit Khaparde, Matan Azrad

The flow_ops_mutex in rte_eth_dev_data lives in shared memory and is
accessed by both primary and secondary processes. It must be
initialized with PTHREAD_PROCESS_SHARED.

Bugzilla ID: 662
Fixes: 80d1a9aff7f6 ("ethdev: make flow API thread safe")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/ethdev/ethdev_driver.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/ethdev/ethdev_driver.c b/lib/ethdev/ethdev_driver.c
index 2ec665be0f..70ddce5bfc 100644
--- a/lib/ethdev/ethdev_driver.c
+++ b/lib/ethdev/ethdev_driver.c
@@ -135,7 +135,7 @@ rte_eth_dev_allocate(const char *name)
 	eth_dev->data->port_id = port_id;
 	eth_dev->data->backer_port_id = RTE_MAX_ETHPORTS;
 	eth_dev->data->mtu = RTE_ETHER_MTU;
-	pthread_mutex_init(&eth_dev->data->flow_ops_mutex, NULL);
+	rte_thread_mutex_init_shared(&eth_dev->data->flow_ops_mutex);
 	RTE_ASSERT(rte_eal_process_type() == RTE_PROC_PRIMARY);
 	eth_dev_shared_data->allocated_ports++;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 3/7] net/failsafe: fix hotplug_mutex for multi-process
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 1/7] eal: add helper to initialize process-shared mutex Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 2/7] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 4/7] net/atlantic: fix mbox_mutex " Stephen Hemminger
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Gaetan Rivet, Matan Azrad

The failsafe driver supports secondary process attachment via
rte_eth_dev_attach_secondary() in rte_pmd_failsafe_probe(). The
hotplug_mutex protects shared state but was initialized without
PTHREAD_PROCESS_SHARED.

The mutex is also recursive, so the EAL helper does not fit; set the
attribute alongside the recursive type. Also add the missing
pthread_mutexattr_destroy() call.

Bugzilla ID: 662
Fixes: 655fcd68c7d2 ("net/failsafe: fix hotplug races")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/failsafe/failsafe.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/net/failsafe/failsafe.c b/drivers/net/failsafe/failsafe.c
index 3e590d38f7..aa3fe53b22 100644
--- a/drivers/net/failsafe/failsafe.c
+++ b/drivers/net/failsafe/failsafe.c
@@ -144,11 +144,20 @@ fs_mutex_init(struct fs_priv *priv)
 	/* Allow mutex relocks for the thread holding the mutex. */
 	ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
 	if (ret) {
-		ERROR("Cannot set mutex type - %s", strerror(ret));
-		return ret;
+		ERROR("Cannot set mutex recursive - %s", strerror(ret));
+		goto out;
+	}
+	/* Allow mutex to be shared between processes. */
+	ret = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	if (ret) {
+		ERROR("Cannot set mutex pshared - %s", strerror(ret));
+		goto out;
 	}
+	pthread_mutex_init(&priv->hotplug_mutex, &attr);
 
-	return pthread_mutex_init(&priv->hotplug_mutex, &attr);
+out:
+	pthread_mutexattr_destroy(&attr);
+	return ret;
 }
 
 static int
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 4/7] net/atlantic: fix mbox_mutex for multi-process
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
                     ` (2 preceding siblings ...)
  2026-04-29 18:46   ` [PATCH v3 3/7] net/failsafe: fix hotplug_mutex " Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 5/7] net/axgbe: fix mutexes " Stephen Hemminger
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Igor Russkikh, Pavel Belous

The Atlantic driver supports secondary processes. The mbox_mutex in
aq_hw_s lives in dev_private (shared memory) and must be initialized
with PTHREAD_PROCESS_SHARED.

Bugzilla ID: 662
Fixes: e9924638f5c9 ("net/atlantic: add FW mailbox guard mutex")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/atlantic/atl_ethdev.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/atlantic/atl_ethdev.c b/drivers/net/atlantic/atl_ethdev.c
index 2925dc2478..f8744da221 100644
--- a/drivers/net/atlantic/atl_ethdev.c
+++ b/drivers/net/atlantic/atl_ethdev.c
@@ -5,6 +5,7 @@
 #include <rte_string_fns.h>
 #include <ethdev_pci.h>
 #include <rte_alarm.h>
+#include <rte_thread.h>
 
 #include "atl_ethdev.h"
 #include "atl_common.h"
@@ -405,7 +406,7 @@ eth_atl_dev_init(struct rte_eth_dev *eth_dev)
 
 	hw->aq_nic_cfg = &adapter->hw_cfg;
 
-	pthread_mutex_init(&hw->mbox_mutex, NULL);
+	rte_thread_mutex_init_shared(&hw->mbox_mutex);
 
 	/* disable interrupt */
 	atl_disable_intr(hw);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 5/7] net/axgbe: fix mutexes for multi-process
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
                     ` (3 preceding siblings ...)
  2026-04-29 18:46   ` [PATCH v3 4/7] net/atlantic: fix mbox_mutex " Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 6/7] net/bnxt: " Stephen Hemminger
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Selwin Sebastian, Ravi Kumar

The AXGBE driver supports secondary processes. The xpcs_mutex,
i2c_mutex, an_mutex and phy_mutex in axgbe_port live in dev_private
(shared memory) and must be initialized with PTHREAD_PROCESS_SHARED.

Also, make headers use standard <> format when accessing
DPDK EAL includes.

Bugzilla ID: 662
Fixes: 572890ef6625 ("net/axgbe: add structs for MAC init and reset")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/axgbe/axgbe_ethdev.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/net/axgbe/axgbe_ethdev.c b/drivers/net/axgbe/axgbe_ethdev.c
index cfcd880961..9491f18b7c 100644
--- a/drivers/net/axgbe/axgbe_ethdev.c
+++ b/drivers/net/axgbe/axgbe_ethdev.c
@@ -8,10 +8,10 @@
 #include "axgbe_common.h"
 #include "axgbe_phy.h"
 #include "axgbe_regs.h"
-#include "rte_time.h"
-
-#include "eal_filesystem.h"
 
+#include <rte_thread.h>
+#include <rte_time.h>
+#include <eal_filesystem.h>
 #include <rte_vect.h>
 
 #ifdef RTE_ARCH_X86
@@ -2419,10 +2419,10 @@ eth_axgbe_dev_init(struct rte_eth_dev *eth_dev)
 
 	pdata->tx_desc_count = AXGBE_MAX_RING_DESC;
 	pdata->rx_desc_count = AXGBE_MAX_RING_DESC;
-	pthread_mutex_init(&pdata->xpcs_mutex, NULL);
-	pthread_mutex_init(&pdata->i2c_mutex, NULL);
-	pthread_mutex_init(&pdata->an_mutex, NULL);
-	pthread_mutex_init(&pdata->phy_mutex, NULL);
+	rte_thread_mutex_init_shared(&pdata->xpcs_mutex);
+	rte_thread_mutex_init_shared(&pdata->i2c_mutex);
+	rte_thread_mutex_init_shared(&pdata->an_mutex);
+	rte_thread_mutex_init_shared(&pdata->phy_mutex);
 
 	ret = pdata->phy_if.phy_init(pdata);
 	if (ret) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 6/7] net/bnxt: fix mutexes for multi-process
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
                     ` (4 preceding siblings ...)
  2026-04-29 18:46   ` [PATCH v3 5/7] net/axgbe: fix mutexes " Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-29 18:46   ` [PATCH v3 7/7] net/hinic: " Stephen Hemminger
  2026-04-30 17:36   ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Kishore Padmanabha, Ajit Khaparde,
	Kalesh AP, Venkat Duvvuru, Somnath Kotur

The BNXT driver supports secondary processes. Several mutexes live in
structures allocated in shared memory (dev_private and rte_zmalloc'd
structures) and must be initialized with PTHREAD_PROCESS_SHARED:

  - flow_lock, def_cp_lock, health_check_lock, err_recovery_lock
    in struct bnxt
  - vfr_start_lock in rep_info
  - txq_lock in struct bnxt_tx_queue
  - bnxt_ulp_mutex in session state
  - flow_db_lock in cfg_data

Bugzilla ID: 662
Fixes: 1cb3d39a48f7 ("net/bnxt: synchronize between flow related functions")
Fixes: 5526c8025d4d ("net/bnxt: fix race between interrupt handler and dev config")
Fixes: b59e4be2b6a7 ("net/bnxt: fix VF representor port add")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
 drivers/net/bnxt/bnxt_txq.c            |  3 ++-
 drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
 drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
 5 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/drivers/net/bnxt/bnxt_ethdev.c b/drivers/net/bnxt/bnxt_ethdev.c
index b677f9491d..4c5c042ed8 100644
--- a/drivers/net/bnxt/bnxt_ethdev.c
+++ b/drivers/net/bnxt/bnxt_ethdev.c
@@ -5899,10 +5899,10 @@ static int bnxt_get_config(struct bnxt *bp)
 static int
 bnxt_init_locks(struct bnxt *bp)
 {
-	pthread_mutex_init(&bp->flow_lock, NULL);
-	pthread_mutex_init(&bp->def_cp_lock, NULL);
-	pthread_mutex_init(&bp->health_check_lock, NULL);
-	pthread_mutex_init(&bp->err_recovery_lock, NULL);
+	rte_thread_mutex_init_shared(&bp->flow_lock);
+	rte_thread_mutex_init_shared(&bp->def_cp_lock);
+	rte_thread_mutex_init_shared(&bp->health_check_lock);
+	rte_thread_mutex_init_shared(&bp->err_recovery_lock);
 
 	return 0;
 }
@@ -6920,7 +6920,8 @@ static int bnxt_init_rep_info(struct bnxt *bp)
 	for (i = 0; i < BNXT_MAX_CFA_CODE; i++)
 		bp->cfa_code_map[i] = BNXT_VF_IDX_INVALID;
 
-	return pthread_mutex_init(&bp->rep_info->vfr_start_lock, NULL);
+	rte_thread_mutex_init_shared(&bp->rep_info->vfr_start_lock);
+	return 0;
 }
 
 static int bnxt_rep_port_probe(struct rte_pci_device *pci_dev,
diff --git a/drivers/net/bnxt/bnxt_txq.c b/drivers/net/bnxt/bnxt_txq.c
index 7752f06eb7..03407c556a 100644
--- a/drivers/net/bnxt/bnxt_txq.c
+++ b/drivers/net/bnxt/bnxt_txq.c
@@ -204,7 +204,8 @@ int bnxt_tx_queue_setup_op(struct rte_eth_dev *eth_dev,
 		goto err;
 	}
 
-	return pthread_mutex_init(&txq->txq_lock, NULL);
+	rte_thread_mutex_init_shared(&txq->txq_lock);
+	return 0;
 err:
 	bnxt_tx_queue_release_op(eth_dev, queue_idx);
 	return rc;
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
index 0c03ae7a83..c28cf4f2b5 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp.c
@@ -214,7 +214,7 @@ ulp_session_init(struct bnxt *bp,
 			session->pci_info.domain = pci_addr->domain;
 			session->pci_info.bus = pci_addr->bus;
 			memcpy(session->dsn, bp->dsn, sizeof(session->dsn));
-			pthread_mutex_init(&session->bnxt_ulp_mutex, NULL);
+			rte_thread_mutex_init_shared(&session->bnxt_ulp_mutex);
 			STAILQ_INSERT_TAIL(&bnxt_ulp_session_list,
 					   session, next);
 		}
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
index bc347de202..6ca9d3309b 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c
@@ -1469,7 +1469,7 @@ ulp_tf_init(struct bnxt *bp,
 		goto jump_to_error;
 	}
 
-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	rte_thread_mutex_init_shared(&bp->ulp_ctx->cfg_data->flow_db_lock);
 
 	/* Initialize ulp dparms with values devargs passed */
 	rc = ulp_tf_dparms_init(bp, bp->ulp_ctx);
diff --git a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
index ad44ec93ca..fa98b2bca9 100644
--- a/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
+++ b/drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c
@@ -1038,7 +1038,7 @@ ulp_tfc_init(struct bnxt *bp,
 		goto jump_to_error;
 	}
 
-	pthread_mutex_init(&bp->ulp_ctx->cfg_data->flow_db_lock, NULL);
+	rte_thread_mutex_init_shared(&bp->ulp_ctx->cfg_data->flow_db_lock);
 
 	rc = ulp_tfc_dparms_init(bp, bp->ulp_ctx, ulp_dev_id);
 	if (rc) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v3 7/7] net/hinic: fix mutexes for multi-process
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
                     ` (5 preceding siblings ...)
  2026-04-29 18:46   ` [PATCH v3 6/7] net/bnxt: " Stephen Hemminger
@ 2026-04-29 18:46   ` Stephen Hemminger
  2026-04-30 17:36   ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-29 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, stable, Xiaoyun Wang, Ziyang Xuan

The hinic driver supports secondary processes. Mutexes in structures
allocated in shared memory must be initialized with
PTHREAD_PROCESS_SHARED. All callers of hinic_mutex_init() pass NULL
as the attribute argument, so route it through the EAL helper and
drop the unused parameter handling.

Bugzilla ID: 662
Fixes: ae865766b334 ("net/hinic: replace spinlock with mutex")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 drivers/net/hinic/base/hinic_compat.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/net/hinic/base/hinic_compat.h b/drivers/net/hinic/base/hinic_compat.h
index d3994c50e9..707a3b92b9 100644
--- a/drivers/net/hinic/base/hinic_compat.h
+++ b/drivers/net/hinic/base/hinic_compat.h
@@ -19,6 +19,7 @@
 #include <rte_spinlock.h>
 #include <rte_cycles.h>
 #include <rte_log.h>
+#include <rte_thread.h>
 
 typedef uint8_t   u8;
 typedef int8_t    s8;
@@ -198,9 +199,10 @@ static inline u16 ilog2(u32 n)
 }
 
 static inline int hinic_mutex_init(pthread_mutex_t *pthreadmutex,
-					const pthread_mutexattr_t *mattr)
+					__rte_unused const pthread_mutexattr_t *mattr)
 {
-	return pthread_mutex_init(pthreadmutex, mattr);
+	rte_thread_mutex_init_shared(pthreadmutex);
+	return 0;
 }
 
 static inline int hinic_mutex_destroy(pthread_mutex_t *pthreadmutex)
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* Re: [PATCH v3 0/7] fix use of pthread mutex between processes
  2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
                     ` (6 preceding siblings ...)
  2026-04-29 18:46   ` [PATCH v3 7/7] net/hinic: " Stephen Hemminger
@ 2026-04-30 17:36   ` Stephen Hemminger
  7 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-04-30 17:36 UTC (permalink / raw)
  To: dev

On Wed, 29 Apr 2026 11:46:37 -0700
Stephen Hemminger <stephen@networkplumber.org> wrote:

> Several drivers and the ethdev layer initialize pthread mutexes
> in shared memory with default (process-private) attributes.
> This is undefined behavior when secondary processes use them.
> 
> This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
> All are on control paths (firmware mailbox, hotplug, flow ops,
> PHY negotiation) where sleeping is acceptable.
> 
> See POSIX spec:
> https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexattr_getpshared.html
> 
> 
> Stephen Hemminger (7):
>   eal: add helper to initialize process-shared mutex
>   ethdev: fix flow_ops_mutex for multi-process
>   net/failsafe: fix hotplug_mutex for multi-process
>   net/atlantic: fix mbox_mutex for multi-process
>   net/axgbe: fix mutexes for multi-process
>   net/bnxt: fix mutexes for multi-process
>   net/hinic: fix mutexes for multi-process


Applied to next-net

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v2 0/6] fix process shared pthread mutexes
  2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
                     ` (7 preceding siblings ...)
  2026-04-23 17:29   ` Konstantin Ananyev
@ 2026-05-08 16:39   ` Stephen Hemminger
  8 siblings, 0 replies; 33+ messages in thread
From: Stephen Hemminger @ 2026-05-08 16:39 UTC (permalink / raw)
  To: dev

On Tue, 14 Apr 2026 07:39:52 -0700
Stephen Hemminger <stephen@networkplumber.org> wrote:

> Several drivers and the ethdev layer initialize pthread mutexes
> in shared memory with default (process-private) attributes.
> This is undefined behavior when secondary processes use them.
> 
> This series adds PTHREAD_PROCESS_SHARED to all affected mutexes.
> All are on control paths (firmware mailbox, hotplug, flow ops,
> PHY negotiation) where sleeping is acceptable.
> 
> See POSIX spec:
> https://pubs.opengroup.org/onlinepubs/009696899/functions/pthread_mutexattr_getpshared.html
> 
> Bugzilla ID: 662
> 
> v2 - fix build on Windows which does not need this.
> 
> Stephen Hemminger (6):
>   ethdev: fix flow_ops_mutex for multi-process
>   net/failsafe: fix hotplug_mutex for multi-process
>   net/atlantic: fix mbox_mutex for multi-process
>   net/axgbe: fix mutexes for multi-process
>   net/bnxt: fix mutexes for multi-process
>   net/hinic: fix mutexes for multi-process
> 
>  drivers/net/atlantic/atl_ethdev.c      | 14 +++++++++++++-
>  drivers/net/axgbe/axgbe_ethdev.c       | 19 +++++++++++++++----
>  drivers/net/bnxt/bnxt_ethdev.c         | 11 ++++++-----
>  drivers/net/bnxt/bnxt_txq.c            |  3 ++-
>  drivers/net/bnxt/bnxt_util.c           | 13 +++++++++++++
>  drivers/net/bnxt/bnxt_util.h           |  2 ++
>  drivers/net/bnxt/tf_ulp/bnxt_ulp.c     |  2 +-
>  drivers/net/bnxt/tf_ulp/bnxt_ulp_tf.c  |  2 +-
>  drivers/net/bnxt/tf_ulp/bnxt_ulp_tfc.c |  2 +-
>  drivers/net/failsafe/failsafe.c        | 15 ++++++++++++---
>  drivers/net/hinic/base/hinic_compat.h  | 13 ++++++++++++-
>  lib/ethdev/ethdev_driver.c             | 22 +++++++++++++++++++++-
>  12 files changed, 99 insertions(+), 19 deletions(-)
> 

Applied to next-net with helper.

^ permalink raw reply	[flat|nested] 33+ messages in thread

end of thread, other threads:[~2026-05-08 16:39 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-13 17:14 [PATCH 0/6] fix pthread mutexes for multi-process Stephen Hemminger
2026-04-13 17:14 ` [PATCH 1/6] ethdev: fix flow_ops_mutex " Stephen Hemminger
2026-04-13 17:14 ` [PATCH 2/6] net/failsafe: fix hotplug_mutex " Stephen Hemminger
2026-04-13 17:14 ` [PATCH 3/6] net/atlantic: fix mbox_mutex " Stephen Hemminger
2026-04-13 17:14 ` [PATCH 4/6] net/axgbe: fix mutexes " Stephen Hemminger
2026-04-13 17:14 ` [PATCH 5/6] net/bnxt: " Stephen Hemminger
2026-04-14 18:51   ` Kishore Padmanabha
2026-04-13 17:14 ` [PATCH 6/6] net/hinic: " Stephen Hemminger
2026-04-14 14:39 ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
2026-04-14 14:39   ` [PATCH v2 1/6] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
2026-04-14 14:39   ` [PATCH v2 2/6] net/failsafe: fix hotplug_mutex " Stephen Hemminger
2026-04-14 14:39   ` [PATCH v2 3/6] net/atlantic: fix mbox_mutex " Stephen Hemminger
2026-04-14 14:39   ` [PATCH v2 4/6] net/axgbe: fix mutexes " Stephen Hemminger
2026-04-14 14:39   ` [PATCH v2 5/6] net/bnxt: " Stephen Hemminger
2026-04-14 14:39   ` [PATCH v2 6/6] net/hinic: " Stephen Hemminger
2026-04-23 15:05   ` [PATCH v2 0/6] fix process shared pthread mutexes Stephen Hemminger
2026-04-23 17:29   ` Konstantin Ananyev
2026-05-08 16:39   ` Stephen Hemminger
2026-04-15 14:51 ` [PATCH v3 0/2] af_packet: cleanup and test Stephen Hemminger
2026-04-15 14:51   ` [PATCH v3 1/2] net/af_packet: fix indentation Stephen Hemminger
2026-04-15 14:51   ` [PATCH v3 2/2] test: add test for af_packet Stephen Hemminger
2026-04-16 17:57 ` [PATCH v4 0/2] af_packet: cleanup and test Stephen Hemminger
2026-04-16 17:57   ` [PATCH v4 1/2] net/af_packet: fix indentation Stephen Hemminger
2026-04-16 17:57   ` [PATCH v4 2/2] test: add test for af_packet Stephen Hemminger
2026-04-29 18:46 ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 1/7] eal: add helper to initialize process-shared mutex Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 2/7] ethdev: fix flow_ops_mutex for multi-process Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 3/7] net/failsafe: fix hotplug_mutex " Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 4/7] net/atlantic: fix mbox_mutex " Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 5/7] net/axgbe: fix mutexes " Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 6/7] net/bnxt: " Stephen Hemminger
2026-04-29 18:46   ` [PATCH v3 7/7] net/hinic: " Stephen Hemminger
2026-04-30 17:36   ` [PATCH v3 0/7] fix use of pthread mutex between processes Stephen Hemminger

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