cfg80211/nl80211: scanning (and mac80211 update to use it)
authorJohannes Berg <johannes@sipsolutions.net>
Tue, 10 Feb 2009 20:25:55 +0000 (21:25 +0100)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 13 Feb 2009 18:45:49 +0000 (13:45 -0500)
This patch adds basic scan capability to cfg80211/nl80211 and
changes mac80211 to use it. The BSS list that cfg80211 maintains
is made driver-accessible with a private area in each BSS struct,
but mac80211 doesn't yet use it. That's another large project.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
21 files changed:
drivers/net/wireless/iwlwifi/iwl-agn.c
drivers/net/wireless/iwlwifi/iwl-core.c
drivers/net/wireless/iwlwifi/iwl-scan.c
drivers/net/wireless/iwlwifi/iwl3945-base.c
include/linux/nl80211.h
include/net/cfg80211.h
include/net/mac80211.h
include/net/wireless.h
net/mac80211/cfg.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/main.c
net/mac80211/mlme.c
net/mac80211/scan.c
net/mac80211/wext.c
net/wireless/Makefile
net/wireless/core.c
net/wireless/core.h
net/wireless/nl80211.c
net/wireless/nl80211.h
net/wireless/scan.c [new file with mode: 0644]

index c196abc6db7aa8ab5d26ddfd0a00e4587092a69a..539960da7e13e939cd8e5432a5c6f1c3c4ab889f 100644 (file)
@@ -2678,11 +2678,19 @@ static void iwl_bss_info_changed(struct ieee80211_hw *hw,
 
 }
 
