summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--net/mac80211/ieee80211_i.h2
-rw-r--r--net/mac80211/ieee80211_sta.c2
-rw-r--r--net/mac80211/key.c9
-rw-r--r--net/mac80211/sta_info.c70
-rw-r--r--net/mac80211/sta_info.h2
5 files changed, 84 insertions, 1 deletions
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 7ab806602183..0997a0f96203 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -606,6 +606,8 @@ struct ieee80211_local {
spinlock_t sta_lock;
unsigned long num_sta;
struct list_head sta_list;
+ struct list_head sta_flush_list;
+ struct work_struct sta_flush_work;
struct sta_info *sta_hash[STA_HASH_SIZE];
struct timer_list sta_cleanup;
diff --git a/net/mac80211/ieee80211_sta.c b/net/mac80211/ieee80211_sta.c
index c5a47f8d873a..75b96a754333 100644
--- a/net/mac80211/ieee80211_sta.c
+++ b/net/mac80211/ieee80211_sta.c
@@ -2254,7 +2254,7 @@ static int ieee80211_sta_join_ibss(struct net_device *dev,
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
/* Remove possible STA entries from other IBSS networks. */
- sta_info_flush(local, sdata);
+ sta_info_flush_delayed(sdata);
if (local->ops->reset_tsf) {
/* Reset own TSF to allow time synchronization work. */
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index f91fb4092652..5df9e0cc009f 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -73,6 +73,15 @@ static void ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
if (!key->local->ops->set_key)
return;
+ /*
+ * This makes sure that all pending flushes have
+ * actually completed prior to uploading new key
+ * material to the hardware. That is necessary to
+ * avoid races between flushing STAs and adding
+ * new keys for them.
+ */
+ __ieee80211_run_pending_flush(key->local);
+
addr = get_mac_for_key(key);
ret = key->local->ops->set_key(local_to_hw(key->local), SET_KEY,
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index dfca96e05d69..f5c65e891288 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -644,10 +644,41 @@ static void sta_info_debugfs_add_work(struct work_struct *work)
}
#endif
+void __ieee80211_run_pending_flush(struct ieee80211_local *local)
+{
+ struct sta_info *sta;
+ unsigned long flags;
+
+ ASSERT_RTNL();
+
+ spin_lock_irqsave(&local->sta_lock, flags);
+ while (!list_empty(&local->sta_flush_list)) {
+ sta = list_first_entry(&local->sta_flush_list,
+ struct sta_info, list);
+ list_del(&sta->list);
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+ sta_info_destroy(sta);
+ spin_lock_irqsave(&local->sta_lock, flags);
+ }
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+}
+
+static void ieee80211_sta_flush_work(struct work_struct *work)
+{
+ struct ieee80211_local *local =
+ container_of(work, struct ieee80211_local, sta_flush_work);
+
+ rtnl_lock();
+ __ieee80211_run_pending_flush(local);
+ rtnl_unlock();
+}
+
void sta_info_init(struct ieee80211_local *local)
{
spin_lock_init(&local->sta_lock);
INIT_LIST_HEAD(&local->sta_list);
+ INIT_LIST_HEAD(&local->sta_flush_list);
+ INIT_WORK(&local->sta_flush_work, ieee80211_sta_flush_work);
setup_timer(&local->sta_cleanup, sta_info_cleanup,
(unsigned long)local);
@@ -668,7 +699,12 @@ int sta_info_start(struct ieee80211_local *local)
void sta_info_stop(struct ieee80211_local *local)
{
del_timer(&local->sta_cleanup);
+ cancel_work_sync(&local->sta_flush_work);
+
+ rtnl_lock();
sta_info_flush(local, NULL);
+ __ieee80211_run_pending_flush(local);
+ rtnl_unlock();
}
/**
@@ -688,6 +724,7 @@ int sta_info_flush(struct ieee80211_local *local,
unsigned long flags;
might_sleep();
+ ASSERT_RTNL();
spin_lock_irqsave(&local->sta_lock, flags);
list_for_each_entry_safe(sta, tmp, &local->sta_list, list) {
@@ -706,3 +743,36 @@ int sta_info_flush(struct ieee80211_local *local,
return ret;
}
+
+/**
+ * sta_info_flush_delayed - flush matching STA entries from the STA table
+ *
+ * This function unlinks all stations for a given interface and queues
+ * them for freeing. Note that the workqueue function scheduled here has
+ * to run before any new keys can be added to the system to avoid set_key()
+ * callback ordering issues.
+ *
+ * @sdata: the interface
+ */
+void sta_info_flush_delayed(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta, *tmp;
+ unsigned long flags;
+ bool work = false;
+
+ spin_lock_irqsave(&local->sta_lock, flags);
+ list_for_each_entry_safe(sta, tmp, &local->sta_list, list) {
+ if (sdata == sta->sdata) {
+ __sta_info_unlink(&sta);
+ if (sta) {
+ list_add_tail(&sta->list,
+ &local->sta_flush_list);
+ work = true;
+ }
+ }
+ }
+ if (work)
+ schedule_work(&local->sta_flush_work);
+ spin_unlock_irqrestore(&local->sta_lock, flags);
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 5e39a4164b9b..b09861eb124e 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -357,5 +357,7 @@ int sta_info_start(struct ieee80211_local *local);
void sta_info_stop(struct ieee80211_local *local);
int sta_info_flush(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata);
+void sta_info_flush_delayed(struct ieee80211_sub_if_data *sdata);
+void __ieee80211_run_pending_flush(struct ieee80211_local *local);
#endif /* STA_INFO_H */