iwlwifi: mvm: implement D3 testing

For testing the D3 (WoWLAN) firmware, it is useful to be able
to run the firmware with instrumentation while the host isn't
sleeping and can poke at the firmware debug logging etc.

Implement this by a debugfs file. When the file is opened the
D3 firmware is loaded and all regular commands are blocked.
While the file is being read, poll the firmware's PME status
flag and report EOF once it changes to non-zero. When it is
closed, do (most of) the resume processing. This lets a user
just "cat" the file. Pressing Ctrl-C to kill the cat process
will resume the firwmare as though the platform resumed for
non-wireless reason and when the firmware wants to wake up
reading from the file automatically completes.

Unlike in real suspend, only disable interrupts and don't
reset the TX/RX hardware while in the test mode. This is a
workaround for some interrupt problems that happen only when
the PCIe link isn't fully reset (presumably by changing the
PCI config space registers which the core PCI code does.)

Note that while regular operations are blocked from sending
commands to the firmware, they could still be made and cause
strange mac80211 issues. Therefore, while using this testing
feature you need to be careful to not try to disconnect, roam
or similar, and will see warnings for such attempts.

Als note that this requires an upcoming firmware change to
tell the driver the location of the PME status flag in SRAM.
D3 test will fail if the firmware doesn't report the pointer.

Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Johannes Berg 2013-05-14 13:53:45 +02:00
parent 774439518a
commit debff6184c
8 changed files with 182 additions and 21 deletions

View File

@ -430,7 +430,7 @@ static int iwlagn_mac_suspend(struct ieee80211_hw *hw,
iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_SET,
CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE);
iwl_trans_d3_suspend(priv->trans);
iwl_trans_d3_suspend(priv->trans, false);
goto out;
@ -504,7 +504,7 @@ static int iwlagn_mac_resume(struct ieee80211_hw *hw)
/* we'll clear ctx->vif during iwlagn_prepare_restart() */
vif = ctx->vif;
ret = iwl_trans_d3_resume(priv->trans, &d3_status);
ret = iwl_trans_d3_resume(priv->trans, &d3_status, false);
if (ret)
goto out_unlock;

View File

