|
|
|
@ -1,3 +1,40 @@ |
|
|
|
|
commit 930b0dffd1731f3f418f9132faea720a23b7af61
|
|
|
|
|
Author: Johannes Berg <johannes.berg@intel.com>
|
|
|
|
|
Date: Tue Jun 3 11:18:47 2014 +0200
|
|
|
|
|
|
|
|
|
|
mac80211: fix station/driver powersave race
|
|
|
|
|
|
|
|
|
|
It is currently possible to have a race due to the station PS
|
|
|
|
|
unblock work like this:
|
|
|
|
|
* station goes to sleep with frames buffered in the driver
|
|
|
|
|
* driver blocks wakeup
|
|
|
|
|
* station wakes up again
|
|
|
|
|
* driver flushes/returns frames, and unblocks, which schedules
|
|
|
|
|
the unblock work
|
|
|
|
|
* unblock work starts to run, and checks that the station is
|
|
|
|
|
awake (i.e. that the WLAN_STA_PS_STA flag isn't set)
|
|
|
|
|
* we process a received frame with PM=1, setting the flag again
|
|
|
|
|
* ieee80211_sta_ps_deliver_wakeup() runs, delivering all frames
|
|
|
|
|
to the driver, and then clearing the WLAN_STA_PS_DRIVER and
|
|
|
|
|
WLAN_STA_PS_STA flags
|
|
|
|
|
|
|
|
|
|
In this scenario, mac80211 will think that the station is awake,
|
|
|
|
|
while it really is asleep, and any TX'ed frames should be filtered
|
|
|
|
|
by the device (it will know that the station is sleeping) but then
|
|
|
|
|
passed to mac80211 again, which will not buffer it either as it
|
|
|
|
|
thinks the station is awake, and eventually the packets will be
|
|
|
|
|
dropped.
|
|
|
|
|
|
|
|
|
|
Fix this by moving the clearing of the flags to exactly where we
|
|
|
|
|
learn about the situation. This creates a problem of reordering,
|
|
|
|
|
so introduce another flag indicating that delivery is being done,
|
|
|
|
|
this new flag also queues frames and is cleared only while the
|
|
|
|
|
spinlock is held (which the queuing code also holds) so that any
|
|
|
|
|
concurrent delivery/TX is handled correctly.
|
|
|
|
|
|
|
|
|
|
Reported-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
|
|
|
|
|
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
|
|
|
|
|
|
|
|
|
|
commit 6df35206bc6c1c6aad1d8077df5786b4a7f77873
|
|
|
|
|
Author: Felix Fietkau <nbd@openwrt.org>
|
|
|
|
|
Date: Fri May 23 19:58:14 2014 +0200
|
|
|
|
@ -101,7 +138,34 @@ Date: Mon May 19 21:20:49 2014 +0200 |
|
|
|
|
if (!budget--)
|
|
|
|
|
--- a/net/mac80211/sta_info.c
|
|
|
|
|
+++ b/net/mac80211/sta_info.c
|
|
|
|
|
@@ -227,6 +227,7 @@ struct sta_info *sta_info_get_by_idx(str
|
|
|
|
|
@@ -100,7 +100,8 @@ static void __cleanup_single_sta(struct
|
|
|
|
|
struct ps_data *ps;
|
|
|
|
|
|
|
|
|
|
if (test_sta_flag(sta, WLAN_STA_PS_STA) ||
|
|
|
|
|
- test_sta_flag(sta, WLAN_STA_PS_DRIVER)) {
|
|
|
|
|
+ test_sta_flag(sta, WLAN_STA_PS_DRIVER) ||
|
|
|
|
|
+ test_sta_flag(sta, WLAN_STA_PS_DELIVER)) {
|
|
|
|
|
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
|
|
|
|
|
sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
|
|
|
|
|
ps = &sdata->bss->ps;
|
|
|
|
|
@@ -111,6 +112,7 @@ static void __cleanup_single_sta(struct
|
|
|
|
|
|
|
|
|
|
clear_sta_flag(sta, WLAN_STA_PS_STA);
|
|
|
|
|
clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PS_DELIVER);
|
|
|
|
|
|
|
|
|
|
atomic_dec(&ps->num_sta_ps);
|
|
|
|
|
sta_info_recalc_tim(sta);
|
|
|
|
|
@@ -125,7 +127,7 @@ static void __cleanup_single_sta(struct
|
|
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif))
|
|
|
|
|
mesh_sta_cleanup(sta);
|
|
|
|
|
|
|
|
|
|
- cancel_work_sync(&sta->drv_unblock_wk);
|
|
|
|
|
+ cancel_work_sync(&sta->drv_deliver_wk);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Destroy aggregation state here. It would be nice to wait for the
|
|
|
|
|
@@ -227,6 +229,7 @@ struct sta_info *sta_info_get_by_idx(str
|
|
|
|
|
*/
|
|
|
|
|
void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
|
|
|
|
|
{
|
|
|
|
@ -109,7 +173,7 @@ Date: Mon May 19 21:20:49 2014 +0200 |
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if (sta->rate_ctrl)
|
|
|
|
|
@@ -238,6 +239,10 @@ void sta_info_free(struct ieee80211_loca
|
|
|
|
|
@@ -238,6 +241,10 @@ void sta_info_free(struct ieee80211_loca
|
|
|
|
|
kfree(sta->tx_lat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -120,6 +184,104 @@ Date: Mon May 19 21:20:49 2014 +0200 |
|
|
|
|
sta_dbg(sta->sdata, "Destroyed STA %pM\n", sta->sta.addr);
|
|
|
|
|
|
|
|
|
|
kfree(sta);
|
|
|
|
|
@@ -252,33 +259,23 @@ static void sta_info_hash_add(struct iee
|
|
|
|
|
rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-static void sta_unblock(struct work_struct *wk)
|
|
|
|
|
+static void sta_deliver_ps_frames(struct work_struct *wk)
|
|
|
|
|
{
|
|
|
|
|
struct sta_info *sta;
|
|
|
|
|
|
|
|
|
|
- sta = container_of(wk, struct sta_info, drv_unblock_wk);
|
|
|
|
|
+ sta = container_of(wk, struct sta_info, drv_deliver_wk);
|
|
|
|
|
|
|
|
|
|
if (sta->dead)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
- if (!test_sta_flag(sta, WLAN_STA_PS_STA)) {
|
|
|
|
|
- local_bh_disable();
|
|
|
|
|
+ local_bh_disable();
|
|
|
|
|
+ if (!test_sta_flag(sta, WLAN_STA_PS_STA))
|
|
|
|
|
ieee80211_sta_ps_deliver_wakeup(sta);
|
|
|
|
|
- local_bh_enable();
|
|
|
|
|
- } else if (test_and_clear_sta_flag(sta, WLAN_STA_PSPOLL)) {
|
|
|
|
|
- clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
-
|
|
|
|
|
- local_bh_disable();
|
|
|
|
|
+ else if (test_and_clear_sta_flag(sta, WLAN_STA_PSPOLL))
|
|
|
|
|
ieee80211_sta_ps_deliver_poll_response(sta);
|
|
|
|
|
- local_bh_enable();
|
|
|
|
|
- } else if (test_and_clear_sta_flag(sta, WLAN_STA_UAPSD)) {
|
|
|
|
|
- clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
-
|
|
|
|
|
- local_bh_disable();
|
|
|
|
|
+ else if (test_and_clear_sta_flag(sta, WLAN_STA_UAPSD))
|
|
|
|
|
ieee80211_sta_ps_deliver_uapsd(sta);
|
|
|
|
|
- local_bh_enable();
|
|
|
|
|
- } else
|
|
|
|
|
- clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
+ local_bh_enable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int sta_prepare_rate_control(struct ieee80211_local *local,
|
|
|
|
|
@@ -340,7 +337,7 @@ struct sta_info *sta_info_alloc(struct i
|
|
|
|
|
|
|
|
|
|
spin_lock_init(&sta->lock);
|
|
|
|
|
spin_lock_init(&sta->ps_lock);
|
|
|
|
|
- INIT_WORK(&sta->drv_unblock_wk, sta_unblock);
|
|
|
|
|
+ INIT_WORK(&sta->drv_deliver_wk, sta_deliver_ps_frames);
|
|
|
|
|
INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work);
|
|
|
|
|
mutex_init(&sta->ampdu_mlme.mtx);
|
|
|
|
|
#ifdef CPTCFG_MAC80211_MESH
|
|
|
|
|
@@ -1140,8 +1137,15 @@ void ieee80211_sta_ps_deliver_wakeup(str
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ieee80211_add_pending_skbs(local, &pending);
|
|
|
|
|
- clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
- clear_sta_flag(sta, WLAN_STA_PS_STA);
|
|
|
|
|
+
|
|
|
|
|
+ /* now we're no longer in the deliver code */
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PS_DELIVER);
|
|
|
|
|
+
|
|
|
|
|
+ /* The station might have polled and then woken up before we responded,
|
|
|
|
|
+ * so clear these flags now to avoid them sticking around.
|
|
|
|
|
+ */
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PSPOLL);
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_UAPSD);
|
|
|
|
|
spin_unlock(&sta->ps_lock);
|
|
|
|
|
|
|
|
|
|
atomic_dec(&ps->num_sta_ps);
|
|
|
|
|
@@ -1542,10 +1546,26 @@ void ieee80211_sta_block_awake(struct ie
|
|
|
|
|
|
|
|
|
|
trace_api_sta_block_awake(sta->local, pubsta, block);
|
|
|
|
|
|
|
|
|
|
- if (block)
|
|
|
|
|
+ if (block) {
|
|
|
|
|
set_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
- else if (test_sta_flag(sta, WLAN_STA_PS_DRIVER))
|
|
|
|
|
- ieee80211_queue_work(hw, &sta->drv_unblock_wk);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!test_sta_flag(sta, WLAN_STA_PS_DRIVER))
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ if (!test_sta_flag(sta, WLAN_STA_PS_STA)) {
|
|
|
|
|
+ set_sta_flag(sta, WLAN_STA_PS_DELIVER);
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
+ ieee80211_queue_work(hw, &sta->drv_deliver_wk);
|
|
|
|
|
+ } else if (test_sta_flag(sta, WLAN_STA_PSPOLL) ||
|
|
|
|
|
+ test_sta_flag(sta, WLAN_STA_UAPSD)) {
|
|
|
|
|
+ /* must be asleep in this case */
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
+ ieee80211_queue_work(hw, &sta->drv_deliver_wk);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PS_DRIVER);
|
|
|
|
|
+ }
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL(ieee80211_sta_block_awake);
|
|
|
|
|
|
|
|
|
|
--- a/net/mac80211/status.c
|
|
|
|
|
+++ b/net/mac80211/status.c
|
|
|
|
|
@@ -541,6 +541,23 @@ static void ieee80211_tx_latency_end_msr
|
|
|
|
@ -161,3 +323,64 @@ Date: Mon May 19 21:20:49 2014 +0200 |
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
--- a/net/mac80211/rx.c
|
|
|
|
|
+++ b/net/mac80211/rx.c
|
|
|
|
|
@@ -1107,6 +1107,8 @@ static void sta_ps_end(struct sta_info *
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ set_sta_flag(sta, WLAN_STA_PS_DELIVER);
|
|
|
|
|
+ clear_sta_flag(sta, WLAN_STA_PS_STA);
|
|
|
|
|
ieee80211_sta_ps_deliver_wakeup(sta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
--- a/net/mac80211/sta_info.h
|
|
|
|
|
+++ b/net/mac80211/sta_info.h
|
|
|
|
|
@@ -82,6 +82,7 @@ enum ieee80211_sta_info_flags {
|
|
|
|
|
WLAN_STA_TOFFSET_KNOWN,
|
|
|
|
|
WLAN_STA_MPSP_OWNER,
|
|
|
|
|
WLAN_STA_MPSP_RECIPIENT,
|
|
|
|
|
+ WLAN_STA_PS_DELIVER,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define ADDBA_RESP_INTERVAL HZ
|
|
|
|
|
@@ -265,7 +266,7 @@ struct ieee80211_tx_latency_stat {
|
|
|
|
|
* @last_rx_rate_vht_nss: rx status nss of last data packet
|
|
|
|
|
* @lock: used for locking all fields that require locking, see comments
|
|
|
|
|
* in the header file.
|
|
|
|
|
- * @drv_unblock_wk: used for driver PS unblocking
|
|
|
|
|
+ * @drv_deliver_wk: used for delivering frames after driver PS unblocking
|
|
|
|
|
* @listen_interval: listen interval of this station, when we're acting as AP
|
|
|
|
|
* @_flags: STA flags, see &enum ieee80211_sta_info_flags, do not use directly
|
|
|
|
|
* @ps_lock: used for powersave (when mac80211 is the AP) related locking
|
|
|
|
|
@@ -345,7 +346,7 @@ struct sta_info {
|
|
|
|
|
void *rate_ctrl_priv;
|
|
|
|
|
spinlock_t lock;
|
|
|
|
|
|
|
|
|
|
- struct work_struct drv_unblock_wk;
|
|
|
|
|
+ struct work_struct drv_deliver_wk;
|
|
|
|
|
|
|
|
|
|
u16 listen_interval;
|
|
|
|
|
|
|
|
|
|
--- a/net/mac80211/tx.c
|
|
|
|
|
+++ b/net/mac80211/tx.c
|
|
|
|
|
@@ -469,7 +469,8 @@ ieee80211_tx_h_unicast_ps_buf(struct iee
|
|
|
|
|
return TX_CONTINUE;
|
|
|
|
|
|
|
|
|
|
if (unlikely((test_sta_flag(sta, WLAN_STA_PS_STA) ||
|
|
|
|
|
- test_sta_flag(sta, WLAN_STA_PS_DRIVER)) &&
|
|
|
|
|
+ test_sta_flag(sta, WLAN_STA_PS_DRIVER) ||
|
|
|
|
|
+ test_sta_flag(sta, WLAN_STA_PS_DELIVER)) &&
|
|
|
|
|
!(info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER))) {
|
|
|
|
|
int ac = skb_get_queue_mapping(tx->skb);
|
|
|
|
|
|
|
|
|
|
@@ -486,7 +487,8 @@ ieee80211_tx_h_unicast_ps_buf(struct iee
|
|
|
|
|
* ahead and Tx the packet.
|
|
|
|
|
*/
|
|
|
|
|
if (!test_sta_flag(sta, WLAN_STA_PS_STA) &&
|
|
|
|
|
- !test_sta_flag(sta, WLAN_STA_PS_DRIVER)) {
|
|
|
|
|
+ !test_sta_flag(sta, WLAN_STA_PS_DRIVER) &&
|
|
|
|
|
+ !test_sta_flag(sta, WLAN_STA_PS_DELIVER)) {
|
|
|
|
|
spin_unlock(&sta->ps_lock);
|
|
|
|
|
return TX_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|