mac80211: fix station destruction in AP/mesh modes
Unfortunately, commit b22cfcfcae
, 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>
This commit is contained in:
@@ -91,9 +91,8 @@ static int sta_info_hash_del(struct ieee80211_local *local,
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void free_sta_work(struct work_struct *wk)
|
||||
static void cleanup_single_sta(struct sta_info *sta)
|
||||
{
|
||||
struct sta_info *sta = container_of(wk, struct sta_info, free_sta_wk);
|
||||
int ac, i;
|
||||
struct tid_ampdu_tx *tid_tx;
|
||||
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
||||
@@ -153,11 +152,35 @@ static void free_sta_work(struct work_struct *wk)
|
||||
sta_info_free(local, sta);
|
||||
}
|
||||
|
||||
void ieee80211_cleanup_sdata_stas(struct ieee80211_sub_if_data *sdata)
|
||||
{
|
||||
struct sta_info *sta;
|
||||
|
||||
spin_lock_bh(&sdata->cleanup_stations_lock);
|
||||
while (!list_empty(&sdata->cleanup_stations)) {
|
||||
sta = list_first_entry(&sdata->cleanup_stations,
|
||||
struct sta_info, list);
|
||||
list_del(&sta->list);
|
||||
spin_unlock_bh(&sdata->cleanup_stations_lock);
|
||||
|
||||
cleanup_single_sta(sta);
|
||||
|
||||
spin_lock_bh(&sdata->cleanup_stations_lock);
|
||||
}
|
||||
|
||||
spin_unlock_bh(&sdata->cleanup_stations_lock);
|
||||
}
|
||||
|
||||
static void free_sta_rcu(struct rcu_head *h)
|
||||
{
|
||||
struct sta_info *sta = container_of(h, struct sta_info, rcu_head);
|
||||
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
||||
|
||||
ieee80211_queue_work(&sta->local->hw, &sta->free_sta_wk);
|
||||
spin_lock(&sdata->cleanup_stations_lock);
|
||||
list_add_tail(&sta->list, &sdata->cleanup_stations);
|
||||
spin_unlock(&sdata->cleanup_stations_lock);
|
||||
|
||||
ieee80211_queue_work(&sdata->local->hw, &sdata->cleanup_stations_wk);
|
||||
}
|
||||
|
||||
/* protected by RCU */
|
||||
@@ -310,7 +333,6 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
|
||||
|
||||
spin_lock_init(&sta->lock);
|
||||
INIT_WORK(&sta->drv_unblock_wk, sta_unblock);
|
||||
INIT_WORK(&sta->free_sta_wk, free_sta_work);
|
||||
INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work);
|
||||
mutex_init(&sta->ampdu_mlme.mtx);
|
||||
|
||||
@@ -891,6 +913,20 @@ int sta_info_flush(struct ieee80211_local *local,
|
||||
}
|
||||
mutex_unlock(&local->sta_mtx);
|
||||
|
||||
rcu_barrier();
|
||||
|
||||
if (sdata) {
|
||||
ieee80211_cleanup_sdata_stas(sdata);
|
||||
cancel_work_sync(&sdata->cleanup_stations_wk);
|
||||
} else {
|
||||
mutex_lock(&local->iflist_mtx);
|
||||
list_for_each_entry(sdata, &local->interfaces, list) {
|
||||
ieee80211_cleanup_sdata_stas(sdata);
|
||||
cancel_work_sync(&sdata->cleanup_stations_wk);
|
||||
}
|
||||
mutex_unlock(&local->iflist_mtx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user