Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[firefly-linux-kernel-4.4.55.git] / net / mac80211 / agg-rx.c
index 6bb9a9a94960e9214e92f1debf023aa0ebb3b987..58eab9e8e4eedaaaef445cfc082546c1af1c1328 100644 (file)
@@ -6,39 +6,70 @@
  * Copyright 2005-2006, Devicescape Software, Inc.
  * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
  * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
- * Copyright 2007-2008, Intel Corporation
+ * Copyright 2007-2010, Intel Corporation
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
 
+/**
+ * DOC: RX A-MPDU aggregation
+ *
+ * Aggregation on the RX side requires only implementing the
+ * @ampdu_action callback that is invoked to start/stop any
+ * block-ack sessions for RX aggregation.
+ *
+ * When RX aggregation is started by the peer, the driver is
+ * notified via @ampdu_action function, with the
+ * %IEEE80211_AMPDU_RX_START action, and may reject the request
+ * in which case a negative response is sent to the peer, if it
+ * accepts it a positive response is sent.
+ *
+ * While the session is active, the device/driver are required
+ * to de-aggregate frames and pass them up one by one to mac80211,
+ * which will handle the reorder buffer.
+ *
+ * When the aggregation session is stopped again by the peer or
+ * ourselves, the driver's @ampdu_action function will be called
+ * with the action %IEEE80211_AMPDU_RX_STOP. In this case, the
+ * call must not fail.
+ */
+
 #include <linux/ieee80211.h>
 #include <linux/slab.h>
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
 #include "driver-ops.h"
 
-static void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
-                                           u16 initiator, u16 reason,
-                                           bool from_timer)
+static void ieee80211_free_tid_rx(struct rcu_head *h)
 {
-       struct ieee80211_local *local = sta->local;
-       struct tid_ampdu_rx *tid_rx;
+       struct tid_ampdu_rx *tid_rx =
+               container_of(h, struct tid_ampdu_rx, rcu_head);
        int i;
 
-       spin_lock_bh(&sta->lock);
+       for (i = 0; i < tid_rx->buf_size; i++)
+               dev_kfree_skb(tid_rx->reorder_buf[i]);
+       kfree(tid_rx->reorder_buf);
+       kfree(tid_rx->reorder_time);
+       kfree(tid_rx);
+}
 
-       /* check if TID is in operational state */
-       if (!sta->ampdu_mlme.tid_active_rx[tid]) {
-               spin_unlock_bh(&sta->lock);
-               return;
-       }
+void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
+                                    u16 initiator, u16 reason)
+{
+       struct ieee80211_local *local = sta->local;
+       struct tid_ampdu_rx *tid_rx;
 
-       sta->ampdu_mlme.tid_active_rx[tid] = false;
+       lockdep_assert_held(&sta->ampdu_mlme.mtx);
 
        tid_rx = sta->ampdu_mlme.tid_rx[tid];
 
+       if (!tid_rx)
+               return;
+
+       rcu_assign_pointer(sta->ampdu_mlme.tid_rx[tid], NULL);
+
 #ifdef CONFIG_MAC80211_HT_DEBUG
        printk(KERN_DEBUG "Rx BA session stop requested for %pM tid %u\n",
               sta->sta.addr, tid);
@@ -54,32 +85,18 @@ static void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
                ieee80211_send_delba(sta->sdata, sta->sta.addr,
                                     tid, 0, reason);
 
-       /* free the reordering buffer */
-       for (i = 0; i < tid_rx->buf_size; i++) {
-               if (tid_rx->reorder_buf[i]) {
-                       /* release the reordered frames */
-                       dev_kfree_skb(tid_rx->reorder_buf[i]);
-                       tid_rx->stored_mpdu_num--;
-                       tid_rx->reorder_buf[i] = NULL;
-               }
-       }
-
-       /* free resources */
-       kfree(tid_rx->reorder_buf);
-       kfree(tid_rx->reorder_time);
-       sta->ampdu_mlme.tid_rx[tid] = NULL;
-
-       spin_unlock_bh(&sta->lock);
+       del_timer_sync(&tid_rx->session_timer);
+       del_timer_sync(&tid_rx->reorder_timer);
 
-       if (!from_timer)
-               del_timer_sync(&tid_rx->session_timer);
-       kfree(tid_rx);
+       call_rcu(&tid_rx->rcu_head, ieee80211_free_tid_rx);
 }
 
 void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
                                    u16 initiator, u16 reason)
 {
-       ___ieee80211_stop_rx_ba_session(sta, tid, initiator, reason, false);
+       mutex_lock(&sta->ampdu_mlme.mtx);
+       ___ieee80211_stop_rx_ba_session(sta, tid, initiator, reason);
+       mutex_unlock(&sta->ampdu_mlme.mtx);
 }
 
 /*
@@ -100,8 +117,22 @@ static void sta_rx_agg_session_timer_expired(unsigned long data)
 #ifdef CONFIG_MAC80211_HT_DEBUG
        printk(KERN_DEBUG "rx session timer expired on tid %d\n", (u16)*ptid);
 #endif
-       ___ieee80211_stop_rx_ba_session(sta, *ptid, WLAN_BACK_RECIPIENT,
-                                       WLAN_REASON_QSTA_TIMEOUT, true);
+       set_bit(*ptid, sta->ampdu_mlme.tid_rx_timer_expired);
+       ieee80211_queue_work(&sta->local->hw, &sta->ampdu_mlme.work);
+}
+
+static void sta_rx_agg_reorder_timer_expired(unsigned long data)
+{
+       u8 *ptid = (u8 *)data;
+       u8 *timer_to_id = ptid - *ptid;
+       struct sta_info *sta = container_of(timer_to_id, struct sta_info,
+                       timer_to_tid[0]);
+
+       rcu_read_lock();
+       spin_lock(&sta->lock);
+       ieee80211_release_reorder_timeout(sta, *ptid);
+       spin_unlock(&sta->lock);
+       rcu_read_unlock();
 }
 
 static void ieee80211_send_addba_resp(struct ieee80211_sub_if_data *sdata, u8 *da, u16 tid,
@@ -212,9 +243,9 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
 
 
        /* examine state machine */
