Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wirel...
[pandora-kernel.git] / net / mac80211 / sta_info.c
index 3db78b6..6bc17fb 100644 (file)
@@ -72,7 +72,7 @@ static int sta_info_hash_del(struct ieee80211_local *local,
        if (!s)
                return -ENOENT;
        if (s == sta) {
-               rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)],
+               RCU_INIT_POINTER(local->sta_hash[STA_HASH(sta->sta.addr)],
                                   s->hnext);
                return 0;
        }
@@ -82,7 +82,7 @@ static int sta_info_hash_del(struct ieee80211_local *local,
                s = rcu_dereference_protected(s->hnext,
                                        lockdep_is_held(&local->sta_lock));
        if (rcu_access_pointer(s->hnext)) {
-               rcu_assign_pointer(s->hnext, sta->hnext);
+               RCU_INIT_POINTER(s->hnext, sta->hnext);
                return 0;
        }
 
@@ -96,6 +96,27 @@ struct sta_info *sta_info_get(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
 
+       sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
+                                   lockdep_is_held(&local->sta_lock) ||
+                                   lockdep_is_held(&local->sta_mtx));
+       while (sta) {
+               if (sta->sdata == sdata && !sta->dummy &&
+                   memcmp(sta->sta.addr, addr, ETH_ALEN) == 0)
+                       break;
+               sta = rcu_dereference_check(sta->hnext,
+                                           lockdep_is_held(&local->sta_lock) ||
+                                           lockdep_is_held(&local->sta_mtx));
+       }
+       return sta;
+}
+
+/* get a station info entry even if it is a dummy station*/
+struct sta_info *sta_info_get_rx(struct ieee80211_sub_if_data *sdata,
+                             const u8 *addr)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct sta_info *sta;
+
        sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
                                    lockdep_is_held(&local->sta_lock) ||
                                    lockdep_is_held(&local->sta_mtx));
@@ -120,6 +141,32 @@ struct sta_info *sta_info_get_bss(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_local *local = sdata->local;
        struct sta_info *sta;
 
+       sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
+                                   lockdep_is_held(&local->sta_lock) ||
+                                   lockdep_is_held(&local->sta_mtx));
+       while (sta) {
+               if ((sta->sdata == sdata ||
+                    (sta->sdata->bss && sta->sdata->bss == sdata->bss)) &&
+                   !sta->dummy &&
+                   memcmp(sta->sta.addr, addr, ETH_ALEN) == 0)
+                       break;
+               sta = rcu_dereference_check(sta->hnext,
+                                           lockdep_is_held(&local->sta_lock) ||
+                                           lockdep_is_held(&local->sta_mtx));
+       }
+       return sta;
+}
+
+/*
+ * Get sta info either from the specified interface
+ * or from one of its vlans (including dummy stations)
+ */
+struct sta_info *sta_info_get_bss_rx(struct ieee80211_sub_if_data *sdata,
+                                 const u8 *addr)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct sta_info *sta;
+
        sta = rcu_dereference_check(local->sta_hash[STA_HASH(addr)],
                                    lockdep_is_held(&local->sta_lock) ||
                                    lockdep_is_held(&local->sta_mtx));
@@ -184,7 +231,7 @@ static void sta_info_hash_add(struct ieee80211_local *local,
                              struct sta_info *sta)
 {
        sta->hnext = local->sta_hash[STA_HASH(sta->sta.addr)];
-       rcu_assign_pointer(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
+       RCU_INIT_POINTER(local->sta_hash[STA_HASH(sta->sta.addr)], sta);
 }
 
 static void sta_unblock(struct work_struct *wk)
@@ -280,7 +327,8 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
        return sta;
 }
 
