aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIlan Peer <ilan.peer@intel.com>2012-05-09 09:11:55 -0400
committerJohannes Berg <johannes.berg@intel.com>2012-06-06 07:03:54 -0400
commit622a9268231bd6b486ec058a28f773fa89926012 (patch)
tree57c0eda48dfa100c44611ba7a88998009b7adfb4
parente19ebcab01cc130fa832764d453b263460ec3b91 (diff)
iwlwifi: handle race condition in ROC flow
When a remain on channel request from mac80211 is followed by a request to tx a mgmt frame offchannel, it is possible that the remain on channel expires before the device reported the tx status for the frame. This causes a race condition in mac80211. To fix this, delay the ROC notification to mac80211 until the device reported the Tx status for all frames in the aux queue. Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com> Signed-off-by: Ilan Peer <ilan.peer@intel.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
-rw-r--r--drivers/net/wireless/iwlwifi/iwl-agn-tx.c18
-rw-r--r--drivers/net/wireless/iwlwifi/iwl-agn.c3
-rw-r--r--drivers/net/wireless/iwlwifi/iwl-agn.h5
-rw-r--r--drivers/net/wireless/iwlwifi/iwl-dev.h1
-rw-r--r--drivers/net/wireless/iwlwifi/iwl-scan.c51
5 files changed, 68 insertions, 10 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-tx.c b/drivers/net/wireless/iwlwifi/iwl-agn-tx.c
index 3366e2e2f00f..ad1d36fa7642 100644
--- a/drivers/net/wireless/iwlwifi/iwl-agn-tx.c
+++ b/drivers/net/wireless/iwlwifi/iwl-agn-tx.c
@@ -486,6 +486,9 @@ int iwlagn_tx_skb(struct iwl_priv *priv, struct sk_buff *skb)
486 if (sta_priv && sta_priv->client && !is_agg) 486 if (sta_priv && sta_priv->client && !is_agg)
487 atomic_inc(&sta_priv->pending_frames); 487 atomic_inc(&sta_priv->pending_frames);
488 488
489 if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN)
490 iwl_scan_offchannel_skb(priv);
491
489 return 0; 492 return 0;
490 493
491drop_unlock_sta: 494drop_unlock_sta:
@@ -1136,6 +1139,7 @@ int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb,
1136 struct sk_buff *skb; 1139 struct sk_buff *skb;
1137 struct iwl_rxon_context *ctx; 1140 struct iwl_rxon_context *ctx;
1138 bool is_agg = (txq_id >= IWLAGN_FIRST_AMPDU_QUEUE); 1141 bool is_agg = (txq_id >= IWLAGN_FIRST_AMPDU_QUEUE);
1142 bool is_offchannel_skb;
1139 1143
1140 tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >> 1144 tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >>
1141 IWLAGN_TX_RES_TID_POS; 1145 IWLAGN_TX_RES_TID_POS;
@@ -1149,6 +1153,8 @@ int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb,
1149 1153
1150 __skb_queue_head_init(&skbs); 1154 __skb_queue_head_init(&skbs);
1151 1155
1156 is_offchannel_skb = false;
1157
1152 if (tx_resp->frame_count == 1) { 1158 if (tx_resp->frame_count == 1) {
1153 u16 next_reclaimed = le16_to_cpu(tx_resp->seq_ctl); 1159 u16 next_reclaimed = le16_to_cpu(tx_resp->seq_ctl);
1154 next_reclaimed = SEQ_TO_SN(next_reclaimed + 0x10); 1160 next_reclaimed = SEQ_TO_SN(next_reclaimed + 0x10);
@@ -1225,10 +1231,19 @@ int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb,
1225 if (!is_agg) 1231 if (!is_agg)
1226 iwlagn_non_agg_tx_status(priv, ctx, hdr->addr1); 1232 iwlagn_non_agg_tx_status(priv, ctx, hdr->addr1);
1227 1233
1234 is_offchannel_skb =
1235 (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN);
1228 freed++; 1236 freed++;
1229 } 1237 }
1230 1238
1231 WARN_ON(!is_agg && freed != 1); 1239 WARN_ON(!is_agg && freed != 1);
1240
1241 /*
1242 * An offchannel frame can be send only on the AUX queue, where
1243 * there is no aggregation (and reordering) so it only is single
1244 * skb is expected to be processed.
1245 */
1246 WARN_ON(is_offchannel_skb && freed != 1);
1232 } 1247 }
1233 1248
1234 iwl_check_abort_status(priv, tx_resp->frame_count, status); 1249 iwl_check_abort_status(priv, tx_resp->frame_count, status);
@@ -1239,6 +1254,9 @@ int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb,
1239 ieee80211_tx_status(priv->hw, skb); 1254 ieee80211_tx_status(priv->hw, skb);
1240 } 1255 }
1241 1256
1257 if (is_offchannel_skb)
1258 iwl_scan_offchannel_skb_status(priv);
1259
1242 return 0; 1260 return 0;
1243} 1261}
1244 1262
diff --git a/drivers/net/wireless/iwlwifi/iwl-agn.c b/drivers/net/wireless/iwlwifi/iwl-agn.c
index 5149e6f72945..cd95a240511a 100644
--- a/drivers/net/wireless/iwlwifi/iwl-agn.c
+++ b/drivers/net/wireless/iwlwifi/iwl-agn.c
@@ -931,6 +931,9 @@ void iwl_down(struct iwl_priv *priv)
931 priv->ucode_loaded = false; 931 priv->ucode_loaded = false;
932 iwl_trans_stop_device(priv->trans); 932 iwl_trans_stop_device(priv->trans);
933 933
934 /* Set num_aux_in_flight must be done after the transport is stopped */
935 atomic_set(&priv->num_aux_in_flight, 0);
936
934 /* Clear out all status bits but a few that are stable across reset */ 937 /* Clear out all status bits but a few that are stable across reset */
935 priv->status &= test_bit(STATUS_RF_KILL_HW, &priv->status) << 938 priv->status &= test_bit(STATUS_RF_KILL_HW, &priv->status) <<
936 STATUS_RF_KILL_HW | 939 STATUS_RF_KILL_HW |
diff --git a/drivers/net/wireless/iwlwifi/iwl-agn.h b/drivers/net/wireless/iwlwifi/iwl-agn.h
index 79c0fe06f4db..5c1693f2573b 100644
--- a/drivers/net/wireless/iwlwifi/iwl-agn.h
+++ b/drivers/net/wireless/iwlwifi/iwl-agn.h
@@ -101,6 +101,7 @@ extern struct iwl_lib_ops iwl6030_lib;
101#define STATUS_CHANNEL_SWITCH_PENDING 11 101#define STATUS_CHANNEL_SWITCH_PENDING 11
102#define STATUS_SCAN_COMPLETE 12 102#define STATUS_SCAN_COMPLETE 12
103#define STATUS_POWER_PMI 13 103#define STATUS_POWER_PMI 13
104#define STATUS_SCAN_ROC_EXPIRED 14
104 105
105struct iwl_ucode_capabilities; 106struct iwl_ucode_capabilities;
106 107
@@ -255,6 +256,10 @@ int __must_check iwl_scan_initiate(struct iwl_priv *priv,
255 enum iwl_scan_type scan_type, 256 enum iwl_scan_type scan_type,
256 enum ieee80211_band band); 257 enum ieee80211_band band);
257 258
259void iwl_scan_roc_expired(struct iwl_priv *priv);
260void iwl_scan_offchannel_skb(struct iwl_priv *priv);
261void iwl_scan_offchannel_skb_status(struct iwl_priv *priv);
262
258/* For faster active scanning, scan will move to the next channel if fewer than 263/* For faster active scanning, scan will move to the next channel if fewer than
259 * PLCP_QUIET_THRESH packets are heard on this channel within 264 * PLCP_QUIET_THRESH packets are heard on this channel within
260 * ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell 265 * ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell
diff --git a/drivers/net/wireless/iwlwifi/iwl-dev.h b/drivers/net/wireless/iwlwifi/iwl-dev.h
index 70062379d0ec..a4936a134162 100644
--- a/drivers/net/wireless/iwlwifi/iwl-dev.h
+++ b/drivers/net/wireless/iwlwifi/iwl-dev.h
@@ -846,6 +846,7 @@ struct iwl_priv {
846 struct iwl_station_entry stations[IWLAGN_STATION_COUNT]; 846 struct iwl_station_entry stations[IWLAGN_STATION_COUNT];
847 unsigned long ucode_key_table; 847 unsigned long ucode_key_table;
848 struct iwl_tid_data tid_data[IWLAGN_STATION_COUNT][IWL_MAX_TID_COUNT]; 848 struct iwl_tid_data tid_data[IWLAGN_STATION_COUNT][IWL_MAX_TID_COUNT];
849 atomic_t num_aux_in_flight;
849 850
850 u8 mac80211_registered; 851 u8 mac80211_registered;
851 852
diff --git a/drivers/net/wireless/iwlwifi/iwl-scan.c b/drivers/net/wireless/iwlwifi/iwl-scan.c
index 031d8e21f82f..eb0ded7a8a99 100644
--- a/drivers/net/wireless/iwlwifi/iwl-scan.c
+++ b/drivers/net/wireless/iwlwifi/iwl-scan.c
@@ -101,11 +101,8 @@ static void iwl_complete_scan(struct iwl_priv *priv, bool aborted)
101 ieee80211_scan_completed(priv->hw, aborted); 101 ieee80211_scan_completed(priv->hw, aborted);
102 } 102 }
103 103
104 if (priv->scan_type == IWL_SCAN_ROC) { 104 if (priv->scan_type == IWL_SCAN_ROC)
105 ieee80211_remain_on_channel_expired(priv->hw); 105 iwl_scan_roc_expired(priv);
106 priv->hw_roc_channel = NULL;
107 schedule_delayed_work(&priv->hw_roc_disable_work, 10 * HZ);
108 }
109 106
110 priv->scan_type = IWL_SCAN_NORMAL; 107 priv->scan_type = IWL_SCAN_NORMAL;
111 priv->scan_vif = NULL; 108 priv->scan_vif = NULL;
@@ -134,11 +131,8 @@ static void iwl_process_scan_complete(struct iwl_priv *priv)
134 goto out_settings; 131 goto out_settings;
135 } 132 }
136 133
137 if (priv->scan_type == IWL_SCAN_ROC) { 134 if (priv->scan_type == IWL_SCAN_ROC)
138 ieee80211_remain_on_channel_expired(priv->hw); 135 iwl_scan_roc_expired(priv);
139 priv->hw_roc_channel = NULL;
140 schedule_delayed_work(&priv->hw_roc_disable_work, 10 * HZ);
141 }
142 136
143 if (priv->scan_type != IWL_SCAN_NORMAL && !aborted) { 137 if (priv->scan_type != IWL_SCAN_NORMAL && !aborted) {
144 int err; 138 int err;
@@ -1158,3 +1152,40 @@ void iwl_cancel_scan_deferred_work(struct iwl_priv *priv)
1158 mutex_unlock(&priv->mutex); 1152 mutex_unlock(&priv->mutex);
1159 } 1153 }
1160} 1154}
1155
1156void iwl_scan_roc_expired(struct iwl_priv *priv)
1157{
1158 /*
1159 * The status bit should be set here, to prevent a race
1160 * where the atomic_read returns 1, but before the execution continues
1161 * iwl_scan_offchannel_skb_status() checks if the status bit is set
1162 */
1163 set_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status);
1164
1165 if (atomic_read(&priv->num_aux_in_flight) == 0) {
1166 ieee80211_remain_on_channel_expired(priv->hw);
1167 priv->hw_roc_channel = NULL;
1168 schedule_delayed_work(&priv->hw_roc_disable_work,
1169 10 * HZ);
1170
1171 clear_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status);
1172 } else {
1173 IWL_DEBUG_SCAN(priv, "ROC done with %d frames in aux\n",
1174 atomic_read(&priv->num_aux_in_flight));
1175 }
1176}
1177
1178void iwl_scan_offchannel_skb(struct iwl_priv *priv)
1179{
1180 WARN_ON(!priv->hw_roc_start_notified);
1181 atomic_inc(&priv->num_aux_in_flight);
1182}
1183
1184void iwl_scan_offchannel_skb_status(struct iwl_priv *priv)
1185{
1186 if (atomic_dec_return(&priv->num_aux_in_flight) == 0 &&
1187 test_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status)) {
1188 IWL_DEBUG_SCAN(priv, "0 aux frames. Calling ROC expired\n");
1189 iwl_scan_roc_expired(priv);
1190 }
1191}