-static int iwl_mac_hw_scan(struct ieee80211_hw *hw, u8 *ssid, size_t ssid_len)
+static int iwl_mac_hw_scan(struct ieee80211_hw *hw,
+                          struct cfg80211_scan_request *req)
 {
        unsigned long flags;
        struct iwl_priv *priv = hw->priv;
        int ret;
+       u8 *ssid = NULL;
+       size_t ssid_len = 0;
+
+       if (req->n_ssids) {
+               ssid = req->ssids[0].ssid;
+               ssid_len = req->ssids[0].ssid_len;
+       }
 
        IWL_DEBUG_MAC80211(priv, "enter\n");
 
@@ -2718,7 +2726,7 @@ static int iwl_mac_hw_scan(struct ieee80211_hw *hw, u8 *ssid, size_t ssid_len)
 
        if (ssid_len) {
                priv->one_direct_scan = 1;
-               priv->direct_ssid_len =  min_t(u8, ssid_len, IW_ESSID_MAX_SIZE);
+               priv->direct_ssid_len = ssid_len;
                memcpy(priv->direct_ssid, ssid, priv->direct_ssid_len);
        } else {
                priv->one_direct_scan = 0;
index e18c3f326f71825b273064d579cacdac29623247..260bf903cb71ae36e3f2e24cc9c1db0fa5d4738e 100644 (file)
@@ -1271,6 +1271,7 @@ int iwl_setup_mac(struct iwl_priv *priv)
                BIT(NL80211_IFTYPE_ADHOC);
 
        hw->wiphy->custom_regulatory = true;
+       hw->wiphy->max_scan_ssids = 1;
 
        /* Default value; 4 EDCA QOS priorities */
        hw->queues = 4;
index 22bad3ce7d6a9a8b2a480162d91ba7563a797b76..1ec2b20eb37c27f245a850442e1433a524752f74 100644 (file)
@@ -860,7 +860,7 @@ void iwl_bg_scan_completed(struct work_struct *work)
        if (test_bit(STATUS_EXIT_PENDING, &priv->status))
                return;
 
-       ieee80211_scan_completed(priv->hw);
+       ieee80211_scan_completed(priv->hw, false);
 
        /* Since setting the TXPOWER may have been deferred while
         * performing the scan, fire one off */
index 42cc2884971c7b2221604e974e6aec21511a5527..0cd8cb96a5ef03d6e319007c90693bf9e4f84388 100644 (file)
@@ -4442,15 +4442,23 @@ static void iwl3945_bss_info_changed(struct ieee80211_hw *hw,
 
 }
 
-static int iwl3945_mac_hw_scan(struct ieee80211_hw *hw, u8 *ssid, size_t len)
+static int iwl3945_mac_hw_scan(struct ieee80211_hw *hw,
+                              struct cfg80211_scan_request *req)
 {
        int rc = 0;
        unsigned long flags;
        struct iwl_priv *priv = hw->priv;
+       size_t len = 0;
+       u8 *ssid = NULL;
        DECLARE_SSID_BUF(ssid_buf);
 
        IWL_DEBUG_MAC80211(priv, "enter\n");
 
+       if (req->n_ssids) {
+               ssid = req->ssids[0].ssid;
+               len = req->ssids[0].ssid_len;
+       }
+
        mutex_lock(&priv->mutex);
        spin_lock_irqsave(&priv->lock, flags);
 
@@ -4478,9 +4486,8 @@ static int iwl3945_mac_hw_scan(struct ieee80211_hw *hw, u8 *ssid, size_t len)
                               print_ssid(ssid_buf, ssid, len), len);
 
                priv->one_direct_scan = 1;
-               priv->direct_ssid_len = (u8)
-                   min((u8) len, (u8) IW_ESSID_MAX_SIZE);
-               memcpy(priv->direct_ssid, ssid, priv->direct_ssid_len);
+               priv->direct_ssid_len = len;
+               memcpy(priv->direct_ssid, ssid, len);
        } else
                priv->one_direct_scan = 0;
 
@@ -5412,6 +5419,8 @@ static int iwl3945_pci_probe(struct pci_dev *pdev, const struct pci_device_id *e
 
        hw->wiphy->custom_regulatory = true;
 
+       hw->wiphy->max_scan_ssids = 1;
+
        /* 4 EDCA QOS priorities */
        hw->queues = 4;
 
index 4bc27049f4e5736bb7d77b0c4fa947d975314c6c..8802d1bda382a1d674ce1ae29cf6f84fc1bb0cf3 100644 (file)
  *     added to all specified management frames generated by
  *     kernel/firmware/driver.
  *
+ * @NL80211_CMD_GET_SCAN: get scan results
+ * @NL80211_CMD_TRIGGER_SCAN: trigger a new scan with the given parameters
+ * @NL80211_CMD_NEW_SCAN_RESULTS: scan notification (as a reply to
+ *     NL80211_CMD_GET_SCAN and on the "scan" multicast group)
+ * @NL80211_CMD_SCAN_ABORTED: scan was aborted, for unspecified reasons,
+ *     partial scan results may be available
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -192,6 +199,11 @@ enum nl80211_commands {
 
        NL80211_CMD_GET_REG,
 
+       NL80211_CMD_GET_SCAN,
+       NL80211_CMD_TRIGGER_SCAN,
+       NL80211_CMD_NEW_SCAN_RESULTS,
+       NL80211_CMD_SCAN_ABORTED,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
@@ -305,6 +317,18 @@ enum nl80211_commands {
  * @NL80211_ATTR_IE: Information element(s) data (used, e.g., with
  *     %NL80211_CMD_SET_MGMT_EXTRA_IE).
  *
+ * @NL80211_ATTR_MAX_NUM_SCAN_SSIDS: number of SSIDs you can scan with
+ *     a single scan request, a wiphy attribute.
+ *
+ * @NL80211_ATTR_SCAN_FREQUENCIES: nested attribute with frequencies (in MHz)
+ * @NL80211_ATTR_SCAN_SSIDS: nested attribute with SSIDs, leave out for passive
+ *     scanning and include a zero-length SSID (wildcard) for wildcard scan
+ * @NL80211_ATTR_SCAN_GENERATION: the scan generation increases whenever the
+ *     scan result list changes (BSS expired or added) so that applications
+ *     can verify that they got a single, consistent snapshot (when all dump
+ *     messages carried the same generation number)
+ * @NL80211_ATTR_BSS: scan result BSS
+ *
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
  */
@@ -372,6 +396,13 @@ enum nl80211_attrs {
        NL80211_ATTR_MGMT_SUBTYPE,
        NL80211_ATTR_IE,
 
+       NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
+
+       NL80211_ATTR_SCAN_FREQUENCIES,
+       NL80211_ATTR_SCAN_SSIDS,
+       NL80211_ATTR_SCAN_GENERATION,
+       NL80211_ATTR_BSS,
+
        /* add attributes here, update the policy in nl80211.c */
 
        __NL80211_ATTR_AFTER_LAST,
@@ -841,4 +872,38 @@ enum nl80211_channel_type {
        NL80211_CHAN_HT40MINUS,
        NL80211_CHAN_HT40PLUS
 };
+
+/**
+ * enum nl80211_bss - netlink attributes for a BSS
+ *
+ * @__NL80211_BSS_INVALID: invalid
+ * @NL80211_BSS_FREQUENCY: frequency in MHz (u32)
+ * @NL80211_BSS_TSF: TSF of the received probe response/beacon (u64)
+ * @NL80211_BSS_BEACON_INTERVAL: beacon interval of the (I)BSS (u16)
+ * @NL80211_BSS_CAPABILITY: capability field (CPU order, u16)
+ * @NL80211_BSS_INFORMATION_ELEMENTS: binary attribute containing the
+ *     raw information elements from the probe response/beacon (bin)
+ * @NL80211_BSS_SIGNAL_MBM: signal strength of probe response/beacon
+ *     in mBm (100 * dBm) (s32)
+ * @NL80211_BSS_SIGNAL_UNSPEC: signal strength of the probe response/beacon
+ *     in unspecified units, scaled to 0..100 (u8)
+ * @__NL80211_BSS_AFTER_LAST: internal
+ * @NL80211_BSS_MAX: highest BSS attribute
+ */
+enum nl80211_bss {
+       __NL80211_BSS_INVALID,
+       NL80211_BSS_BSSID,
+       NL80211_BSS_FREQUENCY,
+       NL80211_BSS_TSF,
+       NL80211_BSS_BEACON_INTERVAL,
+       NL80211_BSS_CAPABILITY,
+       NL80211_BSS_INFORMATION_ELEMENTS,
+       NL80211_BSS_SIGNAL_MBM,
+       NL80211_BSS_SIGNAL_UNSPEC,
+
+       /* keep last */
+       __NL80211_BSS_AFTER_LAST,
+       NL80211_BSS_MAX = __NL80211_BSS_AFTER_LAST - 1
+};
+
 #endif /* __LINUX_NL80211_H */
index dd1fd51638fcd02a67d2cad8b8f9cb89f7d78987..09a0b268e5cfe5718252202d2a7e7ef7436b4f71 100644 (file)
@@ -4,6 +4,10 @@
 #include <linux/netlink.h>
 #include <linux/skbuff.h>
 #include <linux/nl80211.h>
+#include <linux/if_ether.h>
+#include <linux/ieee80211.h>
+#include <linux/wireless.h>
+#include <net/iw_handler.h>
 #include <net/genetlink.h>
 /* remove once we remove the wext stuff */
 #include <net/iw_handler.h>
@@ -504,6 +508,83 @@ struct wiphy;
 /* from net/ieee80211.h */
 struct ieee80211_channel;
 
+/**
+ * struct cfg80211_ssid - SSID description
+ * @ssid: the SSID
+ * @ssid_len: length of the ssid
+ */
+struct cfg80211_ssid {
+       u8 ssid[IEEE80211_MAX_SSID_LEN];
+       u8 ssid_len;
+};
+
+/**
+ * struct cfg80211_scan_request - scan request description
+ *
+ * @ssids: SSIDs to scan for (active scan only)
+ * @n_ssids: number of SSIDs
+ * @channels: channels to scan on.
+ * @n_channels: number of channels for each band
+ * @wiphy: the wiphy this was for
+ * @ifidx: the interface index
+ */
+struct cfg80211_scan_request {
+       struct cfg80211_ssid *ssids;
+       int n_ssids;
+       struct ieee80211_channel **channels;
+       u32 n_channels;
+
+       /* internal */
+       struct wiphy *wiphy;
+       int ifidx;
+};
+
+/**
+ * enum cfg80211_signal_type - signal type
+ *
+ * @CFG80211_SIGNAL_TYPE_NONE: no signal strength information available
+ * @CFG80211_SIGNAL_TYPE_MBM: signal strength in mBm (100*dBm)
+ * @CFG80211_SIGNAL_TYPE_UNSPEC: signal strength, increasing from 0 through 100
+ */
+enum cfg80211_signal_type {
+       CFG80211_SIGNAL_TYPE_NONE,
+       CFG80211_SIGNAL_TYPE_MBM,
+       CFG80211_SIGNAL_TYPE_UNSPEC,
+};
+
+/**
+ * struct cfg80211_bss - BSS description
+ *
+ * This structure describes a BSS (which may also be a mesh network)
+ * for use in scan results and similar.
+ *
+ * @bssid: BSSID of the BSS
+ * @tsf: timestamp of last received update
+ * @beacon_interval: the beacon interval as from the frame
+ * @capability: the capability field in host byte order
+ * @information_elements: the information elements (Note that there
+ *     is no guarantee that these are well-formed!)
+ * @len_information_elements: total length of the information elements
+ * @signal: signal strength value
+ * @signal_type: signal type
+ * @priv: private area for driver use, has at least wiphy->bss_priv_size bytes
+ */
+struct cfg80211_bss {
+       struct ieee80211_channel *channel;
+
+       u8 bssid[ETH_ALEN];
+       u64 tsf;
+       u16 beacon_interval;
+       u16 capability;
+       u8 *information_elements;
+       size_t len_information_elements;
+
+       s32 signal;
+       enum cfg80211_signal_type signal_type;
+
+       u8 priv[0] __attribute__((__aligned__(sizeof(void *))));
+};
+
 /**
  * struct cfg80211_ops - backend description for wireless configuration
  *
@@ -571,6 +652,11 @@ struct ieee80211_channel;
  * @set_channel: Set channel
  *
  * @set_mgmt_extra_ie: Set extra IE data for management frames
+ *
+ * @scan: Request to do a scan. If returning zero, the scan request is given
+ *     the driver, and will be valid until passed to cfg80211_scan_done().
+ *     For scan results, call cfg80211_inform_bss(); you can call this outside
+ *     the scan/scan_done bracket too.
  */
 struct cfg80211_ops {
        int     (*suspend)(struct wiphy *wiphy);
@@ -648,6 +734,9 @@ struct cfg80211_ops {
        int     (*set_mgmt_extra_ie)(struct wiphy *wiphy,
                                     struct net_device *dev,
                                     struct mgmt_extra_ie_params *params);
+
+       int     (*scan)(struct wiphy *wiphy, struct net_device *dev,
+                       struct cfg80211_scan_request *request);
 };
 
 /* temporary wext handlers */
@@ -658,5 +747,47 @@ int cfg80211_wext_siwmode(struct net_device *dev, struct iw_request_info *info,
                          u32 *mode, char *extra);
 int cfg80211_wext_giwmode(struct net_device *dev, struct iw_request_info *info,
                          u32 *mode, char *extra);
+int cfg80211_wext_siwscan(struct net_device *dev,
+                         struct iw_request_info *info,
+                         union iwreq_data *wrqu, char *extra);
+int cfg80211_wext_giwscan(struct net_device *dev,
+                         struct iw_request_info *info,
+                         struct iw_point *data, char *extra);
+
+/**
+ * cfg80211_scan_done - notify that scan finished
+ *
+ * @request: the corresponding scan request
+ * @aborted: set to true if the scan was aborted for any reason,
+ *     userspace will be notified of that
+ */
+void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted);
+
+/**
+ * cfg80211_inform_bss - inform cfg80211 of a new BSS
+ *
+ * @wiphy: the wiphy reporting the BSS
+ * @bss: the found BSS
+ * @gfp: context flags
+ *
+ * This informs cfg80211 that BSS information was found and
+ * the BSS should be updated/added.
+ */
+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);
+
+struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
+                                     struct ieee80211_channel *channel,
+                                     const u8 *bssid,
+                                     const u8 *ssid, size_t ssid_len);
+struct cfg80211_bss *cfg80211_get_mesh(struct wiphy *wiphy,
+                                      struct ieee80211_channel *channel,
+                                      const u8 *meshid, size_t meshidlen,
+                                      const u8 *meshcfg);
+void cfg80211_put_bss(struct cfg80211_bss *bss);
 
 #endif /* __NET_CFG80211_H */
index 341f3e595ebd00f9ba663b51e08f895b21b51b19..88fa3e03e3e9dbf4746f59851c27cfd9407fe7bf 100644 (file)
@@ -1406,7 +1406,8 @@ struct ieee80211_ops {
        void (*update_tkip_key)(struct ieee80211_hw *hw,
                        struct ieee80211_key_conf *conf, const u8 *address,
                        u32 iv32, u16 *phase1key);
-       int (*hw_scan)(struct ieee80211_hw *hw, u8 *ssid, size_t len);
+       int (*hw_scan)(struct ieee80211_hw *hw,
+                      struct cfg80211_scan_request *req);
        int (*get_stats)(struct ieee80211_hw *hw,
                         struct ieee80211_low_level_stats *stats);
        void (*get_tkip_seq)(struct ieee80211_hw *hw, u8 hw_key_idx,
@@ -1844,8 +1845,9 @@ void ieee80211_wake_queues(struct ieee80211_hw *hw);
  * mac80211 that the scan finished.
  *
  * @hw: the hardware that finished the scan
+ * @aborted: set to true if scan was aborted
  */
-void ieee80211_scan_completed(struct ieee80211_hw *hw);
+void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted);
 
 /**
  * ieee80211_iterate_active_interfaces - iterate active interfaces
index a42c1562d52bd61ea377a7253bbaec6ab790e269..1c6285eb166668dbf267d7c79d3d9d8be7307133 100644 (file)
@@ -213,6 +213,9 @@ struct wiphy {
        bool custom_regulatory;
        bool strict_regulatory;
 
+       int bss_priv_size;
+       u8 max_scan_ssids;
+
        /* If multiple wiphys are registered and you're handed e.g.
         * a regular netdev with assigned ieee80211_ptr, you won't
         * know whether it points to a wiphy your driver has registered
index 42d692fd9bec361aaafefef8c1c5202bb92c8135..c8d969be440bd805cfdfa66980f5bf826ee0d846 100644 (file)
@@ -1277,6 +1277,25 @@ static int ieee80211_resume(struct wiphy *wiphy)
 #define ieee80211_resume NULL
 #endif
 
+static int ieee80211_scan(struct wiphy *wiphy,
+                         struct net_device *dev,
+                         struct cfg80211_scan_request *req)
+{
+       struct ieee80211_sub_if_data *sdata;
+
+       if (!netif_running(dev))
+               return -ENETDOWN;
+
+       sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       if (sdata->vif.type != NL80211_IFTYPE_STATION &&
+           sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+           sdata->vif.type != NL80211_IFTYPE_MESH_POINT)
+               return -EOPNOTSUPP;
+
+       return ieee80211_request_scan(sdata, req);
+}
+
 struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
@@ -1309,4 +1328,5 @@ struct cfg80211_ops mac80211_config_ops = {
        .set_mgmt_extra_ie = ieee80211_set_mgmt_extra_ie,
        .suspend = ieee80211_suspend,
        .resume = ieee80211_resume,
+       .scan = ieee80211_scan,
 };
index 9122416fd6af9c8fdaa50b2346b1dc7bec36d4d9..cbc0b7d647f9460c5b485d9cf393382e7d326df6 100644 (file)
@@ -294,8 +294,6 @@ struct ieee80211_if_sta {
        u8 ssid[IEEE80211_MAX_SSID_LEN];
        enum ieee80211_sta_mlme_state state;
        size_t ssid_len;
-       u8 scan_ssid[IEEE80211_MAX_SSID_LEN];
-       size_t scan_ssid_len;
        u16 aid;
        u16 ap_capab, capab;
        u8 *extra_ie; /* to be added to the end of AssocReq */
@@ -658,17 +656,18 @@ struct ieee80211_local {
 
        /* Scanning and BSS list */
        bool sw_scanning, hw_scanning;
+       struct cfg80211_ssid scan_ssid;
+       struct cfg80211_scan_request int_scan_req;
+       struct cfg80211_scan_request *scan_req;
+       struct ieee80211_channel *scan_channel;
        int scan_channel_idx;
-       enum ieee80211_band scan_band;
 
        enum { SCAN_SET_CHANNEL, SCAN_SEND_PROBE } scan_state;
        unsigned long last_scan_completed;
        struct delayed_work scan_work;
        struct ieee80211_sub_if_data *scan_sdata;
-       struct ieee80211_channel *oper_channel, *scan_channel, *csa_channel;
        enum nl80211_channel_type oper_channel_type;
-       u8 scan_ssid[IEEE80211_MAX_SSID_LEN];
-       size_t scan_ssid_len;
+       struct ieee80211_channel *oper_channel, *csa_channel;
        struct list_head bss_list;
        struct ieee80211_bss *bss_hash[STA_HASH_SIZE];
        spinlock_t bss_lock;
@@ -929,7 +928,7 @@ void ieee80211_send_pspoll(struct ieee80211_local *local,
 
 /* scan/BSS handling */
 int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata,
-                          u8 *ssid, size_t ssid_len);
+                          struct cfg80211_scan_request *req);
 int ieee80211_scan_results(struct ieee80211_local *local,
                           struct iw_request_info *info,
                           char *buf, size_t len);
@@ -944,14 +943,15 @@ int ieee80211_sta_set_extra_ie(struct ieee80211_sub_if_data *sdata,
 
 void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local);
 int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata,
-                        u8 *ssid, size_t ssid_len);
+                        struct cfg80211_scan_request *req);
 struct ieee80211_bss *
 ieee80211_bss_info_update(struct ieee80211_local *local,
                          struct ieee80211_rx_status *rx_status,
                          struct ieee80211_mgmt *mgmt,
                          size_t len,
                          struct ieee802_11_elems *elems,
-                         int freq, bool beacon);
+                         struct ieee80211_channel *channel,
+                         bool beacon);
 struct ieee80211_bss *
 ieee80211_rx_bss_add(struct ieee80211_local *local, u8 *bssid, int freq,
                     u8 *ssid, u8 ssid_len);
index 1c17fb8e405878b3ad56409946d2da348f3c9dad..df94b936526472a5f334559b9c7c7300698fbe4d 100644 (file)
@@ -522,7 +522,7 @@ static int ieee80211_stop(struct net_device *dev)
                         * scan event to userspace -- the scan is incomplete.
                         */
                        if (local->sw_scanning)
-                               ieee80211_scan_completed(&local->hw);
+                               ieee80211_scan_completed(&local->hw, true);
                }
 
                conf.vif = &sdata->vif;