-static int sta_info_finish_insert(struct sta_info *sta, bool async)
+static int sta_info_finish_insert(struct sta_info *sta,
+                               bool async, bool dummy_reinsert)
 {
        struct ieee80211_local *local = sta->local;
        struct ieee80211_sub_if_data *sdata = sta->sdata;
@@ -290,50 +338,58 @@ static int sta_info_finish_insert(struct sta_info *sta, bool async)
 
        lockdep_assert_held(&local->sta_mtx);
 
-       /* notify driver */
-       if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
-               sdata = container_of(sdata->bss,
-                                    struct ieee80211_sub_if_data,
-                                    u.ap);
-       err = drv_sta_add(local, sdata, &sta->sta);
-       if (err) {
-               if (!async)
-                       return err;
-               printk(KERN_DEBUG "%s: failed to add IBSS STA %pM to driver (%d)"
-                                 " - keeping it anyway.\n",
-                      sdata->name, sta->sta.addr, err);
-       } else {
-               sta->uploaded = true;
+       if (!sta->dummy || dummy_reinsert) {
+               /* notify driver */
+               if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+                       sdata = container_of(sdata->bss,
+                                            struct ieee80211_sub_if_data,
+                                            u.ap);
+               err = drv_sta_add(local, sdata, &sta->sta);
+               if (err) {
+                       if (!async)
+                               return err;
+                       printk(KERN_DEBUG "%s: failed to add IBSS STA %pM to "
+                                         "driver (%d) - keeping it anyway.\n",
+                              sdata->name, sta->sta.addr, err);
+               } else {
+                       sta->uploaded = true;
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
-               if (async)
-                       wiphy_debug(local->hw.wiphy,
-                                   "Finished adding IBSS STA %pM\n",
-                                   sta->sta.addr);
+                       if (async)
+                               wiphy_debug(local->hw.wiphy,
+                                           "Finished adding IBSS STA %pM\n",
+                                           sta->sta.addr);
 #endif
+               }
+
+               sdata = sta->sdata;
        }
 
-       sdata = sta->sdata;
+       if (!dummy_reinsert) {
+               if (!async) {
+                       local->num_sta++;
+                       local->sta_generation++;
+                       smp_mb();
 
-       if (!async) {
-               local->num_sta++;
-               local->sta_generation++;
-               smp_mb();
+                       /* make the station visible */
+                       spin_lock_irqsave(&local->sta_lock, flags);
+                       sta_info_hash_add(local, sta);
+                       spin_unlock_irqrestore(&local->sta_lock, flags);
+               }
 
-               /* make the station visible */
-               spin_lock_irqsave(&local->sta_lock, flags);
-               sta_info_hash_add(local, sta);
-               spin_unlock_irqrestore(&local->sta_lock, flags);
+               list_add(&sta->list, &local->sta_list);
+       } else {
+               sta->dummy = false;
        }
 
-       list_add(&sta->list, &local->sta_list);
-
-       ieee80211_sta_debugfs_add(sta);
-       rate_control_add_sta_debugfs(sta);
-
-       sinfo.filled = 0;
-       sinfo.generation = local->sta_generation;
-       cfg80211_new_sta(sdata->dev, sta->sta.addr, &sinfo, GFP_KERNEL);
+       if (!sta->dummy) {
+               ieee80211_sta_debugfs_add(sta);
+               rate_control_add_sta_debugfs(sta);
 
+               memset(&sinfo, 0, sizeof(sinfo));
+               sinfo.filled = 0;
+               sinfo.generation = local->sta_generation;
+               cfg80211_new_sta(sdata->dev, sta->sta.addr, &sinfo, GFP_KERNEL);
+       }
 
        return 0;
 }
@@ -350,7 +406,7 @@ static void sta_info_finish_pending(struct ieee80211_local *local)
                list_del(&sta->list);
                spin_unlock_irqrestore(&local->sta_lock, flags);
 
-               sta_info_finish_insert(sta, true);
+               sta_info_finish_insert(sta, true, false);
 
                spin_lock_irqsave(&local->sta_lock, flags);
        }
@@ -367,106 +423,117 @@ static void sta_info_finish_work(struct work_struct *work)
        mutex_unlock(&local->sta_mtx);
 }
 
