* [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(ð_dev->data->flow_ops_mutex, NULL); + eth_dev_flow_ops_mutex_init(ð_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
* 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 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(ð_dev->data->flow_ops_mutex, NULL); + eth_dev_flow_ops_mutex_init(ð_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 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
* 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
* [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(ð_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); + memset(ð_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(ð_hdr->dst_addr, 0xFF, RTE_ETHER_ADDR_LEN); + memset(ð_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
* [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(ð_dev->data->flow_ops_mutex, NULL); + rte_thread_mutex_init_shared(ð_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
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