ALSA: sound/usb: add preliminary support for UAC2 interrupts
[safe/jmp/linux-2.6] / net / wireless / scan.c
index ec148f6..978cac3 100644 (file)
 #include <net/iw_handler.h>
 #include "core.h"
 #include "nl80211.h"
+#include "wext-compat.h"
 
-#define IEEE80211_SCAN_RESULT_EXPIRE   (10 * HZ)
+#define IEEE80211_SCAN_RESULT_EXPIRE   (15 * HZ)
 
-void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
+void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak)
 {
+       struct cfg80211_scan_request *request;
        struct net_device *dev;
-#ifdef CONFIG_WIRELESS_EXT
+#ifdef CONFIG_CFG80211_WEXT
        union iwreq_data wrqu;
 #endif
 
-       dev = dev_get_by_index(&init_net, request->ifidx);
-       if (!dev)
-               goto out;
+       ASSERT_RDEV_LOCK(rdev);
 
-       WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
-       wiphy_to_dev(request->wiphy)->scan_req = NULL;
+       request = rdev->scan_req;
+
+       if (!request)
+               return;
+
+       dev = request->dev;
+
+       /*
+        * This must be before sending the other events!
+        * Otherwise, wpa_supplicant gets completely confused with
+        * wext events.
+        */
+       cfg80211_sme_scan_done(dev);
 
-       if (aborted)
-               nl80211_send_scan_aborted(wiphy_to_dev(request->wiphy), dev);
+       if (request->aborted)
+               nl80211_send_scan_aborted(rdev, dev);
        else
-               nl80211_send_scan_done(wiphy_to_dev(request->wiphy), dev);
+               nl80211_send_scan_done(rdev, dev);
 
-#ifdef CONFIG_WIRELESS_EXT
-       if (!aborted) {
+#ifdef CONFIG_CFG80211_WEXT
+       if (!request->aborted) {
                memset(&wrqu, 0, sizeof(wrqu));
 
                wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
@@ -46,8 +57,38 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
 
        dev_put(dev);
 
- out:
-       kfree(request);
+       rdev->scan_req = NULL;
+
+       /*
+        * OK. If this is invoked with "leak" then we can't
+        * free this ... but we've cleaned it up anyway. The
+        * driver failed to call the scan_done callback, so
+        * all bets are off, it might still be trying to use
+        * the scan request or not ... if it accesses the dev
+        * in there (it shouldn't anyway) then it may crash.
+        */
+       if (!leak)
+               kfree(request);
+}
+
+void __cfg80211_scan_done(struct work_struct *wk)
+{
+       struct cfg80211_registered_device *rdev;
+
+       rdev = container_of(wk, struct cfg80211_registered_device,
+                           scan_done_wk);
+
+       cfg80211_lock_rdev(rdev);
+       ___cfg80211_scan_done(rdev, false);
+       cfg80211_unlock_rdev(rdev);
+}
+
+void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
+{
+       WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
+
+       request->aborted = aborted;
+       queue_work(cfg80211_wq, &wiphy_to_dev(request->wiphy)->scan_done_wk);
 }
 EXPORT_SYMBOL(cfg80211_scan_done);
 
@@ -58,16 +99,38 @@ static void bss_release(struct kref *ref)
        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)
+{
+       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) {
+               bss->ts -= age_jiffies;
+       }
+}
+
+/* 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;
                list_del(&bss->list);
@@ -80,9 +143,9 @@ void cfg80211_bss_expire(struct cfg80211_registered_device *dev)
                dev->bss_generation++;
 }
 
-static u8 *find_ie(u8 num, u8 *ies, size_t len)
+const u8 *cfg80211_find_ie(u8 eid, const u8 *ies, int len)
 {
-       while (len > 2 && ies[0] != num) {
+       while (len > 2 && ies[0] != eid) {
                len -= ies[1] + 2;
                ies += ies[1] + 2;
        }
@@ -92,16 +155,17 @@ static u8 *find_ie(u8 num, u8 *ies, size_t len)
                return NULL;
        return ies;
 }
+EXPORT_SYMBOL(cfg80211_find_ie);
 
 static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2)
 {
-       const u8 *ie1 = find_ie(num, ies1, len1);
-       const u8 *ie2 = find_ie(num, ies2, len2);
+       const u8 *ie1 = cfg80211_find_ie(num, ies1, len1);
+       const u8 *ie2 = cfg80211_find_ie(num, ies2, len2);
        int r;
 
        if (!ie1 && !ie2)
                return 0;
-       if (!ie1)
+       if (!ie1 || !ie2)
                return -1;
 
        r = memcmp(ie1 + 2, ie2 + 2, min(ie1[1], ie2[1]));
@@ -116,12 +180,15 @@ static bool is_bss(struct cfg80211_bss *a,
 {
        const u8 *ssidie;
 
-       if (compare_ether_addr(a->bssid, bssid))
+       if (bssid && compare_ether_addr(a->bssid, bssid))
                return false;
 
-       ssidie = find_ie(WLAN_EID_SSID,
-                        a->information_elements,
-                        a->len_information_elements);
+       if (!ssid)
+               return true;
+
+       ssidie = cfg80211_find_ie(WLAN_EID_SSID,
+                                 a->information_elements,
+                                 a->len_information_elements);
        if (!ssidie)
                return false;
        if (ssidie[1] != ssid_len)
@@ -138,9 +205,9 @@ static bool is_mesh(struct cfg80211_bss *a,
        if (!is_zero_ether_addr(a->bssid))
                return false;
 
-       ie = find_ie(WLAN_EID_MESH_ID,
-                    a->information_elements,
-                    a->len_information_elements);
+       ie = cfg80211_find_ie(WLAN_EID_MESH_ID,
+                             a->information_elements,
+                             a->len_information_elements);
        if (!ie)
                return false;
        if (ie[1] != meshidlen)
@@ -148,10 +215,12 @@ static bool is_mesh(struct cfg80211_bss *a,
        if (memcmp(ie + 2, meshid, meshidlen))
                return false;
 
-       ie = find_ie(WLAN_EID_MESH_CONFIG,
-                    a->information_elements,
-                    a->len_information_elements);
-       if (ie[1] != IEEE80211_MESH_CONFIG_LEN)
+       ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,
+                             a->information_elements,
+                             a->len_information_elements);
+       if (!ie)
+               return false;
+       if (ie[1] != sizeof(struct ieee80211_meshconf_ie))
                return false;
 
        /*
@@ -159,7 +228,8 @@ static bool is_mesh(struct cfg80211_bss *a,
         * comparing since that may differ between stations taking
         * part in the same mesh.
         */
