diff options
author | Andrei Otcheretianski <andrei.otcheretianski@intel.com> | 2014-05-25 10:24:22 -0400 |
---|---|---|
committer | Emmanuel Grumbach <emmanuel.grumbach@intel.com> | 2014-07-07 14:41:21 -0400 |
commit | 003e5236a1fcab3fc4576fe643e31a3d83027256 (patch) | |
tree | a02e051c9e0833c8e4112df7ab6503b9964a1366 | |
parent | 7f0a7c671cdc0396f99b60b77bd02e2dee7c4838 (diff) |
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: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c | 25 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/mac80211.c | 11 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/mvm.h | 8 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/rx.c | 17 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/sta.c | 54 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/sta.h | 10 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/tx.c | 14 |
7 files changed, 136 insertions, 3 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c index a176d008dab1..96b9cf8137e7 100644 --- a/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c +++ b/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c | |||
@@ -1237,6 +1237,7 @@ int iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, | |||
1237 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | 1237 | struct iwl_rx_packet *pkt = rxb_addr(rxb); |
1238 | struct iwl_mvm_tx_resp *beacon_notify_hdr; | 1238 | struct iwl_mvm_tx_resp *beacon_notify_hdr; |
1239 | struct ieee80211_vif *csa_vif; | 1239 | struct ieee80211_vif *csa_vif; |
1240 | struct ieee80211_vif *tx_blocked_vif; | ||
1240 | u64 tsf; | 1241 | u64 tsf; |
1241 | 1242 | ||
1242 | lockdep_assert_held(&mvm->mutex); | 1243 | lockdep_assert_held(&mvm->mutex); |
@@ -1267,6 +1268,30 @@ int iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, | |||
1267 | if (unlikely(csa_vif && csa_vif->csa_active)) | 1268 | if (unlikely(csa_vif && csa_vif->csa_active)) |
1268 | iwl_mvm_csa_count_down(mvm, csa_vif, mvm->ap_last_beacon_gp2); | 1269 | iwl_mvm_csa_count_down(mvm, csa_vif, mvm->ap_last_beacon_gp2); |
1269 | 1270 | ||
1271 | tx_blocked_vif = rcu_dereference_protected(mvm->csa_tx_blocked_vif, | ||
1272 | lockdep_is_held(&mvm->mutex)); | ||
1273 | if (unlikely(tx_blocked_vif)) { | ||
1274 | struct iwl_mvm_vif *mvmvif = | ||
1275 | iwl_mvm_vif_from_mac80211(tx_blocked_vif); | ||
1276 | |||
1277 | /* | ||
1278 | * The channel switch is started and we have blocked the | ||
1279 | * stations. If this is the first beacon (the timeout wasn't | ||
1280 | * set), set the unblock timeout, otherwise countdown | ||
1281 | */ | ||
1282 | if (!mvm->csa_tx_block_bcn_timeout) | ||
1283 | mvm->csa_tx_block_bcn_timeout = | ||
1284 | IWL_MVM_CS_UNBLOCK_TX_TIMEOUT; | ||
1285 | else | ||
1286 | mvm->csa_tx_block_bcn_timeout--; | ||
1287 | |||
1288 | /* Check if the timeout is expired, and unblock tx */ | ||
1289 | if (mvm->csa_tx_block_bcn_timeout == 0) { | ||
1290 | iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); | ||
1291 | RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); | ||
1292 | } | ||
1293 | } | ||
1294 | |||
1270 | return 0; | 1295 | return 0; |
1271 | } | 1296 | } |
1272 | 1297 | ||
diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c index 4a0350fc4b4e..5ed6fb32087c 100644 --- a/drivers/net/wireless/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c | |||
@@ -1614,6 +1614,11 @@ static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw, | |||
1614 | RCU_INIT_POINTER(mvm->csa_vif, NULL); | 1614 | RCU_INIT_POINTER(mvm->csa_vif, NULL); |
1615 | } | 1615 | } |
1616 | 1616 | ||
1617 | if (rcu_access_pointer(mvm->csa_tx_blocked_vif) == vif) { | ||
1618 | RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); | ||
1619 | mvm->csa_tx_block_bcn_timeout = 0; | ||
1620 | } | ||
1621 | |||
1617 | mvmvif->ap_ibss_active = false; | 1622 | mvmvif->ap_ibss_active = false; |
1618 | mvm->ap_last_beacon_gp2 = 0; | 1623 | mvm->ap_last_beacon_gp2 = 0; |
1619 | 1624 | ||
@@ -2491,6 +2496,12 @@ static void __iwl_mvm_unassign_vif_chanctx(struct iwl_mvm *mvm, | |||
2491 | if (!vif->csa_active || !mvmvif->ap_ibss_active) | 2496 | if (!vif->csa_active || !mvmvif->ap_ibss_active) |
2492 | goto out; | 2497 | goto out; |
2493 | 2498 | ||
2499 | /* Set CS bit on all the stations */ | ||
2500 | iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, true); | ||
2501 | |||
2502 | /* Save blocked iface, the timeout is set on the next beacon */ | ||
2503 | rcu_assign_pointer(mvm->csa_tx_blocked_vif, vif); | ||
2504 | |||
2494 | mvmvif->ap_ibss_active = false; | 2505 | mvmvif->ap_ibss_active = false; |
2495 | break; | 2506 | break; |
2496 | case NL80211_IFTYPE_STATION: | 2507 | case NL80211_IFTYPE_STATION: |
diff --git a/drivers/net/wireless/iwlwifi/mvm/mvm.h b/drivers/net/wireless/iwlwifi/mvm/mvm.h index db496c5c73fc..7b308c4834f6 100644 --- a/drivers/net/wireless/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/iwlwifi/mvm/mvm.h | |||
@@ -95,6 +95,12 @@ | |||
95 | */ | 95 | */ |
96 | #define IWL_MVM_CHANNEL_SWITCH_MARGIN 4 | 96 | #define IWL_MVM_CHANNEL_SWITCH_MARGIN 4 |
97 | 97 | ||
98 | /* | ||
99 | * Number of beacons to transmit on a new channel until we unblock tx to | ||
100 | * the stations, even if we didn't identify them on a new channel | ||
101 | */ | ||
102 | #define IWL_MVM_CS_UNBLOCK_TX_TIMEOUT 3 | ||
103 | |||
98 | enum iwl_mvm_tx_fifo { | 104 | enum iwl_mvm_tx_fifo { |
99 | IWL_MVM_TX_FIFO_BK = 0, | 105 | IWL_MVM_TX_FIFO_BK = 0, |
100 | IWL_MVM_TX_FIFO_BE, | 106 | IWL_MVM_TX_FIFO_BE, |
@@ -671,6 +677,8 @@ struct iwl_mvm { | |||
671 | bool ps_disabled; | 677 | bool ps_disabled; |
672 | 678 | ||
673 | struct ieee80211_vif __rcu *csa_vif; | 679 | struct ieee80211_vif __rcu *csa_vif; |
680 | struct ieee80211_vif __rcu *csa_tx_blocked_vif; | ||
681 | u8 csa_tx_block_bcn_timeout; | ||
674 | 682 | ||
675 | /* system time of last beacon (for AP/GO interface) */ | 683 | /* system time of last beacon (for AP/GO interface) */ |
676 | u32 ap_last_beacon_gp2; | 684 | u32 ap_last_beacon_gp2; |
diff --git a/drivers/net/wireless/iwlwifi/mvm/rx.c b/drivers/net/wireless/iwlwifi/mvm/rx.c index cf7276967acd..4b98987fc413 100644 --- a/drivers/net/wireless/iwlwifi/mvm/rx.c +++ b/drivers/net/wireless/iwlwifi/mvm/rx.c | |||
@@ -259,6 +259,23 @@ int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, | |||
259 | memset(&rx_status, 0, sizeof(rx_status)); | 259 | memset(&rx_status, 0, sizeof(rx_status)); |
260 | 260 | ||
261 | /* | 261 | /* |
262 | * We have tx blocked stations (with CS bit). If we heard frames from | ||
263 | * a blocked station on a new channel we can TX to it again. | ||
264 | */ | ||
265 | if (unlikely(mvm->csa_tx_block_bcn_timeout)) { | ||
266 | struct ieee80211_sta *sta; | ||
267 | |||
268 | rcu_read_lock(); | ||
269 | |||
270 | sta = ieee80211_find_sta( | ||
271 | rcu_dereference(mvm->csa_tx_blocked_vif), hdr->addr2); | ||
272 | if (sta) | ||
273 | iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, false); | ||
274 | |||
275 | rcu_read_unlock(); | ||
276 | } | ||
277 | |||
278 | /* | ||
262 | * drop the packet if it has failed being decrypted by HW | 279 | * drop the packet if it has failed being decrypted by HW |
263 | */ | 280 | */ |
264 | if (iwl_mvm_set_mac80211_rx_flag(mvm, hdr, &rx_status, rx_pkt_status)) { | 281 | if (iwl_mvm_set_mac80211_rx_flag(mvm, hdr, &rx_status, rx_pkt_status)) { |
diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.c b/drivers/net/wireless/iwlwifi/mvm/sta.c index d3a6cf7558eb..812813964847 100644 --- a/drivers/net/wireless/iwlwifi/mvm/sta.c +++ b/drivers/net/wireless/iwlwifi/mvm/sta.c | |||
@@ -1468,3 +1468,57 @@ void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, | |||
1468 | if (ret) | 1468 | if (ret) |
1469 | IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); | 1469 | IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); |
1470 | } | 1470 | } |
1471 | |||
1472 | void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm, | ||
1473 | struct ieee80211_sta *sta, | ||
1474 | bool disable) | ||
1475 | { | ||
1476 | struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); | ||
1477 | |||
1478 | spin_lock_bh(&mvm_sta->lock); | ||
1479 | |||
1480 | if (mvm_sta->disable_tx == disable) { | ||
1481 | spin_unlock_bh(&mvm_sta->lock); | ||
1482 | return; | ||
1483 | } | ||
1484 | |||
1485 | mvm_sta->disable_tx = disable; | ||
1486 | |||
1487 | /* | ||
1488 | * Tell mac80211 to start/stop queueing tx for this station, | ||
1489 | * but don't stop queueing if there are still pending frames | ||
1490 | * for this station. | ||
1491 | */ | ||
1492 | if (disable || !atomic_read(&mvm->pending_frames[mvm_sta->sta_id])) | ||
1493 | ieee80211_sta_block_awake(mvm->hw, sta, disable); | ||
1494 | |||
1495 | iwl_mvm_sta_modify_disable_tx(mvm, mvm_sta, disable); | ||
1496 | |||
1497 | spin_unlock_bh(&mvm_sta->lock); | ||
1498 | } | ||
1499 | |||
1500 | void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm, | ||
1501 | struct iwl_mvm_vif *mvmvif, | ||
1502 | bool disable) | ||
1503 | { | ||
1504 | struct ieee80211_sta *sta; | ||
1505 | struct iwl_mvm_sta *mvm_sta; | ||
1506 | int i; | ||
1507 | |||
1508 | lockdep_assert_held(&mvm->mutex); | ||
1509 | |||
1510 | /* Block/unblock all the stations of the given mvmvif */ | ||
1511 | for (i = 0; i < IWL_MVM_STATION_COUNT; i++) { | ||
1512 | sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], | ||
1513 | lockdep_is_held(&mvm->mutex)); | ||
1514 | if (IS_ERR_OR_NULL(sta)) | ||
1515 | continue; | ||
1516 | |||
1517 | mvm_sta = iwl_mvm_sta_from_mac80211(sta); | ||
1518 | if (mvm_sta->mac_id_n_color != | ||
1519 | FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)) | ||
1520 | continue; | ||
1521 | |||
1522 | iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, disable); | ||
1523 | } | ||
1524 | } | ||
diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.h b/drivers/net/wireless/iwlwifi/mvm/sta.h index 10c1a5352651..3b1c8bd6cb54 100644 --- a/drivers/net/wireless/iwlwifi/mvm/sta.h +++ b/drivers/net/wireless/iwlwifi/mvm/sta.h | |||
@@ -73,6 +73,7 @@ | |||
73 | #include "rs.h" | 73 | #include "rs.h" |
74 | 74 | ||
75 | struct iwl_mvm; | 75 | struct iwl_mvm; |
76 | struct iwl_mvm_vif; | ||
76 | 77 | ||
77 | /** | 78 | /** |
78 | * DOC: station table - introduction | 79 | * DOC: station table - introduction |
@@ -295,6 +296,7 @@ static inline u16 iwl_mvm_tid_queued(struct iwl_mvm_tid_data *tid_data) | |||
295 | * @tid_data: per tid data. Look at %iwl_mvm_tid_data. | 296 | * @tid_data: per tid data. Look at %iwl_mvm_tid_data. |
296 | * @tx_protection: reference counter for controlling the Tx protection. | 297 | * @tx_protection: reference counter for controlling the Tx protection. |
297 | * @tt_tx_protection: is thermal throttling enable Tx protection? | 298 | * @tt_tx_protection: is thermal throttling enable Tx protection? |
299 | * @disable_tx: is tx to this STA disabled? | ||
298 | * | 300 | * |
299 | * When mac80211 creates a station it reserves some space (hw->sta_data_size) | 301 | * When mac80211 creates a station it reserves some space (hw->sta_data_size) |
300 | * in the structure for use by driver. This structure is placed in that | 302 | * in the structure for use by driver. This structure is placed in that |
@@ -317,6 +319,8 @@ struct iwl_mvm_sta { | |||
317 | /* Temporary, until the new TLC will control the Tx protection */ | 319 | /* Temporary, until the new TLC will control the Tx protection */ |
318 | s8 tx_protection; | 320 | s8 tx_protection; |
319 | bool tt_tx_protection; | 321 | bool tt_tx_protection; |
322 | |||
323 | bool disable_tx; | ||
320 | }; | 324 | }; |
321 | 325 | ||
322 | static inline struct iwl_mvm_sta * | 326 | static inline struct iwl_mvm_sta * |
@@ -406,5 +410,11 @@ int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, | |||
406 | bool drain); | 410 | bool drain); |
407 | void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, | 411 | void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, |
408 | struct iwl_mvm_sta *mvmsta, bool disable); | 412 | struct iwl_mvm_sta *mvmsta, bool disable); |
413 | void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm, | ||
414 | struct ieee80211_sta *sta, | ||
415 | bool disable); | ||
416 | void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm, | ||
417 | struct iwl_mvm_vif *mvmvif, | ||
418 | bool disable); | ||
409 | 419 | ||
410 | #endif /* __sta_h__ */ | 420 | #endif /* __sta_h__ */ |
diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c index fa87a4ba25ec..f5c0982d297c 100644 --- a/drivers/net/wireless/iwlwifi/mvm/tx.c +++ b/drivers/net/wireless/iwlwifi/mvm/tx.c | |||
@@ -727,13 +727,21 @@ static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm, | |||
727 | goto out; | 727 | goto out; |
728 | 728 | ||
729 | if (mvmsta && mvmsta->vif->type == NL80211_IFTYPE_AP) { | 729 | if (mvmsta && mvmsta->vif->type == NL80211_IFTYPE_AP) { |
730 | |||
730 | /* | 731 | /* |
731 | * If there are no pending frames for this STA, notify | 732 | * If there are no pending frames for this STA and |
732 | * mac80211 that this station can go to sleep in its | 733 | * the tx to this station is not disabled, notify |
734 | * mac80211 that this station can now wake up in its | ||
733 | * STA table. | 735 | * STA table. |
734 | * If mvmsta is not NULL, sta is valid. | 736 | * If mvmsta is not NULL, sta is valid. |
735 | */ | 737 | */ |
736 | ieee80211_sta_block_awake(mvm->hw, sta, false); | 738 | |
739 | spin_lock_bh(&mvmsta->lock); | ||
740 | |||
741 | if (!mvmsta->disable_tx) | ||
742 | ieee80211_sta_block_awake(mvm->hw, sta, false); | ||
743 | |||
744 | spin_unlock_bh(&mvmsta->lock); | ||
737 | } | 745 | } |
738 | 746 | ||
739 | if (PTR_ERR(sta) == -EBUSY || PTR_ERR(sta) == -ENOENT) { | 747 | if (PTR_ERR(sta) == -EBUSY || PTR_ERR(sta) == -ENOENT) { |