diff options
author | Ilan Peer <ilan.peer@intel.com> | 2012-05-09 09:11:55 -0400 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2012-06-06 07:03:54 -0400 |
commit | 622a9268231bd6b486ec058a28f773fa89926012 (patch) | |
tree | 57c0eda48dfa100c44611ba7a88998009b7adfb4 | |
parent | e19ebcab01cc130fa832764d453b263460ec3b91 (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.c | 18 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-agn.c | 3 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-agn.h | 5 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-dev.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-scan.c | 51 |
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 | ||
491 | drop_unlock_sta: | 494 | drop_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 | ||
105 | struct iwl_ucode_capabilities; | 106 | struct 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 | ||
259 | void iwl_scan_roc_expired(struct iwl_priv *priv); | ||
260 | void iwl_scan_offchannel_skb(struct iwl_priv *priv); | ||
261 | void 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 | |||
1156 | void 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 | |||
1178 | void 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 | |||
1184 | void 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 | } | ||