From b52c3cbf9691a654f51d48196985bf1304e6af28 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 3 Jun 2014 13:59:43 +0000 Subject: [PATCH] mac80211: backport a powersave related fix Signed-off-by: Felix Fietkau git-svn-id: svn://svn.openwrt.org/openwrt/trunk@40995 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- .../mac80211/patches/300-pending_work.patch | 227 +++++++++++++++++- 1 file changed, 225 insertions(+), 2 deletions(-) diff --git a/package/kernel/mac80211/patches/300-pending_work.patch b/package/kernel/mac80211/patches/300-pending_work.patch index 0a2e86b1d7..c621ddbdac 100644 --- a/package/kernel/mac80211/patches/300-pending_work.patch +++ b/package/kernel/mac80211/patches/300-pending_work.patch @@ -1,3 +1,40 @@ +commit 930b0dffd1731f3f418f9132faea720a23b7af61 +Author: Johannes Berg +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 + Signed-off-by: Johannes Berg + commit 6df35206bc6c1c6aad1d8077df5786b4a7f77873 Author: Felix Fietkau 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; + } -- 2.35.1