iwlwifi: mvm: Use CS tx block bit for AP/GO
authorAndrei Otcheretianski <andrei.otcheretianski@intel.com>
Sun, 25 May 2014 14:24:22 +0000 (17:24 +0300)
committerEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Mon, 7 Jul 2014 18:41:21 +0000 (21:41 +0300)
An AP/GO may perform the channel switch slightly before its stations.
This scenario may result in packet loss, since the transmission may start
before the client is actually on a new channel. In order to prevent
potential packet loss disable tx to all the stations when the channel
switch flow starts. Clear the disable_tx bit when a station is seen on a
target channel, or after IWL_MVM_CS_UNBLOCK_TX_TIMEOUT beacons on a new
channel. In addition call ieee80211_sta_block_awake in order to inform
mac80211 that the frames for this station should be buffered.

Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c
drivers/net/wireless/iwlwifi/mvm/mac80211.c
drivers/net/wireless/iwlwifi/mvm/mvm.h
drivers/net/wireless/iwlwifi/mvm/rx.c
drivers/net/wireless/iwlwifi/mvm/sta.c
drivers/net/wireless/iwlwifi/mvm/sta.h
drivers/net/wireless/iwlwifi/mvm/tx.c

index a176d008dab14464eac2ca243acccfa741da7346..96b9cf8137e7f158df7e7db16cc32a779ca1f2be 100644 (file)
@@ -1237,6 +1237,7 @@ int iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm,
        struct iwl_rx_packet *pkt = rxb_addr(rxb);
        struct iwl_mvm_tx_resp *beacon_notify_hdr;
        struct ieee80211_vif *csa_vif;
+       struct ieee80211_vif *tx_blocked_vif;
        u64 tsf;
 
        lockdep_assert_held(&mvm->mutex);
@@ -1267,6 +1268,30 @@ int iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm,
        if (unlikely(csa_vif && csa_vif->csa_active))
                iwl_mvm_csa_count_down(mvm, csa_vif, mvm->ap_last_beacon_gp2);
 
+       tx_blocked_vif = rcu_dereference_protected(mvm->csa_tx_blocked_vif,
+                                               lockdep_is_held(&mvm->mutex));
+       if (unlikely(tx_blocked_vif)) {
+               struct iwl_mvm_vif *mvmvif =
+                       iwl_mvm_vif_from_mac80211(tx_blocked_vif);
+
+               /*
+                * The channel switch is started and we have blocked the
+                * stations. If this is the first beacon (the timeout wasn't
+                * set), set the unblock timeout, otherwise countdown
+                */
+               if (!mvm->csa_tx_block_bcn_timeout)
+                       mvm->csa_tx_block_bcn_timeout =
+                               IWL_MVM_CS_UNBLOCK_TX_TIMEOUT;
+               else
+                       mvm->csa_tx_block_bcn_timeout--;
+
+               /* Check if the timeout is expired, and unblock tx */
+               if (mvm->csa_tx_block_bcn_timeout == 0) {
+                       iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false);
+                       RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL);
+               }
+       }
+
        return 0;
 }
 
index 4a0350fc4b4eae215a35adb22a49f52990434826..5ed6fb32087cf07d78cefec8df75a78a160e0c69 100644 (file)
@@ -1614,6 +1614,11 @@ static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw,
                RCU_INIT_POINTER(mvm->csa_vif, NULL);
        }
 
+       if (rcu_access_pointer(mvm->csa_tx_blocked_vif) == vif) {
+               RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL);
+               mvm->csa_tx_block_bcn_timeout = 0;
+       }
+
        mvmvif->ap_ibss_active = false;
        mvm->ap_last_beacon_gp2 = 0;
 
@@ -2491,6 +2496,12 @@ static void __iwl_mvm_unassign_vif_chanctx(struct iwl_mvm *mvm,
                if (!vif->csa_active || !mvmvif->ap_ibss_active)
                        goto out;
 
+               /* Set CS bit on all the stations */
+               iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, true);
+
+               /* Save blocked iface, the timeout is set on the next beacon */
+               rcu_assign_pointer(mvm->csa_tx_blocked_vif, vif);
+
                mvmvif->ap_ibss_active = false;
                break;
        case NL80211_IFTYPE_STATION:
index db496c5c73fcf81873b646fe20025490d3047b46..7b308c4834f66686cad698104a5e4832df7717a8 100644 (file)
  */
 #define IWL_MVM_CHANNEL_SWITCH_MARGIN 4
 