@ -428,8 +428,9 @@ struct iwl_trans_ops {
void (*fw_alive)(struct iwl_trans *trans, u32 scd_addr);
void (*stop_device)(struct iwl_trans *trans);
void (*d3_suspend)(struct iwl_trans *trans);
int (*d3_resume)(struct iwl_trans *trans, enum iwl_d3_status *status);
void (*d3_suspend)(struct iwl_trans *trans, bool test);
int (*d3_resume)(struct iwl_trans *trans, enum iwl_d3_status *status,
bool test);
int (*send_cmd)(struct iwl_trans *trans, struct iwl_host_cmd *cmd);
@ -588,17 +589,18 @@ static inline void iwl_trans_stop_device(struct iwl_trans *trans)
trans->state = IWL_TRANS_NO_FW;
}
static inline void iwl_trans_d3_suspend(struct iwl_trans *trans)
static inline void iwl_trans_d3_suspend(struct iwl_trans *trans, bool test)
{
might_sleep();
trans->ops->d3_suspend(trans);
trans->ops->d3_suspend(trans, test);
}
static inline int iwl_trans_d3_resume(struct iwl_trans *trans,
enum iwl_d3_status *status)
enum iwl_d3_status *status,
bool test)
{
might_sleep();
return trans->ops->d3_resume(trans, status);
return trans->ops->d3_resume(trans, status, test);
}
static inline int iwl_trans_send_cmd(struct iwl_trans *trans,

View File

@ -63,6 +63,7 @@
#include <linux/etherdevice.h>
#include <linux/ip.h>
#include <linux/fs.h>
#include <net/cfg80211.h>
#include <net/ipv6.h>
#include <net/tcp.h>
@ -756,7 +757,9 @@ static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
return 0;
}
int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
static int __iwl_mvm_suspend(struct ieee80211_hw *hw,
struct cfg80211_wowlan *wowlan,
bool test)
{
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_d3_iter_data suspend_iter_data = {
@ -769,7 +772,7 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
struct iwl_wowlan_config_cmd wowlan_config_cmd = {};
struct iwl_wowlan_kek_kck_material_cmd kek_kck_cmd = {};
struct iwl_wowlan_tkip_params_cmd tkip_cmd = {};
struct iwl_d3_manager_config d3_cfg_cmd = {
struct iwl_d3_manager_config d3_cfg_cmd_data = {
/*
* Program the minimum sleep time to 10 seconds, as many
* platforms have issues processing a wakeup signal while
@ -777,17 +780,30 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
*/
.min_sleep_time = cpu_to_le32(10 * 1000 * 1000),
};
struct iwl_host_cmd d3_cfg_cmd = {
.id = D3_CONFIG_CMD,
.flags = CMD_SYNC | CMD_WANT_SKB,
.data[0] = &d3_cfg_cmd_data,
.len[0] = sizeof(d3_cfg_cmd_data),
};
struct wowlan_key_data key_data = {
.use_rsc_tsc = false,
.tkip = &tkip_cmd,
.use_tkip = false,
};
int ret, i;
int len __maybe_unused;
u16 seq;
u8 old_aux_sta_id, old_ap_sta_id = IWL_MVM_STATION_COUNT;
if (WARN_ON(!wowlan))
if (!wowlan) {
/*
* mac80211 shouldn't get here, but for D3 test
* it doesn't warrant a warning
*/
WARN_ON(!test);
return -EINVAL;
}
key_data.rsc_tsc = kzalloc(sizeof(*key_data.rsc_tsc), GFP_KERNEL);
if (!key_data.rsc_tsc)
@ -1012,14 +1028,26 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
goto out;
/* must be last -- this switches firmware state */
ret = iwl_mvm_send_cmd_pdu(mvm, D3_CONFIG_CMD, CMD_SYNC,
sizeof(d3_cfg_cmd), &d3_cfg_cmd);
ret = iwl_mvm_send_cmd(mvm, &d3_cfg_cmd);
if (ret)
goto out;
#ifdef CONFIG_IWLWIFI_DEBUGFS
len = le32_to_cpu(d3_cfg_cmd.resp_pkt->len_n_flags) &
FH_RSCSR_FRAME_SIZE_MSK;
if (len >= sizeof(u32) * 2) {
mvm->d3_test_pme_ptr =
le32_to_cpup((__le32 *)d3_cfg_cmd.resp_pkt->data);
} else if (test) {
/* in test mode we require the pointer */
ret = -EIO;
goto out;
}
#endif
iwl_free_resp(&d3_cfg_cmd);
clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
iwl_trans_d3_suspend(mvm->trans);
iwl_trans_d3_suspend(mvm->trans, test);
out:
mvm->aux_sta.sta_id = old_aux_sta_id;
mvm_ap_sta->sta_id = old_ap_sta_id;
@ -1034,6 +1062,11 @@ int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
return ret;
}
int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
{
return __iwl_mvm_suspend(hw, wowlan, false);
}
static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,
struct ieee80211_vif *vif)
{
@ -1238,9 +1271,8 @@ static void iwl_mvm_read_d3_sram(struct iwl_mvm *mvm)
#endif
}
int iwl_mvm_resume(struct ieee80211_hw *hw)
static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)
{
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
struct iwl_d3_iter_data resume_iter_data = {
.mvm = mvm,
};
@ -1260,7 +1292,7 @@ int iwl_mvm_resume(struct ieee80211_hw *hw)
vif = resume_iter_data.vif;
ret = iwl_trans_d3_resume(mvm->trans, &d3_status);
ret = iwl_trans_d3_resume(mvm->trans, &d3_status, test);
if (ret)
goto out_unlock;
@ -1277,7 +1309,7 @@ int iwl_mvm_resume(struct ieee80211_hw *hw)
out_unlock:
mutex_unlock(&mvm->mutex);
if (vif)
if (!test && vif)
ieee80211_resume_disconnect(vif);
/* return 1 to reconfigure the device */
@ -1285,9 +1317,106 @@ int iwl_mvm_resume(struct ieee80211_hw *hw)
return 1;
}
int iwl_mvm_resume(struct ieee80211_hw *hw)
{
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
return __iwl_mvm_resume(mvm, false);
}
void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled)
{
struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
device_set_wakeup_enable(mvm->trans->dev, enabled);
}
#ifdef CONFIG_IWLWIFI_DEBUGFS
static int iwl_mvm_d3_test_open(struct inode *inode, struct file *file)
{
struct iwl_mvm *mvm = inode->i_private;
int err;
if (mvm->d3_test_active)
return -EBUSY;
file->private_data = inode->i_private;
ieee80211_stop_queues(mvm->hw);
synchronize_net();
/* start pseudo D3 */
rtnl_lock();
err = __iwl_mvm_suspend(mvm->hw, mvm->hw->wiphy->wowlan_config, true);
rtnl_unlock();
if (err > 0)
err = -EINVAL;
if (err) {
ieee80211_wake_queues(mvm->hw);
return err;
}
mvm->d3_test_active = true;
return 0;
}
static ssize_t iwl_mvm_d3_test_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct iwl_mvm *mvm = file->private_data;
u32 pme_asserted;
while (true) {
pme_asserted = iwl_trans_read_mem32(mvm->trans,
mvm->d3_test_pme_ptr);
if (pme_asserted)
break;
if (msleep_interruptible(100))
break;
}
return 0;
}
static void iwl_mvm_d3_test_disconn_work_iter(void *_data, u8 *mac,
struct ieee80211_vif *vif)
{
if (vif->type == NL80211_IFTYPE_STATION)
ieee80211_connection_loss(vif);
}
static int iwl_mvm_d3_test_release(struct inode *inode, struct file *file)
{
struct iwl_mvm *mvm = inode->i_private;
int remaining_time = 10;
mvm->d3_test_active = false;
__iwl_mvm_resume(mvm, true);
iwl_abort_notification_waits(&mvm->notif_wait);
ieee80211_restart_hw(mvm->hw);
/* wait for restart and disconnect all interfaces */
while (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) &&
remaining_time > 0) {
remaining_time--;
msleep(1000);
}
if (remaining_time == 0)
IWL_ERR(mvm, "Timed out waiting for HW restart to finish!\n");
ieee80211_iterate_active_interfaces_atomic(
mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
iwl_mvm_d3_test_disconn_work_iter, NULL);
ieee80211_wake_queues(mvm->hw);
return 0;
}
const struct file_operations iwl_dbgfs_d3_test_ops = {
.llseek = no_llseek,
.open = iwl_mvm_d3_test_open,
.read = iwl_mvm_d3_test_read,
.release = iwl_mvm_d3_test_release,
};
#endif

