|
@@ -1644,6 +1644,115 @@ static int ath6kl_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow)
|
|
|
+{
|
|
|
+ struct ath6kl_vif *vif;
|
|
|
+ int ret, pos, left;
|
|
|
+ u32 filter = 0;
|
|
|
+ u16 i;
|
|
|
+ u8 mask[WOW_MASK_SIZE];
|
|
|
+
|
|
|
+ vif = ath6kl_vif_first(ar);
|
|
|
+ if (!vif)
|
|
|
+ return -EIO;
|
|
|
+
|
|
|
+ if (!ath6kl_cfg80211_ready(vif))
|
|
|
+ return -EIO;
|
|
|
+
|
|
|
+ if (!test_bit(CONNECTED, &vif->flags))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Clear existing WOW patterns */
|
|
|
+ for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++)
|
|
|
+ ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx,
|
|
|
+ WOW_LIST_ID, i);
|
|
|
+ /* Configure new WOW patterns */
|
|
|
+ for (i = 0; i < wow->n_patterns; i++) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Convert given nl80211 specific mask value to equivalent
|
|
|
+ * driver specific mask value and send it to the chip along
|
|
|
+ * with patterns. For example, If the mask value defined in
|
|
|
+ * struct cfg80211_wowlan is 0xA (equivalent binary is 1010),
|
|
|
+ * then equivalent driver specific mask value is
|
|
|
+ * "0xFF 0x00 0xFF 0x00".
|
|
|
+ */
|
|
|
+ memset(&mask, 0, sizeof(mask));
|
|
|
+ for (pos = 0; pos < wow->patterns[i].pattern_len; pos++) {
|
|
|
+ if (wow->patterns[i].mask[pos / 8] & (0x1 << (pos % 8)))
|
|
|
+ mask[pos] = 0xFF;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * Note: Pattern's offset is not passed as part of wowlan
|
|
|
+ * parameter from CFG layer. So it's always passed as ZERO
|
|
|
+ * to the firmware. It means, given WOW patterns are always
|
|
|
+ * matched from the first byte of received pkt in the firmware.
|
|
|
+ */
|
|
|
+ ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi,
|
|
|
+ vif->fw_vif_idx, WOW_LIST_ID,
|
|
|
+ wow->patterns[i].pattern_len,
|
|
|
+ 0 /* pattern offset */,
|
|
|
+ wow->patterns[i].pattern, mask);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (wow->disconnect)
|
|
|
+ filter |= WOW_FILTER_OPTION_NWK_DISASSOC;
|
|
|
+
|
|
|
+ if (wow->magic_pkt)
|
|
|
+ filter |= WOW_FILTER_OPTION_MAGIC_PACKET;
|
|
|
+
|
|
|
+ if (wow->gtk_rekey_failure)
|
|
|
+ filter |= WOW_FILTER_OPTION_GTK_ERROR;
|
|
|
+
|
|
|
+ if (wow->eap_identity_req)
|
|
|
+ filter |= WOW_FILTER_OPTION_EAP_REQ;
|
|
|
+
|
|
|
+ if (wow->four_way_handshake)
|
|
|
+ filter |= WOW_FILTER_OPTION_8021X_4WAYHS;
|
|
|
+
|
|
|
+ ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
|
+ ATH6KL_WOW_MODE_ENABLE,
|
|
|
+ filter,
|
|
|
+ WOW_HOST_REQ_DELAY);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
|
+ ATH6KL_HOST_MODE_ASLEEP);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ if (ar->tx_pending[ar->ctrl_ep]) {
|
|
|
+ left = wait_event_interruptible_timeout(ar->event_wq,
|
|
|
+ ar->tx_pending[ar->ctrl_ep] == 0, WMI_TIMEOUT);
|
|
|
+ if (left == 0) {
|
|
|
+ ath6kl_warn("clear wmi ctrl data timeout\n");
|
|
|
+ ret = -ETIMEDOUT;
|
|
|
+ } else if (left < 0) {
|
|
|
+ ath6kl_warn("clear wmi ctrl data failed: %d\n", left);
|
|
|
+ ret = left;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int ath6kl_wow_resume(struct ath6kl *ar)
|
|
|
+{
|
|
|
+ struct ath6kl_vif *vif;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ vif = ath6kl_vif_first(ar);
|
|
|
+ if (!vif)
|
|
|
+ return -EIO;
|
|
|
+
|
|
|
+ ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
|
|
|
+ ATH6KL_HOST_MODE_AWAKE);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
int ath6kl_cfg80211_suspend(struct ath6kl *ar,
|
|
|
enum ath6kl_cfg_suspend_mode mode)
|
|
|
{
|