mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
mac80211: use call_rcu() on sta deletion
mac80211 calls synchronize_rcu() on sta deletion, which increase the roaming time significantly. Convert it into a call_rcu() mechanism, in order to avoid blocking. Since some of the cleanup functions might sleep, schedule from the call_rcu callback a new work that will do the actual cleanup. In order to make sure the cleanup occurs before the interface went down, flush local->workqueue on ieee80211_do_stop(). Signed-off-by: Yoni Divinsky <yoni.divinsky@ti.com> Signed-off-by: Eliad Peller <eliad@wizery.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
parent
e548c49e6d
commit
b22cfcfcae
3 changed files with 81 additions and 57 deletions
|
@ -793,11 +793,20 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
|
|||
flush_work(&sdata->work);
|
||||
/*
|
||||
* When we get here, the interface is marked down.
|
||||
* Call synchronize_rcu() to wait for the RX path
|
||||
* Call rcu_barrier() to wait both for the RX path
|
||||
* should it be using the interface and enqueuing
|
||||
* frames at this very time on another CPU.
|
||||
* frames at this very time on another CPU, and
|
||||
* for the sta free call_rcu callbacks.
|
||||
*/
|
||||
synchronize_rcu();
|
||||
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);
|
||||
|
||||
skb_queue_purge(&sdata->skb_queue);
|
||||
|
||||
/*
|
||||
|
|
|
@ -91,6 +91,70 @@ static int sta_info_hash_del(struct ieee80211_local *local,
|
|||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void free_sta_work(struct work_struct *wk)
|
||||
{
|
||||
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;
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
|
||||
/*
|
||||
* At this point, when being called as call_rcu callback,
|
||||
* neither mac80211 nor the driver can reference this
|
||||
* sta struct any more except by still existing timers
|
||||
* associated with this station that we clean up below.
|
||||
*/
|
||||
|
||||
if (test_sta_flag(sta, WLAN_STA_PS_STA)) {
|
||||
BUG_ON(!sdata->bss);
|
||||
|
||||
clear_sta_flag(sta, WLAN_STA_PS_STA);
|
||||
|
||||
atomic_dec(&sdata->bss->num_sta_ps);
|
||||
sta_info_recalc_tim(sta);
|
||||
}
|
||||
|
||||
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
|
||||
local->total_ps_buffered -= skb_queue_len(&sta->ps_tx_buf[ac]);
|
||||
__skb_queue_purge(&sta->ps_tx_buf[ac]);
|
||||
__skb_queue_purge(&sta->tx_filtered[ac]);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MAC80211_MESH
|
||||
if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
||||
mesh_accept_plinks_update(sdata);
|
||||
mesh_plink_deactivate(sta);
|
||||
del_timer_sync(&sta->plink_timer);
|
||||
}
|
||||
#endif
|
||||
|
||||
cancel_work_sync(&sta->drv_unblock_wk);
|
||||
|
||||
/*
|
||||
* Destroy aggregation state here. It would be nice to wait for the
|
||||
* driver to finish aggregation stop and then clean up, but for now
|
||||
* drivers have to handle aggregation stop being requested, followed
|
||||
* directly by station destruction.
|
||||
*/
|
||||
for (i = 0; i < STA_TID_NUM; i++) {
|
||||
tid_tx = rcu_dereference_raw(sta->ampdu_mlme.tid_tx[i]);
|
||||
if (!tid_tx)
|
||||
continue;
|
||||
__skb_queue_purge(&tid_tx->pending);
|
||||
kfree(tid_tx);
|
||||
}
|
||||
|
||||
sta_info_free(local, sta);
|
||||
}
|
||||
|
||||
static void free_sta_rcu(struct rcu_head *h)
|
||||
{
|
||||
struct sta_info *sta = container_of(h, struct sta_info, rcu_head);
|
||||
|
||||
ieee80211_queue_work(&sta->local->hw, &sta->free_sta_wk);
|
||||
}
|
||||
|
||||
/* protected by RCU */
|
||||
struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
|
||||
const u8 *addr)
|
||||
|
@ -241,6 +305,7 @@ 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);
|
||||
|
||||
|
@ -654,8 +719,7 @@ int __must_check __sta_info_destroy(struct sta_info *sta)
|
|||
{
|
||||
struct ieee80211_local *local;
|
||||
struct ieee80211_sub_if_data *sdata;
|
||||
int ret, i, ac;
|
||||
struct tid_ampdu_tx *tid_tx;
|
||||
int ret, i;
|
||||
|
||||
might_sleep();
|
||||
|
||||
|
@ -711,65 +775,14 @@ int __must_check __sta_info_destroy(struct sta_info *sta)
|
|||
WARN_ON_ONCE(ret != 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point, after we wait for an RCU grace period,
|
||||
* neither mac80211 nor the driver can reference this
|
||||
* sta struct any more except by still existing timers
|
||||
* associated with this station that we clean up below.
|
||||
*/
|
||||
synchronize_rcu();
|
||||
|
||||
if (test_sta_flag(sta, WLAN_STA_PS_STA)) {
|
||||
BUG_ON(!sdata->bss);
|
||||
|
||||
clear_sta_flag(sta, WLAN_STA_PS_STA);
|
||||
|
||||
atomic_dec(&sdata->bss->num_sta_ps);
|
||||
sta_info_recalc_tim(sta);
|
||||
}
|
||||
|
||||
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
|
||||
local->total_ps_buffered -= skb_queue_len(&sta->ps_tx_buf[ac]);
|
||||
__skb_queue_purge(&sta->ps_tx_buf[ac]);
|
||||
__skb_queue_purge(&sta->tx_filtered[ac]);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MAC80211_MESH
|
||||
if (ieee80211_vif_is_mesh(&sdata->vif))
|
||||
mesh_accept_plinks_update(sdata);
|
||||
#endif
|
||||
|
||||
sta_dbg(sdata, "Removed STA %pM\n", sta->sta.addr);
|
||||
|
||||
cancel_work_sync(&sta->drv_unblock_wk);
|
||||
|
||||
cfg80211_del_sta(sdata->dev, sta->sta.addr, GFP_KERNEL);
|
||||
|
||||
rate_control_remove_sta_debugfs(sta);
|
||||
ieee80211_sta_debugfs_remove(sta);
|
||||
|
||||
#ifdef CONFIG_MAC80211_MESH
|
||||
if (ieee80211_vif_is_mesh(&sta->sdata->vif)) {
|
||||
mesh_plink_deactivate(sta);
|
||||
del_timer_sync(&sta->plink_timer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Destroy aggregation state here. It would be nice to wait for the
|
||||
* driver to finish aggregation stop and then clean up, but for now
|
||||
* drivers have to handle aggregation stop being requested, followed
|
||||
* directly by station destruction.
|
||||
*/
|
||||
for (i = 0; i < STA_TID_NUM; i++) {
|
||||
tid_tx = rcu_dereference_raw(sta->ampdu_mlme.tid_tx[i]);
|
||||
if (!tid_tx)
|
||||
continue;
|
||||
__skb_queue_purge(&tid_tx->pending);
|
||||
kfree(tid_tx);
|
||||
}
|
||||
|
||||
sta_info_free(local, sta);
|
||||
call_rcu(&sta->rcu_head, free_sta_rcu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -287,6 +287,7 @@ struct sta_ampdu_mlme {
|
|||
struct sta_info {
|
||||
/* General information, mostly static */
|
||||
struct list_head list;
|
||||
struct rcu_head rcu_head;
|
||||
struct sta_info __rcu *hnext;
|
||||
struct ieee80211_local *local;
|
||||
struct ieee80211_sub_if_data *sdata;
|
||||
|
@ -297,6 +298,7 @@ struct sta_info {
|
|||
spinlock_t lock;
|
||||
|
||||
struct work_struct drv_unblock_wk;
|
||||
struct work_struct free_sta_wk;
|
||||
|
||||
u16 listen_interval;
|
||||
|
||||
|
|
Loading…
Reference in a new issue