[PATCH v3 3/4] net/iavf: fix watchdog clearing reset state during recovery

Ciara Loftus ciara.loftus at intel.com
Thu Nov 5 13:32:27 CET 2026


Since the watchdog is kept armed across a PF reset, it can clear the
`vf_reset` flag while the reset event is still queued or being handled,
as VFGEN_RSTAT toggles while the PF resets the VF. iavf_dev_close() then
skips re-enabling PCI bus mastering, which the VF reset cleared, so
VIRTCHNL_OP_VERSION never reaches the PF, iavf_dev_init() fails and the
port is left returning -EIO. Each toggle also posts a further reset
event, causing redundant recovery cycles.

Count reset events from before `vf_reset` is set until they have been
handled, and do not let the watchdog clear the flag while any are
outstanding. Also make the `vf_reset` flag atomic, as it is accessed from
multiple places: the watchdog alarm, the interrupt thread and the virtchnl
polling path.

Fixes: a0e296420a70 ("net/iavf: keep watchdog armed for the whole reset window")
Cc: stable at dpdk.org

Suggested-by: Declan Doherty <declan.doherty at intel.com>
Signed-off-by: Ciara Loftus <ciara.loftus at intel.com>
---
v3:
* make vf_reset flag atomic
* add helper functions
---
 drivers/net/intel/iavf/iavf.h        |   6 +-
 drivers/net/intel/iavf/iavf_ethdev.c |  32 ++++---
 drivers/net/intel/iavf/iavf_vchnl.c  | 120 ++++++++++++++++++---------
 3 files changed, 97 insertions(+), 61 deletions(-)

