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>
This commit is contained in:
parent
e19ebcab01
commit
622a926823
5 changed files with 68 additions and 10 deletions
|
@ -486,6 +486,9 @@ int iwlagn_tx_skb(struct iwl_priv *priv, struct sk_buff *skb)
|
||||||
if (sta_priv && sta_priv->client && !is_agg)
|
if (sta_priv && sta_priv->client && !is_agg)
|
||||||
atomic_inc(&sta_priv->pending_frames);
|
atomic_inc(&sta_priv->pending_frames);
|
||||||
|
|
||||||
|
if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN)
|
||||||
|
iwl_scan_offchannel_skb(priv);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
drop_unlock_sta:
|
drop_unlock_sta:
|
||||||
|
@ -1136,6 +1139,7 @@ int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb,
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
struct iwl_rxon_context *ctx;
|
struct iwl_rxon_context *ctx;
|
||||||
bool is_agg = (txq_id >= IWLAGN_FIRST_AMPDU_QUEUE);
|
bool is_agg = (txq_id >= IWLAGN_FIRST_AMPDU_QUEUE);
|
||||||
|
bool is_offchannel_skb;
|
||||||
|
|
||||||
tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >>
|
tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >>
|
||||||
IWLAGN_TX_RES_TID_POS;
|
IWLAGN_TX_RES_TID_POS;
|
||||||
|
@ -1149,6 +1153,8 @@ int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb,
|
||||||
|
|
||||||
__skb_queue_head_init(&skbs);
|
__skb_queue_head_init(&skbs);
|
||||||
|
|
||||||
|
is_offchannel_skb = false;
|
||||||
|
|
||||||
if (tx_resp->frame_count == 1) {
|
if (tx_resp->frame_count == 1) {
|
||||||
u16 next_reclaimed = le16_to_cpu(tx_resp->seq_ctl);
|
u16 next_reclaimed = le16_to_cpu(tx_resp->seq_ctl);
|
||||||
next_reclaimed = SEQ_TO_SN(next_reclaimed + 0x10);
|
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,
|
||||||
if (!is_agg)
|
if (!is_agg)
|
||||||
iwlagn_non_agg_tx_status(priv, ctx, hdr->addr1);
|
iwlagn_non_agg_tx_status(priv, ctx, hdr->addr1);
|
||||||
|
|
||||||
|
is_offchannel_skb =
|
||||||
|
(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN);
|
||||||
freed++;
|
freed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
WARN_ON(!is_agg && freed != 1);
|
WARN_ON(!is_agg && freed != 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An offchannel frame can be send only on the AUX queue, where
|
||||||
|
* there is no aggregation (and reordering) so it only is single
|
||||||
|
* skb is expected to be processed.
|
||||||
|
*/
|
||||||
|
WARN_ON(is_offchannel_skb && freed != 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
iwl_check_abort_status(priv, tx_resp->frame_count, status);
|
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,
|
||||||
ieee80211_tx_status(priv->hw, skb);
|
ieee80211_tx_status(priv->hw, skb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_offchannel_skb)
|
||||||
|
iwl_scan_offchannel_skb_status(priv);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -931,6 +931,9 @@ void iwl_down(struct iwl_priv *priv)
|
||||||
priv->ucode_loaded = false;
|
priv->ucode_loaded = false;
|
||||||
iwl_trans_stop_device(priv->trans);
|
iwl_trans_stop_device(priv->trans);
|
||||||
|
|
||||||
|
/* Set num_aux_in_flight must be done after the transport is stopped */
|
||||||
|
atomic_set(&priv->num_aux_in_flight, 0);
|
||||||
|
|
||||||
/* Clear out all status bits but a few that are stable across reset */
|
/* Clear out all status bits but a few that are stable across reset */
|
||||||
priv->status &= test_bit(STATUS_RF_KILL_HW, &priv->status) <<
|
priv->status &= test_bit(STATUS_RF_KILL_HW, &priv->status) <<
|
||||||
STATUS_RF_KILL_HW |
|
STATUS_RF_KILL_HW |
|
||||||
|
|
|
@ -101,6 +101,7 @@ extern struct iwl_lib_ops iwl6030_lib;
|
||||||
#define STATUS_CHANNEL_SWITCH_PENDING 11
|
#define STATUS_CHANNEL_SWITCH_PENDING 11
|
||||||
#define STATUS_SCAN_COMPLETE 12
|
#define STATUS_SCAN_COMPLETE 12
|
||||||
#define STATUS_POWER_PMI 13
|
#define STATUS_POWER_PMI 13
|
||||||
|
#define STATUS_SCAN_ROC_EXPIRED 14
|
||||||
|
|
||||||
struct iwl_ucode_capabilities;
|
struct iwl_ucode_capabilities;
|
||||||
|
|
||||||
|
@ -255,6 +256,10 @@ int __must_check iwl_scan_initiate(struct iwl_priv *priv,
|
||||||
enum iwl_scan_type scan_type,
|
enum iwl_scan_type scan_type,
|
||||||
enum ieee80211_band band);
|
enum ieee80211_band band);
|
||||||
|
|
||||||
|
void iwl_scan_roc_expired(struct iwl_priv *priv);
|
||||||
|
void iwl_scan_offchannel_skb(struct iwl_priv *priv);
|
||||||
|
void iwl_scan_offchannel_skb_status(struct iwl_priv *priv);
|
||||||
|
|
||||||
/* For faster active scanning, scan will move to the next channel if fewer than
|
/* For faster active scanning, scan will move to the next channel if fewer than
|
||||||
* PLCP_QUIET_THRESH packets are heard on this channel within
|
* PLCP_QUIET_THRESH packets are heard on this channel within
|
||||||
* ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell
|
* ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell
|
||||||
|
|
|
@ -846,6 +846,7 @@ struct iwl_priv {
|
||||||
struct iwl_station_entry stations[IWLAGN_STATION_COUNT];
|
struct iwl_station_entry stations[IWLAGN_STATION_COUNT];
|
||||||
unsigned long ucode_key_table;
|
unsigned long ucode_key_table;
|
||||||
struct iwl_tid_data tid_data[IWLAGN_STATION_COUNT][IWL_MAX_TID_COUNT];
|
struct iwl_tid_data tid_data[IWLAGN_STATION_COUNT][IWL_MAX_TID_COUNT];
|
||||||
|
atomic_t num_aux_in_flight;
|
||||||
|
|
||||||
u8 mac80211_registered;
|
u8 mac80211_registered;
|
||||||
|
|
||||||
|
|
|
@ -101,11 +101,8 @@ static void iwl_complete_scan(struct iwl_priv *priv, bool aborted)
|
||||||
ieee80211_scan_completed(priv->hw, aborted);
|
ieee80211_scan_completed(priv->hw, aborted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priv->scan_type == IWL_SCAN_ROC) {
|
if (priv->scan_type == IWL_SCAN_ROC)
|
||||||
ieee80211_remain_on_channel_expired(priv->hw);
|
iwl_scan_roc_expired(priv);
|
||||||
priv->hw_roc_channel = NULL;
|
|
||||||
schedule_delayed_work(&priv->hw_roc_disable_work, 10 * HZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->scan_type = IWL_SCAN_NORMAL;
|
priv->scan_type = IWL_SCAN_NORMAL;
|
||||||
priv->scan_vif = NULL;
|
priv->scan_vif = NULL;
|
||||||
|
@ -134,11 +131,8 @@ static void iwl_process_scan_complete(struct iwl_priv *priv)
|
||||||
goto out_settings;
|
goto out_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priv->scan_type == IWL_SCAN_ROC) {
|
if (priv->scan_type == IWL_SCAN_ROC)
|
||||||
ieee80211_remain_on_channel_expired(priv->hw);
|
iwl_scan_roc_expired(priv);
|
||||||
priv->hw_roc_channel = NULL;
|
|
||||||
schedule_delayed_work(&priv->hw_roc_disable_work, 10 * HZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priv->scan_type != IWL_SCAN_NORMAL && !aborted) {
|
if (priv->scan_type != IWL_SCAN_NORMAL && !aborted) {
|
||||||
int err;
|
int err;
|
||||||
|
@ -1158,3 +1152,40 @@ void iwl_cancel_scan_deferred_work(struct iwl_priv *priv)
|
||||||
mutex_unlock(&priv->mutex);
|
mutex_unlock(&priv->mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void iwl_scan_roc_expired(struct iwl_priv *priv)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The status bit should be set here, to prevent a race
|
||||||
|
* where the atomic_read returns 1, but before the execution continues
|
||||||
|
* iwl_scan_offchannel_skb_status() checks if the status bit is set
|
||||||
|
*/
|
||||||
|
set_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status);
|
||||||
|
|
||||||
|
if (atomic_read(&priv->num_aux_in_flight) == 0) {
|
||||||
|
ieee80211_remain_on_channel_expired(priv->hw);
|
||||||
|
priv->hw_roc_channel = NULL;
|
||||||
|
schedule_delayed_work(&priv->hw_roc_disable_work,
|
||||||
|
10 * HZ);
|
||||||
|
|
||||||
|
clear_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status);
|
||||||
|
} else {
|
||||||
|
IWL_DEBUG_SCAN(priv, "ROC done with %d frames in aux\n",
|
||||||
|
atomic_read(&priv->num_aux_in_flight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void iwl_scan_offchannel_skb(struct iwl_priv *priv)
|
||||||
|
{
|
||||||
|
WARN_ON(!priv->hw_roc_start_notified);
|
||||||
|
atomic_inc(&priv->num_aux_in_flight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iwl_scan_offchannel_skb_status(struct iwl_priv *priv)
|
||||||
|
{
|
||||||
|
if (atomic_dec_return(&priv->num_aux_in_flight) == 0 &&
|
||||||
|
test_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status)) {
|
||||||
|
IWL_DEBUG_SCAN(priv, "0 aux frames. Calling ROC expired\n");
|
||||||
|
iwl_scan_roc_expired(priv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue