wl12xx: AP mode - support hidden SSID
[pandora-kernel.git] / drivers / net / wireless / wl12xx / main.c
index e5b871c..02b5c00 100644 (file)
@@ -266,8 +266,8 @@ static struct conf_drv_settings default_conf = {
        },
        .sched_scan = {
                /* sched_scan requires dwell times in TU instead of TU/1000 */
-               .min_dwell_time_active = 8,
-               .max_dwell_time_active = 30,
+               .min_dwell_time_active = 30,
+               .max_dwell_time_active = 60,
                .dwell_time_passive    = 100,
                .dwell_time_dfs        = 150,
                .num_probe_reqs        = 2,
@@ -770,13 +770,14 @@ static int wl1271_plt_init(struct wl1271 *wl)
 
 static void wl12xx_irq_ps_regulate_link(struct wl1271 *wl, u8 hlid, u8 tx_pkts)
 {
-       bool fw_ps;
+       bool fw_ps, single_sta;
 
        /* only regulate station links */
        if (hlid < WL1271_AP_STA_HLID_START)
                return;
 
        fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map);
+       single_sta = (wl->active_sta_count == 1);
 
        /*
         * Wake up from high level PS if the STA is asleep with too little
@@ -785,8 +786,12 @@ static void wl12xx_irq_ps_regulate_link(struct wl1271 *wl, u8 hlid, u8 tx_pkts)
        if (!fw_ps || tx_pkts < WL1271_PS_STA_MAX_PACKETS)
                wl1271_ps_link_end(wl, hlid);
 
-       /* Start high-level PS if the STA is asleep with enough blocks in FW */
-       else if (fw_ps && tx_pkts >= WL1271_PS_STA_MAX_PACKETS)
+       /*
+        * Start high-level PS if the STA is asleep with enough blocks in FW.
+        * Make an exception if this is the only connected station. In this
+        * case FW-memory congestion is not a problem.
+        */
+       else if (!single_sta && fw_ps && tx_pkts >= WL1271_PS_STA_MAX_PACKETS)
                wl1271_ps_link_start(wl, hlid, true);
 }
 
