Commit 003e5236 authored by Andrei Otcheretianski's avatar Andrei Otcheretianski Committed by Emmanuel Grumbach

iwlwifi: mvm: Use CS tx block bit for AP/GO

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: default avatarAndrei Otcheretianski <andrei.otcheretianski@intel.com>
Reviewed-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
parent 7f0a7c67
......@@ -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;
}
......
......@@ -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:
......
......@@ -95,6 +95,12 @@
*/
#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;
......
......@@ -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
*/
......
......@@ -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);
}
}
......@@ -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__ */
......@@ -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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment