mac80211: sta_info_flush() fixes
authorJohannes Berg <johannes@sipsolutions.net>
Mon, 31 Mar 2008 17:23:03 +0000 (19:23 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 1 Apr 2008 21:14:10 +0000 (17:14 -0400)
When the IBSS code tries to flush the STA list, it does so in
an atomic context. Flushing isn't safe there, however, and
requires the RTNL, so we need to defer it to a workqueue.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/mac80211/ieee80211_i.h
net/mac80211/ieee80211_sta.c
net/mac80211/key.c
net/mac80211/sta_info.c
net/mac80211/sta_info.h

index 7ab8066021839a99350053ac490ac1817171e162..0997a0f96203bc770ee03eaf6ca0c90df773a915 100644 (file)
@@ -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;
 
index c5a47f8d873a08eb16dbc36bd7cc055ea7576883..75b96a754333a35bc639059c778ec6ef4f7913a5 100644 (file)
@@ -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. */
index f91fb4092652765aa2660695fe99081b85addbab..5df9e0cc009f2700f09d9ae63e80ece52f2bdbe0 100644 (file)
@@ -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,
index dfca96e05d69cbf7d79d7ad11850f229c2bbe3d4..f5c65e8912885ac1883f083dbd986bd20f418fbf 100644 (file)
@@ -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);
+}
index 5e39a4164b9b60f7b2ab990a80a6a11a1c9338c2..b09861eb124ee9108811e5015e1cb7fa8b9f1a04 100644 (file)
@@ -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 */