-int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU)
+static int sta_info_insert_check(struct sta_info *sta)
 {
-       struct ieee80211_local *local = sta->local;
        struct ieee80211_sub_if_data *sdata = sta->sdata;
-       unsigned long flags;
-       int err = 0;
 
        /*
         * Can't be a WARN_ON because it can be triggered through a race:
         * something inserts a STA (on one CPU) without holding the RTNL
         * and another CPU turns off the net device.
         */
-       if (unlikely(!ieee80211_sdata_running(sdata))) {
-               err = -ENETDOWN;
-               rcu_read_lock();
-               goto out_free;
-       }
+       if (unlikely(!ieee80211_sdata_running(sdata)))
+               return -ENETDOWN;
 
        if (WARN_ON(compare_ether_addr(sta->sta.addr, sdata->vif.addr) == 0 ||
-                   is_multicast_ether_addr(sta->sta.addr))) {
-               err = -EINVAL;
+                   is_multicast_ether_addr(sta->sta.addr)))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int sta_info_insert_ibss(struct sta_info *sta) __acquires(RCU)
+{
+       struct ieee80211_local *local = sta->local;
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       unsigned long flags;
+
+       spin_lock_irqsave(&local->sta_lock, flags);
+       /* check if STA exists already */
+       if (sta_info_get_bss_rx(sdata, sta->sta.addr)) {
+               spin_unlock_irqrestore(&local->sta_lock, flags);
                rcu_read_lock();
-               goto out_free;
+               return -EEXIST;
        }
 
-       /*
-        * In ad-hoc mode, we sometimes need to insert stations
-        * from tasklet context from the RX path. To avoid races,
-        * always do so in that case -- see the comment below.
-        */
-       if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
-               spin_lock_irqsave(&local->sta_lock, flags);
-               /* check if STA exists already */
-               if (sta_info_get_bss(sdata, sta->sta.addr)) {
-                       spin_unlock_irqrestore(&local->sta_lock, flags);
-                       rcu_read_lock();
-                       err = -EEXIST;
-                       goto out_free;
-               }
-
-               local->num_sta++;
-               local->sta_generation++;
-               smp_mb();
-               sta_info_hash_add(local, sta);
+       local->num_sta++;
+       local->sta_generation++;
+       smp_mb();
+       sta_info_hash_add(local, sta);
 
-               list_add_tail(&sta->list, &local->sta_pending_list);
+       list_add_tail(&sta->list, &local->sta_pending_list);
 
-               rcu_read_lock();
-               spin_unlock_irqrestore(&local->sta_lock, flags);
+       rcu_read_lock();
+       spin_unlock_irqrestore(&local->sta_lock, flags);
 
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
-               wiphy_debug(local->hw.wiphy, "Added IBSS STA %pM\n",
-                           sta->sta.addr);
+       wiphy_debug(local->hw.wiphy, "Added IBSS STA %pM\n",
+                       sta->sta.addr);
 #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
 
-               ieee80211_queue_work(&local->hw, &local->sta_finish_work);
+       ieee80211_queue_work(&local->hw, &local->sta_finish_work);
 
-               return 0;
-       }
+       return 0;
+}
+
+/*
+ * should be called with sta_mtx locked
+ * this function replaces the mutex lock
+ * with a RCU lock
+ */
+static int sta_info_insert_non_ibss(struct sta_info *sta) __acquires(RCU)
+{
+       struct ieee80211_local *local = sta->local;
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       unsigned long flags;
+       struct sta_info *exist_sta;
+       bool dummy_reinsert = false;
+       int err = 0;
+
+       lockdep_assert_held(&local->sta_mtx);
 
        /*
         * On first glance, this will look racy, because the code
-        * below this point, which inserts a station with sleeping,
+        * in this function, which inserts a station with sleeping,
         * unlocks the sta_lock between checking existence in the
         * hash table and inserting into it.
         *
         * However, it is not racy against itself because it keeps
-        * the mutex locked. It still seems to race against the
-        * above code that atomically inserts the station... That,
-        * however, is not true because the above code can only
-        * be invoked for IBSS interfaces, and the below code will
-        * not be -- and the two do not race against each other as
-        * the hash table also keys off the interface.
+        * the mutex locked.
         */
 
-       might_sleep();
-
-       mutex_lock(&local->sta_mtx);
-
        spin_lock_irqsave(&local->sta_lock, flags);
-       /* check if STA exists already */
-       if (sta_info_get_bss(sdata, sta->sta.addr)) {
-               spin_unlock_irqrestore(&local->sta_lock, flags);
-               mutex_unlock(&local->sta_mtx);
-               rcu_read_lock();
-               err = -EEXIST;
-               goto out_free;
+       /*
+        * check if STA exists already.
+        * only accept a scenario of a second call to sta_info_insert_non_ibss
+        * with a dummy station entry that was inserted earlier
+        * in that case - assume that the dummy station flag should
+        * be removed.
+        */
+       exist_sta = sta_info_get_bss_rx(sdata, sta->sta.addr);
+       if (exist_sta) {
+               if (exist_sta == sta && sta->dummy) {
+                       dummy_reinsert = true;
+               } else {
+                       spin_unlock_irqrestore(&local->sta_lock, flags);
+                       mutex_unlock(&local->sta_mtx);
+                       rcu_read_lock();
+                       return -EEXIST;
+               }
        }
 
        spin_unlock_irqrestore(&local->sta_lock, flags);
 
-       err = sta_info_finish_insert(sta, false);
+       err = sta_info_finish_insert(sta, false, dummy_reinsert);
        if (err) {
                mutex_unlock(&local->sta_mtx);
                rcu_read_lock();
-               goto out_free;
+               return err;
        }
 
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
-       wiphy_debug(local->hw.wiphy, "Inserted STA %pM\n", sta->sta.addr);
+       wiphy_debug(local->hw.wiphy, "Inserted %sSTA %pM\n",
+                       sta->dummy ? "dummy " : "", sta->sta.addr);
 #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
 
        /* move reference to rcu-protected */
@@ -476,6 +543,51 @@ int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU)
        if (ieee80211_vif_is_mesh(&sdata->vif))
                mesh_accept_plinks_update(sdata);
 
+       return 0;
+}
+
+int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU)
+{
+       struct ieee80211_local *local = sta->local;
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       int err = 0;
+
+       err = sta_info_insert_check(sta);
+       if (err) {
+               rcu_read_lock();
+               goto out_free;
+       }
+
+       /*
+        * In ad-hoc mode, we sometimes need to insert stations
+        * from tasklet context from the RX path. To avoid races,
+        * always do so in that case -- see the comment below.
+        */
+       if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
+               err = sta_info_insert_ibss(sta);
+               if (err)
+                       goto out_free;
+
+               return 0;
+       }
+
+       /*
+        * It might seem that the function called below is in race against
+        * the function call above that atomically inserts the station... That,
+        * however, is not true because the above code can only
+        * be invoked for IBSS interfaces, and the below code will
+        * not be -- and the two do not race against each other as
+        * the hash table also keys off the interface.
+        */
+
+       might_sleep();
+
+       mutex_lock(&local->sta_mtx);
+
+       err = sta_info_insert_non_ibss(sta);
+       if (err)
+               goto out_free;
+
        return 0;
  out_free:
        BUG_ON(!err);
