]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/mac80211/mlme.c
Merge branch 'for-linus' of git://git.kernel.dk/linux-2.6-block
[karo-tx-linux.git] / net / mac80211 / mlme.c
index 47bc3030ca875fed70bd2a197fb2fcca48f69971..aca22b00b6a327873ee5ffc5a443ae541fdab5ed 100644 (file)
 #define IEEE80211_ASSOC_TIMEOUT (HZ / 5)
 #define IEEE80211_ASSOC_MAX_TRIES 3
 #define IEEE80211_MONITORING_INTERVAL (2 * HZ)
-#define IEEE80211_PROBE_WAIT (HZ / 20)
+#define IEEE80211_PROBE_WAIT (HZ / 5)
 #define IEEE80211_PROBE_IDLE_TIME (60 * HZ)
 #define IEEE80211_RETRY_AUTH_INTERVAL (1 * HZ)
 
+#define TMR_RUNNING_TIMER      0
+#define TMR_RUNNING_CHANSW     1
+
 /* utils */
 static int ecw2cw(int ecw)
 {
@@ -486,6 +489,108 @@ void ieee80211_send_nullfunc(struct ieee80211_local *local,
        ieee80211_tx_skb(sdata, skb, 0);
 }
 
+/* spectrum management related things */
+static void ieee80211_chswitch_work(struct work_struct *work)
+{
+       struct ieee80211_sub_if_data *sdata =
+               container_of(work, struct ieee80211_sub_if_data, u.mgd.chswitch_work);
+       struct ieee80211_bss *bss;
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+
+       if (!netif_running(sdata->dev))
+               return;
+
+       bss = ieee80211_rx_bss_get(sdata->local, ifmgd->bssid,
+                                  sdata->local->hw.conf.channel->center_freq,
+                                  ifmgd->ssid, ifmgd->ssid_len);
+       if (!bss)
+               goto exit;
+
+       sdata->local->oper_channel = sdata->local->csa_channel;
+       /* XXX: shouldn't really modify cfg80211-owned data! */
+       if (!ieee80211_hw_config(sdata->local, IEEE80211_CONF_CHANGE_CHANNEL))
+               bss->cbss.channel = sdata->local->oper_channel;
+
+       ieee80211_rx_bss_put(sdata->local, bss);
+exit:
+       ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED;
+       ieee80211_wake_queues_by_reason(&sdata->local->hw,
+                                       IEEE80211_QUEUE_STOP_REASON_CSA);
+}
+
+static void ieee80211_chswitch_timer(unsigned long data)
+{
+       struct ieee80211_sub_if_data *sdata =
+               (struct ieee80211_sub_if_data *) data;
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+
+       if (sdata->local->quiescing) {
+               set_bit(TMR_RUNNING_CHANSW, &ifmgd->timers_running);
+               return;
+       }
+
+       queue_work(sdata->local->hw.workqueue, &ifmgd->chswitch_work);
+}
+
+void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
+                                     struct ieee80211_channel_sw_ie *sw_elem,
+                                     struct ieee80211_bss *bss)
+{
+       struct ieee80211_channel *new_ch;
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+       int new_freq = ieee80211_channel_to_frequency(sw_elem->new_ch_num);
+
+       if (ifmgd->state != IEEE80211_STA_MLME_ASSOCIATED)
+               return;
+
+       if (sdata->local->sw_scanning || sdata->local->hw_scanning)
+               return;
+
+       /* Disregard subsequent beacons if we are already running a timer
+          processing a CSA */
+
+       if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED)
+               return;
+
+       new_ch = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq);
+       if (!new_ch || new_ch->flags & IEEE80211_CHAN_DISABLED)
+               return;
+
+       sdata->local->csa_channel = new_ch;
+
+       if (sw_elem->count <= 1) {
+               queue_work(sdata->local->hw.workqueue, &ifmgd->chswitch_work);
+       } else {
+               ieee80211_stop_queues_by_reason(&sdata->local->hw,
+                                       IEEE80211_QUEUE_STOP_REASON_CSA);
+               ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
+               mod_timer(&ifmgd->chswitch_timer,
+                         jiffies +
+                         msecs_to_jiffies(sw_elem->count *
+                                          bss->cbss.beacon_interval));
+       }
+}
+
+static void ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
+                                       u16 capab_info, u8 *pwr_constr_elem,
+                                       u8 pwr_constr_elem_len)
+{
+       struct ieee80211_conf *conf = &sdata->local->hw.conf;
+
+       if (!(capab_info & WLAN_CAPABILITY_SPECTRUM_MGMT))
+               return;
+
+       /* Power constraint IE length should be 1 octet */
+       if (pwr_constr_elem_len != 1)
+               return;
+
+       if ((*pwr_constr_elem <= conf->channel->max_power) &&
+           (*pwr_constr_elem != sdata->local->power_constr_level)) {
+               sdata->local->power_constr_level = *pwr_constr_elem;
+               ieee80211_hw_config(sdata->local, 0);
+       }
+}
+
 /* powersave */
 static void ieee80211_enable_ps(struct ieee80211_local *local,
                                struct ieee80211_sub_if_data *sdata)
