mac80211: make the beacon monitor available externally
[cascardo/linux.git] / net / mac80211 / mlme.c
index 75b896d..07d03e7 100644 (file)
  */
 #define IEEE80211_SIGNAL_AVE_WEIGHT    3
 
+/*
+ * How many Beacon frames need to have been used in average signal strength
+ * before starting to indicate signal change events.
+ */
+#define IEEE80211_SIGNAL_AVE_MIN_COUNT 4
+
 #define TMR_RUNNING_TIMER      0
 #define TMR_RUNNING_CHANSW     1
 
@@ -86,7 +92,7 @@ enum rx_mgmt_action {
 /* utils */
 static inline void ASSERT_MGD_MTX(struct ieee80211_if_managed *ifmgd)
 {
-       WARN_ON(!mutex_is_locked(&ifmgd->mtx));
+       lockdep_assert_held(&ifmgd->mtx);
 }
 
 /*
@@ -109,7 +115,7 @@ static void run_again(struct ieee80211_if_managed *ifmgd,
                mod_timer(&ifmgd->timer, timeout);
 }
 
-static void mod_beacon_timer(struct ieee80211_sub_if_data *sdata)
+void ieee80211_sta_reset_beacon_monitor(struct ieee80211_sub_if_data *sdata)
 {
        if (sdata->local->hw.flags & IEEE80211_HW_BEACON_FILTER)
                return;
@@ -118,6 +124,19 @@ static void mod_beacon_timer(struct ieee80211_sub_if_data *sdata)
                  round_jiffies_up(jiffies + IEEE80211_BEACON_LOSS_TIME));
 }
 
+void ieee80211_sta_reset_conn_monitor(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+
+       if (sdata->local->hw.flags & IEEE80211_HW_CONNECTION_MONITOR)
+               return;
+
+       mod_timer(&sdata->u.mgd.conn_mon_timer,
+                 round_jiffies_up(jiffies + IEEE80211_CONNECTION_IDLE_TIME));
+
+       ifmgd->probe_send_count = 0;
+}
+
 static int ecw2cw(int ecw)
 {
        return (1 << ecw) - 1;
@@ -478,6 +497,39 @@ static void ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
        }
 }
 
+void ieee80211_enable_dyn_ps(struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_conf *conf = &local->hw.conf;
+
+       WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION ||
+               !(local->hw.flags & IEEE80211_HW_SUPPORTS_PS) ||
+               (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS));
+
+       local->disable_dynamic_ps = false;
+       conf->dynamic_ps_timeout = local->dynamic_ps_user_timeout;
+}
+EXPORT_SYMBOL(ieee80211_enable_dyn_ps);
+
+void ieee80211_disable_dyn_ps(struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_conf *conf = &local->hw.conf;
+
+       WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION ||
+               !(local->hw.flags & IEEE80211_HW_SUPPORTS_PS) ||
+               (local->hw.flags & IEEE80211_HW_SUPPORTS_DYNAMIC_PS));
+
+       local->disable_dynamic_ps = true;
+       conf->dynamic_ps_timeout = 0;
+       del_timer_sync(&local->dynamic_ps_timer);
+       ieee80211_queue_work(&local->hw,
+                            &local->dynamic_ps_enable_work);
+}
+EXPORT_SYMBOL(ieee80211_disable_dyn_ps);
+
 /* powersave */
 static void ieee80211_enable_ps(struct ieee80211_local *local,
                                struct ieee80211_sub_if_data *sdata)
@@ -553,6 +605,7 @@ void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency)
            found->u.mgd.associated->beacon_ies &&
            !(found->u.mgd.flags & (IEEE80211_STA_BEACON_POLL |
                                    IEEE80211_STA_CONNECTION_POLL))) {
+               struct ieee80211_conf *conf = &local->hw.conf;
                s32 beaconint_us;
 
                if (latency < 0)
@@ -561,25 +614,24 @@ void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency)
                beaconint_us = ieee80211_tu_to_usec(
                                        found->vif.bss_conf.beacon_int);
 
