]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
wifi: iwlwifi: mvm: fix hibernation
authorEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Sun, 25 Aug 2024 16:17:02 +0000 (19:17 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 26 Aug 2024 15:35:22 +0000 (17:35 +0200)
Fast resume is a feature that was recently introduced to speed up the
resume time. It basically keeps the firmware alive while the system
is suspended and that avoids starting again the whole device.

This flow can't work for hibernation, since when the system boots,
before the frozen image is loaded, the kernel may touch the device. As a
result, we can't assume the device is in the exact same state as before
the hibernation.

Detect that we are resuming from hibernation through the PCI device and
forbid the fast resume flow. We also need to shut down the device
cleanly when that happens.

In addition, in case the device is power gated during S3, we won't be
able to keep the device alive. Detect this situation with BE200 at least
with the help of the CSR_FUNC_SCRATCH register and reset the device upon
resume if it was power gated during S3.

Fixes: e8bb19c1d590 ("wifi: iwlwifi: support fast resume")
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20240825191257.24eb3b19e74f.I3837810318dbef0a0a773cf4c4fcf89cdc6fdbd3@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h
drivers/net/wireless/intel/iwlwifi/mvm/d3.c
drivers/net/wireless/intel/iwlwifi/mvm/ops.c
drivers/net/wireless/intel/iwlwifi/pcie/drv.c

index 595fa6ddf0843bf3c6f1b2674cd8817198d94a71..8ef5ed2db051772ff835bce70de403e25f33b897 100644 (file)
@@ -85,6 +85,10 @@ struct iwl_cfg;
  *     May sleep
  * @wimax_active: invoked when WiMax becomes active. May sleep
  * @time_point: called when transport layer wants to collect debug data
+ * @device_powered_off: called upon resume from hibernation but not only.
+ *     Op_mode needs to reset its internal state because the device did not
+ *     survive the system state transition. The firmware is no longer running,
+ *     etc...
  */
 struct iwl_op_mode_ops {
        struct iwl_op_mode *(*start)(struct iwl_trans *trans,
@@ -107,6 +111,7 @@ struct iwl_op_mode_ops {
        void (*time_point)(struct iwl_op_mode *op_mode,
                           enum iwl_fw_ini_time_point tp_id,
                           union iwl_dbg_tlv_tp_data *tp_data);
+       void (*device_powered_off)(struct iwl_op_mode *op_mode);
 };
 
 int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops);
@@ -204,4 +209,11 @@ static inline void iwl_op_mode_time_point(struct iwl_op_mode *op_mode,
        op_mode->ops->time_point(op_mode, tp_id, tp_data);
 }
 
+static inline void iwl_op_mode_device_powered_off(struct iwl_op_mode *op_mode)
+{
+       if (!op_mode || !op_mode->ops || !op_mode->ops->device_powered_off)
+               return;
+       op_mode->ops->device_powered_off(op_mode);
+}
+
 #endif /* __iwl_op_mode_h__ */
index b4d650583ac27e2ba6d24166a64ec6998cd003cb..99a541d442bb185837f3fa2b9def257fbf36d71c 100644 (file)
@@ -3439,6 +3439,16 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)
 
        mutex_lock(&mvm->mutex);
 
+       /* Apparently, the device went away and device_powered_off() was called,
+        * don't even try to read the rt_status, the device is currently
+        * inaccessible.
+        */
+       if (!test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) {
+               IWL_INFO(mvm,
+                        "Can't resume, device_powered_off() was called during wowlan\n");
+               goto err;
+       }
+
        mvm->last_reset_or_resume_time_jiffies = jiffies;
 
        /* get the BSS vif pointer again */
index b7dcae76a05dfc2a25eba4b101a638da4ae3e482..75fc60a4808cb55cbd8381e3386cbde4d9878fca 100644 (file)
@@ -2090,6 +2090,20 @@ static void iwl_op_mode_mvm_time_point(struct iwl_op_mode *op_mode,
        iwl_dbg_tlv_time_point(&mvm->fwrt, tp_id, tp_data);
 }
 
+static void iwl_op_mode_mvm_device_powered_off(struct iwl_op_mode *op_mode)
+{
+       struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode);
+
+       mutex_lock(&mvm->mutex);
+       clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status);
+       mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED;
+       iwl_mvm_stop_device(mvm);
+#ifdef CONFIG_PM
+       mvm->fast_resume = false;
+#endif
+       mutex_unlock(&mvm->mutex);
+}
+
 #define IWL_MVM_COMMON_OPS                                     \
        /* these could be differentiated */                     \
        .queue_full = iwl_mvm_stop_sw_queue,                    \
