cfg80211: refactor hidden SSID finding
[deliverable/linux.git] / net / wireless / scan.c
index 9730c9862bdcfd624af15641deefe95c0b12c8de..cfc4e1aa8dad706be6a6fad031b5082a9c054e4c 100644 (file)
 #include "core.h"
 #include "nl80211.h"
 #include "wext-compat.h"
+#include "rdev-ops.h"
 
 #define IEEE80211_SCAN_RESULT_EXPIRE   (30 * HZ)
 
+static void bss_release(struct kref *ref)
+{
+       struct cfg80211_bss_ies *ies;
+       struct cfg80211_internal_bss *bss;
+
+       bss = container_of(ref, struct cfg80211_internal_bss, ref);
+
+       if (WARN_ON(atomic_read(&bss->hold)))
+               return;
+
+       ies = (void *)rcu_access_pointer(bss->pub.beacon_ies);
+       if (ies)
+               kfree_rcu(ies, rcu_head);
+       ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies);
+       if (ies)
+               kfree_rcu(ies, rcu_head);
+
+       kfree(bss);
+}
+
+/* must hold dev->bss_lock! */
+static void __cfg80211_unlink_bss(struct cfg80211_registered_device *dev,
+                                 struct cfg80211_internal_bss *bss)
+{
+       list_del_init(&bss->list);
+       rb_erase(&bss->rbn, &dev->bss_tree);
+       kref_put(&bss->ref, bss_release);
+}
+
+/* must hold dev->bss_lock! */
+static void __cfg80211_bss_expire(struct cfg80211_registered_device *dev,
+                                 unsigned long expire_time)
+{
+       struct cfg80211_internal_bss *bss, *tmp;
+       bool expired = false;
+
+       list_for_each_entry_safe(bss, tmp, &dev->bss_list, list) {
+               if (atomic_read(&bss->hold))
+                       continue;
+               if (!time_after(expire_time, bss->ts))
+                       continue;
+
+               __cfg80211_unlink_bss(dev, bss);
+               expired = true;
+       }
+
+       if (expired)
+               dev->bss_generation++;
+}
+
 void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak)
 {
        struct cfg80211_scan_request *request;
@@ -45,10 +96,17 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak)
        if (wdev->netdev)
                cfg80211_sme_scan_done(wdev->netdev);
 
