#include "ieee80211_i.h"
 #include "driver-ops.h"
 
+static void ieee80211_free_tid_rx(struct rcu_head *h)
+{
+       struct tid_ampdu_rx *tid_rx =
+               container_of(h, struct tid_ampdu_rx, rcu_head);
+       int i;
+
+       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);
+}
+
 static void ___ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
                                            u16 initiator, u16 reason,
                                            bool from_timer)
 {
        struct ieee80211_local *local = sta->local;
        struct tid_ampdu_rx *tid_rx;
-       int i;
 
        spin_lock_bh(&sta->lock);
 
-       /* check if TID is in operational state */
-       if (!sta->ampdu_mlme.tid_active_rx[tid]) {
+       tid_rx = sta->ampdu_mlme.tid_rx[tid];
+
+       if (!tid_rx) {
                spin_unlock_bh(&sta->lock);
                return;
        }
 
-       sta->ampdu_mlme.tid_active_rx[tid] = false;
-
-       tid_rx = sta->ampdu_mlme.tid_rx[tid];
+       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",
                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);
 
        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,
        /* examine state machine */
        spin_lock_bh(&sta->lock);
 
-       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 "
        }
 
        /* 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",
 #endif
                goto end;
        }
-       /* 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 = sta->ampdu_mlme.tid_rx[tid];
+       /* rx 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);
 
        /* prepare reordering buffer */
        tid_agg_rx->reorder_buf =
 #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;
        }
 
 
        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;
        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);
 end:
        spin_unlock_bh(&sta->lock);
 
 
        for (i = 0; i < STA_TID_NUM; i++) {
                p += scnprintf(p, sizeof(buf) + buf - p, "%02d", i);
                p += scnprintf(p, sizeof(buf) + buf - p, "\t\t%x",
-                               sta->ampdu_mlme.tid_active_rx[i]);
+                               !!sta->ampdu_mlme.tid_rx[i]);
                p += scnprintf(p, sizeof(buf) + buf - p, "\t%#.2x",
-                               sta->ampdu_mlme.tid_active_rx[i] ?
+                               sta->ampdu_mlme.tid_rx[i] ?
                                sta->ampdu_mlme.tid_rx[i]->dialog_token : 0);
                p += scnprintf(p, sizeof(buf) + buf - p, "\t%#.3x",
-                               sta->ampdu_mlme.tid_active_rx[i] ?
+                               sta->ampdu_mlme.tid_rx[i] ?
                                sta->ampdu_mlme.tid_rx[i]->ssn : 0);
 
                p += scnprintf(p, sizeof(buf) + buf - p, "\t\t%x",
 
 
        tid = *ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_TID_MASK;
 
-       spin_lock(&sta->lock);
-
-       if (!sta->ampdu_mlme.tid_active_rx[tid])
-               goto dont_reorder_unlock;
-
-       tid_agg_rx = sta->ampdu_mlme.tid_rx[tid];
+       tid_agg_rx = rcu_dereference(sta->ampdu_mlme.tid_rx[tid]);
+       if (!tid_agg_rx)
+               goto dont_reorder;
 
        /* qos null data frames are excluded */
        if (unlikely(hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_NULLFUNC)))
-               goto dont_reorder_unlock;
+               goto dont_reorder;
 
        /* new, potentially un-ordered, ampdu frame - process it */
 
        /* if this mpdu is fragmented - terminate rx aggregation session */
        sc = le16_to_cpu(hdr->seq_ctrl);
        if (sc & IEEE80211_SCTL_FRAG) {
-               spin_unlock(&sta->lock);
                skb->pkt_type = IEEE80211_SDATA_QUEUE_TYPE_FRAME;
                skb_queue_tail(&rx->sdata->skb_queue, skb);
                ieee80211_queue_work(&local->hw, &rx->sdata->work);
                return;
        }
 
-       if (ieee80211_sta_manage_reorder_buf(hw, tid_agg_rx, skb, frames)) {
-               spin_unlock(&sta->lock);
+       /*
+        * No locking needed -- we will only ever process one
+        * RX packet at a time, and thus own tid_agg_rx. All
+        * other code manipulating it needs to (and does) make
+        * sure that we cannot get to it any more before doing
+        * anything with it.
+        */
+       if (ieee80211_sta_manage_reorder_buf(hw, tid_agg_rx, skb, frames))
                return;
-       }
 
- dont_reorder_unlock:
-       spin_unlock(&sta->lock);
  dont_reorder:
        __skb_queue_tail(frames, skb);
 }
                                  &bar_data, sizeof(bar_data)))
                        return RX_DROP_MONITOR;
 
-               spin_lock(&rx->sta->lock);
                tid = le16_to_cpu(bar_data.control) >> 12;
-               if (!rx->sta->ampdu_mlme.tid_active_rx[tid]) {
-                       spin_unlock(&rx->sta->lock);
+
+               tid_agg_rx = rcu_dereference(rx->sta->ampdu_mlme.tid_rx[tid]);
+               if (!tid_agg_rx)
                        return RX_DROP_MONITOR;
-               }
-               tid_agg_rx = rx->sta->ampdu_mlme.tid_rx[tid];
 
                start_seq_num = le16_to_cpu(bar_data.start_seq_num) >> 4;
 
                ieee80211_release_reorder_frames(hw, tid_agg_rx, start_seq_num,
                                                 frames);
                kfree_skb(skb);
-               spin_unlock(&rx->sta->lock);
                return RX_QUEUED;
        }
 
 
  * @buf_size: buffer size for incoming A-MPDUs
  * @timeout: reset timer value (in TUs).
  * @dialog_token: dialog token for aggregation session
+ * @rcu_head: RCU head used for freeing this struct
+ *
+ * This structure is protected by RCU and the per-station
+ * spinlock. Assignments to the array holding it must hold
+ * the spinlock, only the RX path can access it under RCU
+ * lock-free. The RX path, since it is single-threaded,
+ * can even modify the structure without locking since the
+ * only other modifications to it are done when the struct
+ * can not yet or no longer be found by the RX path.
  */
 struct tid_ampdu_rx {
+       struct rcu_head rcu_head;
        struct sk_buff **reorder_buf;
        unsigned long *reorder_time;
        struct timer_list session_timer;
 /**
  * struct sta_ampdu_mlme - STA aggregation information.
  *
- * @tid_active_rx: TID's state in Rx session state machine.
- * @tid_rx: aggregation info for Rx per TID
+ * @tid_rx: aggregation info for Rx per TID -- RCU protected
  * @tid_state_tx: TID's state in Tx session state machine.
  * @tid_tx: aggregation info for Tx per TID
  * @addba_req_num: number of times addBA request has been sent.
  */
 struct sta_ampdu_mlme {
        /* rx */
-       bool tid_active_rx[STA_TID_NUM];
        struct tid_ampdu_rx *tid_rx[STA_TID_NUM];
        /* tx */
        u8 tid_state_tx[STA_TID_NUM];