net: add a noref bit on skb dst
[safe/jmp/linux-2.6] / net / mac80211 / mlme.c
index 86f025b..3093e46 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/pm_qos_params.h>
 #include <linux/crc32.h>
+#include <linux/slab.h>
 #include <net/mac80211.h>
 #include <asm/unaligned.h>
 
 #include "rate.h"
 #include "led.h"
 
-#define IEEE80211_AUTH_TIMEOUT (HZ / 5)
-#define IEEE80211_AUTH_MAX_TRIES 3
-#define IEEE80211_ASSOC_TIMEOUT (HZ / 5)
-#define IEEE80211_ASSOC_MAX_TRIES 3
 #define IEEE80211_MAX_PROBE_TRIES 5
 
 /*
  */
 #define IEEE80211_PROBE_WAIT           (HZ / 2)
 
+/*
+ * Weight given to the latest Beacon frame when calculating average signal
+ * strength for Beacon frames received in the current BSS. This must be
+ * between 1 and 15.
+ */
+#define IEEE80211_SIGNAL_AVE_WEIGHT    3
+
 #define TMR_RUNNING_TIMER      0
 #define TMR_RUNNING_CHANSW     1
 
@@ -133,11 +137,14 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
        struct sta_info *sta;
        u32 changed = 0;
        u16 ht_opmode;
-       bool enable_ht = true, ht_changed;
+       bool enable_ht = true;
+       enum nl80211_channel_type prev_chantype;
        enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
 
        sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
 
+       prev_chantype = sdata->vif.bss_conf.channel_type;
+
        /* HT is not supported */
        if (!sband->ht_cap.ht_supported)
                enable_ht = false;
@@ -168,35 +175,37 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
                }
        }
 
-       ht_changed = conf_is_ht(&local->hw.conf) != enable_ht ||
-                    channel_type != local->hw.conf.channel_type;
+       if (local->tmp_channel)
+               local->tmp_channel_type = channel_type;
 
-       local->oper_channel_type = channel_type;
+       if (!ieee80211_set_channel_type(local, sdata, channel_type)) {
+               /* can only fail due to HT40+/- mismatch */
+               channel_type = NL80211_CHAN_HT20;
+               WARN_ON(!ieee80211_set_channel_type(local, sdata, channel_type));
+       }
 
-       if (ht_changed) {
-                /* channel_type change automatically detected */
-               ieee80211_hw_config(local, 0);
+       /* channel_type change automatically detected */
+       ieee80211_hw_config(local, 0);
 
+       if (prev_chantype != channel_type) {
                rcu_read_lock();
                sta = sta_info_get(sdata, bssid);
                if (sta)
                        rate_control_rate_update(local, sband, sta,
-                                                IEEE80211_RC_HT_CHANGED);
+                                                IEEE80211_RC_HT_CHANGED,
+                                                channel_type);
                rcu_read_unlock();
-        }
-
-       /* disable HT */
-       if (!enable_ht)
-               return 0;
+       }
 
        ht_opmode = le16_to_cpu(hti->operation_mode);
 
        /* if bss configuration changed store the new one */