index 956afea4214de7db61e697ae8f331bc93a719828..954edfbb6b6ffe4ef8461ae6fa06058588087aa2 100644 (file)
@@ -733,6 +733,7 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
                return NULL;
 
        wiphy->privid = mac80211_wiphy_privid;
+       wiphy->max_scan_ssids = 4;
 
        local = wiphy_priv(wiphy);
        local->hw.wiphy = wiphy;
@@ -817,25 +818,33 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
        enum ieee80211_band band;
        struct net_device *mdev;
        struct ieee80211_master_priv *mpriv;
+       int channels, i, j;
 
        /*
         * generic code guarantees at least one band,
         * set this very early because much code assumes
         * that hw.conf.channel is assigned
         */
+       channels = 0;
        for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
                struct ieee80211_supported_band *sband;
 
                sband = local->hw.wiphy->bands[band];
-               if (sband) {
+               if (sband && !local->oper_channel) {
                        /* init channel we're on */
                        local->hw.conf.channel =
                        local->oper_channel =
                        local->scan_channel = &sband->channels[0];
-                       break;
                }
+               if (sband)
+                       channels += sband->n_channels;
        }
 
+       local->int_scan_req.n_channels = channels;
+       local->int_scan_req.channels = kzalloc(sizeof(void *) * channels, GFP_KERNEL);
+       if (!local->int_scan_req.channels)
+               return -ENOMEM;
+
        /* if low-level driver supports AP, we also support VLAN */
        if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP))
                local->hw.wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN);
@@ -845,7 +854,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
 
        result = wiphy_register(local->hw.wiphy);
        if (result < 0)
-               return result;
+               goto fail_wiphy_register;
 
        /*
         * We use the number of queues for feature tests (QoS, HT) internally
@@ -948,6 +957,20 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
 
        ieee80211_led_init(local);
 
+       /* alloc internal scan request */
+       i = 0;
+       local->int_scan_req.ssids = &local->scan_ssid;
+       local->int_scan_req.n_ssids = 1;
+       for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+               if (!hw->wiphy->bands[band])
+                       continue;
+               for (j = 0; j < hw->wiphy->bands[band]->n_channels; j++) {
+                       local->int_scan_req.channels[i] =
+                               &hw->wiphy->bands[band]->channels[j];
+                       i++;
+               }
+       }
+
        return 0;
 
 fail_wep:
@@ -966,6 +989,8 @@ fail_workqueue:
                free_netdev(local->mdev);
 fail_mdev_alloc:
        wiphy_unregister(local->hw.wiphy);
+fail_wiphy_register:
+       kfree(local->int_scan_req.channels);
        return result;
 }
 EXPORT_SYMBOL(ieee80211_register_hw);
@@ -1011,6 +1036,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
        ieee80211_wep_free(local);
        ieee80211_led_exit(local);
        free_netdev(local->mdev);
+       kfree(local->int_scan_req.channels);
 }
 EXPORT_SYMBOL(ieee80211_unregister_hw);
 
index bfc47b33068740a643d7d2cbb45b1f02f13f76e5..46b4817cdea93b84c52c506365cf521ea3998b98 100644 (file)
@@ -1743,7 +1743,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
        }
 
        bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, elems,
-                                       freq, beacon);
+                                       channel, beacon);
        if (!bss)
                return;
 
@@ -2162,7 +2162,15 @@ static void ieee80211_sta_merge_ibss(struct ieee80211_sub_if_data *sdata,
 
        printk(KERN_DEBUG "%s: No active IBSS STAs - trying to scan for other "
               "IBSS networks with same SSID (merge)\n", sdata->dev->name);
-       ieee80211_request_scan(sdata, ifsta->ssid, ifsta->ssid_len);
+
+       /* XXX maybe racy? */
+       if (sdata->local->scan_req)
+               return;
+
+       memcpy(sdata->local->int_scan_req.ssids[0].ssid,
+              ifsta->ssid, IEEE80211_MAX_SSID_LEN);
+       sdata->local->int_scan_req.ssids[0].ssid_len = ifsta->ssid_len;
+       ieee80211_request_scan(sdata, &sdata->local->int_scan_req);
 }
 
 
