forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mac80211: fix TX aggregation start/stop callback race
When starting or stopping an aggregation session, one of the steps is that the driver calls back to mac80211 that the start/stop can proceed. This is handled by queueing up a fake SKB and processing it from the normal iface/sdata work. Since this isn't flushed when disassociating, the following race is possible: * associate * start aggregation session * driver callback * disassociate * associate again to the same AP * callback processing runs, leading to a WARN_ON() that the TID hadn't requested aggregation If the second association isn't to the same AP, there would only be a message printed ("Could not find station: <addr>"), but the same race could happen. Fix this by not going the whole detour with a fake SKB etc. but simply looking up the aggregation session in the driver callback, marking it with a START_CB/STOP_CB bit and then scheduling the regular aggregation work that will now process these bits as well. This also simplifies the code and gets rid of the whole problem with allocation failures of said skb, which could have left the session in limbo. Reported-by: Jouni Malinen <[email protected]> Signed-off-by: Johannes Berg <[email protected]>
- Loading branch information
1 parent
029c581
commit 7a7c0a6
Showing
5 changed files
with
71 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
* Copyright 2006-2007 Jiri Benc <[email protected]> | ||
* Copyright 2007, Michael Wu <[email protected]> | ||
* Copyright 2007-2010, Intel Corporation | ||
* Copyright(c) 2015 Intel Deutschland GmbH | ||
* Copyright(c) 2015-2017 Intel Deutschland GmbH | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
|
@@ -741,66 +741,64 @@ static void ieee80211_agg_tx_operational(struct ieee80211_local *local, | |
ieee80211_agg_start_txq(sta, tid, true); | ||
} | ||
|
||
void ieee80211_start_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u16 tid) | ||
void ieee80211_start_tx_ba_cb(struct sta_info *sta, int tid, | ||
struct tid_ampdu_tx *tid_tx) | ||
{ | ||
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); | ||
struct ieee80211_sub_if_data *sdata = sta->sdata; | ||
struct ieee80211_local *local = sdata->local; | ||
struct sta_info *sta; | ||
struct tid_ampdu_tx *tid_tx; | ||
|
||
trace_api_start_tx_ba_cb(sdata, ra, tid); | ||
if (WARN_ON(test_and_set_bit(HT_AGG_STATE_DRV_READY, &tid_tx->state))) | ||
return; | ||
|
||
if (test_bit(HT_AGG_STATE_RESPONSE_RECEIVED, &tid_tx->state)) | ||
ieee80211_agg_tx_operational(local, sta, tid); | ||
} | ||
|
||
static struct tid_ampdu_tx * | ||
ieee80211_lookup_tid_tx(struct ieee80211_sub_if_data *sdata, | ||
const u8 *ra, u16 tid, struct sta_info **sta) | ||
{ | ||
struct tid_ampdu_tx *tid_tx; | ||
|
||
if (tid >= IEEE80211_NUM_TIDS) { | ||
ht_dbg(sdata, "Bad TID value: tid = %d (>= %d)\n", | ||
tid, IEEE80211_NUM_TIDS); | ||
return; | ||
return NULL; | ||
} | ||
|
||
mutex_lock(&local->sta_mtx); | ||
sta = sta_info_get_bss(sdata, ra); | ||
if (!sta) { | ||
mutex_unlock(&local->sta_mtx); | ||
*sta = sta_info_get_bss(sdata, ra); | ||
if (!*sta) { | ||
ht_dbg(sdata, "Could not find station: %pM\n", ra); | ||
return; | ||
return NULL; | ||
} | ||
|
||
mutex_lock(&sta->ampdu_mlme.mtx); | ||
tid_tx = rcu_dereference_protected_tid_tx(sta, tid); | ||
tid_tx = rcu_dereference((*sta)->ampdu_mlme.tid_tx[tid]); | ||
|
||
if (WARN_ON(!tid_tx)) { | ||
if (WARN_ON(!tid_tx)) | ||
ht_dbg(sdata, "addBA was not requested!\n"); | ||
goto unlock; | ||
} | ||
|
||
if (WARN_ON(test_and_set_bit(HT_AGG_STATE_DRV_READY, &tid_tx->state))) | ||
goto unlock; | ||
|
||
if (test_bit(HT_AGG_STATE_RESPONSE_RECEIVED, &tid_tx->state)) | ||
ieee80211_agg_tx_operational(local, sta, tid); | ||
|
||
unlock: | ||
mutex_unlock(&sta->ampdu_mlme.mtx); | ||
mutex_unlock(&local->sta_mtx); | ||
return tid_tx; | ||
} | ||
|
||
void ieee80211_start_tx_ba_cb_irqsafe(struct ieee80211_vif *vif, | ||
const u8 *ra, u16 tid) | ||
{ | ||
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); | ||
struct ieee80211_local *local = sdata->local; | ||
struct ieee80211_ra_tid *ra_tid; | ||
struct sk_buff *skb = dev_alloc_skb(0); | ||
struct sta_info *sta; | ||
struct tid_ampdu_tx *tid_tx; | ||
|
||
if (unlikely(!skb)) | ||
return; | ||
trace_api_start_tx_ba_cb(sdata, ra, tid); | ||
|
||
ra_tid = (struct ieee80211_ra_tid *) &skb->cb; | ||
memcpy(&ra_tid->ra, ra, ETH_ALEN); | ||
ra_tid->tid = tid; | ||
rcu_read_lock(); | ||
tid_tx = ieee80211_lookup_tid_tx(sdata, ra, tid, &sta); | ||
if (!tid_tx) | ||
goto out; | ||
|
||
skb->pkt_type = IEEE80211_SDATA_QUEUE_AGG_START; | ||
skb_queue_tail(&sdata->skb_queue, skb); | ||
ieee80211_queue_work(&local->hw, &sdata->work); | ||
set_bit(HT_AGG_STATE_START_CB, &tid_tx->state); | ||
ieee80211_queue_work(&local->hw, &sta->ampdu_mlme.work); | ||
out: | ||
rcu_read_unlock(); | ||
} | ||
EXPORT_SYMBOL(ieee80211_start_tx_ba_cb_irqsafe); | ||
|
||
|
@@ -860,37 +858,18 @@ int ieee80211_stop_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid) | |
} | ||
EXPORT_SYMBOL(ieee80211_stop_tx_ba_session); | ||
|
||
void ieee80211_stop_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u8 tid) | ||
void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid, | ||
struct tid_ampdu_tx *tid_tx) | ||
{ | ||
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); | ||
struct ieee80211_local *local = sdata->local; | ||
struct sta_info *sta; | ||
struct tid_ampdu_tx *tid_tx; | ||
struct ieee80211_sub_if_data *sdata = sta->sdata; | ||
bool send_delba = false; | ||
|
||
trace_api_stop_tx_ba_cb(sdata, ra, tid); | ||
|
||
if (tid >= IEEE80211_NUM_TIDS) { | ||
ht_dbg(sdata, "Bad TID value: tid = %d (>= %d)\n", | ||
tid, IEEE80211_NUM_TIDS); | ||
return; | ||
} | ||
|
||
ht_dbg(sdata, "Stopping Tx BA session for %pM tid %d\n", ra, tid); | ||
|
||
mutex_lock(&local->sta_mtx); | ||
|
||
sta = sta_info_get_bss(sdata, ra); | ||
if (!sta) { | ||
ht_dbg(sdata, "Could not find station: %pM\n", ra); | ||
goto unlock; | ||
} | ||
ht_dbg(sdata, "Stopping Tx BA session for %pM tid %d\n", | ||
sta->sta.addr, tid); | ||
|
||
mutex_lock(&sta->ampdu_mlme.mtx); | ||
spin_lock_bh(&sta->lock); | ||
tid_tx = rcu_dereference_protected_tid_tx(sta, tid); | ||
|
||
if (!tid_tx || !test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) { | ||
if (!test_bit(HT_AGG_STATE_STOPPING, &tid_tx->state)) { | ||
ht_dbg(sdata, | ||
"unexpected callback to A-MPDU stop for %pM tid %d\n", | ||
sta->sta.addr, tid); | ||
|
@@ -906,32 +885,29 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u8 tid) | |
spin_unlock_bh(&sta->lock); | ||
|
||
if (send_delba) | ||
ieee80211_send_delba(sdata, ra, tid, | ||
ieee80211_send_delba(sdata, sta->sta.addr, tid, | ||
WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE); | ||
|
||
mutex_unlock(&sta->ampdu_mlme.mtx); | ||
unlock: | ||
mutex_unlock(&local->sta_mtx); | ||
} | ||
|
||
void ieee80211_stop_tx_ba_cb_irqsafe(struct ieee80211_vif *vif, | ||
const u8 *ra, u16 tid) | ||
{ | ||
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); | ||
struct ieee80211_local *local = sdata->local; | ||
struct ieee80211_ra_tid *ra_tid; | ||
struct sk_buff *skb = dev_alloc_skb(0); | ||
struct sta_info *sta; | ||
struct tid_ampdu_tx *tid_tx; | ||
|
||
if (unlikely(!skb)) | ||
return; | ||
trace_api_stop_tx_ba_cb(sdata, ra, tid); | ||
|
||
ra_tid = (struct ieee80211_ra_tid *) &skb->cb; | ||
memcpy(&ra_tid->ra, ra, ETH_ALEN); | ||
ra_tid->tid = tid; | ||
rcu_read_lock(); | ||
tid_tx = ieee80211_lookup_tid_tx(sdata, ra, tid, &sta); | ||
if (!tid_tx) | ||
goto out; | ||
|
||
skb->pkt_type = IEEE80211_SDATA_QUEUE_AGG_STOP; | ||
skb_queue_tail(&sdata->skb_queue, skb); | ||
ieee80211_queue_work(&local->hw, &sdata->work); | ||
set_bit(HT_AGG_STATE_STOP_CB, &tid_tx->state); | ||
ieee80211_queue_work(&local->hw, &sta->ampdu_mlme.work); | ||
out: | ||
rcu_read_unlock(); | ||
} | ||
EXPORT_SYMBOL(ieee80211_stop_tx_ba_cb_irqsafe); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
* Copyright 2006-2007 Jiri Benc <[email protected]> | ||
* Copyright 2007, Michael Wu <[email protected]> | ||
* Copyright 2007-2010, Intel Corporation | ||
* Copyright 2017 Intel Deutschland GmbH | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
|
@@ -289,15 +290,16 @@ void ieee80211_sta_tear_down_BA_sessions(struct sta_info *sta, | |
{ | ||
int i; | ||
|
||
cancel_work_sync(&sta->ampdu_mlme.work); | ||
|
||
for (i = 0; i < IEEE80211_NUM_TIDS; i++) { | ||
__ieee80211_stop_tx_ba_session(sta, i, reason); | ||
__ieee80211_stop_rx_ba_session(sta, i, WLAN_BACK_RECIPIENT, | ||
WLAN_REASON_QSTA_LEAVE_QBSS, | ||
reason != AGG_STOP_DESTROY_STA && | ||
reason != AGG_STOP_PEER_REQUEST); | ||
} | ||
|
||
/* stopping might queue the work again - so cancel only afterwards */ | ||
cancel_work_sync(&sta->ampdu_mlme.work); | ||
} | ||
|
||
void ieee80211_ba_session_work(struct work_struct *work) | ||
|
@@ -352,10 +354,16 @@ void ieee80211_ba_session_work(struct work_struct *work) | |
spin_unlock_bh(&sta->lock); | ||
|
||
tid_tx = rcu_dereference_protected_tid_tx(sta, tid); | ||
if (tid_tx && test_and_clear_bit(HT_AGG_STATE_WANT_STOP, | ||
&tid_tx->state)) | ||
if (!tid_tx) | ||
continue; | ||
|
||
if (test_and_clear_bit(HT_AGG_STATE_START_CB, &tid_tx->state)) | ||
ieee80211_start_tx_ba_cb(sta, tid, tid_tx); | ||
if (test_and_clear_bit(HT_AGG_STATE_WANT_STOP, &tid_tx->state)) | ||
___ieee80211_stop_tx_ba_session(sta, tid, | ||
AGG_STOP_LOCAL_REQUEST); | ||
if (test_and_clear_bit(HT_AGG_STATE_STOP_CB, &tid_tx->state)) | ||
ieee80211_stop_tx_ba_cb(sta, tid, tid_tx); | ||
} | ||
mutex_unlock(&sta->ampdu_mlme.mtx); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters