Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 97f97b1f authored by Johannes Berg's avatar Johannes Berg
Browse files

mac80211: fix station destruction in AP/mesh modes



Unfortunately, commit b22cfcfc, 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: default avatarBen Greear <greearb@candelatech.com>
Tested-by: default avatarBen Greear <greearb@candelatech.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent b7cfcd11
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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;
+16 −12
Original line number Diff line number Diff line
@@ -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);

		/*
@@ -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)
@@ -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];
+40 −4
Original line number Diff line number Diff line
@@ -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;
}

+2 −1
Original line number Diff line number Diff line
@@ -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;

@@ -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 */