@@ -2378,8 +2386,15 @@ dont_join:
                              IEEE80211_SCAN_INTERVAL)) {
                printk(KERN_DEBUG "%s: Trigger new scan to find an IBSS to "
                       "join\n", sdata->dev->name);
-               return ieee80211_request_scan(sdata, ifsta->ssid,
-                                             ifsta->ssid_len);
+
+               /* XXX maybe racy? */
+               if (local->scan_req)
+                       return -EBUSY;
+
+               memcpy(local->int_scan_req.ssids[0].ssid,
+                      ifsta->ssid, IEEE80211_MAX_SSID_LEN);
+               local->int_scan_req.ssids[0].ssid_len = ifsta->ssid_len;
+               return ieee80211_request_scan(sdata, &local->int_scan_req);
        } else if (ifsta->state != IEEE80211_STA_MLME_IBSS_JOINED) {
                int interval = IEEE80211_SCAN_INTERVAL;
 
@@ -2478,11 +2493,16 @@ static int ieee80211_sta_config_auth(struct ieee80211_sub_if_data *sdata,
        } else {
                if (ifsta->assoc_scan_tries < IEEE80211_ASSOC_SCANS_MAX_TRIES) {
                        ifsta->assoc_scan_tries++;
+                       /* XXX maybe racy? */
+                       if (local->scan_req)
+                               return -1;
+                       memcpy(local->int_scan_req.ssids[0].ssid,
+                              ifsta->ssid, IEEE80211_MAX_SSID_LEN);
                        if (ifsta->flags & IEEE80211_STA_AUTO_SSID_SEL)
-                               ieee80211_start_scan(sdata, NULL, 0);
+                               local->int_scan_req.ssids[0].ssid_len = 0;
                        else
-                               ieee80211_start_scan(sdata, ifsta->ssid,
-                                                        ifsta->ssid_len);
+                               local->int_scan_req.ssids[0].ssid_len = ifsta->ssid_len;
+                       ieee80211_start_scan(sdata, &local->int_scan_req);
                        ifsta->state = IEEE80211_STA_MLME_AUTHENTICATE;
                        set_bit(IEEE80211_STA_REQ_AUTH, &ifsta->request);
                } else {
@@ -2520,8 +2540,7 @@ static void ieee80211_sta_work(struct work_struct *work)
            ifsta->state != IEEE80211_STA_MLME_AUTHENTICATE &&
            ifsta->state != IEEE80211_STA_MLME_ASSOCIATE &&
            test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request)) {
-               ieee80211_start_scan(sdata, ifsta->scan_ssid,
-                                    ifsta->scan_ssid_len);
+               ieee80211_start_scan(sdata, local->scan_req);
                return;
        }
 
index eddca4e1e13c2810646c02cc02b9ad0022972da6..c6b275b10cf93eb461fcf94c9e7aa5281e130eaa 100644 (file)
@@ -13,6 +13,9 @@
  */
 
 /* TODO:
+ * figure out how to avoid that the "current BSS" expires
+ * clean up IBSS code (in MLME), see why it adds a BSS to the list
+ * use cfg80211's BSS handling (depends on IBSS TODO above)
  * order BSS list by RSSI(?) ("quality of AP")
  * scan result table filtering (by capability (privacy, IBSS/BSS, WPA/RSN IE,
  *    SSID)
@@ -225,10 +228,26 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
                          struct ieee80211_mgmt *mgmt,
                          size_t len,
                          struct ieee802_11_elems *elems,
-                         int freq, bool beacon)
+                         struct ieee80211_channel *channel,
+                         bool beacon)
 {
        struct ieee80211_bss *bss;
-       int clen;
+       int clen, freq = channel->center_freq;
+       enum cfg80211_signal_type sigtype = CFG80211_SIGNAL_TYPE_NONE;
+       s32 signal = 0;
+
+       if (local->hw.flags & IEEE80211_HW_SIGNAL_DBM) {
+               sigtype = CFG80211_SIGNAL_TYPE_MBM;
+               signal = rx_status->signal * 100;
+       } else if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC) {
+               sigtype = CFG80211_SIGNAL_TYPE_UNSPEC;
+               signal = (rx_status->signal * 100) / local->hw.max_signal;
+       }
+
+       cfg80211_put_bss(
+               cfg80211_inform_bss_frame(local->hw.wiphy, channel,
+                                         mgmt, len, signal, sigtype,
+                                         GFP_ATOMIC));
 
 #ifdef CONFIG_MAC80211_MESH
        if (elems->mesh_config)
@@ -401,7 +420,7 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb,
 
        bss = ieee80211_bss_info_update(sdata->local, rx_status,
                                        mgmt, skb->len, &elems,
-                                       freq, beacon);
+                                       channel, beacon);
        if (bss)
                ieee80211_rx_bss_put(sdata->local, bss);
 
@@ -439,26 +458,22 @@ void ieee80211_send_nullfunc(struct ieee80211_local *local,
        ieee80211_tx_skb(sdata, skb, 0);
 }
 
-void ieee80211_scan_completed(struct ieee80211_hw *hw)
+void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
 {
        struct ieee80211_local *local = hw_to_local(hw);
        struct ieee80211_sub_if_data *sdata;
-       union iwreq_data wrqu;
 
        if (WARN_ON(!local->hw_scanning && !local->sw_scanning))
                return;
 
-       local->last_scan_completed = jiffies;
-       memset(&wrqu, 0, sizeof(wrqu));
+       if (WARN_ON(!local->scan_req))
+               return;
 
-       /*
-        * local->scan_sdata could have been NULLed by the interface
-        * down code in case we were scanning on an interface that is
-        * being taken down.
-        */
-       sdata = local->scan_sdata;
-       if (sdata)
-               wireless_send_event(sdata->dev, SIOCGIWSCAN, &wrqu, NULL);
+       if (local->scan_req != &local->int_scan_req)
+               cfg80211_scan_done(local->scan_req, aborted);
+       local->scan_req = NULL;
+
+       local->last_scan_completed = jiffies;
 
        if (local->hw_scanning) {
                local->hw_scanning = false;
@@ -520,9 +535,8 @@ void ieee80211_scan_work(struct work_struct *work)
        struct ieee80211_local *local =
                container_of(work, struct ieee80211_local, scan_work.work);
        struct ieee80211_sub_if_data *sdata = local->scan_sdata;
-       struct ieee80211_supported_band *sband;
        struct ieee80211_channel *chan;
-       int skip;
+       int skip, i;
        unsigned long next_delay = 0;
 
        /*
@@ -533,33 +547,13 @@ void ieee80211_scan_work(struct work_struct *work)
 
        switch (local->scan_state) {
        case SCAN_SET_CHANNEL:
-               /*
-                * Get current scan band. scan_band may be IEEE80211_NUM_BANDS
-                * after we successfully scanned the last channel of the last
-                * band (and the last band is supported by the hw)
-                */
-               if (local->scan_band < IEEE80211_NUM_BANDS)
-                       sband = local->hw.wiphy->bands[local->scan_band];
-               else
-                       sband = NULL;
-
-               /*
-                * If we are at an unsupported band and have more bands
-                * left to scan, advance to the next supported one.
-                */
-               while (!sband && local->scan_band < IEEE80211_NUM_BANDS - 1) {
-                       local->scan_band++;
-                       sband = local->hw.wiphy->bands[local->scan_band];
-                       local->scan_channel_idx = 0;
-               }
-
                /* if no more bands/channels left, complete scan */
-               if (!sband || local->scan_channel_idx >= sband->n_channels) {
-                       ieee80211_scan_completed(local_to_hw(local));
+               if (local->scan_channel_idx >= local->scan_req->n_channels) {
+                       ieee80211_scan_completed(local_to_hw(local), false);
                        return;
                }
                skip = 0;
-               chan = &sband->channels[local->scan_channel_idx];
+               chan = local->scan_req->channels[local->scan_channel_idx];
 
                if (chan->flags & IEEE80211_CHAN_DISABLED ||
                    (sdata->vif.type == NL80211_IFTYPE_ADHOC &&
@@ -575,15 +569,6 @@ void ieee80211_scan_work(struct work_struct *work)
 
                /* advance state machine to next channel/band */
                local->scan_channel_idx++;
-               if (local->scan_channel_idx >= sband->n_channels) {
-                       /*
-                        * scan_band may end up == IEEE80211_NUM_BANDS, but
-                        * we'll catch that case above and complete the scan
-                        * if that is the case.
-                        */
-                       local->scan_band++;
-                       local->scan_channel_idx = 0;
-               }
 
                if (skip)
                        break;
@@ -596,10 +581,14 @@ void ieee80211_scan_work(struct work_struct *work)
                next_delay = IEEE80211_PASSIVE_CHANNEL_TIME;
                local->scan_state = SCAN_SET_CHANNEL;
 
-               if (local->scan_channel->flags & IEEE80211_CHAN_PASSIVE_SCAN)
+               if (local->scan_channel->flags & IEEE80211_CHAN_PASSIVE_SCAN ||
+                   !local->scan_req->n_ssids)
                        break;
-               ieee80211_send_probe_req(sdata, NULL, local->scan_ssid,
-                                        local->scan_ssid_len);
+               for (i = 0; i < local->scan_req->n_ssids; i++)
+                       ieee80211_send_probe_req(
+                               sdata, NULL,
+                               local->scan_req->ssids[i].ssid,
+                               local->scan_req->ssids[i].ssid_len);
                next_delay = IEEE80211_CHANNEL_TIME;
                break;
        }
@@ -610,14 +599,19 @@ void ieee80211_scan_work(struct work_struct *work)
 
 
 int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata,
-                        u8 *ssid, size_t ssid_len)
+                        struct cfg80211_scan_request *req)
 {
        struct ieee80211_local *local = scan_sdata->local;
        struct ieee80211_sub_if_data *sdata;
 
-       if (ssid_len > IEEE80211_MAX_SSID_LEN)
+       if (!req)
                return -EINVAL;
 
+       if (local->scan_req && local->scan_req != req)
+               return -EBUSY;
+
+       local->scan_req = req;
+
        /* MLME-SCAN.request (page 118)  page 144 (11.1.3.1)
         * BSSType: INFRASTRUCTURE, INDEPENDENT, ANY_BSS
         * BSSID: MACAddress
@@ -645,7 +639,7 @@ int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata,
                int rc;
 
                local->hw_scanning = true;
-               rc = local->ops->hw_scan(local_to_hw(local), ssid, ssid_len);
+               rc = local->ops->hw_scan(local_to_hw(local), req);
                if (rc) {
                        local->hw_scanning = false;
                        return rc;
@@ -678,15 +672,10 @@ int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata,
        }
        mutex_unlock(&local->iflist_mtx);
 
-       if (ssid) {
-               local->scan_ssid_len = ssid_len;
-               memcpy(local->scan_ssid, ssid, ssid_len);
-       } else
-               local->scan_ssid_len = 0;
        local->scan_state = SCAN_SET_CHANNEL;
        local->scan_channel_idx = 0;
-       local->scan_band = IEEE80211_BAND_2GHZ;
        local->scan_sdata = scan_sdata;
+       local->scan_req = req;
 
        netif_addr_lock_bh(local->mdev);
        local->filter_flags |= FIF_BCN_PRBRESP_PROMISC;
@@ -706,13 +695,21 @@ int ieee80211_start_scan(struct ieee80211_sub_if_data *scan_sdata,
 
 
 int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata,
-                          u8 *ssid, size_t ssid_len)
+                          struct cfg80211_scan_request *req)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_if_sta *ifsta;
 
+       if (!req)
+               return -EINVAL;
+
+       if (local->scan_req && local->scan_req != req)
+               return -EBUSY;
+
+       local->scan_req = req;
+
        if (sdata->vif.type != NL80211_IFTYPE_STATION)
-               return ieee80211_start_scan(sdata, ssid, ssid_len);
+               return ieee80211_start_scan(sdata, req);
 
        /*
         * STA has a state machine that might need to defer scanning
@@ -727,241 +724,8 @@ int ieee80211_request_scan(struct ieee80211_sub_if_data *sdata,
        }
 
        ifsta = &sdata->u.sta;
-
-       ifsta->scan_ssid_len = ssid_len;
-       if (ssid_len)
-               memcpy(ifsta->scan_ssid, ssid, ssid_len);
        set_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request);
        queue_work(local->hw.workqueue, &ifsta->work);
 
        return 0;
 }
-
-
-static void ieee80211_scan_add_ies(struct iw_request_info *info,
-                                  struct ieee80211_bss *bss,
-                                  char **current_ev, char *end_buf)
-{
-       u8 *pos, *end, *next;
-       struct iw_event iwe;
-
-       if (bss == NULL || bss->ies == NULL)
-               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->ies;
-       end = pos + bss->ies_len;
-
-       while (end - pos > IW_GENERIC_IE_MAX) {
-               next = pos + 2 + pos[1];
-               while (next + 2 + next[1] - pos < IW_GENERIC_IE_MAX)
-                       next = next + 2 + next[1];
-
-               memset(&iwe, 0, sizeof(iwe));
-               iwe.cmd = IWEVGENIE;
-               iwe.u.data.length = next - pos;
-               *current_ev = iwe_stream_add_point(info, *current_ev,
-                                                  end_buf, &iwe, pos);
-
-               pos = next;
-       }
-
-       if (end > pos) {
-               memset(&iwe, 0, sizeof(iwe));
-               iwe.cmd = IWEVGENIE;
-               iwe.u.data.length = end - pos;
-               *current_ev = iwe_stream_add_point(info, *current_ev,
-                                                  end_buf, &iwe, pos);
-       }
-}
-
-
-static char *
-ieee80211_scan_result(struct ieee80211_local *local,
-                     struct iw_request_info *info,
-                     struct ieee80211_bss *bss,
-                     char *current_ev, char *end_buf)
-{
-       struct iw_event iwe;
-       char *buf;
-
-       if (time_after(jiffies,
-                      bss->last_update + IEEE80211_SCAN_RESULT_EXPIRE))
-               return current_ev;
-
-       memset(&iwe, 0, sizeof(iwe));
-       iwe.cmd = SIOCGIWAP;
-       iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
-       memcpy(iwe.u.ap_addr.sa_data, bss->bssid, ETH_ALEN);
-       current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
-                                         IW_EV_ADDR_LEN);
-
-       memset(&iwe, 0, sizeof(iwe));
-       iwe.cmd = SIOCGIWESSID;
-       if (bss_mesh_cfg(bss)) {
-               iwe.u.data.length = bss_mesh_id_len(bss);
-               iwe.u.data.flags = 1;
-               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
-                                                 &iwe, bss_mesh_id(bss));
-       } else {
-               iwe.u.data.length = bss->ssid_len;
-               iwe.u.data.flags = 1;
-               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
-                                                 &iwe, bss->ssid);
-       }
-
-       if (bss->capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)
-           || bss_mesh_cfg(bss)) {
-               memset(&iwe, 0, sizeof(iwe));
-               iwe.cmd = SIOCGIWMODE;
-               if (bss_mesh_cfg(bss))
-                       iwe.u.mode = IW_MODE_MESH;
-               else if (bss->capability & WLAN_CAPABILITY_ESS)
-                       iwe.u.mode = IW_MODE_MASTER;
-               else
-                       iwe.u.mode = IW_MODE_ADHOC;
-               current_ev = iwe_stream_add_event(info, current_ev, end_buf,
-                                                 &iwe, IW_EV_UINT_LEN);
-       }
-
-       memset(&iwe, 0, sizeof(iwe));
-       iwe.cmd = SIOCGIWFREQ;
-       iwe.u.freq.m = ieee80211_frequency_to_channel(bss->freq);
-       iwe.u.freq.e = 0;
-       current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
-                                         IW_EV_FREQ_LEN);
-
-       memset(&iwe, 0, sizeof(iwe));
-       iwe.cmd = SIOCGIWFREQ;
-       iwe.u.freq.m = bss->freq;
-       iwe.u.freq.e = 6;
-       current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
-                                         IW_EV_FREQ_LEN);
-       memset(&iwe, 0, sizeof(iwe));
-       iwe.cmd = IWEVQUAL;
-       iwe.u.qual.qual = bss->qual;
-       iwe.u.qual.level = bss->signal;
-       iwe.u.qual.noise = bss->noise;
-       iwe.u.qual.updated = local->wstats_flags;
-       current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
-                                         IW_EV_QUAL_LEN);
-
-       memset(&iwe, 0, sizeof(iwe));
-       iwe.cmd = SIOCGIWENCODE;
-       if (bss->capability & WLAN_CAPABILITY_PRIVACY)
-               iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
-       else
-               iwe.u.data.flags = IW_ENCODE_DISABLED;
-       iwe.u.data.length = 0;
-       current_ev = iwe_stream_add_point(info, current_ev, end_buf,
-                                         &iwe, "");
-
-       ieee80211_scan_add_ies(info, bss, &current_ev, end_buf);
-
-       if (bss->supp_rates_len > 0) {
-               /* display all supported rates in readable format */
-               char *p = current_ev + iwe_stream_lcp_len(info);
-               int i;
-
-               memset(&iwe, 0, sizeof(iwe));
-               iwe.cmd = SIOCGIWRATE;
-               /* Those two flags are ignored... */
-               iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
-
-               for (i = 0; i < bss->supp_rates_len; i++) {
-                       iwe.u.bitrate.value = ((bss->supp_rates[i] &
-                                                       0x7f) * 500000);
-                       p = iwe_stream_add_value(info, current_ev, p,
-                                       end_buf, &iwe, IW_EV_PARAM_LEN);
-               }
-               current_ev = p;
-       }
-
-       buf = kmalloc(30, GFP_ATOMIC);
-       if (buf) {
-               memset(&iwe, 0, sizeof(iwe));
-               iwe.cmd = IWEVCUSTOM;
-               sprintf(buf, "tsf=%016llx", (unsigned long long)(bss->timestamp));
-               iwe.u.data.length = strlen(buf);
-               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
-                                                 &iwe, buf);
-               memset(&iwe, 0, sizeof(iwe));
-               iwe.cmd = IWEVCUSTOM;
-               sprintf(buf, " Last beacon: %dms ago",
-                       jiffies_to_msecs(jiffies - bss->last_update));
-               iwe.u.data.length = strlen(buf);
-               current_ev = iwe_stream_add_point(info, current_ev,
-                                                 end_buf, &iwe, buf);
-               kfree(buf);
-       }
-
-       if (bss_mesh_cfg(bss)) {
-               u8 *cfg = bss_mesh_cfg(bss);
-               buf = kmalloc(50, GFP_ATOMIC);
-               if (buf) {
-                       memset(&iwe, 0, sizeof(iwe));
-                       iwe.cmd = IWEVCUSTOM;
-                       sprintf(buf, "Mesh network (version %d)", 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 Protocol ID: "
-                               "0x%02X%02X%02X%02X", cfg[1], cfg[2], cfg[3],
-                                                       cfg[4]);
-                       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]);
-                       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]);
-                       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]);
-                       iwe.u.data.length = strlen(buf);
-                       current_ev = iwe_stream_add_point(info, current_ev,
-                                                         end_buf,
-                                                         &iwe, buf);
-                       kfree(buf);
-               }
-       }
-
-       return current_ev;
-}
-
-
-int ieee80211_scan_results(struct ieee80211_local *local,
-                          struct iw_request_info *info,
-                          char *buf, size_t len)
-{
-       char *current_ev = buf;
-       char *end_buf = buf + len;
-       struct ieee80211_bss *bss;
-
-       spin_lock_bh(&local->bss_lock);
-       list_for_each_entry(bss, &local->bss_list, list) {
-               if (buf + len - current_ev <= IW_EV_ADDR_LEN) {
-                       spin_unlock_bh(&local->bss_lock);
-                       return -E2BIG;
-               }
-               current_ev = ieee80211_scan_result(local, info, bss,
-                                                      current_ev, end_buf);
-       }
-       spin_unlock_bh(&local->bss_lock);
-       return current_ev - buf;
-}
index acd5808b87f45bf2535158e6d4917e3fbd96c8b0..b337d7d5edb3b21dfebb365ac3ae825f77a6ad90 100644 (file)
@@ -173,8 +173,9 @@ static int ieee80211_ioctl_giwrange(struct net_device *dev,
        range->num_encoding_sizes = 2;
        range->max_encoding_tokens = NUM_DEFAULT_KEYS;
 
+       /* cfg80211 requires this, and enforces 0..100 */
        if (local->hw.flags & IEEE80211_HW_SIGNAL_UNSPEC)
-               range->max_qual.level = local->hw.max_signal;
+               range->max_qual.level = 100;
        else if  (local->hw.flags & IEEE80211_HW_SIGNAL_DBM)
                range->max_qual.level = -110;
        else
@@ -415,58 +416,6 @@ static int ieee80211_ioctl_giwap(struct net_device *dev,
 }
 
 
-static int ieee80211_ioctl_siwscan(struct net_device *dev,
-                                  struct iw_request_info *info,
-                                  union iwreq_data *wrqu, char *extra)
-{
-       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
-       struct iw_scan_req *req = NULL;
-       u8 *ssid = NULL;
-       size_t ssid_len = 0;
-
-       if (!netif_running(dev))
-               return -ENETDOWN;
-
-       if (sdata->vif.type != NL80211_IFTYPE_STATION &&
-           sdata->vif.type != NL80211_IFTYPE_ADHOC &&
-           sdata->vif.type != NL80211_IFTYPE_MESH_POINT)
-               return -EOPNOTSUPP;
-
-       /* if SSID was specified explicitly then use that */
-       if (wrqu->data.length == sizeof(struct iw_scan_req) &&
-           wrqu->data.flags & IW_SCAN_THIS_ESSID) {
-               req = (struct iw_scan_req *)extra;
-               ssid = req->essid;
-               ssid_len = req->essid_len;
-       }
-
-       return ieee80211_request_scan(sdata, ssid, ssid_len);
-}
-
-
-static int ieee80211_ioctl_giwscan(struct net_device *dev,
-                                  struct iw_request_info *info,
-                                  struct iw_point *data, char *extra)
-{
-       int res;
-       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
-       struct ieee80211_sub_if_data *sdata;
-
-       sdata = IEEE80211_DEV_TO_SUB_IF(dev);
-
-       if (local->sw_scanning || local->hw_scanning)
-               return -EAGAIN;
-
-       res = ieee80211_scan_results(local, info, extra, data->length);
-       if (res >= 0) {
-               data->length = res;
-               return 0;
-       }
-       data->length = 0;
-       return res;
-}
-
-
 static int ieee80211_ioctl_siwrate(struct net_device *dev,
                                  struct iw_request_info *info,
                                  struct iw_param *rate, char *extra)
@@ -1165,8 +1114,8 @@ static const iw_handler ieee80211_handler[] =
        (iw_handler) ieee80211_ioctl_giwap,             /* SIOCGIWAP */
        (iw_handler) ieee80211_ioctl_siwmlme,           /* SIOCSIWMLME */
        (iw_handler) NULL,                              /* SIOCGIWAPLIST */
-       (iw_handler) ieee80211_ioctl_siwscan,           /* SIOCSIWSCAN */
-       (iw_handler) ieee80211_ioctl_giwscan,           /* SIOCGIWSCAN */
+       (iw_handler) cfg80211_wext_siwscan,             /* SIOCSIWSCAN */
+       (iw_handler) cfg80211_wext_giwscan,             /* SIOCGIWSCAN */
        (iw_handler) ieee80211_ioctl_siwessid,          /* SIOCSIWESSID */
        (iw_handler) ieee80211_ioctl_giwessid,          /* SIOCGIWESSID */
        (iw_handler) NULL,                              /* SIOCSIWNICKN */
index 938a334c8dbc3ba6fe72afbd1b320f41bdd7c73f..dad43c24f695283b1faed4e579a5f0cb2abfd07f 100644 (file)
@@ -5,7 +5,7 @@ obj-$(CONFIG_LIB80211_CRYPT_WEP) += lib80211_crypt_wep.o
 obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o
 obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o
 
-cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o
+cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o
 cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o
 cfg80211-$(CONFIG_NL80211) += nl80211.o
 
index 12522647608917c400d2fb905f04863930f65bcc..3cccd1390cea6345af26cc985120236fe797a6f7 100644 (file)
@@ -240,6 +240,8 @@ struct wiphy *wiphy_new(struct cfg80211_ops *ops, int sizeof_priv)
        mutex_init(&drv->mtx);
        mutex_init(&drv->devlist_mtx);
        INIT_LIST_HEAD(&drv->netdev_list);
+       spin_lock_init(&drv->bss_lock);
+       INIT_LIST_HEAD(&drv->bss_list);
 
        device_initialize(&drv->wiphy.dev);
        drv->wiphy.dev.class = &ieee80211_class;
@@ -259,6 +261,9 @@ int wiphy_register(struct wiphy *wiphy)
        int i;
        u16 ifmodes = wiphy->interface_modes;
 
+       if (WARN_ON(wiphy->max_scan_ssids < 1))
+               return -EINVAL;
+
        /* sanity check ifmodes */
        WARN_ON(!ifmodes);
        ifmodes &= ((1 << __NL80211_IFTYPE_AFTER_LAST) - 1) & ~1;
@@ -367,8 +372,11 @@ EXPORT_SYMBOL(wiphy_unregister);
 
 void cfg80211_dev_free(struct cfg80211_registered_device *drv)
 {
+       struct cfg80211_internal_bss *scan, *tmp;
        mutex_destroy(&drv->mtx);
        mutex_destroy(&drv->devlist_mtx);
+       list_for_each_entry_safe(scan, tmp, &drv->bss_list, list)
+               kfree(scan);
        kfree(drv);
 }
 
index f7fb9f413028a7f4783e2d7ae1616ebc1dcfe311..e29ad4cd464f3587fca6f06ff6892f1a6a04fc6f 100644 (file)
@@ -8,6 +8,8 @@
 #include <linux/mutex.h>
 #include <linux/list.h>
 #include <linux/netdevice.h>
+#include <linux/kref.h>
+#include <linux/rbtree.h>
 #include <net/genetlink.h>
 #include <net/wireless.h>
 #include <net/cfg80211.h>
@@ -41,6 +43,13 @@ struct cfg80211_registered_device {
        struct mutex devlist_mtx;
        struct list_head netdev_list;
 
+       /* BSSes/scanning */
+       spinlock_t bss_lock;
+       struct list_head bss_list;
+       struct rb_root bss_tree;
+       u32 bss_generation;
+       struct cfg80211_scan_request *scan_req; /* protected by RTNL */
+
        /* must be last because of the way we do wiphy_priv(),
         * and it should at least be aligned to NETDEV_ALIGN */
        struct wiphy wiphy __attribute__((__aligned__(NETDEV_ALIGN)));
@@ -56,6 +65,15 @@ struct cfg80211_registered_device *wiphy_to_dev(struct wiphy *wiphy)
 extern struct mutex cfg80211_drv_mutex;
 extern struct list_head cfg80211_drv_list;
 
+struct cfg80211_internal_bss {
+       struct list_head list;
+       struct rb_node rbn;
+       unsigned long ts;
+       struct kref ref;
+       /* must be last because of priv member */
+       struct cfg80211_bss pub;
+};
+
 /*
  * This function returns a pointer to the driver
  * that the genl_info item that is passed refers to.
@@ -94,4 +112,6 @@ extern int cfg80211_dev_rename(struct cfg80211_registered_device *drv,
 void ieee80211_set_bitrate_flags(struct wiphy *wiphy);
 void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby);
 
+void cfg80211_bss_expire(struct cfg80211_registered_device *dev);
+
 #endif /* __NET_WIRELESS_CORE_H */
index d452396006eeff1852dc299229a76cc9d1f854bb..298a4de5994833b39380d7936e75dbf66e98a85b 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/nl80211.h>
 #include <linux/rtnetlink.h>
 #include <linux/netlink.h>
+#include <linux/etherdevice.h>
 #include <net/genetlink.h>
 #include <net/cfg80211.h>
 #include "core.h"
@@ -109,6 +110,8 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
        [NL80211_ATTR_MGMT_SUBTYPE] = { .type = NLA_U8 },
        [NL80211_ATTR_IE] = { .type = NLA_BINARY,
                              .len = IEEE80211_MAX_DATA_LEN },
+       [NL80211_ATTR_SCAN_FREQUENCIES] = { .type = NLA_NESTED },
+       [NL80211_ATTR_SCAN_SSIDS] = { .type = NLA_NESTED },
 };
 
 /* message building helper */
@@ -141,6 +144,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
 
        NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->idx);
        NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy));
