]> rtime.felk.cvut.cz Git - can-eth-gw-linux.git/blobdiff - drivers/net/wireless/ath/ath6kl/cfg80211.c
Merge remote branch 'wireless-next/master' into ath6kl-next
[can-eth-gw-linux.git] / drivers / net / wireless / ath / ath6kl / cfg80211.c
index 02526044acb925d4f360d2e6504e6326087f0c2a..6c59a217b1a133f390d1dd2f444494110660b9f7 100644 (file)
 #include "testmode.h"
 
 static unsigned int ath6kl_p2p;
-static unsigned int multi_norm_if_support;
 
 module_param(ath6kl_p2p, uint, 0644);
-module_param(multi_norm_if_support, uint, 0644);
 
 #define RATETAB_ENT(_rate, _rateid, _flags) {   \
        .bitrate    = (_rate),                  \
@@ -127,6 +125,37 @@ static struct ieee80211_supported_band ath6kl_band_5ghz = {
 
 #define CCKM_KRK_CIPHER_SUITE 0x004096ff /* use for KRK */
 
+/* returns true if scheduled scan was stopped */
+static bool __ath6kl_cfg80211_sscan_stop(struct ath6kl_vif *vif)
+{
+       struct ath6kl *ar = vif->ar;
+
+       if (ar->state != ATH6KL_STATE_SCHED_SCAN)
+               return false;
+
+       del_timer_sync(&vif->sched_scan_timer);
+
+       ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
+                                          ATH6KL_HOST_MODE_AWAKE);
+
+       ar->state = ATH6KL_STATE_ON;
+
+       return true;
+}
+
+static void ath6kl_cfg80211_sscan_disable(struct ath6kl_vif *vif)
+{
+       struct ath6kl *ar = vif->ar;
+       bool stopped;
+
+       stopped = __ath6kl_cfg80211_sscan_stop(vif);
+
+       if (!stopped)
+               return;
+
+       cfg80211_sched_scan_stopped(ar->wiphy);
+}
+
 static int ath6kl_set_wpa_version(struct ath6kl_vif *vif,
                                  enum nl80211_wpa_versions wpa_version)
 {
@@ -205,6 +234,10 @@ static int ath6kl_set_cipher(struct ath6kl_vif *vif, u32 cipher, bool ucast)
                *ar_cipher = AES_CRYPT;
                *ar_cipher_len = 0;
                break;
+       case WLAN_CIPHER_SUITE_SMS4:
+               *ar_cipher = WAPI_CRYPT;
+               *ar_cipher_len = 0;
+               break;
        default:
                ath6kl_err("cipher 0x%x not supported\n", cipher);
                return -ENOTSUPP;
@@ -355,7 +388,7 @@ static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type,
 
        if (type == NL80211_IFTYPE_STATION ||
            type == NL80211_IFTYPE_AP || type == NL80211_IFTYPE_ADHOC) {
-               for (i = 0; i < MAX_NUM_VIF; i++) {
+               for (i = 0; i < ar->vif_max; i++) {
                        if ((ar->avail_idx_map >> i) & BIT(0)) {
                                *if_idx = i;
                                return true;
@@ -365,7 +398,7 @@ static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type,
 
        if (type == NL80211_IFTYPE_P2P_CLIENT ||
            type == NL80211_IFTYPE_P2P_GO) {
-               for (i = ar->max_norm_iface; i < MAX_NUM_VIF; i++) {
+               for (i = ar->max_norm_iface; i < ar->vif_max; i++) {
                        if ((ar->avail_idx_map >> i) & BIT(0)) {
                                *if_idx = i;
                                return true;
@@ -382,6 +415,9 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
        struct ath6kl *ar = ath6kl_priv(dev);
        struct ath6kl_vif *vif = netdev_priv(dev);
        int status;
+       u8 nw_subtype = (ar->p2p) ? SUBTYPE_P2PDEV : SUBTYPE_NONE;
+
+       ath6kl_cfg80211_sscan_disable(vif);
 
        vif->sme_state = SME_CONNECTING;
 
@@ -427,9 +463,12 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
 
        if (sme->ie && (sme->ie_len > 0)) {
                status = ath6kl_set_assoc_req_ies(vif, sme->ie, sme->ie_len);
-               if (status)
+               if (status) {
+                       up(&ar->sem);
                        return status;
-       }
+               }
+       } else
+               ar->connect_ctrl_flags &= ~CONNECT_WPS_FLAG;
 
        if (test_bit(CONNECTED, &vif->flags) &&
            vif->ssid_len == sme->ssid_len &&
@@ -519,6 +558,9 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
 
        vif->nw_type = vif->next_mode;
 
+       if (vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT)
+               nw_subtype = SUBTYPE_P2PCLIENT;
+
        ath6kl_dbg(ATH6KL_DBG_WLAN_CFG,
                   "%s: connect called with authmode %d dot11 auth %d"
                   " PW crypto %d PW crypto len %d GRP crypto %d"
@@ -536,7 +578,7 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
                                        vif->grp_crypto, vif->grp_crypto_len,
                                        vif->ssid_len, vif->ssid,
                                        vif->req_bssid, vif->ch_hint,
-                                       ar->connect_ctrl_flags);
+                                       ar->connect_ctrl_flags, nw_subtype);
 
        up(&ar->sem);
 
@@ -563,17 +605,28 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
        return 0;
 }
 
-static int ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, const u8 *bssid,
+static int ath6kl_add_bss_if_needed(struct ath6kl_vif *vif,
+                                   enum network_type nw_type,
+                                   const u8 *bssid,
                                    struct ieee80211_channel *chan,
                                    const u8 *beacon_ie, size_t beacon_ie_len)
 {
        struct ath6kl *ar = vif->ar;
        struct cfg80211_bss *bss;
+       u16 cap_mask, cap_val;
        u8 *ie;
 
+       if (nw_type & ADHOC_NETWORK) {
+               cap_mask = WLAN_CAPABILITY_IBSS;
+               cap_val = WLAN_CAPABILITY_IBSS;
+       } else {
+               cap_mask = WLAN_CAPABILITY_ESS;
+               cap_val = WLAN_CAPABILITY_ESS;
+       }
+
        bss = cfg80211_get_bss(ar->wiphy, chan, bssid,
-                              vif->ssid, vif->ssid_len, WLAN_CAPABILITY_ESS,
-                              WLAN_CAPABILITY_ESS);
+                              vif->ssid, vif->ssid_len,
+                              cap_mask, cap_val);
        if (bss == NULL) {
                /*
                 * Since cfg80211 may not yet know about the BSS,
@@ -591,13 +644,12 @@ static int ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, const u8 *bssid,
                memcpy(ie + 2, vif->ssid, vif->ssid_len);
                memcpy(ie + 2 + vif->ssid_len, beacon_ie, beacon_ie_len);
                bss = cfg80211_inform_bss(ar->wiphy, chan,
-                                         bssid, 0, WLAN_CAPABILITY_ESS, 100,
+                                         bssid, 0, cap_val, 100,
                                          ie, 2 + vif->ssid_len + beacon_ie_len,
                                          0, GFP_KERNEL);
                if (bss)
-                       ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "added dummy bss for "
-                                  "%pM prior to indicating connect/roamed "
-                                  "event\n", bssid);
+                       ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "added bss %pM to "
+                                  "cfg80211\n", bssid);
                kfree(ie);
        } else
                ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "cfg80211 already has a bss "
@@ -660,16 +712,16 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel,
 
        chan = ieee80211_get_channel(ar->wiphy, (int) channel);
 
-
-       if (nw_type & ADHOC_NETWORK) {
-               cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL);
+       if (ath6kl_add_bss_if_needed(vif, nw_type, bssid, chan, assoc_info,
+                                    beacon_ie_len) < 0) {
+               ath6kl_err("could not add cfg80211 bss entry\n");
                return;
        }
 
-       if (ath6kl_add_bss_if_needed(vif, bssid, chan, assoc_info,
-                                    beacon_ie_len) < 0) {
-               ath6kl_err("could not add cfg80211 bss entry for "
-                          "connect/roamed notification\n");
+       if (nw_type & ADHOC_NETWORK) {
+               ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "ad-hoc %s selected\n",
+                          nw_type & ADHOC_CREATOR ? "creator" : "joiner");
+               cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL);
                return;
        }
 
@@ -691,12 +743,14 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel,
 static int ath6kl_cfg80211_disconnect(struct wiphy *wiphy,
                                      struct net_device *dev, u16 reason_code)
 {
-       struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(dev);
+       struct ath6kl *ar = ath6kl_priv(dev);
        struct ath6kl_vif *vif = netdev_priv(dev);
 
        ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: reason=%u\n", __func__,
                   reason_code);
 
+       ath6kl_cfg80211_sscan_disable(vif);
+
        if (!ath6kl_cfg80211_ready(vif))
                return -EIO;
 
@@ -789,7 +843,7 @@ void ath6kl_cfg80211_disconnect_event(struct ath6kl_vif *vif, u8 reason,
 static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
                                struct cfg80211_scan_request *request)
 {
-       struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
+       struct ath6kl *ar = ath6kl_priv(ndev);
        struct ath6kl_vif *vif = netdev_priv(ndev);
        s8 n_channels = 0;
        u16 *channels = NULL;
@@ -799,6 +853,8 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
        if (!ath6kl_cfg80211_ready(vif))
                return -EIO;
 
+       ath6kl_cfg80211_sscan_disable(vif);
+
        if (!ar->usr_bss_filter) {
                clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags);
                ret = ath6kl_wmi_bssfilter_cmd(
@@ -824,6 +880,10 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
                                                  request->ssids[i].ssid);
        }
 
+       /*
+        * FIXME: we should clear the IE in fw if it's not set so just
+        * remove the check altogether
+        */
        if (request->ie) {
                ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
                                               WMI_FRAME_PROBE_REQ,
@@ -860,9 +920,25 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
        if (test_bit(CONNECTED, &vif->flags))
                force_fg_scan = 1;
 
-       ret = ath6kl_wmi_startscan_cmd(ar->wmi, vif->fw_vif_idx, WMI_LONG_SCAN,
-                                      force_fg_scan, false, 0, 0, n_channels,
-                                      channels);
+       if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX,
+                   ar->fw_capabilities)) {
+               /*
+                * If capable of doing P2P mgmt operations using
+                * station interface, send additional information like
+                * supported rates to advertise and xmit rates for
+                * probe requests
+                */
+               ret = ath6kl_wmi_beginscan_cmd(ar->wmi, vif->fw_vif_idx,
+                                               WMI_LONG_SCAN, force_fg_scan,
+                                               false, 0, 0, n_channels,
+                                               channels, request->no_cck,
+                                               request->rates);
+       } else {
+               ret = ath6kl_wmi_startscan_cmd(ar->wmi, vif->fw_vif_idx,
+                                               WMI_LONG_SCAN, force_fg_scan,
+                                               false, 0, 0, n_channels,
+                                               channels);
+       }
        if (ret)
                ath6kl_err("wmi_startscan_cmd failed\n");
        else
@@ -905,7 +981,7 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
                                   const u8 *mac_addr,
                                   struct key_params *params)
 {
-       struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
+       struct ath6kl *ar = ath6kl_priv(ndev);
        struct ath6kl_vif *vif = netdev_priv(ndev);
        struct ath6kl_key *key = NULL;
        u8 key_usage;
@@ -937,13 +1013,19 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
                key_usage = GROUP_USAGE;
 
        if (params) {
+               int seq_len = params->seq_len;
+               if (params->cipher == WLAN_CIPHER_SUITE_SMS4 &&
+                   seq_len > ATH6KL_KEY_SEQ_LEN) {
+                       /* Only first half of the WPI PN is configured */
+                       seq_len = ATH6KL_KEY_SEQ_LEN;
+               }
                if (params->key_len > WLAN_MAX_KEY_LEN ||
-                   params->seq_len > sizeof(key->seq))
+                   seq_len > sizeof(key->seq))
                        return -EINVAL;
 
                key->key_len = params->key_len;
                memcpy(key->key, params->key, key->key_len);
-               key->seq_len = params->seq_len;
+               key->seq_len = seq_len;
                memcpy(key->seq, params->seq, key->seq_len);
                key->cipher = params->cipher;
        }
@@ -961,6 +1043,9 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
        case WLAN_CIPHER_SUITE_CCMP:
                key_type = AES_CRYPT;
                break;
+       case WLAN_CIPHER_SUITE_SMS4:
+               key_type = WAPI_CRYPT;
+               break;
 
        default:
                return -ENOTSUPP;
@@ -976,10 +1061,9 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
                   __func__, key_index, key->key_len, key_type,
                   key_usage, key->seq_len);
 
-       vif->def_txkey_index = key_index;
-
        if (vif->nw_type == AP_NETWORK && !pairwise &&
-           (key_type == TKIP_CRYPT || key_type == AES_CRYPT) && params) {
+           (key_type == TKIP_CRYPT || key_type == AES_CRYPT ||
+            key_type == WAPI_CRYPT) && params) {
                ar->ap_mode_bkey.valid = true;
                ar->ap_mode_bkey.key_index = key_index;
                ar->ap_mode_bkey.key_type = key_type;
@@ -1012,8 +1096,7 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
                return 0;
        }
 
-       return ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx,
-                                    vif->def_txkey_index,
+       return ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, key_index,
                                     key_type, key_usage, key->key_len,
                                     key->seq, key->seq_len, key->key,
                                     KEY_OP_INIT_VAL,
@@ -1024,7 +1107,7 @@ static int ath6kl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev,
                                   u8 key_index, bool pairwise,
                                   const u8 *mac_addr)
 {
-       struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
+       struct ath6kl *ar = ath6kl_priv(ndev);
        struct ath6kl_vif *vif = netdev_priv(ndev);
 
        ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index);
@@ -1090,7 +1173,7 @@ static int ath6kl_cfg80211_set_default_key(struct wiphy *wiphy,
                                           u8 key_index, bool unicast,
                                           bool multicast)
 {
-       struct ath6kl *ar = (struct ath6kl *)ath6kl_priv(ndev);
+       struct ath6kl *ar = ath6kl_priv(ndev);
        struct ath6kl_vif *vif = netdev_priv(ndev);
        struct ath6kl_key *key = NULL;
        u8 key_usage;
@@ -1181,11 +1264,12 @@ static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
 */
 static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy,
                                       enum nl80211_tx_power_setting type,
-                                      int dbm)
+                                      int mbm)
 {
        struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
        struct ath6kl_vif *vif;
        u8 ath6kl_dbm;
+       int dbm = MBM_TO_DBM(mbm);
 
        ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x, dbm %d\n", __func__,
                   type, dbm);
@@ -1288,7 +1372,7 @@ static struct net_device *ath6kl_cfg80211_add_iface(struct wiphy *wiphy,
        struct net_device *ndev;
        u8 if_idx, nw_type;
 
-       if (ar->num_vif == MAX_NUM_VIF) {
+       if (ar->num_vif == ar->vif_max) {
                ath6kl_err("Reached maximum number of supported vif\n");
                return ERR_PTR(-EINVAL);
        }
@@ -1333,9 +1417,6 @@ static int ath6kl_cfg80211_change_iface(struct wiphy *wiphy,
 
        ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type %u\n", __func__, type);
 
-       if (!ath6kl_cfg80211_ready(vif))
-               return -EIO;
-
        switch (type) {
        case NL80211_IFTYPE_STATION:
                vif->next_mode = INFRA_NETWORK;
@@ -1426,7 +1507,7 @@ static int ath6kl_cfg80211_join_ibss(struct wiphy *wiphy,
                                        vif->grp_crypto, vif->grp_crypto_len,
                                        vif->ssid_len, vif->ssid,
                                        vif->req_bssid, vif->ch_hint,
-                                       ar->connect_ctrl_flags);
+                                       ar->connect_ctrl_flags, SUBTYPE_NONE);
        set_bit(CONNECT_PEND, &vif->flags);
 
        return 0;
@@ -1453,6 +1534,7 @@ static const u32 cipher_suites[] = {
        WLAN_CIPHER_SUITE_TKIP,
        WLAN_CIPHER_SUITE_CCMP,
        CCKM_KRK_CIPHER_SUITE,
+       WLAN_CIPHER_SUITE_SMS4,
 };
 
 static bool is_rate_legacy(s32 rate)
@@ -1779,7 +1861,7 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar,
 
        case ATH6KL_CFG_SUSPEND_DEEPSLEEP:
 
-               ath6kl_cfg80211_stop(ar);
+               ath6kl_cfg80211_stop_all(ar);
 
                /* save the current power mode before enabling power save */
                ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode;
@@ -1796,7 +1878,7 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar,
 
        case ATH6KL_CFG_SUSPEND_CUTPOWER:
 
-               ath6kl_cfg80211_stop(ar);
+               ath6kl_cfg80211_stop_all(ar);
 
                if (ar->state == ATH6KL_STATE_OFF) {
                        ath6kl_dbg(ATH6KL_DBG_SUSPEND,
@@ -1816,6 +1898,13 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar,
 
                break;
 
+       case ATH6KL_CFG_SUSPEND_SCHED_SCAN:
+               /*
+                * Nothing needed for schedule scan, firmware is already in
+                * wow mode and sleeping most of the time.
+                */
+               break;
+
        default:
                break;
        }
@@ -1864,6 +1953,9 @@ int ath6kl_cfg80211_resume(struct ath6kl *ar)
                }
                break;
 
+       case ATH6KL_STATE_SCHED_SCAN:
+               break;
+
        default:
                break;
        }
@@ -1987,7 +2079,7 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev,
        int ies_len;
        struct wmi_connect_cmd p;
        int res;
-       int i;
+       int i, ret;
 
        ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: add=%d\n", __func__, add);
 
@@ -2045,7 +2137,9 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev,
        if (info->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE)
                return -EOPNOTSUPP; /* TODO */
 
-       vif->dot11_auth_mode = OPEN_AUTH;
+       ret = ath6kl_set_auth_type(vif, info->auth_type);
+       if (ret)
+               return ret;
 
        memset(&p, 0, sizeof(p));
 
@@ -2081,6 +2175,9 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev,
                case WLAN_CIPHER_SUITE_CCMP:
                        p.prwise_crypto_type |= AES_CRYPT;
                        break;
+               case WLAN_CIPHER_SUITE_SMS4:
+                       p.prwise_crypto_type |= WAPI_CRYPT;
+                       break;
                }
        }
        if (p.prwise_crypto_type == 0) {
@@ -2100,6 +2197,9 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev,
        case WLAN_CIPHER_SUITE_CCMP:
                p.grp_crypto_type = AES_CRYPT;
                break;
+       case WLAN_CIPHER_SUITE_SMS4:
+               p.grp_crypto_type = WAPI_CRYPT;
+               break;
        default:
                p.grp_crypto_type = NONE_CRYPT;
                break;
@@ -2114,6 +2214,16 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev,
        p.dot11_auth_mode = vif->dot11_auth_mode;
        p.ch = cpu_to_le16(vif->next_chan);
 
+       if (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) {
+               p.nw_subtype = SUBTYPE_P2PGO;
+       } else {
+               /*
+                * Due to firmware limitation, it is not possible to
+                * do P2P mgmt operations in AP mode
+                */
+               p.nw_subtype = SUBTYPE_NONE;
+       }
+
        res = ath6kl_wmi_ap_profile_commit(ar->wmi, vif->fw_vif_idx, &p);
        if (res < 0)
                return res;
@@ -2279,9 +2389,23 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
        }
 
        *cookie = id;
-       return ath6kl_wmi_send_action_cmd(ar->wmi, vif->fw_vif_idx, id,
-                                         chan->center_freq, wait,
-                                         buf, len);
+
+       if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX,
+                   ar->fw_capabilities)) {
+               /*
+                * If capable of doing P2P mgmt operations using
+                * station interface, send additional information like
+                * supported rates to advertise and xmit rates for
+                * probe requests
+                */
+               return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id,
+                                               chan->center_freq, wait,
+                                               buf, len, no_cck);
+       } else {
+               return ath6kl_wmi_send_action_cmd(ar->wmi, vif->fw_vif_idx, id,
+                                                 chan->center_freq, wait,
+                                                 buf, len);
+       }
 }
 
 static void ath6kl_mgmt_frame_register(struct wiphy *wiphy,
@@ -2302,6 +2426,90 @@ static void ath6kl_mgmt_frame_register(struct wiphy *wiphy,
        }
 }
 
