iwlwifi: dvm: query and report WoWLAN wakeup reason
authorJohannes Berg <johannes.berg@intel.com>
Tue, 8 Jan 2013 10:29:12 +0000 (11:29 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 12 Feb 2013 15:52:25 +0000 (16:52 +0100)
Implement proper WoWLAN wakeup and query the wakeup
reasons, then report them to userspace.

Note that this is tricky: a firmware bug (that has
been fixed in later versions) means that the status
command response isn't properly closed in hardware
and thus won't arrive at the host. Sending another
command after it closes the status response but the
next command gets stuck, etc. We reset the device
after querying though, so this is not a big issue,
just makes for strange code.

Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/iwlwifi/dvm/commands.h
drivers/net/wireless/iwlwifi/dvm/mac80211.c

index 8bce4b0..02c9ebb 100644 (file)
@@ -3897,6 +3897,24 @@ struct iwlagn_wowlan_kek_kck_material_cmd {
        __le64  replay_ctr;
 } __packed;
 
+#define RF_KILL_INDICATOR_FOR_WOWLAN   0x87
+
+/*
+ * REPLY_WOWLAN_GET_STATUS = 0xe5
+ */
+struct iwlagn_wowlan_status {
+       __le64 replay_ctr;
+       __le32 rekey_status;
+       __le32 wakeup_reason;
+       u8 pattern_number;
+       u8 reserved1;
+       __le16 qos_seq_ctr[8];
+       __le16 non_qos_seq_ctr;
+       __le16 reserved2;
+       union iwlagn_all_tsc_rsc tsc_rsc;
+       __le16 reserved3;
+} __packed;
+
 /*
  * REPLY_WIPAN_PARAMS = 0xb2 (Commands and Notification)
  */
index 75f6f6c..ebbfcf4 100644 (file)
@@ -442,52 +442,154 @@ static int iwlagn_mac_suspend(struct ieee80211_hw *hw,
        return ret;
 }
 
+struct iwl_resume_data {
+       struct iwl_priv *priv;
+       struct iwlagn_wowlan_status *cmd;
+       bool valid;
+};
+
+static bool iwl_resume_status_fn(struct iwl_notif_wait_data *notif_wait,
+                                struct iwl_rx_packet *pkt, void *data)
+{
+       struct iwl_resume_data *resume_data = data;
+       struct iwl_priv *priv = resume_data->priv;
+       u32 len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK;
+
+       if (len - 4 != sizeof(*resume_data->cmd)) {
+               IWL_ERR(priv, "rx wrong size data\n");
+               return true;
+       }
+       memcpy(resume_data->cmd, pkt->data, sizeof(*resume_data->cmd));
+       resume_data->valid = true;
+
+       return true;
+}
+
 static int iwlagn_mac_resume(struct ieee80211_hw *hw)
 {
        struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
        struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS];
        struct ieee80211_vif *vif;
-       unsigned long flags;
-       u32 base, status = 0xffffffff;
-       int ret = -EIO;
+       u32 base;
+       int ret;
+       enum iwl_d3_status d3_status;
+       struct error_table_start {
+               /* cf. struct iwl_error_event_table */
+               u32 valid;
+               u32 error_id;
+       } err_info;
+       struct iwl_notification_wait status_wait;
+       static const u8 status_cmd[] = {
+               REPLY_WOWLAN_GET_STATUS,
+       };
+       struct iwlagn_wowlan_status status_data = {};
+       struct iwl_resume_data resume_data = {
+               .priv = priv,
+               .cmd = &status_data,
+               .valid = false,
+       };
+       struct cfg80211_wowlan_wakeup wakeup = {
+               .pattern_idx = -1,
+       };
+#ifdef CONFIG_IWLWIFI_DEBUGFS
+       const struct fw_img *img;
+#endif
 
        IWL_DEBUG_MAC80211(priv, "enter\n");
        mutex_lock(&priv->mutex);
 
-       iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR,
-                         CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE);
+       /* we'll clear ctx->vif during iwlagn_prepare_restart() */
+       vif = ctx->vif;
+
+       ret = iwl_trans_d3_resume(priv->trans, &d3_status);
+       if (ret)
+               goto out_unlock;
+
+       if (d3_status != IWL_D3_STATUS_ALIVE) {
+               IWL_INFO(priv, "Device was reset during suspend\n");
+               goto out_unlock;
+       }
 
        base = priv->device_pointers.error_event_table;
