]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: iwlwifi: back off on continuous errors
authorJohannes Berg <johannes.berg@intel.com>
Sun, 20 Apr 2025 06:59:58 +0000 (09:59 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 23 Apr 2025 12:45:29 +0000 (14:45 +0200)
When errors occur repeatedly, the driver shouldn't go into a
tight loop trying to reset the device. Implement the backoff
I had already defined IWL_TRANS_RESET_DELAY for, but clearly
forgotten the implementation of.

Fixes: 9a2f13c40c63 ("wifi: iwlwifi: implement reset escalation")
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20250420095642.8816e299efa2.I82cde34e2345a2b33b1f03dbb040f5ad3439a5aa@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/intel/iwlwifi/iwl-trans.c
drivers/net/wireless/intel/iwlwifi/iwl-trans.h
drivers/net/wireless/intel/iwlwifi/pcie/trans.c

index c1607b6d0759e0ee4be689f43181c22b0eb769af..6125fe70ce7288f0cae3bfb87a2d19c2e1048d4d 100644 (file)
@@ -21,6 +21,7 @@ struct iwl_trans_dev_restart_data {
        struct list_head list;
        unsigned int restart_count;
        time64_t last_error;
+       bool backoff;
        char name[];
 };
 
@@ -125,13 +126,20 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans)
        if (!data)
                return at_least;
 
-       if (ktime_get_boottime_seconds() - data->last_error >=
+       if (!data->backoff &&
+           ktime_get_boottime_seconds() - data->last_error >=
                        IWL_TRANS_RESET_OK_TIME)
                data->restart_count = 0;
 
        index = data->restart_count;
-       if (index >= ARRAY_SIZE(escalation_list))
+       if (index >= ARRAY_SIZE(escalation_list)) {
                index = ARRAY_SIZE(escalation_list) - 1;
+               if (!data->backoff) {
+                       data->backoff = true;
+                       return IWL_RESET_MODE_BACKOFF;
+               }
+               data->backoff = false;
+       }
 
        return max(at_least, escalation_list[index]);
 }
@@ -140,7 +148,8 @@ iwl_trans_determine_restart_mode(struct iwl_trans *trans)
 
 static void iwl_trans_restart_wk(struct work_struct *wk)
 {
-       struct iwl_trans *trans = container_of(wk, typeof(*trans), restart.wk);
+       struct iwl_trans *trans = container_of(wk, typeof(*trans),
+                                              restart.wk.work);
        struct iwl_trans_reprobe *reprobe;
        enum iwl_reset_mode mode;
 
@@ -168,6 +177,12 @@ static void iwl_trans_restart_wk(struct work_struct *wk)
                return;
 
        mode = iwl_trans_determine_restart_mode(trans);
+       if (mode == IWL_RESET_MODE_BACKOFF) {
+               IWL_ERR(trans, "Too many device errors - delay next reset\n");
+               queue_delayed_work(system_unbound_wq, &trans->restart.wk,
+                                  IWL_TRANS_RESET_DELAY);
+               return;
+       }
 
        iwl_trans_inc_restart_count(trans->dev);
 
@@ -227,7 +242,7 @@ struct iwl_trans *iwl_trans_alloc(unsigned int priv_size,
        trans->dev = dev;
        trans->num_rx_queues = 1;
 
-       INIT_WORK(&trans->restart.wk, iwl_trans_restart_wk);
+       INIT_DELAYED_WORK(&trans->restart.wk, iwl_trans_restart_wk);
 
        return trans;
 }
@@ -271,7 +286,7 @@ int iwl_trans_init(struct iwl_trans *trans)
 
 void iwl_trans_free(struct iwl_trans *trans)
 {
-       cancel_work_sync(&trans->restart.wk);
+       cancel_delayed_work_sync(&trans->restart.wk);
        kmem_cache_destroy(trans->dev_cmd_pool);
 }
 
@@ -403,7 +418,7 @@ void iwl_trans_op_mode_leave(struct iwl_trans *trans)
 
        iwl_trans_pcie_op_mode_leave(trans);
 
-       cancel_work_sync(&trans->restart.wk);
+       cancel_delayed_work_sync(&trans->restart.wk);
 
        trans->op_mode = NULL;
 
index 18698f0bd2e4d97ab9546d2e8375990c460550f9..ce4954b0d52462f4440b687eb3cc639f1a066c45 100644 (file)
@@ -961,7 +961,7 @@ struct iwl_trans {
        struct iwl_dma_ptr invalid_tx_cmd;
 
        struct {
-               struct work_struct wk;
+               struct delayed_work wk;
                struct iwl_fw_error_dump_mode mode;
                bool during_reset;
        } restart;
@@ -1162,7 +1162,7 @@ static inline void iwl_trans_schedule_reset(struct iwl_trans *trans,
         */
        trans->restart.during_reset = test_bit(STATUS_IN_SW_RESET,
                                               &trans->status);
-       queue_work(system_unbound_wq, &trans->restart.wk);
+       queue_delayed_work(system_unbound_wq, &trans->restart.wk, 0);
 }
 
 static inline void iwl_trans_fw_error(struct iwl_trans *trans,
@@ -1261,6 +1261,9 @@ enum iwl_reset_mode {
        IWL_RESET_MODE_RESCAN,
        IWL_RESET_MODE_FUNC_RESET,
        IWL_RESET_MODE_PROD_RESET,
+
+       /* keep last - special backoff value */
+       IWL_RESET_MODE_BACKOFF,
 };
 
 void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode);
index c917ed4c19bcc3045e8134d70a41523589df05fa..b1ccace7377fbc24e2be6f89ec86cad8b14b2e2e 100644 (file)
@@ -2351,7 +2351,8 @@ void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode)
        struct iwl_trans_pcie_removal *removal;
        char _msg = 0, *msg = &_msg;
 
-       if (WARN_ON(mode < IWL_RESET_MODE_REMOVE_ONLY))
+       if (WARN_ON(mode < IWL_RESET_MODE_REMOVE_ONLY ||
+                   mode == IWL_RESET_MODE_BACKOFF))
                return;
 
        if (test_bit(STATUS_TRANS_DEAD, &trans->status))