-       if (!sdata->ht_opmode_valid ||
-           sdata->vif.bss_conf.ht_operation_mode != ht_opmode) {
+       if (sdata->ht_opmode_valid != enable_ht ||
+           sdata->vif.bss_conf.ht_operation_mode != ht_opmode ||
+           prev_chantype != channel_type) {
                changed |= BSS_CHANGED_HT;
                sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
-               sdata->ht_opmode_valid = true;
+               sdata->ht_opmode_valid = enable_ht;
        }
 
        return changed;
@@ -206,7 +215,7 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
 
 static void ieee80211_send_deauth_disassoc(struct ieee80211_sub_if_data *sdata,
                                           const u8 *bssid, u16 stype, u16 reason,
-                                          void *cookie)
+                                          void *cookie, bool send_frame)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
@@ -243,7 +252,11 @@ static void ieee80211_send_deauth_disassoc(struct ieee80211_sub_if_data *sdata,
                        cfg80211_send_disassoc(sdata->dev, (u8 *)mgmt, skb->len);
        if (!(ifmgd->flags & IEEE80211_STA_MFP_ENABLED))
                IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
-       ieee80211_tx_skb(sdata, skb);
+
+       if (send_frame)
+               ieee80211_tx_skb(sdata, skb);
+       else
+               kfree_skb(skb);
 }
 
 void ieee80211_send_pspoll(struct ieee80211_local *local,
@@ -438,6 +451,11 @@ static void ieee80211_enable_ps(struct ieee80211_local *local,
        } else {
                if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
                        ieee80211_send_nullfunc(local, sdata, 1);
+
+               if ((local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) &&
+                   (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS))
+                       return;
+
                conf->flags |= IEEE80211_CONF_PS;
                ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
        }
@@ -462,6 +480,7 @@ void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency)
 {
        struct ieee80211_sub_if_data *sdata, *found = NULL;
        int count = 0;
+       int timeout;
 
        if (!(local->hw.flags & IEEE80211_HW_SUPPORTS_PS)) {
                local->ps_sdata = NULL;
@@ -484,6 +503,7 @@ void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency)
 
        if (count == 1 && found->u.mgd.powersave &&
            found->u.mgd.associated &&
+           found->u.mgd.associated->beacon_ies &&
            !(found->u.mgd.flags & (IEEE80211_STA_BEACON_POLL |
                                    IEEE80211_STA_CONNECTION_POLL))) {
                s32 beaconint_us;
@@ -494,17 +514,45 @@ 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;
+               if (timeout < 0) {
+                       /*
+                        * 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
+                               timeout = 0;
+               }
+               local->hw.conf.dynamic_ps_timeout = timeout;
+
                if (beaconint_us > latency) {
                        local->ps_sdata = NULL;
                } else {
-                       u8 dtimper = found->vif.bss_conf.dtim_period;
+                       struct ieee80211_bss *bss;
                        int maxslp = 1;
+                       u8 dtimper;
+
+                       bss = (void *)found->u.mgd.associated->priv;
+                       dtimper = bss->dtim_period;
 
-                       if (dtimper > 1)
+                       /* If the TIM IE is invalid, pretend the value is 1 */
+                       if (!dtimper)
+                               dtimper = 1;
+                       else if (dtimper > 1)
                                maxslp = min_t(int, dtimper,
                                                    latency / beaconint_us);
 
                        local->hw.conf.max_sleep_period = maxslp;
+                       local->hw.conf.ps_dtim_period = dtimper;
                        local->ps_sdata = found;
                }
        } else {
@@ -536,6 +584,7 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
                container_of(work, struct ieee80211_local,
                             dynamic_ps_enable_work);
        struct ieee80211_sub_if_data *sdata = local->ps_sdata;
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 
        /* can only happen when PS was just disabled anyway */
        if (!sdata)
@@ -544,11 +593,17 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
        if (local->hw.conf.flags & IEEE80211_CONF_PS)
                return;
 
-       if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
+       if ((local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) &&
+           (!(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)))
                ieee80211_send_nullfunc(local, sdata, 1);
 
-       local->hw.conf.flags |= IEEE80211_CONF_PS;
-       ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+       if (!((local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) &&
+             (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)) ||
+           (ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)) {
+               ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
+               local->hw.conf.flags |= IEEE80211_CONF_PS;
+               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+       }
 }
 
 void ieee80211_dynamic_ps_timer(unsigned long data)
@@ -569,7 +624,10 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
        struct ieee80211_tx_queue_params params;
        size_t left;
        int count;
-       u8 *pos;
+       u8 *pos, uapsd_queues = 0;
+
+       if (!local->ops->conf_tx)
+               return;
 
        if (local->hw.queues < 4)
                return;
@@ -579,6 +637,10 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
 
        if (wmm_param_len < 8 || wmm_param[5] /* version */ != 1)
                return;
+
+       if (ifmgd->flags & IEEE80211_STA_UAPSD_ENABLED)
+               uapsd_queues = local->uapsd_queues;
+
        count = wmm_param[6] & 0x0f;
        if (count == ifmgd->wmm_last_param_set)
                return;
@@ -593,6 +655,7 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
        for (; left >= 4; left -= 4, pos += 4) {
                int aci = (pos[0] >> 5) & 0x03;
                int acm = (pos[0] >> 4) & 0x01;
+               bool uapsd = false;
                int queue;
 
                switch (aci) {
@@ -600,22 +663,30 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
                        queue = 3;
                        if (acm)
                                local->wmm_acm |= BIT(1) | BIT(2); /* BK/- */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
+                               uapsd = true;
                        break;
                case 2: /* AC_VI */
                        queue = 1;
                        if (acm)
                                local->wmm_acm |= BIT(4) | BIT(5); /* CL/VI */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
+                               uapsd = true;
                        break;
                case 3: /* AC_VO */
                        queue = 0;
                        if (acm)
                                local->wmm_acm |= BIT(6) | BIT(7); /* VO/NC */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
+                               uapsd = true;
                        break;
                case 0: /* AC_BE */
                default:
                        queue = 2;
                        if (acm)
                                local->wmm_acm |= BIT(0) | BIT(3); /* BE/EE */
+                       if (uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
+                               uapsd = true;
                        break;
                }
 
@@ -623,17 +694,24 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
                params.cw_max = ecw2cw((pos[1] & 0xf0) >> 4);
                params.cw_min = ecw2cw(pos[1] & 0x0f);
                params.txop = get_unaligned_le16(pos + 2);
+               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\n",
+                      "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.aifs, params.cw_min, params.cw_max, params.txop,
+                      params.uapsd);
 #endif
-               if (drv_conf_tx(local, queue, &params) && local->ops->conf_tx)
+               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);
        }
