diff options
author | Eliad Peller <eliad@wizery.com> | 2012-09-09 07:43:51 -0400 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2012-09-10 06:44:17 -0400 |
commit | b22cfcfcae5b2c1e9b43543b6a23e5ef517de8f8 (patch) | |
tree | b87ec8f76a6ccdd4d8d5ed9752d4bfedf3ea04c3 /net | |
parent | e548c49e6dc6b08b59042930a2e90c69c13c9293 (diff) |
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>
Diffstat (limited to 'net')
-rw-r--r-- | net/mac80211/iface.c | 15 | ||||
-rw-r--r-- | net/mac80211/sta_info.c | 121 | ||||
-rw-r--r-- | net/mac80211/sta_info.h | 2 |
3 files changed, 81 insertions, 57 deletions
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index d747da541747..6f8a73c64fb3 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c | |||
@@ -793,11 +793,20 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, | |||
793 | flush_work(&sdata->work); | 793 | flush_work(&sdata->work); |
794 | /* | 794 | /* |
795 | * When we get here, the interface is marked down. | 795 | * When we get here, the interface is marked down. |
796 | * Call synchronize_rcu() to wait for the RX path | 796 | * Call rcu_barrier() to wait both for the RX path |
797 | * should it be using the interface and enqueuing | 797 | * should it be using the interface and enqueuing |
798 | * frames at this very time on another CPU. | 798 | * frames at this very time on another CPU, and |
799 | * for the sta free call_rcu callbacks. | ||
799 | */ | 800 | */ |
800 | synchronize_rcu(); | 801 | rcu_barrier(); |
802 | |||
803 | /* | ||
804 | * free_sta_rcu() enqueues a work for the actual | ||
805 | * sta cleanup, so we need to flush it while | ||
806 | * sdata is still valid. | ||
807 | */ | ||
808 | flush_workqueue(local->workqueue); | ||
809 | |||
801 | skb_queue_purge(&sdata->skb_queue); | 810 | skb_queue_purge(&sdata->skb_queue); |
802 | 811 | ||
803 | /* | 812 | /* |
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 06fa75ceb025..9c8cd8b8f753 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c | |||
@@ -91,6 +91,70 @@ static int sta_info_hash_del(struct ieee80211_local *local, | |||
91 | return -ENOENT; | 91 | return -ENOENT; |
92 | } | 92 | } |
93 | 93 | ||
94 | static void free_sta_work(struct work_struct *wk) | ||
95 | { | ||
96 | struct sta_info *sta = container_of(wk, struct sta_info, free_sta_wk); | ||
97 | int ac, i; | ||
98 | struct tid_ampdu_tx *tid_tx; | ||
99 | struct ieee80211_sub_if_data *sdata = sta->sdata; | ||
100 | struct ieee80211_local *local = sdata->local; | ||
101 | |||
102 | /* | ||
103 | * At this point, when being called as call_rcu callback, | ||
104 | * neither mac80211 nor the driver can reference this | ||
105 | * sta struct any more except by still existing timers | ||
106 | * associated with this station that we clean up below. | ||
107 | */ | ||
108 | |||
109 | if (test_sta_flag(sta, WLAN_STA_PS_STA)) { | ||
110 | BUG_ON(!sdata->bss); | ||
111 | |||
112 | clear_sta_flag(sta, WLAN_STA_PS_STA); | ||
113 | |||
114 | atomic_dec(&sdata->bss->num_sta_ps); | ||
115 | sta_info_recalc_tim(sta); | ||
116 | } | ||
117 | |||
118 | for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { | ||
119 | local->total_ps_buffered -= skb_queue_len(&sta->ps_tx_buf[ac]); | ||
120 | __skb_queue_purge(&sta->ps_tx_buf[ac]); | ||
121 | __skb_queue_purge(&sta->tx_filtered[ac]); | ||
122 | } | ||
123 | |||
124 | #ifdef CONFIG_MAC80211_MESH | ||
125 | if (ieee80211_vif_is_mesh(&sdata->vif)) { | ||
126 | mesh_accept_plinks_update(sdata); | ||
127 | mesh_plink_deactivate(sta); | ||
128 | del_timer_sync(&sta->plink_timer); | ||
129 | } | ||
130 | #endif | ||
131 | |||
132 | cancel_work_sync(&sta->drv_unblock_wk); | ||
133 | |||
134 | /* | ||
135 | * Destroy aggregation state here. It would be nice to wait for the | ||
136 | * driver to finish aggregation stop and then clean up, but for now | ||
137 | * drivers have to handle aggregation stop being requested, followed | ||
138 | * directly by station destruction. | ||
139 | */ | ||
140 | for (i = 0; i < STA_TID_NUM; i++) { | ||
141 | tid_tx = rcu_dereference_raw(sta->ampdu_mlme.tid_tx[i]); | ||
142 | if (!tid_tx) | ||
143 | continue; | ||
144 | __skb_queue_purge(&tid_tx->pending); | ||
145 | kfree(tid_tx); | ||
146 | } | ||
147 | |||
148 | sta_info_free(local, sta); | ||
149 | } | ||
150 | |||
151 | static void free_sta_rcu(struct rcu_head *h) | ||
152 | { | ||
153 | struct sta_info *sta = container_of(h, struct sta_info, rcu_head); | ||
154 | |||
155 | ieee80211_queue_work(&sta->local->hw, &sta->free_sta_wk); | ||
156 | } | ||
157 | |||
94 | /* protected by RCU */ | 158 | /* protected by RCU */ |
95 | struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata, | 159 | struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata, |
96 | const u8 *addr) | 160 | const u8 *addr) |
@@ -241,6 +305,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, | |||
241 | 305 | ||
242 | spin_lock_init(&sta->lock); | 306 | spin_lock_init(&sta->lock); |
243 | INIT_WORK(&sta->drv_unblock_wk, sta_unblock); | 307 | INIT_WORK(&sta->drv_unblock_wk, sta_unblock); |
308 | INIT_WORK(&sta->free_sta_wk, free_sta_work); | ||
244 | INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work); | 309 | INIT_WORK(&sta->ampdu_mlme.work, ieee80211_ba_session_work); |
245 | mutex_init(&sta->ampdu_mlme.mtx); | 310 | mutex_init(&sta->ampdu_mlme.mtx); |
246 | 311 | ||
@@ -654,8 +719,7 @@ int __must_check __sta_info_destroy(struct sta_info *sta) | |||
654 | { | 719 | { |
655 | struct ieee80211_local *local; | 720 | struct ieee80211_local *local; |
656 | struct ieee80211_sub_if_data *sdata; | 721 | struct ieee80211_sub_if_data *sdata; |
657 | int ret, i, ac; | 722 | int ret, i; |
658 | struct tid_ampdu_tx *tid_tx; | ||
659 | 723 | ||
660 | might_sleep(); | 724 | might_sleep(); |
661 | 725 | ||
@@ -711,65 +775,14 @@ int __must_check __sta_info_destroy(struct sta_info *sta) | |||
711 | WARN_ON_ONCE(ret != 0); | 775 | WARN_ON_ONCE(ret != 0); |
712 | } | 776 | } |
713 | 777 | ||
714 | /* | ||
715 | * At this point, after we wait for an RCU grace period, | ||
716 | * neither mac80211 nor the driver can reference this | ||
717 | * sta struct any more except by still existing timers | ||
718 | * associated with this station that we clean up below. | ||
719 | */ | ||
720 | synchronize_rcu(); | ||
721 | |||
722 | if (test_sta_flag(sta, WLAN_STA_PS_STA)) { | ||
723 | BUG_ON(!sdata->bss); | ||
724 | |||
725 | clear_sta_flag(sta, WLAN_STA_PS_STA); | ||
726 | |||
727 | atomic_dec(&sdata->bss->num_sta_ps); | ||
728 | sta_info_recalc_tim(sta); | ||
729 | } | ||
730 | |||
731 | for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { | ||
732 | local->total_ps_buffered -= skb_queue_len(&sta->ps_tx_buf[ac]); | ||
733 | __skb_queue_purge(&sta->ps_tx_buf[ac]); | ||
734 | __skb_queue_purge(&sta->tx_filtered[ac]); | ||
735 | } | ||
736 | |||
737 | #ifdef CONFIG_MAC80211_MESH | ||
738 | if (ieee80211_vif_is_mesh(&sdata->vif)) | ||
739 | mesh_accept_plinks_update(sdata); | ||
740 | #endif | ||
741 | |||
742 | sta_dbg(sdata, "Removed STA %pM\n", sta->sta.addr); | 778 | sta_dbg(sdata, "Removed STA %pM\n", sta->sta.addr); |
743 | 779 | ||
744 | cancel_work_sync(&sta->drv_unblock_wk); | ||
745 | |||
746 | cfg80211_del_sta(sdata->dev, sta->sta.addr, GFP_KERNEL); | 780 | cfg80211_del_sta(sdata->dev, sta->sta.addr, GFP_KERNEL); |
747 | 781 | ||
748 | rate_control_remove_sta_debugfs(sta); | 782 | rate_control_remove_sta_debugfs(sta); |
749 | ieee80211_sta_debugfs_remove(sta); | 783 | ieee80211_sta_debugfs_remove(sta); |
750 | 784 | ||
751 | #ifdef CONFIG_MAC80211_MESH | 785 | call_rcu(&sta->rcu_head, free_sta_rcu); |
752 | if (ieee80211_vif_is_mesh(&sta->sdata->vif)) { | ||
753 | mesh_plink_deactivate(sta); | ||
754 | del_timer_sync(&sta->plink_timer); | ||
755 | } | ||
756 | #endif | ||
757 | |||
758 | /* | ||
759 | * Destroy aggregation state here. It would be nice to wait for the | ||
760 | * driver to finish aggregation stop and then clean up, but for now | ||
761 | * drivers have to handle aggregation stop being requested, followed | ||
762 | * directly by station destruction. | ||
763 | */ | ||
764 | for (i = 0; i < STA_TID_NUM; i++) { | ||
765 | tid_tx = rcu_dereference_raw(sta->ampdu_mlme.tid_tx[i]); | ||
766 | if (!tid_tx) | ||
767 | continue; | ||
768 | __skb_queue_purge(&tid_tx->pending); | ||
769 | kfree(tid_tx); | ||
770 | } | ||
771 | |||
772 | sta_info_free(local, sta); | ||
773 | 786 | ||
774 | return 0; | 787 | return 0; |
775 | } | 788 | } |
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index a470e1123a55..c88f161f8118 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h | |||
@@ -287,6 +287,7 @@ struct sta_ampdu_mlme { | |||
287 | struct sta_info { | 287 | struct sta_info { |
288 | /* General information, mostly static */ | 288 | /* General information, mostly static */ |
289 | struct list_head list; | 289 | struct list_head list; |
290 | struct rcu_head rcu_head; | ||
290 | struct sta_info __rcu *hnext; | 291 | struct sta_info __rcu *hnext; |
291 | struct ieee80211_local *local; | 292 | struct ieee80211_local *local; |
292 | struct ieee80211_sub_if_data *sdata; | 293 | struct ieee80211_sub_if_data *sdata; |
@@ -297,6 +298,7 @@ struct sta_info { | |||
297 | spinlock_t lock; | 298 | spinlock_t lock; |
298 | 299 | ||
299 | struct work_struct drv_unblock_wk; | 300 | struct work_struct drv_unblock_wk; |
301 | struct work_struct free_sta_wk; | ||
300 | 302 | ||
301 | u16 listen_interval; | 303 | u16 listen_interval; |
302 | 304 | ||