From: Long Li <[email protected]>

When a VF device is hot-removed by the primary process, secondary
processes must be notified to release their references to the VF port.
Without this, secondary processes retain stale port references leading
to crashes or undefined behavior when accessing the removed device.

This patch adds multi-process communication infrastructure to coordinate
VF removal across all processes:

- Shared memory (netvsc_shared_data) to track secondary process count
- Multi-process message handlers (NETVSC_MP_REQ_VF_REMOVE) to notify
  secondaries when primary removes a VF device
- Secondary handler calls rte_eth_dev_release_port() to cleanly release
  the VF port in its own process space
- Primary waits for all secondaries to acknowledge removal before
  proceeding

The implementation uses rte_mp_request_sync() to ensure all secondary
processes respond within NETVSC_MP_REQ_TIMEOUT_SEC (5 seconds) before
the primary completes the VF removal sequence.

Fixes: 7fc4c0997b04 ("net/netvsc: fix hot adding multiple VF PCI devices")
Cc: [email protected]

Signed-off-by: Long Li <[email protected]>
v3:
- Use #define for MZ_NETVSC_SHARED_DATA instead of const char pointer
- Simplify netvsc_secondary_handle_device_remove() to take vf_port
  directly instead of struct hn_data pointer
- Return 0 (not error) when VF port is not present in secondary,
  as this is a normal condition during startup
- Pass vf_port as parameter to netvsc_mp_req_vf() instead of reading
  from hv->vf_ctx internally
- Protect netvsc_init_once() and secondary_cnt increment under same
  spinlock to prevent race between MP handler registration and
  secondary count visibility
- Add secondary_cnt decrement in error and cleanup paths
- Fix misleading comment about cross-process locking

v2:
- Add rte_eth_dev_is_valid_port() check before accessing
  rte_eth_devices[] in secondary VF removal handler
- Rename netvsc_mp_req_VF to netvsc_mp_req_vf per DPDK lowercase
  naming convention
- Use rte_memory_order_acquire/release instead of relaxed for
  secondary_cnt to ensure visibility on ARM
- Initialize ret = 0 in netvsc_init_once()
---
 drivers/net/netvsc/hn_ethdev.c | 275 ++++++++++++++++++++++++++++++++-
 drivers/net/netvsc/hn_nvs.h    |   6 +
 drivers/net/netvsc/hn_vf.c     |   4 +
 3 files changed, 284 insertions(+), 1 deletion(-)

diff --git a/drivers/net/netvsc/hn_ethdev.c b/drivers/net/netvsc/hn_ethdev.c
index 6068c225fd..c1e1fbf0ee 100644
--- a/drivers/net/netvsc/hn_ethdev.c
+++ b/drivers/net/netvsc/hn_ethdev.c
@@ -48,6 +48,31 @@
            (var) = (tvar))
 #endif
 
+/* Spinlock for netvsc_shared_data */
+static rte_spinlock_t netvsc_shared_data_lock = RTE_SPINLOCK_INITIALIZER;
+
+static struct netvsc_shared_data {
+       RTE_ATOMIC(uint32_t) secondary_cnt;
+} *netvsc_shared_data;
+
+static const struct rte_memzone *netvsc_shared_mz;
+#define MZ_NETVSC_SHARED_DATA "netvsc_shared_data"
+
+static struct netvsc_local_data {
+       bool init_done;
+       unsigned int primary_cnt;
+       unsigned int secondary_cnt;
+} netvsc_local_data;
+
+#define NETVSC_MP_NAME "net_netvsc_mp"
+#define NETVSC_MP_REQ_TIMEOUT_SEC 5
+
+struct netvsc_mp_param {
+       enum netvsc_mp_req_type type;
+       int vf_port;
+       int result;
+};
+
 #define HN_TX_OFFLOAD_CAPS (RTE_ETH_TX_OFFLOAD_IPV4_CKSUM | \
                            RTE_ETH_TX_OFFLOAD_TCP_CKSUM  | \
                            RTE_ETH_TX_OFFLOAD_UDP_CKSUM  | \
@@ -1503,6 +1528,213 @@ static void remove_cache_list(void)
        rte_spinlock_unlock(&netvsc_lock);
 }
 
+static int
+netvsc_mp_primary_handle(const struct rte_mp_msg *mp_msg __rte_unused,
+                         const void *peer __rte_unused)
+{
+       /* Stub function required for multi-process message handling 
registration */
+       return 0;
+}
+
+static void
+mp_init_msg(struct rte_mp_msg *msg, enum netvsc_mp_req_type type, int vf_port)
+{
+       struct netvsc_mp_param *param;
+
+       strlcpy(msg->name, NETVSC_MP_NAME, sizeof(msg->name));
+       msg->len_param = sizeof(*param);
+
+       param = (struct netvsc_mp_param *)msg->param;
+       param->type = type;
+       param->vf_port = vf_port;
+}
+
+static int netvsc_secondary_handle_device_remove(int vf_port)
+{
+       if (!rte_eth_dev_is_valid_port(vf_port)) {
+               /* VF not probed in this secondary — nothing to release */
+               PMD_DRV_LOG(DEBUG, "VF port %u not present in secondary, 
skipping",
+                           vf_port);
+               return 0;
+       }
+
+       PMD_DRV_LOG(DEBUG, "Secondary releasing VF port %d", vf_port);
+       return rte_eth_dev_release_port(&rte_eth_devices[vf_port]);
+}
+
+static int
+netvsc_mp_secondary_handle(const struct rte_mp_msg *mp_msg, const void *peer)
+{
+       struct rte_mp_msg mp_res = { 0 };
+       struct netvsc_mp_param *res = (struct netvsc_mp_param *)mp_res.param;
+       const struct netvsc_mp_param *param =
+               (const struct netvsc_mp_param *)mp_msg->param;
+       int ret = 0;
+
+       mp_init_msg(&mp_res, param->type, param->vf_port);
+
+       switch (param->type) {
+       case NETVSC_MP_REQ_VF_REMOVE:
+               res->result = 
netvsc_secondary_handle_device_remove(param->vf_port);
+               ret = rte_mp_reply(&mp_res, peer);
+               break;
+
+       default:
+               PMD_DRV_LOG(ERR, "Unknown primary MP type %u", param->type);
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static int netvsc_mp_init_primary(void)
+{
+       int ret;
+       ret = rte_mp_action_register(NETVSC_MP_NAME, netvsc_mp_primary_handle);
+       if (ret && rte_errno != ENOTSUP) {
+               PMD_DRV_LOG(ERR, "Failed to register primary handler %d %d",
+                       ret, rte_errno);
+               return -1;
+       }
+
+       return 0;
+}
+
+static void netvsc_mp_uninit_primary(void)
+{
+       rte_mp_action_unregister(NETVSC_MP_NAME);
+}
+
+static int netvsc_mp_init_secondary(void)
+{
+       return rte_mp_action_register(NETVSC_MP_NAME, 
netvsc_mp_secondary_handle);
+}
+
+static void netvsc_mp_uninit_secondary(void)
+{
+       rte_mp_action_unregister(NETVSC_MP_NAME);
+}
+
+int netvsc_mp_req_vf(struct hn_data *hv, enum netvsc_mp_req_type type,
+                    int vf_port)
+{
+       struct rte_mp_msg mp_req = { 0 };
+       struct rte_mp_msg *mp_res;
+       struct rte_mp_reply mp_rep = { 0 };
+       struct netvsc_mp_param *res;
+       struct timespec ts = {.tv_sec = NETVSC_MP_REQ_TIMEOUT_SEC, .tv_nsec = 
0};
+       int i, ret;
+
+       /* if secondary count is 0, return */
+       if (rte_atomic_load_explicit(&netvsc_shared_data->secondary_cnt,
+                       rte_memory_order_acquire) == 0)
+               return 0;
+
+       mp_init_msg(&mp_req, type, vf_port);
+
+       ret = rte_mp_request_sync(&mp_req, &mp_rep, &ts);
+       if (ret) {
+               if (rte_errno != ENOTSUP)
+                       PMD_DRV_LOG(ERR, "port %u failed to request VF remove",
+                                   hv->port_id);
+               else
+                       ret = 0;
+               goto exit;
+       }
+
+       if (mp_rep.nb_sent != mp_rep.nb_received) {
+               PMD_DRV_LOG(ERR, "port %u not all secondaries responded type 
%d",
+                           hv->port_id, type);
+               ret = -1;
+               goto exit;
+       }
+       for (i = 0; i < mp_rep.nb_received; i++) {
+               mp_res = &mp_rep.msgs[i];
+               res = (struct netvsc_mp_param *)mp_res->param;
+               if (res->result) {
+                       PMD_DRV_LOG(ERR, "port %u request failed on secondary 
%d",
+                                   hv->port_id, i);
+                       ret = -1;
+                       goto exit;
+               }
+       }
+
+exit:
+       free(mp_rep.msgs);
+       return ret;
+}
+
+static int netvsc_init_once(void)
+{
+       int ret = 0;
+       const struct rte_memzone *secondary_mz;
+
+       if (netvsc_local_data.init_done)
+               return 0;
+
+       switch (rte_eal_process_type()) {
+       case RTE_PROC_PRIMARY:
+               netvsc_shared_mz = rte_memzone_reserve(MZ_NETVSC_SHARED_DATA,
+                               sizeof(*netvsc_shared_data), SOCKET_ID_ANY, 0);
+               if (!netvsc_shared_mz) {
+                       PMD_DRV_LOG(ERR, "Cannot allocate netvsc shared data");
+                       return -rte_errno;
+               }
+               netvsc_shared_data = netvsc_shared_mz->addr;
+               rte_atomic_store_explicit(&netvsc_shared_data->secondary_cnt,
+                               0, rte_memory_order_release);
+
+               ret = netvsc_mp_init_primary();
+               if (ret) {
+                       rte_memzone_free(netvsc_shared_mz);
+                       break;
+               }
+
+               PMD_DRV_LOG(DEBUG, "MP INIT PRIMARY");
+               netvsc_local_data.init_done = true;
+               break;
+
+       case RTE_PROC_SECONDARY:
+               secondary_mz = rte_memzone_lookup(MZ_NETVSC_SHARED_DATA);
+               if (!secondary_mz) {
+                       PMD_DRV_LOG(ERR, "Cannot attach netvsc shared data");
+                       return -rte_errno;
+               }
+               netvsc_shared_data = secondary_mz->addr;
+               ret = netvsc_mp_init_secondary();
+               if (ret)
+                       break;
+
+               PMD_DRV_LOG(DEBUG, "MP INIT SECONDARY");
+               netvsc_local_data.init_done = true;
+               break;
+
+       default:
+               /* Impossible */
+               ret = -EPROTO;
+               break;
+       }
+
+       return ret;
+}
+
+static void netvsc_uninit_once(void)
+{
+       if (netvsc_local_data.primary_cnt ||
+           netvsc_local_data.secondary_cnt)
+               return;
+
+       if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
+               netvsc_mp_uninit_primary();
+               rte_memzone_free(netvsc_shared_mz);
+               netvsc_shared_mz = NULL;
+               netvsc_shared_data = NULL;
+       } else {
+               netvsc_mp_uninit_secondary();
+       }
+       netvsc_local_data.init_done = false;
+}
+
 static int eth_hn_probe(struct rte_vmbus_driver *drv __rte_unused,
                        struct rte_vmbus_device *dev)
 {
@@ -1516,10 +1748,26 @@ static int eth_hn_probe(struct rte_vmbus_driver *drv 
__rte_unused,
        if (ret)
                return ret;
 
+       rte_spinlock_lock(&netvsc_shared_data_lock);
+       ret = netvsc_init_once();
+       if (!ret) {
+               if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
+                       netvsc_local_data.primary_cnt++;
+               } else {
+                       rte_atomic_fetch_add_explicit(
+                               &netvsc_shared_data->secondary_cnt,
+                               1, rte_memory_order_release);
+                       netvsc_local_data.secondary_cnt++;
+               }
+       }
+       rte_spinlock_unlock(&netvsc_shared_data_lock);
+       if (ret)
+               goto fail;
+
        ret = rte_dev_event_monitor_start();
        if (ret) {
                PMD_DRV_LOG(ERR, "Failed to start device event monitoring");
-               goto fail;
+               goto init_once_failed;
        }
 
        eth_dev = eth_dev_vmbus_allocate(dev, sizeof(struct hn_data));
@@ -1545,6 +1793,7 @@ static int eth_hn_probe(struct rte_vmbus_driver *drv 
__rte_unused,
                goto dev_init_failed;
 
        rte_eth_dev_probing_finish(eth_dev);
+
        return ret;
 
 dev_init_failed:
@@ -1556,6 +1805,19 @@ static int eth_hn_probe(struct rte_vmbus_driver *drv 
__rte_unused,
 vmbus_alloc_failed:
        rte_dev_event_monitor_stop();
 
+init_once_failed:
+       rte_spinlock_lock(&netvsc_shared_data_lock);
+       if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
+               netvsc_local_data.primary_cnt--;
+       } else {
+               rte_atomic_fetch_sub_explicit(
+                       &netvsc_shared_data->secondary_cnt,
+                       1, rte_memory_order_release);
+               netvsc_local_data.secondary_cnt--;
+       }
+       netvsc_uninit_once();
+       rte_spinlock_unlock(&netvsc_shared_data_lock);
+
 fail:
        remove_cache_list();
        return ret;
@@ -1569,6 +1831,17 @@ static int eth_hn_remove(struct rte_vmbus_device *dev)
 
        PMD_INIT_FUNC_TRACE();
 
+       rte_spinlock_lock(&netvsc_shared_data_lock);
+       if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
+               netvsc_local_data.primary_cnt--;
+       } else {
+               
rte_atomic_fetch_sub_explicit(&netvsc_shared_data->secondary_cnt,
+                               1, rte_memory_order_release);
+               netvsc_local_data.secondary_cnt--;
+       }
+       netvsc_uninit_once();
+       rte_spinlock_unlock(&netvsc_shared_data_lock);
+
        eth_dev = rte_eth_dev_allocated(dev->device.name);
        if (!eth_dev)
                return 0; /* port already released */
diff --git a/drivers/net/netvsc/hn_nvs.h b/drivers/net/netvsc/hn_nvs.h
index bf10621927..67dbfd7be7 100644
--- a/drivers/net/netvsc/hn_nvs.h
+++ b/drivers/net/netvsc/hn_nvs.h
@@ -243,3 +243,9 @@ hn_nvs_send_sglist(struct hn_data *hv, struct vmbus_channel 
*chan,
        return rte_vmbus_chan_send_sglist(hn_nvs_get_vmbus_device(hv), chan, 
sg, sglen, nvs_msg,
                                          nvs_msglen, (uint64_t)sndc, need_sig);
 }
+
+enum netvsc_mp_req_type {
+       NETVSC_MP_REQ_VF_REMOVE = 1,
+};
+int netvsc_mp_req_vf(struct hn_data *hv, enum netvsc_mp_req_type type,
+                    int vf_port);
diff --git a/drivers/net/netvsc/hn_vf.c b/drivers/net/netvsc/hn_vf.c
index 64c89130f5..f837a52b00 100644
--- a/drivers/net/netvsc/hn_vf.c
+++ b/drivers/net/netvsc/hn_vf.c
@@ -155,6 +155,10 @@ static void hn_remove_delayed(void *args)
                PMD_DRV_LOG(ERR, "rte_eth_dev_close failed port_id=%u ret=%d",
                            port_id, ret);
 
+       ret = netvsc_mp_req_vf(hv, NETVSC_MP_REQ_VF_REMOVE, port_id);
+       if (ret)
+               PMD_DRV_LOG(ERR, "failed to request secondary VF remove");
+
        /* Remove the rte device when all its eth devices are removed */
        all_eth_removed = true;
        RTE_ETH_FOREACH_DEV_OF(port_id, dev) {
-- 
2.43.0

Reply via email to