Commit cb347bd2 authored by Emmanuel Grumbach's avatar Emmanuel Grumbach Committed by Johannes Berg

wifi: iwlwifi: mvm: fix hibernation

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: e8bb19c1 ("wifi: iwlwifi: support fast resume")
Signed-off-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: default avatarMiri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20240825191257.24eb3b19e74f.I3837810318dbef0a0a773cf4c4fcf89cdc6fdbd3@changeidSigned-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 3e9b4021
......@@ -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__ */
......@@ -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 */
......
......@@ -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,
......
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment