idpf_vc_event_task() shuts the transaction manager down before every
IDPF_HR_FUNC_RESET. That is only right when the reset was detected in
HW. Resets requested by idpf_tx_timeout() and idpf_idc_request_reset()
find the device still running, and idpf_init_hard_reset() now closes the
netdevs first, so the DISABLE_VPORT and DISABLE_QUEUES messages sent from
idpf_vport_stop() fail silently and the rings are freed while the queues
may still DMA into them. The device reset only follows later, and on a
VF not at all once IDPF_REMOVE_IN_PROG is set.

- Shut the manager down in idpf_service_task() when it detects a HW
  reset, and only while IDPF_HR_FUNC_RESET is pending, as that reset is
  what rebuilds it. This releases waiters the CP will never answer,
  which may hold RTNL, before idpf_detach_and_close() needs it, and
  leaves alone a manager the running reset has already rebuilt.

- Cap all transactions while a driver initiated reset runs at
  IDPF_VC_XN_SHUTDOWN_TIMEOUT_MSEC, as idpf_shutdown() does, so an
  unresponsive CP cannot hold RTNL for the 60 s default per message.
  Restore the default once the new mailbox is up.

- Add IDPF_SHUTDOWN_IN_PROG for the shutdown only policies that were
  inferred from the shortened timeout: latching the timeout to 0 after
  the first expiry, and giving up on SW markers after the first missing
  one. Resets restore the timeout, so a late latch could carry into the
  next reset, and they should wait for every marker as a close does.
  IDPF_REMOVE_IN_PROG cannot be used as remove may wait on a running
  reset.

- Cancel serv_task in idpf_deinit_dflt_mbx() before the manager is
  freed. Its libie_ctlq_xn_shutdown() can now overlap the one in
  idpf_vc_core_deinit(), and the previous patch requires the manager to
  outlive every caller.

- Cancel serv_task in idpf_remove() before vc_event_task, as
  idpf_shutdown() does, so a reset cannot be queued after remove stopped
  waiting for one.

A CP that answers slower than the cap can still have the disable
messages skipped, as before this change, so this narrows that window
rather than closing it.

Fixes: 2e281e1155fc ("idpf: detach and close netdevs while handling a reset")
Assisted-by: LLM
Signed-off-by: Emil Tantilov <[email protected]>
---
 drivers/net/ethernet/intel/idpf/idpf.h        |  5 ++-
 drivers/net/ethernet/intel/idpf/idpf_lib.c    | 42 +++++++++++--------
 drivers/net/ethernet/intel/idpf/idpf_main.c   |  4 ++
 .../net/ethernet/intel/idpf/idpf_virtchnl.c   | 15 ++++---
 4 files changed, 42 insertions(+), 24 deletions(-)