@@ -516,9 +621,6 @@ static void ieee80211_change_ps(struct ieee80211_local *local)
        struct ieee80211_conf *conf = &local->hw.conf;
 
        if (local->ps_sdata) {
-               if (!(local->ps_sdata->u.mgd.flags & IEEE80211_STA_ASSOCIATED))
-                       return;
-
                ieee80211_enable_ps(local, local->ps_sdata);
        } else if (conf->flags & IEEE80211_CONF_PS) {
                conf->flags &= ~IEEE80211_CONF_PS;
@@ -548,7 +650,9 @@ void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency)
                count++;
        }
 
-       if (count == 1 && found->u.mgd.powersave) {
+       if (count == 1 && found->u.mgd.powersave &&
+           (found->u.mgd.flags & IEEE80211_STA_ASSOCIATED) &&
+           !(found->u.mgd.flags & IEEE80211_STA_PROBEREQ_POLL)) {
                s32 beaconint_us;
 
                if (latency < 0)
@@ -617,6 +721,9 @@ void ieee80211_dynamic_ps_timer(unsigned long data)
 {
        struct ieee80211_local *local = (void *) data;
 
+       if (local->quiescing)
+               return;
+
        queue_work(local->hw.workqueue, &local->dynamic_ps_enable_work);
 }
 
@@ -685,13 +792,13 @@ static void ieee80211_sta_wmm_params(struct ieee80211_local *local,
 #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",
-                      local->mdev->name, queue, aci, acm, params.aifs, params.cw_min,
-                      params.cw_max, params.txop);
+                      wiphy_name(local->hw.wiphy), queue, aci, acm,
+                      params.aifs, params.cw_min, params.cw_max, params.txop);
 #endif
                if (drv_conf_tx(local, queue, &params) && local->ops->conf_tx)
                        printk(KERN_DEBUG "%s: failed to set TX queue "
-                              "parameters for queue %d\n", local->mdev->name,
-                              queue);
+                              "parameters for queue %d\n",
+                              wiphy_name(local->hw.wiphy), queue);
        }
 }
 
@@ -869,6 +976,10 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
         * changed or not.
         */
        bss_info_changed |= BSS_CHANGED_BASIC_RATES;
+
+       /* And the BSSID changed - we're associated now */
+       bss_info_changed |= BSS_CHANGED_BSSID;
+
        ieee80211_bss_info_change_notify(sdata, bss_info_changed);
 
        /* will be same as sdata */
@@ -991,14 +1102,6 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
        struct sta_info *sta;
        u32 changed = 0, config_changed = 0;
 
-       rcu_read_lock();
-
-       sta = sta_info_get(local, ifmgd->bssid);
-       if (!sta) {
-               rcu_read_unlock();
-               return;
-       }
-
        if (deauth) {
                ifmgd->direct_probe_tries = 0;
                ifmgd->auth_tries = 0;
@@ -1009,7 +1112,11 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
        netif_tx_stop_all_queues(sdata->dev);
        netif_carrier_off(sdata->dev);
 
-       ieee80211_sta_tear_down_BA_sessions(sta);
+       rcu_read_lock();
+       sta = sta_info_get(local, ifmgd->bssid);
+       if (sta)
+               ieee80211_sta_tear_down_BA_sessions(sta);
+       rcu_read_unlock();
 
        bss = ieee80211_rx_bss_get(local, ifmgd->bssid,
                                   conf->channel->center_freq,
@@ -1045,8 +1152,6 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
                                ifmgd->ssid, ifmgd->ssid_len);
        }
 