+
+       /* enable WMM or activate new settings */
+       local->hw.conf.flags |= IEEE80211_CONF_QOS;
+       drv_config(local, IEEE80211_CONF_CHANGE_QOS);
 }
 
 static u32 ieee80211_handle_bss_capability(struct ieee80211_sub_if_data *sdata,
@@ -654,6 +732,8 @@ static u32 ieee80211_handle_bss_capability(struct ieee80211_sub_if_data *sdata,
        }
 
        use_short_slot = !!(capab & WLAN_CAPABILITY_SHORT_SLOT_TIME);
+       if (sdata->local->hw.conf.channel->band == IEEE80211_BAND_5GHZ)
+               use_short_slot = true;
 
        if (use_protection != bss_conf->use_cts_prot) {
                bss_conf->use_cts_prot = use_protection;
@@ -684,7 +764,6 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
        /* set timing information */
        sdata->vif.bss_conf.beacon_int = cbss->beacon_interval;
        sdata->vif.bss_conf.timestamp = cbss->tsf;
-       sdata->vif.bss_conf.dtim_period = bss->dtim_period;
 
        bss_info_changed |= BSS_CHANGED_BEACON_INT;
        bss_info_changed |= ieee80211_handle_bss_capability(sdata,
@@ -693,6 +772,8 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
        sdata->u.mgd.associated = cbss;
        memcpy(sdata->u.mgd.bssid, cbss->bssid, ETH_ALEN);
 
+       sdata->u.mgd.flags |= IEEE80211_STA_RESET_SIGNAL_AVE;
+
        /* just to be sure */
        sdata->u.mgd.flags &= ~(IEEE80211_STA_CONNECTION_POLL |
                                IEEE80211_STA_BEACON_POLL);
@@ -718,6 +799,11 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
        /* And the BSSID changed - we're associated now */
        bss_info_changed |= BSS_CHANGED_BSSID;
 
+       /* 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_info_changed |= BSS_CHANGED_CQM;
+
        ieee80211_bss_info_change_notify(sdata, bss_info_changed);
 
        mutex_lock(&local->iflist_mtx);
@@ -729,7 +815,8 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
        netif_carrier_on(sdata->dev);
 }
 
-static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
+static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
+                                  bool remove_sta)
 {
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_local *local = sdata->local;
@@ -766,8 +853,10 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
 
        rcu_read_lock();
        sta = sta_info_get(sdata, bssid);
-       if (sta)
+       if (sta) {
+               set_sta_flags(sta, WLAN_STA_DISASSOC);
                ieee80211_sta_tear_down_BA_sessions(sta);
+       }
        rcu_read_unlock();
 
        changed |= ieee80211_reset_erp_info(sdata);
@@ -779,7 +868,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
        ieee80211_set_wmm_default(sdata);
 
        /* channel(_type) changes are handled by ieee80211_hw_config */
-       local->oper_channel_type = NL80211_CHAN_NO_HT;
+       WARN_ON(!ieee80211_set_channel_type(local, sdata, NL80211_CHAN_NO_HT));
 
        /* on the next assoc, re-program HT parameters */
        sdata->ht_opmode_valid = false;
@@ -796,23 +885,12 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata)
 
        ieee80211_hw_config(local, config_changed);
 
-       /* And the BSSID changed -- not very interesting here */
-       changed |= BSS_CHANGED_BSSID;
+       /* The BSSID (not really interesting) and HT changed */
+       changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT;
        ieee80211_bss_info_change_notify(sdata, changed);
 
-       rcu_read_lock();
-
-       sta = sta_info_get(sdata, bssid);
-       if (!sta) {
-               rcu_read_unlock();
-               return;
-       }
-
-       sta_info_unlink(&sta);
-
-       rcu_read_unlock();
-
-       sta_info_destroy(sta);
+       if (remove_sta)
+               sta_info_destroy_addr(sdata, bssid);
 }
 
 void ieee80211_sta_rx_notify(struct ieee80211_sub_if_data *sdata,
@@ -829,6 +907,9 @@ 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));
 }