-               timeout = local->hw.conf.dynamic_ps_forced_timeout;
+               timeout = local->dynamic_ps_forced_timeout;
                if (timeout < 0) {
                        /*
+                        * Go to full PSM if the user configures a very low
+                        * latency requirement.
                         * The 2 second value is there for compatibility until
                         * the PM_QOS_NETWORK_LATENCY is configured with real
                         * values.
                         */
-                       if (latency == 2000000000)
-                               timeout = 100;
-                       else if (latency <= 50000)
-                               timeout = 300;
-                       else if (latency <= 100000)
-                               timeout = 100;
-                       else if (latency <= 500000)
-                               timeout = 50;
-                       else
+                       if (latency > 1900000000 && latency != 2000000000)
                                timeout = 0;
+                       else
+                               timeout = 100;
                }
-               local->hw.conf.dynamic_ps_timeout = timeout;
+               local->dynamic_ps_user_timeout = timeout;
+               if (!local->disable_dynamic_ps)
+                       conf->dynamic_ps_timeout =
+                               local->dynamic_ps_user_timeout;
 
                if (beaconint_us > latency) {
                        local->ps_sdata = NULL;
@@ -665,10 +717,11 @@ void ieee80211_dynamic_ps_timer(unsigned long data)
 
 /* MLME */
 static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
-                                    struct ieee80211_if_managed *ifmgd,
+                                    struct ieee80211_sub_if_data *sdata,
                                     u8 *wmm_param, size_t wmm_param_len)
 {
        struct ieee80211_tx_queue_params params;
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        size_t left;
        int count;
        u8 *pos, uapsd_queues = 0;
@@ -744,21 +797,22 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
                params.uapsd = uapsd;
 
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
-               printk(KERN_DEBUG "%s: WMM queue=%d aci=%d acm=%d aifs=%d "
-                      "cWmin=%d cWmax=%d txop=%d uapsd=%d\n",
-                      wiphy_name(local->hw.wiphy), queue, aci, acm,
-                      params.aifs, params.cw_min, params.cw_max, params.txop,
-                      params.uapsd);
+               wiphy_debug(local->hw.wiphy,
+                           "WMM queue=%d aci=%d acm=%d aifs=%d "
+                           "cWmin=%d cWmax=%d txop=%d uapsd=%d\n",
+                           queue, aci, acm,
+                           params.aifs, params.cw_min, params.cw_max,
+                           params.txop, params.uapsd);
 #endif
                if (drv_conf_tx(local, queue, &params))
-                       printk(KERN_DEBUG "%s: failed to set TX queue "
-                              "parameters for queue %d\n",
-                              wiphy_name(local->hw.wiphy), queue);
+                       wiphy_debug(local->hw.wiphy,
+                                   "failed to set TX queue parameters for queue %d\n",
+                                   queue);
        }
 
        /* enable WMM or activate new settings */