-       if (request->aborted)
+       if (request->aborted) {
                nl80211_send_scan_aborted(rdev, wdev);
-       else
+       } else {
+               if (request->flags & NL80211_SCAN_FLAG_FLUSH) {
+                       /* flush entries from previous scans */
+                       spin_lock_bh(&rdev->bss_lock);
+                       __cfg80211_bss_expire(rdev, request->scan_start);
+                       spin_unlock_bh(&rdev->bss_lock);
+               }
                nl80211_send_scan_done(rdev, wdev);
+       }
 
 #ifdef CONFIG_CFG80211_WEXT
        if (wdev->netdev && !request->aborted) {
@@ -89,6 +147,7 @@ void __cfg80211_scan_done(struct work_struct *wk)
 
 void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
 {
+       trace_cfg80211_scan_done(request, aborted);
        WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
 
        request->aborted = aborted;
@@ -99,22 +158,34 @@ EXPORT_SYMBOL(cfg80211_scan_done);
 void __cfg80211_sched_scan_results(struct work_struct *wk)
 {
        struct cfg80211_registered_device *rdev;
+       struct cfg80211_sched_scan_request *request;
 
        rdev = container_of(wk, struct cfg80211_registered_device,
                            sched_scan_results_wk);
 
+       request = rdev->sched_scan_req;
+
        mutex_lock(&rdev->sched_scan_mtx);
 
        /* we don't have sched_scan_req anymore if the scan is stopping */
-       if (rdev->sched_scan_req)
-               nl80211_send_sched_scan_results(rdev,
-                                               rdev->sched_scan_req->dev);
+       if (request) {
+               if (request->flags & NL80211_SCAN_FLAG_FLUSH) {
+                       /* flush entries from previous scans */
+                       spin_lock_bh(&rdev->bss_lock);
+                       __cfg80211_bss_expire(rdev, request->scan_start);
+                       spin_unlock_bh(&rdev->bss_lock);
+                       request->scan_start =
+                               jiffies + msecs_to_jiffies(request->interval);
+               }
+               nl80211_send_sched_scan_results(rdev, request->dev);
+       }
 
        mutex_unlock(&rdev->sched_scan_mtx);
 }
 
 void cfg80211_sched_scan_results(struct wiphy *wiphy)
 {
+       trace_cfg80211_sched_scan_results(wiphy);
        /* ignore if we're not scanning */
        if (wiphy_to_dev(wiphy)->sched_scan_req)
                queue_work(cfg80211_wq,
@@ -126,6 +197,8 @@ void cfg80211_sched_scan_stopped(struct wiphy *wiphy)
 {
        struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
 
+       trace_cfg80211_sched_scan_stopped(wiphy);
+
        mutex_lock(&rdev->sched_scan_mtx);
        __cfg80211_stop_sched_scan(rdev, true);
        mutex_unlock(&rdev->sched_scan_mtx);
@@ -145,7 +218,7 @@ int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
        dev = rdev->sched_scan_req->dev;
 
        if (!driver_initiated) {
-               int err = rdev->ops->sched_scan_stop(&rdev->wiphy, dev);
+               int err = rdev_sched_scan_stop(rdev, dev);
                if (err)
                        return err;
        }
@@ -158,24 +231,6 @@ int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev,
        return 0;
 }
 
-static void bss_release(struct kref *ref)
-{
-       struct cfg80211_internal_bss *bss;
-
-       bss = container_of(ref, struct cfg80211_internal_bss, ref);
-       if (bss->pub.free_priv)
-               bss->pub.free_priv(&bss->pub);
-
-       if (bss->beacon_ies_allocated)
-               kfree(bss->pub.beacon_ies);
-       if (bss->proberesp_ies_allocated)
-               kfree(bss->pub.proberesp_ies);
-
-       BUG_ON(atomic_read(&bss->hold));
-
-       kfree(bss);
-}
-
 /* must hold dev->bss_lock! */
 void cfg80211_bss_age(struct cfg80211_registered_device *dev,
                       unsigned long age_secs)
@@ -183,37 +238,13 @@ void cfg80211_bss_age(struct cfg80211_registered_device *dev,
        struct cfg80211_internal_bss *bss;
        unsigned long age_jiffies = msecs_to_jiffies(age_secs * MSEC_PER_SEC);
 
-       list_for_each_entry(bss, &dev->bss_list, list) {
+       list_for_each_entry(bss, &dev->bss_list, list)
                bss->ts -= age_jiffies;
-       }
 }
 
-/* must hold dev->bss_lock! */
-static void __cfg80211_unlink_bss(struct cfg80211_registered_device *dev,
-                                 struct cfg80211_internal_bss *bss)
-{
-       list_del_init(&bss->list);
-       rb_erase(&bss->rbn, &dev->bss_tree);
-       kref_put(&bss->ref, bss_release);
-}
-
-/* must hold dev->bss_lock! */
 void cfg80211_bss_expire(struct cfg80211_registered_device *dev)
 {
-       struct cfg80211_internal_bss *bss, *tmp;
-       bool expired = false;
-
-       list_for_each_entry_safe(bss, tmp, &dev->bss_list, list) {
-               if (atomic_read(&bss->hold))
-                       continue;
-               if (!time_after(jiffies, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE))
-                       continue;
-               __cfg80211_unlink_bss(dev, bss);
-               expired = true;
-       }
-
-       if (expired)
-               dev->bss_generation++;
+       __cfg80211_bss_expire(dev, jiffies - IEEE80211_SCAN_RESULT_EXPIRE);
 }
 
 const u8 *cfg80211_find_ie(u8 eid, const u8 *ies, int len)
@@ -257,7 +288,7 @@ const u8 *cfg80211_find_vendor_ie(unsigned int oui, u8 oui_type,
 }
 EXPORT_SYMBOL(cfg80211_find_vendor_ie);
 
-static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2)
+static int cmp_ies(u8 num, const u8 *ies1, int len1, const u8 *ies2, int len2)
 {
        const u8 *ie1 = cfg80211_find_ie(num, ies1, len1);
        const u8 *ie2 = cfg80211_find_ie(num, ies2, len2);
@@ -277,10 +308,10 @@ static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2)
        return memcmp(ie1 + 2, ie2 + 2, ie1[1]);
 }
 
-static bool is_bss(struct cfg80211_bss *a,
-                  const u8 *bssid,
+static bool is_bss(struct cfg80211_bss *a, const u8 *bssid,
                   const u8 *ssid, size_t ssid_len)
 {
+       const struct cfg80211_bss_ies *ies;
        const u8 *ssidie;
 
        if (bssid && !ether_addr_equal(a->bssid, bssid))
@@ -289,9 +320,10 @@ static bool is_bss(struct cfg80211_bss *a,
        if (!ssid)
                return true;
 
-       ssidie = cfg80211_find_ie(WLAN_EID_SSID,
-                                 a->information_elements,
-                                 a->len_information_elements);
+       ies = rcu_access_pointer(a->ies);
+       if (!ies)
+               return false;
+       ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len);
        if (!ssidie)
                return false;
        if (ssidie[1] != ssid_len)
@@ -301,20 +333,21 @@ static bool is_bss(struct cfg80211_bss *a,
 
 static bool is_mesh_bss(struct cfg80211_bss *a)
 {
+       const struct cfg80211_bss_ies *ies;
        const u8 *ie;
 
        if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability))
                return false;
 
-       ie = cfg80211_find_ie(WLAN_EID_MESH_ID,
-                             a->information_elements,
-                             a->len_information_elements);
+       ies = rcu_access_pointer(a->ies);
+       if (!ies)
+               return false;
+
+       ie = cfg80211_find_ie(WLAN_EID_MESH_ID, ies->data, ies->len);
        if (!ie)
                return false;
 
-       ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,
-                             a->information_elements,
-                             a->len_information_elements);
+       ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, ies->data, ies->len);
        if (!ie)
                return false;
 
@@ -325,14 +358,17 @@ static bool is_mesh(struct cfg80211_bss *a,
                    const u8 *meshid, size_t meshidlen,
                    const u8 *meshcfg)
 {
+       const struct cfg80211_bss_ies *ies;
        const u8 *ie;
 
        if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability))
                return false;
 
-       ie = cfg80211_find_ie(WLAN_EID_MESH_ID,
-                             a->information_elements,
-                             a->len_information_elements);
+       ies = rcu_access_pointer(a->ies);
+       if (!ies)
+               return false;
+
+       ie = cfg80211_find_ie(WLAN_EID_MESH_ID, ies->data, ies->len);
        if (!ie)
                return false;
        if (ie[1] != meshidlen)
@@ -340,9 +376,7 @@ static bool is_mesh(struct cfg80211_bss *a,
        if (memcmp(ie + 2, meshid, meshidlen))
                return false;
 
-       ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,
-                             a->information_elements,
-                             a->len_information_elements);
+       ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, ies->data, ies->len);
        if (!ie)
                return false;
        if (ie[1] != sizeof(struct ieee80211_meshconf_ie))