@@ -906,23 +987,72 @@ static void ieee80211_mgd_probe_ap(struct ieee80211_sub_if_data *sdata,
        mutex_unlock(&ifmgd->mtx);
 }
 
-void ieee80211_beacon_loss_work(struct work_struct *work)
+static void __ieee80211_connection_loss(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+       struct ieee80211_local *local = sdata->local;
+       u8 bssid[ETH_ALEN];
+
+       mutex_lock(&ifmgd->mtx);
+       if (!ifmgd->associated) {
+               mutex_unlock(&ifmgd->mtx);
+               return;
+       }
+
+       memcpy(bssid, ifmgd->associated->bssid, ETH_ALEN);
+
+       printk(KERN_DEBUG "Connection to AP %pM lost.\n", bssid);
+
+       ieee80211_set_disassoc(sdata, true);
+       ieee80211_recalc_idle(local);
+       mutex_unlock(&ifmgd->mtx);
+       /*
+        * must be outside lock due to cfg80211,
+        * but that's not a problem.
+        */
+       ieee80211_send_deauth_disassoc(sdata, bssid,
+                                      IEEE80211_STYPE_DEAUTH,
+                                      WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
+                                      NULL, true);
+}
+
+void ieee80211_beacon_connection_loss_work(struct work_struct *work)
 {
        struct ieee80211_sub_if_data *sdata =
                container_of(work, struct ieee80211_sub_if_data,
-                            u.mgd.beacon_loss_work);
+                            u.mgd.beacon_connection_loss_work);
 
-       ieee80211_mgd_probe_ap(sdata, true);
+       if (sdata->local->hw.flags & IEEE80211_HW_CONNECTION_MONITOR)
+               __ieee80211_connection_loss(sdata);
+       else
+               ieee80211_mgd_probe_ap(sdata, true);
 }
 
 void ieee80211_beacon_loss(struct ieee80211_vif *vif)
 {
        struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_hw *hw = &sdata->local->hw;
+
+       trace_api_beacon_loss(sdata);
 
-       ieee80211_queue_work(&sdata->local->hw, &sdata->u.mgd.beacon_loss_work);
+       WARN_ON(hw->flags & IEEE80211_HW_CONNECTION_MONITOR);
+       ieee80211_queue_work(hw, &sdata->u.mgd.beacon_connection_loss_work);
 }
 EXPORT_SYMBOL(ieee80211_beacon_loss);
 