-       local->hw.conf.flags |= IEEE80211_CONF_QOS;
-       drv_config(local, IEEE80211_CONF_CHANGE_QOS);
+       sdata->vif.bss_conf.qos = true;
+       ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_QOS);
 }
 
 static u32 ieee80211_handle_bss_capability(struct ieee80211_sub_if_data *sdata,
@@ -806,11 +860,12 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
 {
        struct ieee80211_bss *bss = (void *)cbss->priv;
        struct ieee80211_local *local = sdata->local;
+       struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf;
 
        bss_info_changed |= BSS_CHANGED_ASSOC;
        /* set timing information */
-       sdata->vif.bss_conf.beacon_int = cbss->beacon_interval;
-       sdata->vif.bss_conf.timestamp = cbss->tsf;
+       bss_conf->beacon_int = cbss->beacon_interval;
+       bss_conf->timestamp = cbss->tsf;
 
        bss_info_changed |= BSS_CHANGED_BEACON_INT;
        bss_info_changed |= ieee80211_handle_bss_capability(sdata,
@@ -835,7 +890,12 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
 
        ieee80211_led_assoc(local, 1);
 
-       sdata->vif.bss_conf.assoc = 1;
+       if (local->hw.flags & IEEE80211_HW_NEED_DTIM_PERIOD)
+               bss_conf->dtim_period = bss->dtim_period;
+       else
+               bss_conf->dtim_period = 0;
+
+       bss_conf->assoc = 1;
        /*
         * For now just always ask the driver to update the basic rateset
         * when we have associated, we aren't checking whether it actually
@@ -848,9 +908,15 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
 
        /* Tell the driver to monitor connection quality (if supported) */
        if ((local->hw.flags & IEEE80211_HW_SUPPORTS_CQM_RSSI) &&
-           sdata->vif.bss_conf.cqm_rssi_thold)
+           bss_conf->cqm_rssi_thold)
                bss_info_changed |= BSS_CHANGED_CQM;
 
+       /* Enable ARP filtering */
+       if (bss_conf->arp_filter_enabled != sdata->arp_filter_state) {
+               bss_conf->arp_filter_enabled = sdata->arp_filter_state;
+               bss_info_changed |= BSS_CHANGED_ARP_FILTER;
+       }
+
        ieee80211_bss_info_change_notify(sdata, bss_info_changed);
 
        mutex_lock(&local->iflist_mtx);
@@ -932,12 +998,23 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
 
        ieee80211_hw_config(local, config_changed);
 
+       /* Disable ARP filtering */
+       if (sdata->vif.bss_conf.arp_filter_enabled) {
+               sdata->vif.bss_conf.arp_filter_enabled = false;
+               changed |= BSS_CHANGED_ARP_FILTER;
+       }
+
        /* The BSSID (not really interesting) and HT changed */
        changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT;
        ieee80211_bss_info_change_notify(sdata, changed);
 
        if (remove_sta)
                sta_info_destroy_addr(sdata, bssid);
+
+       del_timer_sync(&sdata->u.mgd.conn_mon_timer);
+       del_timer_sync(&sdata->u.mgd.bcn_mon_timer);
+       del_timer_sync(&sdata->u.mgd.timer);
+       del_timer_sync(&sdata->u.mgd.chswitch_timer);
 }
 
 void ieee80211_sta_rx_notify(struct ieee80211_sub_if_data *sdata,
@@ -954,11 +1031,7 @@ void ieee80211_sta_rx_notify(struct ieee80211_sub_if_data *sdata,
        if (is_multicast_ether_addr(hdr->addr1))
                return;
 
-       if (sdata->local->hw.flags & IEEE80211_HW_CONNECTION_MONITOR)
-               return;
-
-       mod_timer(&sdata->u.mgd.conn_mon_timer,
-                 round_jiffies_up(jiffies + IEEE80211_CONNECTION_IDLE_TIME));
+       ieee80211_sta_reset_conn_monitor(sdata);
 }
 
 static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata)
@@ -1051,8 +1124,11 @@ static void __ieee80211_connection_loss(struct ieee80211_sub_if_data *sdata)
        printk(KERN_DEBUG "Connection to AP %pM lost.\n", bssid);
 
        ieee80211_set_disassoc(sdata, true);
-       ieee80211_recalc_idle(local);
        mutex_unlock(&ifmgd->mtx);
+
+       mutex_lock(&local->mtx);
+       ieee80211_recalc_idle(local);
+       mutex_unlock(&local->mtx);
        /*
         * must be outside lock due to cfg80211,
         * but that's not a problem.
@@ -1121,7 +1197,9 @@ ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
                        sdata->name, bssid, reason_code);
 
        ieee80211_set_disassoc(sdata, true);
+       mutex_lock(&sdata->local->mtx);
        ieee80211_recalc_idle(sdata->local);
+       mutex_unlock(&sdata->local->mtx);
 
        return RX_MGMT_CFG80211_DEAUTH;
 }
@@ -1151,7 +1229,9 @@ ieee80211_rx_mgmt_disassoc(struct ieee80211_sub_if_data *sdata,
                        sdata->name, mgmt->sa, reason_code);
 
        ieee80211_set_disassoc(sdata, true);
+       mutex_lock(&sdata->local->mtx);
        ieee80211_recalc_idle(sdata->local);
+       mutex_unlock(&sdata->local->mtx);
        return RX_MGMT_CFG80211_DISASSOC;
 }
 
@@ -1279,7 +1359,7 @@ static bool ieee80211_assoc_success(struct ieee80211_work *wk,
        }
 
        if (elems.wmm_param)
-               ieee80211_sta_wmm_params(local, ifmgd, elems.wmm_param,
+               ieee80211_sta_wmm_params(local, sdata, elems.wmm_param,
                                         elems.wmm_param_len);
        else
                ieee80211_set_wmm_default(sdata);
@@ -1310,7 +1390,7 @@ static bool ieee80211_assoc_success(struct ieee80211_work *wk,
         * Also start the timer that will detect beacon loss.
         */
        ieee80211_sta_rx_notify(sdata, (struct ieee80211_hdr *)mgmt);
-       mod_beacon_timer(sdata);
+       ieee80211_sta_reset_beacon_monitor(sdata);
 
        return true;
 }