-       rcu_read_unlock();
-
        ieee80211_set_wmm_default(sdata);
 
        ieee80211_recalc_idle(local);
@@ -1068,6 +1173,9 @@ 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;
        ieee80211_bss_info_change_notify(sdata, changed);
 
        rcu_read_lock();
@@ -1207,6 +1315,11 @@ void ieee80211_beacon_loss_work(struct work_struct *work)
 #endif
 
        ifmgd->flags |= IEEE80211_STA_PROBEREQ_POLL;
+
+       mutex_lock(&sdata->local->iflist_mtx);
+       ieee80211_recalc_ps(sdata->local, -1);
+       mutex_unlock(&sdata->local->iflist_mtx);
+
        ieee80211_send_probe_req(sdata, ifmgd->bssid, ifmgd->ssid,
                                 ifmgd->ssid_len, NULL, 0);
 
@@ -1227,6 +1340,7 @@ static void ieee80211_associated(struct ieee80211_sub_if_data *sdata)
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
+       unsigned long last_rx;
        bool disassoc = false;
 
        /* TODO: start monitoring current AP signal quality and number of
@@ -1243,17 +1357,21 @@ static void ieee80211_associated(struct ieee80211_sub_if_data *sdata)
                printk(KERN_DEBUG "%s: No STA entry for own AP %pM\n",
                       sdata->dev->name, ifmgd->bssid);
                disassoc = true;
-               goto unlock;
+               rcu_read_unlock();
+               goto out;
        }
 
+       last_rx = sta->last_rx;
+       rcu_read_unlock();
+
        if ((ifmgd->flags & IEEE80211_STA_PROBEREQ_POLL) &&
-           time_after(jiffies, sta->last_rx + IEEE80211_PROBE_WAIT)) {
+           time_after(jiffies, last_rx + IEEE80211_PROBE_WAIT)) {
                printk(KERN_DEBUG "%s: no probe response from AP %pM "
                       "- disassociating\n",
                       sdata->dev->name, ifmgd->bssid);
                disassoc = true;
                ifmgd->flags &= ~IEEE80211_STA_PROBEREQ_POLL;
-               goto unlock;
+               goto out;
        }
 
        /*
@@ -1272,27 +1390,31 @@ static void ieee80211_associated(struct ieee80211_sub_if_data *sdata)
                }
 #endif
                ifmgd->flags |= IEEE80211_STA_PROBEREQ_POLL;
+               mutex_lock(&local->iflist_mtx);
+               ieee80211_recalc_ps(local, -1);
+               mutex_unlock(&local->iflist_mtx);
                ieee80211_send_probe_req(sdata, ifmgd->bssid, ifmgd->ssid,
                                         ifmgd->ssid_len, NULL, 0);
-               goto unlock;
-
+               mod_timer(&ifmgd->timer, jiffies + IEEE80211_PROBE_WAIT);
+               goto out;
        }
 
-       if (time_after(jiffies, sta->last_rx + IEEE80211_PROBE_IDLE_TIME)) {
+       if (time_after(jiffies, last_rx + IEEE80211_PROBE_IDLE_TIME)) {
                ifmgd->flags |= IEEE80211_STA_PROBEREQ_POLL;
+               mutex_lock(&local->iflist_mtx);
+               ieee80211_recalc_ps(local, -1);
+               mutex_unlock(&local->iflist_mtx);
                ieee80211_send_probe_req(sdata, ifmgd->bssid, ifmgd->ssid,
                                         ifmgd->ssid_len, NULL, 0);
        }
 
- unlock:
-       rcu_read_unlock();
-
-       if (disassoc)
+ out:
+       if (!disassoc)
+               mod_timer(&ifmgd->timer,
+                         jiffies + IEEE80211_MONITORING_INTERVAL);
+       else
                ieee80211_set_disassoc(sdata, true, true,
                                        WLAN_REASON_PREV_AUTH_NOT_VALID);
-       else
-               mod_timer(&ifmgd->timer, jiffies +
-                                     IEEE80211_MONITORING_INTERVAL);
 }
 
 
@@ -1736,7 +1858,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
            (memcmp(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN) == 0)) {
                struct ieee80211_channel_sw_ie *sw_elem =
                        (struct ieee80211_channel_sw_ie *)elems->ch_switch_elem;
-               ieee80211_process_chanswitch(sdata, sw_elem, bss);
+               ieee80211_sta_process_chanswitch(sdata, sw_elem, bss);
        }
 
        ieee80211_rx_bss_put(local, bss);
@@ -1773,8 +1895,12 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_sub_if_data *sdata,
                ieee80211_authenticate(sdata);
        }
 
-       if (ifmgd->flags & IEEE80211_STA_PROBEREQ_POLL)
+       if (ifmgd->flags & IEEE80211_STA_PROBEREQ_POLL) {
                ifmgd->flags &= ~IEEE80211_STA_PROBEREQ_POLL;
+               mutex_lock(&sdata->local->iflist_mtx);
+               ieee80211_recalc_ps(sdata->local, -1);
+               mutex_unlock(&sdata->local->iflist_mtx);
+       }
 }
 
 /*
@@ -1832,6 +1958,9 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
                }
 #endif
                ifmgd->flags &= ~IEEE80211_STA_PROBEREQ_POLL;
+               mutex_lock(&local->iflist_mtx);
+               ieee80211_recalc_ps(local, -1);
+               mutex_unlock(&local->iflist_mtx);
        }
 
        ncrc = crc32_be(0, (void *)&mgmt->u.beacon.beacon_int, 4);
@@ -1843,16 +1972,13 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
                directed_tim = ieee80211_check_tim(elems.tim, elems.tim_len,
                                                   ifmgd->aid);
 
-       ncrc = crc32_be(ncrc, (void *)&directed_tim, sizeof(directed_tim));
-
-       if (ncrc == ifmgd->beacon_crc)
-               return;
-       ifmgd->beacon_crc = ncrc;
-
-       ieee80211_rx_bss_info(sdata, mgmt, len, rx_status, &elems, true);
+       if (ncrc != ifmgd->beacon_crc) {
+               ieee80211_rx_bss_info(sdata, mgmt, len, rx_status, &elems,
+                                     true);
 
-       ieee80211_sta_wmm_params(local, ifmgd, elems.wmm_param,
-                                elems.wmm_param_len);
+               ieee80211_sta_wmm_params(local, ifmgd, elems.wmm_param,
+                                        elems.wmm_param_len);
+       }
 
        if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) {
                if (directed_tim) {
@@ -1877,6 +2003,10 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
                }
        }
 
+       if (ncrc == ifmgd->beacon_crc)
+               return;
+       ifmgd->beacon_crc = ncrc;
+
        if (elems.erp_info && elems.erp_info_len >= 1) {
                erp_valid = true;
                erp_value = elems.erp_info[0];
@@ -2011,6 +2141,11 @@ static void ieee80211_sta_timer(unsigned long data)
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct ieee80211_local *local = sdata->local;
 
+       if (local->quiescing) {
+               set_bit(TMR_RUNNING_TIMER, &ifmgd->timers_running);
+               return;
+       }
+
        set_bit(IEEE80211_STA_REQ_RUN, &ifmgd->request);
        queue_work(local->hw.workqueue, &ifmgd->work);
 }
@@ -2082,7 +2217,10 @@ static int ieee80211_sta_config_auth(struct ieee80211_sub_if_data *sdata)
                                       capa_mask, capa_val);
 
        if (bss) {
-               ieee80211_set_freq(sdata, bss->cbss.channel->center_freq);
+               local->oper_channel = bss->cbss.channel;
+               local->oper_channel_type = NL80211_CHAN_NO_HT;
+               ieee80211_hw_config(local, 0);
+
                if (!(ifmgd->flags & IEEE80211_STA_SSID_SET))
                        ieee80211_sta_set_ssid(sdata, bss->ssid,
                                               bss->ssid_len);
@@ -2143,6 +2281,17 @@ static void ieee80211_sta_work(struct work_struct *work)
 
        if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION))
                return;
+
+       /*
+        * Nothing should have been stuffed into the workqueue during
+        * the suspend->resume cycle. If this WARN is seen then there
+        * is a bug with either the driver suspend or something in
+        * mac80211 stuffing into the workqueue which we haven't yet
+        * cleared during mac80211's suspend cycle.
+        */
+       if (WARN_ON(local->suspended))
+               return;
+
        ifmgd = &sdata->u.mgd;
 
        while ((skb = skb_dequeue(&ifmgd->skb_queue)))