+void ieee80211_connection_loss(struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct ieee80211_hw *hw = &sdata->local->hw;
+
+       trace_api_connection_loss(sdata);
+
+       WARN_ON(!(hw->flags & IEEE80211_HW_CONNECTION_MONITOR));
+       ieee80211_queue_work(hw, &sdata->u.mgd.beacon_connection_loss_work);
+}
+EXPORT_SYMBOL(ieee80211_connection_loss);
+
+
 static enum rx_mgmt_action __must_check
 ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
                         struct ieee80211_mgmt *mgmt, size_t len)
@@ -943,7 +1073,7 @@ ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
        printk(KERN_DEBUG "%s: deauthenticated from %pM (Reason: %u)\n",
                        sdata->name, bssid, reason_code);
 
-       ieee80211_set_disassoc(sdata);
+       ieee80211_set_disassoc(sdata, true);
        ieee80211_recalc_idle(sdata->local);
 
        return RX_MGMT_CFG80211_DEAUTH;
@@ -973,7 +1103,7 @@ ieee80211_rx_mgmt_disassoc(struct ieee80211_sub_if_data *sdata,
        printk(KERN_DEBUG "%s: disassociated from %pM (Reason: %u)\n",
                        sdata->name, mgmt->sa, reason_code);
 
-       ieee80211_set_disassoc(sdata);
+       ieee80211_set_disassoc(sdata, true);
        ieee80211_recalc_idle(sdata->local);
        return RX_MGMT_CFG80211_DISASSOC;
 }
@@ -1150,6 +1280,13 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
        int freq;
        struct ieee80211_bss *bss;
        struct ieee80211_channel *channel;
+       bool need_ps = false;
+
+       if (sdata->u.mgd.associated) {
+               bss = (void *)sdata->u.mgd.associated->priv;
+               /* not previously set so we may need to recalc */
+               need_ps = !bss->dtim_period;
+       }
 
        if (elems->ds_params && elems->ds_params_len == 1)
                freq = ieee80211_channel_to_frequency(elems->ds_params[0]);
@@ -1169,6 +1306,12 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
        if (!sdata->u.mgd.associated)
                return;
 