@@ -354,30 +388,33 @@ static bool is_mesh(struct cfg80211_bss *a,
         * part in the same mesh.
         */
        return memcmp(ie + 2, meshcfg,
-           sizeof(struct ieee80211_meshconf_ie) - 2) == 0;
+                     sizeof(struct ieee80211_meshconf_ie) - 2) == 0;
 }
 
-static int cmp_bss_core(struct cfg80211_bss *a,
-                       struct cfg80211_bss *b)
+static int cmp_bss_core(struct cfg80211_bss *a, struct cfg80211_bss *b)
 {
+       const struct cfg80211_bss_ies *a_ies, *b_ies;
        int r;
 
        if (a->channel != b->channel)
                return b->channel->center_freq - a->channel->center_freq;
 
        if (is_mesh_bss(a) && is_mesh_bss(b)) {
+               a_ies = rcu_access_pointer(a->ies);
+               if (!a_ies)
+                       return -1;
+               b_ies = rcu_access_pointer(b->ies);
+               if (!b_ies)
+                       return 1;
+
                r = cmp_ies(WLAN_EID_MESH_ID,
-                           a->information_elements,
-                           a->len_information_elements,
-                           b->information_elements,
-                           b->len_information_elements);
+                           a_ies->data, a_ies->len,
+                           b_ies->data, b_ies->len);
                if (r)
                        return r;
                return cmp_ies(WLAN_EID_MESH_CONFIG,
-                              a->information_elements,
-                              a->len_information_elements,
-                              b->information_elements,
-                              b->len_information_elements);
+                              a_ies->data, a_ies->len,
+                              b_ies->data, b_ies->len);
        }
 
        /*
@@ -388,44 +425,36 @@ static int cmp_bss_core(struct cfg80211_bss *a,
 }
 
 static int cmp_bss(struct cfg80211_bss *a,
-                  struct cfg80211_bss *b)
-{
-       int r;
-
-       r = cmp_bss_core(a, b);
-       if (r)
-               return r;
-
-       return cmp_ies(WLAN_EID_SSID,
-                      a->information_elements,
-                      a->len_information_elements,
-                      b->information_elements,
-                      b->len_information_elements);
-}
-
-static int cmp_hidden_bss(struct cfg80211_bss *a,
-                  struct cfg80211_bss *b)
+                  struct cfg80211_bss *b,
+                  bool hide_ssid)
 {
+       const struct cfg80211_bss_ies *a_ies, *b_ies;
        const u8 *ie1;
        const u8 *ie2;
-       int i;
-       int r;
+       int i, r;
 
        r = cmp_bss_core(a, b);
        if (r)
                return r;
 
-       ie1 = cfg80211_find_ie(WLAN_EID_SSID,
-                       a->information_elements,
-                       a->len_information_elements);
-       ie2 = cfg80211_find_ie(WLAN_EID_SSID,
-                       b->information_elements,
-                       b->len_information_elements);
+       a_ies = rcu_access_pointer(a->ies);
+       if (!a_ies)
+               return -1;
+       b_ies = rcu_access_pointer(b->ies);
+       if (!b_ies)
+               return 1;
+
+       ie1 = cfg80211_find_ie(WLAN_EID_SSID, a_ies->data, a_ies->len);
+       ie2 = cfg80211_find_ie(WLAN_EID_SSID, b_ies->data, b_ies->len);
+
+       if (!ie1 && !ie2)
+               return 0;
 
-       /* Key comparator must use same algorithm in any rb-tree
-        * search function (order is important), otherwise ordering
-        * of items in the tree is broken and search gives incorrect
-        * results. This code uses same order as cmp_ies() does. */
+       /*
+        * Note that with "hide_ssid", the function returns a match if
+        * the already-present BSS ("b") is a hidden SSID beacon for
+        * the new BSS ("a").
+        */
 
        /* sort missing IE before (left of) present IE */
        if (!ie1)
