diff options
-rw-r--r-- | net/mac80211/ieee80211_i.h | 2 | ||||
-rw-r--r-- | net/mac80211/ieee80211_sta.c | 2 | ||||
-rw-r--r-- | net/mac80211/key.c | 9 | ||||
-rw-r--r-- | net/mac80211/sta_info.c | 70 | ||||
-rw-r--r-- | net/mac80211/sta_info.h | 2 |
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 */ |