+       if (need_ps) {
+               mutex_lock(&local->iflist_mtx);
+               ieee80211_recalc_ps(local, -1);
+               mutex_unlock(&local->iflist_mtx);
+       }
+
        if (elems->ch_switch_elem && (elems->ch_switch_elem_len == 3) &&
            (memcmp(mgmt->bssid, sdata->u.mgd.associated->bssid,
                                                        ETH_ALEN) == 0)) {
@@ -1213,12 +1356,17 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_sub_if_data *sdata,
                mutex_lock(&sdata->local->iflist_mtx);
                ieee80211_recalc_ps(sdata->local, -1);
                mutex_unlock(&sdata->local->iflist_mtx);
+
+               if (sdata->local->hw.flags & IEEE80211_HW_CONNECTION_MONITOR)
+                       return;
+
                /*
                 * We've received a probe response, but are not sure whether
                 * we have or will be receiving any beacons or data, so let's
                 * schedule the timers again, just in case.
                 */
                mod_beacon_timer(sdata);
+
                mod_timer(&ifmgd->conn_mon_timer,
                          round_jiffies_up(jiffies +
                                           IEEE80211_CONNECTION_IDLE_TIME));
@@ -1252,6 +1400,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
                                     struct ieee80211_rx_status *rx_status)
 {
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+       struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf;
        size_t baselen;
        struct ieee802_11_elems elems;
        struct ieee80211_local *local = sdata->local;
@@ -1287,6 +1436,41 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
        if (memcmp(bssid, mgmt->bssid, ETH_ALEN) != 0)
                return;
 
+       /* Track average RSSI from the Beacon frames of the current AP */
+       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->last_cqm_event_signal = 0;
+       } else {
+               ifmgd->ave_beacon_signal =
+                       (IEEE80211_SIGNAL_AVE_WEIGHT * rx_status->signal * 16 +
+                        (16 - IEEE80211_SIGNAL_AVE_WEIGHT) *
+                        ifmgd->ave_beacon_signal) / 16;
+       }
+       if (bss_conf->cqm_rssi_thold &&
+           !(local->hw.flags & IEEE80211_HW_SUPPORTS_CQM_RSSI)) {
+               int sig = ifmgd->ave_beacon_signal / 16;
+               int last_event = ifmgd->last_cqm_event_signal;
+               int thold = bss_conf->cqm_rssi_thold;
+               int hyst = bss_conf->cqm_rssi_hyst;
+               if (sig < thold &&
+                   (last_event == 0 || sig < last_event - hyst)) {
+                       ifmgd->last_cqm_event_signal = sig;
+                       ieee80211_cqm_rssi_notify(
+                               &sdata->vif,
+                               NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW,
+                               GFP_KERNEL);
+               } else if (sig > thold &&
+                          (last_event == 0 || sig > last_event + hyst)) {
+                       ifmgd->last_cqm_event_signal = sig;
+                       ieee80211_cqm_rssi_notify(
+                               &sdata->vif,
+                               NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH,
+                               GFP_KERNEL);
+               }
+       }
+
        if (ifmgd->flags & IEEE80211_STA_BEACON_POLL) {
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
                if (net_ratelimit()) {
@@ -1460,7 +1644,9 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
                        rma = ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len);
                        break;
                case IEEE80211_STYPE_ACTION:
-                       /* XXX: differentiate, can only happen for CSA now! */
+                       if (mgmt->u.action.category != WLAN_CATEGORY_SPECTRUM_MGMT)
+                               break;
+
                        ieee80211_sta_process_chanswitch(sdata,
                                        &mgmt->u.action.u.chan_switch.sw_elem,
                                        (void *)ifmgd->associated->priv);
@@ -1570,7 +1756,7 @@ static void ieee80211_sta_work(struct work_struct *work)
                        printk(KERN_DEBUG "No probe response from AP %pM"
                                " after %dms, disconnecting.\n",
                                bssid, (1000 * IEEE80211_PROBE_WAIT)/HZ);
-                       ieee80211_set_disassoc(sdata);
+                       ieee80211_set_disassoc(sdata, true);
                        ieee80211_recalc_idle(local);
                        mutex_unlock(&ifmgd->mtx);
                        /*
@@ -1580,7 +1766,7 @@ static void ieee80211_sta_work(struct work_struct *work)
                        ieee80211_send_deauth_disassoc(sdata, bssid,
                                        IEEE80211_STYPE_DEAUTH,
                                        WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
-                                       NULL);
+                                       NULL, true);
                        mutex_lock(&ifmgd->mtx);
                }
        }
@@ -1597,7 +1783,8 @@ static void ieee80211_sta_bcn_mon_timer(unsigned long data)
        if (local->quiescing)
                return;
 
-       ieee80211_queue_work(&sdata->local->hw, &sdata->u.mgd.beacon_loss_work);
+       ieee80211_queue_work(&sdata->local->hw,
+                            &sdata->u.mgd.beacon_connection_loss_work);
 }
 
 static void ieee80211_sta_conn_mon_timer(unsigned long data)
@@ -1649,7 +1836,7 @@ void ieee80211_sta_quiesce(struct ieee80211_sub_if_data *sdata)
         */
 
        cancel_work_sync(&ifmgd->work);
-       cancel_work_sync(&ifmgd->beacon_loss_work);
+       cancel_work_sync(&ifmgd->beacon_connection_loss_work);
        if (del_timer_sync(&ifmgd->timer))
                set_bit(TMR_RUNNING_TIMER, &ifmgd->timers_running);
 
@@ -1683,7 +1870,8 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
        INIT_WORK(&ifmgd->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_loss_work, ieee80211_beacon_loss_work);
+       INIT_WORK(&ifmgd->beacon_connection_loss_work,
+                 ieee80211_beacon_connection_loss_work);
        setup_timer(&ifmgd->timer, ieee80211_sta_timer,
                    (unsigned long) sdata);
        setup_timer(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer,
@@ -1762,6 +1950,9 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_work *wk;
        u16 auth_alg;
 
+       if (req->local_state_change)
+               return 0; /* no need to update mac80211 state */
+
        switch (req->auth_type) {
        case NL80211_AUTHTYPE_OPEN_SYSTEM:
                auth_alg = WLAN_AUTH_OPEN;
@@ -1803,7 +1994,11 @@ int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
        wk->probe_auth.algorithm = auth_alg;
        wk->probe_auth.privacy = req->bss->capability & WLAN_CAPABILITY_PRIVACY;
 
-       wk->type = IEEE80211_WORK_DIRECT_PROBE;
+       /* if we already have a probe, don't probe again */
+       if (req->bss->proberesp_ies)
+               wk->type = IEEE80211_WORK_AUTH;
+       else
+               wk->type = IEEE80211_WORK_DIRECT_PROBE;
        wk->chan = req->bss->channel;
        wk->sdata = sdata;
        wk->done = ieee80211_probe_auth_done;
@@ -1853,8 +2048,20 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
 
        mutex_lock(&ifmgd->mtx);
        if (ifmgd->associated) {
-               mutex_unlock(&ifmgd->mtx);
-               return -EALREADY;
+               if (!req->prev_bssid ||
+                   memcmp(req->prev_bssid, ifmgd->associated->bssid,
+                          ETH_ALEN)) {
+                       /*
+                        * We are already associated and the request was not a
+                        * reassociation request from the current BSS, so
+                        * reject it.
+                        */
+                       mutex_unlock(&ifmgd->mtx);
+                       return -EALREADY;
+               }
+
+               /* Trying to reassociate - clear previous association state */
+               ieee80211_set_disassoc(sdata, true);
        }
        mutex_unlock(&ifmgd->mtx);
 
@@ -1863,6 +2070,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
                return -ENOMEM;
 
        ifmgd->flags &= ~IEEE80211_STA_DISABLE_11N;
+       ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
 
        for (i = 0; i < req->crypto.n_ciphers_pairwise; i++)
                if (req->crypto.ciphers_pairwise[i] == WLAN_CIPHER_SUITE_WEP40 ||
@@ -1906,6 +2114,15 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
        wk->assoc.ht_information_ie =
                ieee80211_bss_get_ie(req->bss, WLAN_EID_HT_INFORMATION);
 
+       if (bss->wmm_used && bss->uapsd_supported &&
+           (sdata->local->hw.flags & IEEE80211_HW_SUPPORTS_UAPSD)) {
+               wk->assoc.uapsd_used = true;
+               ifmgd->flags |= IEEE80211_STA_UAPSD_ENABLED;
+       } else {
+               wk->assoc.uapsd_used = false;
+               ifmgd->flags &= ~IEEE80211_STA_UAPSD_ENABLED;
+       }
+
        ssid = ieee80211_bss_get_ie(req->bss, WLAN_EID_SSID);
        memcpy(wk->assoc.ssid, ssid + 2, ssid[1]);
        wk->assoc.ssid_len = ssid[1];
@@ -1948,7 +2165,7 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
 
        if (ifmgd->associated == req->bss) {
                bssid = req->bss->bssid;
-               ieee80211_set_disassoc(sdata);
+               ieee80211_set_disassoc(sdata, true);
                mutex_unlock(&ifmgd->mtx);
        } else {
                bool not_auth_yet = false;
@@ -1957,12 +2174,19 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
 
                mutex_lock(&local->work_mtx);
                list_for_each_entry(wk, &local->work_list, list) {
-                       if (wk->type != IEEE80211_WORK_DIRECT_PROBE)
+                       if (wk->sdata != sdata)
                                continue;
+
+                       if (wk->type != IEEE80211_WORK_DIRECT_PROBE &&
+                           wk->type != IEEE80211_WORK_AUTH &&
+                           wk->type != IEEE80211_WORK_ASSOC)
+                               continue;
+
                        if (memcmp(req->bss->bssid, wk->filter_ta, ETH_ALEN))
                                continue;
-                       not_auth_yet = true;
-                       list_del(&wk->list);
+
+                       not_auth_yet = wk->type == IEEE80211_WORK_DIRECT_PROBE;
+                       list_del_rcu(&wk->list);
                        free_work(wk);
                        break;
                }
@@ -1985,9 +2209,9 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
        printk(KERN_DEBUG "%s: deauthenticating from %pM by local choice (reason=%d)\n",
               sdata->name, bssid, req->reason_code);
 
-       ieee80211_send_deauth_disassoc(sdata, bssid,
-                       IEEE80211_STYPE_DEAUTH, req->reason_code,
-                       cookie);
+       ieee80211_send_deauth_disassoc(sdata, bssid, IEEE80211_STYPE_DEAUTH,
+                                      req->reason_code, cookie,
+                                      !req->local_state_change);
 
        ieee80211_recalc_idle(sdata->local);
 
@@ -1999,6 +2223,7 @@ int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata,
                           void *cookie)
 {
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+       u8 bssid[ETH_ALEN];
 
        mutex_lock(&ifmgd->mtx);
 
@@ -2016,15 +2241,64 @@ int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata,
        printk(KERN_DEBUG "%s: disassociating from %pM by local choice (reason=%d)\n",
               sdata->name, req->bss->bssid, req->reason_code);
 
-       ieee80211_set_disassoc(sdata);
+       memcpy(bssid, req->bss->bssid, ETH_ALEN);
+       ieee80211_set_disassoc(sdata, false);
 
        mutex_unlock(&ifmgd->mtx);
 
        ieee80211_send_deauth_disassoc(sdata, req->bss->bssid,
                        IEEE80211_STYPE_DISASSOC, req->reason_code,
-                       cookie);
+                       cookie, !req->local_state_change);
+       sta_info_destroy_addr(sdata, bssid);
 
        ieee80211_recalc_idle(sdata->local);
 
        return 0;
 }
+
+int ieee80211_mgd_action(struct ieee80211_sub_if_data *sdata,
+                        struct ieee80211_channel *chan,
+                        enum nl80211_channel_type channel_type,
+                        const u8 *buf, size_t len, u64 *cookie)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+       struct sk_buff *skb;
+
+       /* Check that we are on the requested channel for transmission */
+       if ((chan != local->tmp_channel ||
+            channel_type != local->tmp_channel_type) &&
+           (chan != local->oper_channel ||
+            channel_type != local->_oper_channel_type))
+               return -EBUSY;
+
+       skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
+       if (!skb)
+               return -ENOMEM;
+       skb_reserve(skb, local->hw.extra_tx_headroom);
+
+       memcpy(skb_put(skb, len), buf, len);
+
+       if (!(ifmgd->flags & IEEE80211_STA_MFP_ENABLED))
+               IEEE80211_SKB_CB(skb)->flags |=
+                       IEEE80211_TX_INTFL_DONT_ENCRYPT;
+       IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_NL80211_FRAME_TX |
+               IEEE80211_TX_CTL_REQ_TX_STATUS;
+       skb->dev = sdata->dev;
+       ieee80211_tx_skb(sdata, skb);
+
+       *cookie = (unsigned long) skb;
+       return 0;
+}
+
+void ieee80211_cqm_rssi_notify(struct ieee80211_vif *vif,
+                              enum nl80211_cqm_rssi_threshold_event rssi_event,
+                              gfp_t gfp)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+       trace_api_cqm_rssi_notify(sdata, rssi_event);
+
+       cfg80211_cqm_rssi_notify(sdata->dev, rssi_event, gfp);
+}
+EXPORT_SYMBOL(ieee80211_cqm_rssi_notify);