* [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 an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.