[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], ¬ify_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