+static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy,
+                       struct net_device *dev,
+                       struct cfg80211_sched_scan_request *request)
+{
+       struct ath6kl *ar = ath6kl_priv(dev);
+       struct ath6kl_vif *vif = netdev_priv(dev);
+       u16 interval;
+       int ret;
+       u8 i;
+
+       if (ar->state != ATH6KL_STATE_ON)
+               return -EIO;
+
+       if (vif->sme_state != SME_DISCONNECTED)
+               return -EBUSY;
+
+       for (i = 0; i < ar->wiphy->max_sched_scan_ssids; i++) {
+               ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
+                                         i, DISABLE_SSID_FLAG,
+                                         0, NULL);
+       }
+
+       /* fw uses seconds, also make sure that it's >0 */
+       interval = max_t(u16, 1, request->interval / 1000);
+
+       ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx,
+                                 interval, interval,
+                                 10, 0, 0, 0, 3, 0, 0, 0);
+
+       if (request->n_ssids && request->ssids[0].ssid_len) {
+               for (i = 0; i < request->n_ssids; i++) {
+                       ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx,
+                                                 i, SPECIFIC_SSID_FLAG,
+                                                 request->ssids[i].ssid_len,
+                                                 request->ssids[i].ssid);
+               }
+       }
+
+       ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx,
+                                         ATH6KL_WOW_MODE_ENABLE,
+                                         WOW_FILTER_SSID,
+                                         WOW_HOST_REQ_DELAY);
+       if (ret) {
+               ath6kl_warn("Failed to enable wow with ssid filter: %d\n", ret);
+               return ret;
+       }
+
+       /* this also clears IE in fw if it's not set */
+       ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx,
+                                      WMI_FRAME_PROBE_REQ,
+                                      request->ie, request->ie_len);
+       if (ret) {
+               ath6kl_warn("Failed to set probe request IE for scheduled scan: %d",
+                           ret);
+               return ret;
+       }
+
+       ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx,
+                                                ATH6KL_HOST_MODE_ASLEEP);
+       if (ret) {
+               ath6kl_warn("Failed to enable host sleep mode for sched scan: %d\n",
+                           ret);
+               return ret;
+       }
+
+       ar->state = ATH6KL_STATE_SCHED_SCAN;
+
+       return ret;
+}
+
+static int ath6kl_cfg80211_sscan_stop(struct wiphy *wiphy,
+                                     struct net_device *dev)
+{
+       struct ath6kl_vif *vif = netdev_priv(dev);
+       bool stopped;
+
+       stopped = __ath6kl_cfg80211_sscan_stop(vif);
+
+       if (!stopped)
+               return -EIO;
+
+       return 0;
+}
+
 static const struct ieee80211_txrx_stypes
 ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = {
        [NL80211_IFTYPE_STATION] = {
@@ -2359,25 +2567,17 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = {
        .cancel_remain_on_channel = ath6kl_cancel_remain_on_channel,
        .mgmt_tx = ath6kl_mgmt_tx,
        .mgmt_frame_register = ath6kl_mgmt_frame_register,
+       .sched_scan_start = ath6kl_cfg80211_sscan_start,
+       .sched_scan_stop = ath6kl_cfg80211_sscan_stop,
 };
 
-void ath6kl_cfg80211_stop(struct ath6kl *ar)
+void ath6kl_cfg80211_stop(struct ath6kl_vif *vif)
 {
-       struct ath6kl_vif *vif;
-
-       /* FIXME: for multi vif */
-       vif = ath6kl_vif_first(ar);
-       if (!vif) {
-               /* save the current power mode before enabling power save */
-               ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode;
-
-               if (ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER) != 0)
-                       ath6kl_warn("ath6kl_deep_sleep_enable: "
-                                   "wmi_powermode_cmd failed\n");
-               return;
-       }
+       ath6kl_cfg80211_sscan_disable(vif);
 
        switch (vif->sme_state) {
+       case SME_DISCONNECTED:
+               break;
        case SME_CONNECTING:
                cfg80211_connect_result(vif->ndev, vif->bssid, NULL, 0,
                                        NULL, 0,
@@ -2385,33 +2585,50 @@ void ath6kl_cfg80211_stop(struct ath6kl *ar)
                                        GFP_KERNEL);
                break;
        case SME_CONNECTED:
-       default:
-               /*
-                * FIXME: oddly enough smeState is in DISCONNECTED during
-                * suspend, why? Need to send disconnected event in that
-                * state.
-                */
                cfg80211_disconnected(vif->ndev, 0, NULL, 0, GFP_KERNEL);
                break;
        }
 
        if (test_bit(CONNECTED, &vif->flags) ||
            test_bit(CONNECT_PEND, &vif->flags))
-               ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx);
+               ath6kl_wmi_disconnect_cmd(vif->ar->wmi, vif->fw_vif_idx);
 
        vif->sme_state = SME_DISCONNECTED;
        clear_bit(CONNECTED, &vif->flags);
        clear_bit(CONNECT_PEND, &vif->flags);
 
        /* disable scanning */
-       if (ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, 0xFFFF, 0, 0,
-                                     0, 0, 0, 0, 0, 0, 0) != 0)
-               printk(KERN_WARNING "ath6kl: failed to disable scan "
-                      "during suspend\n");
+       if (ath6kl_wmi_scanparams_cmd(vif->ar->wmi, vif->fw_vif_idx, 0xFFFF,
+                                     0, 0, 0, 0, 0, 0, 0, 0, 0) != 0)
+               ath6kl_warn("failed to disable scan during stop\n");
 
        ath6kl_cfg80211_scan_complete_event(vif, true);
 }
 
