diff options
author | Johannes Berg <johannes.berg@intel.com> | 2010-11-16 22:50:28 +0300 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2010-11-18 00:19:33 +0300 |
commit | 50a9432daeece6fc1309bef1dc0a7b8fde8204cb (patch) | |
tree | 11b8bdf724dd9951391ea7f963e6539ca86ea4b6 /net/mac80211/sta_info.c | |
parent | 4bce22b9b84032c77c7e038b07b24fcc706dfc10 (diff) | |
download | linux-50a9432daeece6fc1309bef1dc0a7b8fde8204cb.tar.xz |
mac80211: fix powersaving clients races
The code to handle powersaving stations has a race:
when the powersave flag is lifted from a station,
we could transmit a packet that is being processed
for TX at the same time right away, even if there
are other frames queued for it. This would cause
frame reordering. To fix this, lift the flag only
under the appropriate lock that blocks TX.
Additionally, the code to allow drivers to block a
station while frames for it are on the HW queue is
never re-enabled the station, so traffic would get
stuck indefinitely. Fix this by clearing the flag
for this appropriately.
Finally, as an optimisation, don't do anything if
the driver unblocks an already unblocked station.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/mac80211/sta_info.c')
-rw-r--r-- | net/mac80211/sta_info.c | 17 |
1 files changed, 14 insertions, 3 deletions
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 6d8f897d8763..eff58571fd7e 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -199,8 +199,11 @@ static void sta_unblock(struct work_struct *wk) if (!test_sta_flags(sta, WLAN_STA_PS_STA)) ieee80211_sta_ps_deliver_wakeup(sta); - else if (test_and_clear_sta_flags(sta, WLAN_STA_PSPOLL)) + else if (test_and_clear_sta_flags(sta, WLAN_STA_PSPOLL)) { + clear_sta_flags(sta, WLAN_STA_PS_DRIVER); ieee80211_sta_ps_deliver_poll_response(sta); + } else + clear_sta_flags(sta, WLAN_STA_PS_DRIVER); } static int sta_prepare_rate_control(struct ieee80211_local *local, @@ -880,6 +883,13 @@ struct ieee80211_sta *ieee80211_find_sta(struct ieee80211_vif *vif, } EXPORT_SYMBOL(ieee80211_find_sta); +static void clear_sta_ps_flags(void *_sta) +{ + struct sta_info *sta = _sta; + + clear_sta_flags(sta, WLAN_STA_PS_DRIVER | WLAN_STA_PS_STA); +} + /* powersave support code */ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) { @@ -894,7 +904,8 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) /* Send all buffered frames to the station */ sent = ieee80211_add_pending_skbs(local, &sta->tx_filtered); - buffered = ieee80211_add_pending_skbs(local, &sta->ps_tx_buf); + buffered = ieee80211_add_pending_skbs_fn(local, &sta->ps_tx_buf, + clear_sta_ps_flags, sta); sent += buffered; local->total_ps_buffered -= buffered; @@ -973,7 +984,7 @@ void ieee80211_sta_block_awake(struct ieee80211_hw *hw, if (block) set_sta_flags(sta, WLAN_STA_PS_DRIVER); - else + else if (test_sta_flags(sta, WLAN_STA_PS_DRIVER)) ieee80211_queue_work(hw, &sta->drv_unblock_wk); } EXPORT_SYMBOL(ieee80211_sta_block_awake); |