@@ -1413,7 +1493,7 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_sub_if_data *sdata,
                 * we have or will be receiving any beacons or data, so let's
                 * schedule the timers again, just in case.
                 */
-               mod_beacon_timer(sdata);
+               ieee80211_sta_reset_beacon_monitor(sdata);
 
                mod_timer(&ifmgd->conn_mon_timer,
                          round_jiffies_up(jiffies +
@@ -1488,15 +1568,18 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
        ifmgd->last_beacon_signal = rx_status->signal;
        if (ifmgd->flags & IEEE80211_STA_RESET_SIGNAL_AVE) {
                ifmgd->flags &= ~IEEE80211_STA_RESET_SIGNAL_AVE;
-               ifmgd->ave_beacon_signal = rx_status->signal;
+               ifmgd->ave_beacon_signal = rx_status->signal * 16;
                ifmgd->last_cqm_event_signal = 0;
+               ifmgd->count_beacon_signal = 1;
        } else {
                ifmgd->ave_beacon_signal =
                        (IEEE80211_SIGNAL_AVE_WEIGHT * rx_status->signal * 16 +
                         (16 - IEEE80211_SIGNAL_AVE_WEIGHT) *
                         ifmgd->ave_beacon_signal) / 16;
+               ifmgd->count_beacon_signal++;
        }
        if (bss_conf->cqm_rssi_thold &&
+           ifmgd->count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT &&
            !(local->hw.flags & IEEE80211_HW_SUPPORTS_CQM_RSSI)) {
                int sig = ifmgd->ave_beacon_signal / 16;
                int last_event = ifmgd->last_cqm_event_signal;
@@ -1536,7 +1619,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
         * Push the beacon loss detection into the future since
         * we are processing a beacon from the AP just now.
         */
-       mod_beacon_timer(sdata);
+       ieee80211_sta_reset_beacon_monitor(sdata);
 
        ncrc = crc32_be(0, (void *)&mgmt->u.beacon.beacon_int, 4);
        ncrc = ieee802_11_parse_elems_crc(mgmt->u.beacon.variable,
@@ -1551,7 +1634,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
                ieee80211_rx_bss_info(sdata, mgmt, len, rx_status, &elems,
                                      true);
 
-               ieee80211_sta_wmm_params(local, ifmgd, elems.wmm_param,
+               ieee80211_sta_wmm_params(local, sdata, elems.wmm_param,
                                         elems.wmm_param_len);
        }
 
@@ -1633,35 +1716,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
        ieee80211_bss_info_change_notify(sdata, changed);
 }
 
-ieee80211_rx_result ieee80211_sta_rx_mgmt(struct ieee80211_sub_if_data *sdata,
-                                         struct sk_buff *skb)
-{
-       struct ieee80211_local *local = sdata->local;
-       struct ieee80211_mgmt *mgmt;
-       u16 fc;
-
-       if (skb->len < 24)
-               return RX_DROP_MONITOR;
-
-       mgmt = (struct ieee80211_mgmt *) skb->data;
-       fc = le16_to_cpu(mgmt->frame_control);
-
-       switch (fc & IEEE80211_FCTL_STYPE) {
-       case IEEE80211_STYPE_PROBE_RESP:
-       case IEEE80211_STYPE_BEACON:
-       case IEEE80211_STYPE_DEAUTH:
-       case IEEE80211_STYPE_DISASSOC:
-       case IEEE80211_STYPE_ACTION:
-               skb_queue_tail(&sdata->skb_queue, skb);
-               ieee80211_queue_work(&local->hw, &sdata->work);
-               return RX_QUEUED;
-       }
-
-       return RX_DROP_MONITOR;
-}
-
-static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
-                                        struct sk_buff *skb)
+void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
+                                 struct sk_buff *skb)
 {
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_rx_status *rx_status;
@@ -1693,44 +1749,6 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
                        break;
                case IEEE80211_STYPE_ACTION:
                        switch (mgmt->u.action.category) {
-                       case WLAN_CATEGORY_BACK: {
-                               struct ieee80211_local *local = sdata->local;
-                               int len = skb->len;
-                               struct sta_info *sta;
-
-                               rcu_read_lock();
-                               sta = sta_info_get(sdata, mgmt->sa);
-                               if (!sta) {
-                                       rcu_read_unlock();
-                                       break;
-                               }
-
-                               local_bh_disable();
-
-                               switch (mgmt->u.action.u.addba_req.action_code) {
-                               case WLAN_ACTION_ADDBA_REQ:
-                                       if (len < (IEEE80211_MIN_ACTION_SIZE +
-                                                  sizeof(mgmt->u.action.u.addba_req)))
-                                               break;
-                                       ieee80211_process_addba_request(local, sta, mgmt, len);
-                                       break;
-                               case WLAN_ACTION_ADDBA_RESP:
-                                       if (len < (IEEE80211_MIN_ACTION_SIZE +
-                                                  sizeof(mgmt->u.action.u.addba_resp)))
-                                               break;
-                                       ieee80211_process_addba_resp(local, sta, mgmt, len);
-                                       break;
-                               case WLAN_ACTION_DELBA:
-                                       if (len < (IEEE80211_MIN_ACTION_SIZE +
-                                                  sizeof(mgmt->u.action.u.delba)))
-                                               break;
-                                       ieee80211_process_delba(sdata, sta, mgmt, len);
-                                       break;
-                               }
-                               local_bh_enable();
-                               rcu_read_unlock();
-                               break;
-                               }
                        case WLAN_CATEGORY_SPECTRUM_MGMT:
                                ieee80211_sta_process_chanswitch(sdata,
                                                &mgmt->u.action.u.chan_switch.sw_elem,
@@ -1754,17 +1772,52 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
                default:
                        WARN(1, "unexpected: %d", rma);
                }
-               goto out;
+               return;
        }
 
        mutex_unlock(&ifmgd->mtx);
 
        if (skb->len >= 24 + 2 /* mgmt + deauth reason */ &&
-           (fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_DEAUTH)
-               cfg80211_send_deauth(sdata->dev, (u8 *)mgmt, skb->len);
+           (fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_DEAUTH) {
+               struct ieee80211_local *local = sdata->local;
+               struct ieee80211_work *wk;
 
- out:
-       kfree_skb(skb);
+               mutex_lock(&local->mtx);
+               list_for_each_entry(wk, &local->work_list, list) {
+                       if (wk->sdata != sdata)
+                               continue;
+
+                       if (wk->type != IEEE80211_WORK_ASSOC &&
+                           wk->type != IEEE80211_WORK_ASSOC_BEACON_WAIT)
+                               continue;
+
+                       if (memcmp(mgmt->bssid, wk->filter_ta, ETH_ALEN))
+                               continue;
+                       if (memcmp(mgmt->sa, wk->filter_ta, ETH_ALEN))
+                               continue;
+
+                       /*
+                        * Printing the message only here means we can't
+                        * spuriously print it, but it also means that it
+                        * won't be printed when the frame comes in before
+                        * we even tried to associate or in similar cases.
+                        *
+                        * Ultimately, I suspect cfg80211 should print the
+                        * messages instead.
+                        */
+                       printk(KERN_DEBUG
+                              "%s: deauthenticated from %pM (Reason: %u)\n",
+                              sdata->name, mgmt->bssid,
+                              le16_to_cpu(mgmt->u.deauth.reason_code));
+
+                       list_del_rcu(&wk->list);
+                       free_work(wk);
+                       break;
+               }
+               mutex_unlock(&local->mtx);
+
+               cfg80211_send_deauth(sdata->dev, (u8 *)mgmt, skb->len);
+       }
 }
 
 static void ieee80211_sta_timer(unsigned long data)
@@ -1782,36 +1835,10 @@ static void ieee80211_sta_timer(unsigned long data)
        ieee80211_queue_work(&local->hw, &sdata->work);
 }
 
-static void ieee80211_sta_work(struct work_struct *work)
+void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata)
 {
-       struct ieee80211_sub_if_data *sdata =
-               container_of(work, struct ieee80211_sub_if_data, work);
        struct ieee80211_local *local = sdata->local;
-       struct ieee80211_if_managed *ifmgd;
-       struct sk_buff *skb;
-
-       if (!ieee80211_sdata_running(sdata))
-               return;
-
-       if (local->scanning)
-               return;
-
-       if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION))
-               return;
-
-       /*
-        * ieee80211_queue_work() should have picked up most cases,
-        * here we'll pick the rest.
-        */
-       if (WARN(local->suspended, "STA MLME work scheduled while "
-                "going to suspend\n"))
-               return;
-
-       ifmgd = &sdata->u.mgd;
-
-       /* first process frames to avoid timing out while a frame is pending */
-       while ((skb = skb_dequeue(&sdata->skb_queue)))
-               ieee80211_sta_rx_queued_mgmt(sdata, skb);
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 
        /* then process the rest of the work */
        mutex_lock(&ifmgd->mtx);