diff --git a/drivers/net/ethernet/intel/idpf/idpf.h 
b/drivers/net/ethernet/intel/idpf/idpf.h
index 98adaed4d3a2e..3d0216e89db80 100644
--- a/drivers/net/ethernet/intel/idpf/idpf.h
+++ b/drivers/net/ethernet/intel/idpf/idpf.h
@@ -86,6 +86,7 @@ enum idpf_state {
  * @IDPF_HR_DRV_LOAD: Set on driver load for a clean HW
  * @IDPF_HR_RESET_IN_PROG: Reset in progress
  * @IDPF_REMOVE_IN_PROG: Driver remove or shutdown in progress
+ * @IDPF_SHUTDOWN_IN_PROG: Driver shutdown in progress
  * @IDPF_MB_INTR_MODE: Mailbox in interrupt mode
  * @IDPF_VC_CORE_INIT: virtchnl core has been init
  * @IDPF_FLAGS_NBITS: Must be last
@@ -95,6 +96,7 @@ enum idpf_flags {
        IDPF_HR_DRV_LOAD,
        IDPF_HR_RESET_IN_PROG,
        IDPF_REMOVE_IN_PROG,
+       IDPF_SHUTDOWN_IN_PROG,
        IDPF_MB_INTR_MODE,
        IDPF_VC_CORE_INIT,
        IDPF_FLAGS_NBITS,
@@ -643,7 +645,8 @@ struct idpf_edt_caps_ilog2 {
  * @arq: Receive control queue info
  * @xnm: Xn transaction manager
  * @vc_xn_timeout_ms: Upper bound for each Xn transaction timeout, shortened
- *                   on shutdown and set to 0 once a shortened one timed out
+ *                   on shutdown and reset teardown, and set to 0 once a
+ *                   shortened one timed out on shutdown
  * @num_avail_msix: Available number of MSIX vectors
  * @num_msix_entries: Number of entries in MSIX table
  * @msix_entries: MSIX table
diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c 
b/drivers/net/ethernet/intel/idpf/idpf_lib.c
index 0cdbd81b13711..1f38d3e3a3f63 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_lib.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c
@@ -1431,13 +1431,22 @@ void idpf_service_task(struct work_struct *work)
        adapter = container_of(work, struct idpf_adapter, serv_task.work);
 
        if (idpf_is_reset_detected(adapter) &&
-           !idpf_is_reset_in_prog(adapter) &&
            !test_bit(IDPF_REMOVE_IN_PROG, adapter->flags)) {
-               dev_info(&adapter->pdev->dev, "HW reset detected\n");
-               set_bit(IDPF_HR_FUNC_RESET, adapter->flags);
-               queue_delayed_work(adapter->vc_event_wq,
-                                  &adapter->vc_event_task,
-                                  msecs_to_jiffies(10));
+               bool new_reset = !idpf_is_reset_in_prog(adapter);
+
+               if (new_reset) {
+                       dev_info(&adapter->pdev->dev, "HW reset detected\n");
+                       set_bit(IDPF_HR_FUNC_RESET, adapter->flags);
+               }
+
+               /* Release the waiters on a mailbox that a reset will rebuild */
+               if (adapter->xnm && test_bit(IDPF_HR_FUNC_RESET, 
adapter->flags))
+                       libie_ctlq_xn_shutdown(adapter->xnm);
+
+               if (new_reset)
+                       queue_delayed_work(adapter->vc_event_wq,
+                                          &adapter->vc_event_task,
+                                          msecs_to_jiffies(10));
        }
 
        queue_delayed_work(adapter->serv_wq, &adapter->serv_task,
@@ -1930,6 +1939,11 @@ static void idpf_init_hard_reset(struct idpf_adapter 
*adapter)
        struct device *dev = &adapter->pdev->dev;
        int err;
 
+       /* As in idpf_shutdown(), bound the teardown if the CP is unresponsive 
*/
+       if (test_bit(IDPF_HR_FUNC_RESET, adapter->flags))
+               WRITE_ONCE(adapter->vc_xn_timeout_ms,
+                          IDPF_VC_XN_SHUTDOWN_TIMEOUT_MSEC);
+
        idpf_detach_and_close(adapter);
        mutex_lock(&adapter->vport_ctrl_lock);
 
@@ -1968,6 +1982,8 @@ static void idpf_init_hard_reset(struct idpf_adapter 
*adapter)
                goto unlock_mutex;
        }
 
+       WRITE_ONCE(adapter->vc_xn_timeout_ms, IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC);
+
        /* Initialize the state machine, also allocate memory and request
         * resources
         */
@@ -2009,18 +2025,10 @@ void idpf_vc_event_task(struct work_struct *work)
        if (test_bit(IDPF_REMOVE_IN_PROG, adapter->flags))
                return;
 
-       if (test_bit(IDPF_HR_FUNC_RESET, adapter->flags))
-               goto func_reset;
-
-       if (test_bit(IDPF_HR_DRV_LOAD, adapter->flags))
-               goto drv_load;
-
-       return;
+       if (!test_bit(IDPF_HR_FUNC_RESET, adapter->flags) &&
+           !test_bit(IDPF_HR_DRV_LOAD, adapter->flags))
+               return;
 
-func_reset:
-       if (adapter->xnm)
-               libie_ctlq_xn_shutdown(adapter->xnm);
-drv_load:
        set_bit(IDPF_HR_RESET_IN_PROG, adapter->flags);
        idpf_init_hard_reset(adapter);
 }
diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c 
b/drivers/net/ethernet/intel/idpf/idpf_main.c
index 21be83dc82b1b..edbf55d4e490e 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_main.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_main.c
@@ -125,6 +125,9 @@ static void idpf_remove(struct pci_dev *pdev)
 
        set_bit(IDPF_REMOVE_IN_PROG, adapter->flags);
 
+       /* serv_task can still queue vc_event_task until it is cancelled */
+       cancel_delayed_work_sync(&adapter->serv_task);
+
        /* Wait until vc_event_task is done to consider if any hard reset is
         * in progress else we may go ahead and release the resources but the
         * thread doing the hard reset might continue the init path and
@@ -198,6 +201,7 @@ static void idpf_shutdown(struct pci_dev *pdev)
         * destroy the vports while the mailbox is still up.
         */
        set_bit(IDPF_REMOVE_IN_PROG, adapter->flags);
+       set_bit(IDPF_SHUTDOWN_IN_PROG, adapter->flags);
        cancel_delayed_work_sync(&adapter->serv_task);
        cancel_delayed_work_sync(&adapter->vc_event_task);
 
diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c 
b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
index eca954783454f..f45cdd227d482 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
@@ -196,8 +196,8 @@ static void idpf_prepare_ptp_mb_msg(struct idpf_adapter 
*adapter, u32 op,
  * unmap and release the buffer.
  *
  * The transaction timeout is capped at adapter->vc_xn_timeout_ms. Once a
- * synchronous transaction with a shortened timeout times out, later ones
- * fail at once instead of waiting again.
+ * synchronous transaction with a shortened timeout times out on shutdown,
+ * later ones fail at once instead of waiting again.
  *
  * Return: 0 if the request was successful, -%EBUSY if reset is detected
  *        or Tx control queue is full, -%ETIMEDOUT if a shortened transaction
@@ -236,7 +236,9 @@ int idpf_send_mb_msg(struct idpf_adapter *adapter,
        idpf_mb_clean(xn_params->ctlq, false);
 
        err = libie_ctlq_xn_send(xn_params);
-       if (err == -ETIMEDOUT && timeout_ms < IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC)
+       /* Resets restore the timeout, so only shutdown can latch it */
+       if (err == -ETIMEDOUT && timeout_ms < IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC &&
+           test_bit(IDPF_SHUTDOWN_IN_PROG, adapter->flags))
                WRITE_ONCE(adapter->vc_xn_timeout_ms, 0);
 
        return err;
@@ -392,8 +394,7 @@ static int idpf_wait_for_marker_event_set(const struct 
idpf_queue_set *qs)
        bool wait_all;
 
        /* On shutdown, once one marker is missing, don't wait for the rest */
-       wait_all = READ_ONCE(qs->adapter->vc_xn_timeout_ms) >=
-                  IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC;
+       wait_all = !test_bit(IDPF_SHUTDOWN_IN_PROG, qs->adapter->flags);
 
        for (u32 i = 0; i < qs->num; i++) {
                switch (qs->qs[i].type) {
@@ -3027,6 +3028,8 @@ int idpf_init_dflt_mbx(struct idpf_adapter *adapter)
 void idpf_deinit_dflt_mbx(struct idpf_adapter *adapter)
 {
        idpf_mb_intr_rel_irq(adapter);
+       /* serv_task may be in libie_ctlq_xn_shutdown(), xnm must outlive it */
+       cancel_delayed_work_sync(&adapter->serv_task);
        cancel_delayed_work_sync(&adapter->mbx_task);
 
        if (adapter->xnm) {
@@ -3287,7 +3290,7 @@ void idpf_vc_core_deinit(struct idpf_adapter *adapter)
        if (!test_bit(IDPF_VC_CORE_INIT, adapter->flags))
                return;
 
-       /* Avoid transaction timeouts when called during reset */
+       /* Driver initiated resets may end up here with mailbox still up */
        remove_in_prog = test_bit(IDPF_REMOVE_IN_PROG, adapter->flags);
        if (!remove_in_prog) {
                libie_ctlq_xn_shutdown(adapter->xnm);
-- 
2.37.3

Reply via email to