Merge branch 'master' of git://git.infradead.org/users/linville/wireless-next into...
[pandora-kernel.git] / net / mac80211 / sta_info.c
index f9079e4..ce962d2 100644 (file)
@@ -24,6 +24,7 @@
 #include "sta_info.h"
 #include "debugfs_sta.h"
 #include "mesh.h"
+#include "wme.h"
 
 /**
  * DOC: STA information lifetime rules
@@ -72,7 +73,7 @@ static int sta_info_hash_del(struct ieee80211_local *local,
        if (!s)
                return -ENOENT;
        if (s == sta) {
-               rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)],
+               RCU_INIT_POINTER(local->sta_hash[STA_HASH(sta->sta.addr)],
                                   s->hnext);
                return 0;
        }
@@ -82,7 +83,7 @@ static int sta_info_hash_del(struct ieee80211_local *local,
                s = rcu_dereference_protected(s->hnext,
                                        lockdep_is_held(&local->sta_lock));
        if (rcu_access_pointer(s->hnext)) {
-               rcu_assign_pointer(s->hnext, sta->hnext);
+               RCU_INIT_POINTER(s->hnext, sta->hnext);
                return 0;
        }
 
@@ -231,7 +232,7 @@ static void sta_info_hash_add(struct ieee80211_local *local,
                              struct sta_info *sta)
 {
        sta->hnext = local->sta_hash[STA_HASH(sta->sta.addr)];
-       rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
+       RCU_INIT_POINTER(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
 }
 
 static void sta_unblock(struct work_struct *wk)
@@ -243,16 +244,22 @@ static void sta_unblock(struct work_struct *wk)
        if (sta->dead)
                return;
 
-       if (!test_sta_flags(sta, WLAN_STA_PS_STA))
+       if (!test_sta_flag(sta, WLAN_STA_PS_STA))
                ieee80211_sta_ps_deliver_wakeup(sta);
-       else if (test_and_clear_sta_flags(sta, WLAN_STA_PSPOLL)) {
-               clear_sta_flags(sta, WLAN_STA_PS_DRIVER);
+       else if (test_and_clear_sta_flag(sta, WLAN_STA_PSPOLL)) {
+               clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
+
+               local_bh_disable();
                ieee80211_sta_ps_deliver_poll_response(sta);
-       } else if (test_and_clear_sta_flags(sta, WLAN_STA_UAPSD)) {
-               clear_sta_flags(sta, WLAN_STA_PS_DRIVER);
+               local_bh_enable();
+       } else if (test_and_clear_sta_flag(sta, WLAN_STA_UAPSD)) {
+               clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
+
+               local_bh_disable();
                ieee80211_sta_ps_deliver_uapsd(sta);
+               local_bh_enable();
        } else
-               clear_sta_flags(sta, WLAN_STA_PS_DRIVER);
+               clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
 }
 
 static int sta_prepare_rate_control(struct ieee80211_local *local,
@@ -285,7 +292,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
                return NULL;
 
        spin_lock_init(&sta->lock);
-       spin_lock_init(&sta->flaglock);
        INIT_WORK(&sta->drv_unblock_wk, sta_unblock);
        INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work);
        mutex_init(&sta->ampdu_mlme.mtx);
@@ -864,7 +870,7 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
         * sessions -- block that to make sure the tear-down
         * will be sufficient.
         */
-       set_sta_flags(sta, WLAN_STA_BLOCK_BA);
+       set_sta_flag(sta, WLAN_STA_BLOCK_BA);
        ieee80211_sta_tear_down_BA_sessions(sta, true);
 
        spin_lock_irqsave(&local->sta_lock, flags);
@@ -885,10 +891,13 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
 
        sta->dead = true;
 
