iwlwifi: mvm: implement D3 testing
authorJohannes Berg <johannes.berg@intel.com>
Tue, 14 May 2013 11:53:45 +0000 (13:53 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 29 May 2013 19:56:57 +0000 (21:56 +0200)
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>
drivers/net/wireless/iwlwifi/dvm/mac80211.c
drivers/net/wireless/iwlwifi/iwl-trans.h
drivers/net/wireless/iwlwifi/mvm/d3.c
drivers/net/wireless/iwlwifi/mvm/debugfs.c
drivers/net/wireless/iwlwifi/mvm/mvm.h
drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c
drivers/net/wireless/iwlwifi/mvm/utils.c
drivers/net/wireless/iwlwifi/pcie/trans.c

index ac7ed3f809a0b7a7603329cd2f2f874098356719..c0039a992909ec29b65a4ee7de331e49404591dc 100644 (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;
 
index 84f1c8dc97413fa3fe5c2cb48f33bfcc9c031505..be4b2ac3dbbf008dbe872d11359bbc5bcc55d2a5 100644 (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,
index 4d3c978b5c76a08c2f8ee0f18c1015ff81f8fe5a..7a2ef3f013fdcf06a51e204bb356894016ba89cc 100644 (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
index 69e0806075a22cd739f8a6807d21009d9e89afdd..b7643c16201f67ca44779c3d040fef0184824f9b 100644 (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
 
        /*
index 6a3220b6273ed052c3a22e9186a6c52525a5a123..384259baf52552041c4793e315a75f9d1a65527b 100644 (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);
index 1b4db25d53fbab1272b0b6d1bbdf145349121502..a8652ddd6bedb54ed7bd045d44fc414c618b5ce9 100644 (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;
index c9b44ab4af07ac05dc173813c95e80429101765a..1e1332839e4a745b2a57f9d837b112fff52d75bd 100644 (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.
index 0b021305eedf121149072fecb6ad9f3d039bfd06..197dbe0a868c5c1a97e8a7c386c5a2ce009038e7 100644 (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);
This page took 0.034904 seconds and 5 git commands to generate.