diff --git a/drivers/net/intel/iavf/iavf.h b/drivers/net/intel/iavf/iavf.h
index 5550e57c35..04c2900afe 100644
--- a/drivers/net/intel/iavf/iavf.h
+++ b/drivers/net/intel/iavf/iavf.h
@@ -261,7 +261,7 @@ struct iavf_info {
 	uint16_t mc_addrs_num;   /* Multicast mac addresses number */
 
 	struct iavf_vsi vsi;
-	bool vf_reset;	/* true for VF reset pending, false for no VF reset */
+	RTE_ATOMIC(bool)vf_reset; /* true for VF reset pending, false for no VF reset */
 	iavf_pre_reset_cb_t pre_reset_cb; /* Pre reset callback function ptr */
 	iavf_post_reset_cb_t post_reset_cb; /* Post reset callback function ptr */
 	void *pre_reset_cb_arg; /* Pre reset callback argument */
@@ -290,6 +290,7 @@ struct iavf_info {
 	struct iavf_tm_conf tm_conf;
 
 	RTE_ATOMIC(bool)in_reset_recovery;
+	RTE_ATOMIC(uint32_t)reset_events_pending;
 	bool reset_pending;
 	bool pf_reset_in_progress;
 	bool start_pending;
@@ -455,9 +456,10 @@ struct iavf_cmd_info {
 
 int iavf_check_api_version(struct iavf_adapter *adapter);
 int iavf_get_vf_resource(struct iavf_adapter *adapter);
-void iavf_dev_event_post(struct rte_eth_dev *dev,
+int iavf_dev_event_post(struct rte_eth_dev *dev,
 		enum rte_eth_event_type event,
 		void *param, size_t param_alloc_size);
+bool iavf_dev_enter_reset(struct rte_eth_dev *dev, bool drain_tx);
 void iavf_dev_event_handler_fini(void);
 int iavf_dev_event_handler_init(void);
 void iavf_handle_virtchnl_msg(struct rte_eth_dev *dev);
diff --git a/drivers/net/intel/iavf/iavf_ethdev.c b/drivers/net/intel/iavf/iavf_ethdev.c
index 05b90a6230..83470a636d 100644
--- a/drivers/net/intel/iavf/iavf_ethdev.c
+++ b/drivers/net/intel/iavf/iavf_ethdev.c
@@ -335,32 +335,27 @@ iavf_dev_watchdog(void *cb_arg)
 		return;
 
 	/* If in reset then poll vfr_inprogress register for completion */
-	if (adapter->vf.vf_reset) {
+	if (rte_atomic_load_explicit(&adapter->vf.vf_reset, rte_memory_order_acquire)) {
 		vfr_inprogress = iavf_vfr_inprogress(hw);
 
-		if (!vfr_inprogress) {
+		if (!vfr_inprogress &&
+		    rte_atomic_load_explicit(&adapter->vf.reset_events_pending,
+				rte_memory_order_acquire) == 0) {
 			PMD_DRV_LOG(INFO, "VF \"%s\" reset has completed",
 				dev->data->name);
-			adapter->vf.vf_reset = false;
+			rte_atomic_store_explicit(&adapter->vf.vf_reset, false,
+					rte_memory_order_release);
 			iavf_set_no_poll(adapter, false);
 		}
 	/* If not in reset then poll vfr_inprogress register for VFLR event */
 	} else {
 		vfr_inprogress = iavf_vfr_inprogress(hw);
 
-		if (vfr_inprogress) {
+		/* enter reset state with VFLR event */
+		if (vfr_inprogress && iavf_dev_enter_reset(dev, false))
 			PMD_DRV_LOG(INFO,
 				"VF \"%s\" reset event detected by watchdog",
 				dev->data->name);
-
-			/* enter reset state with VFLR event */
-			adapter->vf.vf_reset = true;
-			iavf_set_no_poll(adapter, false);
-			adapter->vf.link_up = false;
-
-			iavf_dev_event_post(dev, RTE_ETH_EVENT_INTR_RESET,
-				NULL, 0);
-		}
 	}
 
 	if (adapter->devargs.watchdog_period) {
@@ -3320,8 +3315,9 @@ iavf_dev_close(struct rte_eth_dev *dev)
 	 * effect.
 	 */
 out:
-	if (vf->vf_reset && !rte_pci_set_bus_master(pci_dev, true)) {
-		vf->vf_reset = false;
+	if (rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire) &&
+	    !rte_pci_set_bus_master(pci_dev, true)) {
+		rte_atomic_store_explicit(&vf->vf_reset, false, rte_memory_order_release);
 		iavf_set_no_poll(adapter, false);
 	}
 
@@ -3657,7 +3653,7 @@ iavf_set_no_poll(struct iavf_adapter *adapter, bool link_change)
 	bool no_poll;
 
 	no_poll = (link_change & !vf->link_up) ||
-		vf->vf_reset ||
+		rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire) ||
 		rte_atomic_load_explicit(&vf->in_reset_recovery, rte_memory_order_relaxed);
 
 	rte_atomic_store_explicit(&adapter->no_poll, no_poll,
@@ -3740,8 +3736,8 @@ iavf_resume_pending_start(struct rte_eth_dev *dev)
 	if (!vf->start_pending)
 		return;
 
-	if (vf->vf_reset || rte_atomic_load_explicit(&vf->in_reset_recovery,
-			rte_memory_order_acquire))
+	if (rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire) ||
+	    rte_atomic_load_explicit(&vf->in_reset_recovery, rte_memory_order_acquire))
 		return;
 
 	/*
diff --git a/drivers/net/intel/iavf/iavf_vchnl.c b/drivers/net/intel/iavf/iavf_vchnl.c
index 34961fe654..a8d17ed733 100644
--- a/drivers/net/intel/iavf/iavf_vchnl.c
+++ b/drivers/net/intel/iavf/iavf_vchnl.c
@@ -59,6 +59,26 @@ static struct iavf_event_handler event_handler = {
 	(var) = (tvar))
 #endif
 
+static void
+iavf_dev_reset_event_handle(struct iavf_event_element *elem)
+{
+	struct rte_eth_dev *dev = elem->dev;
+	struct iavf_adapter *adapter = dev->data->dev_private;
+	uint16_t port_id = dev->data->port_id;
+
+	if (adapter->devargs.auto_reset)
+		iavf_handle_hw_reset(dev, false);
+	else
+		rte_eth_dev_callback_process(dev, elem->event, elem->param);
+
+	/* Ensure the port still exists before touching the adapter
+	 * as the application callback may have closed it.
+	 */
+	if (rte_eth_dev_is_valid_port(port_id))
+		rte_atomic_fetch_sub_explicit(&adapter->vf.reset_events_pending, 1,
+				rte_memory_order_release);
+}
+
 static uint32_t
 iavf_dev_event_handle(void *param __rte_unused)
 {
@@ -80,15 +100,10 @@ iavf_dev_event_handle(void *param __rte_unused)
 		TAILQ_FOREACH_SAFE(pos, &pending, next, save_next) {
 			TAILQ_REMOVE(&pending, pos, next);
 
-			struct iavf_adapter *adapter = pos->dev->data->dev_private;
-			if (pos->event == RTE_ETH_EVENT_INTR_RESET &&
-			    adapter->devargs.auto_reset) {
-				iavf_handle_hw_reset(pos->dev, false);
-				rte_free(pos);
-				continue;
-			}
-
-			rte_eth_dev_callback_process(pos->dev, pos->event, pos->param);
+			if (pos->event == RTE_ETH_EVENT_INTR_RESET)
+				iavf_dev_reset_event_handle(pos);
+			else
+				rte_eth_dev_callback_process(pos->dev, pos->event, pos->param);
 			rte_free(pos);
 		}
 	}
@@ -96,7 +111,7 @@ iavf_dev_event_handle(void *param __rte_unused)
 	return 0;
 }
 
-void
+int
 iavf_dev_event_post(struct rte_eth_dev *dev,
 		enum rte_eth_event_type event,
 		void *param, size_t param_alloc_size)
@@ -105,7 +120,7 @@ iavf_dev_event_post(struct rte_eth_dev *dev,
 	char notify_byte = 0;
 	struct iavf_event_element *elem = rte_malloc(NULL, sizeof(*elem) + param_alloc_size, 0);
 	if (!elem)
-		return;
+		return -ENOMEM;
 
 	elem->dev = dev;
 	elem->event = event;
@@ -122,6 +137,53 @@ iavf_dev_event_post(struct rte_eth_dev *dev,
 
 	ssize_t nw = write(handler->fd[1], &notify_byte, 1);
 	RTE_SET_USED(nw);
+
+	return 0;
+}
+
+/*
+ * Enter the VF reset state. If this is a new reset, rather than one already
+ * in progress, gate the datapath and post the reset event.
+ * Returns true if this call started the reset.
+ */
+bool
+iavf_dev_enter_reset(struct rte_eth_dev *dev, bool drain_tx)
+{
+	struct iavf_adapter *adapter = IAVF_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
+	struct iavf_info *vf = &adapter->vf;
+	bool expected = false;
+
+	/*
+	 * Force link down to drop the cached link-up state; a fresh LSC up
+	 * event will be re-issued by the PF once the VF is reinitialised.
+	 */
+	vf->link_up = false;
+
+	/*
+	 * Count the event before vf_reset becomes visible. The watchdog clears
+	 * vf_reset once it sees it set with no events pending, so setting the
+	 * flag first would open a window in which the event is lost. If another
+	 * thread has already entered reset, back the count out: no event is
+	 * posted on this path.
+	 */
+	rte_atomic_fetch_add_explicit(&vf->reset_events_pending, 1, rte_memory_order_relaxed);
+	if (!rte_atomic_compare_exchange_strong_explicit(&vf->vf_reset, &expected, true,
+			rte_memory_order_release, rte_memory_order_acquire)) {
+		rte_atomic_fetch_sub_explicit(&vf->reset_events_pending, 1,
+				rte_memory_order_relaxed);
+		return false;
+	}
+
+	iavf_set_no_poll(adapter, false);
+	/* drain before posting the event, as the event handler tears down the queues */
+	if (drain_tx && adapter->devargs.no_poll_on_link_down)
+		iavf_dev_tx_drain(dev);
+
+	if (iavf_dev_event_post(dev, RTE_ETH_EVENT_INTR_RESET, NULL, 0) != 0)
+		rte_atomic_fetch_sub_explicit(&vf->reset_events_pending, 1,
+				rte_memory_order_release);
+
+	return true;
 }
 
 int
@@ -260,7 +322,7 @@ iavf_handle_link_change_event(struct rte_eth_dev *dev,
 	 * (link is down or a VF reset is in progress); the watchdog drives
 	 * auto-reset recovery, so it must remain armed in those cases.
 	 */
-	if (vf->link_up && !vf->vf_reset &&
+	if (vf->link_up && !rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire) &&
 	    !rte_atomic_load_explicit(&vf->in_reset_recovery, rte_memory_order_relaxed))
 		iavf_dev_watchdog_disable(adapter);
 	else
@@ -324,23 +386,8 @@ iavf_read_msg_from_pf(struct iavf_adapter *adapter, uint16_t buf_len,
 			iavf_handle_link_change_event(dev, vpe);
 			break;
 		case VIRTCHNL_EVENT_RESET_IMPENDING:
-			/*
-			 * Force link down on impending reset to drop
-			 * the cached link-up state; a fresh LSC up
-			 * event will be re-issued by the PF once the
-			 * VF is reinitialised.
-			 */
-			vf->link_up = false;
-			if (!vf->vf_reset) {
-				vf->vf_reset = true;
-				iavf_set_no_poll(adapter, false);
+			if (iavf_dev_enter_reset(dev, true))
 				iavf_dev_watchdog_enable(adapter);
-				if (adapter->devargs.no_poll_on_link_down)
-					iavf_dev_tx_drain(dev);
-				iavf_dev_event_post(dev,
-					RTE_ETH_EVENT_INTR_RESET,
-					NULL, 0);
-			}
 			PMD_DRV_LOG(DEBUG, "VF is resetting");
 			break;
 		case VIRTCHNL_EVENT_PF_DRIVER_CLOSE:
@@ -485,7 +532,7 @@ iavf_execute_vf_cmd(struct iavf_adapter *adapter, struct iavf_cmd_info *args)
 	bool poll = iavf_op_needs_poll(vf);
 	int err;
 
-	if (vf->vf_reset)
+	if (rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire))
 		return -EIO;
 
 	resp_count = iavf_get_cmd_resp_count(args->ops);
@@ -555,7 +602,6 @@ iavf_handle_pf_event_msg(struct rte_eth_dev *dev, uint8_t *msg,
 {
 	struct iavf_adapter *adapter =
 		IAVF_DEV_PRIVATE_TO_ADAPTER(dev->data->dev_private);
-	struct iavf_info *vf = &adapter->vf;
 	struct virtchnl_pf_event *pf_msg =
 			(struct virtchnl_pf_event *)msg;
 
@@ -571,16 +617,8 @@ iavf_handle_pf_event_msg(struct rte_eth_dev *dev, uint8_t *msg,
 	switch (pf_msg->event) {
 	case VIRTCHNL_EVENT_RESET_IMPENDING:
 		PMD_DRV_LOG(DEBUG, "VIRTCHNL_EVENT_RESET_IMPENDING event");
-		vf->link_up = false;
-		if (!vf->vf_reset) {
-			vf->vf_reset = true;
-			iavf_set_no_poll(adapter, false);
+		if (iavf_dev_enter_reset(dev, true))
 			iavf_dev_watchdog_enable(adapter);
-			if (adapter->devargs.no_poll_on_link_down)
-				iavf_dev_tx_drain(dev);
-			iavf_dev_event_post(dev, RTE_ETH_EVENT_INTR_RESET,
-				NULL, 0);
-		}
 		break;
 	case VIRTCHNL_EVENT_LINK_CHANGE:
 		PMD_DRV_LOG(DEBUG, "VIRTCHNL_EVENT_LINK_CHANGE event");
@@ -2394,13 +2432,13 @@ iavf_request_queues(struct rte_eth_dev *dev, uint16_t num)
 
 	/* wait for interrupt notification vf is resetting */
 	while (i++ < MAX_TRY_TIMES) {
-		if (vf->vf_reset)
+		if (rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire))
 			break;
 		iavf_msec_delay(ASQ_DELAY_MS);
 	}
 
 	/* request queues succeeded, vf is resetting */
-	if (vf->vf_reset) {
+	if (rte_atomic_load_explicit(&vf->vf_reset, rte_memory_order_acquire)) {
 		PMD_DRV_LOG(INFO, "vf is resetting");
 		return 0;
 	}
-- 
2.43.0



More information about the stable mailing list