+       NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
+                  dev->wiphy.max_scan_ssids);
 
        nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES);
        if (!nl_modes)
@@ -2270,6 +2275,246 @@ static int nl80211_set_mgmt_extra_ie(struct sk_buff *skb,
        return err;
 }
 
+static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       struct net_device *dev;
+       struct cfg80211_scan_request *request;
+       struct cfg80211_ssid *ssid;
+       struct ieee80211_channel *channel;
+       struct nlattr *attr;
+       struct wiphy *wiphy;
+       int err, tmp, n_ssids = 0, n_channels = 0, i;
+       enum ieee80211_band band;
+
+       err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
+       if (err)
+               return err;
+
+       wiphy = &drv->wiphy;
+
+       if (!drv->ops->scan) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       rtnl_lock();
+
+       if (drv->scan_req) {
+               err = -EBUSY;
+               goto out_unlock;
+       }
+
+       if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp)
+                       n_channels++;
+               if (!n_channels) {
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+       } else {
+               for (band = 0; band < IEEE80211_NUM_BANDS; band++)
+                       if (wiphy->bands[band])
+                               n_channels += wiphy->bands[band]->n_channels;
+       }
+
+       if (info->attrs[NL80211_ATTR_SCAN_SSIDS])
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp)
+                       n_ssids++;
+
+       if (n_ssids > wiphy->max_scan_ssids) {
+               err = -EINVAL;
+               goto out_unlock;
+       }
+
+       request = kzalloc(sizeof(*request)
+                       + sizeof(*ssid) * n_ssids
+                       + sizeof(channel) * n_channels, GFP_KERNEL);
+       if (!request) {
+               err = -ENOMEM;
+               goto out_unlock;
+       }
+
+       request->channels = (void *)((char *)request + sizeof(*request));
+       request->n_channels = n_channels;
+       if (n_ssids)
+               request->ssids = (void *)(request->channels + n_channels);
+       request->n_ssids = n_ssids;
+
+       if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) {
+               /* user specified, bail out if channel not found */
+               request->n_channels = n_channels;
+               i = 0;
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], tmp) {
+                       request->channels[i] = ieee80211_get_channel(wiphy, nla_get_u32(attr));
+                       if (!request->channels[i]) {
+                               err = -EINVAL;
+                               goto out_free;
+                       }
+                       i++;
+               }
+       } else {
+               /* all channels */
+               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++) {
+                               request->channels[i] = &wiphy->bands[band]->channels[j];
+                               i++;
+                       }
+               }
+       }
+
+       i = 0;
+       if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) {
+               nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], tmp) {
+                       if (request->ssids[i].ssid_len > IEEE80211_MAX_SSID_LEN) {
+                               err = -EINVAL;
+                               goto out_free;
+                       }
+                       memcpy(request->ssids[i].ssid, nla_data(attr), nla_len(attr));
+                       request->ssids[i].ssid_len = nla_len(attr);
+                       i++;
+               }
+       }
+
+       request->ifidx = dev->ifindex;
+       request->wiphy = &drv->wiphy;
+
+       drv->scan_req = request;
+       err = drv->ops->scan(&drv->wiphy, dev, request);
+
+ out_free:
+       if (err) {
+               drv->scan_req = NULL;
+               kfree(request);
+       }
+ out_unlock:
+       rtnl_unlock();
+ out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+       return err;
+}
+
+static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags,
+                           struct cfg80211_registered_device *rdev,
+                           struct net_device *dev,
+                           struct cfg80211_bss *res)
+{
+       void *hdr;
+       struct nlattr *bss;
+
+       hdr = nl80211hdr_put(msg, pid, seq, flags,
+                            NL80211_CMD_NEW_SCAN_RESULTS);
+       if (!hdr)
+               return -1;
+
+       NLA_PUT_U32(msg, NL80211_ATTR_SCAN_GENERATION,
+                   rdev->bss_generation);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+
+       bss = nla_nest_start(msg, NL80211_ATTR_BSS);
+       if (!bss)
+               goto nla_put_failure;
+       if (!is_zero_ether_addr(res->bssid))
+               NLA_PUT(msg, NL80211_BSS_BSSID, ETH_ALEN, res->bssid);
+       if (res->information_elements && res->len_information_elements)
+               NLA_PUT(msg, NL80211_BSS_INFORMATION_ELEMENTS,
+                       res->len_information_elements,
+                       res->information_elements);
+       if (res->tsf)
+               NLA_PUT_U64(msg, NL80211_BSS_TSF, res->tsf);
+       if (res->beacon_interval)
+               NLA_PUT_U16(msg, NL80211_BSS_BEACON_INTERVAL, res->beacon_interval);
+       NLA_PUT_U16(msg, NL80211_BSS_CAPABILITY, res->capability);
+       NLA_PUT_U32(msg, NL80211_BSS_FREQUENCY, res->channel->center_freq);
+
+       switch (res->signal_type) {
+       case CFG80211_SIGNAL_TYPE_MBM:
+               NLA_PUT_U32(msg, NL80211_BSS_SIGNAL_MBM, res->signal);
+               break;
+       case CFG80211_SIGNAL_TYPE_UNSPEC:
+               NLA_PUT_U8(msg, NL80211_BSS_SIGNAL_UNSPEC, res->signal);
+               break;
+       default:
+               break;
+       }
+
+       nla_nest_end(msg, bss);
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int nl80211_dump_scan(struct sk_buff *skb,
+                            struct netlink_callback *cb)
+{
+       struct cfg80211_registered_device *dev;
+       struct net_device *netdev;
+       struct cfg80211_internal_bss *scan;
+       int ifidx = cb->args[0];
+       int start = cb->args[1], idx = 0;
+       int err;
+
+       if (!ifidx) {
+               err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
+                                 nl80211_fam.attrbuf, nl80211_fam.maxattr,
+                                 nl80211_policy);
+               if (err)
+                       return err;
+
+               if (!nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX])
+                       return -EINVAL;
+
+               ifidx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_IFINDEX]);
+               if (!ifidx)
+                       return -EINVAL;
+               cb->args[0] = ifidx;
+       }
+
+       netdev = dev_get_by_index(&init_net, ifidx);
+       if (!netdev)
+               return -ENODEV;
+
+       dev = cfg80211_get_dev_from_ifindex(ifidx);
+       if (IS_ERR(dev)) {
+               err = PTR_ERR(dev);
+               goto out_put_netdev;
+       }
+
+       spin_lock_bh(&dev->bss_lock);
+       cfg80211_bss_expire(dev);
+
+       list_for_each_entry(scan, &dev->bss_list, list) {
+               if (++idx <= start)
+                       continue;
+               if (nl80211_send_bss(skb,
+                               NETLINK_CB(cb->skb).pid,
+                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                               dev, netdev, &scan->pub) < 0) {
+                       idx--;
+                       goto out;
+               }
+       }
+
+ out:
+       spin_unlock_bh(&dev->bss_lock);
+
+       cb->args[1] = idx;
+       err = skb->len;
+       cfg80211_put_dev(dev);
+ out_put_netdev:
+       dev_put(netdev);
+
+       return err;
+}
+
 static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_WIPHY,