@@ -1844,8 +1871,10 @@ static void ieee80211_sta_work(struct work_struct *work)
                                " after %dms, disconnecting.\n",
                                bssid, (1000 * IEEE80211_PROBE_WAIT)/HZ);
                        ieee80211_set_disassoc(sdata, true);
-                       ieee80211_recalc_idle(local);
                        mutex_unlock(&ifmgd->mtx);
+                       mutex_lock(&local->mtx);
+                       ieee80211_recalc_idle(local);
+                       mutex_unlock(&local->mtx);
                        /*
                         * must be outside lock due to cfg80211,
                         * but that's not a problem.
@@ -1921,6 +1950,8 @@ void ieee80211_sta_quiesce(struct ieee80211_sub_if_data *sdata)
         * time -- the code here is properly synchronised.
         */
 
+       cancel_work_sync(&ifmgd->request_smps_work);
+
        cancel_work_sync(&ifmgd->beacon_connection_loss_work);
        if (del_timer_sync(&ifmgd->timer))
                set_bit(TMR_RUNNING_TIMER, &ifmgd->timers_running);
@@ -1952,11 +1983,11 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
        struct ieee80211_if_managed *ifmgd;
 
        ifmgd = &sdata->u.mgd;
-       INIT_WORK(&sdata->work, ieee80211_sta_work);
        INIT_WORK(&ifmgd->monitor_work, ieee80211_sta_monitor_work);
        INIT_WORK(&ifmgd->chswitch_work, ieee80211_chswitch_work);
        INIT_WORK(&ifmgd->beacon_connection_loss_work,
                  ieee80211_beacon_connection_loss_work);
