diff options
author | Johannes Berg <johannes.berg@intel.com> | 2012-12-13 16:54:58 -0500 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2013-01-03 06:59:59 -0500 |
commit | 97f97b1f5fe0878b35c8e314f98591771696321b (patch) | |
tree | d51b86324030567fe51f9112c2d9d2242e07c008 /net/mac80211/sta_info.c | |
parent | b7cfcd113ac2a1e6b02afc7d283295729fc178a9 (diff) |
mac80211: fix station destruction in AP/mesh modes
Unfortunately, commit b22cfcfcae5b, intended to speed up roaming
by avoiding the synchronize_rcu() broke AP/mesh modes as it moved
some code into that work item that will still call into the driver
at a time where it's no longer expected to handle this: after the
AP or mesh has been stopped.
To fix this problem remove the per-station work struct, maintain a
station cleanup list instead and flush this list when stations are
flushed. To keep this patch smaller for stable, do this when the
stations are flushed (sta_info_flush()). This unfortunately brings
back the original roaming delay; I'll fix that again in a separate
patch.
Also, Ben reported that the original commit could sometimes (with
many interfaces) cause long delays when an interface is set down,
due to blocking on flush_workqueue(). Since we now maintain the
cleanup list, this particular change of the original patch can be
reverted.
Cc: stable@vger.kernel.org [3.7]
Reported-by: Ben Greear <greearb@candelatech.com>
Tested-by: Ben Greear <greearb@candelatech.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/mac80211/sta_info.c')
-rw-r--r-- | net/mac80211/sta_info.c | 44 |
1 files changed, 40 insertions, 4 deletions
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index f3e502502fee..8bbd3b0fdbcc 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c | |||
@@ -91,9 +91,8 @@ static int sta_info_hash_del(struct ieee80211_local *local, | |||
91 | return -ENOENT; | 91 | return -ENOENT; |
92 | } | 92 | } |
93 | 93 | ||
94 | static void free_sta_work(struct work_struct *wk) | 94 | static void cleanup_single_sta(struct sta_info *sta) |
95 | { | 95 | { |
96 | struct sta_info *sta = container_of(wk, struct sta_info, free_sta_wk); | ||
97 | int ac, i; | 96 | int ac, i; |
98 | struct tid_ampdu_tx *tid_tx; | 97 | struct tid_ampdu_tx *tid_tx; |
99 | struct ieee80211_sub_if_data *sdata = sta->sdata; | 98 | struct ieee80211_sub_if_data *sdata = sta->sdata; |
@@ -153,11 +152,35 @@ static void free_sta_work(struct work_struct *wk) | |||
153 | sta_info_free(local, sta); | 152 | sta_info_free(local, sta); |
154 | } | 153 | } |
155 | 154 | ||
155 | void ieee80211_cleanup_sdata_stas(struct ieee80211_sub_if_data *sdata) | ||
156 | { | ||
157 | struct sta_info *sta; | ||
158 | |||
159 | spin_lock_bh(&sdata->cleanup_stations_lock); | ||
160 | while (!list_empty(&sdata->cleanup_stations)) { | ||
161 | sta = list_first_entry(&sdata->cleanup_stations, | ||
162 | struct sta_info, list); | ||
163 | list_del(&sta->list); | ||
164 | spin_unlock_bh(&sdata->cleanup_stations_lock); | ||
165 | |||
166 | cleanup_single_sta(sta); | ||
167 | |||
168 | spin_lock_bh(&sdata->cleanup_stations_lock); | ||
169 | } | ||
170 | |||
171 | spin_unlock_bh(&sdata->cleanup_stations_lock); | ||
172 | } | ||
173 | |||
156 | static void free_sta_rcu(struct rcu_head *h) | 174 | static void free_sta_rcu(struct rcu_head *h) |
157 | { | 175 | { |
158 | struct sta_info *sta = container_of(h, struct sta_info, rcu_head); | 176 | struct sta_info *sta = container_of(h, struct sta_info, rcu_head); |
177 | struct ieee80211_sub_if_data *sdata = sta->sdata; | ||
159 | 178 | ||
160 | ieee80211_queue_work(&sta->local->hw, &sta->free_sta_wk); | 179 | spin_lock(&sdata->cleanup_stations_lock); |
180 | list_add_tail(&sta->list, &sdata->cleanup_stations); | ||
181 | spin_unlock(&sdata->cleanup_stations_lock); | ||
182 | |||
183 | ieee80211_queue_work(&sdata->local->hw, &sdata->cleanup_stations_wk); | ||
161 | } | 184 | } |
162 | 185 | ||
163 | /* protected by RCU */ | 186 | /* protected by RCU */ |
@@ -310,7 +333,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, | |||
310 | 333 | ||
311 | spin_lock_init(&sta->lock); | 334 | spin_lock_init(&sta->lock); |
312 | INIT_WORK(&sta->drv_unblock_wk, sta_unblock); | 335 | INIT_WORK(&sta->drv_unblock_wk, sta_unblock); |
313 | INIT_WORK(&sta->free_sta_wk, free_sta_work); | ||
314 | INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work); | 336 | INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work); |
315 | mutex_init(&sta->ampdu_mlme.mtx); | 337 | mutex_init(&sta->ampdu_mlme.mtx); |
316 | 338 | ||
@@ -891,6 +913,20 @@ int sta_info_flush(struct ieee80211_local *local, | |||
891 | } | 913 | } |
892 | mutex_unlock(&local->sta_mtx); | 914 | mutex_unlock(&local->sta_mtx); |
893 | 915 | ||
916 | rcu_barrier(); | ||
917 | |||
918 | if (sdata) { | ||
919 | ieee80211_cleanup_sdata_stas(sdata); | ||
920 | cancel_work_sync(&sdata->cleanup_stations_wk); | ||
921 | } else { | ||
922 | mutex_lock(&local->iflist_mtx); | ||
923 | list_for_each_entry(sdata, &local->interfaces, list) { | ||
924 | ieee80211_cleanup_sdata_stas(sdata); | ||
925 | cancel_work_sync(&sdata->cleanup_stations_wk); | ||
926 | } | ||
927 | mutex_unlock(&local->iflist_mtx); | ||
928 | } | ||
929 | |||
894 | return ret; | 930 | return ret; |
895 | } | 931 | } |
896 | 932 | ||