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
@ -793,11 +793,20 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
|
|||||||
flush_work(&sdata->work);
|
flush_work(&sdata->work);
|
||||||
/*
|
/*
|
||||||
* When we get here, the interface is marked down.
|
* 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
|
* 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);
|
skb_queue_purge(&sdata->skb_queue);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -91,6 +91,70 @@ static int sta_info_hash_del(struct ieee80211_local *local,
|
|||||||
return -ENOENT;
|
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 */
|
/* protected by RCU */
|
||||||
struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
|
struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
|
||||||
const u8 *addr)
|
const u8 *addr)
|
||||||
@ -241,6 +305,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
|
|||||||
|
|
||||||
spin_lock_init(&sta->lock);
|
spin_lock_init(&sta->lock);
|
||||||
INIT_WORK(&sta->drv_unblock_wk, sta_unblock);
|
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);
|
INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work);
|
||||||
mutex_init(&sta->ampdu_mlme.mtx);
|
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_local *local;
|
||||||
struct ieee80211_sub_if_data *sdata;
|
struct ieee80211_sub_if_data *sdata;
|
||||||
int ret, i, ac;
|
int ret, i;
|
||||||
struct tid_ampdu_tx *tid_tx;
|
|
||||||
|
|
||||||
might_sleep();
|
might_sleep();
|
||||||
|
|
||||||
@ -711,65 +775,14 @@ int __must_check __sta_info_destroy(struct sta_info *sta)
|
|||||||
WARN_ON_ONCE(ret != 0);
|
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);
|
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);
|
cfg80211_del_sta(sdata->dev, sta->sta.addr, GFP_KERNEL);
|
||||||
|
|
||||||
rate_control_remove_sta_debugfs(sta);
|
rate_control_remove_sta_debugfs(sta);
|
||||||
ieee80211_sta_debugfs_remove(sta);
|
ieee80211_sta_debugfs_remove(sta);
|
||||||
|
|
||||||
#ifdef CONFIG_MAC80211_MESH
|
call_rcu(&sta->rcu_head, free_sta_rcu);
|
||||||
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);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,7 @@ struct sta_ampdu_mlme {
|
|||||||
struct sta_info {
|
struct sta_info {
|
||||||
/* General information, mostly static */
|
/* General information, mostly static */
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
|
struct rcu_head rcu_head;
|
||||||
struct sta_info __rcu *hnext;
|
struct sta_info __rcu *hnext;
|
||||||
struct ieee80211_local *local;
|
struct ieee80211_local *local;
|
||||||
struct ieee80211_sub_if_data *sdata;
|
struct ieee80211_sub_if_data *sdata;
|
||||||
@ -297,6 +298,7 @@ struct sta_info {
|
|||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
struct work_struct drv_unblock_wk;
|
struct work_struct drv_unblock_wk;
|
||||||
|
struct work_struct free_sta_wk;
|
||||||
|
|
||||||
u16 listen_interval;
|
u16 listen_interval;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user