* 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
        struct ieee80211_local *local = sta->local;
        struct tid_ampdu_rx *tid_rx;
 
-       lockdep_assert_held(&sta->lock);
+       lockdep_assert_held(&sta->ampdu_mlme.mtx);
 
        tid_rx = sta->ampdu_mlme.tid_rx[tid];
 
 void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
                                    u16 initiator, u16 reason)
 {
-       spin_lock_bh(&sta->lock);
+       mutex_lock(&sta->ampdu_mlme.mtx);
        ___ieee80211_stop_rx_ba_session(sta, tid, initiator, reason);
-       spin_unlock_bh(&sta->lock);
+       mutex_unlock(&sta->ampdu_mlme.mtx);
 }
 
 /*
 
 
        /* examine state machine */
-       spin_lock_bh(&sta->lock);
+       mutex_lock(&sta->ampdu_mlme.mtx);
 
        if (sta->ampdu_mlme.tid_rx[tid]) {
 #ifdef CONFIG_MAC80211_HT_DEBUG
                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,
 
                                   u16 *ssn)
 {
        int ret = -EOPNOTSUPP;
+       local_bh_disable();
        if (local->ops->ampdu_action)
                ret = local->ops->ampdu_action(&local->hw, &sdata->vif, action,
                                               sta, tid, ssn);
+       local_bh_enable();
        trace_drv_ampdu_action(local, sdata, action, sta, tid, ssn, ret);
        return ret;
 }
 
        if (test_sta_flags(sta, WLAN_STA_BLOCK_BA))
                return;
 
-       spin_lock_bh(&sta->lock);
+       mutex_lock(&sta->ampdu_mlme.mtx);
        for (tid = 0; tid < STA_TID_NUM; tid++) {
                if (test_and_clear_bit(tid, sta->ampdu_mlme.tid_rx_timer_expired))
                        ___ieee80211_stop_rx_ba_session(
                                sta, tid, WLAN_BACK_RECIPIENT,
                                WLAN_REASON_QSTA_TIMEOUT);
+       }
+       mutex_unlock(&sta->ampdu_mlme.mtx);
 
+       spin_lock_bh(&sta->lock);
+       for (tid = 0; tid < STA_TID_NUM; tid++) {
                tid_tx = sta->ampdu_mlme.tid_tx[tid];
                if (!tid_tx)
                        continue;
 
                           mgmt->u.action.category == WLAN_CATEGORY_BACK) {
                        int len = skb->len;
 
-                       rcu_read_lock();
+                       mutex_lock(&local->sta_mtx);
                        sta = sta_info_get(sdata, mgmt->sa);
                        if (sta) {
                                switch (mgmt->u.action.u.addba_req.action_code) {
                                        break;
                                }
                        }
-                       rcu_read_unlock();
+                       mutex_unlock(&local->sta_mtx);
                } else if (ieee80211_is_data_qos(mgmt->frame_control)) {
                        struct ieee80211_hdr *hdr = (void *)mgmt;
                        /*
                         * a block-ack session was active. That cannot be
                         * right, so terminate the session.
                         */
-                       rcu_read_lock();
+                       mutex_lock(&local->sta_mtx);
                        sta = sta_info_get(sdata, mgmt->sa);
                        if (sta) {
                                u16 tid = *ieee80211_get_qos_ctl(hdr) &
                                        sta, tid, WLAN_BACK_RECIPIENT,
                                        WLAN_REASON_QSTA_REQUIRE_SETUP);
                        }
-                       rcu_read_unlock();
+                       mutex_unlock(&local->sta_mtx);
                } else switch (sdata->vif.type) {
                case NL80211_IFTYPE_STATION:
                        ieee80211_sta_rx_queued_mgmt(sdata, skb);
 
        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);
 
        memcpy(sta->sta.addr, addr, ETH_ALEN);
        sta->local = local;
 
  * @work: work struct for starting/stopping aggregation
  * @tid_rx_timer_expired: bitmap indicating on which TIDs the
  *     RX timer expired until the work for it runs
+ * @mtx: mutex to protect all TX data (except non-NULL assignments
+ *     to tid_tx[idx], which are protected by the sta spinlock)
  */
 struct sta_ampdu_mlme {
+       struct mutex mtx;
        /* rx */
        struct tid_ampdu_rx *tid_rx[STA_TID_NUM];
        unsigned long tid_rx_timer_expired[BITS_TO_LONGS(STA_TID_NUM)];