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 | |
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')
-rw-r--r-- | net/mac80211/ieee80211_i.h | 4 | ||||
-rw-r--r-- | net/mac80211/iface.c | 28 | ||||
-rw-r--r-- | net/mac80211/sta_info.c | 44 | ||||
-rw-r--r-- | net/mac80211/sta_info.h | 3 |
4 files changed, 62 insertions, 17 deletions
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 28fc9875537c..2fcd274877d7 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -773,6 +773,10 @@ struct ieee80211_sub_if_data { | |||
773 | u32 mntr_flags; | 773 | u32 mntr_flags; |
774 | } u; | 774 | } u; |
775 | 775 | ||
776 | spinlock_t cleanup_stations_lock; | ||
777 | struct list_head cleanup_stations; | ||
778 | struct work_struct cleanup_stations_wk; | ||
779 | |||
776 | #ifdef CONFIG_MAC80211_DEBUGFS | 780 | #ifdef CONFIG_MAC80211_DEBUGFS |
777 | struct { | 781 | struct { |
778 | struct dentry *dir; | 782 | struct dentry *dir; |
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 54fb7f9db564..0f2a9f987f79 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c | |||
@@ -868,20 +868,11 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, | |||
868 | cancel_work_sync(&sdata->work); | 868 | cancel_work_sync(&sdata->work); |
869 | /* | 869 | /* |
870 | * When we get here, the interface is marked down. | 870 | * When we get here, the interface is marked down. |
871 | * Call rcu_barrier() to wait both for the RX path | 871 | * Call synchronize_rcu() to wait for the RX path |
872 | * should it be using the interface and enqueuing | 872 | * should it be using the interface and enqueuing |
873 | * frames at this very time on another CPU, and | 873 | * frames at this very time on another CPU. |
874 | * for the sta free call_rcu callbacks. | ||
875 | */ | 874 | */ |
876 | rcu_barrier(); | 875 | synchronize_rcu(); |
877 | |||
878 | /* | ||
879 | * free_sta_rcu() enqueues a work for the actual | ||
880 | * sta cleanup, so we need to flush it while | ||
881 | * sdata is still valid. | ||
882 | */ | ||
883 | flush_workqueue(local->workqueue); | ||
884 | |||
885 | skb_queue_purge(&sdata->skb_queue); | 876 | skb_queue_purge(&sdata->skb_queue); |
886 | 877 | ||
887 | /* | 878 | /* |
@@ -1501,6 +1492,15 @@ static void ieee80211_assign_perm_addr(struct ieee80211_local *local, | |||
1501 | mutex_unlock(&local->iflist_mtx); | 1492 | mutex_unlock(&local->iflist_mtx); |
1502 | } | 1493 | } |
1503 | 1494 | ||
1495 | static void ieee80211_cleanup_sdata_stas_wk(struct work_struct *wk) | ||
1496 | { | ||
1497 | struct ieee80211_sub_if_data *sdata; | ||
1498 | |||
1499 | sdata = container_of(wk, struct ieee80211_sub_if_data, cleanup_stations_wk); | ||
1500 | |||
1501 | ieee80211_cleanup_sdata_stas(sdata); | ||
1502 | } | ||
1503 | |||
1504 | int ieee80211_if_add(struct ieee80211_local *local, const char *name, | 1504 | int ieee80211_if_add(struct ieee80211_local *local, const char *name, |
1505 | struct wireless_dev **new_wdev, enum nl80211_iftype type, | 1505 | struct wireless_dev **new_wdev, enum nl80211_iftype type, |
1506 | struct vif_params *params) | 1506 | struct vif_params *params) |
@@ -1576,6 +1576,10 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, | |||
1576 | 1576 | ||
1577 | INIT_LIST_HEAD(&sdata->key_list); | 1577 | INIT_LIST_HEAD(&sdata->key_list); |
1578 | 1578 | ||
1579 | spin_lock_init(&sdata->cleanup_stations_lock); | ||
1580 | INIT_LIST_HEAD(&sdata->cleanup_stations); | ||
1581 | INIT_WORK(&sdata->cleanup_stations_wk, ieee80211_cleanup_sdata_stas_wk); | ||
1582 | |||
1579 | for (i = 0; i < IEEE80211_NUM_BANDS; i++) { | 1583 | for (i = 0; i < IEEE80211_NUM_BANDS; i++) { |
1580 | struct ieee80211_supported_band *sband; | 1584 | struct ieee80211_supported_band *sband; |
1581 | sband = local->hw.wiphy->bands[i]; | 1585 | sband = local->hw.wiphy->bands[i]; |
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 | ||
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 1489bca9ea97..37c1889afd3a 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h | |||
@@ -299,7 +299,6 @@ struct sta_info { | |||
299 | spinlock_t lock; | 299 | spinlock_t lock; |
300 | 300 | ||
301 | struct work_struct drv_unblock_wk; | 301 | struct work_struct drv_unblock_wk; |
302 | struct work_struct free_sta_wk; | ||
303 | 302 | ||
304 | u16 listen_interval; | 303 | u16 listen_interval; |
305 | 304 | ||
@@ -563,4 +562,6 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta); | |||
563 | void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta); | 562 | void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta); |
564 | void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta); | 563 | void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta); |
565 | 564 | ||
565 | void ieee80211_cleanup_sdata_stas(struct ieee80211_sub_if_data *sdata); | ||
566 | |||
566 | #endif /* STA_INFO_H */ | 567 | #endif /* STA_INFO_H */ |