@@ -2443,12 +2688,26 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = NL80211_CMD_TRIGGER_SCAN,
+               .doit = nl80211_trigger_scan,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_GET_SCAN,
+               .policy = nl80211_policy,
+               .dumpit = nl80211_dump_scan,
+       },
 };
 
 /* multicast groups */
 static struct genl_multicast_group nl80211_config_mcgrp = {
        .name = "config",
 };
+static struct genl_multicast_group nl80211_scan_mcgrp = {
+       .name = "scan",
+};
 
 /* notification functions */
 
@@ -2468,6 +2727,66 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
        genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL);
 }
 
+static int nl80211_send_scan_donemsg(struct sk_buff *msg,
+                                   struct cfg80211_registered_device *rdev,
+                                   struct net_device *netdev,
+                                   u32 pid, u32 seq, int flags,
+                                   u32 cmd)
+{
+       void *hdr;
+
+       hdr = nl80211hdr_put(msg, pid, seq, flags, cmd);
+       if (!hdr)
+               return -1;
+
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+
+       /* XXX: we should probably bounce back the request? */
+
+       return genlmsg_end(msg, hdr);
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+void nl80211_send_scan_done(struct cfg80211_registered_device *rdev,
+                           struct net_device *netdev)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0,
+                                     NL80211_CMD_NEW_SCAN_RESULTS) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL);
+}
+
+void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev,
+                              struct net_device *netdev)
+{
+       struct sk_buff *msg;
+
+       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       if (nl80211_send_scan_donemsg(msg, rdev, netdev, 0, 0, 0,
+                                     NL80211_CMD_SCAN_ABORTED) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL);
+}
+
 /* initialisation/exit functions */
 
 int nl80211_init(void)
@@ -2488,6 +2807,10 @@ int nl80211_init(void)
        if (err)
                goto err_out;
 
+       err = genl_register_mc_group(&nl80211_fam, &nl80211_scan_mcgrp);
+       if (err)
+               goto err_out;
+
        return 0;
  err_out:
        genl_unregister_family(&nl80211_fam);
index f3ea5c029aeeaf8e19342471b6933408e9a29cd5..b565a5f84e9782af4723956055c19f5ac972dbdf 100644 (file)
@@ -7,6 +7,10 @@
 extern int nl80211_init(void);
 extern void nl80211_exit(void);
 extern void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev);