@@ -433,18 +462,25 @@ static int cmp_hidden_bss(struct cfg80211_bss *a,
        if (!ie2)
                return 1;
 
-       /* zero-size SSID is used as an indication of the hidden bss */
-       if (!ie2[1])
+       /* zero-length SSID is used as an indication of the hidden bss */
+       if (hide_ssid && !ie2[1])
                return 0;
 
        /* sort by length first, then by contents */
        if (ie1[1] != ie2[1])
                return ie2[1] - ie1[1];
 
-       /* zeroed SSID ie is another indication of a hidden bss */
+       if (!hide_ssid)
+               return memcmp(ie1 + 2, ie2 + 2, ie1[1]);
+
+       /*
+        * zeroed SSID ie is another indication of a hidden bss;
+        * if it isn't zeroed just return the regular sort value
+        * to find the next candidate
+        */
        for (i = 0; i < ie2[1]; i++)
                if (ie2[i + 2])
-                       return -1;
+                       return memcmp(ie1 + 2, ie2 + 2, ie1[1]);
 
        return 0;
 }
@@ -459,6 +495,9 @@ struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
        struct cfg80211_internal_bss *bss, *res = NULL;
        unsigned long now = jiffies;
 
+       trace_cfg80211_get_bss(wiphy, channel, bssid, ssid, ssid_len, capa_mask,
+                              capa_val);
+
        spin_lock_bh(&dev->bss_lock);
 
        list_for_each_entry(bss, &dev->bss_list, list) {
@@ -480,6 +519,7 @@ struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
        spin_unlock_bh(&dev->bss_lock);
        if (!res)
                return NULL;
+       trace_cfg80211_return_bss(&res->pub);
        return &res->pub;
 }
 EXPORT_SYMBOL(cfg80211_get_bss);