-       if (test_and_clear_sta_flags(sta,
-                               WLAN_STA_PS_STA | WLAN_STA_PS_DRIVER)) {
+       if (test_sta_flag(sta, WLAN_STA_PS_STA) ||
+           test_sta_flag(sta, WLAN_STA_PS_DRIVER)) {
                BUG_ON(!sdata->bss);
 
+               clear_sta_flag(sta, WLAN_STA_PS_STA);
+               clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
+
                atomic_dec(&sdata->bss->num_sta_ps);
                sta_info_recalc_tim(sta);
        }
@@ -897,7 +906,7 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
        local->sta_generation++;
 
        if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
-               rcu_assign_pointer(sdata->u.vlan.sta, NULL);
+               RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
 
        if (sta->uploaded) {
                if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
@@ -1109,7 +1118,8 @@ static void clear_sta_ps_flags(void *_sta)
 {
        struct sta_info *sta = _sta;
 
-       clear_sta_flags(sta, WLAN_STA_PS_DRIVER | WLAN_STA_PS_STA);
+       clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
+       clear_sta_flag(sta, WLAN_STA_PS_STA);
 }
 
 /* powersave support code */
@@ -1120,7 +1130,7 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
        struct sk_buff_head pending;
        int filtered = 0, buffered = 0, ac;
 
-       clear_sta_flags(sta, WLAN_STA_SP);
+       clear_sta_flag(sta, WLAN_STA_SP);
 
        BUILD_BUG_ON(BITS_TO_LONGS(STA_TID_NUM) > 1);
        sta->driver_buffered_tids = 0;
@@ -1157,6 +1167,69 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
 #endif /* CONFIG_MAC80211_VERBOSE_PS_DEBUG */
 }
 
+static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata,
+                                        struct sta_info *sta, int tid,
+                                        enum ieee80211_frame_release_type reason)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_qos_hdr *nullfunc;
+       struct sk_buff *skb;
+       int size = sizeof(*nullfunc);
+       __le16 fc;
+       bool qos = test_sta_flag(sta, WLAN_STA_WME);
+       struct ieee80211_tx_info *info;
+
+       if (qos) {
+               fc = cpu_to_le16(IEEE80211_FTYPE_DATA |
+                                IEEE80211_STYPE_QOS_NULLFUNC |
+                                IEEE80211_FCTL_FROMDS);
+       } else {
+               size -= 2;
+               fc = cpu_to_le16(IEEE80211_FTYPE_DATA |
+                                IEEE80211_STYPE_NULLFUNC |
+                                IEEE80211_FCTL_FROMDS);
+       }
+
+       skb = dev_alloc_skb(local->hw.extra_tx_headroom + size);
+       if (!skb)
+               return;
+
+       skb_reserve(skb, local->hw.extra_tx_headroom);
+
+       nullfunc = (void *) skb_put(skb, size);
+       nullfunc->frame_control = fc;
+       nullfunc->duration_id = 0;
+       memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN);
+       memcpy(nullfunc->addr2, sdata->vif.addr, ETH_ALEN);
+       memcpy(nullfunc->addr3, sdata->vif.addr, ETH_ALEN);
+
+       skb->priority = tid;
+       skb_set_queue_mapping(skb, ieee802_1d_to_ac[tid]);
+       if (qos) {
+               nullfunc->qos_ctrl = cpu_to_le16(tid);
+
+               if (reason == IEEE80211_FRAME_RELEASE_UAPSD)
+                       nullfunc->qos_ctrl |=
+                               cpu_to_le16(IEEE80211_QOS_CTL_EOSP);
+       }
+
+       info = IEEE80211_SKB_CB(skb);
+
+       /*
+        * Tell TX path to send this frame even though the
+        * STA may still remain is PS mode after this frame
+        * exchange. Also set EOSP to indicate this packet
+        * ends the poll/service period.
+        */
+       info->flags |= IEEE80211_TX_CTL_POLL_RESPONSE |
+                      IEEE80211_TX_STATUS_EOSP |
+                      IEEE80211_TX_CTL_REQ_TX_STATUS;
+
+       drv_allow_buffered_frames(local, sta, BIT(tid), 1, reason, false);
+
+       ieee80211_xmit(sdata, skb);
+}
+
 static void
 ieee80211_sta_ps_deliver_response(struct sta_info *sta,
                                  int n_frames, u8 ignored_acs,
@@ -1170,6 +1243,9 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
        unsigned long driver_release_tids = 0;
        struct sk_buff_head frames;
 
+       /* Service or PS-Poll period starts */
+       set_sta_flag(sta, WLAN_STA_SP);
+
        __skb_queue_head_init(&frames);
 
        /*
@@ -1228,31 +1304,44 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
        }
 
        if (!found) {
-#ifdef CONFIG_MAC80211_VERBOSE_PS_DEBUG
+               int tid;
+
                /*
-                * FIXME: This can be the result of a race condition between
-                *        us expiring a frame and the station polling for it.
-                *        Should we send it a null-func frame indicating we
-                *        have nothing buffered for it?
+                * For PS-Poll, this can only happen due to a race condition
+                * when we set the TIM bit and the station notices it, but
+                * before it can poll for the frame we expire it.
+                *
+                * For uAPSD, this is said in the standard (11.2.1.5 h):
+                *      At each unscheduled SP for a non-AP STA, the AP shall
+                *      attempt to transmit at least one MSDU or MMPDU, but no
+                *      more than the value specified in the Max SP Length field
+                *      in the QoS Capability element from delivery-enabled ACs,
+                *      that are destined for the non-AP STA.
+                *
+                * Since we have no other MSDU/MMPDU, transmit a QoS null frame.
                 */
-               if (reason == IEEE80211_FRAME_RELEASE_PSPOLL)
-                       printk(KERN_DEBUG "%s: STA %pM sent PS Poll even "
-                              "though there are no buffered frames for it\n",
-                              sdata->name, sta->sta.addr);
-#endif /* CONFIG_MAC80211_VERBOSE_PS_DEBUG */
 
+               /* This will evaluate to 1, 3, 5 or 7. */
+               tid = 7 - ((ffs(~ignored_acs) - 1) << 1);
+
+               ieee80211_send_null_response(sdata, sta, tid, reason);
                return;
        }
 
        if (!driver_release_tids) {
                struct sk_buff_head pending;
                struct sk_buff *skb;
+               int num = 0;
+               u16 tids = 0;
 
                skb_queue_head_init(&pending);
 
                while ((skb = __skb_dequeue(&frames))) {
                        struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
                        struct ieee80211_hdr *hdr = (void *) skb->data;
+                       u8 *qoshdr = NULL;
+
+                       num++;
 
                        /*
                         * Tell TX path to send this frame even though the
@@ -1272,18 +1361,29 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
                                hdr->frame_control |=
                                        cpu_to_le16(IEEE80211_FCTL_MOREDATA);
 
+                       if (ieee80211_is_data_qos(hdr->frame_control) ||
+                           ieee80211_is_qos_nullfunc(hdr->frame_control))
+                               qoshdr = ieee80211_get_qos_ctl(hdr);
+
+                       /* set EOSP for the frame */
                        if (reason == IEEE80211_FRAME_RELEASE_UAPSD &&
-                           skb_queue_empty(&frames)) {
-                               /* set EOSP for the frame */
-                               u8 *p = ieee80211_get_qos_ctl(hdr);
-                               *p |= IEEE80211_QOS_CTL_EOSP;
-                               info->flags |= IEEE80211_TX_STATUS_EOSP |
-                                              IEEE80211_TX_CTL_REQ_TX_STATUS;
-                       }
+                           qoshdr && skb_queue_empty(&frames))
+                               *qoshdr |= IEEE80211_QOS_CTL_EOSP;
+
+                       info->flags |= IEEE80211_TX_STATUS_EOSP |
+                                      IEEE80211_TX_CTL_REQ_TX_STATUS;
+
+                       if (qoshdr)
+                               tids |= BIT(*qoshdr & IEEE80211_QOS_CTL_TID_MASK);
+                       else
+                               tids |= BIT(0);
 
                        __skb_queue_tail(&pending, skb);
                }
 