@@ -2102,7 +2116,8 @@ static void iwl_op_mode_mvm_time_point(struct iwl_op_mode *op_mode,
        /* as we only register one, these MUST be common! */    \
        .start = iwl_op_mode_mvm_start,                         \
        .stop = iwl_op_mode_mvm_stop,                           \
-       .time_point = iwl_op_mode_mvm_time_point
+       .time_point = iwl_op_mode_mvm_time_point,               \
+       .device_powered_off = iwl_op_mode_mvm_device_powered_off
 
 static const struct iwl_op_mode_ops iwl_mvm_ops = {
        IWL_MVM_COMMON_OPS,
index 9ad43464b702bee69fff779f9a513697f593740b..84fd93278450b02105f3d58b7700e77193678e97 100644 (file)
@@ -1577,11 +1577,12 @@ static int iwl_pci_suspend(struct device *device)
        return 0;
 }
 
-static int iwl_pci_resume(struct device *device)
+static int _iwl_pci_resume(struct device *device, bool restore)
 {
        struct pci_dev *pdev = to_pci_dev(device);
        struct iwl_trans *trans = pci_get_drvdata(pdev);
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+       bool device_was_powered_off = false;
 
        /* Before you put code here, think about WoWLAN. You cannot check here
         * whether WoWLAN is enabled or not, and your code will run even if
@@ -1597,6 +1598,26 @@ static int iwl_pci_resume(struct device *device)
        if (!trans->op_mode)
                return 0;
 
+       /*
+        * Scratch value was altered, this means the device was powered off, we
+        * need to reset it completely.
+        * Note: MAC (bits 0:7) will be cleared upon suspend even with wowlan,
+        * so assume that any bits there mean that the device is usable.
+        */
+       if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ &&
+           !iwl_read32(trans, CSR_FUNC_SCRATCH))
+               device_was_powered_off = true;
+
+       if (restore || device_was_powered_off) {
+               trans->state = IWL_TRANS_NO_FW;
+               /* Hope for the best here ... If one of those steps fails we
+                * won't really know how to recover.
+                */
+               iwl_pcie_prepare_card_hw(trans);
+               iwl_finish_nic_init(trans);
+               iwl_op_mode_device_powered_off(trans->op_mode);
+       }
+
        /* In WOWLAN, let iwl_trans_pcie_d3_resume do the rest of the work */
        if (test_bit(STATUS_DEVICE_ENABLED, &trans->status))
                return 0;
@@ -1617,9 +1638,23 @@ static int iwl_pci_resume(struct device *device)
        return 0;
 }
 
+static int iwl_pci_restore(struct device *device)
+{
+       return _iwl_pci_resume(device, true);
+}
+
+static int iwl_pci_resume(struct device *device)
+{
+       return _iwl_pci_resume(device, false);
+}
+
 static const struct dev_pm_ops iwl_dev_pm_ops = {
-       SET_SYSTEM_SLEEP_PM_OPS(iwl_pci_suspend,
-                               iwl_pci_resume)
+       .suspend = pm_sleep_ptr(iwl_pci_suspend),
+       .resume = pm_sleep_ptr(iwl_pci_resume),
+       .freeze = pm_sleep_ptr(iwl_pci_suspend),
+       .thaw = pm_sleep_ptr(iwl_pci_resume),
+       .poweroff = pm_sleep_ptr(iwl_pci_suspend),
+       .restore = pm_sleep_ptr(iwl_pci_restore),
 };
 
 #define IWL_PM_OPS     (&iwl_dev_pm_ops)