@@ -524,7 +564,7 @@ static void rb_insert_bss(struct cfg80211_registered_device *dev,
                parent = *p;
                tbss = rb_entry(parent, struct cfg80211_internal_bss, rbn);
 
-               cmp = cmp_bss(&bss->pub, &tbss->pub);
+               cmp = cmp_bss(&bss->pub, &tbss->pub, false);
 
                if (WARN_ON(!cmp)) {
                        /* will sort of leak this BSS */
@@ -543,30 +583,8 @@ static void rb_insert_bss(struct cfg80211_registered_device *dev,
 
 static struct cfg80211_internal_bss *
 rb_find_bss(struct cfg80211_registered_device *dev,
-           struct cfg80211_internal_bss *res)
-{
-       struct rb_node *n = dev->bss_tree.rb_node;
-       struct cfg80211_internal_bss *bss;
-       int r;
-
-       while (n) {
-               bss = rb_entry(n, struct cfg80211_internal_bss, rbn);
-               r = cmp_bss(&res->pub, &bss->pub);
-
-               if (r == 0)
-                       return bss;
-               else if (r < 0)
-                       n = n->rb_left;
-               else
-                       n = n->rb_right;
-       }
-
-       return NULL;
-}
-
-static struct cfg80211_internal_bss *
-rb_find_hidden_bss(struct cfg80211_registered_device *dev,
-           struct cfg80211_internal_bss *res)
+           struct cfg80211_internal_bss *res,
+           bool hidden)
 {
        struct rb_node *n = dev->bss_tree.rb_node;
        struct cfg80211_internal_bss *bss;
@@ -574,7 +592,7 @@ rb_find_hidden_bss(struct cfg80211_registered_device *dev,
 
        while (n) {
                bss = rb_entry(n, struct cfg80211_internal_bss, rbn);
-               r = cmp_hidden_bss(&res->pub, &bss->pub);
+               r = cmp_bss(&res->pub, &bss->pub, hidden);
 
                if (r == 0)
                        return bss;
@@ -589,127 +607,86 @@ rb_find_hidden_bss(struct cfg80211_registered_device *dev,
 
 static void
 copy_hidden_ies(struct cfg80211_internal_bss *res,
-                struct cfg80211_internal_bss *hidden)
+               struct cfg80211_internal_bss *hidden)
 {
-       if (unlikely(res->pub.beacon_ies))
-               return;
-       if (WARN_ON(!hidden->pub.beacon_ies))
+       const struct cfg80211_bss_ies *ies;
+
+       if (rcu_access_pointer(res->pub.beacon_ies))
                return;
 
-       res->pub.beacon_ies = kmalloc(hidden->pub.len_beacon_ies, GFP_ATOMIC);
-       if (unlikely(!res->pub.beacon_ies))
+       ies = rcu_access_pointer(hidden->pub.beacon_ies);
+       if (WARN_ON(!ies))
                return;
 
-       res->beacon_ies_allocated = true;
-       res->pub.len_beacon_ies = hidden->pub.len_beacon_ies;
-       memcpy(res->pub.beacon_ies, hidden->pub.beacon_ies,
-                       res->pub.len_beacon_ies);
+       ies = kmemdup(ies, sizeof(*ies) + ies->len, GFP_ATOMIC);
+       if (unlikely(!ies))
+               return;
+       rcu_assign_pointer(res->pub.beacon_ies, ies);
 }
 
 static struct cfg80211_internal_bss *
 cfg80211_bss_update(struct cfg80211_registered_device *dev,
-                   struct cfg80211_internal_bss *res)
+                   struct cfg80211_internal_bss *tmp)
 {
        struct cfg80211_internal_bss *found = NULL;
 
-       /*
-        * The reference to "res" is donated to this function.
-        */
-
-       if (WARN_ON(!res->pub.channel)) {
-               kref_put(&res->ref, bss_release);
+       if (WARN_ON(!tmp->pub.channel))
                return NULL;
-       }
 
-       res->ts = jiffies;
+       tmp->ts = jiffies;
 
        spin_lock_bh(&dev->bss_lock);
 
-       found = rb_find_bss(dev, res);
+       if (WARN_ON(!rcu_access_pointer(tmp->pub.ies))) {
+               spin_unlock_bh(&dev->bss_lock);
+               return NULL;
+       }
+
+       found = rb_find_bss(dev, tmp, false);
 
        if (found) {
-               found->pub.beacon_interval = res->pub.beacon_interval;
-               found->pub.tsf = res->pub.tsf;
-               found->pub.signal = res->pub.signal;
-               found->pub.capability = res->pub.capability;
-               found->ts = res->ts;
+               found->pub.beacon_interval = tmp->pub.beacon_interval;
+               found->pub.tsf = tmp->pub.tsf;
+               found->pub.signal = tmp->pub.signal;
+               found->pub.capability = tmp->pub.capability;
+               found->ts = tmp->ts;
 
                /* Update IEs */
-               if (res->pub.proberesp_ies) {
-                       size_t used = dev->wiphy.bss_priv_size + sizeof(*res);
-                       size_t ielen = res->pub.len_proberesp_ies;
-
-                       if (found->pub.proberesp_ies &&
-                           !found->proberesp_ies_allocated &&
-                           ksize(found) >= used + ielen) {
-                               memcpy(found->pub.proberesp_ies,
-                                      res->pub.proberesp_ies, ielen);
-                               found->pub.len_proberesp_ies = ielen;
-                       } else {
-                               u8 *ies = found->pub.proberesp_ies;
-
-                               if (found->proberesp_ies_allocated)
-                                       ies = krealloc(ies, ielen, GFP_ATOMIC);
-                               else
-                                       ies = kmalloc(ielen, GFP_ATOMIC);
-
-                               if (ies) {
-                                       memcpy(ies, res->pub.proberesp_ies,
-                                              ielen);
-                                       found->proberesp_ies_allocated = true;
-                                       found->pub.proberesp_ies = ies;
-                                       found->pub.len_proberesp_ies = ielen;
-                               }
-                       }
+               if (rcu_access_pointer(tmp->pub.proberesp_ies)) {
+                       const struct cfg80211_bss_ies *old;
+
+                       old = rcu_access_pointer(found->pub.proberesp_ies);
 
+                       rcu_assign_pointer(found->pub.proberesp_ies,
+                                          tmp->pub.proberesp_ies);
                        /* Override possible earlier Beacon frame IEs */
-                       found->pub.information_elements =
-                               found->pub.proberesp_ies;
-                       found->pub.len_information_elements =
-                               found->pub.len_proberesp_ies;
-               }
-               if (res->pub.beacon_ies) {
-                       size_t used = dev->wiphy.bss_priv_size + sizeof(*res);
-                       size_t ielen = res->pub.len_beacon_ies;
-                       bool information_elements_is_beacon_ies =
-                               (found->pub.information_elements ==
-                                found->pub.beacon_ies);
-
-                       if (found->pub.beacon_ies &&
-                           !found->beacon_ies_allocated &&
-                           ksize(found) >= used + ielen) {
-                               memcpy(found->pub.beacon_ies,
-                                      res->pub.beacon_ies, ielen);
-                               found->pub.len_beacon_ies = ielen;
-                       } else {
-                               u8 *ies = found->pub.beacon_ies;
-
-                               if (found->beacon_ies_allocated)
-                                       ies = krealloc(ies, ielen, GFP_ATOMIC);
-                               else
-                                       ies = kmalloc(ielen, GFP_ATOMIC);
-
-                               if (ies) {
-                                       memcpy(ies, res->pub.beacon_ies,
-                                              ielen);
-                                       found->beacon_ies_allocated = true;
-                                       found->pub.beacon_ies = ies;
-                                       found->pub.len_beacon_ies = ielen;
-                               }
-                       }
+                       rcu_assign_pointer(found->pub.ies,
+                                          tmp->pub.proberesp_ies);
+                       if (old)
+                               kfree_rcu((struct cfg80211_bss_ies *)old,
+                                         rcu_head);
+               } else if (rcu_access_pointer(tmp->pub.beacon_ies)) {
+                       const struct cfg80211_bss_ies *old, *ies;
+
+                       old = rcu_access_pointer(found->pub.beacon_ies);
+                       ies = rcu_access_pointer(found->pub.ies);
+
+                       rcu_assign_pointer(found->pub.beacon_ies,
+                                          tmp->pub.beacon_ies);
 
                        /* Override IEs if they were from a beacon before */
-                       if (information_elements_is_beacon_ies) {
-                               found->pub.information_elements =
-                                       found->pub.beacon_ies;
-                               found->pub.len_information_elements =
-                                       found->pub.len_beacon_ies;
-                       }
-               }
+                       if (old == ies)
+                               rcu_assign_pointer(found->pub.ies,
+                                                  tmp->pub.beacon_ies);
 
-               kref_put(&res->ref, bss_release);
+                       if (old)
+                               kfree_rcu((struct cfg80211_bss_ies *)old,
+                                         rcu_head);
+               }
        } else {
+               struct cfg80211_internal_bss *new;
                struct cfg80211_internal_bss *hidden;
+               struct cfg80211_bss_ies *ies;
 
                /* First check if the beacon is a probe response from
                 * a hidden bss. If so, copy beacon ies (with nullified
@@ -720,14 +697,32 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
                /* TODO: The code is not trying to update existing probe
                 * response bss entries when beacon ies are
                 * getting changed. */
-               hidden = rb_find_hidden_bss(dev, res);
+               hidden = rb_find_bss(dev, tmp, true);
                if (hidden)
-                       copy_hidden_ies(res, hidden);
-
-               /* this "consumes" the reference */
-               list_add_tail(&res->list, &dev->bss_list);
-               rb_insert_bss(dev, res);
-               found = res;
+                       copy_hidden_ies(tmp, hidden);
+
+               /*
+                * create a copy -- the "res" variable that is passed in
+                * is allocated on the stack since it's not needed in the
+                * more common case of an update
+                */
+               new = kzalloc(sizeof(*new) + dev->wiphy.bss_priv_size,
+                             GFP_ATOMIC);
+               if (!new) {
+                       ies = (void *)rcu_dereference(tmp->pub.beacon_ies);
+                       if (ies)
+                               kfree_rcu(ies, rcu_head);
+                       ies = (void *)rcu_dereference(tmp->pub.proberesp_ies);
+                       if (ies)
+                               kfree_rcu(ies, rcu_head);
+                       spin_unlock_bh(&dev->bss_lock);
+                       return NULL;
+               }
+               memcpy(new, tmp, sizeof(*new));
+               kref_init(&new->ref);
+               list_add_tail(&new->list, &dev->bss_list);
+               rb_insert_bss(dev, new);
+               found = new;
        }
 
        dev->bss_generation++;
@@ -737,6 +732,38 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
        return found;
 }
 
+static struct ieee80211_channel *
+cfg80211_get_bss_channel(struct wiphy *wiphy, const u8 *ie, size_t ielen,
+                        struct ieee80211_channel *channel)
+{
+       const u8 *tmp;
+       u32 freq;
+       int channel_number = -1;
+
+       tmp = cfg80211_find_ie(WLAN_EID_DS_PARAMS, ie, ielen);
+       if (tmp && tmp[1] == 1) {
+               channel_number = tmp[2];
+       } else {
+               tmp = cfg80211_find_ie(WLAN_EID_HT_OPERATION, ie, ielen);
+               if (tmp && tmp[1] >= sizeof(struct ieee80211_ht_operation)) {
+                       struct ieee80211_ht_operation *htop = (void *)(tmp + 2);
+
+                       channel_number = htop->primary_chan;
+               }
+       }
+
+       if (channel_number < 0)
+               return channel;
+
+       freq = ieee80211_channel_to_frequency(channel_number, channel->band);
+       channel = ieee80211_get_channel(wiphy, freq);
+       if (!channel)
+               return NULL;
+       if (channel->flags & IEEE80211_CHAN_DISABLED)
+               return NULL;
+       return channel;
+}
+
 struct cfg80211_bss*
 cfg80211_inform_bss(struct wiphy *wiphy,
                    struct ieee80211_channel *channel,
@@ -744,54 +771,54 @@ cfg80211_inform_bss(struct wiphy *wiphy,
                    u16 beacon_interval, const u8 *ie, size_t ielen,
                    s32 signal, gfp_t gfp)
 {
-       struct cfg80211_internal_bss *res;
-       size_t privsz;
+       struct cfg80211_bss_ies *ies;
+       struct cfg80211_internal_bss tmp = {}, *res;
 
        if (WARN_ON(!wiphy))
                return NULL;
 
-       privsz = wiphy->bss_priv_size;
-
        if (WARN_ON(wiphy->signal_type == CFG80211_SIGNAL_TYPE_UNSPEC &&
                        (signal < 0 || signal > 100)))
                return NULL;
 
-       res = kzalloc(sizeof(*res) + privsz + ielen, gfp);
-       if (!res)
+       channel = cfg80211_get_bss_channel(wiphy, ie, ielen, channel);
+       if (!channel)
                return NULL;
 
-       memcpy(res->pub.bssid, bssid, ETH_ALEN);
-       res->pub.channel = channel;
-       res->pub.signal = signal;
-       res->pub.tsf = tsf;
-       res->pub.beacon_interval = beacon_interval;
-       res->pub.capability = capability;
+       memcpy(tmp.pub.bssid, bssid, ETH_ALEN);
+       tmp.pub.channel = channel;
+       tmp.pub.signal = signal;
+       tmp.pub.tsf = tsf;
+       tmp.pub.beacon_interval = beacon_interval;
+       tmp.pub.capability = capability;
        /*
         * Since we do not know here whether the IEs are from a Beacon or Probe
         * Response frame, we need to pick one of the options and only use it
         * with the driver that does not provide the full Beacon/Probe Response
         * frame. Use Beacon frame pointer to avoid indicating that this should
-        * override the information_elements pointer should we have received an
-        * earlier indication of Probe Response data.
+        * override the iies pointer should we have received an earlier
+        * indication of Probe Response data.
         *
         * The initial buffer for the IEs is allocated with the BSS entry and
         * is located after the private area.
         */
-       res->pub.beacon_ies = (u8 *)res + sizeof(*res) + privsz;
-       memcpy(res->pub.beacon_ies, ie, ielen);
-       res->pub.len_beacon_ies = ielen;
-       res->pub.information_elements = res->pub.beacon_ies;
-       res->pub.len_information_elements = res->pub.len_beacon_ies;
+       ies = kmalloc(sizeof(*ies) + ielen, gfp);
+       if (!ies)
+               return NULL;
+       ies->len = ielen;
+       memcpy(ies->data, ie, ielen);
 
-       kref_init(&res->ref);
+       rcu_assign_pointer(tmp.pub.beacon_ies, ies);
+       rcu_assign_pointer(tmp.pub.ies, ies);
 
-       res = cfg80211_bss_update(wiphy_to_dev(wiphy), res);
+       res = cfg80211_bss_update(wiphy_to_dev(wiphy), &tmp);
        if (!res)
                return NULL;
 
        if (res->pub.capability & WLAN_CAPABILITY_ESS)
                regulatory_hint_found_beacon(wiphy, channel, gfp);
 
+       trace_cfg80211_return_bss(&res->pub);
        /* cfg80211_bss_update gives us a referenced result */
        return &res->pub;
 }
@@ -803,10 +830,15 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy,
                          struct ieee80211_mgmt *mgmt, size_t len,
                          s32 signal, gfp_t gfp)
 {
-       struct cfg80211_internal_bss *res;
+       struct cfg80211_internal_bss tmp = {}, *res;
+       struct cfg80211_bss_ies *ies;
        size_t ielen = len - offsetof(struct ieee80211_mgmt,
                                      u.probe_resp.variable);
-       size_t privsz;
+
+       BUILD_BUG_ON(offsetof(struct ieee80211_mgmt, u.probe_resp.variable) !=
+                       offsetof(struct ieee80211_mgmt, u.beacon.variable));
+
+       trace_cfg80211_inform_bss_frame(wiphy, channel, mgmt, len, signal);
 
        if (WARN_ON(!mgmt))
                return NULL;
@@ -821,46 +853,38 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy,
        if (WARN_ON(len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable)))
                return NULL;
 
-       privsz = wiphy->bss_priv_size;
-
-       res = kzalloc(sizeof(*res) + privsz + ielen, gfp);
-       if (!res)
+       channel = cfg80211_get_bss_channel(wiphy, mgmt->u.beacon.variable,
+                                          ielen, channel);
+       if (!channel)
                return NULL;
 
-       memcpy(res->pub.bssid, mgmt->bssid, ETH_ALEN);
-       res->pub.channel = channel;
-       res->pub.signal = signal;
-       res->pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp);
-       res->pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int);
-       res->pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info);
-       /*
-        * The initial buffer for the IEs is allocated with the BSS entry and
-        * is located after the private area.
-        */
-       if (ieee80211_is_probe_resp(mgmt->frame_control)) {
-               res->pub.proberesp_ies = (u8 *) res + sizeof(*res) + privsz;
-               memcpy(res->pub.proberesp_ies, mgmt->u.probe_resp.variable,
-                      ielen);
-               res->pub.len_proberesp_ies = ielen;
-               res->pub.information_elements = res->pub.proberesp_ies;
-               res->pub.len_information_elements = res->pub.len_proberesp_ies;
-       } else {
-               res->pub.beacon_ies = (u8 *) res + sizeof(*res) + privsz;
-               memcpy(res->pub.beacon_ies, mgmt->u.beacon.variable, ielen);
-               res->pub.len_beacon_ies = ielen;
-               res->pub.information_elements = res->pub.beacon_ies;
-               res->pub.len_information_elements = res->pub.len_beacon_ies;
-       }
-
-       kref_init(&res->ref);
+       ies = kmalloc(sizeof(*ies) + ielen, gfp);
+       if (!ies)
+               return NULL;
+       ies->len = ielen;
+       memcpy(ies->data, mgmt->u.probe_resp.variable, ielen);
 
-       res = cfg80211_bss_update(wiphy_to_dev(wiphy), res);
+       if (ieee80211_is_probe_resp(mgmt->frame_control))
+               rcu_assign_pointer(tmp.pub.proberesp_ies, ies);
+       else
+               rcu_assign_pointer(tmp.pub.beacon_ies, ies);
+       rcu_assign_pointer(tmp.pub.ies, ies);
+       
+       memcpy(tmp.pub.bssid, mgmt->bssid, ETH_ALEN);
+       tmp.pub.channel = channel;
+       tmp.pub.signal = signal;
+       tmp.pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp);
+       tmp.pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int);
+       tmp.pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info);
+
+       res = cfg80211_bss_update(wiphy_to_dev(wiphy), &tmp);
        if (!res)
                return NULL;
 
        if (res->pub.capability & WLAN_CAPABILITY_ESS)
                regulatory_hint_found_beacon(wiphy, channel, gfp);
 