+void ath6kl_cfg80211_stop_all(struct ath6kl *ar)
+{
+       struct ath6kl_vif *vif;
+
+       vif = ath6kl_vif_first(ar);
+       if (!vif) {
+               /* save the current power mode before enabling power save */
+               ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode;
+
+               if (ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER) != 0)
+                       ath6kl_warn("ath6kl_deep_sleep_enable: "
+                                   "wmi_powermode_cmd failed\n");
+               return;
+       }
+
+       /*
+        * FIXME: we should take ar->list_lock to protect changes in the
+        * vif_list, but that's not trivial to do as ath6kl_cfg80211_stop()
+        * sleeps.
+        */
+       list_for_each_entry(vif, &ar->vif_list, list)
+               ath6kl_cfg80211_stop(vif);
+}
+
 struct ath6kl *ath6kl_core_alloc(struct device *dev)
 {
        struct ath6kl *ar;
@@ -2427,17 +2644,12 @@ struct ath6kl *ath6kl_core_alloc(struct device *dev)
        }
 
        ar = wiphy_priv(wiphy);
-       if (!multi_norm_if_support)
-               ar->p2p = !!ath6kl_p2p;
+       ar->p2p = !!ath6kl_p2p;
        ar->wiphy = wiphy;
        ar->dev = dev;
 