+/*
+ * Number of beacons to transmit on a new channel until we unblock tx to
+ * the stations, even if we didn't identify them on a new channel
+ */
+#define IWL_MVM_CS_UNBLOCK_TX_TIMEOUT 3
+
 enum iwl_mvm_tx_fifo {
        IWL_MVM_TX_FIFO_BK = 0,
        IWL_MVM_TX_FIFO_BE,
@@ -671,6 +677,8 @@ struct iwl_mvm {
        bool ps_disabled;
 
        struct ieee80211_vif __rcu *csa_vif;
+       struct ieee80211_vif __rcu *csa_tx_blocked_vif;
+       u8 csa_tx_block_bcn_timeout;
 
        /* system time of last beacon (for AP/GO interface) */
        u32 ap_last_beacon_gp2;
index cf7276967acdec6439392c82e44e94de52d453c8..4b98987fc4133f6b7d7c0a21a1d11e57658a35f7 100644 (file)
@@ -258,6 +258,23 @@ int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb,
 
        memset(&rx_status, 0, sizeof(rx_status));
 
+       /*
+        * We have tx blocked stations (with CS bit). If we heard frames from
+        * a blocked station on a new channel we can TX to it again.
+        */
+       if (unlikely(mvm->csa_tx_block_bcn_timeout)) {
+               struct ieee80211_sta *sta;
+
+               rcu_read_lock();
+
+               sta = ieee80211_find_sta(
+                       rcu_dereference(mvm->csa_tx_blocked_vif), hdr->addr2);
+               if (sta)
+                       iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, false);
+
+               rcu_read_unlock();
+       }
+
        /*
         * drop the packet if it has failed being decrypted by HW
         */
index d3a6cf7558eb5d4351937da3a9427a296509cc04..81281396484724a9fc6cab21bb59b91b12ad52c1 100644 (file)
@@ -1468,3 +1468,57 @@ void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm,
        if (ret)
                IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret);
 }
+
+void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm,
+                                     struct ieee80211_sta *sta,
+                                     bool disable)
+{
+       struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
+
+       spin_lock_bh(&mvm_sta->lock);
+
+       if (mvm_sta->disable_tx == disable) {
+               spin_unlock_bh(&mvm_sta->lock);
+               return;
+       }
+
+       mvm_sta->disable_tx = disable;
+
+       /*
+        * Tell mac80211 to start/stop queueing tx for this station,
+        * but don't stop queueing if there are still pending frames
+        * for this station.
+        */
+       if (disable || !atomic_read(&mvm->pending_frames[mvm_sta->sta_id]))
+               ieee80211_sta_block_awake(mvm->hw, sta, disable);
+
+       iwl_mvm_sta_modify_disable_tx(mvm, mvm_sta, disable);
+
+       spin_unlock_bh(&mvm_sta->lock);
+}
+
+void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm,
+                                      struct iwl_mvm_vif *mvmvif,
+                                      bool disable)
+{
+       struct ieee80211_sta *sta;
+       struct iwl_mvm_sta *mvm_sta;
+       int i;
+
+       lockdep_assert_held(&mvm->mutex);
+
+       /* Block/unblock all the stations of the given mvmvif */
+       for (i = 0; i < IWL_MVM_STATION_COUNT; i++) {
+               sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i],
+                                               lockdep_is_held(&mvm->mutex));
+               if (IS_ERR_OR_NULL(sta))
+                       continue;
+
+               mvm_sta = iwl_mvm_sta_from_mac80211(sta);
+               if (mvm_sta->mac_id_n_color !=
+                   FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color))
+                       continue;
+
+               iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, disable);
+       }
+}
index 10c1a53526518cf2eea9359641fdeb044070a24f..3b1c8bd6cb54356c41c102402657cf765f617a50 100644 (file)
@@ -73,6 +73,7 @@
 #include "rs.h"
 
 struct iwl_mvm;
+struct iwl_mvm_vif;
 
 /**
  * DOC: station table - introduction
@@ -295,6 +296,7 @@ static inline u16 iwl_mvm_tid_queued(struct iwl_mvm_tid_data *tid_data)
  * @tid_data: per tid data. Look at %iwl_mvm_tid_data.
  * @tx_protection: reference counter for controlling the Tx protection.
  * @tt_tx_protection: is thermal throttling enable Tx protection?
+ * @disable_tx: is tx to this STA disabled?
  *
  * When mac80211 creates a station it reserves some space (hw->sta_data_size)
  * in the structure for use by driver. This structure is placed in that
@@ -317,6 +319,8 @@ struct iwl_mvm_sta {
        /* Temporary, until the new TLC will control the Tx protection */
        s8 tx_protection;
        bool tt_tx_protection;
+
+       bool disable_tx;
 };
 
 static inline struct iwl_mvm_sta *
@@ -406,5 +410,11 @@ int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta,
                      bool drain);
 void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm,
                                   struct iwl_mvm_sta *mvmsta, bool disable);
+void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm,
+                                     struct ieee80211_sta *sta,
+                                     bool disable);
+void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm,
+                                      struct iwl_mvm_vif *mvmvif,
+                                      bool disable);
 
 #endif /* __sta_h__ */
index fa87a4ba25ec3547f07ec5aa18d6cbda004b0867..f5c0982d297c10f9c9ba5d1561efc01eefb469b4 100644 (file)
@@ -727,13 +727,21 @@ static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm,
                goto out;
 
        if (mvmsta && mvmsta->vif->type == NL80211_IFTYPE_AP) {
+
                /*
-                * If there are no pending frames for this STA, notify
-                * mac80211 that this station can go to sleep in its
+                * If there are no pending frames for this STA and
+                * the tx to this station is not disabled, notify
+                * mac80211 that this station can now wake up in its
                 * STA table.
                 * If mvmsta is not NULL, sta is valid.
                 */
-               ieee80211_sta_block_awake(mvm->hw, sta, false);
+
+               spin_lock_bh(&mvmsta->lock);
+
+               if (!mvmsta->disable_tx)
+                       ieee80211_sta_block_awake(mvm->hw, sta, false);
+
+               spin_unlock_bh(&mvmsta->lock);
        }
 
        if (PTR_ERR(sta) == -EBUSY || PTR_ERR(sta) == -ENOENT) {