net/mana: add device reset support
Subsystems (2)
v3 cover net/mana: add device reset support
From: Wei Hu <[email protected]>
Add support for handling hardware service reset events in the MANA driver. When the MANA kernel driver receives a hardware service event, it initiates a device reset and notifies userspace via IBV_EVENT_DEVICE_FATAL. The MANA PMD handles this by performing an automatic teardown and recovery sequence.
The driver uses ethdev recovery events (ERR_RECOVERING, RECOVERY_SUCCESS, RECOVERY_FAILED) to notify upper layers of the reset lifecycle, and a PCI device removal event callback to distinguish hot-remove from service reset.
Changes since v2:
- Fixed dev_state_qsv memory leak on device removal
- Fixed reset thread TCB/stack leak: reset_thread_active is now only cleared by the joiner, not the thread itself
- Fixed second reset crash: removed reset thread join logic from mana_dev_close (inner function) to avoid corrupting dev_state when called from mana_reset_enter
- Made reset_thread_active RTE_ATOMIC(bool) with explicit ordering
- Added retry loop for rte_dev_event_callback_unregister on -EAGAIN
- Initialized condvar/mutex with PTHREAD_PROCESS_SHARED since priv is in hugepage shared memory
- Added re-check of dev_state after lock acquisition in mana_intr_handler to prevent racing with pci_remove_event_cb
- Replaced (void *)0 with NULL in mp.c
- Added lock ownership comment block at mana_reset_enter
- Documented rte_dev_event_monitor_start() requirement
- Added mana.rst documentation and release note (patch 2/2)
Changes since v1:
- Removed net/netvsc patch from this series
- Simplified reset exit: mana_reset_exit calls mana_reset_exit_delay directly instead of spawning a thread
- Added __rte_no_thread_safety_analysis annotations for clang
- Switched to rte_thread_create_internal_control
- Fixed declaration-after-statement style issues
- Removed unnecessary blank lines and stale comments
Wei Hu (2):
net/mana: add device reset support net/mana: add documentation for device reset support
doc/guides/nics/mana.rst | 33 + doc/guides/rel_notes/release_26_07.rst | 8 + drivers/net/mana/mana.c | 995 ++++++++++++++++++++++--- drivers/net/mana/mana.h | 33 +- drivers/net/mana/meson.build | 2 +- drivers/net/mana/mp.c | 89 ++- drivers/net/mana/mr.c | 6 +- drivers/net/mana/rx.c | 24 +- drivers/net/mana/tx.c | 40 +- 9 files changed, 1123 insertions(+), 107 deletions(-)
Signature · 1 line
2.34.1
PATCH v3 1/2 net/mana: add device reset support
From: Wei Hu <[email protected]>
Add support for handling hardware reset events in the MANA driver. When the MANA kernel driver receives a hardware service event, it initiates a device reset and notifies userspace via IBV_EVENT_DEVICE_FATAL. The DPDK driver handles this by performing an automatic teardown and recovery sequence.
The reset flow has two phases. In the enter phase, running on the EAL interrupt thread, the driver transitions the device state, waits for data path threads to reach a quiescent state using RCU, stops queues, tears down IB resources, and frees per-queue MR caches. A control thread is then spawned to handle the exit phase:
it waits for the hardware to recover, unregisters the interrupt handler, re-probes the PCI device, reinitializes MR caches, and restarts queues.
A per-device spinlock serializes the reset path with ethdev operations. Operations that cannot wait (configure, queue setup) return -EBUSY during reset, while dev_stop and dev_close join the reset thread and use a blocking lock to ensure proper sequencing.
Multi-process support is included: secondary processes unmap and remap doorbell pages via IPC during the reset enter and exit phases. Data path functions in both primary and secondary processes check the device state atomically and return early when the device is not active.
The driver uses ethdev recovery events to notify upper layers (e.g. netvsc) of the reset lifecycle: RTE_ETH_EVENT_ERR_RECOVERING on entry, RTE_ETH_EVENT_RECOVERY_SUCCESS or RTE_ETH_EVENT_RECOVERY_FAILED on completion. A PCI device removal event callback distinguishes hot-remove from service reset.
--- drivers/net/mana/mana.c | 995 ++++++++++++++++++++++++++++++++--- drivers/net/mana/mana.h | 33 +- drivers/net/mana/meson.build | 2 +- drivers/net/mana/mp.c | 89 +++- drivers/net/mana/mr.c | 6 +- drivers/net/mana/rx.c | 24 +- drivers/net/mana/tx.c | 40 +- 7 files changed, 1082 insertions(+), 107 deletions(-)
drivers/net/mana/mana.c +911 -84
@@ -13,7 +13,10 @@#include <ethdev_pci.h> #include <rte_kvargs.h> #include <rte_eal_paging.h> +#include <rte_alarm.h> #include <rte_pci.h> +#include <rte_rcu_qsbr.h> +#include <rte_lock_annotations.h> #include <infiniband/verbs.h> #include <infiniband/manadv.h>
@@ -103,6 +106,23 @@ mana_dev_configure(struct rte_eth_dev *dev) RTE_ETH_RX_OFFLOAD_VLAN_STRIP); priv->num_queues = dev->data->nb_rx_queues; +DRV_LOG (DEBUG ,"priv %p, port %u, dev port %u, num_queues: %u" , + priv, priv->port_id, priv->dev_port, priv->num_queues); + +/* +* Register data path thread IDs (rx and tx) with the RCU +* quiescent state variable for device state synchronization. +*/ +for (int i =0 ; i <2 * priv->num_queues; i++) { +if (rte_rcu_qsbr_thread_register (priv->dev_state_qsv, i) !=0 ) { +DRV_LOG (ERR ,"Failed to register rcu qsv thread %d of total %d" , + i,2 * priv->num_queues -1 ); +return -EINVAL ; + } +DRV_LOG (DEBUG , +"Register thread 0x%x for priv %p, port %u" , + i, priv, priv->port_id); + } manadv_set_context_attr(priv->ib_ctx, MANADV_CTX_ATTR_BUF_ALLOCATORS, (void *)((uintptr_t)&(struct manadv_ctx_allocators){
@@ -214,8 +234,8 @@ mana_dev_start(struct rte_eth_dev *dev)DRV_LOG (INFO ,"TX/RX queues have started" ); -/* Enable datapath for secondary processes */ -mana_mp_req_on_rxtx (dev,MANA_MP_REQ_START_RXTX ); +/* Intentionally ignore errors — secondary may not be running */ + (void )mana_mp_req_on_rxtx (dev,MANA_MP_REQ_START_RXTX ); ret =rxq_intr_enable (priv);if (ret) {
@@ -242,26 +262,33 @@ mana_dev_stop(struct rte_eth_dev *dev) {int ret;struct mana_priv *priv = dev->data->dev_private; - -rxq_intr_disable (priv); +enum mana_device_state state; + + state =rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire); +if (state ==MANA_DEV_ACTIVE || + state ==MANA_DEV_RESET_FAILED ) { +rxq_intr_disable (priv); +DRV_LOG (DEBUG ,"rxq_intr_disable called" ); + } dev->tx_pkt_burst = mana_tx_burst_removed; dev->rx_pkt_burst = mana_rx_burst_removed; -/* Stop datapath on secondary processes */ -mana_mp_req_on_rxtx (dev,MANA_MP_REQ_STOP_RXTX ); +/* Intentionally ignore errors — secondary may not be running */ + (void )mana_mp_req_on_rxtx (dev,MANA_MP_REQ_STOP_RXTX );rte_wmb (); ret =mana_stop_tx_queues (dev);if (ret) { -DRV_LOG (ERR ,"failed to stop tx queues" ); +DRV_LOG (ERR ,"failed to stop tx queues, ret %d" , ret);return ret; } ret =mana_stop_rx_queues (dev);if (ret) { -DRV_LOG (ERR ,"failed to stop tx queues" ); +DRV_LOG (ERR ,"failed to stop rx queues, ret %d" , ret);return ret; }
@@ -275,36 +302,69 @@ mana_dev_close(struct rte_eth_dev *dev) {struct mana_priv *priv = dev->data->dev_private;int ret; +enum mana_device_state state; +DRV_LOG (DEBUG ,"Free MR for priv %p" , priv);mana_remove_all_mr (priv); - ret =mana_intr_uninstall (priv); -if (ret) -return ret; + state =rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire); +if (state ==MANA_DEV_ACTIVE || + state ==MANA_DEV_RESET_FAILED ) { + ret =mana_intr_uninstall (priv); +if (ret) +return ret; + }if (priv->ib_parent_pd) { -int err =ibv_dealloc_pd (priv->ib_parent_pd); -if (err) -DRV_LOG (ERR ,"Failed to deallocate parent PD: %d" , err); + ret =ibv_dealloc_pd (priv->ib_parent_pd); +if (ret) +DRV_LOG (ERR , +"Failed to deallocate parent PD: %d" , ret); priv->ib_parent_pd =NULL ; }if (priv->ib_pd) { -int err =ibv_dealloc_pd (priv->ib_pd); -if (err) -DRV_LOG (ERR ,"Failed to deallocate PD: %d" , err); + ret =ibv_dealloc_pd (priv->ib_pd); +if (ret) +DRV_LOG (ERR ,"Failed to deallocate PD: %d" , ret); priv->ib_pd =NULL ; } - ret =ibv_close_device (priv->ib_ctx); -if (ret) { - ret = errno; -return ret; + state =rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire); +if (state ==MANA_DEV_ACTIVE || + state ==MANA_DEV_RESET_FAILED ) { +if (priv->ib_ctx) { + ret =ibv_close_device (priv->ib_ctx); +if (ret) { + ret = errno; +return ret; + } + priv->ib_ctx =NULL ; + } }return 0 ; } +/* +* Called from mana_pci_remove to free resources allocated +* during probe that are not freed by dev_close. +*/ +static void +mana_dev_free_resources (struct rte_eth_dev *dev) +{ +struct mana_priv *priv = dev->data->dev_private; + +if (priv->dev_state_qsv) { +rte_free (priv->dev_state_qsv); + priv->dev_state_qsv =NULL ; + } +pthread_mutex_destroy (&priv->reset_cond_mutex); +pthread_cond_destroy (&priv->reset_cond); +} +static int mana_dev_info_get (struct rte_eth_dev *dev,struct rte_eth_dev_info *dev_info)
@@ -391,6 +451,27 @@ mana_dev_info_get(struct rte_eth_dev *dev,return 0 ; } +static int +mana_dev_info_get_lock (struct rte_eth_dev *dev, +struct rte_eth_dev_info *dev_info) +{ +struct mana_priv *priv = dev->data->dev_private; +int ret; + +if (rte_spinlock_trylock (&priv->reset_ops_lock)) { +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE ) { +rte_spinlock_unlock (&priv->reset_ops_lock); +return -EBUSY ; + } + ret =mana_dev_info_get (dev, dev_info); +rte_spinlock_unlock (&priv->reset_ops_lock); + }else { + ret = -EBUSY ; + } +return ret; +} +static void mana_dev_tx_queue_info (struct rte_eth_dev *dev,uint16_t queue_id,struct rte_eth_txq_info *qinfo)
@@ -552,6 +633,29 @@ mana_dev_tx_queue_setup(struct rte_eth_dev *dev, uint16_t queue_idx,return ret; } +static int +mana_dev_tx_queue_setup_lock (struct rte_eth_dev *dev,uint16_t queue_idx, +uint16_t nb_desc,unsigned int socket_id, +const struct rte_eth_txconf *tx_conf) +{ +struct mana_priv *priv = dev->data->dev_private; +int ret; + +if (rte_spinlock_trylock (&priv->reset_ops_lock)) { +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE ) { +rte_spinlock_unlock (&priv->reset_ops_lock); +return -EBUSY ; + } + ret =mana_dev_tx_queue_setup (dev, queue_idx, + nb_desc, socket_id, tx_conf); +rte_spinlock_unlock (&priv->reset_ops_lock); + }else { + ret = -EBUSY ; + } +return ret; +} +static void mana_dev_tx_queue_release (struct rte_eth_dev *dev,uint16_t qid) {
@@ -629,6 +733,30 @@ mana_dev_rx_queue_setup(struct rte_eth_dev *dev, uint16_t queue_idx,return ret; } +static int +mana_dev_rx_queue_setup_lock (struct rte_eth_dev *dev,uint16_t queue_idx, +uint16_t nb_desc,unsigned int socket_id, + const struct rte_eth_rxconf *rx_conf __rte_unused, +struct rte_mempool *mp) +{ +struct mana_priv *priv = dev->data->dev_private; +int ret; + +if (rte_spinlock_trylock (&priv->reset_ops_lock)) { +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE ) { +rte_spinlock_unlock (&priv->reset_ops_lock); +return -EBUSY ; + } + ret =mana_dev_rx_queue_setup (dev, queue_idx, nb_desc, + socket_id, rx_conf, mp); +rte_spinlock_unlock (&priv->reset_ops_lock); + }else { + ret = -EBUSY ; + } +return ret; +} +static void mana_dev_rx_queue_release (struct rte_eth_dev *dev,uint16_t qid) {
@@ -820,33 +948,221 @@ mana_mtu_set(struct rte_eth_dev *dev, uint16_t mtu)return mana_ifreq (priv,SIOCSIFMTU , &request); } +#define MANA_OPS_1_LOCK (_func) \ +static int \ +_func##_lock(struct rte_eth_dev *dev) \ +{ \ + struct mana_priv *priv = dev->data->dev_private; \ + int ret; \ + if (rte_spinlock_trylock(&priv->reset_ops_lock)) { \ + if (rte_atomic_load_explicit(&priv->dev_state, \ + rte_memory_order_acquire) != \ + MANA_DEV_ACTIVE) { \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + return -EBUSY; \ + } \ + ret = _func(dev); \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + } else { \ + ret = -EBUSY; \ + } \ + return ret; \ +} + +MANA_OPS_1_LOCK(mana_dev_configure) + +MANA_OPS_1_LOCK(mana_dev_start) + +#undef MANA_OPS_1_LOCK + +/* +* Custom lock wrappers for dev_stop and dev_close. +* These join any active reset thread and use a blocking lock (not +* trylock) so they wait for any in-progress reset processing to +* finish, rather than returning -EBUSY. When the device is not in +* MANA_DEV_ACTIVE state, they transition state to MANA_DEV_ACTIVE. +*/ +static int +mana_dev_stop_lock (struct rte_eth_dev *dev) +{ +struct mana_priv *priv = dev->data->dev_private; +int ret; + +/* Signal reset thread to stop by setting state, then wait for it. +* Must be done before acquiring the lock to avoid deadlock +* (reset thread also acquires the lock). +*/ +if (rte_atomic_load_explicit (&priv->reset_thread_active, + rte_memory_order_acquire)) { +pthread_mutex_lock (&priv->reset_cond_mutex); +rte_atomic_store_explicit (&priv->dev_state, +MANA_DEV_ACTIVE , rte_memory_order_release); +pthread_cond_signal (&priv->reset_cond); +pthread_mutex_unlock (&priv->reset_cond_mutex); +rte_thread_join (priv->reset_thread,NULL ); +rte_atomic_store_explicit (&priv->reset_thread_active, + false, rte_memory_order_release); + } + +rte_spinlock_lock (&priv->reset_ops_lock); + +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE ) { +rte_atomic_store_explicit (&priv->dev_state, +MANA_DEV_ACTIVE , rte_memory_order_release); +rte_spinlock_unlock (&priv->reset_ops_lock); +return 0 ; + } + + ret =mana_dev_stop (dev); +rte_spinlock_unlock (&priv->reset_ops_lock); +return ret; +} + +static int +mana_dev_close_lock (struct rte_eth_dev *dev) +{ +struct mana_priv *priv = dev->data->dev_private; +int ret; + +/* Signal reset thread to stop by setting state, then wait for it. +* Must be done before acquiring the lock to avoid deadlock +* (reset thread also acquires the lock). +*/ +if (rte_atomic_load_explicit (&priv->reset_thread_active, + rte_memory_order_acquire)) { +pthread_mutex_lock (&priv->reset_cond_mutex); +rte_atomic_store_explicit (&priv->dev_state, +MANA_DEV_ACTIVE , rte_memory_order_release); +pthread_cond_signal (&priv->reset_cond); +pthread_mutex_unlock (&priv->reset_cond_mutex); +rte_thread_join (priv->reset_thread,NULL ); +rte_atomic_store_explicit (&priv->reset_thread_active, + false, rte_memory_order_release); + } + +rte_spinlock_lock (&priv->reset_ops_lock); + +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE ) { +rte_atomic_store_explicit (&priv->dev_state, +MANA_DEV_ACTIVE , rte_memory_order_release); + } + + ret =mana_dev_close (dev); +rte_spinlock_unlock (&priv->reset_ops_lock); +return ret; +} + +#define MANA_OPS_2_LOCK (_func) \ +static int \ +_func##_lock(struct rte_eth_dev *dev, \ + struct rte_eth_rss_conf *rss_conf) \ +{ \ + struct mana_priv *priv = dev->data->dev_private; \ + int ret; \ + if (rte_spinlock_trylock(&priv->reset_ops_lock)) { \ + if (rte_atomic_load_explicit(&priv->dev_state, \ + rte_memory_order_acquire) != \ + MANA_DEV_ACTIVE) { \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + return -EBUSY; \ + } \ + ret = _func(dev, rss_conf); \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + } else { \ + ret = -EBUSY; \ + } \ + return ret; \ +} + +MANA_OPS_2_LOCK(mana_rss_hash_update) + +MANA_OPS_2_LOCK(mana_rss_hash_conf_get) +#undef MANA_OPS_2_LOCK + +#define MANA_OPS_3_LOCK (_func, _arg) \ +static void \ +_func##_lock(struct rte_eth_dev *dev, uint16_t _arg) \ +{ \ + struct mana_priv *priv = dev->data->dev_private; \ + if (rte_spinlock_trylock(&priv->reset_ops_lock)) { \ + if (rte_atomic_load_explicit(&priv->dev_state, \ + rte_memory_order_acquire) != \ + MANA_DEV_ACTIVE) { \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + DRV_LOG(ERR, "Device reset in progress, " \ + "%s not called", #_func); \ + return; \ + } \ + _func(dev, _arg); \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + } else { \ + DRV_LOG(ERR, "Device reset in progress, " \ + "%s not called", #_func); \ + } \ +} + +MANA_OPS_3_LOCK(mana_dev_tx_queue_release, qid) + +MANA_OPS_3_LOCK(mana_dev_rx_queue_release, qid) +#undef MANA_OPS_3_LOCK + +#define MANA_OPS_4_LOCK (_func, _arg) \ +static int \ +_func##_lock(struct rte_eth_dev *dev, uint16_t _arg) \ +{ \ + struct mana_priv *priv = dev->data->dev_private; \ + int ret; \ + if (rte_spinlock_trylock(&priv->reset_ops_lock)) { \ + if (rte_atomic_load_explicit(&priv->dev_state, \ + rte_memory_order_acquire) != \ + MANA_DEV_ACTIVE) { \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + return -EBUSY; \ + } \ + ret = _func(dev, _arg); \ + rte_spinlock_unlock(&priv->reset_ops_lock); \ + } else { \ + ret = -EBUSY; \ + } \ + return ret; \ +} + +MANA_OPS_4_LOCK(mana_rx_intr_enable, rx_queue_id) + +MANA_OPS_4_LOCK(mana_rx_intr_disable, rx_queue_id) + +MANA_OPS_4_LOCK(mana_mtu_set, mtu) +#undef MANA_OPS_4_LOCK +static const struct eth_dev_ops mana_dev_ops = { - .dev_configure = mana_dev_configure, - .dev_start = mana_dev_start, - .dev_stop = mana_dev_stop, - .dev_close = mana_dev_close, - .dev_infos_get = mana_dev_info_get, + .dev_configure = mana_dev_configure_lock, + .dev_start = mana_dev_start_lock, + .dev_stop = mana_dev_stop_lock, + .dev_close = mana_dev_close_lock, + .dev_infos_get = mana_dev_info_get_lock, .txq_info_get = mana_dev_tx_queue_info, .rxq_info_get = mana_dev_rx_queue_info, .dev_supported_ptypes_get = mana_supported_ptypes, - .rss_hash_update = mana_rss_hash_update, - .rss_hash_conf_get = mana_rss_hash_conf_get, - .tx_queue_setup = mana_dev_tx_queue_setup, - .tx_queue_release = mana_dev_tx_queue_release, - .rx_queue_setup = mana_dev_rx_queue_setup, - .rx_queue_release = mana_dev_rx_queue_release, - .rx_queue_intr_enable = mana_rx_intr_enable, - .rx_queue_intr_disable = mana_rx_intr_disable, + .rss_hash_update = mana_rss_hash_update_lock, + .rss_hash_conf_get = mana_rss_hash_conf_get_lock, + .tx_queue_setup = mana_dev_tx_queue_setup_lock, + .tx_queue_release = mana_dev_tx_queue_release_lock, + .rx_queue_setup = mana_dev_rx_queue_setup_lock, + .rx_queue_release = mana_dev_rx_queue_release_lock, + .rx_queue_intr_enable = mana_rx_intr_enable_lock, + .rx_queue_intr_disable = mana_rx_intr_disable_lock, .link_update = mana_dev_link_update, .stats_get = mana_dev_stats_get, .stats_reset = mana_dev_stats_reset, - .mtu_set = mana_mtu_set, + .mtu_set = mana_mtu_set_lock, };static const struct eth_dev_ops mana_dev_secondary_ops = { .stats_get = mana_dev_stats_get, .stats_reset = mana_dev_stats_reset, - .dev_infos_get = mana_dev_info_get, + .dev_infos_get = mana_dev_info_get_lock, };uint16_t
@@ -1031,28 +1347,434 @@ mana_ibv_device_to_pci_addr(const struct ibv_device *device,return 0 ; } +static int mana_pci_probe (struct rte_pci_driver *pci_drv, +struct rte_pci_device *pci_dev); +static void mana_intr_handler (void *arg); +static void mana_reset_exit (struct mana_priv *priv); + +/* Delay before initiating reset exit after reset enter completes */ +#define MANA_RESET_TIMER_US (15 * 1000000ULL)/* 15 seconds */ +/* -* Interrupt handler from IB layer to notify this device is being removed. +* Callback for PCI device removal events from EAL. +* If the device is in reset (RESET_EXIT state), this means the PCI +* device was hot-removed rather than a service reset. Wake the reset +* thread via condvar and notify netvsc via RTE_ETH_EVENT_INTR_RMV. +*/ +static void +mana_pci_remove_event_cb (const char *device_name, +enum rte_dev_event_type event,void *cb_arg) + __rte_no_thread_safety_analysis +{ +struct mana_priv *priv = cb_arg; +struct rte_eth_dev *dev; + +if (event !=RTE_DEV_EVENT_REMOVE ) +return ; + +DRV_LOG (INFO ,"PCI device %s removed" , device_name); + +/* Wake the reset thread immediately */ +pthread_mutex_lock (&priv->reset_cond_mutex); +rte_atomic_store_explicit (&priv->dev_state, +MANA_DEV_RESET_FAILED , rte_memory_order_release); +pthread_cond_signal (&priv->reset_cond); +pthread_mutex_unlock (&priv->reset_cond_mutex); + +rte_spinlock_lock (&priv->reset_ops_lock); + + dev = &rte_eth_devices[priv->port_id]; +DRV_LOG (INFO ,"Sending RTE_ETH_EVENT_INTR_RMV for port %u" , + priv->port_id); +rte_eth_dev_callback_process (dev, +RTE_ETH_EVENT_INTR_RMV ,NULL ); + +rte_spinlock_unlock (&priv->reset_ops_lock); +} + +/* +* Reset thread: sleeps for the reset timer period, then performs +* the reset exit sequence. Runs on a control thread so it can call +* rte_intr_callback_unregister (which fails from alarm/intr thread). +*/ +static uint32_t +mana_reset_thread (void *arg) + __rte_no_thread_safety_analysis +{ +struct mana_priv *priv = (struct mana_priv *)arg; +struct timespec ts; + +DRV_LOG (INFO ,"Reset thread started, waiting %us" , + (unsigned int )(MANA_RESET_TIMER_US /1000000 )); + +/* Wait on condvar with timeout — can be woken early by PCI remove */ +clock_gettime (CLOCK_REALTIME , &ts); + ts.tv_sec +=MANA_RESET_TIMER_US /1000000 ; + +pthread_mutex_lock (&priv->reset_cond_mutex); +pthread_cond_timedwait (&priv->reset_cond, &priv->reset_cond_mutex, &ts); +pthread_mutex_unlock (&priv->reset_cond_mutex); + +rte_spinlock_lock (&priv->reset_ops_lock); + +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_RESET_EXIT ) { +DRV_LOG (INFO ,"Reset thread: dev_state=%d, skipping" , + (int )rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire)); +rte_spinlock_unlock (&priv->reset_ops_lock); +return 0 ; + } + +DRV_LOG (INFO ,"Reset thread: initiating reset exit" ); +mana_reset_exit (priv); +/* Lock is released by mana_reset_exit_delay at the end of +* the reset exit processing. +* +* reset_thread_active is NOT cleared here — the joiner +* (dev_stop_lock/dev_close_lock) is responsible for joining +* and clearing the flag to avoid leaking the thread. +*/ +return 0 ; +} + +static void +mana_reset_enter (struct mana_priv *priv) + __rte_no_thread_safety_analysis +{ +int ret; +uint64_t ticket; +struct rte_eth_dev *dev = &rte_eth_devices[priv->port_id]; + +/* +* Lock ownership for reset_ops_lock through the reset path: +* +* mana_intr_handler — acquires the lock +* mana_reset_enter — called with lock held, releases it +* after spawning the reset thread +* mana_reset_thread — re-acquires the lock after the +* recovery delay +* mana_reset_exit — called with lock held, passes it +* to mana_reset_exit_delay +* mana_reset_exit_delay — called with lock held, releases it +* on completion +*/ + +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_ENTER , + rte_memory_order_release); + +DRV_LOG (DEBUG ,"Entering into device reset state" ); +DRV_LOG (DEBUG ,"Resetting dev = %p, priv = %p" , dev, priv); + + ticket =rte_rcu_qsbr_start (priv->dev_state_qsv); + +while (rte_rcu_qsbr_check (priv->dev_state_qsv, ticket, false) ==0 ) +rte_pause (); + +DRV_LOG (DEBUG ,"All threads are quiescent" ); + +/* Stop data path on primary and secondary before unmapping doorbell */ + ret =mana_dev_stop (dev); +if (ret) { +DRV_LOG (ERR ,"Failed to stop mana dev ret %d" , ret); +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_FAILED , + rte_memory_order_release); + goto reset_failed; + } + +/* Unmap secondary doorbell pages after data path is stopped */ + ret =mana_mp_req_on_rxtx (dev,MANA_MP_REQ_RESET_ENTER ); +if (ret) { +DRV_LOG (ERR ,"Failed to reset secondary processes ret = %d" , + ret); +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_FAILED , + rte_memory_order_release); + goto reset_failed; + } + + ret =mana_dev_close (dev); +if (ret) { +DRV_LOG (ERR ,"Failed to close mana dev ret %d" , ret); +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_FAILED , + rte_memory_order_release); + goto reset_failed; + } + +for (int i =0 ; i < priv->num_queues; i++) { +struct mana_rxq *rxq = dev->data->rx_queues[i]; +struct mana_txq *txq = dev->data->tx_queues[i]; + +DRV_LOG (DEBUG ,"Free MR for priv = %p, rxq %u, txq %u" , + priv, rxq->rxq_idx, txq->txq_idx); +mana_mr_btree_free (&rxq->mr_btree); +mana_mr_btree_free (&txq->mr_btree); + } + +DRV_LOG (DEBUG ,"Reset processing exited successfully" ); + +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_EXIT , + rte_memory_order_release); + +/* Join previous reset thread if it completed but was not joined */ +if (rte_atomic_load_explicit (&priv->reset_thread_active, + rte_memory_order_acquire)) { +rte_thread_join (priv->reset_thread,NULL ); +rte_atomic_store_explicit (&priv->reset_thread_active, + false, rte_memory_order_release); + } + + ret =rte_thread_create_internal_control (&priv->reset_thread, +"mana-reset" , + mana_reset_thread, priv); +if (ret) { +DRV_LOG (ERR ,"Failed to create reset thread ret %d" , ret); +rte_atomic_store_explicit (&priv->dev_state, +MANA_DEV_RESET_FAILED , + rte_memory_order_release); +rte_spinlock_unlock (&priv->reset_ops_lock); +return ; + } +rte_atomic_store_explicit (&priv->reset_thread_active, + true, rte_memory_order_release); + +DRV_LOG (DEBUG ,"Reset thread started" ); + +/* Release the lock so the application can call dev_stop/dev_close */ +rte_spinlock_unlock (&priv->reset_ops_lock); +return ; + +reset_failed: +rte_spinlock_unlock (&priv->reset_ops_lock); +} + +static uint32_t +mana_reset_exit_delay (void *arg) + __rte_no_thread_safety_analysis +{ +struct mana_priv *priv = (struct mana_priv *)arg; +uint32_t ret =0 ; +int i; +struct rte_eth_dev *dev; +struct rte_pci_device *pci_dev; + +DRV_LOG (DEBUG ,"Delayed mana device reset complete processing" ); + +/* If the app called dev_stop/dev_close during the timer window, +* state is no longer RESET_EXIT. Nothing to do. +*/ +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_RESET_EXIT ) { +DRV_LOG (DEBUG ,"State is not RESET_EXIT, skipping" ); +rte_spinlock_unlock (&priv->reset_ops_lock); +return ret; + } + + dev = &rte_eth_devices[priv->port_id]; + pci_dev =RTE_ETH_DEV_TO_PCI (dev); + +DRV_LOG (DEBUG ,"Resetting dev = %p, priv = %p" , dev, priv); + + ret =ibv_close_device (priv->ib_ctx); + priv->ib_ctx =NULL ; +if (ret) { +DRV_LOG (ERR ,"Failed to close ibv device %d" , ret); +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_FAILED , + rte_memory_order_release); + goto out; + } + + ret =mana_pci_probe (NULL , pci_dev); +if (ret) { +DRV_LOG (ERR ,"Failed to probe mana pci dev ret %d" , ret); +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_FAILED , + rte_memory_order_release); + goto out; + } + +/* +* Init the local MR caches. +*/ +for (i =0 ; i < priv->num_queues; i++) { +struct mana_rxq *rxq = dev->data->rx_queues[i]; +struct mana_txq *txq = dev->data->tx_queues[i]; + + ret =mana_mr_btree_init (&rxq->mr_btree, +MANA_MR_BTREE_PER_QUEUE_N , + rxq->socket); +if (ret) { +DRV_LOG (ERR ,"Failed to init RXQ %d MR btree " +"on socket %u, ret %d" , i, rxq->socket, ret); + goto mr_init_failed_rxq; + } + + ret =mana_mr_btree_init (&txq->mr_btree, +MANA_MR_BTREE_PER_QUEUE_N , + txq->socket); +if (ret) { +DRV_LOG (ERR ,"Failed to init TXQ %d MR btree " +"on socket %u, ret %d" , i, txq->socket, ret); + goto mr_init_failed_txq; + } + } +DRV_LOG (DEBUG ,"priv %p, num_queues %u" , priv, priv->num_queues); + +/* Start secondaries */ + ret =mana_mp_req_on_rxtx (dev,MANA_MP_REQ_RESET_EXIT ); +if (ret) { +DRV_LOG (ERR ,"Failed to start secondary processes ret = %d" , + ret); + goto mr_init_failed_all; + } + + ret =mana_dev_start (dev); +if (ret) { +DRV_LOG (ERR ,"Failed to start mana dev ret %d" , ret); + goto mr_init_failed_all; + } + +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_ACTIVE , + rte_memory_order_release); + +DRV_LOG (DEBUG ,"Exiting the reset complete processing" ); + +DRV_LOG (INFO ,"Sending RTE_ETH_EVENT_RECOVERY_SUCCESS for port %u" , + priv->port_id); +rte_eth_dev_callback_process (dev, +RTE_ETH_EVENT_RECOVERY_SUCCESS ,NULL ); + +out: +if (ret) { +DRV_LOG (INFO ,"Sending RTE_ETH_EVENT_RECOVERY_FAILED for port %u" , + priv->port_id); +rte_eth_dev_callback_process (dev, +RTE_ETH_EVENT_RECOVERY_FAILED ,NULL ); + } +rte_spinlock_unlock (&priv->reset_ops_lock); +return ret; + +mr_init_failed_all: + i = priv->num_queues; + goto mr_init_failed_rxq; + +mr_init_failed_txq: +/* RXQ btree at index i was initialized, free it */ +mana_mr_btree_free (&((struct mana_rxq *) + dev->data->rx_queues[i])->mr_btree); + +mr_init_failed_rxq: +/* Free all fully initialized btrees for indices < i */ +for (int j =0 ; j < i; j++) { +struct mana_rxq *rxq = dev->data->rx_queues[j]; +struct mana_txq *txq = dev->data->tx_queues[j]; + +mana_mr_btree_free (&rxq->mr_btree); +mana_mr_btree_free (&txq->mr_btree); + } +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_RESET_FAILED , + rte_memory_order_release); + +DRV_LOG (INFO ,"Sending RTE_ETH_EVENT_RECOVERY_FAILED (MR init) for port %u" , + priv->port_id); +rte_eth_dev_callback_process (dev, +RTE_ETH_EVENT_RECOVERY_FAILED ,NULL ); + +rte_spinlock_unlock (&priv->reset_ops_lock); +return ret; +} + +static void +mana_reset_exit (struct mana_priv *priv) + __rte_no_thread_safety_analysis +{ +int ret; + +if (!priv) { +DRV_LOG (ERR ,"Private structure invalid" ); +return ; + } +DRV_LOG (DEBUG ,"Entering into device reset complete processing" ); + +rxq_intr_disable (priv); + +/* Unregister the interrupt handler. Since mana_reset_exit is always +* called from mana_reset_thread (a non-interrupt thread), the +* interrupt source is inactive and rte_intr_callback_unregister +* succeeds directly. +*/ +if (priv->intr_handle) { + ret =rte_intr_callback_unregister (priv->intr_handle, + mana_intr_handler, priv); +if (ret <0 ) +DRV_LOG (ERR ,"Failed to unregister intr callback ret %d" , + ret); +else +DRV_LOG (DEBUG ,"%d intr callback(s) removed" , ret); + +rte_intr_instance_free (priv->intr_handle); + priv->intr_handle =NULL ; + } + +/* Proceed directly to reset exit delay (re-probe and restart). +* No need for a separate thread - we are already on +* mana_reset_thread which is a non-interrupt control thread. +*/ +mana_reset_exit_delay (priv); +} + +/* +* Interrupt handler from IB layer to notify this device is +* being removed or reset. */ static void mana_intr_handler (void *arg) + __rte_no_thread_safety_analysis {struct mana_priv *priv = arg;struct ibv_context *ctx = priv->ib_ctx; -struct ibv_async_event event; +struct ibv_async_event event = {0 }; +struct rte_eth_dev *dev;/* Read and ack all messages from IB device */ while (true) {if (ibv_get_async_event (ctx, &event))break ; -if (event.event_type ==IBV_EVENT_DEVICE_FATAL ) { -struct rte_eth_dev *dev; - - dev = &rte_eth_devices[priv->port_id]; -if (dev->data->dev_conf.intr_conf.rmv) +switch (event.event_type) { +case IBV_EVENT_DEVICE_FATAL : +DRV_LOG (INFO ,"IBV_EVENT_DEVICE_FATAL received, dev_state=%d" , + (int )rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire)); +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) ==MANA_DEV_ACTIVE ) { +rte_spinlock_lock (&priv->reset_ops_lock); + +/* Re-check after lock to avoid racing with +* mana_pci_remove_event_cb which may have +* set RESET_FAILED while we waited. +*/ +if (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) != +MANA_DEV_ACTIVE ) { +rte_spinlock_unlock ( + &priv->reset_ops_lock); +break ; + } +mana_reset_enter (priv); + + dev = &rte_eth_devices[priv->port_id]; +DRV_LOG (INFO ,"Sending RTE_ETH_EVENT_ERR_RECOVERING for port %u" , + priv->port_id);rte_eth_dev_callback_process (dev, -RTE_ETH_EVENT_INTR_RMV ,NULL ); +RTE_ETH_EVENT_ERR_RECOVERING ,NULL ); + }else { +DRV_LOG (ERR ,"Already in reset handling, dev_state=%d" , + (int )rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire)); + } +break ; + +default : +break ; }ibv_ack_async_event (&event);
@@ -1063,6 +1785,20 @@ static int mana_intr_uninstall(struct mana_priv *priv) {int ret; +struct rte_eth_dev *dev; + +if (!priv->intr_handle) +return 0 ; + +/* Unregister PCI device removal event callback */ + dev = &rte_eth_devices[priv->port_id]; +if (dev->device) { +do { + ret =rte_dev_event_callback_unregister ( + dev->device->name, + mana_pci_remove_event_cb, priv); + }while (ret == -EAGAIN ); + } ret =rte_intr_callback_unregister (priv->intr_handle, mana_intr_handler, priv);
@@ -1072,6 +1808,7 @@ mana_intr_uninstall(struct mana_priv *priv) }rte_intr_instance_free (priv->intr_handle); + priv->intr_handle =NULL ;return 0 ; }
@@ -1127,6 +1864,16 @@ mana_intr_install(struct rte_eth_dev *eth_dev, struct mana_priv *priv) goto free_intr; } +/* Register for PCI device removal events to distinguish +* PCI hot-remove from service reset. This requires the +* application to call rte_dev_event_monitor_start() for +* events to be delivered (e.g. testpmd --hot-plug-handling). +*/ + ret =rte_dev_event_callback_register (eth_dev->device->name, + mana_pci_remove_event_cb, priv); +if (ret) +DRV_LOG (WARNING ,"Failed to register PCI remove event callback" ); + eth_dev->intr_handle = priv->intr_handle;return 0 ;
@@ -1156,7 +1903,7 @@ mana_proc_priv_init(struct rte_eth_dev *dev)/* * Map the doorbell page for the secondary process through IB device handle. */ -static int +int mana_map_doorbell_secondary (struct rte_eth_dev *eth_dev,int fd) {struct mana_process_priv *priv = eth_dev->process_private;
@@ -1294,17 +2041,30 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr,char name[RTE_ETH_NAME_MAX_LEN ];int ret;struct ibv_context *ctx =NULL ; +size_t sz; +bool is_reset = false; +pthread_mutexattr_t mattr; +pthread_condattr_t cattr;rte_ether_format_addr (address,sizeof (address), addr); -DRV_LOG (INFO ,"device located port %u address %s" , port, address); - priv =rte_zmalloc_socket (NULL ,sizeof (*priv),RTE_CACHE_LINE_SIZE , -SOCKET_ID_ANY ); -if (!priv) -return -ENOMEM ; +DRV_LOG (DEBUG ,"device located port %u address %s" , port, address);snprintf (name,sizeof (name),"%s_port%d" , pci_dev->device.name, port); + eth_dev =rte_eth_dev_allocated (name); +if (eth_dev) { + is_reset = true; + priv = eth_dev->data->dev_private; +DRV_LOG (DEBUG ,"Device reset for eth_dev %p priv %p" , + eth_dev, priv); + }else { + priv =rte_zmalloc_socket (NULL ,sizeof (*priv),RTE_CACHE_LINE_SIZE , +SOCKET_ID_ANY ); +if (!priv) +return -ENOMEM ; + } +if (rte_eal_process_type () ==RTE_PROC_SECONDARY ) {int fd;
@@ -1317,6 +2077,7 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr, eth_dev->device = &pci_dev->device; eth_dev->dev_ops = &mana_dev_secondary_ops; + ret =mana_proc_priv_init (eth_dev);if (ret) goto failed;
@@ -1336,7 +2097,7 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr, goto failed; } -/* fd is no not used after mapping doorbell */ +/* fd is not used after mapping doorbell */ close (fd); eth_dev->tx_pkt_burst = mana_tx_burst;
@@ -1355,22 +2116,6 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr, goto failed; } - eth_dev =rte_eth_dev_allocate (name); -if (!eth_dev) { - ret = -ENOMEM ; - goto failed; - } - - eth_dev->data->mac_addrs = -rte_calloc ("mana_mac" ,1 , -sizeof (struct rte_ether_addr ),0 ); -if (!eth_dev->data->mac_addrs) { - ret = -ENOMEM ; - goto failed; - } - -rte_ether_addr_copy (addr, eth_dev->data->mac_addrs); - priv->ib_pd =ibv_alloc_pd (ctx);if (!priv->ib_pd) {DRV_LOG (ERR ,"ibv_alloc_pd failed port %d" , port);
@@ -1390,10 +2135,6 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr, } priv->ib_ctx = ctx; - priv->port_id = eth_dev->data->port_id; - priv->dev_port = port; - eth_dev->data->dev_private = priv; - priv->dev_data = eth_dev->data; priv->max_rx_queues = dev_attr->orig_attr.max_qp; priv->max_tx_queues = dev_attr->orig_attr.max_qp;
@@ -1415,23 +2156,93 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr, name, priv->max_rx_queues, priv->max_rx_desc, priv->max_send_sge, priv->max_mr_size); +if (!is_reset) { + eth_dev =rte_eth_dev_allocate (name); +if (!eth_dev) { + ret = -ENOMEM ; + goto failed; + } + + eth_dev->data->mac_addrs = +rte_calloc ("mana_mac" ,1 , +sizeof (struct rte_ether_addr ),0 ); +if (!eth_dev->data->mac_addrs) { + ret = -ENOMEM ; + goto failed; + } + +rte_ether_addr_copy (addr, eth_dev->data->mac_addrs); + }else { +/* +* Reset path. +*/ +rte_ether_format_addr (address,RTE_ETHER_ADDR_FMT_SIZE , + eth_dev->data->mac_addrs); +DRV_LOG (DEBUG ,"Found existing eth_dev %p with mac addr %s" , + eth_dev, address); +DRV_LOG (DEBUG ,"ib_ctx = %p" , priv->ib_ctx); + goto out; + } + + priv->port_id = eth_dev->data->port_id; + priv->dev_port = port; + eth_dev->data->dev_private = priv; + priv->dev_data = eth_dev->data; +rte_atomic_store_explicit (&priv->dev_state,MANA_DEV_ACTIVE , + rte_memory_order_release); +rte_eth_copy_pci_info (eth_dev, pci_dev); -/* Create async interrupt handler */ - ret =mana_intr_install (eth_dev, priv); -if (ret) { -DRV_LOG (ERR ,"Failed to install intr handler" ); +/* +* Now we've got maximum queues. Init the qsv to be the +* double of maximum queues for both rx and tx queues. +*/ + sz =rte_rcu_qsbr_get_memsize (2 * priv->max_rx_queues); + priv->dev_state_qsv =rte_zmalloc_socket ("mana_rcu" , sz, +RTE_CACHE_LINE_SIZE , +SOCKET_ID_ANY ); +if (!priv->dev_state_qsv) { +DRV_LOG (ERR ,"No memory for dev_state_qsv" ); + ret = -ENOMEM ; + goto failed; + } + ret =rte_rcu_qsbr_init (priv->dev_state_qsv,2 * priv->max_rx_queues); +if (ret <0 ) { +DRV_LOG (ERR ,"Init dev_state_qsv failed ret %d" , ret); goto failed; } - eth_dev->device = &pci_dev->device; +rte_spinlock_init (&priv->reset_ops_lock); + +pthread_mutexattr_init (&mattr); +pthread_mutexattr_setpshared (&mattr,PTHREAD_PROCESS_SHARED ); +pthread_mutex_init (&priv->reset_cond_mutex, &mattr); +pthread_mutexattr_destroy (&mattr); -DRV_LOG (INFO ,"device %s at port %u" , name, eth_dev->data->port_id); +pthread_condattr_init (&cattr); +pthread_condattr_setpshared (&cattr,PTHREAD_PROCESS_SHARED ); +pthread_cond_init (&priv->reset_cond, &cattr); +pthread_condattr_destroy (&cattr); + + eth_dev->device = &pci_dev->device; eth_dev->rx_pkt_burst = mana_rx_burst_removed; eth_dev->tx_pkt_burst = mana_tx_burst_removed; eth_dev->dev_ops = &mana_dev_ops; +out: +/* Create async interrupt handler */ + ret =mana_intr_install (eth_dev, priv); +if (ret) { +DRV_LOG (ERR ,"Failed to install intr handler, ret %d" , ret); + goto failed; + }else { +DRV_LOG (INFO ,"mana_intr_install succeeded" ); + } + +DRV_LOG (INFO ,"device %s priv %p dev port %d at port %u" , + name, priv, priv->dev_port, eth_dev->data->port_id); +rte_eth_dev_probing_finish (eth_dev);return 0 ;
@@ -1439,20 +2250,32 @@ mana_probe_port(struct ibv_device *ibdev, struct ibv_device_attr_ex *dev_attr, failed:/* Free the resource for the port failed */ if (priv) { -if (priv->ib_parent_pd) +if (!is_reset && priv->dev_state_qsv) +rte_free (priv->dev_state_qsv); + +if (priv->ib_parent_pd) {ibv_dealloc_pd (priv->ib_parent_pd); + priv->ib_parent_pd =NULL ; + } -if (priv->ib_pd) +if (priv->ib_pd) {ibv_dealloc_pd (priv->ib_pd); + priv->ib_pd =NULL ; + } } -if (eth_dev) -rte_eth_dev_release_port (eth_dev); +if (!is_reset) { +if (eth_dev) +rte_eth_dev_release_port (eth_dev); -rte_free (priv); +rte_free (priv); + } -if (ctx) +if (ctx) {ibv_close_device (ctx); +if (is_reset && priv) + priv->ib_ctx =NULL ; + }return ret; }
@@ -1617,7 +2440,11 @@ mana_pci_probe(struct rte_pci_driver *pci_drv __rte_unused,static int mana_dev_uninit (struct rte_eth_dev *dev) { -return mana_dev_close (dev); +int ret; + + ret =mana_dev_close (dev); +mana_dev_free_resources (dev); +return ret; }/*
drivers/net/mana/mana.h +32 -1
@@ -5,6 +5,8 @@#ifndef __MANA_H__#define __MANA_H__ +#include <pthread.h> +#define PCI_VENDOR_ID_MICROSOFT 0x1414#define PCI_DEVICE_ID_MICROSOFT_MANA_PF 0x00b9#define PCI_DEVICE_ID_MICROSOFT_MANA 0x00ba
@@ -337,6 +339,20 @@ struct mana_process_priv {void *db_page; }; +enum mana_device_state { +/* Normal running */ +MANA_DEV_ACTIVE =0 , +/* In reset enter processing */ +MANA_DEV_RESET_ENTER =1 , +/* +* Reset enter processing completed. +* Waiting for reset exit or in reset exit processing. +*/ +MANA_DEV_RESET_EXIT =2 , +/* Reset failed */ +MANA_DEV_RESET_FAILED =3 , +}; +struct mana_priv {struct rte_eth_dev_data *dev_data;struct mana_process_priv *process_priv;
@@ -368,6 +384,16 @@ struct mana_priv {uint64_t max_mr_size;struct mana_mr_btree mr_btree;rte_spinlock_t mr_btree_lock; +RTE_ATOMIC (enum mana_device_state ) dev_state; +struct rte_rcu_qsbr *dev_state_qsv; +/* lock for synchronizing mana reset and some mana_dev_ops callbacks */ +rte_spinlock_t reset_ops_lock; +/* Reset thread ID, valid when reset_thread_active is true */ +rte_thread_t reset_thread; +RTE_ATOMIC (bool ) reset_thread_active; +/* Condvar to wake reset thread early on PCI remove */ +pthread_mutex_t reset_cond_mutex; +pthread_cond_t reset_cond; };struct mana_txq_desc {
@@ -427,6 +453,7 @@ struct mana_txq {struct mana_mr_btree mr_btree;struct mana_stats stats;unsigned int socket; +unsigned int txq_idx; };struct mana_rxq {
@@ -462,6 +489,7 @@ struct mana_rxq {struct mana_mr_btree mr_btree;unsigned int socket; +unsigned int rxq_idx; };extern int mana_logtype_driver;
@@ -543,6 +571,8 @@ enum mana_mp_req_type {MANA_MP_REQ_CREATE_MR ,MANA_MP_REQ_START_RXTX ,MANA_MP_REQ_STOP_RXTX , +MANA_MP_REQ_RESET_ENTER , + MANA_MP_REQ_RESET_EXIT, };/* Pameters for IPC. */
@@ -563,8 +593,9 @@ void mana_mp_uninit_primary(void);void mana_mp_uninit_secondary (void );int mana_mp_req_verbs_cmd_fd (struct rte_eth_dev *dev);int mana_mp_req_mr_create (struct mana_priv *priv,uintptr_t addr,uint32_t len); +int mana_map_doorbell_secondary (struct rte_eth_dev *eth_dev,int fd); -void mana_mp_req_on_rxtx (struct rte_eth_dev *dev,enum mana_mp_req_type type); +int mana_mp_req_on_rxtx (struct rte_eth_dev *dev,enum mana_mp_req_type type);void *mana_alloc_verbs_buf (size_t size,void *data); void mana_free_verbs_buf(void *ptr, void *data __rte_unused);
drivers/net/mana/meson.build +1 -1
@@ -7,7 +7,7 @@ if not is_linux or not (dpdk_conf.has('RTE_ARCH_X86') or dpdk_conf.has('RTE_ARCH subdir_done() endif -deps += ['pci', 'bus_pci', 'net', 'eal', 'kvargs'] +deps += ['pci', 'bus_pci', 'net', 'eal', 'kvargs', 'rcu'] sources += files( 'gdma.c',
drivers/net/mana/mp.c +82 -7
153 lines not on this page · raw message
drivers/net/mana/mr.c +4 -2
@@ -314,8 +314,10 @@ mana_mr_btree_init(struct mana_mr_btree *bt, int n, int socket)void mana_mr_btree_free (struct mana_mr_btree *bt) { -rte_free (bt->table); -memset (bt,0 ,sizeof (*bt)); +if (bt && bt->table) { +rte_free (bt->table); +memset (bt,0 ,sizeof (*bt)); + } }int
drivers/net/mana/rx.c +23 -1
@@ -2,6 +2,7 @@* Copyright 2022 Microsoft Corporation */ #include <ethdev_driver.h> +#include <rte_rcu_qsbr.h> #include <infiniband/verbs.h> #include <infiniband/manadv.h>
@@ -36,6 +37,11 @@ mana_rq_ring_doorbell(struct mana_rxq *rxq) db_page = process_priv->db_page; } +if (!db_page) { +DP_LOG (ERR ,"db_page is NULL, cannot ring RX doorbell" ); +return -EINVAL ; + } +/* Hardware Spec specifies that software client should set 0 for * wqe_cnt for Receive Queues. */
@@ -172,7 +178,7 @@ mana_stop_rx_queues(struct rte_eth_dev *dev)for (i =0 ; i < priv->num_queues; i++)if (dev->data->rx_queue_state[i] ==RTE_ETH_QUEUE_STATE_STOPPED ) -return -EINVAL ; +return 0 ;if (priv->rwq_qp) { ret =ibv_destroy_qp (priv->rwq_qp);
@@ -256,6 +262,9 @@ mana_start_rx_queues(struct rte_eth_dev *dev)struct mana_rxq *rxq = dev->data->rx_queues[i];struct ibv_wq_init_attr wq_attr = {}; + rxq->rxq_idx = i; +DRV_LOG (DEBUG ,"assigning rxq_idx to %d" , i); + manadv_set_context_attr(priv->ib_ctx, MANADV_CTX_ATTR_BUF_ALLOCATORS, (void *)((uintptr_t)&(struct manadv_ctx_allocators){
@@ -451,6 +460,17 @@ mana_rx_burst(void *dpdk_rxq, struct rte_mbuf **pkts, uint16_t pkts_n)uint32_t pkt_len;uint32_t i;int polled =0 ; +struct rte_rcu_qsbr *dstate_qsv = priv->dev_state_qsv; +unsigned int tid = rxq->rxq_idx; + +rte_rcu_qsbr_thread_online (dstate_qsv, tid); + +if (unlikely (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE )) { +/* Device reset occurred. */ +rte_rcu_qsbr_thread_offline (dstate_qsv, tid); +return 0 ; + } repoll:/* Polling on new completions if we have no backlog */
@@ -592,6 +612,8 @@ mana_rx_burst(void *dpdk_rxq, struct rte_mbuf **pkts, uint16_t pkts_n) wqe_consumed, ret); } +rte_rcu_qsbr_thread_offline (dstate_qsv, tid); +return pkt_received; }
drivers/net/mana/tx.c +29 -11
@@ -3,6 +3,7 @@*/ #include <ethdev_driver.h> +#include <rte_rcu_qsbr.h> #include <infiniband/verbs.h> #include <infiniband/manadv.h>
@@ -17,7 +18,7 @@ mana_stop_tx_queues(struct rte_eth_dev *dev)for (i =0 ; i < priv->num_queues; i++)if (dev->data->tx_queue_state[i] ==RTE_ETH_QUEUE_STATE_STOPPED ) -return -EINVAL ; +return 0 ;for (i =0 ; i < priv->num_queues; i++) {struct mana_txq *txq = dev->data->tx_queues[i];
@@ -83,6 +84,9 @@ mana_start_tx_queues(struct rte_eth_dev *dev) txq = dev->data->tx_queues[i]; + txq->txq_idx = i; +DRV_LOG (DEBUG ,"assigning txq_idx to %d" , txq->txq_idx); + manadv_set_context_attr(priv->ib_ctx, MANADV_CTX_ATTR_BUF_ALLOCATORS, (void *)((uintptr_t)&(struct manadv_ctx_allocators){
@@ -190,10 +194,30 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)void *db_page;uint16_t pkt_sent =0 ;uint32_t num_comp, i; +unsigned int tid = priv->num_queues + txq->txq_idx; +struct rte_rcu_qsbr *dstate_qsv = priv->dev_state_qsv;#ifdef RTE_ARCH_32 uint32_t wqe_count =0 ;#endif + db_page = priv->db_page; +if (rte_eal_process_type () ==RTE_PROC_SECONDARY ) { +struct rte_eth_dev *dev = + &rte_eth_devices[priv->dev_data->port_id]; +struct mana_process_priv *process_priv = dev->process_private; + + db_page = process_priv->db_page; + } + +rte_rcu_qsbr_thread_online (dstate_qsv, tid); + +if (unlikely (rte_atomic_load_explicit (&priv->dev_state, + rte_memory_order_acquire) !=MANA_DEV_ACTIVE || !db_page)) { +/* Device reset event occurred. */ +rte_rcu_qsbr_thread_offline (dstate_qsv, tid); +return 0 ; + } +/* Process send completions from GDMA */ num_comp =gdma_poll_completion_queue (&txq->gdma_cq, txq->gdma_comp_buf, txq->num_desc);
@@ -216,7 +240,8 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) }if (!desc->pkt) { -DP_LOG (ERR ,"mana_txq_desc has a NULL pkt" ); +DP_LOG (ERR ,"mana_txq_desc has a NULL pkt, priv %p, " +"txq = %d" , priv, txq->txq_idx); }else { txq->stats.bytes += desc->pkt->pkt_len;rte_pktmbuf_free (desc->pkt);
@@ -474,15 +499,6 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) }/* Ring hardware door bell */ - db_page = priv->db_page; -if (rte_eal_process_type () ==RTE_PROC_SECONDARY ) { -struct rte_eth_dev *dev = - &rte_eth_devices[priv->dev_data->port_id]; -struct mana_process_priv *process_priv = dev->process_private; - - db_page = process_priv->db_page; - } -if (pkt_sent) {#ifdef RTE_ARCH_32 ret = mana_ring_short_doorbell(db_page, GDMA_QUEUE_SEND,
@@ -501,5 +517,7 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)DP_LOG (ERR ,"mana_ring_doorbell failed ret %d" , ret); } +rte_rcu_qsbr_thread_offline (dstate_qsv, tid); +return pkt_sent; }
Signature · 1 line
2.34.1
PATCH v3 2/2 net/mana: add documentation for device reset support
In reply to Wei HuFrom: Wei Hu <[email protected]>
Add device reset section to mana.rst describing the two-phase reset flow, recovery events, and PCI remove event dependency. Add release note entry for the 26.07 release.
--- doc/guides/nics/mana.rst | 33 ++++++++++++++++++++++++++ doc/guides/rel_notes/release_26_07.rst | 8 +++++++ 2 files changed, 41 insertions(+)
doc/guides/nics/mana.rst +33 -0
@@ -71,3 +71,36 @@ The user can specify below argument in devargs. The default value is not set, meaning all the NICs will be probed and loaded. User can specify multiple mac=xx:xx:xx:xx:xx:xx arguments for up to 8 NICs. + +Device Reset Support +-------------------- + +The MANA PMD supports automatic recovery from hardware service reset events. +When the MANA kernel driver receives a hardware service event, +it initiates a device reset and notifies userspace +via ``IBV_EVENT_DEVICE_FATAL``. + +The driver handles this transparently through a two-phase reset flow: + +* **Enter phase**: The driver stops the data path, + waits for all threads to reach a quiescent state using RCU, + tears down IB resources and queues, + and unmaps secondary process doorbell pages. + +* **Exit phase**: After a delay for hardware recovery, + a control thread re-probes the PCI device, + reinstalls the interrupt handler, + reinitializes resources, and restarts queues. + +The driver emits the following ethdev recovery events +to notify upper layers (e.g. netvsc) of the reset lifecycle: + +* ``RTE_ETH_EVENT_ERR_RECOVERING`` — reset has started +* ``RTE_ETH_EVENT_RECOVERY_SUCCESS`` — device has recovered successfully +* ``RTE_ETH_EVENT_RECOVERY_FAILED`` — recovery failed + +To distinguish a PCI hot-remove from a service reset, +the driver registers for PCI device removal events. +This requires the application to call ``rte_dev_event_monitor_start()`` +for removal events to be delivered +(e.g. testpmd ``--hot-plug-handling`` option).
doc/guides/rel_notes/release_26_07.rst +8 -0
@@ -77,6 +77,14 @@ New Features Added network driver for the Linkdata Network Adapters. +* **Added device reset support to the MANA PMD.** + + Added automatic recovery from hardware service reset events + in the MANA poll mode driver. The driver uses ethdev recovery events + (``RTE_ETH_EVENT_ERR_RECOVERING``, ``RTE_ETH_EVENT_RECOVERY_SUCCESS``, + ``RTE_ETH_EVENT_RECOVERY_FAILED``) to notify upper layers of the + reset lifecycle. + Removed Items -------------
Signature · 1 line
2.34.1
Quoted 44 lines · On Thu, 21 May 2026 23:59:41 -0700 Wei Hu <[email protected]> wrote:
From: Wei Hu <[email protected]>
Add support for handling hardware service reset events in the MANA driver. When the MANA kernel driver receives a hardware service event, it initiates a device reset and notifies userspace via
IBV_EVENT_DEVICE_FATAL. The MANA PMD handles this by performing an automatic teardown and recovery sequence.The driver uses ethdev recovery events (
ERR_RECOVERING,RECOVERY_SUCCESS,RECOVERY_FAILED) to notify upper layers of the reset lifecycle, and a PCI device removal event callback to distinguish hot-remove from service reset.Changes since v2:
- Fixeddev_state_qsvmemory leak on device removal
- Fixed reset thread TCB/stack leak:reset_thread_activeis now only cleared by the joiner, not the thread itself
- Fixed second reset crash: removed reset thread join logic frommana_dev_close(inner function) to avoid corruptingdev_statewhen called frommana_reset_enter
- Madereset_thread_activeRTE_ATOMIC(bool)with explicit ordering
- Added retry loop forrte_dev_event_callback_unregisteron-EAGAIN
- Initialized condvar/mutex withPTHREAD_PROCESS_SHAREDsince priv is in hugepage shared memory
- Added re-check ofdev_stateafter lock acquisition inmana_intr_handlerto prevent racing withpci_remove_event_cb
- Replaced (void *)0 with NULL inmp.c
- Added lock ownership comment block atmana_reset_enter
- Documentedrte_dev_event_monitor_start()requirement
- Addedmana.rstdocumentation and release note (patch 2/2)Changes since v1:
- Removed net/netvsc patch from this series
- Simplified reset exit:mana_reset_exitcallsmana_reset_exit_delaydirectly instead of spawning a thread
- Added__rte_no_thread_safety_analysisannotations for clang
- Switched torte_thread_create_internal_control
- Fixed declaration-after-statement style issues
- Removed unnecessary blank lines and stale commentsWei Hu (2):
net/mana: add device reset support net/mana: add documentation for device reset support
Quoted 11 lines
doc/guides/nics/mana.rst | 33 + doc/guides/rel_notes/release_26_07.rst | 8 + drivers/net/mana/mana.c | 995 ++++++++++++++++++++++--- drivers/net/mana/mana.h | 33 +- drivers/net/mana/meson.build | 2 +- drivers/net/mana/mp.c | 89 ++- drivers/net/mana/mr.c | 6 +- drivers/net/mana/rx.c | 24 +- drivers/net/mana/tx.c | 40 +- 9 files changed, 1123 insertions(+), 107 deletions(-)
AI review found a few things...
Thanks for the v3. Most of the v2 items are addressed: dev_state_qsv is freed via mana_dev_free_resources, reset_thread_active is now RTE_ATOMIC(bool), the joiner owns the flag, pthread mutex/cond use PROCESS_SHARED attrs, intr_handler re-checks state under the lock, the lock hand-off has a clear comment, and a doc + release note patch is included.
A few items remain or were introduced by the v2->v3 changes.
Errors
1. Deadlock in dev_close_lock + EAGAIN loop on PCI-remove callback.
The v3 fix for the discarded unregister return uses a busy-wait:
if (dev->device) {
do {
ret = rte_dev_event_callback_unregister(...);
} while (ret == -EAGAIN);
} mana_intr_uninstall runs from mana_dev_close, which runs from mana_dev_close_lock with reset_ops_lock held. EAL returns -EAGAIN while cb_lst->active is 1 (callback dispatching). The callback in question is mana_pci_remove_event_cb, which itself blocks on rte_spinlock_lock(&priv->reset_ops_lock). Result:
dev_close holds the spinlock, the callback waits for the spinlock, the loop waits for the callback to finish -- three-way deadlock if a hot-remove event arrives during close.
Either drop the lock before unregistering and re-take it after, or (better) use a sleeping mutex for reset_ops_lock so the callback can complete while close is suspended.
2. reset_ops_lock is still rte_spinlock_t held across blocking work.
Unchanged from v2: the spinlock is held through
- mana_reset_enter: rte_rcu_qsbr_check spin,
mana_dev_stop, mana_mp_req_on_rxtx (5s IPC timeout), mana_dev_close
- mana_reset_exit_delay: ibv_close_device, mana_pci_probe, mana_mp_req_on_rxtx, mana_dev_start
Any other ethdev op that hits dev_ops while reset is in flight spins for tens of seconds. Blocking calls under a spinlock are an Error; use a properly-initialised pthread_mutex_t (you already have PROCESS_SHARED attrs handy).
Warnings
3. Secondary process: qsbr does not actually quiesce secondary lcores.
rte_rcu_qsbr_thread_register is only called from mana_dev_configure, which the secondary never runs. The secondary's rx_burst/tx_burst still call thread_online/offline against the shared qsv, but the secondary tids are unregistered, so they are invisible to rte_rcu_qsbr_check in the primary, and the secondary MP handler (mana_mp_reset_enter) does not call qsbr_check at all -- it just sets db_page = NULL and munmaps.
The dev_state check at the top of secondary tx_burst is racy:
the page can be munmapped after the in-loop read of db_page but before the doorbell write at the bottom. The "All secondary threads are quiescent" log line in mana_mp_reset_enter is not true.
The secondary needs a real reader-side primitive -- its own qsbr with secondary lcore registration, or an rwlock the MP handler takes before munmap.
4. Double-join race between dev_close_lock and mana_reset_enter.
dev_close_lock signals + joins the reset thread *before* taking reset_ops_lock. intr_handler may fire in that window:
1. dev_close_lock: pthread_cond_signal, rte_thread_join
2. intr_handler: take lock, see ACTIVE, call reset_enter
3. reset_enter: reset_thread_active still true (step 1
hasn't cleared it yet) -> rte_thread_join on a thread that step 1 just joined -> UB
The pre-lock signal/join was added to avoid deadlock with the reset thread holding the lock, which is fine in itself, but the reset_thread_active update needs to be inside that critical section, or reset_enter has to recheck under the lock.
5. mana_dev_close (non-_lock) does not join the reset thread.
mana_dev_uninit -> mana_dev_close path doesn't go through dev_close_lock, so if pci_remove arrives while the reset thread is alive, mana_dev_free_resources can destroy reset_cond_mutex / reset_cond before the thread has exited. In practice the pci_remove callback signals RESET_FAILED and the reset thread should exit quickly, but the ordering isn't enforced. Either join in mana_dev_uninit, or document the assumption.
Info
6. mana_reset_exit_delay sets priv->ib_ctx = NULL even when ibv_close_device fails:
ret = ibv_close_device(priv->ib_ctx);
priv->ib_ctx = NULL;
if (ret) { ... goto out; }The handle is leaked if close fails. Either keep the old pointer on failure or accept that close-failure is unrecoverable and free it anyway with a comment.
Patch 2/2
7. The events list is a term/description pattern and should be a definition list per DPDK RST style:
``RTE_ETH_EVENT_ERR_RECOVERING``
Reset has started.
``RTE_ETH_EVENT_RECOVERY_SUCCESS``
Device has recovered successfully.
``RTE_ETH_EVENT_RECOVERY_FAILED``
Recovery failed.
8. Convention is to fold doc + release note updates into the same commit as the feature. Patches 1/2 and 2/2 can be squashed.
Quoted 27 lines
-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Wednesday, May 27, 2026 3:38 AM
To: Wei Hu <[email protected]>
Cc: [email protected]; Long Li <[email protected]>; Wei Hu <[email protected]>
Subject: [EXTERNAL] Re: [PATCH v3 0/2] net/mana: add device reset supportWarnings
3. Secondary process: qsbr does not actually quiesce secondary lcores.
rte_rcu_qsbr_thread_registeris only called frommana_dev_configure, which the secondary never runs. The secondary's rx_burst/tx_burst still call thread_online/offline against the shared qsv, but the secondary tids are unregistered, so they are invisible torte_rcu_qsbr_checkin the primary, and the secondary MP handler (mana_mp_reset_enter) does not callqsbr_checkat all -- it just setsdb_page= NULL and munmaps.The
dev_statecheck at the top of secondarytx_burstis racy:
the page can be munmapped after the in-loop read ofdb_pagebut before the doorbell write at the bottom. The "All secondary threads are quiescent" log line inmana_mp_reset_enteris not true.
Quoted 4 lines
The secondary needs a real reader-side primitive -- its own qsbr with secondary lcore registration, or an rwlock the MP handler takes before munmap.
Thanks for the v3 review, @Stephen. I will send out v4 to incorporate most of the review comments except for this one.
The review on this point is not correct. Here I am providing analysis from
AI and my own test results to show why.
The concern is that "rte_rcu_qsbr_thread_register is only called from mana_dev_configure, which the secondary never runs", so secondary tids are unregistered and invisible to rte_rcu_qsbr_check.
This is incorrect. The RCU thread IDs are per-queue, not per-process.
The tid used in rte_rcu_qsbr_thread_online/offline is:
- RX path: tid = rxq->rxq_idx (0 to num_queues-1)
- TX path: tid = num_queues + txq_idx (num_queues to 2*num_queues-1)
These tids are registered once by primary in mana_dev_configure (mana.c:117), which calls rte_rcu_qsbr_thread_register for IDs 0..2*num_queues-1. The QSV (priv->dev_state_qsv) is allocated with rte_zmalloc_socket in shared hugepage memory (mana.c:2207). The queue structures (rxq, txq) also live in shared memory via dev->data->rx_queues[] / tx_queues[].
When a secondary lcore polls a queue, it calls rte_rcu_qsbr_thread_online(qsv, tid) with the SAME shared QSV and SAME queue-based tid. DPDK's fundamental model requires that a given queue is polled by exactly one lcore at a time (queues are not thread-safe), so the tid maps 1:1 to a lcore regardless of which process that lcore belongs to. The tid IS registered — registration is a property of the QSV structure, not of the process.
The reset synchronization flow (mana_reset_enter, mana.c:1458-1488):
1. Primary: dev_state = MANA_DEV_RESET_ENTER (release store)
2. Primary: rte_rcu_qsbr_start + rte_rcu_qsbr_check — blocks until ALL registered tids have passed through thread_offline
3. Secondary lcores in rx_burst/tx_burst eventually call rte_rcu_qsbr_thread_offline, and on re-entry see dev_state != ACTIVE → return 0 without touching db_page
4. Only after QSBR check passes (ALL tids quiescent, including secondary's) does primary proceed to mana_dev_stop (line 1472)
5. Primary sends MANA_MP_REQ_RESET_ENTER IPC (line 1481)
6. Secondary MP handler: sets db_page = NULL, munmaps
The alleged race — "db_page can be munmapped after the in-loop read but before the doorbell write" — cannot happen because:
- db_page is munmapped at step 6, which is AFTER step 2 (QSBR).
- After QSBR passes, any secondary re-entry into tx_burst/rx_burst reads dev_state with acquire ordering (tx.c:214, rx.c:468), sees RESET_ENTER, and returns immediately without using the local db_page copy.
- The memory ordering is sound: primary's release store of dev_state (step 1) happens-before QSBR start (step 2). Secondary's thread_offline (release) synchronizes-with primary's QSBR check (acquire). On secondary re-entry, thread_online + acquire load of dev_state sees the update.
The secondary MP handler does NOT need its own QSBR check — the primary's check at step 2 already covers all data path threads including secondary's.
Note: The log "All secondary threads are quiescent" in mana_mp_reset_enter is misleading and has been removed. The actual quiescence is enforced by the primary's QSBR check before IPC is sent.
Followings are from my own test. I added the debug log in mana_tx_burst() call.
drivers/net/mana/mana.h +3 -0
@@ -454,6 +454,9 @@ struct mana_txq {struct mana_stats stats;unsigned int socket;unsigned int txq_idx; +#if 1 +unsigned int call_cnt; +#endif };struct mana_rxq {
drivers/net/mana/tx.c +8 -0
@@ -210,6 +210,14 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) }rte_rcu_qsbr_thread_online (dstate_qsv, tid); +#if 1 +if (txq->call_cnt++ %1000000 ==0 ) { +if (rte_eal_process_type () ==RTE_PROC_SECONDARY ) +DP_LOG (ERR ,"secondary tid %u online" , tid); +else +DP_LOG (ERR ,"primary tid %u online" , tid); + } +#endif if (unlikely (rte_atomic_load_explicit (&priv->dev_state, rte_memory_order_acquire) !=MANA_DEV_ACTIVE || !db_page)) {
Primary command:
dpdk-testpmd --log-level='.*,8' -l 2-4 ... --proc-type=primary -- --nb-cores 2 --forward-mode=txonly ... --txq=4 --rxq=4 ... --num-procs=2 --proc-id 0
Secondary command:
dpdk-testpmd --log-level='.*,8' -l 6-8 ... --proc-type=secondary -- --nb-cores 2 --forward-mode=txonly ... --txq=4 --rxq=4 ... --num-procs=2 --proc-id 1
Here in both commands, I specify the number of txq and rxq to be 4.
After primary start, the log shows 8 threads total were registered in mana_dev_configure and their tids:
Configuring Port 0 (socket 0)MANA_DRIVER: mana_dev_configure(): priv 0x1003cdc80, port 0, dev port 1, num_queues: 4MANA_DRIVER: mana_dev_configure(): Register thread 0x0 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x1 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x2 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x3 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x4 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x5 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x6 for priv 0x1003cdc80, port 0MANA_DRIVER: mana_dev_configure(): Register thread 0x7 for priv 0x1003cdc80, port 0
ETHDEV: Port 0 Rx offload RSS_HASH is not requested but enabled
Note tid 0x0 to 0x3 are rx threads, 0x4 to 0x7 are tx threads. Then the primary only printed out the tid 4 and 5 sere online:
MANA_DRIVER: primary tid 4 onlineMANA_DRIVER: primary tid 5 online
Then I started the secondary and saw tid 6 and 7 were online in its mana_tx_burst call:
MANA_DRIVER: secondary tid 7 onlineMANA_DRIVER: secondary tid 6 onlineMANA_DRIVER: secondary tid 7 online
This simply means primary has registered all threads which would be up in both primary and secondary in mana_ddev_configure. Primary only starts half of the threads since --num-procs=2 passed in already tells primary that there will be a secondary to start.
When the secondary starts, it takes the rest two tx and rx threads. All these threads have already been registered in mana_dev_configure() by calling rte_rcu_qsbr_thread_register() in the primary.
So, the review comments on this point is incorrect. All threads are covered and there is no race at all.
Thanks,
Wei
Quoted 18 lines · On Thu, 28 May 2026 07:30:03 +0000 Wei Hu <[email protected]> wrote:
Quoted 10 lines
so they are invisible to
rte_rcu_qsbr_checkin the primary, and the secondary MP handler (mana_mp_reset_enter) does not callqsbr_checkat all -- it just setsdb_page= NULL and munmaps.The
dev_statecheck at the top of secondarytx_burstis racy:
the page can be munmapped after the in-loop read ofdb_pagebut before the doorbell write at the bottom. The "All secondary threads are quiescent" log line inmana_mp_reset_enteris not true.Quoted 4 lines
The secondary needs a real reader-side primitive -- its own qsbr with secondary lcore registration, or an rwlock the MP handler takes before munmap.
Thanks for the v3 review, @Stephen. I will send out v4 to incorporate most of the review comments except for this one.
Quoted 6 lines
The review on this point is not correct. Here I am providing analysis from
AI and my own test results to show why.The concern is that "
rte_rcu_qsbr_thread_registeris only called frommana_dev_configure, which the secondary never runs", so secondary tids are unregistered and invisible torte_rcu_qsbr_check.
Thanks, I have become way to familiar with AI reaching false conclusions.