-       spin_lock_bh(&sta->lock);
+       mutex_lock(&sta->ampdu_mlme.mtx);
 
-       if (sta->ampdu_mlme.tid_active_rx[tid]) {
+       if (sta->ampdu_mlme.tid_rx[tid]) {
 #ifdef CONFIG_MAC80211_HT_DEBUG
                if (net_ratelimit())
                        printk(KERN_DEBUG "unexpected AddBA Req from "
@@ -225,9 +256,8 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
        }
 
        /* prepare A-MPDU MLME for Rx aggregation */
-       sta->ampdu_mlme.tid_rx[tid] =
-                       kmalloc(sizeof(struct tid_ampdu_rx), GFP_ATOMIC);
-       if (!sta->ampdu_mlme.tid_rx[tid]) {
+       tid_agg_rx = kmalloc(sizeof(struct tid_ampdu_rx), GFP_ATOMIC);
+       if (!tid_agg_rx) {
 #ifdef CONFIG_MAC80211_HT_DEBUG
                if (net_ratelimit())
                        printk(KERN_ERR "allocate rx mlme to tid %d failed\n",
@@ -235,14 +265,18 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
 #endif
                goto end;
        }
+
+       spin_lock_init(&tid_agg_rx->reorder_lock);
+
        /* rx timer */
-       sta->ampdu_mlme.tid_rx[tid]->session_timer.function =
-                               sta_rx_agg_session_timer_expired;
-       sta->ampdu_mlme.tid_rx[tid]->session_timer.data =
-                               (unsigned long)&sta->timer_to_tid[tid];
-       init_timer(&sta->ampdu_mlme.tid_rx[tid]->session_timer);
+       tid_agg_rx->session_timer.function = sta_rx_agg_session_timer_expired;
+       tid_agg_rx->session_timer.data = (unsigned long)&sta->timer_to_tid[tid];
+       init_timer(&tid_agg_rx->session_timer);
 
-       tid_agg_rx = sta->ampdu_mlme.tid_rx[tid];
+       /* rx reorder timer */
+       tid_agg_rx->reorder_timer.function = sta_rx_agg_reorder_timer_expired;
+       tid_agg_rx->reorder_timer.data = (unsigned long)&sta->timer_to_tid[tid];
+       init_timer(&tid_agg_rx->reorder_timer);
 
        /* prepare reordering buffer */
        tid_agg_rx->reorder_buf =
@@ -257,8 +291,7 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
 #endif
                kfree(tid_agg_rx->reorder_buf);
                kfree(tid_agg_rx->reorder_time);
-               kfree(sta->ampdu_mlme.tid_rx[tid]);
-               sta->ampdu_mlme.tid_rx[tid] = NULL;
+               kfree(tid_agg_rx);
                goto end;
        }
 
@@ -270,13 +303,12 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
 
        if (ret) {
                kfree(tid_agg_rx->reorder_buf);
+               kfree(tid_agg_rx->reorder_time);
                kfree(tid_agg_rx);
-               sta->ampdu_mlme.tid_rx[tid] = NULL;
                goto end;
        }
 
-       /* change state and send addba resp */
-       sta->ampdu_mlme.tid_active_rx[tid] = true;
+       /* update data */
        tid_agg_rx->dialog_token = dialog_token;
        tid_agg_rx->ssn = start_seq_num;
        tid_agg_rx->head_seq_num = start_seq_num;
@@ -284,8 +316,15 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
        tid_agg_rx->timeout = timeout;
        tid_agg_rx->stored_mpdu_num = 0;
        status = WLAN_STATUS_SUCCESS;
+
+       /* activate it for RX */
+       rcu_assign_pointer(sta->ampdu_mlme.tid_rx[tid], tid_agg_rx);
+
+       if (timeout)
+               mod_timer(&tid_agg_rx->session_timer, TU_TO_EXP_TIME(timeout));
+
 end:
-       spin_unlock_bh(&sta->lock);
+       mutex_unlock(&sta->ampdu_mlme.mtx);
 
 end_no_lock:
        ieee80211_send_addba_resp(sta->sdata, sta->sta.addr, tid,