diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h index f00c9ad9d3c21bbfaad8e45dcca32f5768b9e7c9..8caf68bce1efcc363cc33370f9e3e102ab8c2282 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -669,18 +669,61 @@ enum iwl_trans_state { }; /** - * enum iwl_d0i3_mode - d0i3 mode + * DOC: Platform power management * - * @IWL_D0I3_MODE_OFF - d0i3 is disabled - * @IWL_D0I3_MODE_ON_IDLE - enter d0i3 when device is idle - * (e.g. no active references) - * @IWL_D0I3_MODE_ON_SUSPEND - enter d0i3 only on suspend - * (in case of 'any' trigger) + * There are two types of platform power management: system-wide + * (WoWLAN) and runtime. + * + * In system-wide power management the entire platform goes into a low + * power state (e.g. idle or suspend to RAM) at the same time and the + * device is configured as a wakeup source for the entire platform. + * This is usually triggered by userspace activity (e.g. the user + * presses the suspend button or a power management daemon decides to + * put the platform in low power mode). The device's behavior in this + * mode is dictated by the wake-on-WLAN configuration. + * + * In runtime power management, only the devices which are themselves + * idle enter a low power state. This is done at runtime, which means + * that the entire system is still running normally. This mode is + * usually triggered automatically by the device driver and requires + * the ability to enter and exit the low power modes in a very short + * time, so there is not much impact in usability. + * + * The terms used for the device's behavior are as follows: + * + * - D0: the device is fully powered and the host is awake; + * - D3: the device is in low power mode and only reacts to + * specific events (e.g. magic-packet received or scan + * results found); + * - D0I3: the device is in low power mode and reacts to any + * activity (e.g. RX); + * + * These terms reflect the power modes in the firmware and are not to + * be confused with the physical device power state. The NIC can be + * in D0I3 mode even if, for instance, the PCI device is in D3 state. */ -enum iwl_d0i3_mode { - IWL_D0I3_MODE_OFF = 0, - IWL_D0I3_MODE_ON_IDLE, - IWL_D0I3_MODE_ON_SUSPEND, + +/** + * enum iwl_plat_pm_mode - platform power management mode + * + * This enumeration describes the device's platform power management + * behavior when in idle mode (i.e. runtime power management) or when + * in system-wide suspend (i.e WoWLAN). + * + * @IWL_PLAT_PM_MODE_DISABLED: power management is disabled for this + * device. At runtime, this means that nothing happens and the + * device always remains in active. In system-wide suspend mode, + * it means that the all connections will be closed automatically + * by mac80211 before the platform is suspended. + * @IWL_PLAT_PM_MODE_D3: the device goes into D3 mode (i.e. WoWLAN). + * For runtime power management, this mode is not officially + * supported. + * @IWL_PLAT_PM_MODE_D0I3: the device goes into D0I3 mode. + */ +enum iwl_plat_pm_mode { + IWL_PLAT_PM_MODE_DISABLED, + IWL_PLAT_PM_MODE_D3, + IWL_PLAT_PM_MODE_D0I3, }; /** @@ -720,6 +763,12 @@ enum iwl_d0i3_mode { * the opmode. * @paging_download_buf: Buffer used for copying all of the pages before * downloading them to the FW. The buffer is allocated in the opmode + * @system_pm_mode: the system-wide power management mode in use. + * This mode is set dynamically, depending on the WoWLAN values + * configured from the userspace at runtime. + * @runtime_pm_mode: the runtime power management mode in use. This + * mode is set during the initialization phase and is not + * supposed to change during runtime. */ struct iwl_trans { const struct iwl_trans_ops *ops; @@ -770,9 +819,8 @@ struct iwl_trans { struct iwl_fw_paging *paging_db; void *paging_download_buf; - enum iwl_d0i3_mode d0i3_mode; - - bool wowlan_d0i3; + enum iwl_plat_pm_mode system_pm_mode; + enum iwl_plat_pm_mode runtime_pm_mode; /* pointer to trans specific struct */ /*Ensure that this pointer will always be aligned to sizeof pointer */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 542de7401a8d718823782152d118c69dae26dec7..b1c99921c376589cf8a3e46f844adcb5ba185c69 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -1183,19 +1183,20 @@ static int iwl_mvm_enter_d0i3_sync(struct iwl_mvm *mvm) int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_trans *trans = mvm->trans; int ret; /* make sure the d0i3 exit work is not pending */ flush_work(&mvm->d0i3_exit_work); - ret = iwl_trans_suspend(mvm->trans); + ret = iwl_trans_suspend(trans); if (ret) return ret; - mvm->trans->wowlan_d0i3 = wowlan->any; - if (mvm->trans->wowlan_d0i3) { - /* 'any' trigger means d0i3 usage */ - if (mvm->trans->d0i3_mode == IWL_D0I3_MODE_ON_SUSPEND) { + if (wowlan->any) { + trans->system_pm_mode = IWL_PLAT_PM_MODE_D0I3; + + if (iwl_mvm_enter_d0i3_on_suspend(mvm)) { ret = iwl_mvm_enter_d0i3_sync(mvm); if (ret) @@ -1206,11 +1207,13 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) __set_bit(D0I3_DEFER_WAKEUP, &mvm->d0i3_suspend_flags); mutex_unlock(&mvm->d0i3_suspend_mutex); - iwl_trans_d3_suspend(mvm->trans, false); + iwl_trans_d3_suspend(trans, false); return 0; } + trans->system_pm_mode = IWL_PLAT_PM_MODE_D3; + return __iwl_mvm_suspend(hw, wowlan, false); } @@ -1973,8 +1976,9 @@ static int iwl_mvm_resume_d0i3(struct iwl_mvm *mvm) { bool exit_now; enum iwl_d3_status d3_status; + struct iwl_trans *trans = mvm->trans; - iwl_trans_d3_resume(mvm->trans, &d3_status, false); + iwl_trans_d3_resume(trans, &d3_status, false); /* * make sure to clear D0I3_DEFER_WAKEUP before @@ -1991,9 +1995,9 @@ static int iwl_mvm_resume_d0i3(struct iwl_mvm *mvm) _iwl_mvm_exit_d0i3(mvm); } - iwl_trans_resume(mvm->trans); + iwl_trans_resume(trans); - if (mvm->trans->d0i3_mode == IWL_D0I3_MODE_ON_SUSPEND) { + if (iwl_mvm_enter_d0i3_on_suspend(mvm)) { int ret = iwl_mvm_exit_d0i3(mvm->hw->priv); if (ret) @@ -2009,12 +2013,16 @@ static int iwl_mvm_resume_d0i3(struct iwl_mvm *mvm) int iwl_mvm_resume(struct ieee80211_hw *hw) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; - /* 'any' trigger means d0i3 was used */ - if (hw->wiphy->wowlan_config->any) - return iwl_mvm_resume_d0i3(mvm); + if (mvm->trans->system_pm_mode == IWL_PLAT_PM_MODE_D0I3) + ret = iwl_mvm_resume_d0i3(mvm); else - return iwl_mvm_resume_d3(mvm); + ret = iwl_mvm_resume_d3(mvm); + + mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; + + return ret; } void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled) @@ -2038,6 +2046,8 @@ static int iwl_mvm_d3_test_open(struct inode *inode, struct file *file) ieee80211_stop_queues(mvm->hw); synchronize_net(); + mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_D3; + /* start pseudo D3 */ rtnl_lock(); err = __iwl_mvm_suspend(mvm->hw, mvm->hw->wiphy->wowlan_config, true); @@ -2092,9 +2102,13 @@ static int iwl_mvm_d3_test_release(struct inode *inode, struct file *file) int remaining_time = 10; mvm->d3_test_active = false; + rtnl_lock(); __iwl_mvm_resume(mvm, true); rtnl_unlock(); + + mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; + iwl_abort_notification_waits(&mvm->notif_wait); ieee80211_restart_hw(mvm->hw); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 7487eb08d156cf5da3c2b6a3234b91d052aa001b..3a78a3f1610ef99233991a9f4f270f240255216d 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -1087,15 +1087,13 @@ static void iwl_mvm_restart_complete(struct iwl_mvm *mvm) static void iwl_mvm_resume_complete(struct iwl_mvm *mvm) { - if (!iwl_mvm_is_d0i3_supported(mvm)) - return; - - if (mvm->trans->d0i3_mode == IWL_D0I3_MODE_ON_SUSPEND) - if (!wait_event_timeout(mvm->d0i3_exit_waitq, - !test_bit(IWL_MVM_STATUS_IN_D0I3, - &mvm->status), - HZ)) - WARN_ONCE(1, "D0i3 exit on resume timed out\n"); + if (iwl_mvm_is_d0i3_supported(mvm) && + iwl_mvm_enter_d0i3_on_suspend(mvm)) + WARN_ONCE(!wait_event_timeout(mvm->d0i3_exit_waitq, + !test_bit(IWL_MVM_STATUS_IN_D0I3, + &mvm->status), + HZ), + "D0i3 exit on resume timed out\n"); } static void diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index 3f0db29d5d56133a78c060a8afb38c447ade6ef7..3fc7199abc9485fe44170388062980bd32d484f4 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -933,6 +933,19 @@ static inline bool iwl_mvm_is_dqa_supported(struct iwl_mvm *mvm) IWL_UCODE_TLV_CAPA_DQA_SUPPORT); } +static inline bool iwl_mvm_enter_d0i3_on_suspend(struct iwl_mvm *mvm) +{ + /* For now we only use this mode to differentiate between + * slave transports, which handle D0i3 entry in suspend by + * themselves in conjunction with runtime PM D0i3. So, this + * function is used to check whether we need to do anything + * when entering suspend or if the transport layer has already + * done it. + */ + return (mvm->trans->system_pm_mode == IWL_PLAT_PM_MODE_D0I3) && + (mvm->trans->runtime_pm_mode != IWL_PLAT_PM_MODE_D0I3); +} + static inline bool iwl_mvm_is_lar_supported(struct iwl_mvm *mvm) { bool nvm_lar = mvm->nvm_data->lar_enabled; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index 0d927635d0dc35c8a034455089845c2852e233f3..05d5a5b1e71a9913ca9e5aea334436c743c13754 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1213,7 +1213,7 @@ static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test) { struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); - if (trans->wowlan_d0i3) { + if (trans->system_pm_mode == IWL_PLAT_PM_MODE_D0I3) { /* Enable persistence mode to avoid reset */ iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, CSR_HW_IF_CONFIG_REG_PERSIST_MODE); @@ -1237,7 +1237,7 @@ static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test) iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); - if (!trans->wowlan_d0i3) { + if (trans->system_pm_mode == IWL_PLAT_PM_MODE_D3) { /* * reset TX queues -- some of their registers reset during S3 * so if we don't reset everything here the D3 image would try @@ -1286,7 +1286,7 @@ static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans, iwl_pcie_set_pwr(trans, false); - if (trans->wowlan_d0i3) { + if (trans->system_pm_mode == IWL_PLAT_PM_MODE_D0I3) { iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); } else { @@ -2677,7 +2677,6 @@ struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev, } trans_pcie->inta_mask = CSR_INI_SET_MASK; - trans->d0i3_mode = IWL_D0I3_MODE_ON_SUSPEND; return trans;