patch 'net/netvsc: support multi-process VF device removal' has been queued to stable release 25.11.1
Kevin Traynor
ktraynor at redhat.com
Thu Mar 19 11:02:51 CET 2026
Hi,
FYI, your patch has been queued to stable release 25.11.1
Note it hasn't been pushed to http://dpdk.org/browse/dpdk-stable yet.
It will be pushed if I get no objections before 03/23/26. So please
shout if anyone has objections.
Also note that after the patch there's a diff of the upstream commit vs the
patch applied to the branch. This will indicate if there was any rebasing
needed to apply to the stable branch. If there were code changes for rebasing
(ie: not only metadata diffs), please double check that the rebase was
correctly done.
Queued patches are on a temporary branch at:
https://github.com/kevintraynor/dpdk-stable
This queued commit can be viewed at:
https://github.com/kevintraynor/dpdk-stable/commit/a04b72bafb97d13a21f3ed651df0da0c91855b0d
Thanks.
Kevin
---
>From a04b72bafb97d13a21f3ed651df0da0c91855b0d Mon Sep 17 00:00:00 2001
From: Long Li <longli at microsoft.com>
Date: Thu, 26 Feb 2026 17:59:22 -0800
Subject: [PATCH] net/netvsc: support multi-process VF device removal
[ upstream commit f741298f702772570ff20df000050483488092d0 ]
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")
Signed-off-by: Long Li <longli at microsoft.com>
---
drivers/net/netvsc/hn_ethdev.c | 288 ++++++++++++++++++++++++++++++++-
drivers/net/netvsc/hn_nvs.h | 6 +
drivers/net/netvsc/hn_vf.c | 4 +
3 files changed, 293 insertions(+), 5 deletions(-)
diff --git a/drivers/net/netvsc/hn_ethdev.c b/drivers/net/netvsc/hn_ethdev.c
index b51c11554c..0142ff99bc 100644
--- a/drivers/net/netvsc/hn_ethdev.c
+++ b/drivers/net/netvsc/hn_ethdev.c
@@ -49,4 +49,29 @@
#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 | \
@@ -1506,4 +1531,215 @@ out:
}
+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);
+ netvsc_shared_mz = NULL;
+ netvsc_shared_data = NULL;
+ 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) {
+ netvsc_shared_data = NULL;
+ 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)
@@ -1519,8 +1755,23 @@ static int eth_hn_probe(struct rte_vmbus_driver *drv __rte_unused,
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;
}
@@ -1548,4 +1799,5 @@ static int eth_hn_probe(struct rte_vmbus_driver *drv __rte_unused,
rte_eth_dev_probing_finish(eth_dev);
+
return ret;
@@ -1559,4 +1811,16 @@ 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();
@@ -1573,10 +1837,12 @@ static int eth_hn_remove(struct rte_vmbus_device *dev)
eth_dev = rte_eth_dev_allocated(dev->device.name);
- if (!eth_dev)
- return 0; /* port already released */
+ if (!eth_dev) {
+ ret = 0; /* port already released */
+ goto uninit;
+ }
ret = eth_hn_dev_uninit(eth_dev);
if (ret)
- return ret;
+ goto uninit;
process_priv = eth_dev->process_private;
@@ -1588,5 +1854,17 @@ static int eth_hn_remove(struct rte_vmbus_device *dev)
remove_cache_list();
- return 0;
+uninit:
+ 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);
+
+ return ret;
}
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
@@ -244,2 +244,8 @@ hn_nvs_send_sglist(struct hn_data *hv, struct vmbus_channel *chan,
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 dfd328d550..d9efa7e96f 100644
--- a/drivers/net/netvsc/hn_vf.c
+++ b/drivers/net/netvsc/hn_vf.c
@@ -156,4 +156,8 @@ static void hn_remove_delayed(void *args)
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;
--
2.53.0
---
Diff of the applied patch vs upstream commit (please double-check if non-empty:
---
--- - 2026-03-19 10:01:08.661310643 +0000
+++ 0053-net-netvsc-support-multi-process-VF-device-removal.patch 2026-03-19 10:01:07.120391519 +0000
@@ -1 +1 @@
-From f741298f702772570ff20df000050483488092d0 Mon Sep 17 00:00:00 2001
+From a04b72bafb97d13a21f3ed651df0da0c91855b0d Mon Sep 17 00:00:00 2001
@@ -5,0 +6,2 @@
+[ upstream commit f741298f702772570ff20df000050483488092d0 ]
+
@@ -27 +28,0 @@
-Cc: stable at dpdk.org
More information about the stable
mailing list