+       trace_cfg80211_return_bss(&res->pub);
        /* cfg80211_bss_update gives us a referenced result */
        return &res->pub;
 }
@@ -962,6 +986,7 @@ int cfg80211_wext_siwscan(struct net_device *dev,
        creq->ssids = (void *)&creq->channels[n_channels];
        creq->n_channels = n_channels;
        creq->n_ssids = 1;
+       creq->scan_start = jiffies;
 
        /* translate "Scan on frequencies" request */
        i = 0;
@@ -1026,7 +1051,7 @@ int cfg80211_wext_siwscan(struct net_device *dev,
                        creq->rates[i] = (1 << wiphy->bands[i]->n_bitrates) - 1;
 
        rdev->scan_req = creq;
-       err = rdev->ops->scan(wiphy, creq);
+       err = rdev_scan(rdev, creq);
        if (err) {
                rdev->scan_req = NULL;
                /* creq will be freed below */
@@ -1044,22 +1069,21 @@ int cfg80211_wext_siwscan(struct net_device *dev,
 EXPORT_SYMBOL_GPL(cfg80211_wext_siwscan);
 
 static void ieee80211_scan_add_ies(struct iw_request_info *info,
-                                  struct cfg80211_bss *bss,
+                                  const struct cfg80211_bss_ies *ies,
                                   char **current_ev, char *end_buf)
 {
-       u8 *pos, *end, *next;
+       const u8 *pos, *end, *next;
        struct iw_event iwe;
 
-       if (!bss->information_elements ||
-           !bss->len_information_elements)
+       if (!ies)
                return;
 
        /*
         * If needed, fragment the IEs buffer (at IE boundaries) into short
         * enough fragments to fit into IW_GENERIC_IE_MAX octet messages.
         */
-       pos = bss->information_elements;
-       end = pos + bss->len_information_elements;
+       pos = ies->data;
+       end = pos + ies->len;
 
        while (end - pos > IW_GENERIC_IE_MAX) {
                next = pos + 2 + pos[1];
@@ -1070,7 +1094,8 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info,
                iwe.cmd = IWEVGENIE;
                iwe.u.data.length = next - pos;
                *current_ev = iwe_stream_add_point(info, *current_ev,
-                                                  end_buf, &iwe, pos);
+                                                  end_buf, &iwe,
+                                                  (void *)pos);
 
                pos = next;
        }
@@ -1080,7 +1105,8 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info,
                iwe.cmd = IWEVGENIE;
                iwe.u.data.length = end - pos;
                *current_ev = iwe_stream_add_point(info, *current_ev,
-                                                  end_buf, &iwe, pos);
+                                                  end_buf, &iwe,
+                                                  (void *)pos);
        }
 }
 
@@ -1099,10 +1125,11 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
              struct cfg80211_internal_bss *bss, char *current_ev,
              char *end_buf)
 {
+       const struct cfg80211_bss_ies *ies;
        struct iw_event iwe;
+       const u8 *ie;
        u8 *buf, *cfg, *p;
-       u8 *ie = bss->pub.information_elements;
-       int rem = bss->pub.len_information_elements, i, sig;
+       int rem, i, sig;
        bool ismesh = false;
 
        memset(&iwe, 0, sizeof(iwe));
@@ -1167,7 +1194,17 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
        current_ev = iwe_stream_add_point(info, current_ev, end_buf,
                                          &iwe, "");
 
-       while (rem >= 2) {
+       rcu_read_lock();
+       ies = rcu_dereference(bss->pub.ies);
+       if (ies) {
+               rem = ies->len;
+               ie = ies->data;
+       } else {
+               rem = 0;
+               ie = NULL;
+       }
+
+       while (ies && rem >= 2) {
                /* invalid data */
                if (ie[1] > rem - 2)
                        break;
@@ -1179,7 +1216,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
                        iwe.u.data.length = ie[1];
                        iwe.u.data.flags = 1;
                        current_ev = iwe_stream_add_point(info, current_ev, end_buf,
-                                                         &iwe, ie + 2);
+                                                         &iwe, (u8 *)ie + 2);
                        break;
                case WLAN_EID_MESH_ID:
                        memset(&iwe, 0, sizeof(iwe));
@@ -1187,7 +1224,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
                        iwe.u.data.length = ie[1];
                        iwe.u.data.flags = 1;
                        current_ev = iwe_stream_add_point(info, current_ev, end_buf,
-                                                         &iwe, ie + 2);
+                                                         &iwe, (u8 *)ie + 2);
                        break;
                case WLAN_EID_MESH_CONFIG:
                        ismesh = true;
@@ -1196,7 +1233,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
                        buf = kmalloc(50, GFP_ATOMIC);
                        if (!buf)
                                break;
-                       cfg = ie + 2;
+                       cfg = (u8 *)ie + 2;
                        memset(&iwe, 0, sizeof(iwe));
                        iwe.cmd = IWEVCUSTOM;
                        sprintf(buf, "Mesh Network Path Selection Protocol ID: "
@@ -1294,7 +1331,8 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
                kfree(buf);
        }
 
-       ieee80211_scan_add_ies(info, &bss->pub, &current_ev, end_buf);
+       ieee80211_scan_add_ies(info, ies, &current_ev, end_buf);
+       rcu_read_unlock();
 
        return current_ev;
 }
This page took 0.045874 seconds and 5 git commands to generate.