Skip to content

Commit

Permalink
mac80211: fix station destruction in AP/mesh modes
Browse files Browse the repository at this point in the history
Unfortunately, commit b22cfcf, 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>
  • Loading branch information
Johannes Berg committed Jan 3, 2013
1 parent b7cfcd1 commit 97f97b1
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 17 deletions.
4 changes: 4 additions & 0 deletions net/mac80211/ieee80211_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,10 @@ struct ieee80211_sub_if_data {
u32 mntr_flags;
} u;

spinlock_t cleanup_stations_lock;
struct list_head cleanup_stations;
struct work_struct cleanup_stations_wk;

#ifdef CONFIG_MAC80211_DEBUGFS
struct {
struct dentry *dir;
Expand Down
28 changes: 16 additions & 12 deletions net/mac80211/iface.c
Original file line number Diff line number Diff line change
Expand Up @@ -868,20 +868,11 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
cancel_work_sync(&sdata->work);
/*
* When we get here, the interface is marked down.
* Call rcu_barrier() to wait both for the RX path
* Call synchronize_rcu() to wait for the RX path
* should it be using the interface and enqueuing
* frames at this very time on another CPU, and
* for the sta free call_rcu callbacks.
* frames at this very time on another CPU.
*/
rcu_barrier();

/*
* free_sta_rcu() enqueues a work for the actual
* sta cleanup, so we need to flush it while
* sdata is still valid.
*/
flush_workqueue(local->workqueue);

synchronize_rcu();
skb_queue_purge(&sdata->skb_queue);

/*
Expand Down Expand Up @@ -1501,6 +1492,15 @@ static void ieee80211_assign_perm_addr(struct ieee80211_local *local,
mutex_unlock(&local->iflist_mtx);
}

static void ieee80211_cleanup_sdata_stas_wk(struct work_struct *wk)
{
struct ieee80211_sub_if_data *sdata;

sdata = container_of(wk, struct ieee80211_sub_if_data, cleanup_stations_wk);

ieee80211_cleanup_sdata_stas(sdata);
}

int ieee80211_if_add(struct ieee80211_local *local, const char *name,
struct wireless_dev **new_wdev, enum nl80211_iftype type,
struct vif_params *params)
Expand Down Expand Up @@ -1576,6 +1576,10 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,

INIT_LIST_HEAD(&sdata->key_list);

spin_lock_init(&sdata->cleanup_stations_lock);
INIT_LIST_HEAD(&sdata->cleanup_stations);
INIT_WORK(&sdata->cleanup_stations_wk, ieee80211_cleanup_sdata_stas_wk);

for (i = 0; i < IEEE80211_NUM_BANDS; i++) {
struct ieee80211_supported_band *sband;
sband = local->hw.wiphy->bands[i];
Expand Down
44 changes: 40 additions & 4 deletions net/mac80211/sta_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion net/mac80211/sta_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ struct sta_info {
spinlock_t lock;

struct work_struct drv_unblock_wk;
struct work_struct free_sta_wk;

u16 listen_interval;

Expand Down Expand Up @@ -563,4 +562,6 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta);
void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta);
void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);

void ieee80211_cleanup_sdata_stas(struct ieee80211_sub_if_data *sdata);

#endif /* STA_INFO_H */

0 comments on commit 97f97b1

Please sign in to comment.