View File

@ -938,6 +938,7 @@ int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir)
MVM_DEBUGFS_ADD_FILE(fw_restart, mvm->debugfs_dir, S_IWUSR);
#ifdef CONFIG_PM_SLEEP
MVM_DEBUGFS_ADD_FILE(d3_sram, mvm->debugfs_dir, S_IRUSR | S_IWUSR);
MVM_DEBUGFS_ADD_FILE(d3_test, mvm->debugfs_dir, S_IRUSR);
#endif
/*

View File

@ -459,8 +459,10 @@ struct iwl_mvm {
#ifdef CONFIG_PM_SLEEP
int gtk_ivlen, gtk_icvlen, ptk_ivlen, ptk_icvlen;
#ifdef CONFIG_IWLWIFI_DEBUGFS
bool d3_test_active;
bool store_d3_resume_sram;
void *d3_resume_sram;
u32 d3_test_pme_ptr;
#endif
#endif
@ -669,6 +671,7 @@ void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw,
struct inet6_dev *idev);
void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw,
struct ieee80211_vif *vif, int idx);
extern const struct file_operations iwl_dbgfs_d3_test_ops;
/* BT Coex */
int iwl_send_bt_prio_tbl(struct iwl_mvm *mvm);

View File

@ -204,7 +204,8 @@ int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt,
{
int ret;
WARN_ON(ctxt->ref);
WARN_ON(!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) &&
ctxt->ref);
lockdep_assert_held(&mvm->mutex);
ctxt->channel = chandef->chan;

View File

@ -76,6 +76,11 @@ int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd)
{
int ret;
#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP)
if (WARN_ON(mvm->d3_test_active))
return -EIO;
#endif
/*
* Synchronous commands from this op-mode must hold
* the mutex, this ensures we don't try to send two
@ -125,6 +130,11 @@ int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd,
lockdep_assert_held(&mvm->mutex);
#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP)
if (WARN_ON(mvm->d3_test_active))
return -EIO;
#endif
/*
* Only synchronous commands can wait for status,
* we use WANT_SKB so the caller can't.

View File

@ -578,9 +578,17 @@ static void iwl_trans_pcie_stop_device(struct iwl_trans *trans)
clear_bit(STATUS_RFKILL, &trans_pcie->status);
}
static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans)
static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test)
{
iwl_disable_interrupts(trans);
/*
* in testing mode, the host stays awake and the
* hardware won't be reset (not even partially)
*/
if (test)
return;
iwl_pcie_disable_ict(trans);
iwl_clear_bit(trans, CSR_GP_CNTRL,
@ -599,11 +607,18 @@ static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans)
}
static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans,
enum iwl_d3_status *status)
enum iwl_d3_status *status,
bool test)
{
u32 val;
int ret;
if (test) {
iwl_enable_interrupts(trans);
*status = IWL_D3_STATUS_ALIVE;
return 0;
}
iwl_pcie_set_pwr(trans, false);
val = iwl_read32(trans, CSR_RESET);