@@ -1820,10 +1825,16 @@ static u8 wl12xx_get_role_type(struct wl1271 *wl)
 {
        switch (wl->bss_type) {
        case BSS_TYPE_AP_BSS:
-               return WL1271_ROLE_AP;
+               if (wl->p2p)
+                       return WL1271_ROLE_P2P_GO;
+               else
+                       return WL1271_ROLE_AP;
 
        case BSS_TYPE_STA_BSS:
-               return WL1271_ROLE_STA;
+               if (wl->p2p)
+                       return WL1271_ROLE_P2P_CL;
+               else
+                       return WL1271_ROLE_STA;
 
        case BSS_TYPE_IBSS:
                return WL1271_ROLE_IBSS;
@@ -1845,7 +1856,7 @@ static int wl1271_op_add_interface(struct ieee80211_hw *hw,
        bool booted = false;
 
        wl1271_debug(DEBUG_MAC80211, "mac80211 add interface type %d mac %pM",
-                    vif->type, vif->addr);
+                    ieee80211_vif_type_p2p(vif), vif->addr);
 
        mutex_lock(&wl->mutex);
        if (wl->vif) {
@@ -1865,7 +1876,10 @@ static int wl1271_op_add_interface(struct ieee80211_hw *hw,
                goto out;
        }
 
-       switch (vif->type) {
+       switch (ieee80211_vif_type_p2p(vif)) {
+       case NL80211_IFTYPE_P2P_CLIENT:
+               wl->p2p = 1;
+               /* fall-through */
        case NL80211_IFTYPE_STATION:
                wl->bss_type = BSS_TYPE_STA_BSS;
                wl->set_bss_type = BSS_TYPE_STA_BSS;
@@ -1874,6 +1888,9 @@ static int wl1271_op_add_interface(struct ieee80211_hw *hw,
                wl->bss_type = BSS_TYPE_IBSS;
                wl->set_bss_type = BSS_TYPE_STA_BSS;
                break;
+       case NL80211_IFTYPE_P2P_GO:
+               wl->p2p = 1;
+               /* fall-through */
        case NL80211_IFTYPE_AP:
                wl->bss_type = BSS_TYPE_AP_BSS;
                break;
@@ -2069,6 +2086,7 @@ deinit:
        wl->ssid_len = 0;
        wl->bss_type = MAX_BSS_TYPE;
        wl->set_bss_type = MAX_BSS_TYPE;
+       wl->p2p = 0;
        wl->band = IEEE80211_BAND_2GHZ;
 
        wl->rx_counter = 0;
@@ -2093,6 +2111,7 @@ deinit:
        memset(wl->roles_map, 0, sizeof(wl->roles_map));
        memset(wl->links_map, 0, sizeof(wl->links_map));
        memset(wl->roc_map, 0, sizeof(wl->roc_map));
+       wl->active_sta_count = 0;
 
        /* The system link is always allocated */
        __set_bit(WL12XX_SYSTEM_HLID, wl->links_map);
@@ -2336,6 +2355,8 @@ static int wl1271_op_config(struct ieee80211_hw *hw, u32 changed)
        if (changed & IEEE80211_CONF_CHANGE_CHANNEL &&
            ((wl->band != conf->channel->band) ||
             (wl->channel != channel))) {
+               /* send all pending packets */
+               wl1271_tx_work_locked(wl);
                wl->band = conf->channel->band;
                wl->channel = channel;
 
@@ -3050,6 +3071,93 @@ static int wl1271_ssid_set(struct wl1271 *wl, struct sk_buff *skb,
        return 0;
 }
 
+static void wl12xx_remove_ie(struct sk_buff *skb, u8 eid, int ieoffset)
+{
+       int len;
+       const u8 *next, *end = skb->data + skb->len;
+       u8 *ie = (u8 *)cfg80211_find_ie(eid, skb->data + ieoffset,
+                                       skb->len - ieoffset);
+       if (!ie)
+               return;
+       len = ie[1] + 2;
+       next = ie + len;
+       memmove(ie, next, end - next);
+       skb_trim(skb, skb->len - len);
+}
+
+static void wl12xx_remove_vendor_ie(struct sk_buff *skb,
+                                           unsigned int oui, u8 oui_type,
+                                           int ieoffset)
+{
+       int len;
+       const u8 *next, *end = skb->data + skb->len;
+       u8 *ie = (u8 *)cfg80211_find_vendor_ie(oui, oui_type,
+                                              skb->data + ieoffset,
+                                              skb->len - ieoffset);
+       if (!ie)
+               return;
+       len = ie[1] + 2;
+       next = ie + len;
+       memmove(ie, next, end - next);
+       skb_trim(skb, skb->len - len);
+}
+
+static int wl1271_ap_set_probe_resp_tmpl(struct wl1271 *wl,
+                                        u8 *probe_rsp_data,
+                                        size_t probe_rsp_len,
+                                        u32 rates)
+{
+       struct ieee80211_bss_conf *bss_conf = &wl->vif->bss_conf;
+       u8 probe_rsp_templ[WL1271_CMD_TEMPL_MAX_SIZE];
+       int ssid_ie_offset, ie_offset, templ_len;
+       const u8 *ptr;
+
+       /* no need to change probe response if the SSID is set correctly */
+       if (wl->ssid_len > 0)
+               return wl1271_cmd_template_set(wl,
+                                              CMD_TEMPL_AP_PROBE_RESPONSE,
+                                              probe_rsp_data,
+                                              probe_rsp_len, 0,
+                                              rates);
+
+       if (probe_rsp_len + bss_conf->ssid_len > WL1271_CMD_TEMPL_MAX_SIZE) {
+               wl1271_error("probe_rsp template too big");
+               return -EINVAL;
+       }
+
+       /* start searching from IE offset */
+       ie_offset = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
+
+       ptr = cfg80211_find_ie(WLAN_EID_SSID, probe_rsp_data + ie_offset,
+                              probe_rsp_len - ie_offset);
+       if (!ptr) {
+               wl1271_error("No SSID in beacon!");
+               return -EINVAL;
+       }
+
+       ssid_ie_offset = ptr - probe_rsp_data;
+       ptr += (ptr[1] + 2);
+
+       memcpy(probe_rsp_templ, probe_rsp_data, ssid_ie_offset);
+
+       /* insert SSID from bss_conf */
+       probe_rsp_templ[ssid_ie_offset] = WLAN_EID_SSID;
+       probe_rsp_templ[ssid_ie_offset + 1] = bss_conf->ssid_len;
+       memcpy(probe_rsp_templ + ssid_ie_offset + 2,
+              bss_conf->ssid, bss_conf->ssid_len);
+       templ_len = ssid_ie_offset + 2 + bss_conf->ssid_len;
+
+       memcpy(probe_rsp_templ + ssid_ie_offset + 2 + bss_conf->ssid_len,
+              ptr, probe_rsp_len - (ptr - probe_rsp_data));
+       templ_len += probe_rsp_len - (ptr - probe_rsp_data);
+
+       return wl1271_cmd_template_set(wl,
+                                      CMD_TEMPL_AP_PROBE_RESPONSE,
+                                      probe_rsp_templ,
+                                      templ_len, 0,
+                                      rates);
+}
+
 static int wl1271_bss_erp_info_changed(struct wl1271 *wl,
                                       struct ieee80211_bss_conf *bss_conf,
                                       u32 changed)
@@ -3132,17 +3240,34 @@ static int wl1271_bss_beacon_info_changed(struct wl1271 *wl,
                        goto out;
                }
 
+               /* remove TIM ie from probe response */
+               wl12xx_remove_ie(beacon, WLAN_EID_TIM, ieoffset);
+
+               /*
+                * remove p2p ie from probe response.
+                * the fw reponds to probe requests that don't include
+                * the p2p ie. probe requests with p2p ie will be passed,
+                * and will be responded by the supplicant (the spec
+                * forbids including the p2p ie when responding to probe
+                * requests that didn't include it).
+                */
+               wl12xx_remove_vendor_ie(beacon, WLAN_OUI_WFA,
+                                       WLAN_OUI_TYPE_WFA_P2P, ieoffset);
+
                hdr = (struct ieee80211_hdr *) beacon->data;
                hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
                                                 IEEE80211_STYPE_PROBE_RESP);
-
-               tmpl_id = is_ap ? CMD_TEMPL_AP_PROBE_RESPONSE :
-                                 CMD_TEMPL_PROBE_RESPONSE;
-               ret = wl1271_cmd_template_set(wl,
-                                             tmpl_id,
-                                             beacon->data,
-                                             beacon->len, 0,
-                                             wl1271_tx_min_rate_get(wl));
+               if (is_ap)
+                       ret = wl1271_ap_set_probe_resp_tmpl(wl,
+                                               beacon->data,
+                                               beacon->len,
+                                               wl1271_tx_min_rate_get(wl));
+               else
+                       ret = wl1271_cmd_template_set(wl,
+                                               CMD_TEMPL_PROBE_RESPONSE,
+                                               beacon->data,
+                                               beacon->len, 0,
+                                               wl1271_tx_min_rate_get(wl));
                dev_kfree_skb(beacon);
                if (ret < 0)
                        goto out;
@@ -3453,7 +3578,7 @@ sta_not_found:
                                                                         rates);
                        wl->basic_rate = wl1271_tx_min_rate_get(wl);
 
-                       /* by default, use 11b rates */
+                       /* by default, use 11b + OFDM rates */
                        wl->rate_set = CONF_TX_IBSS_DEFAULT_RATES;
                        ret = wl1271_acx_sta_rate_policies(wl);
                        if (ret < 0)
@@ -3747,14 +3872,18 @@ static int wl1271_allocate_sta(struct wl1271 *wl,
        wl_sta->hlid = WL1271_AP_STA_HLID_START + id;
        *hlid = wl_sta->hlid;
        memcpy(wl->links[wl_sta->hlid].addr, sta->addr, ETH_ALEN);
+       wl->active_sta_count++;
        return 0;
 }
 
-static void wl1271_free_sta(struct wl1271 *wl, u8 hlid)
+void wl1271_free_sta(struct wl1271 *wl, u8 hlid)
 {
        int id = hlid - WL1271_AP_STA_HLID_START;
 
-       if (WARN_ON(!test_bit(id, wl->ap_hlid_map)))
+       if (hlid < WL1271_AP_STA_HLID_START)
+               return;
+
+       if (!test_bit(id, wl->ap_hlid_map))
                return;
 
        clear_bit(id, wl->ap_hlid_map);
@@ -3763,6 +3892,7 @@ static void wl1271_free_sta(struct wl1271 *wl, u8 hlid)
        wl1271_tx_reset_link_queues(wl, hlid);
        __clear_bit(hlid, &wl->ap_ps_map);
        __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map);
+       wl->active_sta_count--;
 }
 
 static int wl1271_op_sta_add(struct ieee80211_hw *hw,
@@ -4495,15 +4625,19 @@ int wl1271_init_ieee80211(struct wl1271 *wl)
                IEEE80211_HW_SUPPORTS_CQM_RSSI |
                IEEE80211_HW_REPORTS_TX_ACK_STATUS |
                IEEE80211_HW_SPECTRUM_MGMT |
-               IEEE80211_HW_AP_LINK_PS;
+               IEEE80211_HW_AP_LINK_PS |
+               IEEE80211_HW_AMPDU_AGGREGATION |
+               IEEE80211_HW_TX_AMPDU_SETUP_IN_HW;
 
        wl->hw->wiphy->cipher_suites = cipher_suites;
        wl->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
 
        wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
-               BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_AP);
+               BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_AP) |
+               BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO);
        wl->hw->wiphy->max_scan_ssids = 1;
-       wl->hw->wiphy->max_sched_scan_ssids = 8;
+       wl->hw->wiphy->max_sched_scan_ssids = 16;
+       wl->hw->wiphy->max_match_sets = 16;
        /*
         * Maximum length of elements in scanning probe request templates
         * should be the maximum length possible for a template, without
@@ -4512,6 +4646,8 @@ int wl1271_init_ieee80211(struct wl1271 *wl)
        wl->hw->wiphy->max_scan_ie_len = WL1271_CMD_TEMPL_DFLT_SIZE -
                        sizeof(struct ieee80211_header);
 
+       wl->hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+
        /* make sure all our channels fit in the scanned_ch bitmask */
        BUILD_BUG_ON(ARRAY_SIZE(wl1271_channels) +
                     ARRAY_SIZE(wl1271_channels_5ghz) >
@@ -4637,6 +4773,7 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
        wl->session_counter = 0;
        wl->ap_bcast_hlid = WL12XX_INVALID_LINK_ID;
        wl->ap_global_hlid = WL12XX_INVALID_LINK_ID;
+       wl->active_sta_count = 0;
        setup_timer(&wl->rx_streaming_timer, wl1271_rx_streaming_timer,
                    (unsigned long) wl);
        wl->fwlog_size = 0;