-       return memcmp(ie + 2, meshcfg, IEEE80211_MESH_CONFIG_LEN - 2) == 0;
+       return memcmp(ie + 2, meshcfg,
+           sizeof(struct ieee80211_meshconf_ie) - 2) == 0;
 }
 
 static int cmp_bss(struct cfg80211_bss *a,
@@ -199,7 +269,8 @@ static int cmp_bss(struct cfg80211_bss *a,
 struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
                                      struct ieee80211_channel *channel,
                                      const u8 *bssid,
-                                     const u8 *ssid, size_t ssid_len)
+                                     const u8 *ssid, size_t ssid_len,
+                                     u16 capa_mask, u16 capa_val)
 {
        struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
        struct cfg80211_internal_bss *bss, *res = NULL;
@@ -207,6 +278,8 @@ struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
        spin_lock_bh(&dev->bss_lock);
 
        list_for_each_entry(bss, &dev->bss_list, list) {
+               if ((bss->pub.capability & capa_mask) != capa_val)
+                       continue;
                if (channel && bss->pub.channel != channel)
                        continue;
                if (is_bss(&bss->pub, bssid, ssid, ssid_len)) {
@@ -305,8 +378,7 @@ rb_find_bss(struct cfg80211_registered_device *dev,
 
 static struct cfg80211_internal_bss *
 cfg80211_bss_update(struct cfg80211_registered_device *dev,
-                   struct cfg80211_internal_bss *res,
-                   bool overwrite)
+                   struct cfg80211_internal_bss *res)
 {
        struct cfg80211_internal_bss *found = NULL;
        const u8 *meshid, *meshcfg;
@@ -324,13 +396,14 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
 
        if (is_zero_ether_addr(res->pub.bssid)) {
                /* must be mesh, verify */
-               meshid = find_ie(WLAN_EID_MESH_ID, res->pub.information_elements,
-                                res->pub.len_information_elements);
-               meshcfg = find_ie(WLAN_EID_MESH_CONFIG,
-                                 res->pub.information_elements,
-                                 res->pub.len_information_elements);
+               meshid = cfg80211_find_ie(WLAN_EID_MESH_ID,
+                                         res->pub.information_elements,
+                                         res->pub.len_information_elements);
+               meshcfg = cfg80211_find_ie(WLAN_EID_MESH_CONFIG,
+                                          res->pub.information_elements,
+                                          res->pub.len_information_elements);
                if (!meshid || !meshcfg ||
-                   meshcfg[1] != IEEE80211_MESH_CONFIG_LEN) {
+                   meshcfg[1] != sizeof(struct ieee80211_meshconf_ie)) {
                        /* bogus mesh */
                        kref_put(&res->ref, bss_release);
                        return NULL;
@@ -341,20 +414,75 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
 
        found = rb_find_bss(dev, res);
 
-       if (found && overwrite) {
-               list_replace(&found->list, &res->list);
-               rb_replace_node(&found->rbn, &res->rbn,
-                               &dev->bss_tree);
-               kref_put(&found->ref, bss_release);
-               found = res;
-       } else if (found) {
-               kref_get(&found->ref);
+       if (found) {
                found->pub.beacon_interval = res->pub.beacon_interval;
                found->pub.tsf = res->pub.tsf;
                found->pub.signal = res->pub.signal;
-               found->pub.signal_type = res->pub.signal_type;
                found->pub.capability = res->pub.capability;
                found->ts = res->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;
+                               }
+                       }
+
+                       /* 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;
+
+                       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;
+                               }
+                       }
+               }
+
                kref_put(&res->ref, bss_release);
        } else {
                /* this "consumes" the reference */
@@ -370,20 +498,79 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev,
        return found;
 }
 
+struct cfg80211_bss*
+cfg80211_inform_bss(struct wiphy *wiphy,
+                   struct ieee80211_channel *channel,
+                   const u8 *bssid,
+                   u64 timestamp, u16 capability, u16 beacon_interval,
+                   const u8 *ie, size_t ielen,
+                   s32 signal, gfp_t gfp)
+{
+       struct cfg80211_internal_bss *res;
+       size_t privsz;
+
+       if (WARN_ON(!wiphy))
+               return NULL;
+
+       privsz = wiphy->bss_priv_size;
+
+       if (WARN_ON(wiphy->signal_type == NL80211_BSS_SIGNAL_UNSPEC &&
+                       (signal < 0 || signal > 100)))
+               return NULL;
+
+       res = kzalloc(sizeof(*res) + privsz + ielen, gfp);
+       if (!res)
+               return NULL;
+
+       memcpy(res->pub.bssid, bssid, ETH_ALEN);
+       res->pub.channel = channel;
+       res->pub.signal = signal;
+       res->pub.tsf = timestamp;
+       res->pub.beacon_interval = beacon_interval;
+       res->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.
+        *
+        * 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;
+
+       kref_init(&res->ref);
+
+       res = cfg80211_bss_update(wiphy_to_dev(wiphy), res);
+       if (!res)
+               return NULL;
+
+       if (res->pub.capability & WLAN_CAPABILITY_ESS)
+               regulatory_hint_found_beacon(wiphy, channel, gfp);
+
+       /* cfg80211_bss_update gives us a referenced result */
+       return &res->pub;
+}
+EXPORT_SYMBOL(cfg80211_inform_bss);
+
 struct cfg80211_bss *
 cfg80211_inform_bss_frame(struct wiphy *wiphy,
                          struct ieee80211_channel *channel,
                          struct ieee80211_mgmt *mgmt, size_t len,
-                         s32 signal, enum cfg80211_signal_type sigtype,
-                         gfp_t gfp)
+                         s32 signal, gfp_t gfp)
 {
        struct cfg80211_internal_bss *res;
        size_t ielen = len - offsetof(struct ieee80211_mgmt,
                                      u.probe_resp.variable);
-       bool overwrite;
        size_t privsz = wiphy->bss_priv_size;
 
-       if (WARN_ON(sigtype == NL80211_BSS_SIGNAL_UNSPEC &&
+       if (WARN_ON(wiphy->signal_type == NL80211_BSS_SIGNAL_UNSPEC &&
                    (signal < 0 || signal > 100)))
                return NULL;
 
@@ -397,24 +584,38 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy,
 
        memcpy(res->pub.bssid, mgmt->bssid, ETH_ALEN);
        res->pub.channel = channel;
-       res->pub.signal_type = sigtype;
        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);
-       /* point to after the private area */
-       res->pub.information_elements = (u8 *)res + sizeof(*res) + privsz;
-       memcpy(res->pub.information_elements, mgmt->u.probe_resp.variable, ielen);
-       res->pub.len_information_elements = ielen;
+       /*
+        * 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);
 
-       overwrite = ieee80211_is_probe_resp(mgmt->frame_control);
-
-       res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, overwrite);
+       res = cfg80211_bss_update(wiphy_to_dev(wiphy), res);
        if (!res)
                return NULL;
 
+       if (res->pub.capability & WLAN_CAPABILITY_ESS)
+               regulatory_hint_found_beacon(wiphy, channel, gfp);
+
        /* cfg80211_bss_update gives us a referenced result */
        return &res->pub;
 }
@@ -432,7 +633,29 @@ void cfg80211_put_bss(struct cfg80211_bss *pub)
 }
 EXPORT_SYMBOL(cfg80211_put_bss);
 
-#ifdef CONFIG_WIRELESS_EXT
+void cfg80211_unlink_bss(struct wiphy *wiphy, struct cfg80211_bss *pub)
+{
+       struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
+       struct cfg80211_internal_bss *bss;
+
+       if (WARN_ON(!pub))
+               return;
+
+       bss = container_of(pub, struct cfg80211_internal_bss, pub);
+
+       spin_lock_bh(&dev->bss_lock);
+
+       list_del(&bss->list);
+       dev->bss_generation++;
+       rb_erase(&bss->rbn, &dev->bss_tree);
+
+       spin_unlock_bh(&dev->bss_lock);
+
+       kref_put(&bss->ref, bss_release);
+}
+EXPORT_SYMBOL(cfg80211_unlink_bss);
+
+#ifdef CONFIG_CFG80211_WEXT
 int cfg80211_wext_siwscan(struct net_device *dev,
                          struct iw_request_info *info,
                          union iwreq_data *wrqu, char *extra)
@@ -440,14 +663,17 @@ int cfg80211_wext_siwscan(struct net_device *dev,
        struct cfg80211_registered_device *rdev;
        struct wiphy *wiphy;
        struct iw_scan_req *wreq = NULL;
-       struct cfg80211_scan_request *creq;
+       struct cfg80211_scan_request *creq = NULL;
        int i, err, n_channels = 0;
        enum ieee80211_band band;
 
        if (!netif_running(dev))
                return -ENETDOWN;
 
-       rdev = cfg80211_get_dev_from_ifindex(dev->ifindex);
+       if (wrqu->data.length == sizeof(struct iw_scan_req))
+               wreq = (struct iw_scan_req *)extra;
+
+       rdev = cfg80211_get_dev_from_ifindex(dev_net(dev), dev->ifindex);
 
        if (IS_ERR(rdev))
                return PTR_ERR(rdev);
@@ -459,9 +685,14 @@ int cfg80211_wext_siwscan(struct net_device *dev,
 
        wiphy = &rdev->wiphy;
 
-       for (band = 0; band < IEEE80211_NUM_BANDS; band++)
-               if (wiphy->bands[band])
-                       n_channels += wiphy->bands[band]->n_channels;
+       /* Determine number of channels, needed to allocate creq */
+       if (wreq && wreq->num_channels)
+               n_channels = wreq->num_channels;
+       else {
+               for (band = 0; band < IEEE80211_NUM_BANDS; band++)
+                       if (wiphy->bands[band])
+                               n_channels += wiphy->bands[band]->n_channels;
+       }
 
        creq = kzalloc(sizeof(*creq) + sizeof(struct cfg80211_ssid) +
                       n_channels * sizeof(void *),
@@ -472,31 +703,63 @@ int cfg80211_wext_siwscan(struct net_device *dev,
        }
 
        creq->wiphy = wiphy;
-       creq->ifidx = dev->ifindex;
-       creq->ssids = (void *)(creq + 1);
-       creq->channels = (void *)(creq->ssids + 1);
+       creq->dev = dev;
+       /* SSIDs come after channels */
+       creq->ssids = (void *)&creq->channels[n_channels];
        creq->n_channels = n_channels;
        creq->n_ssids = 1;
 
-       /* all channels */
+       /* translate "Scan on frequencies" request */
        i = 0;
        for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
                int j;
+
                if (!wiphy->bands[band])
                        continue;
+
                for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
+                       /* ignore disabled channels */
+                       if (wiphy->bands[band]->channels[j].flags &
+                                               IEEE80211_CHAN_DISABLED)
+                               continue;
+
+                       /* If we have a wireless request structure and the
+                        * wireless request specifies frequencies, then search
+                        * for the matching hardware channel.
+                        */
+                       if (wreq && wreq->num_channels) {
+                               int k;
+                               int wiphy_freq = wiphy->bands[band]->channels[j].center_freq;
+                               for (k = 0; k < wreq->num_channels; k++) {
+                                       int wext_freq = cfg80211_wext_freq(wiphy, &wreq->channel_list[k]);
+                                       if (wext_freq == wiphy_freq)
+                                               goto wext_freq_found;
+                               }
+                               goto wext_freq_not_found;
+                       }
+
+               wext_freq_found:
                        creq->channels[i] = &wiphy->bands[band]->channels[j];
                        i++;
+               wext_freq_not_found: ;
                }
        }
+       /* No channels found? */
+       if (!i) {
+               err = -EINVAL;
+               goto out;
+       }
 
-       /* translate scan request */
-       if (wrqu->data.length == sizeof(struct iw_scan_req)) {
-               wreq = (struct iw_scan_req *)extra;
+       /* Set real number of channels specified in creq->channels[] */
+       creq->n_channels = i;
 
+       /* translate "Scan for SSID" request */
+       if (wreq) {
                if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
-                       if (wreq->essid_len > IEEE80211_MAX_SSID_LEN)
-                               return -EINVAL;
+                       if (wreq->essid_len > IEEE80211_MAX_SSID_LEN) {
+                               err = -EINVAL;
+                               goto out;
+                       }
                        memcpy(creq->ssids[0].ssid, wreq->essid, wreq->essid_len);
                        creq->ssids[0].ssid_len = wreq->essid_len;
                }
@@ -508,13 +771,19 @@ int cfg80211_wext_siwscan(struct net_device *dev,
        err = rdev->ops->scan(wiphy, dev, creq);
        if (err) {
                rdev->scan_req = NULL;
-               kfree(creq);
+               /* creq will be freed below */
+       } else {
+               nl80211_send_scan_start(rdev, dev);
+               /* creq now owned by driver */
+               creq = NULL;
+               dev_hold(dev);
        }
  out:
-       cfg80211_put_dev(rdev);
+       kfree(creq);
+       cfg80211_unlock_rdev(rdev);
        return err;
 }
-EXPORT_SYMBOL(cfg80211_wext_siwscan);
+EXPORT_SYMBOL_GPL(cfg80211_wext_siwscan);
 
 static void ieee80211_scan_add_ies(struct iw_request_info *info,
                                   struct cfg80211_bss *bss,
@@ -557,16 +826,25 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info,
        }
 }
 
+static inline unsigned int elapsed_jiffies_msecs(unsigned long start)
+{
+       unsigned long end = jiffies;
+
+       if (end >= start)
+               return jiffies_to_msecs(end - start);
+
+       return jiffies_to_msecs(end + (MAX_JIFFY_OFFSET - start) + 1);
+}
 
 static char *
-ieee80211_bss(struct iw_request_info *info,
-                     struct cfg80211_internal_bss *bss,
-                     char *current_ev, char *end_buf)
+ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info,
+             struct cfg80211_internal_bss *bss, char *current_ev,
+             char *end_buf)
 {
        struct iw_event iwe;
        u8 *buf, *cfg, *p;
        u8 *ie = bss->pub.information_elements;
-       int rem = bss->pub.len_information_elements, i;
+       int rem = bss->pub.len_information_elements, i, sig;
        bool ismesh = false;
 
        memset(&iwe, 0, sizeof(iwe));
@@ -590,19 +868,28 @@ ieee80211_bss(struct iw_request_info *info,
        current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
                                          IW_EV_FREQ_LEN);
 
-       if (bss->pub.signal_type != CFG80211_SIGNAL_TYPE_NONE) {
+       if (wiphy->signal_type != CFG80211_SIGNAL_TYPE_NONE) {
                memset(&iwe, 0, sizeof(iwe));
                iwe.cmd = IWEVQUAL;
                iwe.u.qual.updated = IW_QUAL_LEVEL_UPDATED |
                                     IW_QUAL_NOISE_INVALID |
-                                    IW_QUAL_QUAL_INVALID;
-               switch (bss->pub.signal_type) {
+                                    IW_QUAL_QUAL_UPDATED;
+               switch (wiphy->signal_type) {
                case CFG80211_SIGNAL_TYPE_MBM:
-                       iwe.u.qual.level = bss->pub.signal / 100;
+                       sig = bss->pub.signal / 100;
+                       iwe.u.qual.level = sig;
                        iwe.u.qual.updated |= IW_QUAL_DBM;
+                       if (sig < -110)         /* rather bad */
+                               sig = -110;
+                       else if (sig > -40)     /* perfect */
+                               sig = -40;
+                       /* will give a range of 0 .. 70 */
+                       iwe.u.qual.qual = sig + 110;
                        break;
                case CFG80211_SIGNAL_TYPE_UNSPEC:
                        iwe.u.qual.level = bss->pub.signal;
+                       /* will give range 0 .. 100 */
+                       iwe.u.qual.qual = bss->pub.signal;
                        break;
                default:
                        /* not reached */
@@ -646,7 +933,7 @@ ieee80211_bss(struct iw_request_info *info,
                        break;
                case WLAN_EID_MESH_CONFIG:
                        ismesh = true;
-                       if (ie[1] != IEEE80211_MESH_CONFIG_LEN)
+                       if (ie[1] != sizeof(struct ieee80211_meshconf_ie))
                                break;
                        buf = kmalloc(50, GFP_ATOMIC);
                        if (!buf)
@@ -654,35 +941,40 @@ ieee80211_bss(struct iw_request_info *info,
                        cfg = ie + 2;
                        memset(&iwe, 0, sizeof(iwe));
                        iwe.cmd = IWEVCUSTOM;
-                       sprintf(buf, "Mesh network (version %d)", cfg[0]);
+                       sprintf(buf, "Mesh Network Path Selection Protocol ID: "
+                               "0x%02X", cfg[0]);
+                       iwe.u.data.length = strlen(buf);
+                       current_ev = iwe_stream_add_point(info, current_ev,
+                                                         end_buf,
+                                                         &iwe, buf);
+                       sprintf(buf, "Path Selection Metric ID: 0x%02X",
+                               cfg[1]);
+                       iwe.u.data.length = strlen(buf);
+                       current_ev = iwe_stream_add_point(info, current_ev,
+                                                         end_buf,
+                                                         &iwe, buf);
+                       sprintf(buf, "Congestion Control Mode ID: 0x%02X",
+                               cfg[2]);
                        iwe.u.data.length = strlen(buf);
                        current_ev = iwe_stream_add_point(info, current_ev,
                                                          end_buf,
                                                          &iwe, buf);
-                       sprintf(buf, "Path Selection Protocol ID: "
-                               "0x%02X%02X%02X%02X", cfg[1], cfg[2], cfg[3],
-                                                       cfg[4]);
+                       sprintf(buf, "Synchronization ID: 0x%02X", cfg[3]);
                        iwe.u.data.length = strlen(buf);
                        current_ev = iwe_stream_add_point(info, current_ev,
                                                          end_buf,
                                                          &iwe, buf);
-                       sprintf(buf, "Path Selection Metric ID: "
-                               "0x%02X%02X%02X%02X", cfg[5], cfg[6], cfg[7],
-                                                       cfg[8]);
+                       sprintf(buf, "Authentication ID: 0x%02X", cfg[4]);
                        iwe.u.data.length = strlen(buf);
                        current_ev = iwe_stream_add_point(info, current_ev,
                                                          end_buf,
                                                          &iwe, buf);
-                       sprintf(buf, "Congestion Control Mode ID: "
-                               "0x%02X%02X%02X%02X", cfg[9], cfg[10],
-                                                       cfg[11], cfg[12]);
+                       sprintf(buf, "Formation Info: 0x%02X", cfg[5]);
                        iwe.u.data.length = strlen(buf);
                        current_ev = iwe_stream_add_point(info, current_ev,
                                                          end_buf,
                                                          &iwe, buf);
-                       sprintf(buf, "Channel Precedence: "
-                               "0x%02X%02X%02X%02X", cfg[13], cfg[14],
-                                                       cfg[15], cfg[16]);
+                       sprintf(buf, "Capabilities: 0x%02X", cfg[6]);
                        iwe.u.data.length = strlen(buf);
                        current_ev = iwe_stream_add_point(info, current_ev,
                                                          end_buf,
@@ -712,8 +1004,8 @@ ieee80211_bss(struct iw_request_info *info,
                ie += ie[1] + 2;
        }
 
-       if (bss->pub.capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)
-           || ismesh) {
+       if (bss->pub.capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS) ||
+           ismesh) {
                memset(&iwe, 0, sizeof(iwe));
                iwe.cmd = SIOCGIWMODE;
                if (ismesh)
@@ -736,8 +1028,8 @@ ieee80211_bss(struct iw_request_info *info,
                                                  &iwe, buf);
                memset(&iwe, 0, sizeof(iwe));
                iwe.cmd = IWEVCUSTOM;
-               sprintf(buf, " Last beacon: %dms ago",
-                       jiffies_to_msecs(jiffies - bss->ts));
+               sprintf(buf, " Last beacon: %ums ago",
+                       elapsed_jiffies_msecs(bss->ts));
                iwe.u.data.length = strlen(buf);
                current_ev = iwe_stream_add_point(info, current_ev,
                                                  end_buf, &iwe, buf);
@@ -766,8 +1058,8 @@ static int ieee80211_scan_results(struct cfg80211_registered_device *dev,
                        spin_unlock_bh(&dev->bss_lock);
                        return -E2BIG;
                }
-               current_ev = ieee80211_bss(info, bss,
-                                                  current_ev, end_buf);
+               current_ev = ieee80211_bss(&dev->wiphy, info, bss,
+                                          current_ev, end_buf);
        }
        spin_unlock_bh(&dev->bss_lock);
        return current_ev - buf;
@@ -784,7 +1076,7 @@ int cfg80211_wext_giwscan(struct net_device *dev,
        if (!netif_running(dev))
                return -ENETDOWN;
 
-       rdev = cfg80211_get_dev_from_ifindex(dev->ifindex);
+       rdev = cfg80211_get_dev_from_ifindex(dev_net(dev), dev->ifindex);
 
        if (IS_ERR(rdev))
                return PTR_ERR(rdev);
@@ -802,8 +1094,8 @@ int cfg80211_wext_giwscan(struct net_device *dev,
        }
 
  out:
-       cfg80211_put_dev(rdev);
+       cfg80211_unlock_rdev(rdev);
        return res;
 }
-EXPORT_SYMBOL(cfg80211_wext_giwscan);
+EXPORT_SYMBOL_GPL(cfg80211_wext_giwscan);
 #endif