@@ -492,6 +604,25 @@ int sta_info_insert(struct sta_info *sta)
        return err;
 }
 
+/* Caller must hold sta->local->sta_mtx */
+int sta_info_reinsert(struct sta_info *sta)
+{
+       struct ieee80211_local *local = sta->local;
+       int err = 0;
+
+       err = sta_info_insert_check(sta);
+       if (err) {
+               mutex_unlock(&local->sta_mtx);
+               return err;
+       }
+
+       might_sleep();
+
+       err = sta_info_insert_non_ibss(sta);
+       rcu_read_unlock();
+       return err;
+}
+
 static inline void __bss_tim_set(struct ieee80211_if_ap *bss, u16 aid)
 {
        /*
@@ -672,7 +803,7 @@ static int __must_check __sta_info_destroy(struct sta_info *sta)
        local->sta_generation++;
 
        if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
-               rcu_assign_pointer(sdata->u.vlan.sta, NULL);
+               RCU_INIT_POINTER(sdata->u.vlan.sta, NULL);
 
        if (sta->uploaded) {
                if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
@@ -732,7 +863,7 @@ int sta_info_destroy_addr(struct ieee80211_sub_if_data *sdata, const u8 *addr)
        int ret;
 
        mutex_lock(&sdata->local->sta_mtx);
-       sta = sta_info_get(sdata, addr);
+       sta = sta_info_get_rx(sdata, addr);
        ret = __sta_info_destroy(sta);
        mutex_unlock(&sdata->local->sta_mtx);
 
@@ -746,7 +877,7 @@ int sta_info_destroy_addr_bss(struct ieee80211_sub_if_data *sdata,
        int ret;
 
        mutex_lock(&sdata->local->sta_mtx);
-       sta = sta_info_get_bss(sdata, addr);
+       sta = sta_info_get_bss_rx(sdata, addr);
        ret = __sta_info_destroy(sta);
        mutex_unlock(&sdata->local->sta_mtx);