-       if (iwlagn_hw_valid_rtc_data_addr(base)) {
-               if (iwl_trans_grab_nic_access(priv->trans, true, &flags)) {
-                       iwl_write32(priv->trans, HBUS_TARG_MEM_RADDR, base);
-                       status = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT);
-                       iwl_trans_release_nic_access(priv->trans, &flags);
-                       ret = 0;
+       if (!iwlagn_hw_valid_rtc_data_addr(base)) {
+               IWL_WARN(priv, "Invalid error table during resume!\n");
+               goto out_unlock;
+       }
+
+       iwl_trans_read_mem_bytes(priv->trans, base,
+                                &err_info, sizeof(err_info));
+
+       if (err_info.valid) {
+               IWL_INFO(priv, "error table is valid (%d, 0x%x)\n",
+                        err_info.valid, err_info.error_id);
+               if (err_info.error_id == RF_KILL_INDICATOR_FOR_WOWLAN) {
+                       wakeup.rfkill_release = true;
+                       ieee80211_report_wowlan_wakeup(vif, &wakeup,
+                                                      GFP_KERNEL);
                }
+               goto out_unlock;
+       }
 
 #ifdef CONFIG_IWLWIFI_DEBUGFS
-               if (ret == 0) {
-                       const struct fw_img *img;
-
-                       img = &(priv->fw->img[IWL_UCODE_WOWLAN]);
-                       if (!priv->wowlan_sram) {
-                               priv->wowlan_sram =
-                                  kzalloc(img->sec[IWL_UCODE_SECTION_DATA].len,
-                                               GFP_KERNEL);
-                       }
+       img = &priv->fw->img[IWL_UCODE_WOWLAN];
+       if (!priv->wowlan_sram)
+               priv->wowlan_sram =
+                       kzalloc(img->sec[IWL_UCODE_SECTION_DATA].len,
+                               GFP_KERNEL);
+
+       if (priv->wowlan_sram)
+               iwl_trans_read_mem(priv->trans, 0x800000,
+                                  priv->wowlan_sram,
+                                  img->sec[IWL_UCODE_SECTION_DATA].len / 4);
+#endif
 
-                       if (priv->wowlan_sram)
-                               iwl_trans_read_mem(
-                                     priv->trans, 0x800000,
-                                     priv->wowlan_sram,
-                                     img->sec[IWL_UCODE_SECTION_DATA].len / 4);
+       /*
+        * This is very strange. The GET_STATUS command is sent but the device
+        * doesn't reply properly, it seems it doesn't close the RBD so one is
+        * always left open ... As a result, we need to send another command
+        * and have to reset the driver afterwards. As we need to switch to
+        * runtime firmware again that'll happen.
+        */
+
+       iwl_init_notification_wait(&priv->notif_wait, &status_wait, status_cmd,
+                                  ARRAY_SIZE(status_cmd), iwl_resume_status_fn,
+                                  &resume_data);
+
+       iwl_dvm_send_cmd_pdu(priv, REPLY_WOWLAN_GET_STATUS, CMD_ASYNC, 0, NULL);
+       iwl_dvm_send_cmd_pdu(priv, REPLY_ECHO, CMD_ASYNC, 0, NULL);
+       /* an RBD is left open in the firmware now! */
+
+       ret = iwl_wait_notification(&priv->notif_wait, &status_wait, HZ/5);
+       if (ret)
+               goto out_unlock;
+
+       if (resume_data.valid && priv->contexts[IWL_RXON_CTX_BSS].vif) {
+               u32 reasons = le32_to_cpu(status_data.wakeup_reason);
+               struct cfg80211_wowlan_wakeup *wakeup_report;
+
+               IWL_INFO(priv, "WoWLAN wakeup reason(s): 0x%.8x\n", reasons);
+
+               if (reasons) {
+                       if (reasons & IWLAGN_WOWLAN_WAKEUP_MAGIC_PACKET)
+                               wakeup.magic_pkt = true;
+                       if (reasons & IWLAGN_WOWLAN_WAKEUP_PATTERN_MATCH)
+                               wakeup.pattern_idx = status_data.pattern_number;
+                       if (reasons & (IWLAGN_WOWLAN_WAKEUP_BEACON_MISS |
+                                      IWLAGN_WOWLAN_WAKEUP_LINK_CHANGE))
+                               wakeup.disconnect = true;
+                       if (reasons & IWLAGN_WOWLAN_WAKEUP_GTK_REKEY_FAIL)
+                               wakeup.gtk_rekey_failure = true;
+                       if (reasons & IWLAGN_WOWLAN_WAKEUP_EAP_IDENT_REQ)
+                               wakeup.eap_identity_req = true;
+                       if (reasons & IWLAGN_WOWLAN_WAKEUP_4WAY_HANDSHAKE)
+                               wakeup.four_way_handshake = true;
+                       wakeup_report = &wakeup;
+               } else {
+                       wakeup_report = NULL;
                }
-#endif
-       }
 
-       /* we'll clear ctx->vif during iwlagn_prepare_restart() */
-       vif = ctx->vif;
+               ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL);
+       }
 
        priv->wowlan = false;
 
@@ -497,6 +599,7 @@ static int iwlagn_mac_resume(struct ieee80211_hw *hw)
        iwl_connection_init_rx_config(priv, ctx);
        iwlagn_set_rxon_chain(priv, ctx);
 
+ out_unlock:
        mutex_unlock(&priv->mutex);
        IWL_DEBUG_MAC80211(priv, "leave\n");