@@ -2210,6 +2359,38 @@ static void ieee80211_restart_sta_timer(struct ieee80211_sub_if_data *sdata)
        }
 }
 
+#ifdef CONFIG_PM
+void ieee80211_sta_quiesce(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+
+       /*
+        * we need to use atomic bitops for the running bits
+        * only because both timers might fire at the same
+        * time -- the code here is properly synchronised.
+        */
+
+       cancel_work_sync(&ifmgd->work);
+       cancel_work_sync(&ifmgd->beacon_loss_work);
+       if (del_timer_sync(&ifmgd->timer))
+               set_bit(TMR_RUNNING_TIMER, &ifmgd->timers_running);
+
+       cancel_work_sync(&ifmgd->chswitch_work);
+       if (del_timer_sync(&ifmgd->chswitch_timer))
+               set_bit(TMR_RUNNING_CHANSW, &ifmgd->timers_running);
+}
+
+void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+
+       if (test_and_clear_bit(TMR_RUNNING_TIMER, &ifmgd->timers_running))
+               add_timer(&ifmgd->timer);
+       if (test_and_clear_bit(TMR_RUNNING_CHANSW, &ifmgd->timers_running))
+               add_timer(&ifmgd->chswitch_timer);
+}
+#endif
+
 /* interface setup */
 void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
 {
@@ -2261,6 +2442,14 @@ void ieee80211_sta_req_auth(struct ieee80211_sub_if_data *sdata)
                        ieee80211_set_disassoc(sdata, true, true,
                                               WLAN_REASON_DEAUTH_LEAVING);
 
+               if (ifmgd->ssid_len == 0) {
+                       /*
+                        * Only allow association to be started if a valid SSID
+                        * is configured.
+                        */
+                       return;
+               }
+
                if (!(ifmgd->flags & IEEE80211_STA_EXT_SME) ||
                    ifmgd->state != IEEE80211_STA_MLME_ASSOCIATE)
                        set_bit(IEEE80211_STA_REQ_AUTH, &ifmgd->request);
@@ -2292,6 +2481,10 @@ int ieee80211_sta_set_ssid(struct ieee80211_sub_if_data *sdata, char *ssid, size
        ifmgd = &sdata->u.mgd;
 
        if (ifmgd->ssid_len != len || memcmp(ifmgd->ssid, ssid, len) != 0) {
+               if (ifmgd->state == IEEE80211_STA_MLME_ASSOCIATED)
+                       ieee80211_set_disassoc(sdata, true, true,
+                                              WLAN_REASON_DEAUTH_LEAVING);
+
                /*
                 * Do not use reassociation if SSID is changed (different ESS).
                 */
@@ -2316,6 +2509,11 @@ int ieee80211_sta_set_bssid(struct ieee80211_sub_if_data *sdata, u8 *bssid)
 {
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 
+       if (compare_ether_addr(bssid, ifmgd->bssid) != 0 &&
+           ifmgd->state == IEEE80211_STA_MLME_ASSOCIATED)
+               ieee80211_set_disassoc(sdata, true, true,
+                                      WLAN_REASON_DEAUTH_LEAVING);
+
        if (is_valid_ether_addr(bssid)) {
                memcpy(ifmgd->bssid, bssid, ETH_ALEN);
                ifmgd->flags |= IEEE80211_STA_BSSID_SET;
@@ -2324,9 +2522,6 @@ int ieee80211_sta_set_bssid(struct ieee80211_sub_if_data *sdata, u8 *bssid)
                ifmgd->flags &= ~IEEE80211_STA_BSSID_SET;
        }
 
-       if (netif_running(sdata->dev))
-               ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BSSID);
-
        return ieee80211_sta_commit(sdata);
 }
 
@@ -2335,6 +2530,13 @@ int ieee80211_sta_set_extra_ie(struct ieee80211_sub_if_data *sdata,
 {
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 
+       if (len == 0 && ifmgd->extra_ie_len == 0)
+               return -EALREADY;
+
+       if (len == ifmgd->extra_ie_len && ifmgd->extra_ie &&
+           memcmp(ifmgd->extra_ie, ie, len) == 0)
+               return -EALREADY;
+
        kfree(ifmgd->extra_ie);
        if (len == 0) {
                ifmgd->extra_ie = NULL;