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/iface.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/iface.c')
-rw-r--r-- | net/mac80211/iface.c | 28 |
1 files changed, 16 insertions, 12 deletions
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]; |