+extern void nl80211_send_scan_done(struct cfg80211_registered_device *rdev,
+                                  struct net_device *netdev);
+extern void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev,
+                                     struct net_device *netdev);
 #else
 static inline int nl80211_init(void)
 {
@@ -19,6 +23,10 @@ static inline void nl80211_notify_dev_rename(
        struct cfg80211_registered_device *rdev)
 {
 }
+static inline void
+nl80211_send_scan_done(struct cfg80211_registered_device *rdev,
+                      struct net_device *netdev)
+{}
 #endif /* CONFIG_NL80211 */
 
 #endif /* __NET_WIRELESS_NL80211_H */
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
new file mode 100644 (file)
index 0000000..009d128
--- /dev/null
@@ -0,0 +1,807 @@
+/*
+ * cfg80211 scan result handling
+ *
+ * Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/wireless.h>
+#include <linux/nl80211.h>
+#include <linux/etherdevice.h>
+#include <net/arp.h>
+#include <net/cfg80211.h>
+#include <net/iw_handler.h>
+#include "core.h"
+#include "nl80211.h"
+
+#define IEEE80211_SCAN_RESULT_EXPIRE   (10 * HZ)
+
+void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted)
+{
+       struct net_device *dev;
+#ifdef CONFIG_WIRELESS_EXT
+       union iwreq_data wrqu;
+#endif
+
+       dev = dev_get_by_index(&init_net, request->ifidx);
+       if (!dev)
+               goto out;
+
+       WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req);
+       wiphy_to_dev(request->wiphy)->scan_req = NULL;
+
+       if (aborted)
+               nl80211_send_scan_aborted(wiphy_to_dev(request->wiphy), dev);
+       else
+               nl80211_send_scan_done(wiphy_to_dev(request->wiphy), dev);
+
+#ifdef CONFIG_WIRELESS_EXT
+       if (!aborted) {
+               memset(&wrqu, 0, sizeof(wrqu));
+
+               wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
+       }
+#endif
+
+       dev_put(dev);
+
+ out:
+       kfree(request);
+}
+EXPORT_SYMBOL(cfg80211_scan_done);
+
+static void bss_release(struct kref *ref)
+{
+       struct cfg80211_internal_bss *bss;
+
+       bss = container_of(ref, struct cfg80211_internal_bss, ref);
+       kfree(bss);
+}
+
+/* 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 (!time_after(jiffies, bss->ts + IEEE80211_SCAN_RESULT_EXPIRE))
+                       continue;
+               list_del(&bss->list);
+               rb_erase(&bss->rbn, &dev->bss_tree);
+               kref_put(&bss->ref, bss_release);
+               expired = true;
+       }
+
+       if (expired)
+               dev->bss_generation++;
+}
+
+static u8 *find_ie(u8 num, u8 *ies, size_t len)
+{
+       while (len > 2 && ies[0] != num) {
+               len -= ies[1] + 2;
+               ies += ies[1] + 2;
+       }
+       if (len < 2)
+               return NULL;
+       if (len < 2 + ies[1])
+               return NULL;
+       return ies;
+}
+
+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);
+       int r;
+
+       if (!ie1 && !ie2)
+               return 0;
+       if (!ie1)
+               return -1;
+
+       r = memcmp(ie1 + 2, ie2 + 2, min(ie1[1], ie2[1]));
+       if (r == 0 && ie1[1] != ie2[1])
+               return ie2[1] - ie1[1];
+       return r;
+}
+
+static bool is_bss(struct cfg80211_bss *a,
+                  const u8 *bssid,
+                  const u8 *ssid, size_t ssid_len)
+{
+       const u8 *ssidie;
+
+       if (compare_ether_addr(a->bssid, bssid))
+               return false;
+
+       ssidie = find_ie(WLAN_EID_SSID,
+                        a->information_elements,
+                        a->len_information_elements);
+       if (!ssidie)
+               return false;
+       if (ssidie[1] != ssid_len)
+               return false;
+       return memcmp(ssidie + 2, ssid, ssid_len) == 0;
+}
+
+static bool is_mesh(struct cfg80211_bss *a,
+                   const u8 *meshid, size_t meshidlen,
+                   const u8 *meshcfg)
+{
+       const u8 *ie;
+
+       if (!is_zero_ether_addr(a->bssid))
+               return false;
+
+       ie = find_ie(WLAN_EID_MESH_ID,
+                    a->information_elements,
+                    a->len_information_elements);
+       if (!ie)
+               return false;
+       if (ie[1] != meshidlen)
+               return false;
+       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)
+               return false;
+
+       /*
+        * Ignore mesh capability (last two bytes of the IE) when
+        * comparing since that may differ between stations taking
+        * part in the same mesh.
+        */
+       return memcmp(ie + 2, meshcfg, IEEE80211_MESH_CONFIG_LEN - 2) == 0;
+}
+
+static int cmp_bss(struct cfg80211_bss *a,
+                  struct cfg80211_bss *b)
+{
+       int r;
+
+       if (a->channel != b->channel)
+               return b->channel->center_freq - a->channel->center_freq;
+
+       r = memcmp(a->bssid, b->bssid, ETH_ALEN);
+       if (r)
+               return r;
+
+       if (is_zero_ether_addr(a->bssid)) {
+               r = cmp_ies(WLAN_EID_MESH_ID,
+                           a->information_elements,
+                           a->len_information_elements,
+                           b->information_elements,
+                           b->len_information_elements);
+               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);
+       }
+
+       return cmp_ies(WLAN_EID_SSID,
+                      a->information_elements,
+                      a->len_information_elements,
+                      b->information_elements,
+                      b->len_information_elements);
+}
+
+struct cfg80211_bss *cfg80211_get_bss(struct wiphy *wiphy,
+                                     struct ieee80211_channel *channel,
+                                     const u8 *bssid,
+                                     const u8 *ssid, size_t ssid_len)
+{
+       struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
+       struct cfg80211_internal_bss *bss, *res = NULL;
+
+       spin_lock_bh(&dev->bss_lock);
+
+       list_for_each_entry(bss, &dev->bss_list, list) {
+               if (channel && bss->pub.channel != channel)
+                       continue;
+               if (is_bss(&bss->pub, bssid, ssid, ssid_len)) {
+                       res = bss;
+                       kref_get(&res->ref);
+                       break;
+               }
+       }
+
+       spin_unlock_bh(&dev->bss_lock);
+       if (!res)
+               return NULL;
+       return &res->pub;
+}
+EXPORT_SYMBOL(cfg80211_get_bss);
+
+struct cfg80211_bss *cfg80211_get_mesh(struct wiphy *wiphy,
+                                      struct ieee80211_channel *channel,
+                                      const u8 *meshid, size_t meshidlen,
+                                      const u8 *meshcfg)
+{
+       struct cfg80211_registered_device *dev = wiphy_to_dev(wiphy);
+       struct cfg80211_internal_bss *bss, *res = NULL;
+
+       spin_lock_bh(&dev->bss_lock);
+
+       list_for_each_entry(bss, &dev->bss_list, list) {
+               if (channel && bss->pub.channel != channel)
+                       continue;
+               if (is_mesh(&bss->pub, meshid, meshidlen, meshcfg)) {
+                       res = bss;
+                       kref_get(&res->ref);
+                       break;
+               }
+       }
+
+       spin_unlock_bh(&dev->bss_lock);
+       if (!res)
+               return NULL;
+       return &res->pub;
+}
+EXPORT_SYMBOL(cfg80211_get_mesh);
+
+
+static void rb_insert_bss(struct cfg80211_registered_device *dev,
+                         struct cfg80211_internal_bss *bss)
+{
+       struct rb_node **p = &dev->bss_tree.rb_node;
+       struct rb_node *parent = NULL;
+       struct cfg80211_internal_bss *tbss;
+       int cmp;
+
+       while (*p) {
+               parent = *p;
+               tbss = rb_entry(parent, struct cfg80211_internal_bss, rbn);
+
+               cmp = cmp_bss(&bss->pub, &tbss->pub);
+
+               if (WARN_ON(!cmp)) {
+                       /* will sort of leak this BSS */
+                       return;
+               }
+
+               if (cmp < 0)
+                       p = &(*p)->rb_left;
+               else
+                       p = &(*p)->rb_right;
+       }
+
+       rb_link_node(&bss->rbn, parent, p);
+       rb_insert_color(&bss->rbn, &dev->bss_tree);
+}
+
+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 *
+cfg80211_bss_update(struct cfg80211_registered_device *dev,
+                   struct cfg80211_internal_bss *res,
+                   bool overwrite)
+{
+       struct cfg80211_internal_bss *found = NULL;
+       const u8 *meshid, *meshcfg;
+
+       /*
+        * The reference to "res" is donated to this function.
+        */
+
+       if (WARN_ON(!res->pub.channel)) {
+               kref_put(&res->ref, bss_release);
+               return NULL;
+       }
+
+       res->ts = jiffies;
+
+       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);
+               if (!meshid || !meshcfg ||
+                   meshcfg[1] != IEEE80211_MESH_CONFIG_LEN) {
+                       /* bogus mesh */
+                       kref_put(&res->ref, bss_release);
+                       return NULL;
+               }
+       }
+
+       spin_lock_bh(&dev->bss_lock);
+
+       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);
+               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;
+               kref_put(&res->ref, bss_release);
+       } else {
+               /* this "consumes" the reference */
+               list_add_tail(&res->list, &dev->bss_list);
+               rb_insert_bss(dev, res);
+               found = res;
+       }
+
+       dev->bss_generation++;
+       spin_unlock_bh(&dev->bss_lock);
+
+       kref_get(&found->ref);
+       return found;
+}
+
+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)
+{
+       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 &&
+                   (signal < 0 || signal > 100)))
+               return NULL;
+
+       if (WARN_ON(!mgmt || !wiphy ||
+                   len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable)))
+               return NULL;
+
+       res = kzalloc(sizeof(*res) + privsz + ielen, gfp);
+       if (!res)
+               return NULL;
+
+       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;
+
+       kref_init(&res->ref);
+
+       overwrite = ieee80211_is_probe_resp(mgmt->frame_control);
+
+       res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, overwrite);
+       if (!res)
+               return NULL;
+
+       /* cfg80211_bss_update gives us a referenced result */
+       return &res->pub;
+}
+EXPORT_SYMBOL(cfg80211_inform_bss_frame);
+
+void cfg80211_put_bss(struct cfg80211_bss *pub)
+{
+       struct cfg80211_internal_bss *bss;
+
+       if (!pub)
+               return;
+
+       bss = container_of(pub, struct cfg80211_internal_bss, pub);
+       kref_put(&bss->ref, bss_release);
+}
+EXPORT_SYMBOL(cfg80211_put_bss);
+
+#ifdef CONFIG_WIRELESS_EXT
+int cfg80211_wext_siwscan(struct net_device *dev,
+                         struct iw_request_info *info,
+                         union iwreq_data *wrqu, char *extra)
+{
+       struct cfg80211_registered_device *rdev;
+       struct wiphy *wiphy;
+       struct iw_scan_req *wreq = NULL;
+       struct cfg80211_scan_request *creq;
+       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 (IS_ERR(rdev))
+               return PTR_ERR(rdev);
+
+       if (rdev->scan_req) {
+               err = -EBUSY;
+               goto out;
+       }
+
+       wiphy = &rdev->wiphy;
+
+       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 *),
+                      GFP_ATOMIC);
+       if (!creq) {
+               err = -ENOMEM;
+               goto out;
+       }
+
+       creq->wiphy = wiphy;
+       creq->ifidx = dev->ifindex;
+       creq->ssids = (void *)(creq + 1);
+       creq->channels = (void *)(creq->ssids + 1);
+       creq->n_channels = n_channels;
+       creq->n_ssids = 1;
+
+       /* all channels */
+       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++) {
+                       creq->channels[i] = &wiphy->bands[band]->channels[j];
+                       i++;
+               }
+       }
+
+       /* translate scan request */
+       if (wrqu->data.length == sizeof(struct iw_scan_req)) {
+               wreq = (struct iw_scan_req *)extra;
+
+               if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
+                       if (wreq->essid_len > IEEE80211_MAX_SSID_LEN)
+                               return -EINVAL;
+                       memcpy(creq->ssids[0].ssid, wreq->essid, wreq->essid_len);
+                       creq->ssids[0].ssid_len = wreq->essid_len;
+               }
+               if (wreq->scan_type == IW_SCAN_TYPE_PASSIVE)
+                       creq->n_ssids = 0;
+       }
+
+       rdev->scan_req = creq;
+       err = rdev->ops->scan(wiphy, dev, creq);
+       if (err) {
+               rdev->scan_req = NULL;
+               kfree(creq);
+       }
+ out:
+       cfg80211_put_dev(rdev);
+       return err;
+}
+EXPORT_SYMBOL(cfg80211_wext_siwscan);
+
+static void ieee80211_scan_add_ies(struct iw_request_info *info,
+                                  struct cfg80211_bss *bss,
+                                  char **current_ev, char *end_buf)
+{
+       u8 *pos, *end, *next;
+       struct iw_event iwe;
+
+       if (!bss->information_elements ||
+           !bss->len_information_elements)
+               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;
+
+       while (end - pos > IW_GENERIC_IE_MAX) {
+               next = pos + 2 + pos[1];
+               while (next + 2 + next[1] - pos < IW_GENERIC_IE_MAX)
+                       next = next + 2 + next[1];
+
+               memset(&iwe, 0, sizeof(iwe));
+               iwe.cmd = IWEVGENIE;
+               iwe.u.data.length = next - pos;
+               *current_ev = iwe_stream_add_point(info, *current_ev,
+                                                  end_buf, &iwe, pos);
+
+               pos = next;
+       }
+
+       if (end > pos) {
+               memset(&iwe, 0, sizeof(iwe));
+               iwe.cmd = IWEVGENIE;
+               iwe.u.data.length = end - pos;
+               *current_ev = iwe_stream_add_point(info, *current_ev,
+                                                  end_buf, &iwe, pos);
+       }
+}
+
+
+static char *
+ieee80211_bss(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;
+       bool ismesh = false;
+
+       memset(&iwe, 0, sizeof(iwe));
+       iwe.cmd = SIOCGIWAP;
+       iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
+       memcpy(iwe.u.ap_addr.sa_data, bss->pub.bssid, ETH_ALEN);
+       current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
+                                         IW_EV_ADDR_LEN);
+
+       memset(&iwe, 0, sizeof(iwe));
+       iwe.cmd = SIOCGIWFREQ;
+       iwe.u.freq.m = ieee80211_frequency_to_channel(bss->pub.channel->center_freq);
+       iwe.u.freq.e = 0;
+       current_ev = iwe_stream_add_event(info, current_ev, end_buf, &iwe,
+                                         IW_EV_FREQ_LEN);
+
+       memset(&iwe, 0, sizeof(iwe));
+       iwe.cmd = SIOCGIWFREQ;
+       iwe.u.freq.m = bss->pub.channel->center_freq;
+       iwe.u.freq.e = 6;
+       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) {
+               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) {
+               case CFG80211_SIGNAL_TYPE_MBM:
+                       iwe.u.qual.level = bss->pub.signal / 100;
+                       iwe.u.qual.updated |= IW_QUAL_DBM;
+                       break;
+               case CFG80211_SIGNAL_TYPE_UNSPEC:
+                       iwe.u.qual.level = bss->pub.signal;
+                       break;
+               default:
+                       /* not reached */
+                       break;
+               }
+               current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                                 &iwe, IW_EV_QUAL_LEN);
+       }
+
+       memset(&iwe, 0, sizeof(iwe));
+       iwe.cmd = SIOCGIWENCODE;
+       if (bss->pub.capability & WLAN_CAPABILITY_PRIVACY)
+               iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
+       else
+               iwe.u.data.flags = IW_ENCODE_DISABLED;
+       iwe.u.data.length = 0;
+       current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                         &iwe, "");
+
+       while (rem >= 2) {
+               /* invalid data */
+               if (ie[1] > rem - 2)
+                       break;
+
+               switch (ie[0]) {
+               case WLAN_EID_SSID:
+                       memset(&iwe, 0, sizeof(iwe));
+                       iwe.cmd = SIOCGIWESSID;
+                       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);
+                       break;
+               case WLAN_EID_MESH_ID:
+                       memset(&iwe, 0, sizeof(iwe));
+                       iwe.cmd = SIOCGIWESSID;
+                       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);
+                       break;
+               case WLAN_EID_MESH_CONFIG:
+                       ismesh = true;
+                       if (ie[1] != IEEE80211_MESH_CONFIG_LEN)
+                               break;
+                       buf = kmalloc(50, GFP_ATOMIC);
+                       if (!buf)
+                               break;
+                       cfg = ie + 2;
+                       memset(&iwe, 0, sizeof(iwe));
+                       iwe.cmd = IWEVCUSTOM;
+                       sprintf(buf, "Mesh network (version %d)", 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 Protocol ID: "
+                               "0x%02X%02X%02X%02X", cfg[1], cfg[2], cfg[3],
+                                                       cfg[4]);
+                       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]);
+                       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]);
+                       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]);
+                       iwe.u.data.length = strlen(buf);
+                       current_ev = iwe_stream_add_point(info, current_ev,
+                                                         end_buf,
+                                                         &iwe, buf);
+                       kfree(buf);
+                       break;
+               case WLAN_EID_SUPP_RATES:
+               case WLAN_EID_EXT_SUPP_RATES:
+                       /* display all supported rates in readable format */
+                       p = current_ev + iwe_stream_lcp_len(info);
+
+                       memset(&iwe, 0, sizeof(iwe));
+                       iwe.cmd = SIOCGIWRATE;
+                       /* Those two flags are ignored... */
+                       iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
+
+                       for (i = 0; i < ie[1]; i++) {
+                               iwe.u.bitrate.value =
+                                       ((ie[i + 2] & 0x7f) * 500000);
+                               p = iwe_stream_add_value(info, current_ev, p,
+                                               end_buf, &iwe, IW_EV_PARAM_LEN);
+                       }
+                       current_ev = p;
+                       break;
+               }
+               rem -= ie[1] + 2;
+               ie += ie[1] + 2;
+       }
+
+       if (bss->pub.capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)
+           || ismesh) {
+               memset(&iwe, 0, sizeof(iwe));
+               iwe.cmd = SIOCGIWMODE;
+               if (ismesh)
+                       iwe.u.mode = IW_MODE_MESH;
+               else if (bss->pub.capability & WLAN_CAPABILITY_ESS)
+                       iwe.u.mode = IW_MODE_MASTER;
+               else
+                       iwe.u.mode = IW_MODE_ADHOC;
+               current_ev = iwe_stream_add_event(info, current_ev, end_buf,
+                                                 &iwe, IW_EV_UINT_LEN);
+       }
+
+       buf = kmalloc(30, GFP_ATOMIC);
+       if (buf) {
+               memset(&iwe, 0, sizeof(iwe));
+               iwe.cmd = IWEVCUSTOM;
+               sprintf(buf, "tsf=%016llx", (unsigned long long)(bss->pub.tsf));
+               iwe.u.data.length = strlen(buf);
+               current_ev = iwe_stream_add_point(info, current_ev, end_buf,
+                                                 &iwe, buf);
+               memset(&iwe, 0, sizeof(iwe));
+               iwe.cmd = IWEVCUSTOM;
+               sprintf(buf, " Last beacon: %dms ago",
+                       jiffies_to_msecs(jiffies - bss->ts));
+               iwe.u.data.length = strlen(buf);
+               current_ev = iwe_stream_add_point(info, current_ev,
+                                                 end_buf, &iwe, buf);
+               kfree(buf);
+       }
+
+       ieee80211_scan_add_ies(info, &bss->pub, &current_ev, end_buf);
+
+       return current_ev;
+}
+
+
+static int ieee80211_scan_results(struct cfg80211_registered_device *dev,
+                                 struct iw_request_info *info,
+                                 char *buf, size_t len)
+{
+       char *current_ev = buf;
+       char *end_buf = buf + len;
+       struct cfg80211_internal_bss *bss;
+
+       spin_lock_bh(&dev->bss_lock);
+       cfg80211_bss_expire(dev);
+
+       list_for_each_entry(bss, &dev->bss_list, list) {
+               if (buf + len - current_ev <= IW_EV_ADDR_LEN) {
+                       spin_unlock_bh(&dev->bss_lock);
+                       return -E2BIG;
+               }
+               current_ev = ieee80211_bss(info, bss,
+                                                  current_ev, end_buf);
+       }
+       spin_unlock_bh(&dev->bss_lock);
+       return current_ev - buf;
+}
+
+
+int cfg80211_wext_giwscan(struct net_device *dev,
+                         struct iw_request_info *info,
+                         struct iw_point *data, char *extra)
+{
+       struct cfg80211_registered_device *rdev;
+       int res;
+
+       if (!netif_running(dev))
+               return -ENETDOWN;
+
+       rdev = cfg80211_get_dev_from_ifindex(dev->ifindex);
+
+       if (IS_ERR(rdev))
+               return PTR_ERR(rdev);
+
+       if (rdev->scan_req) {
+               res = -EAGAIN;
+               goto out;
+       }
+
+       res = ieee80211_scan_results(rdev, info, extra, data->length);
+       data->length = 0;
+       if (res >= 0) {
+               data->length = res;
+               res = 0;
+       }
+
+ out:
+       cfg80211_put_dev(rdev);
+       return res;
+}
+EXPORT_SYMBOL(cfg80211_wext_giwscan);
+#endif