+       INIT_WORK(&ifmgd->request_smps_work, ieee80211_request_smps_work);
        setup_timer(&ifmgd->timer, ieee80211_sta_timer,
                    (unsigned long) sdata);
        setup_timer(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer,
@@ -2042,6 +2073,8 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
                auth_alg = WLAN_AUTH_OPEN;
                break;
        case NL80211_AUTHTYPE_SHARED_KEY:
+               if (IS_ERR(sdata->local->wep_tx_tfm))
+                       return -EOPNOTSUPP;
                auth_alg = WLAN_AUTH_SHARED_KEY;
                break;
        case NL80211_AUTHTYPE_FT:
@@ -2095,6 +2128,8 @@ static enum work_done_result ieee80211_assoc_done(struct ieee80211_work *wk,
                                                  struct sk_buff *skb)
 {
        struct ieee80211_mgmt *mgmt;
+       struct ieee80211_rx_status *rx_status;
+       struct ieee802_11_elems elems;
        u16 status;
 
        if (!skb) {
@@ -2102,6 +2137,19 @@ static enum work_done_result ieee80211_assoc_done(struct ieee80211_work *wk,
                return WORK_DONE_DESTROY;
        }
 
+       if (wk->type == IEEE80211_WORK_ASSOC_BEACON_WAIT) {
+               mutex_lock(&wk->sdata->u.mgd.mtx);
+               rx_status = (void *) skb->cb;
+               ieee802_11_parse_elems(skb->data + 24 + 12, skb->len - 24 - 12, &elems);
+               ieee80211_rx_bss_info(wk->sdata, (void *)skb->data, skb->len, rx_status,
+                                     &elems, true);
+               mutex_unlock(&wk->sdata->u.mgd.mtx);
+
+               wk->type = IEEE80211_WORK_ASSOC;
+               /* not really done yet */
+               return WORK_DONE_REQUEUE;
+       }
+
        mgmt = (void *)skb->data;
        status = le16_to_cpu(mgmt->u.assoc_resp.status_code);
 
@@ -2113,18 +2161,9 @@ static enum work_done_result ieee80211_assoc_done(struct ieee80211_work *wk,
                        cfg80211_send_assoc_timeout(wk->sdata->dev,
                                                    wk->filter_ta);
                        return WORK_DONE_DESTROY;
-               } else {
-                       mutex_unlock(&wk->sdata->u.mgd.mtx);
-#ifdef CONFIG_INET
-                       /*
-                        * configure ARP filter IP addresses to the driver,
-                        * intentionally outside the mgd mutex.
-                        */
-                       rtnl_lock();
-                       ieee80211_set_arp_filter(wk->sdata);
-                       rtnl_unlock();
-#endif
                }
+
+               mutex_unlock(&wk->sdata->u.mgd.mtx);
        }
 
        cfg80211_send_rx_assoc(wk->sdata->dev, skb->data, skb->len);
@@ -2224,10 +2263,14 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
        if (req->prev_bssid)
                memcpy(wk->assoc.prev_bssid, req->prev_bssid, ETH_ALEN);
 
-       wk->type = IEEE80211_WORK_ASSOC;
        wk->chan = req->bss->channel;
        wk->sdata = sdata;
        wk->done = ieee80211_assoc_done;
+       if (!bss->dtim_period &&
+           sdata->local->hw.flags & IEEE80211_HW_NEED_DTIM_PERIOD)
+               wk->type = IEEE80211_WORK_ASSOC_BEACON_WAIT;
+       else
+               wk->type = IEEE80211_WORK_ASSOC;
 
        if (req->use_mfp) {
                ifmgd->mfp = IEEE80211_MFP_REQUIRED;
@@ -2242,6 +2285,9 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
        else
                ifmgd->flags &= ~IEEE80211_STA_CONTROL_PORT;
 
+       sdata->control_port_protocol = req->crypto.control_port_ethertype;
+       sdata->control_port_no_encrypt = req->crypto.control_port_no_encrypt;
+
        ieee80211_add_work(wk);
        return 0;
 }
@@ -2253,27 +2299,30 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_work *wk;
-       const u8 *bssid = req->bss->bssid;
+       u8 bssid[ETH_ALEN];
+       bool assoc_bss = false;
 
        mutex_lock(&ifmgd->mtx);
 
+       memcpy(bssid, req->bss->bssid, ETH_ALEN);
        if (ifmgd->associated == req->bss) {
-               bssid = req->bss->bssid;
-               ieee80211_set_disassoc(sdata, true);
+               ieee80211_set_disassoc(sdata, false);
                mutex_unlock(&ifmgd->mtx);
+               assoc_bss = true;
        } else {
                bool not_auth_yet = false;
 
                mutex_unlock(&ifmgd->mtx);
 
-               mutex_lock(&local->work_mtx);
+               mutex_lock(&local->mtx);
                list_for_each_entry(wk, &local->work_list, list) {
                        if (wk->sdata != sdata)
                                continue;
 
                        if (wk->type != IEEE80211_WORK_DIRECT_PROBE &&
                            wk->type != IEEE80211_WORK_AUTH &&
-                           wk->type != IEEE80211_WORK_ASSOC)
+                           wk->type != IEEE80211_WORK_ASSOC &&
+                           wk->type != IEEE80211_WORK_ASSOC_BEACON_WAIT)
                                continue;
 
                        if (memcmp(req->bss->bssid, wk->filter_ta, ETH_ALEN))
@@ -2284,7 +2333,7 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
                        free_work(wk);
                        break;
                }
-               mutex_unlock(&local->work_mtx);
+               mutex_unlock(&local->mtx);
 
                /*
                 * If somebody requests authentication and we haven't
@@ -2306,8 +2355,12 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
        ieee80211_send_deauth_disassoc(sdata, bssid, IEEE80211_STYPE_DEAUTH,
                                       req->reason_code, cookie,
                                       !req->local_state_change);
+       if (assoc_bss)
+               sta_info_destroy_addr(sdata, bssid);
 
+       mutex_lock(&sdata->local->mtx);
        ieee80211_recalc_idle(sdata->local);
+       mutex_unlock(&sdata->local->mtx);
 
        return 0;
 }
@@ -2345,7 +2398,9 @@ int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata,
                        cookie, !req->local_state_change);
        sta_info_destroy_addr(sdata, bssid);
 
+       mutex_lock(&sdata->local->mtx);
        ieee80211_recalc_idle(sdata->local);
+       mutex_unlock(&sdata->local->mtx);
 
        return 0;
 }