Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next-2.6
[safe/jmp/linux-2.6] / net / mac80211 / mlme.c
index 57a3c62..0839c4e 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>
 
@@ -136,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;
@@ -171,36 +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,
-                                                local->oper_channel_type);
+                                                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;
@@ -210,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;
@@ -247,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,
@@ -333,7 +342,11 @@ static void ieee80211_chswitch_work(struct work_struct *work)
                goto out;
 
        sdata->local->oper_channel = sdata->local->csa_channel;
-       ieee80211_hw_config(sdata->local, IEEE80211_CONF_CHANGE_CHANNEL);
+       if (!sdata->local->ops->channel_switch) {
+               /* call "hw_config" only if doing sw channel switch */
+               ieee80211_hw_config(sdata->local,
+                       IEEE80211_CONF_CHANGE_CHANNEL);
+       }
 
        /* XXX: shouldn't really modify cfg80211-owned data! */
        ifmgd->associated->channel = sdata->local->oper_channel;
@@ -345,6 +358,29 @@ static void ieee80211_chswitch_work(struct work_struct *work)
        mutex_unlock(&ifmgd->mtx);
 }
 
+void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success)
+{
+       struct ieee80211_sub_if_data *sdata;
+       struct ieee80211_if_managed *ifmgd;
+
+       sdata = vif_to_sdata(vif);
+       ifmgd = &sdata->u.mgd;
+
+       trace_api_chswitch_done(sdata, success);
+       if (!success) {
+               /*
+                * If the channel switch was not successful, stay
+                * around on the old channel. We currently lack
+                * good handling of this situation, possibly we
+                * should just drop the association.
+                */
+               sdata->local->csa_channel = sdata->local->oper_channel;
+       }
+
+       ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work);
+}
+EXPORT_SYMBOL(ieee80211_chswitch_done);
+
 static void ieee80211_chswitch_timer(unsigned long data)
 {
        struct ieee80211_sub_if_data *sdata =
@@ -361,7 +397,8 @@ static void ieee80211_chswitch_timer(unsigned long data)
 
 void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                                      struct ieee80211_channel_sw_ie *sw_elem,
-                                     struct ieee80211_bss *bss)
+                                     struct ieee80211_bss *bss,
+                                     u64 timestamp)
 {
        struct cfg80211_bss *cbss =
                container_of((void *)bss, struct cfg80211_bss, priv);
@@ -389,10 +426,29 @@ void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 
        sdata->local->csa_channel = new_ch;
 
+       if (sdata->local->ops->channel_switch) {
+               /* use driver's channel switch callback */
+               struct ieee80211_channel_switch ch_switch;
+               memset(&ch_switch, 0, sizeof(ch_switch));
+               ch_switch.timestamp = timestamp;
+               if (sw_elem->mode) {
+                       ch_switch.block_tx = true;
+                       ieee80211_stop_queues_by_reason(&sdata->local->hw,
+                                       IEEE80211_QUEUE_STOP_REASON_CSA);
+               }
+               ch_switch.channel = new_ch;
+               ch_switch.count = sw_elem->count;
+               ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
+               drv_channel_switch(sdata->local, &ch_switch);
+               return;
+       }
+
+       /* channel switch handled in software */
        if (sw_elem->count <= 1) {
                ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work);
        } else {
-               ieee80211_stop_queues_by_reason(&sdata->local->hw,
+               if (sw_elem->mode)
+                       ieee80211_stop_queues_by_reason(&sdata->local->hw,
                                        IEEE80211_QUEUE_STOP_REASON_CSA);
                ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
                mod_timer(&ifmgd->chswitch_timer,
@@ -443,10 +499,12 @@ static void ieee80211_enable_ps(struct ieee80211_local *local,
                if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)
                        ieee80211_send_nullfunc(local, sdata, 1);
 
-               if (!(local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) {
-                       conf->flags |= IEEE80211_CONF_PS;
-                       ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
-               }
+               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);
        }
 }
 
@@ -469,6 +527,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;
@@ -497,11 +556,31 @@ void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency)
                s32 beaconint_us;
 
                if (latency < 0)
-                       latency = pm_qos_requirement(PM_QOS_NETWORK_LATENCY);
+                       latency = pm_qos_request(PM_QOS_NETWORK_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 {
@@ -565,7 +644,8 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
            (!(ifmgd->flags & IEEE80211_STA_NULLFUNC_ACKED)))
                ieee80211_send_nullfunc(local, sdata, 1);
 
-       if (!(local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) ||
+       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;
@@ -835,7 +915,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;
@@ -852,8 +932,8 @@ 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);
 
        if (remove_sta)
@@ -980,7 +1060,7 @@ static void __ieee80211_connection_loss(struct ieee80211_sub_if_data *sdata)
        ieee80211_send_deauth_disassoc(sdata, bssid,
                                       IEEE80211_STYPE_DEAUTH,
                                       WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
-                                      NULL);
+                                      NULL, true);
 }
 
 void ieee80211_beacon_connection_loss_work(struct work_struct *work)
@@ -1000,6 +1080,8 @@ 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);
+
        WARN_ON(hw->flags & IEEE80211_HW_CONNECTION_MONITOR);
        ieee80211_queue_work(hw, &sdata->u.mgd.beacon_connection_loss_work);
 }
@@ -1010,6 +1092,8 @@ 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);
 }
@@ -1280,7 +1364,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
                                                        ETH_ALEN) == 0)) {
                struct ieee80211_channel_sw_ie *sw_elem =
                        (struct ieee80211_channel_sw_ie *)elems->ch_switch_elem;
-               ieee80211_sta_process_chanswitch(sdata, sw_elem, bss);
+               ieee80211_sta_process_chanswitch(sdata, sw_elem,
+                                                bss, rx_status->mactime);
        }
 }
 
@@ -1319,12 +1404,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));
@@ -1607,7 +1697,8 @@ static void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
 
                        ieee80211_sta_process_chanswitch(sdata,
                                        &mgmt->u.action.u.chan_switch.sw_elem,
-                                       (void *)ifmgd->associated->priv);
+                                       (void *)ifmgd->associated->priv,
+                                       rx_status->mactime);
                        break;
                }
                mutex_unlock(&ifmgd->mtx);
@@ -1724,7 +1815,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);
                }
        }
@@ -1908,6 +1999,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;
@@ -2133,7 +2227,8 @@ int ieee80211_mgd_deauth(struct ieee80211_sub_if_data *sdata,
                                continue;
 
                        if (wk->type != IEEE80211_WORK_DIRECT_PROBE &&
-                           wk->type != IEEE80211_WORK_AUTH)
+                           wk->type != IEEE80211_WORK_AUTH &&
+                           wk->type != IEEE80211_WORK_ASSOC)
                                continue;
 
                        if (memcmp(req->bss->bssid, wk->filter_ta, ETH_ALEN))
@@ -2163,9 +2258,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);
 
@@ -2202,7 +2297,7 @@ int ieee80211_mgd_disassoc(struct ieee80211_sub_if_data *sdata,
 
        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);
@@ -2223,7 +2318,7 @@ int ieee80211_mgd_action(struct ieee80211_sub_if_data *sdata,
        if ((chan != local->tmp_channel ||
             channel_type != local->tmp_channel_type) &&
            (chan != local->oper_channel ||
-            channel_type != local->oper_channel_type))
+            channel_type != local->_oper_channel_type))
                return -EBUSY;
 
        skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
@@ -2251,6 +2346,8 @@ void ieee80211_cqm_rssi_notify(struct ieee80211_vif *vif,
 {
        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);