-       if (multi_norm_if_support)
-               ar->max_norm_iface = 2;
-       else
-               ar->max_norm_iface = 1;
+       ar->vif_max = 1;
 
-       /* FIXME: Remove this once the multivif support is enabled */
        ar->max_norm_iface = 1;
 
        spin_lock_init(&ar->lock);
@@ -2459,9 +2671,6 @@ struct ath6kl *ath6kl_core_alloc(struct device *dev)
        ar->tx_pwr = 0;
 
        ar->intra_bss = 1;
-       memset(&ar->sc_params, 0, sizeof(ar->sc_params));
-       ar->sc_params.short_scan_ratio = WMI_SHORTSCANRATIO_DEFAULT;
-       ar->sc_params.scan_ctrl_flags = DEFAULT_SCAN_CTRL_FLAGS;
        ar->lrssi_roam_threshold = DEF_LRSSI_ROAM_THRESHOLD;
 
        ar->state = ATH6KL_STATE_OFF;
@@ -2522,6 +2731,8 @@ int ath6kl_register_ieee80211_hw(struct ath6kl *ar)
        wiphy->wowlan.pattern_min_len = 1;
        wiphy->wowlan.pattern_max_len = WOW_PATTERN_SIZE;
 
+       wiphy->max_sched_scan_ssids = 10;
+
        ret = wiphy_register(wiphy);
        if (ret < 0) {
                ath6kl_err("couldn't register wiphy device\n");
@@ -2541,6 +2752,9 @@ static int ath6kl_init_if_data(struct ath6kl_vif *vif)
 
        setup_timer(&vif->disconnect_timer, disconnect_timer_handler,
                    (unsigned long) vif->ndev);
+       setup_timer(&vif->sched_scan_timer, ath6kl_wmi_sscan_timer,
+                   (unsigned long) vif);
+
        set_bit(WMM_ENABLED, &vif->flags);
        spin_lock_init(&vif->if_lock);