+               drv_allow_buffered_frames(local, sta, tids, num,
+                                         reason, more_data);
+
                ieee80211_add_pending_skbs(local, &pending);
 
                sta_info_recalc_tim(sta);
@@ -1342,9 +1442,6 @@ void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta)
        if (!delivery_enabled)
                return;
 
-       /* Ohh, finally, the service period starts :-) */
-       set_sta_flags(sta, WLAN_STA_SP);
-
        switch (sta->sta.max_sp) {
        case 1:
                n_frames = 2;
@@ -1373,12 +1470,37 @@ void ieee80211_sta_block_awake(struct ieee80211_hw *hw,
        trace_api_sta_block_awake(sta->local, pubsta, block);
 
        if (block)
-               set_sta_flags(sta, WLAN_STA_PS_DRIVER);
-       else if (test_sta_flags(sta, WLAN_STA_PS_DRIVER))
+               set_sta_flag(sta, WLAN_STA_PS_DRIVER);
+       else if (test_sta_flag(sta, WLAN_STA_PS_DRIVER))
                ieee80211_queue_work(hw, &sta->drv_unblock_wk);
 }
 EXPORT_SYMBOL(ieee80211_sta_block_awake);
 
+void ieee80211_sta_eosp_irqsafe(struct ieee80211_sta *pubsta)
+{
+       struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
+       struct ieee80211_local *local = sta->local;
+       struct sk_buff *skb;
+       struct skb_eosp_msg_data *data;
+
+       trace_api_eosp(local, pubsta);
+
+       skb = alloc_skb(0, GFP_ATOMIC);
+       if (!skb) {
+               /* too bad ... but race is better than loss */
+               clear_sta_flag(sta, WLAN_STA_SP);
+               return;
+       }
+
+       data = (void *)skb->cb;
+       memcpy(data->sta, pubsta->addr, ETH_ALEN);
+       memcpy(data->iface, sta->sdata->vif.addr, ETH_ALEN);
+       skb->pkt_type = IEEE80211_EOSP_MSG;
+       skb_queue_tail(&local->skb_queue, skb);
+       tasklet_schedule(&local->tasklet);
+}
+EXPORT_SYMBOL(ieee80211_sta_eosp_irqsafe);
+
 void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
                                u8 tid, bool buffered)
 {